homebridge-ratgdo 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,22 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ratgdoAccessory = void 0;
7
- const settings_js_1 = require("./settings.js");
8
- const ratgdo_options_js_1 = require("./ratgdo-options.js");
9
- const node_util_1 = __importDefault(require("node:util"));
10
- class ratgdoAccessory {
1
+ import { RATGDO_MOTION_DURATION, RATGDO_OCCUPANCY_DURATION, RATGDO_TRANSITION_DURATION } from "./settings.js";
2
+ import { RatgdoReservedNames } from "./ratgdo-types.js";
3
+ import { getOptionFloat, getOptionNumber, getOptionValue, isOptionEnabled } from "./ratgdo-options.js";
4
+ import util from "node:util";
5
+ export class RatgdoAccessory {
6
+ accessory;
7
+ api;
8
+ config;
9
+ device;
10
+ doorOccupancyTimer;
11
+ doorTimer;
12
+ hap;
13
+ hints;
14
+ log;
15
+ motionOccupancyTimer;
16
+ motionTimer;
17
+ obstructionTimer;
18
+ platform;
19
+ status;
11
20
  // The constructor initializes key variables and calls configureDevice().
12
21
  constructor(platform, accessory, device) {
13
22
  this.accessory = accessory;
@@ -20,10 +29,10 @@ class ratgdoAccessory {
20
29
  this.device = device;
21
30
  this.platform = platform;
22
31
  this.log = {
23
- debug: (message, ...parameters) => platform.debug(node_util_1.default.format(this.name + ": " + message, ...parameters)),
24
- error: (message, ...parameters) => platform.log.error(node_util_1.default.format(this.name + ": " + message, ...parameters)),
25
- info: (message, ...parameters) => platform.log.info(node_util_1.default.format(this.name + ": " + message, ...parameters)),
26
- warn: (message, ...parameters) => platform.log.warn(node_util_1.default.format(this.name + ": " + message, ...parameters))
32
+ debug: (message, ...parameters) => platform.debug(util.format(this.name + ": " + message, ...parameters)),
33
+ error: (message, ...parameters) => platform.log.error(util.format(this.name + ": " + message, ...parameters)),
34
+ info: (message, ...parameters) => platform.log.info(util.format(this.name + ": " + message, ...parameters)),
35
+ warn: (message, ...parameters) => platform.log.warn(util.format(this.name + ": " + message, ...parameters))
27
36
  };
28
37
  // Initialize our internal state.
29
38
  this.status.availability = false;
@@ -32,9 +41,10 @@ class ratgdoAccessory {
32
41
  this.status.lock = this.hap.Characteristic.LockCurrentState.UNSECURED;
33
42
  this.status.motion = false;
34
43
  this.status.obstruction = false;
44
+ this.doorOccupancyTimer = null;
45
+ this.motionOccupancyTimer = null;
35
46
  this.motionTimer = null;
36
47
  this.obstructionTimer = null;
37
- this.occupancyTimer = null;
38
48
  this.configureDevice();
39
49
  }
40
50
  // Configure a garage door accessory for HomeKit.
@@ -45,40 +55,102 @@ class ratgdoAccessory {
45
55
  this.configureHints();
46
56
  this.configureInfo();
47
57
  this.configureGarageDoor();
58
+ this.configureMqtt();
59
+ this.configureAutomationSwitch();
60
+ this.configureDoorOpenOccupancySensor();
48
61
  this.configureLight();
49
62
  this.configureMotionSensor();
50
- // this.configureSwitch();
51
- // this.configureOccupancySensor();
63
+ this.configureMotionOccupancySensor();
52
64
  }
53
65
  // Configure device-specific settings.
54
66
  configureHints() {
55
67
  this.hints.automationSwitch = this.hasFeature("Opener.Switch");
56
- this.hints.occupancySensor = this.hasFeature("Opener.OccupancySensor");
57
- // this.hints.occupancyDuration = this.getFeatureNumber("Opener.OccupancySensor.Duration") ?? RATGDO_OCCUPANCY_DURATION;
68
+ this.hints.doorOpenOccupancySensor = this.hasFeature("Opener.OccupancySensor");
69
+ this.hints.doorOpenOccupancyDuration = this.getFeatureNumber("Opener.OccupancySensor.Duration") ?? RATGDO_OCCUPANCY_DURATION;
70
+ this.hints.light = this.hasFeature("Light");
71
+ this.hints.motionOccupancySensor = this.hasFeature("Motion.OccupancySensor");
72
+ this.hints.motionOccupancyDuration = this.getFeatureNumber("Motion.OccupancySensor.Duration") ?? RATGDO_OCCUPANCY_DURATION;
73
+ this.hints.motionSensor = this.hasFeature("Motion");
58
74
  this.hints.readOnly = this.hasFeature("Opener.ReadOnly");
59
- this.hints.syncNames = this.hasFeature("Device.SyncNames");
75
+ this.hints.syncName = this.hasFeature("Device.SyncName");
60
76
  return true;
61
77
  }
62
78
  // Configure the device information for HomeKit.
63
79
  configureInfo() {
64
- var _a, _b, _c, _d;
65
80
  // Update the manufacturer information for this device.
66
- (_a = this.accessory.getService(this.hap.Service.AccessoryInformation)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.Manufacturer, "Liftmaster");
81
+ this.accessory.getService(this.hap.Service.AccessoryInformation)?.updateCharacteristic(this.hap.Characteristic.Manufacturer, "github.com/hjdhjd");
67
82
  // Update the model information for this device.
68
- (_b = this.accessory.getService(this.hap.Service.AccessoryInformation)) === null || _b === void 0 ? void 0 : _b.updateCharacteristic(this.hap.Characteristic.Model, "Ratgdo");
83
+ this.accessory.getService(this.hap.Service.AccessoryInformation)?.updateCharacteristic(this.hap.Characteristic.Model, "Ratgdo");
69
84
  // Update the serial number for this device.
70
- (_c = this.accessory.getService(this.hap.Service.AccessoryInformation)) === null || _c === void 0 ? void 0 : _c.updateCharacteristic(this.hap.Characteristic.SerialNumber, this.device.mac.replace(/:/g, ""));
85
+ this.accessory.getService(this.hap.Service.AccessoryInformation)?.updateCharacteristic(this.hap.Characteristic.SerialNumber, this.device.mac);
71
86
  // Update the firmware information for this device.
72
- (_d = this.accessory.getService(this.hap.Service.AccessoryInformation)) === null || _d === void 0 ? void 0 : _d.updateCharacteristic(this.hap.Characteristic.FirmwareRevision, this.device.firmwareVersion);
87
+ this.accessory.getService(this.hap.Service.AccessoryInformation)?.updateCharacteristic(this.hap.Characteristic.FirmwareRevision, this.device.firmwareVersion);
88
+ return true;
89
+ }
90
+ // Configure MQTT services.
91
+ configureMqtt() {
92
+ // Return our garage door state.
93
+ this.platform.mqtt?.subscribeGet(this, "garagedoor", "Garage Door", () => {
94
+ // Return our current status using our HomeKit current state decoder ring.
95
+ return this.translateCurrentDoorState(this.status.door);
96
+ });
97
+ // Set our garage door state.
98
+ this.platform.mqtt?.subscribeSet(this, "garagedoor", "Garage Door", (value) => {
99
+ let command;
100
+ switch (value) {
101
+ case "close":
102
+ command = this.hap.Characteristic.TargetDoorState.CLOSED;
103
+ break;
104
+ case "open":
105
+ command = this.hap.Characteristic.TargetDoorState.OPEN;
106
+ break;
107
+ default:
108
+ this.log.error("Invalid command.");
109
+ return;
110
+ break;
111
+ }
112
+ // Set our door state accordingly.
113
+ this.setDoorState(command);
114
+ });
115
+ // Return our obstruction state.
116
+ this.platform.mqtt?.subscribeGet(this, "obstruction", "Obstruction", () => {
117
+ return this.status.obstruction.toString();
118
+ });
119
+ // Return our door open occupancy state if configured to do so.
120
+ if (this.hints.doorOpenOccupancySensor) {
121
+ this.platform.mqtt?.subscribeGet(this, "dooropenoccupancy", "Door Open Indicator Occupancy", () => {
122
+ return (this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_DOOR_OPEN)
123
+ ?.getCharacteristic(this.hap.Characteristic.OccupancyDetected).value ?? "false").toString();
124
+ });
125
+ }
126
+ // Return our light state if configured to do so.
127
+ if (this.hints.light) {
128
+ this.platform.mqtt?.subscribeGet(this, "light", "Light", () => {
129
+ return this.status.light.toString();
130
+ });
131
+ }
132
+ // Return our motion occupancy state if configured to do so.
133
+ if (this.hints.motionOccupancySensor) {
134
+ this.platform.mqtt?.subscribeGet(this, "occupancy", "Occupancy", () => {
135
+ return (this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_MOTION)
136
+ ?.getCharacteristic(this.hap.Characteristic.OccupancyDetected).value ?? "false").toString();
137
+ });
138
+ }
139
+ // Return our motion state if configured to do so.
140
+ if (this.hints.motionSensor) {
141
+ this.platform.mqtt?.subscribeGet(this, "motion", "Motion", () => {
142
+ return this.status.motion.toString();
143
+ });
144
+ }
73
145
  return true;
74
146
  }
75
147
  // Configure the garage door service for HomeKit.
76
148
  configureGarageDoor() {
77
- var _a;
78
149
  let garageDoorService = this.accessory.getService(this.hap.Service.GarageDoorOpener);
79
150
  // Add the garage door opener service to the accessory, if needed.
80
151
  if (!garageDoorService) {
81
152
  garageDoorService = new this.hap.Service.GarageDoorOpener(this.name);
153
+ garageDoorService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
82
154
  this.accessory.addService(garageDoorService);
83
155
  }
84
156
  // Set the initial current and target door states to closed since ratgdo doesn't tell us initial state over MQTT on startup.
@@ -92,23 +164,30 @@ class ratgdoAccessory {
92
164
  garageDoorService.getCharacteristic(this.hap.Characteristic.CurrentDoorState).onGet(() => this.status.door);
93
165
  // Inform HomeKit of any obstructions.
94
166
  garageDoorService.getCharacteristic(this.hap.Characteristic.ObstructionDetected).onGet(() => this.status.obstruction === true);
95
- // Add the lock garage door lock current and target state characteristics.
96
- garageDoorService.addOptionalCharacteristic(this.hap.Characteristic.LockCurrentState);
97
- garageDoorService.addOptionalCharacteristic(this.hap.Characteristic.LockTargetState);
167
+ // Configure the lock garage door lock current and target state characteristics.
98
168
  garageDoorService.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.status.lock);
99
169
  garageDoorService.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.lockTargetStateBias(this.status.lock));
100
- // Add the configured name for this device.
101
- garageDoorService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
102
- // switchService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, switchName);
103
170
  // Update our configured name, if requested.
104
- if (this.hints.syncNames) {
105
- garageDoorService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.device.name);
106
- if (this.hints.occupancySensor) {
107
- (_a = this.accessory.getService(this.hap.Service.OccupancySensor)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.device.name + " Open");
171
+ if (this.hints.syncName) {
172
+ this.setServiceName(garageDoorService, this.device.name);
173
+ if (this.hints.automationSwitch) {
174
+ this.setServiceName(this.accessory.getServiceById(this.hap.Service.Switch, RatgdoReservedNames.SWITCH_OPENER_AUTOMATION), this.device.name + " Automation Switch");
175
+ }
176
+ if (this.hints.doorOpenOccupancySensor) {
177
+ this.setServiceName(this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_DOOR_OPEN), this.device.name + " Open");
108
178
  }
179
+ if (this.hints.light) {
180
+ this.setServiceName(this.accessory.getService(this.hap.Service.Lightbulb), this.device.name);
181
+ }
182
+ if (this.hints.motionSensor) {
183
+ this.setServiceName(this.accessory.getService(this.hap.Service.MotionSensor), this.device.name);
184
+ }
185
+ if (this.hints.motionOccupancySensor) {
186
+ this.setServiceName(this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_MOTION), this.device.name);
187
+ }
188
+ // Finally, update the accessory name.
189
+ this.accessoryName = this.device.name;
109
190
  }
110
- // Add our status active characteristic.
111
- garageDoorService.addOptionalCharacteristic(this.hap.Characteristic.StatusActive);
112
191
  garageDoorService.getCharacteristic(this.hap.Characteristic.StatusActive).onGet(() => this.status.availability);
113
192
  garageDoorService.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
114
193
  // Let HomeKit know that this is the primary service on this accessory.
@@ -117,9 +196,16 @@ class ratgdoAccessory {
117
196
  }
118
197
  // Configure the light for HomeKit.
119
198
  configureLight() {
120
- var _a, _b;
121
199
  // Find the service, if it exists.
122
200
  let lightService = this.accessory.getService(this.hap.Service.Lightbulb);
201
+ // Have we disabled the light?
202
+ if (!this.hints.light) {
203
+ if (lightService) {
204
+ this.accessory.removeService(lightService);
205
+ this.log.info("Disabling light.");
206
+ }
207
+ return false;
208
+ }
123
209
  // Add the service to the accessory, if needed.
124
210
  if (!lightService) {
125
211
  lightService = new this.hap.Service.Lightbulb(this.name);
@@ -127,24 +213,34 @@ class ratgdoAccessory {
127
213
  this.log.error("Unable to add the light.");
128
214
  return false;
129
215
  }
216
+ lightService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
130
217
  this.accessory.addService(lightService);
131
218
  this.log.info("Enabling light.");
132
219
  }
133
- // Turn the light on or off.
134
- (_a = lightService.getCharacteristic(this.hap.Characteristic.On)) === null || _a === void 0 ? void 0 : _a.onGet(() => this.status.light);
135
- (_b = lightService.getCharacteristic(this.hap.Characteristic.On)) === null || _b === void 0 ? void 0 : _b.onSet((value) => {
136
- this.command("light", value === true ? "on" : "off");
137
- });
138
220
  // Initialize the light.
139
221
  lightService.displayName = this.name;
140
222
  lightService.updateCharacteristic(this.hap.Characteristic.Name, this.name);
223
+ lightService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.name);
141
224
  lightService.updateCharacteristic(this.hap.Characteristic.On, this.status.light);
225
+ // Turn the light on or off.
226
+ lightService.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => this.status.light);
227
+ lightService.getCharacteristic(this.hap.Characteristic.On)?.onSet((value) => {
228
+ this.command("light", value === true ? "on" : "off");
229
+ });
142
230
  return true;
143
231
  }
144
232
  // Configure the motion sensor for HomeKit.
145
233
  configureMotionSensor() {
146
234
  // Find the motion sensor service, if it exists.
147
235
  let motionService = this.accessory.getService(this.hap.Service.MotionSensor);
236
+ // Have we disabled the motion sensor?
237
+ if (!this.hints.motionSensor) {
238
+ if (motionService) {
239
+ this.accessory.removeService(motionService);
240
+ this.log.info("Disabling motion sensor.");
241
+ }
242
+ return false;
243
+ }
148
244
  // We don't have a motion sensor, let's add it to the device.
149
245
  if (!motionService) {
150
246
  // We don't have it, add the motion sensor to the device.
@@ -153,17 +249,132 @@ class ratgdoAccessory {
153
249
  this.log.error("Unable to add the motion sensor.");
154
250
  return false;
155
251
  }
252
+ motionService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
156
253
  this.accessory.addService(motionService);
157
254
  this.log.info("Enabling motion sensor.");
158
255
  }
159
256
  // Initialize the state of the motion sensor.
160
257
  motionService.displayName = this.name;
161
258
  motionService.updateCharacteristic(this.hap.Characteristic.Name, this.name);
259
+ motionService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.name);
162
260
  motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
163
261
  motionService.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
164
262
  motionService.getCharacteristic(this.hap.Characteristic.StatusActive).onGet(() => this.status.availability);
165
263
  return true;
166
264
  }
265
+ // Configure a switch to automate open and close events in HomeKit beyond what HomeKit might allow for a garage opener service that gets treated as a secure service.
266
+ configureAutomationSwitch() {
267
+ // Find the switch service, if it exists.
268
+ let switchService = this.accessory.getServiceById(this.hap.Service.Switch, RatgdoReservedNames.SWITCH_OPENER_AUTOMATION);
269
+ // The switch is disabled by default and primarily exists for automation purposes.
270
+ if (!this.hints.automationSwitch) {
271
+ if (switchService) {
272
+ this.accessory.removeService(switchService);
273
+ this.log.info("Disabling automation switch.");
274
+ }
275
+ return false;
276
+ }
277
+ // Add the switch to the opener, if needed.
278
+ if (!switchService) {
279
+ switchService = new this.hap.Service.Switch(this.name + " Automation Switch", RatgdoReservedNames.SWITCH_OPENER_AUTOMATION);
280
+ if (!switchService) {
281
+ this.log.error("Unable to add automation switch.");
282
+ return false;
283
+ }
284
+ switchService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
285
+ this.accessory.addService(switchService);
286
+ }
287
+ // Return the current state of the opener.
288
+ switchService.getCharacteristic(this.hap.Characteristic.On)?.onGet(() => {
289
+ // We're on if we are in any state other than closed (specifically open or stopped).
290
+ return this.doorCurrentStateBias(this.status.door) !== this.hap.Characteristic.CurrentDoorState.CLOSED;
291
+ });
292
+ // Open or close the opener.
293
+ switchService.getCharacteristic(this.hap.Characteristic.On)?.onSet((value) => {
294
+ // Inform the user.
295
+ this.log.info("Automation switch: %s.", value ? "open" : "close");
296
+ // Send the command.
297
+ if (!this.setDoorState(value ? this.hap.Characteristic.TargetDoorState.OPEN : this.hap.Characteristic.TargetDoorState.CLOSED)) {
298
+ // Something went wrong. Let's make sure we revert the switch to it's prior state.
299
+ setTimeout(() => {
300
+ switchService?.updateCharacteristic(this.hap.Characteristic.On, !value);
301
+ }, 50);
302
+ }
303
+ });
304
+ // Initialize the switch.
305
+ switchService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.name + " Automation Switch");
306
+ switchService.updateCharacteristic(this.hap.Characteristic.On, this.doorCurrentStateBias(this.status.door) !== this.hap.Characteristic.CurrentDoorState.CLOSED);
307
+ this.log.info("Enabling automation switch.");
308
+ return true;
309
+ }
310
+ // Configure the door open occupancy sensor for HomeKit.
311
+ configureDoorOpenOccupancySensor() {
312
+ // Find the occupancy sensor service, if it exists.
313
+ let occupancyService = this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_DOOR_OPEN);
314
+ // The occupancy sensor is disabled by default and primarily exists for automation purposes.
315
+ if (!this.hints.doorOpenOccupancySensor) {
316
+ if (occupancyService) {
317
+ this.accessory.removeService(occupancyService);
318
+ this.log.info("Disabling door open indicator occupancy sensor.");
319
+ }
320
+ return false;
321
+ }
322
+ // We don't have an occupancy sensor, let's add it to the device.
323
+ if (!occupancyService) {
324
+ // We don't have it, add the occupancy sensor to the device.
325
+ occupancyService = new this.hap.Service.OccupancySensor(this.name + " Open", RatgdoReservedNames.OCCUPANCY_SENSOR_DOOR_OPEN);
326
+ if (!occupancyService) {
327
+ this.log.error("Unable to add door open occupancy sensor.");
328
+ return false;
329
+ }
330
+ occupancyService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
331
+ this.accessory.addService(occupancyService);
332
+ }
333
+ // Ensure we can configure the name of the occupancy sensor.
334
+ occupancyService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.name + " Open");
335
+ // Initialize the state of the occupancy sensor.
336
+ occupancyService.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, false);
337
+ occupancyService.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
338
+ occupancyService.getCharacteristic(this.hap.Characteristic.StatusActive).onGet(() => {
339
+ return this.status.availability;
340
+ });
341
+ this.log.info("Enabling door open indicator occupancy sensor. Occupancy will be triggered when the opener has been continuously open for more than %s seconds.", this.hints.doorOpenOccupancyDuration);
342
+ return true;
343
+ }
344
+ // Configure the motion occupancy sensor for HomeKit.
345
+ configureMotionOccupancySensor() {
346
+ // Find the occupancy sensor service, if it exists.
347
+ let occupancyService = this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_MOTION);
348
+ // The occupancy sensor is disabled by default and primarily exists for automation purposes.
349
+ if (!this.hints.motionOccupancySensor) {
350
+ if (occupancyService) {
351
+ this.accessory.removeService(occupancyService);
352
+ this.log.info("Disabling occupancy sensor.");
353
+ }
354
+ return false;
355
+ }
356
+ // We don't have an occupancy sensor, let's add it to the device.
357
+ if (!occupancyService) {
358
+ // We don't have it, add the occupancy sensor to the device.
359
+ occupancyService = new this.hap.Service.OccupancySensor(this.name, RatgdoReservedNames.OCCUPANCY_SENSOR_MOTION);
360
+ if (!occupancyService) {
361
+ this.log.error("Unable to add occupancy sensor.");
362
+ return false;
363
+ }
364
+ occupancyService.addOptionalCharacteristic(this.hap.Characteristic.ConfiguredName);
365
+ this.accessory.addService(occupancyService);
366
+ }
367
+ // Ensure we can configure the name of the occupancy sensor.
368
+ occupancyService.updateCharacteristic(this.hap.Characteristic.ConfiguredName, this.name);
369
+ // Initialize the state of the occupancy sensor.
370
+ occupancyService.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, false);
371
+ occupancyService.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
372
+ occupancyService.getCharacteristic(this.hap.Characteristic.StatusActive).onGet(() => {
373
+ return this.status.availability;
374
+ });
375
+ this.log.info("Enabling occupancy sensor. Occupancy event duration set to %s seconds.", this.hints.motionOccupancyDuration);
376
+ return true;
377
+ }
167
378
  // Open or close the garage door.
168
379
  setDoorState(value) {
169
380
  const actionExisting = this.status.door === this.hap.Characteristic.CurrentDoorState.OPENING ? "opening" : "closing";
@@ -173,13 +384,12 @@ class ratgdoAccessory {
173
384
  this.log.info("Unable to %s door. The door has been configured to be read only.", actionAttempt);
174
385
  // Tell HomeKit that we haven't in fact changed our state so we don't end up in an inadvertent opening or closing state.
175
386
  setImmediate(() => {
176
- var _a;
177
- (_a = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.TargetDoorState, value === this.hap.Characteristic.TargetDoorState.CLOSED ? this.hap.Characteristic.TargetDoorState.OPEN : this.hap.Characteristic.TargetDoorState.CLOSED);
387
+ this.accessory.getService(this.hap.Service.GarageDoorOpener)?.updateCharacteristic(this.hap.Characteristic.TargetDoorState, value === this.hap.Characteristic.TargetDoorState.CLOSED ? this.hap.Characteristic.TargetDoorState.OPEN : this.hap.Characteristic.TargetDoorState.CLOSED);
178
388
  });
179
389
  return false;
180
390
  }
181
- // If we are already opening or closing the garage door, we error out. As a precaution, we ensure we complete the current action before allowing a new one. This behavior may
182
- // change in the future, but for now, we manage this edge case by eliminating the possibility of doing so.
391
+ // If we are already opening or closing the garage door, we error out. As a precaution, we ensure we complete the current action before allowing a new one.
392
+ // This behavior may change in the future, but for now, we manage this edge case by eliminating the possibility of doing so.
183
393
  if ((this.status.door === this.hap.Characteristic.CurrentDoorState.OPENING) || (this.status.door === this.hap.Characteristic.CurrentDoorState.CLOSING)) {
184
394
  this.log.error("Unable to %s door while currently attempting to complete %s. The existing action must be allowed to complete before attempting a new one.", actionAttempt, actionExisting);
185
395
  return false;
@@ -189,7 +399,7 @@ class ratgdoAccessory {
189
399
  // HomeKit is asking us to close the garage door, but let's make sure it's not already closed first.
190
400
  if (this.status.door !== this.hap.Characteristic.CurrentDoorState.CLOSED) {
191
401
  // Execute the command.
192
- this.command("door", "close", "closing");
402
+ this.command("door", "close");
193
403
  }
194
404
  return true;
195
405
  }
@@ -198,7 +408,7 @@ class ratgdoAccessory {
198
408
  // HomeKit is informing us to open the door, but we don't want to act if it's already open.
199
409
  if (this.status.door !== this.hap.Characteristic.CurrentDoorState.OPEN) {
200
410
  // Execute the command.
201
- this.command("door", "open", "opening");
411
+ this.command("door", "open");
202
412
  }
203
413
  return true;
204
414
  }
@@ -208,18 +418,29 @@ class ratgdoAccessory {
208
418
  }
209
419
  // Update the state of the accessory.
210
420
  updateState(event, payload) {
211
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
212
421
  const camelCase = (text) => text.charAt(0).toUpperCase() + text.slice(1);
422
+ const doorOccupancyService = this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_DOOR_OPEN);
423
+ const garageDoorService = this.accessory.getService(this.hap.Service.GarageDoorOpener);
424
+ const lightBulbService = this.accessory.getService(this.hap.Service.Lightbulb);
425
+ const motionOccupancyService = this.accessory.getServiceById(this.hap.Service.OccupancySensor, RatgdoReservedNames.OCCUPANCY_SENSOR_MOTION);
426
+ const motionService = this.accessory.getService(this.hap.Service.MotionSensor);
427
+ const switchService = this.accessory.getServiceById(this.hap.Service.Switch, RatgdoReservedNames.SWITCH_OPENER_AUTOMATION);
213
428
  switch (event) {
214
429
  case "availability":
215
430
  this.status.availability = payload === "online";
216
431
  // Update our availability.
217
- (_a = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
218
- (_b = this.accessory.getService(this.hap.Service.MotionSensor)) === null || _b === void 0 ? void 0 : _b.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
432
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
433
+ doorOccupancyService?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
434
+ motionOccupancyService?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
435
+ motionService?.updateCharacteristic(this.hap.Characteristic.StatusActive, this.status.availability);
219
436
  // Inform the user:
220
437
  this.log.info("Device %s.", this.status.availability ? "connected" : "disconnected");
221
438
  break;
222
439
  case "door":
440
+ // If we're already in the state we're updating to, we're done.
441
+ if (this.translateCurrentDoorState(this.status.door) === payload) {
442
+ break;
443
+ }
223
444
  // Clear out our door transition timer, if we have one.
224
445
  if (this.doorTimer) {
225
446
  clearTimeout(this.doorTimer);
@@ -231,28 +452,37 @@ class ratgdoAccessory {
231
452
  break;
232
453
  case "closing":
233
454
  this.status.door = this.hap.Characteristic.CurrentDoorState.CLOSING;
234
- // As a safety measure for occasionally unreliable MQTT message delivery, let's ensure we generate a closed state after a reasonable transition period. If we receive
235
- // an actual state update before this, this safety measure won't be triggered.
455
+ // As a safety measure for occasionally unreliable MQTT message delivery, let's ensure we generate a closed state after a reasonable transition period. If we
456
+ // receive an actual state update before this, this safety measure won't be triggered.
236
457
  this.doorTimer = setTimeout(() => {
237
458
  // Mark the door as closed.
238
459
  this.log.debug("Generating a close event to complete the state transition.");
239
460
  this.updateState("door", "closed");
240
461
  this.doorTimer = null;
241
- }, settings_js_1.RATGDO_TRANSITION_DURATION * 1000);
462
+ }, RATGDO_TRANSITION_DURATION * 1000);
242
463
  break;
243
464
  case "open":
244
465
  this.status.door = this.hap.Characteristic.CurrentDoorState.OPEN;
466
+ // Trigger our occupancy timer, if configured to do so and we don't have one yet.
467
+ if (this.hints.doorOpenOccupancySensor && !this.doorOccupancyTimer) {
468
+ this.doorOccupancyTimer = setTimeout(() => {
469
+ doorOccupancyService?.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, true);
470
+ this.log.info("Garage door open occupancy detected.");
471
+ // Publish to MQTT, if the user has configured it.
472
+ this.platform.mqtt?.publish(this, "dooropenoccupancy", "true");
473
+ }, this.hints.doorOpenOccupancyDuration * 1000);
474
+ }
245
475
  break;
246
476
  case "opening":
247
477
  this.status.door = this.hap.Characteristic.CurrentDoorState.OPENING;
248
- // As a safety measure for occasionally unreliable MQTT message delivery, let's ensure we generate an open state after a reasonable transition period. If we receive
249
- // an actual state update before this, this safety measure won't be triggered.
478
+ // As a safety measure for occasionally unreliable MQTT message delivery, let's ensure we generate an open state after a reasonable transition period. If we
479
+ // receive an actual state update before this, this safety measure won't be triggered.
250
480
  this.doorTimer = setTimeout(() => {
251
481
  // Mark the door as open.
252
482
  this.log.debug("Generating an open event to complete the state transition.");
253
483
  this.updateState("door", "open");
254
484
  this.doorTimer = null;
255
- }, settings_js_1.RATGDO_TRANSITION_DURATION * 1000);
485
+ }, RATGDO_TRANSITION_DURATION * 1000);
256
486
  break;
257
487
  case "stopped":
258
488
  this.status.door = this.hap.Characteristic.CurrentDoorState.STOPPED;
@@ -264,26 +494,53 @@ class ratgdoAccessory {
264
494
  // We are only going to update the target state if our current state is NOT stopped. If we are stopped, we are at the target state by definition. We also want to
265
495
  // ensure we update TargetDoorState before updating CurrentDoorState in order to work around some notification quirks HomeKit occasionally has.
266
496
  if (this.status.door !== this.hap.Characteristic.CurrentDoorState.STOPPED) {
267
- (_c = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _c === void 0 ? void 0 : _c.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.doorTargetStateBias(this.status.door));
497
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.doorTargetStateBias(this.status.door));
268
498
  }
269
- (_d = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _d === void 0 ? void 0 : _d.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.status.door);
499
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.status.door);
500
+ // Update our automation switch, if configured.
501
+ switchService?.updateCharacteristic(this.hap.Characteristic.On, this.doorTargetStateBias(this.status.door) === this.hap.Characteristic.TargetDoorState.OPEN);
270
502
  // Inform the user:
271
503
  this.log.info("%s.", camelCase(payload));
504
+ // If we have an open occupancy sensor configured and our door state is anything other than open, clear our occupancy state.
505
+ if (this.hints.doorOpenOccupancySensor && this.doorOccupancyTimer && (payload !== "open")) {
506
+ clearTimeout(this.doorOccupancyTimer);
507
+ this.doorOccupancyTimer = null;
508
+ if (doorOccupancyService?.getCharacteristic(this.hap.Characteristic.OccupancyDetected).value) {
509
+ doorOccupancyService?.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, false);
510
+ this.log.info("Garage door open occupancy no longer detected.");
511
+ // Publish to MQTT, if the user has configured it.
512
+ this.platform.mqtt?.publish(this, "dooropenoccupancy", "false");
513
+ }
514
+ }
515
+ // Publish to MQTT, if the user has configured it.
516
+ this.platform.mqtt?.publish(this, "garagedoor", payload);
272
517
  break;
273
518
  case "light":
274
- this.status.light = payload === "on";
275
- (_e = this.accessory.getService(this.hap.Service.Lightbulb)) === null || _e === void 0 ? void 0 : _e.updateCharacteristic(this.hap.Characteristic.On, this.status.light);
276
- // Inform the user:
277
- this.log.info("Light %s.", payload);
519
+ // Only act if we're not already at the state we're updating to.
520
+ if (this.status.light !== (payload === "on")) {
521
+ this.status.light = payload === "on";
522
+ lightBulbService?.updateCharacteristic(this.hap.Characteristic.On, this.status.light);
523
+ // Inform the user:
524
+ this.log.info("Light %s.", payload);
525
+ // Publish to MQTT, if the user has configured it.
526
+ this.platform.mqtt?.publish(this, "light", this.status.light.toString());
527
+ }
278
528
  break;
279
529
  case "lock":
280
- // Determine our current and target lock states.
530
+ // If we're already in the state we're updating to, we're done.
531
+ if (this.status.lock === (payload === "locked" ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED)) {
532
+ break;
533
+ }
534
+ // Determine our lock state.
281
535
  this.status.lock = payload === "locked" ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED;
282
536
  // Update our lock state.
283
- (_f = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _f === void 0 ? void 0 : _f.updateCharacteristic(this.hap.Characteristic.LockTargetState, payload === "locked" ? this.hap.Characteristic.LockTargetState.SECURED : this.hap.Characteristic.LockTargetState.UNSECURED);
284
- (_g = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _g === void 0 ? void 0 : _g.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.status.lock);
537
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.LockTargetState, payload === "locked" ?
538
+ this.hap.Characteristic.LockTargetState.SECURED : this.hap.Characteristic.LockTargetState.UNSECURED);
539
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.status.lock);
285
540
  // Inform the user:
286
541
  this.log.info("%s.", camelCase(payload));
542
+ // Publish to MQTT, if the user has configured it.
543
+ this.platform.mqtt?.publish(this, "lock", this.status.lock.toString());
287
544
  break;
288
545
  case "motion":
289
546
  this.status.motion = payload === "detected";
@@ -294,30 +551,92 @@ class ratgdoAccessory {
294
551
  break;
295
552
  }
296
553
  // Update the motion sensor state.
297
- (_h = this.accessory.getService(this.hap.Service.MotionSensor)) === null || _h === void 0 ? void 0 : _h.updateCharacteristic(this.hap.Characteristic.MotionDetected, this.status.motion);
298
- // If we already have an inflight motion sensor timer, clear it out since we're restarting the timer. Also, if it's our first time detecting motion for this event cycle,
299
- // let the user know.
300
- this.motionTimer ? clearTimeout(this.motionTimer) : this.log.info("Motion detected.");
554
+ motionService?.updateCharacteristic(this.hap.Characteristic.MotionDetected, this.status.motion);
555
+ // If we already have an inflight motion sensor timer, clear it out since we're restarting the timer. Also, if it's our first time detecting motion for this event
556
+ // cycle, let the user know.
557
+ if (this.motionTimer) {
558
+ clearTimeout(this.motionTimer);
559
+ }
560
+ else {
561
+ this.log.info("Motion detected.");
562
+ // Publish to MQTT, if the user has configured it.
563
+ this.platform.mqtt?.publish(this, "motion", this.status.motion.toString());
564
+ }
301
565
  // Set a timer for the motion event.
302
566
  this.motionTimer = setTimeout(() => {
303
- var _a;
304
567
  this.status.motion = false;
305
- (_a = this.accessory.getService(this.hap.Service.MotionSensor)) === null || _a === void 0 ? void 0 : _a.updateCharacteristic(this.hap.Characteristic.MotionDetected, this.status.motion);
306
- }, settings_js_1.RATGDO_MOTION_DURATION * 1000);
568
+ motionService?.updateCharacteristic(this.hap.Characteristic.MotionDetected, this.status.motion);
569
+ // Publish to MQTT, if the user has configured it.
570
+ this.platform.mqtt?.publish(this, "motion", this.status.motion.toString());
571
+ }, RATGDO_MOTION_DURATION * 1000);
572
+ // Kill any inflight occupancy sensor.
573
+ if (this.motionOccupancyTimer) {
574
+ clearTimeout(this.motionOccupancyTimer);
575
+ this.motionOccupancyTimer = null;
576
+ }
577
+ // If the motion occupancy sensor isn't already triggered, let's do so now.
578
+ if (motionOccupancyService?.getCharacteristic(this.hap.Characteristic.OccupancyDetected).value !== true) {
579
+ // Trigger the occupancy event in HomeKit.
580
+ motionOccupancyService?.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, true);
581
+ // Publish to MQTT, if the user has configured it.
582
+ this.platform.mqtt?.publish(this, "occupancy", "true");
583
+ // Log the event.
584
+ this.log.info("Occupancy detected.");
585
+ }
586
+ // Reset our occupancy state after occupancyDuration.
587
+ this.motionOccupancyTimer = setTimeout(() => {
588
+ // Reset the occupancy sensor.
589
+ motionOccupancyService?.updateCharacteristic(this.hap.Characteristic.OccupancyDetected, false);
590
+ // Publish to MQTT, if the user has configured it.
591
+ this.platform.mqtt?.publish(this, "occupancy", "false");
592
+ // Log the event.
593
+ this.log.info("Occupancy no longer detected.");
594
+ // Delete the timer.
595
+ this.motionOccupancyTimer = null;
596
+ }, this.hints.motionOccupancyDuration * 1000);
307
597
  break;
308
598
  case "obstruction":
309
- this.status.obstruction = payload === "obstructed";
310
- (_j = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _j === void 0 ? void 0 : _j.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, this.status.obstruction);
311
- this.log.info("Obstruction %sdetected.", this.status.obstruction ? "" : "no longer ");
599
+ garageDoorService?.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, payload === "obstructed");
600
+ // Only act if we're not already at the state we're updating to.
601
+ if (this.status.obstruction !== (payload === "obstructed")) {
602
+ this.status.obstruction = payload === "obstructed";
603
+ this.log.info("Obstruction %sdetected.", this.status.obstruction ? "" : "no longer ");
604
+ // Publish to MQTT, if the user has configured it.
605
+ this.platform.mqtt?.publish(this, "obstruction", this.status.obstruction.toString());
606
+ }
312
607
  break;
313
608
  case "default":
314
609
  break;
315
610
  }
316
611
  }
612
+ // Utility function to translate HomeKit's current door state values into human-readable form.
613
+ translateCurrentDoorState(value) {
614
+ // HomeKit state decoder ring.
615
+ switch (value) {
616
+ case this.hap.Characteristic.CurrentDoorState.CLOSED:
617
+ return "closed";
618
+ break;
619
+ case this.hap.Characteristic.CurrentDoorState.CLOSING:
620
+ return "closing";
621
+ break;
622
+ case this.hap.Characteristic.CurrentDoorState.OPEN:
623
+ return "open";
624
+ break;
625
+ case this.hap.Characteristic.CurrentDoorState.OPENING:
626
+ return "opening";
627
+ break;
628
+ case this.hap.Characteristic.CurrentDoorState.STOPPED:
629
+ return "stopped";
630
+ break;
631
+ default:
632
+ break;
633
+ }
634
+ return "unknown";
635
+ }
317
636
  // Utility function to return our bias for what the current door state should be. This is primarily used for our initial bias on startup.
318
637
  doorCurrentStateBias(state) {
319
- // Our current door state reflects our opinion on what open or closed means in HomeKit terms. For the obvious states, this is easy. For some of the edge cases, it can be
320
- // less so. Our north star is that if we are in an obstructed state, we are open.
638
+ // Our current door state reflects our opinion on what open or closed means in HomeKit terms. For the obvious states, this is easy. For some of the edge cases, it can
639
+ // be less so. Our north star is that if we are in an obstructed state, we are open.
321
640
  if (this.status.obstruction) {
322
641
  return this.hap.Characteristic.CurrentDoorState.OPEN;
323
642
  }
@@ -338,9 +657,9 @@ class ratgdoAccessory {
338
657
  }
339
658
  // Utility function to return our bias for what the target door state should be.
340
659
  doorTargetStateBias(state) {
341
- // We need to be careful with respect to the target state and we need to make some reasonable assumptions about where we intend to end up. If we are opening or closing, our
342
- // target state needs to be the completion of those actions. If we're stopped or obstructed, we're going to assume the desired target state is to be open, since that is the
343
- // typical opener behavior, and it's impossible for us to know with reasonable certainty what the original intention of the action was.
660
+ // We need to be careful with respect to the target state and we need to make some reasonable assumptions about where we intend to end up. If we are opening or
661
+ // closing, our target state needs to be the completion of those actions. If we're stopped or obstructed, we're going to assume the desired target state is to be
662
+ // open, since that is the typical opener behavior, and it's impossible for us to know with reasonable certainty what the original intention of the action was.
344
663
  if (this.status.obstruction) {
345
664
  return this.hap.Characteristic.TargetDoorState.OPEN;
346
665
  }
@@ -372,31 +691,52 @@ class ratgdoAccessory {
372
691
  }
373
692
  }
374
693
  // Utility function to transmit a command to Ratgdo.
375
- command(topic, payload, updatePayload) {
376
- this.platform.broker.publish({ cmd: "publish", dup: false, payload: payload, qos: 2, retain: false, topic: this.device.name + "/command/" + topic }, () => { });
377
- // Update our internal state as well.
378
- if (updatePayload) {
379
- this.updateState(topic, updatePayload);
380
- }
694
+ command(topic, payload) {
695
+ this.platform.broker.publish({ cmd: "publish", dup: false, payload: payload, qos: 2, retain: false, topic: this.device.name + "/command/" + topic }, (error) => {
696
+ if (error) {
697
+ this.log.error("Publish error:");
698
+ this.log.error(util.inspect(error), { colors: true, depth: null, sorted: true });
699
+ }
700
+ });
381
701
  }
382
702
  // Utility function to return a floating point configuration parameter on a device.
383
703
  getFeatureFloat(option) {
384
- return (0, ratgdo_options_js_1.getOptionFloat)((0, ratgdo_options_js_1.getOptionValue)(this.platform.configOptions, this.device, option));
704
+ return getOptionFloat(getOptionValue(this.platform.configOptions, this.device, option));
385
705
  }
386
706
  // Utility function to return an integer configuration parameter on a device.
387
707
  getFeatureNumber(option) {
388
- return (0, ratgdo_options_js_1.getOptionNumber)((0, ratgdo_options_js_1.getOptionValue)(this.platform.configOptions, this.device, option));
708
+ return getOptionNumber(getOptionValue(this.platform.configOptions, this.device, option));
389
709
  }
390
710
  // Utility for checking feature options on a device.
391
711
  hasFeature(option) {
392
- return (0, ratgdo_options_js_1.isOptionEnabled)(this.platform.configOptions, this.device, option, this.platform.featureOptionDefault(option));
712
+ return isOptionEnabled(this.platform.configOptions, this.device, option, this.platform.featureOptionDefault(option));
713
+ }
714
+ // Utility function to set the name of a service.
715
+ setServiceName(service, name) {
716
+ if (!service) {
717
+ return;
718
+ }
719
+ service.displayName = name;
720
+ service.updateCharacteristic(this.hap.Characteristic.ConfiguredName, name);
393
721
  }
394
- // Name utility function.
722
+ // Utility function to return the name of this device.
395
723
  get name() {
396
- var _a;
397
- const configuredName = (_a = this.accessory.getService(this.hap.Service.GarageDoorOpener)) === null || _a === void 0 ? void 0 : _a.getCharacteristic(this.hap.Characteristic.ConfiguredName).value;
398
- return (configuredName === null || configuredName === void 0 ? void 0 : configuredName.length) ? configuredName : this.device.name;
724
+ // We use the garage door service as the natural proxy for the name.
725
+ const configuredName = this.accessory.getService(this.hap.Service.GarageDoorOpener)?.getCharacteristic(this.hap.Characteristic.ConfiguredName).value;
726
+ // If we don't have a name for the garage door service, return the device name from Ratgdo.
727
+ return configuredName?.length ? configuredName : this.device.name;
728
+ }
729
+ // Utility function to return the current accessory name of this device.
730
+ get accessoryName() {
731
+ return this.accessory.getService(this.hap.Service.AccessoryInformation)?.getCharacteristic(this.hap.Characteristic.Name).value ?? this.device.name;
732
+ }
733
+ // Utility function to set the current accessory name of this device.
734
+ set accessoryName(name) {
735
+ // Set all the internally managed names within Homebridge to the new accessory name.
736
+ this.accessory.displayName = name;
737
+ this.accessory._associatedHAPAccessory.displayName = name;
738
+ // Set all the HomeKit-visible names.
739
+ this.accessory.getService(this.hap.Service.AccessoryInformation)?.updateCharacteristic(this.hap.Characteristic.Name, name);
399
740
  }
400
741
  }
401
- exports.ratgdoAccessory = ratgdoAccessory;
402
742
  //# sourceMappingURL=ratgdo-device.js.map