homebridge-smartsystem 6.8.13 → 7.0.1
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/.vscode/launch.json +15 -4
- package/MIXINS-GUIDE.md +210 -0
- package/README.md +32 -0
- package/SETTINGS-UPDATE.md +195 -0
- package/TESTING-GUIDE.md +220 -0
- package/WEBSOCKET-API.md +303 -0
- package/accessories/accessory.js +20 -4
- package/accessories/accessory.ts +19 -4
- package/accessories/bulb.js +0 -5
- package/accessories/bulb.ts +1 -5
- package/accessories/dimmer.js +11 -3
- package/accessories/dimmer.ts +11 -4
- package/accessories/garagedoor.js +19 -9
- package/accessories/garagedoor.ts +21 -9
- package/accessories/lock.js +11 -3
- package/accessories/lock.ts +10 -3
- package/accessories/temperature.js +10 -2
- package/accessories/temperature.ts +9 -2
- package/accessories/windowcovering.js +30 -14
- package/accessories/windowcovering.ts +29 -15
- package/config.json +57 -19
- package/duotecno/types.js +48 -2
- package/duotecno/types.ts +75 -1
- package/package.json +12 -4
- package/raspberry-pi-setup.sh +80 -0
- package/server/HA-API.js +4 -1
- package/server/HA-API.ts +4 -1
- package/server/HB.js +6 -1
- package/server/HB.ts +5 -1
- package/server/mDNS.js +168 -0
- package/server/mDNS.ts +198 -0
- package/server/platform.js +7 -0
- package/server/platform.ts +8 -0
- package/server/proxy.js +58 -19
- package/server/proxy.ts +31 -3
- package/server/smartapp.js +397 -18
- package/server/smartapp.ts +432 -20
- package/server/socapp.js +2 -1
- package/server/socapp.ts +2 -1
- package/server/views/assets/Duotecno_logo_black.svg +10 -0
- package/server/views/assets/Duotecno_logo_white.svg +10 -0
- package/server/views/device-details.ejs +139 -0
- package/server/views/device-list.ejs +44 -0
- package/server/views/footer.ejs +0 -1
- package/server/views/head.ejs +9 -0
- package/server/views/homekit.ejs +2 -9
- package/server/views/link-details.ejs +2 -15
- package/server/views/link-list.ejs +2 -9
- package/server/views/login.ejs +8 -9
- package/server/views/master-detail.ejs +3 -9
- package/server/views/master-list.ejs +3 -10
- package/server/views/nav.ejs +44 -11
- package/server/views/node-details.ejs +1 -8
- package/server/views/power-binding.ejs +3 -10
- package/server/views/power-rule.ejs +3 -10
- package/server/views/power.ejs +8 -15
- package/server/views/proxy.ejs +1 -8
- package/server/views/scripts.ejs +10 -0
- package/server/views/service-list.ejs +2 -9
- package/server/views/settings.ejs +61 -22
- package/server/views/style.ejs +49 -5
- package/server/views/switch-details.ejs +2 -9
- package/server/views/switch-list.ejs +2 -9
- package/server/webapp.js +150 -11
- package/server/webapp.ts +166 -14
- package/{index.spec.js → testHB.js} +1 -1
- package/{run.js → testProxy.js} +2 -2
- package/{run.ts → testProxy.ts} +1 -1
- package/testWS.js +137 -0
- package/testWS.ts +174 -0
- package/run9998.js +0 -7
- package/run9998.ts +0 -6
- package/run9999.js +0 -7
- package/run9999.ts +0 -6
- /package/{index.spec.ts → testHB.ts} +0 -0
package/WEBSOCKET-API.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# WebSocket Device Control API
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The SmartApp now includes a WebSocket-based device control system that allows real-time monitoring and control of Duotecno devices with integrated power measurement support.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **WebSocket Server**: Runs on same ports as HTTP server (80 and configured port)
|
|
10
|
+
- **Real-time Device Status**: Get current relay states and power measurements
|
|
11
|
+
- **Power Measurement Integration**: Supports P1, Shelly, Smappee, or estimated power
|
|
12
|
+
- **Remote Control**: Toggle device relays by ID
|
|
13
|
+
- **Keep-alive Polling**: Send empty array to get status updates
|
|
14
|
+
|
|
15
|
+
## WebSocket Endpoint
|
|
16
|
+
|
|
17
|
+
**URL**: `ws://duotecno-gateway.local/devices`
|
|
18
|
+
|
|
19
|
+
Or using IP address: `ws://your-server-ip/devices`
|
|
20
|
+
|
|
21
|
+
## Device Configuration
|
|
22
|
+
|
|
23
|
+
### Web Interface
|
|
24
|
+
|
|
25
|
+
1. Navigate to `http://duotecno-gateway.local/devices` (or `http://your-server-ip/devices`)
|
|
26
|
+
2. Click "Add device"
|
|
27
|
+
3. Configure device:
|
|
28
|
+
- **Name**: Friendly name for the device
|
|
29
|
+
- **Master**: Select Duotecno master controller
|
|
30
|
+
- **Unit**: Select or enter unit address (node/unit)
|
|
31
|
+
- **Power Source**: Choose measurement source:
|
|
32
|
+
- **Non-measured (Estimated)**: Use fixed power estimate
|
|
33
|
+
- **P1 Meter**: Read from P1 smart meter
|
|
34
|
+
- **Shelly PM**: Read from Shelly power meter
|
|
35
|
+
- **Smappee**: Read from Smappee energy monitor
|
|
36
|
+
- **Power Channel**: Channel/register for measured sources (e.g., "0", "0+1+2")
|
|
37
|
+
- **Estimated Power**: Power in watts when not measured (used when relay is ON)
|
|
38
|
+
|
|
39
|
+
### Device ID Calculation
|
|
40
|
+
|
|
41
|
+
Device IDs are automatically calculated as:
|
|
42
|
+
```
|
|
43
|
+
Device ID = (Node Number × 256) + Unit Number
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
- Node 2, Unit 1 → ID = 513
|
|
48
|
+
- Node 1, Unit 2 → ID = 258
|
|
49
|
+
- Node 3, Unit 10 → ID = 778
|
|
50
|
+
|
|
51
|
+
## WebSocket Protocol
|
|
52
|
+
|
|
53
|
+
### Message Format
|
|
54
|
+
|
|
55
|
+
All messages are JSON arrays.
|
|
56
|
+
|
|
57
|
+
### Client → Server Messages
|
|
58
|
+
|
|
59
|
+
#### 1. Keep-alive / Status Poll
|
|
60
|
+
|
|
61
|
+
Send empty array to receive current device status:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
[]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 2. Control Relays
|
|
68
|
+
|
|
69
|
+
Send array of control commands:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
[
|
|
73
|
+
{ "id": 513, "relay": true }, // Turn ON device 513 (node 2, unit 1)
|
|
74
|
+
{ "id": 258, "relay": false } // Turn OFF device 258 (node 1, unit 2)
|
|
75
|
+
]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Server → Client Messages
|
|
79
|
+
|
|
80
|
+
Server always responds with complete device list:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
[
|
|
84
|
+
{
|
|
85
|
+
"id": 513,
|
|
86
|
+
"name": "Airco",
|
|
87
|
+
"power": 1250.5, // Current power in watts
|
|
88
|
+
"energy": 10.5, // Energy consumption in kWh (future)
|
|
89
|
+
"voltage": 230.0, // Voltage
|
|
90
|
+
"current": 5.44, // Current in amps (calculated)
|
|
91
|
+
"relay": true, // Relay state (ON/OFF)
|
|
92
|
+
"measured": true // true = real measurements, false = estimated
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": 258,
|
|
96
|
+
"name": "Zwembad pomp",
|
|
97
|
+
"power": 0,
|
|
98
|
+
"energy": 0,
|
|
99
|
+
"voltage": 230.0,
|
|
100
|
+
"current": 0,
|
|
101
|
+
"relay": false,
|
|
102
|
+
"measured": false
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Power Measurement Sources
|
|
108
|
+
|
|
109
|
+
### Smappee
|
|
110
|
+
|
|
111
|
+
- **Channel Format**: `"0"`, `"1"`, `"0+1+2"` (sum multiple channels)
|
|
112
|
+
- **Data Source**: `this.power.smappee.bindings[].value`
|
|
113
|
+
|
|
114
|
+
### P1 Meter
|
|
115
|
+
|
|
116
|
+
- **Channel Format**: `"0"`, `"1"`, `"0+1+2"`
|
|
117
|
+
- **Data Source**: `this.power.p1.bindings[].value`
|
|
118
|
+
|
|
119
|
+
### Shelly PM
|
|
120
|
+
|
|
121
|
+
- **Channel Format**: `"0"`, `"1"`, `"0+1+2"`
|
|
122
|
+
- **Data Source**: `this.power.shelly.bindings[].value`
|
|
123
|
+
|
|
124
|
+
### Non-measured (Estimated)
|
|
125
|
+
|
|
126
|
+
- **Power Calculation**: Uses `estimatedPower` when relay is ON, 0 when OFF
|
|
127
|
+
- **Use Case**: Devices without power monitoring
|
|
128
|
+
|
|
129
|
+
## Testing with testWS.ts
|
|
130
|
+
|
|
131
|
+
### Running the Test Client
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Default (localhost:80)
|
|
135
|
+
ts-node testWS.ts
|
|
136
|
+
|
|
137
|
+
# Custom server
|
|
138
|
+
ts-node testWS.ts ws://192.168.0.99:5002/devices
|
|
139
|
+
|
|
140
|
+
# Or with compiled JavaScript
|
|
141
|
+
node testWS.js ws://192.168.0.99:5002/devices
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Test Client Features
|
|
145
|
+
|
|
146
|
+
- **Auto-connect**: Connects to WebSocket server
|
|
147
|
+
- **Keep-alive**: Polls every 5 seconds with `[]`
|
|
148
|
+
- **Random Control**: Toggles 1-3 random relays every 10-15 seconds
|
|
149
|
+
- **Pretty Display**: Shows device status in formatted table
|
|
150
|
+
- **Graceful Shutdown**: Press Ctrl+C to disconnect cleanly
|
|
151
|
+
|
|
152
|
+
### Example Output
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
╔════════════════════════════════════════════════════════════════════╗
|
|
156
|
+
║ WebSocket Device Control Test Client ║
|
|
157
|
+
║ Press Ctrl+C to exit ║
|
|
158
|
+
╚════════════════════════════════════════════════════════════════════╝
|
|
159
|
+
|
|
160
|
+
🔌 Connecting to WebSocket server: ws://192.168.0.99:5002/devices
|
|
161
|
+
✅ Connected to WebSocket server
|
|
162
|
+
|
|
163
|
+
📊 Received 2 device(s):
|
|
164
|
+
════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
165
|
+
ID | Name | Power(W) | Current(A) | Voltage(V) | Relay | Measured
|
|
166
|
+
────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
513 | Airco | 1250.5 | 5.44 | 230.0 | 🟢 ON | 📊
|
|
168
|
+
258 | Zwembad pomp | 0.0 | 0.00 | 230.0 | 🔴 OFF | 📝
|
|
169
|
+
════════════════════════════════════════════════════════════════════════════════════════════════════
|
|
170
|
+
|
|
171
|
+
⏱️ Sending keep-alive poll...
|
|
172
|
+
|
|
173
|
+
🎲 Sending random control commands:
|
|
174
|
+
→ Airco (node=2, unit=1, id=513): 🔴 OFF
|
|
175
|
+
→ Zwembad pomp (node=1, unit=2, id=258): 🟢 ON
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Configuration Example
|
|
179
|
+
|
|
180
|
+
Add to your `config.json`:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"smartapp": {
|
|
185
|
+
"port": 5002,
|
|
186
|
+
"devices": [
|
|
187
|
+
{
|
|
188
|
+
"name": "Airco",
|
|
189
|
+
"masterAddress": "192.168.0.97",
|
|
190
|
+
"masterPort": 5001,
|
|
191
|
+
"logicalNodeAddress": 2,
|
|
192
|
+
"logicalAddress": 1,
|
|
193
|
+
"powerSource": "smappee",
|
|
194
|
+
"powerChannel": "0",
|
|
195
|
+
"measured": true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "Zwembad pomp",
|
|
199
|
+
"masterAddress": "192.168.0.97",
|
|
200
|
+
"masterPort": 5001,
|
|
201
|
+
"logicalNodeAddress": 1,
|
|
202
|
+
"logicalAddress": 2,
|
|
203
|
+
"powerSource": "none",
|
|
204
|
+
"estimatedPower": 750,
|
|
205
|
+
"measured": false
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Client Implementation Example
|
|
213
|
+
|
|
214
|
+
### JavaScript/TypeScript
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import * as WebSocket from 'ws';
|
|
218
|
+
|
|
219
|
+
const ws = new WebSocket('ws://192.168.0.99:5002/devices');
|
|
220
|
+
|
|
221
|
+
ws.on('open', () => {
|
|
222
|
+
console.log('Connected');
|
|
223
|
+
|
|
224
|
+
// Poll for status
|
|
225
|
+
ws.send('[]');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
ws.on('message', (data: string) => {
|
|
229
|
+
const devices = JSON.parse(data.toString());
|
|
230
|
+
console.log('Devices:', devices);
|
|
231
|
+
|
|
232
|
+
// Control a device
|
|
233
|
+
ws.send(JSON.stringify([
|
|
234
|
+
{ id: 513, relay: true }
|
|
235
|
+
]));
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Python
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
import websocket
|
|
243
|
+
import json
|
|
244
|
+
|
|
245
|
+
def on_message(ws, message):
|
|
246
|
+
devices = json.loads(message)
|
|
247
|
+
print(f"Devices: {devices}")
|
|
248
|
+
|
|
249
|
+
# Control device 513
|
|
250
|
+
ws.send(json.dumps([{"id": 513, "relay": True}]))
|
|
251
|
+
|
|
252
|
+
def on_open(ws):
|
|
253
|
+
print("Connected")
|
|
254
|
+
# Poll for status
|
|
255
|
+
ws.send("[]")
|
|
256
|
+
|
|
257
|
+
ws = websocket.WebSocketApp(
|
|
258
|
+
"ws://192.168.0.99:5002/devices",
|
|
259
|
+
on_message=on_message,
|
|
260
|
+
on_open=on_open
|
|
261
|
+
)
|
|
262
|
+
ws.run_forever()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Error Handling
|
|
266
|
+
|
|
267
|
+
- Invalid JSON: Server ignores message
|
|
268
|
+
- Non-array messages: Server logs error and ignores
|
|
269
|
+
- Invalid device IDs: Silently ignored (no such device)
|
|
270
|
+
- Connection closed: Automatic cleanup
|
|
271
|
+
|
|
272
|
+
## Security Considerations
|
|
273
|
+
|
|
274
|
+
- WebSocket uses same authentication as HTTP (if enabled)
|
|
275
|
+
- No encryption by default (use reverse proxy with WSS for production)
|
|
276
|
+
- Consider rate limiting for control commands
|
|
277
|
+
- Validate device IDs match authorized units
|
|
278
|
+
|
|
279
|
+
## Troubleshooting
|
|
280
|
+
|
|
281
|
+
### Connection Refused
|
|
282
|
+
|
|
283
|
+
- Check server is running
|
|
284
|
+
- Verify port is correct (80 or configured port)
|
|
285
|
+
- Check firewall rules
|
|
286
|
+
|
|
287
|
+
### Devices Not Updating
|
|
288
|
+
|
|
289
|
+
- Verify device configuration in web UI
|
|
290
|
+
- Check power source is configured correctly
|
|
291
|
+
- Verify channel numbers match power meter setup
|
|
292
|
+
- Check server logs for errors
|
|
293
|
+
|
|
294
|
+
### Power Always Zero
|
|
295
|
+
|
|
296
|
+
- For measured devices: Check power source connection
|
|
297
|
+
- For estimated: Verify `estimatedPower` is set
|
|
298
|
+
- Check relay state is updating correctly
|
|
299
|
+
|
|
300
|
+
## API Version
|
|
301
|
+
|
|
302
|
+
Current version: 1.0
|
|
303
|
+
Last updated: November 2025
|
package/accessories/accessory.js
CHANGED
|
@@ -49,8 +49,16 @@ class Accessory {
|
|
|
49
49
|
getState(next) {
|
|
50
50
|
if (this.unit) {
|
|
51
51
|
this.unit.reqState(unit => {
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
try {
|
|
53
|
+
if (!unit) {
|
|
54
|
+
return next(new Error("Unit state not available"));
|
|
55
|
+
}
|
|
56
|
+
next(null, unit.status);
|
|
57
|
+
(0, logger_1.log)("accessory", "getState was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.status);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
next(err);
|
|
61
|
+
}
|
|
54
62
|
}).catch(err => next(err));
|
|
55
63
|
}
|
|
56
64
|
else {
|
|
@@ -60,8 +68,16 @@ class Accessory {
|
|
|
60
68
|
getValue(next) {
|
|
61
69
|
if (this.unit) {
|
|
62
70
|
this.unit.reqState(unit => {
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
try {
|
|
72
|
+
if (!unit) {
|
|
73
|
+
return next(new Error("Unit state not available"));
|
|
74
|
+
}
|
|
75
|
+
next(null, unit.value);
|
|
76
|
+
(0, logger_1.log)("accessory", "getValue was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
next(err);
|
|
80
|
+
}
|
|
65
81
|
}).catch(err => next(err));
|
|
66
82
|
}
|
|
67
83
|
else {
|
package/accessories/accessory.ts
CHANGED
|
@@ -68,8 +68,15 @@ export class Accessory {
|
|
|
68
68
|
getState(next) {
|
|
69
69
|
if (this.unit) {
|
|
70
70
|
this.unit.reqState(unit => {
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
try {
|
|
72
|
+
if (!unit) {
|
|
73
|
+
return next(new Error("Unit state not available"));
|
|
74
|
+
}
|
|
75
|
+
next(null, unit.status);
|
|
76
|
+
log("accessory", "getState was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.status);
|
|
77
|
+
} catch(err) {
|
|
78
|
+
next(err);
|
|
79
|
+
}
|
|
73
80
|
}).catch(err => next(err));
|
|
74
81
|
} else {
|
|
75
82
|
next( new Error("accessory - getState needs to be overridden if no unit is provided.") );
|
|
@@ -79,14 +86,22 @@ export class Accessory {
|
|
|
79
86
|
getValue(next) {
|
|
80
87
|
if (this.unit) {
|
|
81
88
|
this.unit.reqState(unit => {
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
try {
|
|
90
|
+
if (!unit) {
|
|
91
|
+
return next(new Error("Unit state not available"));
|
|
92
|
+
}
|
|
93
|
+
next(null, unit.value);
|
|
94
|
+
log("accessory", "getValue was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
95
|
+
} catch(err) {
|
|
96
|
+
next(err);
|
|
97
|
+
}
|
|
84
98
|
}).catch(err => next(err));
|
|
85
99
|
} else {
|
|
86
100
|
next( new Error("accessory - getState needs to be overridden if no unit is provided.") );
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
|
|
90
105
|
setState(value, next) {
|
|
91
106
|
if (this.unit)
|
|
92
107
|
this.unit.setState(value)
|
package/accessories/bulb.js
CHANGED
|
@@ -25,11 +25,6 @@ class Bulb extends accessory_1.Accessory {
|
|
|
25
25
|
.then(() => next())
|
|
26
26
|
.catch(err => next(err));
|
|
27
27
|
}
|
|
28
|
-
setBrightness(value, next) {
|
|
29
|
-
this.unit.setState(value)
|
|
30
|
-
.then(() => next())
|
|
31
|
-
.catch(err => next(err));
|
|
32
|
-
}
|
|
33
28
|
}
|
|
34
29
|
exports.Bulb = Bulb;
|
|
35
30
|
//# sourceMappingURL=bulb.js.map
|
package/accessories/bulb.ts
CHANGED
package/accessories/dimmer.js
CHANGED
|
@@ -30,12 +30,20 @@ class Dimmer extends accessory_1.Accessory {
|
|
|
30
30
|
getBrightness(next) {
|
|
31
31
|
if (this.unit) {
|
|
32
32
|
this.unit.reqState(unit => {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
try {
|
|
34
|
+
if (!unit) {
|
|
35
|
+
return next(new Error("Unit state not available"));
|
|
36
|
+
}
|
|
37
|
+
next(null, unit.value);
|
|
38
|
+
(0, logger_1.log)("accessory", "getBrightness was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
next(err);
|
|
42
|
+
}
|
|
35
43
|
}).catch(err => next(err));
|
|
36
44
|
}
|
|
37
45
|
else {
|
|
38
|
-
next(new Error("accessory -
|
|
46
|
+
next(new Error("accessory - getBrightness needs to be overridden if no unit is provided."));
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
49
|
setPower(powerOn, next) {
|
package/accessories/dimmer.ts
CHANGED
|
@@ -41,14 +41,21 @@ export class Dimmer extends Accessory {
|
|
|
41
41
|
getBrightness(next) {
|
|
42
42
|
if (this.unit) {
|
|
43
43
|
this.unit.reqState(unit => {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
try {
|
|
45
|
+
if (!unit) {
|
|
46
|
+
return next(new Error("Unit state not available"));
|
|
47
|
+
}
|
|
48
|
+
next(null, unit.value);
|
|
49
|
+
log("accessory", "getBrightness was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
50
|
+
} catch(err) {
|
|
51
|
+
next(err);
|
|
52
|
+
}
|
|
46
53
|
}).catch(err => next(err));
|
|
47
54
|
} else {
|
|
48
|
-
next( new Error("accessory -
|
|
55
|
+
next( new Error("accessory - getBrightness needs to be overridden if no unit is provided.") );
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
|
-
|
|
58
|
+
|
|
52
59
|
setPower(powerOn, next) {
|
|
53
60
|
this.waitingSetPower = true;
|
|
54
61
|
log("accessory", "setPower: *** waiting *** " + powerOn);
|
|
@@ -13,7 +13,7 @@ const accessory_1 = require("./accessory");
|
|
|
13
13
|
class GarageDoor extends accessory_1.Accessory {
|
|
14
14
|
constructor(homebridge, unit) {
|
|
15
15
|
super(homebridge, unit);
|
|
16
|
-
this.targetState = this.homebridge.Characteristic.
|
|
16
|
+
this.targetState = this.homebridge.Characteristic.TargetDoorState.CLOSED;
|
|
17
17
|
}
|
|
18
18
|
getAccessoryServices() {
|
|
19
19
|
// GarageDoorOpener needs authentication
|
|
@@ -39,13 +39,15 @@ class GarageDoor extends accessory_1.Accessory {
|
|
|
39
39
|
return this.homebridge.Characteristic.CurrentDoorState.OPENING;
|
|
40
40
|
else if (status == types_1.UnitState.kStopped)
|
|
41
41
|
return this.homebridge.Characteristic.CurrentDoorState.STOPPED;
|
|
42
|
-
else
|
|
43
|
-
|
|
42
|
+
else {
|
|
43
|
+
(0, logger_1.log)("accessory", `Unknown Door/UnitState ${status}, defaulting to STOPPED`);
|
|
44
|
+
return this.homebridge.Characteristic.CurrentDoorState.STOPPED;
|
|
45
|
+
}
|
|
44
46
|
}
|
|
45
47
|
HB2DT(state) {
|
|
46
|
-
if (state == this.homebridge.Characteristic.
|
|
48
|
+
if (state == this.homebridge.Characteristic.TargetDoorState.OPEN)
|
|
47
49
|
return types_1.UnitMotorCmd.kOpen;
|
|
48
|
-
else if (state == this.homebridge.Characteristic.
|
|
50
|
+
else if (state == this.homebridge.Characteristic.TargetDoorState.CLOSED)
|
|
49
51
|
return types_1.UnitMotorCmd.kClose;
|
|
50
52
|
else if (state == this.homebridge.Characteristic.CurrentDoorState.STOPPED)
|
|
51
53
|
return types_1.UnitMotorCmd.kStop;
|
|
@@ -56,7 +58,7 @@ class GarageDoor extends accessory_1.Accessory {
|
|
|
56
58
|
try {
|
|
57
59
|
this.unit.reqState(unit => {
|
|
58
60
|
const hb = this.DT2HB(unit.status);
|
|
59
|
-
(0, logger_1.log)("accessory", "Get
|
|
61
|
+
(0, logger_1.log)("accessory", "Get CurrentDoorState of " + this.name + " = " + unit.status + " -> " + hb);
|
|
60
62
|
next(null, hb);
|
|
61
63
|
});
|
|
62
64
|
}
|
|
@@ -86,9 +88,17 @@ class GarageDoor extends accessory_1.Accessory {
|
|
|
86
88
|
}
|
|
87
89
|
// in response to Duotecno status messages
|
|
88
90
|
updateState() {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const current = this.DT2HB(this.unit.status);
|
|
92
|
+
const target = current === this.homebridge.Characteristic.CurrentDoorState.OPEN
|
|
93
|
+
? this.homebridge.Characteristic.TargetDoorState.OPEN
|
|
94
|
+
: this.homebridge.Characteristic.TargetDoorState.CLOSED;
|
|
95
|
+
(0, logger_1.log)("accessory", "Received updateState -> Homekit GarageDoor for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.status + " -> passing to HB (current/target): " + current + " / " + target);
|
|
96
|
+
this.me
|
|
97
|
+
.getCharacteristic(this.homebridge.Characteristic.CurrentDoorState)
|
|
98
|
+
.updateValue(current);
|
|
99
|
+
this.me
|
|
100
|
+
.getCharacteristic(this.homebridge.Characteristic.TargetDoorState)
|
|
101
|
+
.updateValue(target);
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
exports.GarageDoor = GarageDoor;
|
|
@@ -14,7 +14,7 @@ import { Accessory } from "./accessory";
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
export class GarageDoor extends Accessory {
|
|
17
|
-
targetState = this.homebridge.Characteristic.
|
|
17
|
+
targetState = this.homebridge.Characteristic.TargetDoorState.CLOSED;
|
|
18
18
|
|
|
19
19
|
constructor(homebridge: API, unit: Unit) {
|
|
20
20
|
super(homebridge, unit);
|
|
@@ -56,15 +56,17 @@ export class GarageDoor extends Accessory {
|
|
|
56
56
|
else if (status == UnitState.kStopped)
|
|
57
57
|
return this.homebridge.Characteristic.CurrentDoorState.STOPPED;
|
|
58
58
|
|
|
59
|
-
else
|
|
60
|
-
|
|
59
|
+
else {
|
|
60
|
+
log("accessory", `Unknown Door/UnitState ${status}, defaulting to STOPPED`);
|
|
61
|
+
return this.homebridge.Characteristic.CurrentDoorState.STOPPED;
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
HB2DT(state): UnitMotorCmd | null {
|
|
64
|
-
if (state == this.homebridge.Characteristic.
|
|
66
|
+
if (state == this.homebridge.Characteristic.TargetDoorState.OPEN)
|
|
65
67
|
return UnitMotorCmd.kOpen;
|
|
66
68
|
|
|
67
|
-
else if (state == this.homebridge.Characteristic.
|
|
69
|
+
else if (state == this.homebridge.Characteristic.TargetDoorState.CLOSED)
|
|
68
70
|
return UnitMotorCmd.kClose;
|
|
69
71
|
|
|
70
72
|
else if (state == this.homebridge.Characteristic.CurrentDoorState.STOPPED)
|
|
@@ -78,7 +80,7 @@ export class GarageDoor extends Accessory {
|
|
|
78
80
|
try {
|
|
79
81
|
this.unit.reqState(unit => {
|
|
80
82
|
const hb = this.DT2HB(unit.status);
|
|
81
|
-
log("accessory", "Get
|
|
83
|
+
log("accessory", "Get CurrentDoorState of " + this.name + " = " + unit.status + " -> " + hb);
|
|
82
84
|
next(null, hb);
|
|
83
85
|
});
|
|
84
86
|
|
|
@@ -114,9 +116,19 @@ export class GarageDoor extends Accessory {
|
|
|
114
116
|
|
|
115
117
|
// in response to Duotecno status messages
|
|
116
118
|
updateState() {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
const current = this.DT2HB(this.unit.status);
|
|
120
|
+
const target =
|
|
121
|
+
current === this.homebridge.Characteristic.CurrentDoorState.OPEN
|
|
122
|
+
? this.homebridge.Characteristic.TargetDoorState.OPEN
|
|
123
|
+
: this.homebridge.Characteristic.TargetDoorState.CLOSED;
|
|
124
|
+
|
|
125
|
+
log("accessory", "Received updateState -> Homekit GarageDoor for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.status + " -> passing to HB (current/target): " + current + " / " + target);
|
|
126
|
+
this.me
|
|
127
|
+
.getCharacteristic(this.homebridge.Characteristic.CurrentDoorState)
|
|
128
|
+
.updateValue(current);
|
|
129
|
+
this.me
|
|
130
|
+
.getCharacteristic(this.homebridge.Characteristic.TargetDoorState)
|
|
131
|
+
.updateValue(target);
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
}
|
package/accessories/lock.js
CHANGED
|
@@ -63,9 +63,17 @@ class Lock extends accessory_1.Accessory {
|
|
|
63
63
|
}
|
|
64
64
|
getCurrent(next) {
|
|
65
65
|
this.unit.reqState(unit => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
try {
|
|
67
|
+
if (!unit) {
|
|
68
|
+
return next(new Error("Unit state not available"));
|
|
69
|
+
}
|
|
70
|
+
(0, logger_1.log)("accessory", "HB getting current state of lock = " + this.unit.status + " of " + this.unit.getDescription());
|
|
71
|
+
next(null, !!this.unit.status);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
next(err);
|
|
75
|
+
}
|
|
76
|
+
}).catch(err => next(err));
|
|
69
77
|
}
|
|
70
78
|
// in response to Duotecno status messages
|
|
71
79
|
updateState() {
|
package/accessories/lock.ts
CHANGED
|
@@ -81,9 +81,16 @@ export class Lock extends Accessory {
|
|
|
81
81
|
|
|
82
82
|
getCurrent(next) {
|
|
83
83
|
this.unit.reqState(unit => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
try {
|
|
85
|
+
if (!unit) {
|
|
86
|
+
return next(new Error("Unit state not available"));
|
|
87
|
+
}
|
|
88
|
+
log("accessory", "HB getting current state of lock = " + this.unit.status + " of " + this.unit.getDescription());
|
|
89
|
+
next(null, !!this.unit.status);
|
|
90
|
+
} catch(err) {
|
|
91
|
+
next(err);
|
|
92
|
+
}
|
|
93
|
+
}).catch(err => next(err));
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
|
|
@@ -23,8 +23,16 @@ class Temperature extends accessory_1.Accessory {
|
|
|
23
23
|
getTemperature(next) {
|
|
24
24
|
if (this.unit) {
|
|
25
25
|
this.unit.reqState(unit => {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
try {
|
|
27
|
+
if (!unit) {
|
|
28
|
+
return next(new Error("Unit state not available"));
|
|
29
|
+
}
|
|
30
|
+
(0, logger_1.log)("accessory", "reqState/getTemperature returned a value for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
31
|
+
next(null, unit.value / 10.0);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
next(err);
|
|
35
|
+
}
|
|
28
36
|
})
|
|
29
37
|
.catch(err => next(err));
|
|
30
38
|
}
|
|
@@ -31,8 +31,15 @@ export class Temperature extends Accessory {
|
|
|
31
31
|
getTemperature(next) {
|
|
32
32
|
if (this.unit) {
|
|
33
33
|
this.unit.reqState(unit => {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
try {
|
|
35
|
+
if (!unit) {
|
|
36
|
+
return next(new Error("Unit state not available"));
|
|
37
|
+
}
|
|
38
|
+
log("accessory", "reqState/getTemperature returned a value for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
|
|
39
|
+
next(null, <number>unit.value / 10.0);
|
|
40
|
+
} catch(err) {
|
|
41
|
+
next(err);
|
|
42
|
+
}
|
|
36
43
|
})
|
|
37
44
|
.catch(err => next(err));
|
|
38
45
|
} else {
|