homebridge-smartsystem 6.8.0 → 6.8.3

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 CHANGED
@@ -301,8 +301,12 @@ Homebridge UI version
301
301
  - 3: do dimmers/up-downs
302
302
  - 4: better up-down values to and from HB
303
303
 
304
+
304
305
  ### v6.8.0 - 08/07/2025
305
306
  - 0: first version including the proxy
307
+ - 1: max/min values for hbValues
308
+ - 2: HB up/down -> don't respond to motor unit's, only macro
309
+ - 3: remember request before the login kicked in (and redirect to that one, once the login was succesful)
306
310
 
307
311
  ## Hardware
308
312
  A Raspberry Pi that connects to a Duotecno IP Node
package/config.json CHANGED
@@ -194,42 +194,6 @@
194
194
  "type": "httpdim",
195
195
  "plug": "http://192.168.0.96/light/0?brightness=$&turn=#"
196
196
  },
197
- {
198
- "logicalNodeAddress": 252,
199
- "logicalAddress": 105,
200
- "masterAddress": "192.168.0.97",
201
- "masterPort": 5001,
202
- "name": "Screen Salon Top",
203
- "displayName": "Screen Salon Top",
204
- "extendedType": 0,
205
- "nomacro": "N",
206
- "nostop": "N",
207
- "unitName": "Salon Top",
208
- "value": 0,
209
- "data": "",
210
- "header": "",
211
- "method": "GET",
212
- "type": "somfy",
213
- "plug": "1"
214
- },
215
- {
216
- "logicalNodeAddress": 252,
217
- "logicalAddress": 104,
218
- "masterAddress": "192.168.0.97",
219
- "masterPort": 5001,
220
- "name": "Screen Tim small",
221
- "displayName": "Screen Tim small",
222
- "extendedType": 0,
223
- "nomacro": "N",
224
- "nostop": "Y",
225
- "unitName": "Tim Small",
226
- "value": 0,
227
- "data": "",
228
- "header": "",
229
- "method": "GET",
230
- "type": "httpupdown",
231
- "plug": "2"
232
- },
233
197
  {
234
198
  "logicalNodeAddress": 16,
235
199
  "logicalAddress": 35,
@@ -254,8 +218,8 @@
254
218
  "password": "GMTecno",
255
219
  "links": [
256
220
  {
257
- "logicalNodeAddress": 3,
258
- "logicalAddress": 30,
221
+ "logicalNodeAddress": 252,
222
+ "logicalAddress": 104,
259
223
  "masterAddress": "192.168.0.97",
260
224
  "masterPort": 5001,
261
225
  "name": "unit",
@@ -269,7 +233,9 @@
269
233
  "TargetPosition",
270
234
  "ObstructionDetected"
271
235
  ],
272
- "type": "accessory"
236
+ "type": "accessory",
237
+ "max": 100,
238
+ "min": 0
273
239
  }
274
240
  ]
275
241
  },
@@ -304,7 +270,7 @@
304
270
  "masterPort": 5001,
305
271
  "name": "<Solar Panels",
306
272
  "displayName": "<Solar Panels",
307
- "extendedType": 4,
273
+ "extendedType": 0,
308
274
  "active": "N",
309
275
  "used": "Y",
310
276
  "group": 0
@@ -316,7 +282,7 @@
316
282
  "masterPort": 5001,
317
283
  "name": ">Cars",
318
284
  "displayName": ">Cars",
319
- "extendedType": 4,
285
+ "extendedType": 0,
320
286
  "active": "N",
321
287
  "used": "Y",
322
288
  "group": 0
@@ -328,7 +294,7 @@
328
294
  "masterPort": 5001,
329
295
  "name": ">Grid",
330
296
  "displayName": ">Grid",
331
- "extendedType": 4,
297
+ "extendedType": 0,
332
298
  "active": "N",
333
299
  "used": "Y",
334
300
  "group": 0
@@ -340,11 +306,23 @@
340
306
  "masterPort": 5001,
341
307
  "name": ">Swimmingpool",
342
308
  "displayName": ">Swimmingpool",
343
- "extendedType": 4,
309
+ "extendedType": 0,
344
310
  "active": "N",
345
311
  "used": "Y",
346
312
  "group": 0
347
313
  },
314
+ {
315
+ "logicalNodeAddress": 252,
316
+ "logicalAddress": 104,
317
+ "masterAddress": "192.168.0.97",
318
+ "masterPort": 5001,
319
+ "name": "Tim Small",
320
+ "displayName": "Tim Small",
321
+ "extendedType": 8,
322
+ "active": "Y",
323
+ "used": "Y",
324
+ "group": 0
325
+ },
348
326
  {
349
327
  "logicalNodeAddress": 252,
350
328
  "logicalAddress": 7,
@@ -365,7 +343,7 @@
365
343
  "name": "Garage $",
366
344
  "displayName": "Garage ",
367
345
  "extendedType": 8,
368
- "active": "N",
346
+ "active": "Y",
369
347
  "used": "Y",
370
348
  "group": 0
371
349
  },
@@ -376,7 +354,7 @@
376
354
  "masterPort": 5001,
377
355
  "name": "2|Dimmer",
378
356
  "displayName": "Somfy Dimmer",
379
- "extendedType": 0,
357
+ "extendedType": 1,
380
358
  "active": "Y",
381
359
  "used": "Y",
382
360
  "group": 0
@@ -388,7 +366,7 @@
388
366
  "masterPort": 5001,
389
367
  "name": "Blue LED",
390
368
  "displayName": "Blue LED",
391
- "extendedType": 0,
369
+ "extendedType": 1,
392
370
  "active": "N",
393
371
  "used": "Y",
394
372
  "group": 0
@@ -400,7 +378,7 @@
400
378
  "masterPort": 5001,
401
379
  "name": "Red LED",
402
380
  "displayName": "Red LED",
403
- "extendedType": 0,
381
+ "extendedType": 1,
404
382
  "active": "N",
405
383
  "used": "Y",
406
384
  "group": 0
@@ -15,7 +15,7 @@ var LogLevel;
15
15
  exports.logSettings = {
16
16
  "*": LogLevel.log,
17
17
  "master": LogLevel.log,
18
- "protocol": LogLevel.debug,
18
+ "protocol": LogLevel.noLog,
19
19
  "webapp": LogLevel.log,
20
20
  "p1": LogLevel.log,
21
21
  "shelly": LogLevel.log,
@@ -10,7 +10,7 @@ export enum LogLevel { noLog = 0, log = 1, debug = 2 };
10
10
  export const logSettings = {
11
11
  "*": LogLevel.log,
12
12
  "master": LogLevel.log,
13
- "protocol": LogLevel.debug,
13
+ "protocol": LogLevel.noLog,
14
14
  "webapp": LogLevel.log,
15
15
  "p1": LogLevel.log,
16
16
  "shelly": LogLevel.log,
@@ -790,6 +790,7 @@ exports.Protocol = {
790
790
  }
791
791
  else if (next.cmd === Rec.Motor) {
792
792
  // motor -> boolean/status
793
+ // [38,0,252,104,8,0,0]
793
794
  // 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
794
795
  unit.status = next.message[6];
795
796
  unit.value = next.message[6];
@@ -800,7 +801,8 @@ exports.Protocol = {
800
801
  // = EV_UNITMACROCOMMANDO
801
802
  // examples: On 50%: [69,0,NodeAddress,UnitAddress,6,1,0,50]
802
803
  // Off: [69,0,NodeAddress,UnitAddress,6,0,0,0] -> don't touch dimmer value
803
- // STATUS
804
+ // unit_type_duoswitch -> Event = 32, state 0, 1, 2
805
+ // STATUS (Event: 7 = dimmer, 5 = switch, on = 1, off = 0)
804
806
  if (((next.message[4] == 7) || (next.message[4] == 5)) && (next.message[5] == 1)) {
805
807
  // PIR ON -- override status value with "2" (our PIR ON)
806
808
  unit.status = 2;
@@ -815,6 +817,13 @@ exports.Protocol = {
815
817
  (next.message[4] === 11)) {
816
818
  unit.value = this.makeWord(next.message, 6);
817
819
  }
820
+ else if (next.message[4] === 32) {
821
+ // unit_type_duoswitch -> Event = 32
822
+ // [69,0,252,104,32,2,0,0]
823
+ // state = 0 -> stop, state = 1 -> up, state = 2 -> down
824
+ unit.value = 10 + next.message[5];
825
+ unit.status = next.message[5];
826
+ }
818
827
  (0, logger_1.debug)("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
819
828
  kind = "M";
820
829
  }
@@ -913,6 +913,7 @@ export const Protocol = {
913
913
 
914
914
  } else if (next.cmd === Rec.Motor) {
915
915
  // motor -> boolean/status
916
+ // [38,0,252,104,8,0,0]
916
917
  // 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
917
918
  unit.status = next.message[6];
918
919
  unit.value = next.message[6];
@@ -923,8 +924,9 @@ export const Protocol = {
923
924
  // = EV_UNITMACROCOMMANDO
924
925
  // examples: On 50%: [69,0,NodeAddress,UnitAddress,6,1,0,50]
925
926
  // Off: [69,0,NodeAddress,UnitAddress,6,0,0,0] -> don't touch dimmer value
927
+ // unit_type_duoswitch -> Event = 32, state 0, 1, 2
926
928
 
927
- // STATUS
929
+ // STATUS (Event: 7 = dimmer, 5 = switch, on = 1, off = 0)
928
930
  if (((next.message[4] == 7) || (next.message[4] == 5)) && (next.message[5] == 1)) {
929
931
  // PIR ON -- override status value with "2" (our PIR ON)
930
932
  unit.status = 2;
@@ -939,6 +941,13 @@ export const Protocol = {
939
941
  if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
940
942
  (next.message[4] === 11)) {
941
943
  unit.value = this.makeWord(next.message, 6);
944
+
945
+ } else if (next.message[4] === 32) {
946
+ // unit_type_duoswitch -> Event = 32
947
+ // [69,0,252,104,32,2,0,0]
948
+ // state = 0 -> stop, state = 1 -> up, state = 2 -> down
949
+ unit.value = 10 + next.message[5];
950
+ unit.status = next.message[5];
942
951
  }
943
952
 
944
953
  debug("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
package/duotecno/types.js CHANGED
@@ -154,7 +154,7 @@ var LinkType;
154
154
  LinkType["kNoType"] = "";
155
155
  LinkType["kAccessory"] = "accessory";
156
156
  })(LinkType = exports.LinkType || (exports.LinkType = {}));
157
- exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kAccessory, accName: "acc", accId: "", accService: [] });
157
+ exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: [] });
158
158
  exports.kEmptyBinding = {
159
159
  channel: "0", masterAddress: "0.0.0.0", masterPort: 5001, register: 0
160
160
  };
@@ -301,7 +301,9 @@ exports.Sanitizers = {
301
301
  link.accId = aLink.accId || "";
302
302
  link.accName = aLink.accName || "";
303
303
  link.accService = aLink.accService || [];
304
- link.type = aLink.type || LinkType.kNoType;
304
+ link.type = aLink.type || exports.kEmptyLink.type;
305
+ link.max = aLink.max || exports.kEmptyLink.max;
306
+ link.min = aLink.min || exports.kEmptyLink.min;
305
307
  return link;
306
308
  },
307
309
  switchConfig: function (aSwitch) {
package/duotecno/types.ts CHANGED
@@ -269,13 +269,15 @@ export interface Link extends UnitDef{
269
269
  accId: string;
270
270
  accService: Array<string>; // e.g. ["On", "Brightness"], ["TargetPosition", "CurrentPosition", ...]
271
271
  type: LinkType;
272
+ max?: number;
273
+ min?: number;
272
274
  unit?: Unit;
273
275
  dtValue?: number | boolean | string;
274
276
  hbValue?: number | boolean | string
275
277
  }
276
278
 
277
279
  export const kEmptyLink: Link = {
278
- ...kEmptyUnit, type: LinkType.kAccessory, accName: "acc", accId: "", accService: []
280
+ ...kEmptyUnit, type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: []
279
281
  };
280
282
 
281
283
 
@@ -519,7 +521,9 @@ export const Sanitizers = {
519
521
  link.accId = aLink.accId || "";
520
522
  link.accName = aLink.accName || "";
521
523
  link.accService = aLink.accService || [];
522
- link.type = aLink.type || LinkType.kNoType;
524
+ link.type = aLink.type || kEmptyLink.type;
525
+ link.max = aLink.max || kEmptyLink.max;
526
+ link.min = aLink.min || kEmptyLink.min;
523
527
  return link;
524
528
  },
525
529
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-smartsystem",
3
3
  "displayname": "Duotecno Bridge",
4
- "version": "6.8.0",
4
+ "version": "6.8.3",
5
5
  "description": "SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
6
6
  "main": "index.js",
7
7
  "author": "Johan Coppieters",
package/server/HB.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.hbUnsubscribe = exports.hbSubscribe = exports.hbGetValue = exports.hbSetState = exports.dt2hbvalue = exports.hb2dtvalue = exports.hbLogin = exports.hbAccessories = exports.hbService = exports.hbName = void 0;
12
+ exports.hbUnsubscribe = exports.hbSubscribe = exports.hbStop = exports.hbGetValue = exports.hbSetState = exports.dt2hbvalue = exports.hb2dtvalue = exports.hbLogin = exports.hbAccessories = exports.hbService = exports.hbName = void 0;
13
13
  const logger_1 = require("../duotecno/logger");
14
14
  const HA_API_1 = require("./HA-API");
15
15
  let gAccessories = [];
@@ -173,14 +173,15 @@ function hbLoad(value, service) {
173
173
  return (value) ? 100 : 0;
174
174
  }
175
175
  else if (typeof value === "number") {
176
- if (value <= 2) {
176
+ value = Math.round(value);
177
+ if (value <= 1) {
177
178
  return 0;
178
179
  }
179
- else if (value >= 98) {
180
+ else if (value >= 99) {
180
181
  return 100;
181
182
  }
182
183
  else {
183
- return Math.round(value);
184
+ return value;
184
185
  }
185
186
  }
186
187
  }
@@ -217,16 +218,31 @@ function hb2dtvalue(unit, value) {
217
218
  return { dtTarget, dtValue };
218
219
  }
219
220
  exports.hb2dtvalue = hb2dtvalue;
220
- function dt2hbvalue(unit, dontChangeValue) {
221
- let value = (typeof unit.value === "boolean") ? (unit.value ? 100 : 0) : Math.round(unit.value);
222
- // status Motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
223
- if (unit.isUpDown())
224
- return ((value === 1) || (value === 3)) ? 0 : ((value === 2) || (value === 4)) ? 100 : dontChangeValue;
225
- if (unit.isDimmer())
226
- return (value <= 2) ? 0 : (value >= 99) ? 100 : value;
227
- if (unit.isSwitch() || unit.isMood())
228
- return (value) ? 1 : 0;
229
- return value;
221
+ function dt2hbvalue(unit, dontChangeValue, min, max) {
222
+ // tunr into (rounded) number
223
+ let value = (typeof unit.value === "boolean") ? (unit.value ? max : min) : Math.round(unit.value);
224
+ // Virtual motor: state = 0 -> stop, state = 1 -> up, state = 2 -> down
225
+ // Real motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
226
+ if (unit.isUpDown()) {
227
+ // don't send status of real motor
228
+ return (value == 10) ? -1 : // stop [ virtual ]
229
+ (value == 11) ? max : // up [ virtual ]
230
+ (value == 12) ? min : undefined; // down [ virtual ]
231
+ // return ((value === 1) || (value === 3)) ? min : // down [ motor ]
232
+ // ((value === 2) || (value === 4)) ? max : // up [ motor ]
233
+ // (value == 0) ? dontChangeValue : // no change [ motor ]
234
+ // (value == 10) ? -1 : // stop [ virtual ]
235
+ // (value == 11) ? max : min; // up/down [ virtual ]
236
+ }
237
+ else if (unit.isDimmer()) {
238
+ return (value <= 1) ? 0 : (value >= 99) ? 100 : value;
239
+ }
240
+ else if (unit.isSwitch() || unit.isMood()) {
241
+ return (value) ? max : min;
242
+ }
243
+ else {
244
+ return value;
245
+ }
230
246
  }
231
247
  exports.dt2hbvalue = dt2hbvalue;
232
248
  function hbValue(data, service) {
@@ -276,6 +292,28 @@ function hbGetValue(server, id, service, token) {
276
292
  });
277
293
  }
278
294
  exports.hbGetValue = hbGetValue;
295
+ function hbStop(server, id, service, token) {
296
+ return __awaiter(this, void 0, void 0, function* () {
297
+ // does not exist, but we'll do a getCurrentPosition + setTargetPosition to this value
298
+ const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'GET', "", HA_API_1.ContentType.json, {
299
+ Authorization: 'Bearer ' + token
300
+ });
301
+ if (respBody && respBody.error) {
302
+ (0, logger_1.log)("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
303
+ return undefined;
304
+ }
305
+ else if (respBody && respBody.values) {
306
+ (0, logger_1.log)("HB", "hbStop GetState: " + id + " -> " + JSON.stringify(respBody.values));
307
+ const data = { value: respBody.values.CurrentPosition, characteristicType: "TargetPosition" };
308
+ (0, logger_1.log)("HB", "hbStop SetState: " + id + " -> " + JSON.stringify(data));
309
+ yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'PUT', JSON.stringify(data), HA_API_1.ContentType.json, {
310
+ Authorization: 'Bearer ' + token
311
+ });
312
+ return hbValue(respBody.values, service);
313
+ }
314
+ });
315
+ }
316
+ exports.hbStop = hbStop;
279
317
  function hbSubscribe(callback) {
280
318
  if (timer) {
281
319
  clearInterval(timer);
package/server/HB.ts CHANGED
@@ -177,12 +177,13 @@ function hbLoad(value: number | boolean, service: Array<string>): Record<string,
177
177
  if (typeof value === "boolean") {
178
178
  return (value) ? 100 : 0;
179
179
  } else if (typeof value === "number") {
180
- if (value <= 2) {
180
+ value = Math.round(value);
181
+ if (value <= 1) {
181
182
  return 0;
182
- } else if (value >= 98) {
183
+ } else if (value >= 99) {
183
184
  return 100;
184
185
  } else {
185
- return Math.round(value);
186
+ return value;
186
187
  }
187
188
  }
188
189
  }
@@ -223,16 +224,32 @@ export function hb2dtvalue(unit: Unit, value: number | boolean): {dtValue: numbe
223
224
  return {dtTarget, dtValue};
224
225
  }
225
226
 
226
- export function dt2hbvalue(unit: Unit, dontChangeValue: number): number {
227
- let value = (typeof unit.value === "boolean") ? (unit.value ? 100 : 0) : Math.round(unit.value);
227
+ export function dt2hbvalue(unit: Unit, dontChangeValue: number, min: number, max: number): number {
228
+ // tunr into (rounded) number
229
+ let value = (typeof unit.value === "boolean") ? (unit.value ? max : min) : Math.round(unit.value);
228
230
 
229
- // status Motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
230
- if (unit.isUpDown()) return ((value === 1) || (value === 3)) ? 0 : ((value === 2) || (value === 4)) ? 100 : dontChangeValue;
231
-
232
- if (unit.isDimmer()) return (value <= 2) ? 0 : (value >= 99) ? 100 : value;
233
- if (unit.isSwitch() || unit.isMood()) return (value) ? 1 : 0;
231
+ // Virtual motor: state = 0 -> stop, state = 1 -> up, state = 2 -> down
232
+ // Real motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
233
+ if (unit.isUpDown()) {
234
+ // don't send status of real motor
235
+ return (value == 10) ? -1 : // stop [ virtual ]
236
+ (value == 11) ? max : // up [ virtual ]
237
+ (value == 12) ? min : undefined; // down [ virtual ]
238
+ // return ((value === 1) || (value === 3)) ? min : // down [ motor ]
239
+ // ((value === 2) || (value === 4)) ? max : // up [ motor ]
240
+ // (value == 0) ? dontChangeValue : // no change [ motor ]
241
+ // (value == 10) ? -1 : // stop [ virtual ]
242
+ // (value == 11) ? max : min; // up/down [ virtual ]
243
+
244
+ } else if (unit.isDimmer()) {
245
+ return (value <= 1) ? 0 : (value >= 99) ? 100 : value;
246
+
247
+ } else if (unit.isSwitch() || unit.isMood()) {
248
+ return (value) ? max : min;
234
249
 
235
- return value;
250
+ } else {
251
+ return value;
252
+ }
236
253
  }
237
254
 
238
255
  function hbValue(data: Record<string, any>, service: Array<string>): number {
@@ -282,6 +299,28 @@ export async function hbGetValue(server: string, id: string, service: Array<stri
282
299
  };
283
300
  }
284
301
 
302
+ export async function hbStop(server: string, id: string, service: Array<string>, token: string) : Promise<number | undefined> {
303
+ // does not exist, but we'll do a getCurrentPosition + setTargetPosition to this value
304
+ const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
305
+ Authorization: 'Bearer ' + token
306
+ });
307
+ if (respBody && respBody.error) {
308
+ log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
309
+ return undefined;
310
+
311
+ } else if (respBody && respBody.values) {
312
+ log("HB", "hbStop GetState: " + id + " -> " + JSON.stringify(respBody.values));
313
+
314
+ const data = {value: respBody.values.CurrentPosition, characteristicType: "TargetPosition"};
315
+ log("HB", "hbStop SetState: " + id + " -> " + JSON.stringify(data));
316
+
317
+ await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
318
+ Authorization: 'Bearer ' + token
319
+ });
320
+ return hbValue(respBody.values, service);
321
+ }
322
+ }
323
+
285
324
  export function hbSubscribe(callback) {
286
325
  if (timer) {
287
326
  clearInterval(timer);
@@ -460,14 +460,22 @@ class SmartApp extends webapp_1.WebApp {
460
460
  (0, logger_1.debug)("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
461
461
  for (const link of this.links) {
462
462
  if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
463
- const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue);
464
- if ((typeof hbValue != "undefined") && (hbValue !== link.hbValue)) {
465
- (0, logger_1.log)("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
466
- link.hbValue = hbValue;
467
- yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, hbValue, this.token);
468
- }
469
- else {
470
- (0, logger_1.debug)("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
463
+ const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue, link.min, link.max);
464
+ if (typeof hbValue != "undefined") {
465
+ if (hbValue === -1) {
466
+ (0, logger_1.log)("smartapp", "-> Virtual unit send 'stop' for accessory: " + link.accName + " of Duotecno unit: " + u.getDisplayName());
467
+ const val = yield (0, HB_1.hbStop)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, this.token);
468
+ if (typeof val != "undefined")
469
+ link.hbValue = val;
470
+ }
471
+ else if (hbValue !== link.hbValue) {
472
+ (0, logger_1.log)("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
473
+ link.hbValue = hbValue;
474
+ yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, hbValue, this.token);
475
+ }
476
+ else {
477
+ (0, logger_1.debug)("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
478
+ }
471
479
  }
472
480
  }
473
481
  }
@@ -504,7 +512,7 @@ class SmartApp extends webapp_1.WebApp {
504
512
  let message;
505
513
  try {
506
514
  if (context.action === "add") {
507
- this.links.push(types_1.kEmptyLink);
515
+ this.links.push(Object.assign(Object.assign({}, types_1.kEmptyLink), { type: types_1.LinkType.kAccessory }));
508
516
  inx = this.links.length - 1;
509
517
  return this.ejs("linkDetail", context, { link: this.links[inx], id: inx,
510
518
  masters: this.system.masters, accessories, units });
@@ -536,11 +544,15 @@ class SmartApp extends webapp_1.WebApp {
536
544
  // for now only accessory links.
537
545
  const stype = context.getParam({ name: "type", type: "string", default: types_1.LinkType.kAccessory });
538
546
  const name = context.getParam({ name: "name", type: "string", default: "--" });
547
+ // max & min values
548
+ const min = context.getParam({ name: "min", type: "integer", default: 0 });
549
+ const max = context.getParam({ name: "max", type: "integer", default: 100 });
539
550
  // uniqiue id for the accessory
540
551
  const accId = context.getParam({ name: "accId", type: "string", default: "" });
541
552
  const accName = (0, HB_1.hbName)(accId);
542
553
  const accService = (0, HB_1.hbService)(accId);
543
- return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
554
+ return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, min, max };
555
+ ;
544
556
  }
545
557
  updateLink(inx, link) {
546
558
  if ((inx >= 0) && (inx < this.links.length)) {
@@ -22,7 +22,7 @@ import { Shelly } from './shelly';
22
22
  import { P1 } from './p1';
23
23
  import { setToken } from "../duotecno/config";
24
24
  import { PowerBase } from "./powerbase";
25
- import { dt2hbvalue, hb2dtvalue, hbAccessories, hbGetValue, hbLogin, hbName, hbService, hbSetState, hbSubscribe } from "./HB";
25
+ import { dt2hbvalue, hb2dtvalue, hbAccessories, hbGetValue, hbLogin, hbName, hbService, hbSetState, hbStop, hbSubscribe } from "./HB";
26
26
  import { httpRequest, ContentType } from "./HA-API";
27
27
  import { cleanStart, kEmptyProxy, ProxyConfig, setProxyConfig } from "./proxy";
28
28
 
@@ -279,7 +279,7 @@ export class SmartApp extends WebApp {
279
279
 
280
280
  } else if (context.action === "restart") {
281
281
  cleanStart(true);
282
-
282
+
283
283
  } else {
284
284
  // just show the proxy config
285
285
  }
@@ -515,15 +515,22 @@ export class SmartApp extends WebApp {
515
515
  debug("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
516
516
  for (const link of this.links) {
517
517
  if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
518
- const hbValue = dt2hbvalue(u, link.hbValue as number);
519
-
520
- if ((typeof hbValue != "undefined") && (hbValue !== link.hbValue)) {
521
- log("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
522
- link.hbValue = hbValue;
523
- await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, hbValue, this.token);
524
-
525
- } else {
526
- debug("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
518
+ const hbValue = dt2hbvalue(u, link.hbValue as number, link.min, link.max);
519
+
520
+ if (typeof hbValue != "undefined") {
521
+ if (hbValue === -1) {
522
+ log("smartapp", "-> Virtual unit send 'stop' for accessory: " + link.accName + " of Duotecno unit: " + u.getDisplayName());
523
+ const val = await hbStop(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, this.token);
524
+ if (typeof val != "undefined") link.hbValue = val;
525
+
526
+ } else if (hbValue !== link.hbValue) {
527
+ log("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
528
+ link.hbValue = hbValue;
529
+ await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, hbValue, this.token);
530
+
531
+ } else {
532
+ debug("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
533
+ }
527
534
  }
528
535
  }
529
536
  };
@@ -559,7 +566,7 @@ export class SmartApp extends WebApp {
559
566
  let message: string;
560
567
  try {
561
568
  if (context.action === "add") {
562
- this.links.push(kEmptyLink);
569
+ this.links.push({...kEmptyLink, type: LinkType.kAccessory});
563
570
  inx = this.links.length-1;
564
571
  return this.ejs("linkDetail", context, { link: this.links[inx], id: inx,
565
572
  masters: this.system.masters, accessories, units });
@@ -594,11 +601,15 @@ export class SmartApp extends WebApp {
594
601
  const stype = context.getParam({name: "type", type: "string", default: LinkType.kAccessory });
595
602
  const name = context.getParam({name: "name", type: "string", default: "--" });
596
603
 
604
+ // max & min values
605
+ const min = context.getParam({name: "min", type: "integer", default: 0 });
606
+ const max = context.getParam({name: "max", type: "integer", default: 100 });
607
+
597
608
  // uniqiue id for the accessory
598
609
  const accId = context.getParam({name: "accId", type: "string", default: "" });
599
610
  const accName = hbName(accId);
600
611
  const accService = hbService(accId);
601
- return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
612
+ return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, min, max};;
602
613
  }
603
614
 
604
615
  updateLink(inx: number, link: Link) {
@@ -66,8 +66,6 @@
66
66
  </select>
67
67
  <label for="unitSelector">or Pick a unit</label>
68
68
  </div>
69
-
70
- </div>
71
69
  </div>
72
70
 
73
71
  <div class="row">
@@ -89,6 +87,18 @@
89
87
  </div>
90
88
  </div>
91
89
 
90
+ <div class="row">
91
+ <div class="input-field col s4">
92
+ <label for="min">Off value</label>
93
+ <input placeholder="0" id="min" name="min" type="text" value="<%= link.min %>">
94
+ </div>
95
+
96
+ <div class="input-field col s4">
97
+ <label for="max">On value</label>
98
+ <input placeholder="0" id="mx" name="max" type="text" value="<%= link.max %>">
99
+ </div>
100
+ </div>
101
+
92
102
  </form>
93
103
  <%- include('/views/footer'); -%>
94
104
 
package/server/webapp.js CHANGED
@@ -20,7 +20,7 @@ const url = require("url");
20
20
  const qs = require("querystring");
21
21
  const base_1 = require("./base");
22
22
  const types = require("../duotecno/types");
23
- const LogLevel = require("../duotecno/logger");
23
+ const logger = require("../duotecno/logger");
24
24
  const logger_1 = require("../duotecno/logger");
25
25
  const child_process_1 = require("child_process");
26
26
  ;
@@ -310,9 +310,10 @@ class WebApp extends base_1.Base {
310
310
  doServe(req, res) {
311
311
  return __awaiter(this, void 0, void 0, function* () {
312
312
  const context = yield this.parseRequest(req, res);
313
+ let prevRequest = "";
313
314
  try {
314
315
  if (this.needsLogin(context)) {
315
- LogLevel.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request + "/" + context.action + " => " + (context.cookies["DTicket"] == this.token));
316
+ logger.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request + "/" + context.action + " => " + (context.cookies["DTicket"] == this.token));
316
317
  // if a password is set, check if the tickets is valid, if not: redirect to login page
317
318
  if (!this.token || (context.cookies["DTicket"] != this.token)) {
318
319
  context.request = "login";
@@ -320,6 +321,10 @@ class WebApp extends base_1.Base {
320
321
  }
321
322
  }
322
323
  const httpResult = yield this.doRequest(context);
324
+ // see if we can go to the original request...
325
+ if ((context.request === "login") && (context.path != "/login")) {
326
+ httpResult.header = Object.assign(Object.assign({}, httpResult.header), { "Set-Cookie": "DPath=" + context.path });
327
+ }
323
328
  return httpResult;
324
329
  }
325
330
  catch (e) {
@@ -393,10 +398,11 @@ class WebApp extends base_1.Base {
393
398
  const pw = context.getParam({ name: "password", type: "string" });
394
399
  const user = context.getParam({ name: "user", type: "string" });
395
400
  if (yield this.pwOK(context, user, pw)) {
396
- return this.redirect("/", { "Set-Cookie": "DTicket=" + this.token });
401
+ const path = context.getCookie("DPath") || "/";
402
+ return this.redirect(path, { "Set-Cookie": "DTicket=" + this.token });
397
403
  }
398
404
  else {
399
- return this.ejs("login", context, { message: "authentication failed" }, { "Set-Cookie": "DTicket=x" });
405
+ return this.ejs("login", context, { message: "authentication failed", user }, { "Set-Cookie": "DTicket=x" });
400
406
  }
401
407
  });
402
408
  }
package/server/webapp.ts CHANGED
@@ -9,7 +9,7 @@ import * as url from "url";
9
9
  import * as qs from "querystring";
10
10
  import { Base } from './base';
11
11
  import * as types from "../duotecno/types";
12
- import * as LogLevel from "../duotecno/logger";
12
+ import * as logger from "../duotecno/logger";
13
13
  import { log } from "../duotecno/logger";
14
14
  import { exec } from "child_process";
15
15
 
@@ -372,15 +372,20 @@ export class WebApp extends Base {
372
372
 
373
373
  async doServe(req: http.IncomingMessage, res: http.ServerResponse): Promise<HttpResponse> {
374
374
  const context = await this.parseRequest(req, res);
375
+ let prevRequest = "";
375
376
  try {
376
377
  if (this.needsLogin(context)) {
377
- LogLevel.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request+"/"+context.action + " => " + (context.cookies["DTicket"] == this.token));
378
+ logger.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request+"/"+context.action + " => " + (context.cookies["DTicket"] == this.token));
378
379
  // if a password is set, check if the tickets is valid, if not: redirect to login page
379
380
  if (!this.token || (context.cookies["DTicket"] != this.token)) {
380
381
  context.request = "login"; context.action = "";
381
382
  }
382
383
  }
383
384
  const httpResult = await this.doRequest(context);
385
+ // see if we can go to the original request...
386
+ if ((context.request === "login") && (context.path != "/login")) {
387
+ httpResult.header = {...httpResult.header, "Set-Cookie": "DPath="+context.path};
388
+ }
384
389
  return httpResult;
385
390
 
386
391
  } catch (e) {
@@ -452,10 +457,11 @@ export class WebApp extends Base {
452
457
  const user = context.getParam({name: "user", type: "string"});
453
458
 
454
459
  if (await this.pwOK(context, user, pw)) {
455
- return this.redirect("/", {"Set-Cookie": "DTicket="+this.token});
460
+ const path = context.getCookie("DPath") || "/"
461
+ return this.redirect(path, {"Set-Cookie": "DTicket="+this.token});
456
462
 
457
463
  } else {
458
- return this.ejs("login", context, {message: "authentication failed"}, {"Set-Cookie": "DTicket=x"});
464
+ return this.ejs("login", context, {message: "authentication failed", user}, {"Set-Cookie": "DTicket=x"});
459
465
  }
460
466
  }
461
467
  async doLogout(context: Context): Promise<HttpResponse> {