@boozilla/homebridge-shome 1.0.12 → 1.0.13
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.
- package/README.md +4 -1
- package/dist/accessories/doorbellAccessory.d.ts +9 -0
- package/dist/accessories/doorbellAccessory.js +24 -0
- package/dist/accessories/doorbellAccessory.js.map +1 -0
- package/dist/platform.d.ts +6 -2
- package/dist/platform.js +100 -43
- package/dist/platform.js.map +1 -1
- package/dist/shomeClient.d.ts +11 -1
- package/dist/shomeClient.js +44 -12
- package/dist/shomeClient.js.map +1 -1
- package/package.json +1 -1
- package/src/accessories/doorbellAccessory.ts +29 -0
- package/src/platform.ts +116 -46
- package/src/shomeClient.ts +57 -12
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Homebridge sHome
|
|
2
2
|
|
|
3
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
4
|
+
|
|
3
5
|
`homebridge-shome` is a [Homebridge](https://homebridge.io/) plugin that allows you to integrate devices from the
|
|
4
6
|
Samsung Smart Home (sHome) platform into Apple HomeKit.
|
|
5
7
|
|
|
@@ -15,13 +17,14 @@ door locks, using the Home app and Siri on your Apple devices.
|
|
|
15
17
|
* **Digital Door Lock**: Check the current state of your door lock (locked/unlocked) and unlock it remotely.
|
|
16
18
|
* **Individual Accessory Support**: Each sub-device registered in your system, such as 'Living Room Light' or 'Master
|
|
17
19
|
Bedroom Thermostat', is added as an independent HomeKit accessory for granular control.
|
|
20
|
+
* **Visitor Notifications**: Automatically creates a "Visitor Alert" motion sensor in HomeKit. When a new visitor is detected, this sensor is triggered, sending a notification to your Apple devices (if you have notifications enabled for the sensor in the Home app).
|
|
18
21
|
|
|
19
22
|
## Installation
|
|
20
23
|
|
|
21
24
|
If you have a running Homebridge setup, install the plugin using the following command:
|
|
22
25
|
|
|
23
26
|
```sh
|
|
24
|
-
npm install -g homebridge-shome
|
|
27
|
+
npm install -g @boozilla/homebridge-shome
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
## Configuration
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PlatformAccessory } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
export declare class DoorbellAccessory {
|
|
4
|
+
private readonly platform;
|
|
5
|
+
private readonly accessory;
|
|
6
|
+
private service;
|
|
7
|
+
constructor(platform: ShomePlatform, accessory: PlatformAccessory);
|
|
8
|
+
ring(visitorLabel: string): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class DoorbellAccessory {
|
|
2
|
+
platform;
|
|
3
|
+
accessory;
|
|
4
|
+
service;
|
|
5
|
+
constructor(platform, accessory) {
|
|
6
|
+
this.platform = platform;
|
|
7
|
+
this.accessory = accessory;
|
|
8
|
+
// set accessory information
|
|
9
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)
|
|
10
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'sHome')
|
|
11
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'sHome Doorbell')
|
|
12
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'shome-doorbell');
|
|
13
|
+
this.service = this.accessory.getService(this.platform.Service.StatelessProgrammableSwitch)
|
|
14
|
+
|| this.accessory.addService(this.platform.Service.StatelessProgrammableSwitch);
|
|
15
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
|
|
16
|
+
this.service.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent);
|
|
17
|
+
}
|
|
18
|
+
ring(visitorLabel) {
|
|
19
|
+
this.platform.log.info(`Triggered Doorbell event from [${visitorLabel}]`);
|
|
20
|
+
this.service.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent)
|
|
21
|
+
.updateValue(this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=doorbellAccessory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doorbellAccessory.js","sourceRoot":"","sources":["../../src/accessories/doorbellAccessory.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,iBAAiB;IAIL;IACA;IAJf,OAAO,CAAU;IAEzB,YACuB,QAAuB,EACvB,SAA4B;QAD5B,aAAQ,GAAR,QAAQ,CAAe;QACvB,cAAS,GAAT,SAAS,CAAmB;QAG7C,4BAA4B;QAC5B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAE;aACnE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC;aACrE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,gBAAgB,CAAC;aACvE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;QAClF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,2BAA2B,CAAC;eACpF,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QACzF,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAC,CAAC;IAC3F,CAAC;IAEM,IAAI,CAAC,YAAoB;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,kCAAkC,YAAY,GAAG,CAAC,CAAC;QAE1E,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAC;aACjF,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACpF,CAAC;CACF"}
|
package/dist/platform.d.ts
CHANGED
|
@@ -9,12 +9,16 @@ export declare class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
9
9
|
readonly accessories: PlatformAccessory[];
|
|
10
10
|
readonly shomeClient: ShomeClient;
|
|
11
11
|
private readonly accessoryHandlers;
|
|
12
|
-
private pollingInterval;
|
|
12
|
+
private readonly pollingInterval;
|
|
13
13
|
private pollingTimer?;
|
|
14
|
+
private lastCheckedTimestamp;
|
|
14
15
|
constructor(log: Logger, config: PlatformConfig, api: API);
|
|
15
16
|
configureAccessory(accessory: PlatformAccessory): void;
|
|
16
17
|
discoverDevices(): Promise<void>;
|
|
17
18
|
setupAccessory(mainDevice: MainDevice, subDevice: SubDevice | null, uuid: string): PlatformAccessory;
|
|
18
|
-
|
|
19
|
+
createAccessoryHandler(accessory: PlatformAccessory): void;
|
|
19
20
|
startPolling(): void;
|
|
21
|
+
pollDeviceUpdates(): Promise<void>;
|
|
22
|
+
checkForNewVisitors(): Promise<void>;
|
|
23
|
+
private parseRecodDt;
|
|
20
24
|
}
|
package/dist/platform.js
CHANGED
|
@@ -4,6 +4,7 @@ import { LightAccessory } from './accessories/lightAccessory.js';
|
|
|
4
4
|
import { VentilatorAccessory } from './accessories/ventilatorAccessory.js';
|
|
5
5
|
import { HeaterAccessory } from './accessories/heaterAccessory.js';
|
|
6
6
|
import { DoorlockAccessory } from './accessories/doorlockAccessory.js';
|
|
7
|
+
import { DoorbellAccessory } from './accessories/doorbellAccessory.js';
|
|
7
8
|
const CONTROLLABLE_MULTI_DEVICE_TYPES = ['LIGHT', 'HEATER', 'VENTILATOR'];
|
|
8
9
|
const SPECIAL_CONTROLLABLE_TYPES = ['DOORLOCK'];
|
|
9
10
|
export class ShomePlatform {
|
|
@@ -17,6 +18,7 @@ export class ShomePlatform {
|
|
|
17
18
|
accessoryHandlers = new Map();
|
|
18
19
|
pollingInterval;
|
|
19
20
|
pollingTimer;
|
|
21
|
+
lastCheckedTimestamp = new Date();
|
|
20
22
|
constructor(log, config, api) {
|
|
21
23
|
this.log = log;
|
|
22
24
|
this.config = config;
|
|
@@ -31,6 +33,7 @@ export class ShomePlatform {
|
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
35
|
this.shomeClient = new ShomeClient(this.log, this.config.username, this.config.password, this.config.deviceId);
|
|
36
|
+
this.log.info(`Doorbell notification service is active. Baseline time: ${this.lastCheckedTimestamp.toISOString()}`);
|
|
34
37
|
this.api.on('didFinishLaunching', () => {
|
|
35
38
|
this.discoverDevices();
|
|
36
39
|
if (this.pollingInterval > 0) {
|
|
@@ -54,9 +57,6 @@ export class ShomePlatform {
|
|
|
54
57
|
this.log.info('Discovering devices...');
|
|
55
58
|
const devices = await this.shomeClient.getDeviceList();
|
|
56
59
|
const foundAccessories = [];
|
|
57
|
-
if (!devices || devices.length === 0) {
|
|
58
|
-
this.log.warn('No devices found on your sHome account.');
|
|
59
|
-
}
|
|
60
60
|
for (const device of devices) {
|
|
61
61
|
if (CONTROLLABLE_MULTI_DEVICE_TYPES.includes(device.thngModelTypeName)) {
|
|
62
62
|
const deviceInfoList = await this.shomeClient.getDeviceInfo(device.thngId, device.thngModelTypeName);
|
|
@@ -73,10 +73,11 @@ export class ShomePlatform {
|
|
|
73
73
|
const accessory = this.setupAccessory(device, null, uuid);
|
|
74
74
|
foundAccessories.push(accessory);
|
|
75
75
|
}
|
|
76
|
-
else {
|
|
77
|
-
this.log.info(`Ignoring device: ${device.nickname} (Type: ${device.thngModelTypeName})`);
|
|
78
|
-
}
|
|
79
76
|
}
|
|
77
|
+
const doorbellUUID = this.api.hap.uuid.generate('shome-doorbell-accessory');
|
|
78
|
+
const doorbellDevice = { thngModelTypeName: 'DOORBELL', nickname: 'Doorbell', thngId: 'shome-doorbell' };
|
|
79
|
+
const doorbellAccessory = this.setupAccessory(doorbellDevice, null, doorbellUUID);
|
|
80
|
+
foundAccessories.push(doorbellAccessory);
|
|
80
81
|
const accessoriesToRemove = this.accessories.filter(cachedAccessory => !foundAccessories.some(foundAccessory => foundAccessory.UUID === cachedAccessory.UUID));
|
|
81
82
|
if (accessoriesToRemove.length > 0) {
|
|
82
83
|
this.log.info('Removing stale accessories:', accessoriesToRemove.map(a => a.displayName));
|
|
@@ -92,28 +93,27 @@ export class ShomePlatform {
|
|
|
92
93
|
setupAccessory(mainDevice, subDevice, uuid) {
|
|
93
94
|
const displayName = subDevice ? subDevice.nickname : mainDevice.nickname;
|
|
94
95
|
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
|
|
96
|
+
const accessory = existingAccessory ?? new this.api.platformAccessory(displayName, uuid);
|
|
97
|
+
accessory.context.device = mainDevice;
|
|
98
|
+
accessory.context.subDevice = subDevice;
|
|
95
99
|
if (existingAccessory) {
|
|
96
100
|
this.log.info('Restoring existing accessory from cache:', displayName);
|
|
97
|
-
existingAccessory.context.device = mainDevice;
|
|
98
|
-
existingAccessory.context.subDevice = subDevice;
|
|
99
|
-
this.createAccessory(existingAccessory);
|
|
100
|
-
return existingAccessory;
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
103
|
this.log.info('Adding new accessory:', displayName);
|
|
104
|
-
const accessory = new this.api.platformAccessory(displayName, uuid);
|
|
105
|
-
accessory.context.device = mainDevice;
|
|
106
|
-
accessory.context.subDevice = subDevice;
|
|
107
|
-
this.createAccessory(accessory);
|
|
108
104
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
109
|
-
return accessory;
|
|
110
105
|
}
|
|
106
|
+
this.createAccessoryHandler(accessory);
|
|
107
|
+
return accessory;
|
|
111
108
|
}
|
|
112
|
-
|
|
109
|
+
createAccessoryHandler(accessory) {
|
|
113
110
|
const device = accessory.context.device;
|
|
114
111
|
const accessoryType = device.thngModelTypeName;
|
|
115
|
-
if (
|
|
116
|
-
|
|
112
|
+
if (this.accessoryHandlers.has(accessory.UUID)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (accessory.context.subDevice) {
|
|
116
|
+
switch (device.thngModelTypeName) {
|
|
117
117
|
case 'LIGHT':
|
|
118
118
|
this.accessoryHandlers.set(accessory.UUID, new LightAccessory(this, accessory));
|
|
119
119
|
break;
|
|
@@ -123,44 +123,101 @@ export class ShomePlatform {
|
|
|
123
123
|
case 'HEATER':
|
|
124
124
|
this.accessoryHandlers.set(accessory.UUID, new HeaterAccessory(this, accessory));
|
|
125
125
|
break;
|
|
126
|
-
case 'DOORLOCK':
|
|
127
|
-
this.accessoryHandlers.set(accessory.UUID, new DoorlockAccessory(this, accessory));
|
|
128
|
-
break;
|
|
129
126
|
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
switch (accessoryType) {
|
|
130
|
+
case 'DOORLOCK':
|
|
131
|
+
this.accessoryHandlers.set(accessory.UUID, new DoorlockAccessory(this, accessory));
|
|
132
|
+
break;
|
|
133
|
+
case 'DOORBELL':
|
|
134
|
+
this.accessoryHandlers.set(accessory.UUID, new DoorbellAccessory(this, accessory));
|
|
135
|
+
break;
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
startPolling() {
|
|
133
|
-
this.log.info(`Starting periodic
|
|
139
|
+
this.log.info(`Starting periodic state polling every ${this.pollingInterval / 1000} seconds.`);
|
|
134
140
|
this.pollingTimer = setInterval(async () => {
|
|
135
|
-
this.log.debug('Polling for
|
|
141
|
+
this.log.debug('Polling for updates...');
|
|
136
142
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
await this.pollDeviceUpdates();
|
|
144
|
+
await this.checkForNewVisitors();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.log.error('An error occurred during polling:', error);
|
|
148
|
+
}
|
|
149
|
+
}, this.pollingInterval);
|
|
150
|
+
}
|
|
151
|
+
async pollDeviceUpdates() {
|
|
152
|
+
const devices = await this.shomeClient.getDeviceList();
|
|
153
|
+
for (const device of devices) {
|
|
154
|
+
if (CONTROLLABLE_MULTI_DEVICE_TYPES.includes(device.thngModelTypeName)) {
|
|
155
|
+
const deviceInfoList = await this.shomeClient.getDeviceInfo(device.thngId, device.thngModelTypeName);
|
|
156
|
+
if (deviceInfoList) {
|
|
157
|
+
for (const subDevice of deviceInfoList) {
|
|
158
|
+
const deviceId = `${device.thngId}-${subDevice.deviceId}`;
|
|
159
|
+
if (this.shomeClient.isDeviceBusy(deviceId)) {
|
|
160
|
+
this.log.debug(`Skipping polling update for ${subDevice.nickname} as it has a pending request.`);
|
|
161
|
+
continue;
|
|
149
162
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const uuid = this.api.hap.uuid.generate(device.thngId);
|
|
153
|
-
const handler = this.accessoryHandlers.get(uuid);
|
|
163
|
+
const subUuid = this.api.hap.uuid.generate(deviceId);
|
|
164
|
+
const handler = this.accessoryHandlers.get(subUuid);
|
|
154
165
|
if (handler) {
|
|
155
|
-
handler.updateState(
|
|
166
|
+
handler.updateState(subDevice);
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
else if (SPECIAL_CONTROLLABLE_TYPES.includes(device.thngModelTypeName)) {
|
|
172
|
+
const deviceId = device.thngId;
|
|
173
|
+
if (this.shomeClient.isDeviceBusy(deviceId)) {
|
|
174
|
+
this.log.debug(`Skipping polling update for ${device.nickname} as it has a pending request.`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const uuid = this.api.hap.uuid.generate(deviceId);
|
|
178
|
+
const handler = this.accessoryHandlers.get(uuid);
|
|
179
|
+
if (handler) {
|
|
180
|
+
handler.updateState(device);
|
|
181
|
+
}
|
|
162
182
|
}
|
|
163
|
-
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async checkForNewVisitors() {
|
|
186
|
+
this.log.debug('Checking for new doorbell events...');
|
|
187
|
+
const visitorList = await this.shomeClient.getVisitorHistory();
|
|
188
|
+
const newVisitors = [];
|
|
189
|
+
for (const visitor of visitorList) {
|
|
190
|
+
const visitorTime = this.parseRecodDt(visitor.recodDt);
|
|
191
|
+
if (visitorTime > this.lastCheckedTimestamp) {
|
|
192
|
+
newVisitors.push(visitor);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (newVisitors.length > 0) {
|
|
196
|
+
this.log.info(`Found ${newVisitors.length} new doorbell event(s).`);
|
|
197
|
+
newVisitors.sort((a, b) => a.recodDt.localeCompare(b.recodDt));
|
|
198
|
+
const doorbellUUID = this.api.hap.uuid.generate('shome-doorbell-accessory');
|
|
199
|
+
const doorbellHandler = this.accessoryHandlers.get(doorbellUUID);
|
|
200
|
+
if (doorbellHandler) {
|
|
201
|
+
for (const visitor of newVisitors) {
|
|
202
|
+
doorbellHandler.ring(visitor.deviceLabel);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.log.warn('Doorbell accessory handler not found.');
|
|
207
|
+
}
|
|
208
|
+
const latestVisitor = newVisitors[newVisitors.length - 1];
|
|
209
|
+
this.lastCheckedTimestamp = this.parseRecodDt(latestVisitor.recodDt);
|
|
210
|
+
this.log.debug(`Updated last checked timestamp to: ${this.lastCheckedTimestamp.toISOString()}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
parseRecodDt(recodDt) {
|
|
214
|
+
const year = parseInt(recodDt.substring(0, 4), 10);
|
|
215
|
+
const month = parseInt(recodDt.substring(4, 6), 10) - 1;
|
|
216
|
+
const day = parseInt(recodDt.substring(6, 8), 10);
|
|
217
|
+
const hour = parseInt(recodDt.substring(8, 10), 10);
|
|
218
|
+
const minute = parseInt(recodDt.substring(10, 12), 10);
|
|
219
|
+
const second = parseInt(recodDt.substring(12, 14), 10);
|
|
220
|
+
return new Date(year, month, day, hour, minute, second);
|
|
164
221
|
}
|
|
165
222
|
}
|
|
166
223
|
//# sourceMappingURL=platform.js.map
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/shomeClient.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface SubDevice {
|
|
|
10
10
|
nickname: string;
|
|
11
11
|
[key: string]: unknown;
|
|
12
12
|
}
|
|
13
|
+
export interface Visitor {
|
|
14
|
+
sttId: string;
|
|
15
|
+
thumbNail: string;
|
|
16
|
+
recodDt: string;
|
|
17
|
+
deviceLabel: string;
|
|
18
|
+
}
|
|
13
19
|
export declare class ShomeClient {
|
|
14
20
|
private readonly log;
|
|
15
21
|
private readonly username;
|
|
@@ -17,20 +23,24 @@ export declare class ShomeClient {
|
|
|
17
23
|
private readonly deviceId;
|
|
18
24
|
private cachedAccessToken;
|
|
19
25
|
private ihdId;
|
|
26
|
+
private homeId;
|
|
20
27
|
private tokenExpiry;
|
|
21
28
|
private putQueue;
|
|
22
29
|
private isProcessingPut;
|
|
23
30
|
private loginPromise;
|
|
31
|
+
private pendingPutRequests;
|
|
24
32
|
constructor(log: Logger, username: string, password: string, deviceId: string);
|
|
25
33
|
private login;
|
|
26
34
|
private performLogin;
|
|
27
35
|
private enqueuePut;
|
|
28
36
|
private processPutQueue;
|
|
29
37
|
private executeWithRetries;
|
|
38
|
+
isDeviceBusy(deviceId: string): boolean;
|
|
30
39
|
getDeviceList(): Promise<MainDevice[]>;
|
|
31
40
|
getDeviceInfo(thingId: string, type: string): Promise<SubDevice[] | null>;
|
|
32
|
-
setDevice(thingId: string,
|
|
41
|
+
setDevice(thingId: string, subDeviceId: string, type: string, controlType: string, state: string, nickname?: string): Promise<boolean>;
|
|
33
42
|
unlockDoorlock(thingId: string, nickname?: string): Promise<boolean>;
|
|
43
|
+
getVisitorHistory(): Promise<Visitor[]>;
|
|
34
44
|
private sha512;
|
|
35
45
|
private isTokenExpired;
|
|
36
46
|
private getDateTime;
|
package/dist/shomeClient.js
CHANGED
|
@@ -14,11 +14,13 @@ export class ShomeClient {
|
|
|
14
14
|
deviceId;
|
|
15
15
|
cachedAccessToken = null;
|
|
16
16
|
ihdId = null;
|
|
17
|
+
homeId = null;
|
|
17
18
|
tokenExpiry = 0;
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
20
|
putQueue = [];
|
|
20
21
|
isProcessingPut = false;
|
|
21
22
|
loginPromise = null;
|
|
23
|
+
pendingPutRequests = new Set();
|
|
22
24
|
constructor(log, username, password, deviceId) {
|
|
23
25
|
this.log = log;
|
|
24
26
|
this.username = username;
|
|
@@ -70,6 +72,7 @@ export class ShomeClient {
|
|
|
70
72
|
if (response.data && response.data.accessToken) {
|
|
71
73
|
this.cachedAccessToken = response.data.accessToken;
|
|
72
74
|
this.ihdId = response.data.ihdId;
|
|
75
|
+
this.homeId = response.data.homeId;
|
|
73
76
|
const payload = JSON.parse(Buffer.from(this.cachedAccessToken.split('.')[1], 'base64').toString());
|
|
74
77
|
this.tokenExpiry = payload.exp * 1000;
|
|
75
78
|
this.log.info('Successfully logged in to sHome API.');
|
|
@@ -85,9 +88,9 @@ export class ShomeClient {
|
|
|
85
88
|
throw error;
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
|
-
enqueuePut(request) {
|
|
91
|
+
enqueuePut(request, deviceId) {
|
|
89
92
|
return new Promise((resolve, reject) => {
|
|
90
|
-
this.putQueue.push({ request, resolve, reject });
|
|
93
|
+
this.putQueue.push({ request, resolve, reject, deviceId });
|
|
91
94
|
this.processPutQueue();
|
|
92
95
|
});
|
|
93
96
|
}
|
|
@@ -98,6 +101,9 @@ export class ShomeClient {
|
|
|
98
101
|
this.isProcessingPut = true;
|
|
99
102
|
while (this.putQueue.length > 0) {
|
|
100
103
|
const task = this.putQueue.shift();
|
|
104
|
+
if (task.deviceId) {
|
|
105
|
+
this.pendingPutRequests.add(task.deviceId);
|
|
106
|
+
}
|
|
101
107
|
try {
|
|
102
108
|
const result = await this.executeWithRetries(task.request, true);
|
|
103
109
|
task.resolve(result);
|
|
@@ -105,6 +111,11 @@ export class ShomeClient {
|
|
|
105
111
|
catch (error) {
|
|
106
112
|
task.reject(error);
|
|
107
113
|
}
|
|
114
|
+
finally {
|
|
115
|
+
if (task.deviceId) {
|
|
116
|
+
this.pendingPutRequests.delete(task.deviceId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
108
119
|
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
|
109
120
|
}
|
|
110
121
|
this.isProcessingPut = false;
|
|
@@ -113,8 +124,6 @@ export class ShomeClient {
|
|
|
113
124
|
let retries = 0;
|
|
114
125
|
while (true) {
|
|
115
126
|
try {
|
|
116
|
-
// For non-queued (concurrent) requests, we need to ensure login happens before the request.
|
|
117
|
-
// For queued requests, the login is handled as part of the queue, so we can just await it.
|
|
118
127
|
if (!isQueued) {
|
|
119
128
|
await this.login();
|
|
120
129
|
}
|
|
@@ -137,11 +146,13 @@ export class ShomeClient {
|
|
|
137
146
|
this.log.warn(`Request failed. Retrying in ${backoffTime}ms... (Attempt ${retries}/${MAX_RETRIES})`);
|
|
138
147
|
}
|
|
139
148
|
await new Promise(resolve => setTimeout(resolve, backoffTime));
|
|
140
|
-
// After a failure, always ensure we are logged in before the next attempt.
|
|
141
149
|
await this.login();
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
}
|
|
153
|
+
isDeviceBusy(deviceId) {
|
|
154
|
+
return this.pendingPutRequests.has(deviceId);
|
|
155
|
+
}
|
|
145
156
|
async getDeviceList() {
|
|
146
157
|
return this.executeWithRetries(async () => {
|
|
147
158
|
const token = this.cachedAccessToken;
|
|
@@ -173,17 +184,18 @@ export class ShomeClient {
|
|
|
173
184
|
return response.data.deviceInfoList || null;
|
|
174
185
|
});
|
|
175
186
|
}
|
|
176
|
-
async setDevice(thingId,
|
|
177
|
-
|
|
187
|
+
async setDevice(thingId, subDeviceId, type, controlType, state, nickname) {
|
|
188
|
+
const deviceId = `${thingId}-${subDeviceId}`;
|
|
189
|
+
const request = async () => {
|
|
178
190
|
const token = this.cachedAccessToken;
|
|
179
191
|
if (!token) {
|
|
180
192
|
return false;
|
|
181
193
|
}
|
|
182
194
|
const createDate = this.getDateTime();
|
|
183
|
-
const hashData = this.sha512(`IHRESTAPI${thingId}${
|
|
195
|
+
const hashData = this.sha512(`IHRESTAPI${thingId}${subDeviceId}${state}${createDate}`);
|
|
184
196
|
const typePath = type.toLowerCase().replace(/_/g, '');
|
|
185
197
|
const controlPath = controlType.toLowerCase().replace(/_/g, '-');
|
|
186
|
-
await axios.put(`${BASE_URL}/v18/settings/${typePath}/${thingId}/${
|
|
198
|
+
await axios.put(`${BASE_URL}/v18/settings/${typePath}/${thingId}/${subDeviceId}/${controlPath}`, null, {
|
|
187
199
|
params: {
|
|
188
200
|
createDate,
|
|
189
201
|
[controlType === 'WINDSPEED' ? 'mode' : 'state']: state,
|
|
@@ -191,13 +203,15 @@ export class ShomeClient {
|
|
|
191
203
|
},
|
|
192
204
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
193
205
|
});
|
|
194
|
-
const displayName = nickname ||
|
|
206
|
+
const displayName = nickname || deviceId;
|
|
195
207
|
this.log.info(`[${displayName}] state set to ${state}.`);
|
|
196
208
|
return true;
|
|
197
|
-
}
|
|
209
|
+
};
|
|
210
|
+
return this.enqueuePut(request, deviceId);
|
|
198
211
|
}
|
|
199
212
|
async unlockDoorlock(thingId, nickname) {
|
|
200
|
-
|
|
213
|
+
const deviceId = thingId;
|
|
214
|
+
const request = async () => {
|
|
201
215
|
const token = this.cachedAccessToken;
|
|
202
216
|
if (!token) {
|
|
203
217
|
return false;
|
|
@@ -215,6 +229,24 @@ export class ShomeClient {
|
|
|
215
229
|
const displayName = nickname || thingId;
|
|
216
230
|
this.log.info(`Unlocked [${displayName}].`);
|
|
217
231
|
return true;
|
|
232
|
+
};
|
|
233
|
+
return this.enqueuePut(request, deviceId);
|
|
234
|
+
}
|
|
235
|
+
async getVisitorHistory() {
|
|
236
|
+
return this.executeWithRetries(async () => {
|
|
237
|
+
const token = this.cachedAccessToken;
|
|
238
|
+
if (!token || !this.homeId) {
|
|
239
|
+
this.log.error('Cannot fetch visitor history: Not logged in or homeId is missing.');
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const createDate = this.getDateTime();
|
|
243
|
+
const offset = 0;
|
|
244
|
+
const hashData = this.sha512(`IHRESTAPI${this.homeId}${offset}${createDate}`);
|
|
245
|
+
const response = await axios.get(`${BASE_URL}/v16/histories/${this.homeId}/video-histories`, {
|
|
246
|
+
params: { createDate, hashData, offset },
|
|
247
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
248
|
+
});
|
|
249
|
+
return response.data.videoList || [];
|
|
218
250
|
});
|
|
219
251
|
}
|
|
220
252
|
sha512(input) {
|
package/dist/shomeClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shomeClient.js","sourceRoot":"","sources":["../src/shomeClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,WAAW,CAAC;AAGjC,MAAM,QAAQ,GAAG,mCAAmC,CAAC;AACrD,MAAM,YAAY,GAAG,kEAAkE,CAAC;AACxF,MAAM,kBAAkB,GAAG,yEAAyE,CAAC;AACrG,MAAM,QAAQ,GAAG,KAAK,CAAC;AACvB,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,gBAAgB,GAAG,GAAG,CAAC,CAAC,wBAAwB;
|
|
1
|
+
{"version":3,"file":"shomeClient.js","sourceRoot":"","sources":["../src/shomeClient.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
|
|
4
|
+
export class DoorbellAccessory {
|
|
5
|
+
private service: Service;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly platform: ShomePlatform,
|
|
9
|
+
private readonly accessory: PlatformAccessory,
|
|
10
|
+
) {
|
|
11
|
+
|
|
12
|
+
// set accessory information
|
|
13
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
|
14
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'sHome')
|
|
15
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'sHome Doorbell')
|
|
16
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'shome-doorbell');
|
|
17
|
+
this.service = this.accessory.getService(this.platform.Service.StatelessProgrammableSwitch)
|
|
18
|
+
|| this.accessory.addService(this.platform.Service.StatelessProgrammableSwitch);
|
|
19
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.displayName);
|
|
20
|
+
this.service.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public ring(visitorLabel: string) {
|
|
24
|
+
this.platform.log.info(`Triggered Doorbell event from [${visitorLabel}]`);
|
|
25
|
+
|
|
26
|
+
this.service.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent)
|
|
27
|
+
.updateValue(this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/platform.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';
|
|
2
2
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
3
|
-
import { ShomeClient, MainDevice, SubDevice } from './shomeClient.js';
|
|
3
|
+
import { ShomeClient, MainDevice, SubDevice, Visitor } from './shomeClient.js';
|
|
4
4
|
import { LightAccessory } from './accessories/lightAccessory.js';
|
|
5
5
|
import { VentilatorAccessory } from './accessories/ventilatorAccessory.js';
|
|
6
6
|
import { HeaterAccessory } from './accessories/heaterAccessory.js';
|
|
7
7
|
import { DoorlockAccessory } from './accessories/doorlockAccessory.js';
|
|
8
|
+
import { DoorbellAccessory } from './accessories/doorbellAccessory.js';
|
|
8
9
|
|
|
9
10
|
const CONTROLLABLE_MULTI_DEVICE_TYPES = ['LIGHT', 'HEATER', 'VENTILATOR'];
|
|
10
11
|
const SPECIAL_CONTROLLABLE_TYPES = ['DOORLOCK'];
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
type AccessoryHandler = LightAccessory | VentilatorAccessory | HeaterAccessory | DoorlockAccessory;
|
|
13
|
+
type AccessoryHandler = LightAccessory | VentilatorAccessory | HeaterAccessory | DoorlockAccessory | DoorbellAccessory;
|
|
14
14
|
|
|
15
15
|
export class ShomePlatform implements DynamicPlatformPlugin {
|
|
16
16
|
public readonly Service: typeof Service;
|
|
@@ -18,8 +18,10 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
18
18
|
public readonly accessories: PlatformAccessory[] = [];
|
|
19
19
|
public readonly shomeClient: ShomeClient;
|
|
20
20
|
private readonly accessoryHandlers = new Map<string, AccessoryHandler>();
|
|
21
|
-
private pollingInterval: number;
|
|
21
|
+
private readonly pollingInterval: number;
|
|
22
|
+
|
|
22
23
|
private pollingTimer?: NodeJS.Timeout;
|
|
24
|
+
private lastCheckedTimestamp: Date = new Date();
|
|
23
25
|
|
|
24
26
|
constructor(
|
|
25
27
|
public readonly log: Logger,
|
|
@@ -44,6 +46,8 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
44
46
|
this.config.deviceId,
|
|
45
47
|
);
|
|
46
48
|
|
|
49
|
+
this.log.info(`Doorbell notification service is active. Baseline time: ${this.lastCheckedTimestamp.toISOString()}`);
|
|
50
|
+
|
|
47
51
|
this.api.on('didFinishLaunching', () => {
|
|
48
52
|
this.discoverDevices();
|
|
49
53
|
if (this.pollingInterval > 0) {
|
|
@@ -72,10 +76,6 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
72
76
|
const devices = await this.shomeClient.getDeviceList();
|
|
73
77
|
const foundAccessories: PlatformAccessory[] = [];
|
|
74
78
|
|
|
75
|
-
if (!devices || devices.length === 0) {
|
|
76
|
-
this.log.warn('No devices found on your sHome account.');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
79
|
for (const device of devices) {
|
|
80
80
|
if (CONTROLLABLE_MULTI_DEVICE_TYPES.includes(device.thngModelTypeName)) {
|
|
81
81
|
const deviceInfoList = await this.shomeClient.getDeviceInfo(device.thngId, device.thngModelTypeName);
|
|
@@ -90,11 +90,14 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
90
90
|
const uuid = this.api.hap.uuid.generate(device.thngId);
|
|
91
91
|
const accessory = this.setupAccessory(device, null, uuid);
|
|
92
92
|
foundAccessories.push(accessory);
|
|
93
|
-
} else {
|
|
94
|
-
this.log.info(`Ignoring device: ${device.nickname} (Type: ${device.thngModelTypeName})`);
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
const doorbellUUID = this.api.hap.uuid.generate('shome-doorbell-accessory');
|
|
97
|
+
const doorbellDevice = { thngModelTypeName: 'DOORBELL', nickname: 'Doorbell', thngId: 'shome-doorbell' } as MainDevice;
|
|
98
|
+
const doorbellAccessory = this.setupAccessory(doorbellDevice, null, doorbellUUID);
|
|
99
|
+
foundAccessories.push(doorbellAccessory);
|
|
100
|
+
|
|
98
101
|
const accessoriesToRemove = this.accessories.filter(cachedAccessory =>
|
|
99
102
|
!foundAccessories.some(foundAccessory => foundAccessory.UUID === cachedAccessory.UUID),
|
|
100
103
|
);
|
|
@@ -114,29 +117,31 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
114
117
|
const displayName = subDevice ? subDevice.nickname : mainDevice.nickname;
|
|
115
118
|
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
|
|
116
119
|
|
|
120
|
+
const accessory = existingAccessory ?? new this.api.platformAccessory(displayName, uuid);
|
|
121
|
+
accessory.context.device = mainDevice;
|
|
122
|
+
accessory.context.subDevice = subDevice;
|
|
123
|
+
|
|
117
124
|
if (existingAccessory) {
|
|
118
125
|
this.log.info('Restoring existing accessory from cache:', displayName);
|
|
119
|
-
existingAccessory.context.device = mainDevice;
|
|
120
|
-
existingAccessory.context.subDevice = subDevice;
|
|
121
|
-
this.createAccessory(existingAccessory);
|
|
122
|
-
return existingAccessory;
|
|
123
126
|
} else {
|
|
124
127
|
this.log.info('Adding new accessory:', displayName);
|
|
125
|
-
const accessory = new this.api.platformAccessory(displayName, uuid);
|
|
126
|
-
accessory.context.device = mainDevice;
|
|
127
|
-
accessory.context.subDevice = subDevice;
|
|
128
|
-
this.createAccessory(accessory);
|
|
129
128
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
130
|
-
return accessory;
|
|
131
129
|
}
|
|
130
|
+
|
|
131
|
+
this.createAccessoryHandler(accessory);
|
|
132
|
+
return accessory;
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
createAccessoryHandler(accessory: PlatformAccessory) {
|
|
135
136
|
const device = accessory.context.device;
|
|
136
137
|
const accessoryType = device.thngModelTypeName;
|
|
137
138
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
139
|
+
if (this.accessoryHandlers.has(accessory.UUID)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (accessory.context.subDevice) {
|
|
144
|
+
switch (device.thngModelTypeName) {
|
|
140
145
|
case 'LIGHT':
|
|
141
146
|
this.accessoryHandlers.set(accessory.UUID, new LightAccessory(this, accessory));
|
|
142
147
|
break;
|
|
@@ -146,42 +151,107 @@ export class ShomePlatform implements DynamicPlatformPlugin {
|
|
|
146
151
|
case 'HEATER':
|
|
147
152
|
this.accessoryHandlers.set(accessory.UUID, new HeaterAccessory(this, accessory));
|
|
148
153
|
break;
|
|
149
|
-
case 'DOORLOCK':
|
|
150
|
-
this.accessoryHandlers.set(accessory.UUID, new DoorlockAccessory(this, accessory));
|
|
151
|
-
break;
|
|
152
154
|
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
switch (accessoryType) {
|
|
159
|
+
case 'DOORLOCK':
|
|
160
|
+
this.accessoryHandlers.set(accessory.UUID, new DoorlockAccessory(this, accessory));
|
|
161
|
+
break;
|
|
162
|
+
case 'DOORBELL':
|
|
163
|
+
this.accessoryHandlers.set(accessory.UUID, new DoorbellAccessory(this, accessory));
|
|
164
|
+
break;
|
|
153
165
|
}
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
startPolling() {
|
|
157
|
-
this.log.info(`Starting periodic
|
|
169
|
+
this.log.info(`Starting periodic state polling every ${this.pollingInterval / 1000} seconds.`);
|
|
158
170
|
this.pollingTimer = setInterval(async () => {
|
|
159
|
-
this.log.debug('Polling for
|
|
171
|
+
this.log.debug('Polling for updates...');
|
|
160
172
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
await this.pollDeviceUpdates();
|
|
174
|
+
await this.checkForNewVisitors();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.log.error('An error occurred during polling:', error);
|
|
177
|
+
}
|
|
178
|
+
}, this.pollingInterval);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async pollDeviceUpdates() {
|
|
182
|
+
const devices = await this.shomeClient.getDeviceList();
|
|
183
|
+
for (const device of devices) {
|
|
184
|
+
if (CONTROLLABLE_MULTI_DEVICE_TYPES.includes(device.thngModelTypeName)) {
|
|
185
|
+
const deviceInfoList = await this.shomeClient.getDeviceInfo(device.thngId, device.thngModelTypeName);
|
|
186
|
+
if (deviceInfoList) {
|
|
187
|
+
for (const subDevice of deviceInfoList) {
|
|
188
|
+
const deviceId = `${device.thngId}-${subDevice.deviceId}`;
|
|
189
|
+
if (this.shomeClient.isDeviceBusy(deviceId)) {
|
|
190
|
+
this.log.debug(`Skipping polling update for ${subDevice.nickname} as it has a pending request.`);
|
|
191
|
+
continue;
|
|
173
192
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
const handler = this.accessoryHandlers.get(uuid) as DoorlockAccessory;
|
|
193
|
+
const subUuid = this.api.hap.uuid.generate(deviceId);
|
|
194
|
+
const handler = this.accessoryHandlers.get(subUuid) as LightAccessory | HeaterAccessory | VentilatorAccessory;
|
|
177
195
|
if (handler) {
|
|
178
|
-
handler.updateState(
|
|
196
|
+
handler.updateState(subDevice);
|
|
179
197
|
}
|
|
180
198
|
}
|
|
181
199
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
200
|
+
} else if (SPECIAL_CONTROLLABLE_TYPES.includes(device.thngModelTypeName)) {
|
|
201
|
+
const deviceId = device.thngId;
|
|
202
|
+
if (this.shomeClient.isDeviceBusy(deviceId)) {
|
|
203
|
+
this.log.debug(`Skipping polling update for ${device.nickname} as it has a pending request.`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const uuid = this.api.hap.uuid.generate(deviceId);
|
|
207
|
+
const handler = this.accessoryHandlers.get(uuid) as DoorlockAccessory;
|
|
208
|
+
if (handler) {
|
|
209
|
+
handler.updateState(device);
|
|
210
|
+
}
|
|
184
211
|
}
|
|
185
|
-
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async checkForNewVisitors() {
|
|
216
|
+
this.log.debug('Checking for new doorbell events...');
|
|
217
|
+
const visitorList = await this.shomeClient.getVisitorHistory();
|
|
218
|
+
const newVisitors: Visitor[] = [];
|
|
219
|
+
|
|
220
|
+
for (const visitor of visitorList) {
|
|
221
|
+
const visitorTime = this.parseRecodDt(visitor.recodDt);
|
|
222
|
+
if (visitorTime > this.lastCheckedTimestamp) {
|
|
223
|
+
newVisitors.push(visitor);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (newVisitors.length > 0) {
|
|
228
|
+
this.log.info(`Found ${newVisitors.length} new doorbell event(s).`);
|
|
229
|
+
newVisitors.sort((a, b) => a.recodDt.localeCompare(b.recodDt));
|
|
230
|
+
|
|
231
|
+
const doorbellUUID = this.api.hap.uuid.generate('shome-doorbell-accessory');
|
|
232
|
+
const doorbellHandler = this.accessoryHandlers.get(doorbellUUID) as DoorbellAccessory | undefined;
|
|
233
|
+
|
|
234
|
+
if (doorbellHandler) {
|
|
235
|
+
for (const visitor of newVisitors) {
|
|
236
|
+
doorbellHandler.ring(visitor.deviceLabel);
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
this.log.warn('Doorbell accessory handler not found.');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const latestVisitor = newVisitors[newVisitors.length - 1];
|
|
243
|
+
this.lastCheckedTimestamp = this.parseRecodDt(latestVisitor.recodDt);
|
|
244
|
+
this.log.debug(`Updated last checked timestamp to: ${this.lastCheckedTimestamp.toISOString()}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private parseRecodDt(recodDt: string): Date {
|
|
249
|
+
const year = parseInt(recodDt.substring(0, 4), 10);
|
|
250
|
+
const month = parseInt(recodDt.substring(4, 6), 10) - 1;
|
|
251
|
+
const day = parseInt(recodDt.substring(6, 8), 10);
|
|
252
|
+
const hour = parseInt(recodDt.substring(8, 10), 10);
|
|
253
|
+
const minute = parseInt(recodDt.substring(10, 12), 10);
|
|
254
|
+
const second = parseInt(recodDt.substring(12, 14), 10);
|
|
255
|
+
return new Date(year, month, day, hour, minute, second);
|
|
186
256
|
}
|
|
187
257
|
}
|
package/src/shomeClient.ts
CHANGED
|
@@ -24,20 +24,30 @@ export interface SubDevice {
|
|
|
24
24
|
[key: string]: unknown;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface Visitor {
|
|
28
|
+
sttId: string;
|
|
29
|
+
thumbNail: string;
|
|
30
|
+
recodDt: string;
|
|
31
|
+
deviceLabel: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
type QueueTask<T = unknown> = {
|
|
28
35
|
request: () => Promise<T>;
|
|
29
36
|
resolve: (value: T | PromiseLike<T>) => void;
|
|
30
37
|
reject: (reason?: unknown) => void;
|
|
38
|
+
deviceId?: string;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
export class ShomeClient {
|
|
34
42
|
private cachedAccessToken: string | null = null;
|
|
35
43
|
private ihdId: string | null = null;
|
|
44
|
+
private homeId: string | null = null;
|
|
36
45
|
private tokenExpiry: number = 0;
|
|
37
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
47
|
private putQueue: QueueTask<any>[] = [];
|
|
39
48
|
private isProcessingPut = false;
|
|
40
49
|
private loginPromise: Promise<string | null> | null = null;
|
|
50
|
+
private pendingPutRequests = new Set<string>();
|
|
41
51
|
|
|
42
52
|
constructor(
|
|
43
53
|
private readonly log: Logger,
|
|
@@ -100,6 +110,7 @@ export class ShomeClient {
|
|
|
100
110
|
if (response.data && response.data.accessToken) {
|
|
101
111
|
this.cachedAccessToken = response.data.accessToken;
|
|
102
112
|
this.ihdId = response.data.ihdId;
|
|
113
|
+
this.homeId = response.data.homeId;
|
|
103
114
|
|
|
104
115
|
const payload = JSON.parse(Buffer.from(this.cachedAccessToken!.split('.')[1], 'base64').toString());
|
|
105
116
|
this.tokenExpiry = payload.exp * 1000;
|
|
@@ -116,9 +127,9 @@ export class ShomeClient {
|
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
private enqueuePut<T>(request: () => Promise<T
|
|
130
|
+
private enqueuePut<T>(request: () => Promise<T>, deviceId?: string): Promise<T> {
|
|
120
131
|
return new Promise<T>((resolve, reject) => {
|
|
121
|
-
this.putQueue.push({ request, resolve, reject });
|
|
132
|
+
this.putQueue.push({ request, resolve, reject, deviceId });
|
|
122
133
|
this.processPutQueue();
|
|
123
134
|
});
|
|
124
135
|
}
|
|
@@ -131,11 +142,18 @@ export class ShomeClient {
|
|
|
131
142
|
|
|
132
143
|
while (this.putQueue.length > 0) {
|
|
133
144
|
const task = this.putQueue.shift()!;
|
|
145
|
+
if (task.deviceId) {
|
|
146
|
+
this.pendingPutRequests.add(task.deviceId);
|
|
147
|
+
}
|
|
134
148
|
try {
|
|
135
149
|
const result = await this.executeWithRetries(task.request, true);
|
|
136
150
|
task.resolve(result);
|
|
137
151
|
} catch (error) {
|
|
138
152
|
task.reject(error);
|
|
153
|
+
} finally {
|
|
154
|
+
if (task.deviceId) {
|
|
155
|
+
this.pendingPutRequests.delete(task.deviceId);
|
|
156
|
+
}
|
|
139
157
|
}
|
|
140
158
|
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
|
141
159
|
}
|
|
@@ -147,8 +165,6 @@ export class ShomeClient {
|
|
|
147
165
|
let retries = 0;
|
|
148
166
|
while (true) {
|
|
149
167
|
try {
|
|
150
|
-
// For non-queued (concurrent) requests, we need to ensure login happens before the request.
|
|
151
|
-
// For queued requests, the login is handled as part of the queue, so we can just await it.
|
|
152
168
|
if (!isQueued) {
|
|
153
169
|
await this.login();
|
|
154
170
|
}
|
|
@@ -174,12 +190,15 @@ export class ShomeClient {
|
|
|
174
190
|
}
|
|
175
191
|
await new Promise(resolve => setTimeout(resolve, backoffTime));
|
|
176
192
|
|
|
177
|
-
// After a failure, always ensure we are logged in before the next attempt.
|
|
178
193
|
await this.login();
|
|
179
194
|
}
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
197
|
|
|
198
|
+
public isDeviceBusy(deviceId: string): boolean {
|
|
199
|
+
return this.pendingPutRequests.has(deviceId);
|
|
200
|
+
}
|
|
201
|
+
|
|
183
202
|
async getDeviceList(): Promise<MainDevice[]> {
|
|
184
203
|
return this.executeWithRetries(async () => {
|
|
185
204
|
const token = this.cachedAccessToken;
|
|
@@ -219,19 +238,20 @@ export class ShomeClient {
|
|
|
219
238
|
});
|
|
220
239
|
}
|
|
221
240
|
|
|
222
|
-
async setDevice(thingId: string,
|
|
223
|
-
|
|
241
|
+
async setDevice(thingId: string, subDeviceId: string, type: string, controlType: string, state: string, nickname?: string): Promise<boolean> {
|
|
242
|
+
const deviceId = `${thingId}-${subDeviceId}`;
|
|
243
|
+
const request = async () => {
|
|
224
244
|
const token = this.cachedAccessToken;
|
|
225
245
|
if (!token) {
|
|
226
246
|
return false;
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
const createDate = this.getDateTime();
|
|
230
|
-
const hashData = this.sha512(`IHRESTAPI${thingId}${
|
|
250
|
+
const hashData = this.sha512(`IHRESTAPI${thingId}${subDeviceId}${state}${createDate}`);
|
|
231
251
|
const typePath = type.toLowerCase().replace(/_/g, '');
|
|
232
252
|
const controlPath = controlType.toLowerCase().replace(/_/g, '-');
|
|
233
253
|
|
|
234
|
-
await axios.put(`${BASE_URL}/v18/settings/${typePath}/${thingId}/${
|
|
254
|
+
await axios.put(`${BASE_URL}/v18/settings/${typePath}/${thingId}/${subDeviceId}/${controlPath}`, null, {
|
|
235
255
|
params: {
|
|
236
256
|
createDate,
|
|
237
257
|
[controlType === 'WINDSPEED' ? 'mode' : 'state']: state,
|
|
@@ -240,14 +260,17 @@ export class ShomeClient {
|
|
|
240
260
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
241
261
|
});
|
|
242
262
|
|
|
243
|
-
const displayName = nickname ||
|
|
263
|
+
const displayName = nickname || deviceId;
|
|
244
264
|
this.log.info(`[${displayName}] state set to ${state}.`);
|
|
245
265
|
return true;
|
|
246
|
-
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return this.enqueuePut(request, deviceId);
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
async unlockDoorlock(thingId: string, nickname?: string): Promise<boolean> {
|
|
250
|
-
|
|
272
|
+
const deviceId = thingId;
|
|
273
|
+
const request = async () => {
|
|
251
274
|
const token = this.cachedAccessToken;
|
|
252
275
|
if (!token) {
|
|
253
276
|
return false;
|
|
@@ -268,9 +291,31 @@ export class ShomeClient {
|
|
|
268
291
|
const displayName = nickname || thingId;
|
|
269
292
|
this.log.info(`Unlocked [${displayName}].`);
|
|
270
293
|
return true;
|
|
294
|
+
};
|
|
295
|
+
return this.enqueuePut(request, deviceId);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async getVisitorHistory(): Promise<Visitor[]> {
|
|
299
|
+
return this.executeWithRetries(async () => {
|
|
300
|
+
const token = this.cachedAccessToken;
|
|
301
|
+
if (!token || !this.homeId) {
|
|
302
|
+
this.log.error('Cannot fetch visitor history: Not logged in or homeId is missing.');
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const createDate = this.getDateTime();
|
|
307
|
+
const offset = 0;
|
|
308
|
+
const hashData = this.sha512(`IHRESTAPI${this.homeId}${offset}${createDate}`);
|
|
309
|
+
const response = await axios.get(`${BASE_URL}/v16/histories/${this.homeId}/video-histories`, {
|
|
310
|
+
params: { createDate, hashData, offset },
|
|
311
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return response.data.videoList || [];
|
|
271
315
|
});
|
|
272
316
|
}
|
|
273
317
|
|
|
318
|
+
|
|
274
319
|
private sha512(input: string): string {
|
|
275
320
|
return CryptoJS.SHA512(input).toString();
|
|
276
321
|
}
|