homebridge-smartsystem 6.3.3 → 6.4.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/README.md +38 -34
- package/config.json +126 -153
- package/duotecno/master.js +85 -3
- package/duotecno/master.ts +90 -6
- package/duotecno/protocol.js +97 -25
- package/duotecno/protocol.ts +103 -27
- package/duotecno/types.js +8 -4
- package/duotecno/types.ts +12 -6
- package/index.spec.js +1 -0
- package/index.spec.ts +1 -0
- package/package.json +3 -2
- package/server/powerbase.js +15 -16
- package/server/powerbase.ts +18 -17
- package/server/smappee.js +8 -3
- package/server/smappee.ts +7 -3
- package/server/smartapp.js +11 -6
- package/server/smartapp.ts +15 -8
- package/server/views/master-detail.ejs +16 -0
- package/server/views/master-list.ejs +2 -0
- package/server/views/power-binding.ejs +3 -3
- package/server/views/power.ejs +3 -3
package/duotecno/master.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Node, Protocol, Unit, recName, Rec, TempPreset } from "./protocol";
|
|
1
|
+
import { Node, Protocol, Unit, recName, Rec, TempPreset, reqNodeAttributes } from "./protocol";
|
|
2
2
|
import { MasterConfig, WriteError, CommRecord, Message, Sanitizers } from "./types";
|
|
3
3
|
import { log, debug } from "./logger";
|
|
4
4
|
import { System } from "./system";
|
|
@@ -15,6 +15,10 @@ export class Master extends Base {
|
|
|
15
15
|
public schedule: number;
|
|
16
16
|
public date: Date;
|
|
17
17
|
public lastHeartbeat: number;
|
|
18
|
+
public protocol: number = 0; // major * 100 + minor
|
|
19
|
+
public version: number = 0; // idem
|
|
20
|
+
|
|
21
|
+
public registers: {[register: number]: number} = {};
|
|
18
22
|
|
|
19
23
|
public isOpen: boolean;
|
|
20
24
|
public isLoggedIn: boolean;
|
|
@@ -166,6 +170,10 @@ export class Master extends Base {
|
|
|
166
170
|
|
|
167
171
|
// incoming data
|
|
168
172
|
this.buffer = "";
|
|
173
|
+
|
|
174
|
+
// init to v1.00
|
|
175
|
+
this.version = 100;
|
|
176
|
+
this.protocol = 100;
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
getName() {
|
|
@@ -398,8 +406,8 @@ export class Master extends Base {
|
|
|
398
406
|
return false;
|
|
399
407
|
|
|
400
408
|
} else {
|
|
401
|
-
|
|
402
|
-
|
|
409
|
+
// don't log all incoming (no time, no sensor status messages)
|
|
410
|
+
if ((next.cmd != Rec.TimeDateStatus) && (next.cmd != Rec.Sensor))
|
|
403
411
|
this.info("received -> " + recName(next.cmd) + " -> " + ((!next.message) ?
|
|
404
412
|
"--" : ((next.message instanceof Array) ? next.message.join(",") : next.message)));
|
|
405
413
|
|
|
@@ -423,6 +431,12 @@ export class Master extends Base {
|
|
|
423
431
|
|
|
424
432
|
} else if (next.cmd === Rec.ScheduleStatus) {
|
|
425
433
|
this.receiveSchedule(next.message);
|
|
434
|
+
|
|
435
|
+
} else if (next.cmd === Rec.NodeMgtInfo) {
|
|
436
|
+
this.receiveNodeMgtInfo(next.message);
|
|
437
|
+
|
|
438
|
+
} else if (next.cmd === Rec.Register) {
|
|
439
|
+
this.receiveRegister(next.message);
|
|
426
440
|
|
|
427
441
|
} else {
|
|
428
442
|
this.info("not handled: " + next.message);
|
|
@@ -442,6 +456,12 @@ export class Master extends Base {
|
|
|
442
456
|
if (this.resolveLogin) {
|
|
443
457
|
this.resolveLogin(this.isLoggedIn);
|
|
444
458
|
this.resolveLogin = null;
|
|
459
|
+
|
|
460
|
+
// if logged in, ask protocol version
|
|
461
|
+
if (this.isLoggedIn) {
|
|
462
|
+
this.send(Protocol.buildRequestNodeMgt(reqNodeAttributes.nodeProtocol));
|
|
463
|
+
}
|
|
464
|
+
|
|
445
465
|
} else {
|
|
446
466
|
this.err("unexpected ConnectStatus ?");
|
|
447
467
|
}
|
|
@@ -451,7 +471,48 @@ export class Master extends Base {
|
|
|
451
471
|
// Info messages //
|
|
452
472
|
///////////////////
|
|
453
473
|
|
|
454
|
-
|
|
474
|
+
receiveNodeMgtInfo(message: Message) {
|
|
475
|
+
if (message[1] === reqNodeAttributes.isMaster) {
|
|
476
|
+
this.log("NodeMgtInfo - master " + this.getName() + " is set to master: " + message[2]);
|
|
477
|
+
|
|
478
|
+
} else if (message[1] === reqNodeAttributes.nodeInfo) {
|
|
479
|
+
// should be the same as the one from the DBInfo (64)
|
|
480
|
+
this.log("NodeMgtInfo - node info for " + this.getName() + ": " + message);
|
|
481
|
+
|
|
482
|
+
} else if (message[1] === reqNodeAttributes.masterSupported) {
|
|
483
|
+
this.log("NodeMgtInfo - master " + this.getName() + " can be master: " + message[2]);
|
|
484
|
+
|
|
485
|
+
} else if (message[1] === reqNodeAttributes.nodeVersion) {
|
|
486
|
+
console.log("*** -> " + message);
|
|
487
|
+
this.log("NodeMgtInfo - version " + this.getName() + ", version: " + message[2] + "." + message[3]);
|
|
488
|
+
this.version = message[2] * 100 + message[3];
|
|
489
|
+
//BUG - TODO: remove
|
|
490
|
+
this.protocol = message[2] * 100 + message[3];
|
|
491
|
+
|
|
492
|
+
} else if (message[1] === reqNodeAttributes.nodeProtocol) {
|
|
493
|
+
console.log("*** -> " + message);
|
|
494
|
+
this.log("NodeMgtInfo - protocol " + this.getName() + ", protocol: " + message[2] + "." + message[3]);
|
|
495
|
+
this.protocol = message[2] * 100 + message[3];
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
} else {
|
|
499
|
+
this.err("dropped NodeMgtInfo info message = " + message[1] + ", message: " + message);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
receiveRegister(message: Message) {
|
|
504
|
+
const register = Protocol.makeWord(message, 2);
|
|
505
|
+
const value = Protocol.makeSigned(message, 4);
|
|
506
|
+
|
|
507
|
+
if ((register >=0) && (register < 1024)) {
|
|
508
|
+
this.registers[register] = value;
|
|
509
|
+
this.log("receiveRegister(" + register + ") = " + value);
|
|
510
|
+
} else {
|
|
511
|
+
this.err("Received register out of bound: " + register);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
receiveHeartbeat(message: Message) {
|
|
455
516
|
this.lastHeartbeat = new Date().getTime();
|
|
456
517
|
//console.log("*HB* Received HB");
|
|
457
518
|
this.log("Received heartbeat at " + (new Date()));
|
|
@@ -478,6 +539,7 @@ export class Master extends Base {
|
|
|
478
539
|
this.info("received week schedule = " + this.schedule);
|
|
479
540
|
}
|
|
480
541
|
|
|
542
|
+
|
|
481
543
|
receiveDateTime(message: Message) {
|
|
482
544
|
// 71,0,9,37,3,3,4,3,21,20 -> 09:37:03 Wednesday(3) 4 march 2120
|
|
483
545
|
this.date = new Date( (message[8]-1) * 100 + message[9], message[7]-1, message[6],
|
|
@@ -540,7 +602,9 @@ export class Master extends Base {
|
|
|
540
602
|
this.system.setActiveState(unit);
|
|
541
603
|
}
|
|
542
604
|
|
|
543
|
-
|
|
605
|
+
//////////////////
|
|
606
|
+
// request info //
|
|
607
|
+
//////////////////
|
|
544
608
|
async fetchAllUnits(node: Node) {
|
|
545
609
|
for (let unitInx=0; unitInx < node.nrUnits; unitInx++) {
|
|
546
610
|
await this.fetchUnit(node, unitInx);
|
|
@@ -755,6 +819,18 @@ export class Master extends Base {
|
|
|
755
819
|
this.info("get value of " + unit.node.getName() + "-" + unit.getName());
|
|
756
820
|
}
|
|
757
821
|
|
|
822
|
+
async requestRegisterValue(register: number) {
|
|
823
|
+
if (this.version >= 209) {
|
|
824
|
+
await this.send(Protocol.buildRegister(register));
|
|
825
|
+
this.info("request register: " + register);
|
|
826
|
+
} else {
|
|
827
|
+
this.err("FC_REGISTERMAP not available before protocol v2.09")
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/////////////////////
|
|
832
|
+
// Settings values //
|
|
833
|
+
/////////////////////
|
|
758
834
|
async setUnitStatus(unit: Unit, value: number) {
|
|
759
835
|
const params = Protocol.buildSetCmd(unit.node, unit, value);
|
|
760
836
|
if (params.cmd) {
|
|
@@ -762,6 +838,15 @@ export class Master extends Base {
|
|
|
762
838
|
}
|
|
763
839
|
}
|
|
764
840
|
|
|
841
|
+
async setRegisterValue(register: number, value: number) {
|
|
842
|
+
if (this.version >= 209) {
|
|
843
|
+
await this.send(Protocol.buildRegister(register, value));
|
|
844
|
+
this.info("set register: " + register + ", to: " + value);
|
|
845
|
+
} else {
|
|
846
|
+
this.err("FC_REGISTERMAP not available before protocol v2.09")
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
765
850
|
async setSensorValue(unit: Unit, value: number) {
|
|
766
851
|
this.info("SetSensorValue of " + unit.node.logicalAddress + "/" + unit.logicalAddress + " to " + value);
|
|
767
852
|
await this.send(Protocol.buildSetPreset(unit.node, unit, TempPreset.sun, Math.trunc(value / 100 / 100 / 100) % 100));
|
|
@@ -798,5 +883,4 @@ export class Master extends Base {
|
|
|
798
883
|
this.info("set temp preset of " + unit.node.getName() + "-" + unit.getName() + ": " + (inc ? "up" : "down"));
|
|
799
884
|
}
|
|
800
885
|
|
|
801
|
-
|
|
802
886
|
}
|
package/duotecno/protocol.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.Protocol = exports.Unit = exports.Node = exports.recName = exports.Rec = exports.TempPreset = exports.cmdName = void 0;
|
|
12
|
+
exports.Protocol = exports.Unit = exports.Node = exports.recName = exports.Rec = exports.TempPreset = exports.cmdName = exports.reqNodeAttributes = void 0;
|
|
13
13
|
const types_1 = require("./types");
|
|
14
14
|
const logger_1 = require("./logger");
|
|
15
15
|
// Duotecno master IP protocol implementation
|
|
@@ -19,21 +19,29 @@ const logger_1 = require("./logger");
|
|
|
19
19
|
// Mar 2019 - v2 - rewrite to support multiple masters
|
|
20
20
|
//
|
|
21
21
|
// Dec 2019 - v3 - for app side only
|
|
22
|
+
// May-Nov 2021 - v4 - changes for pro-app
|
|
23
|
+
// Nov 2022 - Register and merge with gateway
|
|
22
24
|
///////////////////////////
|
|
23
25
|
// Commands + attributes //
|
|
24
26
|
///////////////////////////
|
|
25
27
|
var Cmd;
|
|
26
28
|
(function (Cmd) {
|
|
27
29
|
Cmd[Cmd["SetSensorValue"] = 7] = "SetSensorValue";
|
|
30
|
+
Cmd[Cmd["Internal"] = 9] = "Internal";
|
|
31
|
+
Cmd[Cmd["SetBasicAudio"] = 159] = "SetBasicAudio";
|
|
32
|
+
Cmd[Cmd["SetExtendedAudio"] = 208] = "SetExtendedAudio";
|
|
33
|
+
Cmd[Cmd["SetAVMatrix"] = 202] = "SetAVMatrix";
|
|
28
34
|
Cmd[Cmd["SetSwitch"] = 163] = "SetSwitch";
|
|
29
35
|
Cmd[Cmd["SetDimmer"] = 162] = "SetDimmer";
|
|
30
36
|
Cmd[Cmd["SetControl"] = 168] = "SetControl";
|
|
31
37
|
Cmd[Cmd["SetMotor"] = 182] = "SetMotor";
|
|
32
38
|
Cmd[Cmd["SetSensor"] = 136] = "SetSensor";
|
|
39
|
+
Cmd[Cmd["SetDateTime"] = 170] = "SetDateTime";
|
|
33
40
|
Cmd[Cmd["Login"] = 214] = "Login";
|
|
34
41
|
Cmd[Cmd["Heartbeat"] = 215] = "Heartbeat";
|
|
35
42
|
Cmd[Cmd["DatabaseInfo"] = 209] = "DatabaseInfo";
|
|
36
43
|
Cmd[Cmd["SetSchedule"] = 217] = "SetSchedule";
|
|
44
|
+
Cmd[Cmd["Register"] = 224] = "Register";
|
|
37
45
|
})(Cmd || (Cmd = {}));
|
|
38
46
|
// for Set Switch/Dimmer/Control
|
|
39
47
|
const reqDim = 3;
|
|
@@ -49,6 +57,16 @@ const reqUnitInfo = 2;
|
|
|
49
57
|
const reqUnitStatus = 3;
|
|
50
58
|
// for Schedule
|
|
51
59
|
const reqSchedule = 218;
|
|
60
|
+
// for versions et al
|
|
61
|
+
const reqNodeManagement = 220;
|
|
62
|
+
var reqNodeAttributes;
|
|
63
|
+
(function (reqNodeAttributes) {
|
|
64
|
+
reqNodeAttributes[reqNodeAttributes["masterSupported"] = 0] = "masterSupported";
|
|
65
|
+
reqNodeAttributes[reqNodeAttributes["isMaster"] = 4] = "isMaster";
|
|
66
|
+
reqNodeAttributes[reqNodeAttributes["nodeInfo"] = 5] = "nodeInfo";
|
|
67
|
+
reqNodeAttributes[reqNodeAttributes["nodeVersion"] = 6] = "nodeVersion";
|
|
68
|
+
reqNodeAttributes[reqNodeAttributes["nodeProtocol"] = 7] = "nodeProtocol";
|
|
69
|
+
})(reqNodeAttributes = exports.reqNodeAttributes || (exports.reqNodeAttributes = {}));
|
|
52
70
|
function cmdName(cmd) {
|
|
53
71
|
return Cmd[cmd] || "cmd" + cmd;
|
|
54
72
|
}
|
|
@@ -80,6 +98,8 @@ var Rec;
|
|
|
80
98
|
Rec[Rec["TimeDateStatus"] = 71] = "TimeDateStatus";
|
|
81
99
|
Rec[Rec["HeartbeatStatus"] = 72] = "HeartbeatStatus";
|
|
82
100
|
Rec[Rec["ScheduleStatus"] = 73] = "ScheduleStatus";
|
|
101
|
+
Rec[Rec["NodeMgtInfo"] = 74] = "NodeMgtInfo";
|
|
102
|
+
Rec[Rec["Register"] = 77] = "Register";
|
|
83
103
|
// return info from recDBInfo
|
|
84
104
|
Rec[Rec["Info"] = 64] = "Info";
|
|
85
105
|
// return info from reqUnitStatus
|
|
@@ -151,8 +171,8 @@ class Unit {
|
|
|
151
171
|
this.extendedType = this.extendedType || this.calcExtendedType();
|
|
152
172
|
this.name = this.name || this.getSerialNr();
|
|
153
173
|
// delete the "<" and ">" for power meters
|
|
154
|
-
if (this.extendedType ===
|
|
155
|
-
|
|
174
|
+
// if (this.extendedType === UnitExtendedType.kPower)
|
|
175
|
+
// this.name = this.name.substring(1);
|
|
156
176
|
// make a name for homekit, without the | but add § is 'specials' to add "sfeer", etc...
|
|
157
177
|
// if the display name is empty make a N[nodeAdr]-U[unitAdr] name.
|
|
158
178
|
// delete all type modifiers ( $, * and ! )
|
|
@@ -229,11 +249,12 @@ class Unit {
|
|
|
229
249
|
case types_1.UnitType.kDimmer: return "03|" + name;
|
|
230
250
|
case types_1.UnitType.kSwitch: return "04|" + name;
|
|
231
251
|
case types_1.UnitType.kMood: return "09|" + name;
|
|
232
|
-
case types_1.UnitType.kInput: return "
|
|
252
|
+
case types_1.UnitType.kInput: return "10|" + name;
|
|
253
|
+
// case UnitType.kPower: return "11|" + name;
|
|
233
254
|
case types_1.UnitType.kExtendedAudio: "12|" + name;
|
|
234
|
-
case types_1.UnitType.kAudio: return "
|
|
235
|
-
case types_1.UnitType.kAV: return "
|
|
236
|
-
case types_1.UnitType.kVideo: return "
|
|
255
|
+
case types_1.UnitType.kAudio: return "13|" + name;
|
|
256
|
+
case types_1.UnitType.kAV: return "14|" + name;
|
|
257
|
+
case types_1.UnitType.kVideo: return "15|" + name;
|
|
237
258
|
case types_1.UnitType.kIRTX: return "19|" + name;
|
|
238
259
|
default: return "99|" + name;
|
|
239
260
|
}
|
|
@@ -280,9 +301,9 @@ class Unit {
|
|
|
280
301
|
//////////////////
|
|
281
302
|
// Temp / Power //
|
|
282
303
|
//////////////////
|
|
283
|
-
if ((this.type ===
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
//if ((this.type === UnitType.kTemperature) &&
|
|
305
|
+
// ((this.name[0] === "<") || (this.name[0] === ">")))
|
|
306
|
+
// return UnitExtendedType.kPower;
|
|
286
307
|
/////////////
|
|
287
308
|
// Up/Down //
|
|
288
309
|
/////////////
|
|
@@ -472,12 +493,55 @@ exports.Protocol = {
|
|
|
472
493
|
.map(val => String.fromCharCode(val))
|
|
473
494
|
.join("");
|
|
474
495
|
},
|
|
496
|
+
makeWord: function (arr, at) {
|
|
497
|
+
return arr[at + 0] * 256 + arr[at + 1];
|
|
498
|
+
},
|
|
499
|
+
makeSignedWord: function (arr, at) {
|
|
500
|
+
if (arr[at + 0] > 127)
|
|
501
|
+
return (arr[at + 0] - 255) * 256 +
|
|
502
|
+
(arr[at + 1] - 255) - 1;
|
|
503
|
+
else
|
|
504
|
+
return arr[at + 0] * 256 + arr[at + 1];
|
|
505
|
+
},
|
|
475
506
|
makeLong: function (arr, at) {
|
|
476
507
|
return arr[at + 0] * 256 * 256 * 256 +
|
|
477
508
|
arr[at + 1] * 256 * 256 +
|
|
478
509
|
arr[at + 2] * 256 +
|
|
479
510
|
arr[at + 3];
|
|
480
511
|
},
|
|
512
|
+
makeSigned: function (arr, at) {
|
|
513
|
+
if (arr[at + 0] > 127)
|
|
514
|
+
return (arr[at + 0] - 255) * 256 * 256 * 256 +
|
|
515
|
+
(arr[at + 1] - 255) * 256 * 256 +
|
|
516
|
+
(arr[at + 2] - 255) * 256 +
|
|
517
|
+
(arr[at + 3] - 255) - 1;
|
|
518
|
+
else
|
|
519
|
+
return arr[at + 0] * 256 * 256 * 256 +
|
|
520
|
+
arr[at + 1] * 256 * 256 +
|
|
521
|
+
arr[at + 2] * 256 +
|
|
522
|
+
arr[at + 3];
|
|
523
|
+
},
|
|
524
|
+
signedToArray: function (value) {
|
|
525
|
+
if (value < 0)
|
|
526
|
+
return [256 + Math.floor(value / 256 / 256 / 256),
|
|
527
|
+
256 + Math.floor(value / 256 / 256) % 256,
|
|
528
|
+
256 + Math.floor(value / 256) % 256,
|
|
529
|
+
256 + Math.floor(value) % 256];
|
|
530
|
+
else
|
|
531
|
+
return [Math.floor(value / 256 / 256 / 256),
|
|
532
|
+
Math.floor(value / 256 / 256) % 256,
|
|
533
|
+
Math.floor(value / 256) % 256,
|
|
534
|
+
Math.floor(value) % 256];
|
|
535
|
+
},
|
|
536
|
+
signedWordToArray: function (value) {
|
|
537
|
+
if (value < 0)
|
|
538
|
+
return [256 + Math.floor(value / 256) % 256, 256 + Math.floor(value) % 256];
|
|
539
|
+
else
|
|
540
|
+
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
541
|
+
},
|
|
542
|
+
wordToArray: function (value) {
|
|
543
|
+
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
544
|
+
},
|
|
481
545
|
isStatus: function (cmd) {
|
|
482
546
|
return (cmd === Rec.Mood) ||
|
|
483
547
|
(cmd === Rec.Dimmer) ||
|
|
@@ -529,8 +593,8 @@ exports.Protocol = {
|
|
|
529
593
|
socket.write(data + String.fromCharCode(10));
|
|
530
594
|
return types_1.WriteError.writeOK;
|
|
531
595
|
}
|
|
532
|
-
catch (
|
|
533
|
-
(0, logger_1.
|
|
596
|
+
catch (e) {
|
|
597
|
+
(0, logger_1.err)("protocol", "error sending through socket " + e.message);
|
|
534
598
|
return types_1.WriteError.writeFatal;
|
|
535
599
|
}
|
|
536
600
|
}
|
|
@@ -609,6 +673,9 @@ exports.Protocol = {
|
|
|
609
673
|
buildRequestSchedule() {
|
|
610
674
|
return [reqSchedule, 0];
|
|
611
675
|
},
|
|
676
|
+
buildRequestNodeMgt(method = reqNodeAttributes.nodeProtocol) {
|
|
677
|
+
return [reqNodeManagement, method];
|
|
678
|
+
},
|
|
612
679
|
getCmdAndMethod: function (unit, value) {
|
|
613
680
|
switch (unit.type) {
|
|
614
681
|
case types_1.UnitType.kDimmer:
|
|
@@ -656,10 +723,10 @@ exports.Protocol = {
|
|
|
656
723
|
return [Cmd.SetSensor, 13, node.logicalAddress, unit.logicalAddress, preset];
|
|
657
724
|
},
|
|
658
725
|
buildSetSensor(node, unit, value) {
|
|
659
|
-
return [Cmd.SetSensorValue, 10, node.logicalAddress, unit.logicalAddress, types_1.UnitType.kTemperature,
|
|
726
|
+
return [Cmd.SetSensorValue, 10, node.logicalAddress, unit.logicalAddress, types_1.UnitType.kTemperature, ...this.wordToArray(value)];
|
|
660
727
|
},
|
|
661
728
|
buildSetPreset(node, unit, preset, value) {
|
|
662
|
-
return [Cmd.SetSensor, 1, node.logicalAddress, unit.logicalAddress, preset,
|
|
729
|
+
return [Cmd.SetSensor, 1, node.logicalAddress, unit.logicalAddress, preset, ...this.wordToArray(value)];
|
|
663
730
|
},
|
|
664
731
|
buildIncDecPreset(node, unit, inc) {
|
|
665
732
|
return [Cmd.SetSensor, (inc) ? 5 : 6, node.logicalAddress, unit.logicalAddress];
|
|
@@ -671,6 +738,16 @@ exports.Protocol = {
|
|
|
671
738
|
buildSendSchedule(schedule) {
|
|
672
739
|
return [Cmd.SetSchedule, 0, Math.max(0, Math.min(3, schedule))];
|
|
673
740
|
},
|
|
741
|
+
buildRegister(register, value) {
|
|
742
|
+
if (typeof value != "undefined") {
|
|
743
|
+
// set register
|
|
744
|
+
return [Cmd.Register, 1, ...this.wordToArray(register), ...this.signedToArray(value)];
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
// get register
|
|
748
|
+
return [Cmd.Register, 2, ...this.wordToArray(register)];
|
|
749
|
+
}
|
|
750
|
+
},
|
|
674
751
|
///////////////////
|
|
675
752
|
// Received info //
|
|
676
753
|
///////////////////
|
|
@@ -678,13 +755,13 @@ exports.Protocol = {
|
|
|
678
755
|
let kind = "-";
|
|
679
756
|
if (next.cmd === Rec.Sensor) {
|
|
680
757
|
// sensor -> value
|
|
681
|
-
unit.value = next.message
|
|
758
|
+
unit.value = this.makeWord(next.message, 9); // 10x current temperature
|
|
682
759
|
unit.status = next.message[7]; // 0=idle, 1=heating, 2=cooling
|
|
683
760
|
unit.preset = (next.message[6]) ? next.message[8] : -1; // 0=sun, 1=half sun, 2=moon, 3=half moon, -1 = off
|
|
684
|
-
unit.sun = next.message
|
|
685
|
-
unit.hsun = next.message
|
|
686
|
-
unit.moon = next.message
|
|
687
|
-
unit.hmoon = next.message
|
|
761
|
+
unit.sun = this.makeWord(next.message, 11); // 10x temperature
|
|
762
|
+
unit.hsun = this.makeWord(next.message, 13); // 10x temperature
|
|
763
|
+
unit.moon = this.makeWord(next.message, 15); // 10x temperature
|
|
764
|
+
unit.hmoon = this.makeWord(next.message, 17); // 10x temperature
|
|
688
765
|
(0, logger_1.debug)("protocol", "received status - " + unit.getDisplayName() + ", temperature = " + unit.value / 10.0 + ", sun = " + unit.sun);
|
|
689
766
|
kind = "S";
|
|
690
767
|
// Dimmers, switches and moods have
|
|
@@ -737,16 +814,11 @@ exports.Protocol = {
|
|
|
737
814
|
// only change dim value when state = 1 (ON, PIR ON, DIM STOP) for Dimmers and IRTX + for any setpoint event
|
|
738
815
|
if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
|
|
739
816
|
(next.message[4] === 11)) {
|
|
740
|
-
unit.value = next.message
|
|
817
|
+
unit.value = this.makeWord(next.message, 6);
|
|
741
818
|
}
|
|
742
819
|
(0, logger_1.debug)("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
|
|
743
820
|
kind = "M";
|
|
744
821
|
}
|
|
745
|
-
// clear the timer to turn the mood off again.
|
|
746
|
-
//if (unit.resetTimer) {
|
|
747
|
-
// clearInterval(unit.resetTimer);
|
|
748
|
-
// unit.resetTimer = null;
|
|
749
|
-
//}
|
|
750
822
|
this.alertSubscriber(unit);
|
|
751
823
|
this.emitter.emit('update', unit, kind);
|
|
752
824
|
},
|
package/duotecno/protocol.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommRecord, Message, Sanitizers, NodeInfo, UnitInfo, DBInfo, WriteError, hex, NodeType, UnitType, UnitExtendedType, UnitState, two } from "./types";
|
|
2
|
-
import { debug, log } from "./logger";
|
|
2
|
+
import { debug, log, err } from "./logger";
|
|
3
3
|
import { Master } from "./master";
|
|
4
4
|
import { SmartSocket } from "./smartsocket";
|
|
5
5
|
import { EventEmitter } from "events";
|
|
@@ -11,6 +11,8 @@ import { EventEmitter } from "events";
|
|
|
11
11
|
// Mar 2019 - v2 - rewrite to support multiple masters
|
|
12
12
|
//
|
|
13
13
|
// Dec 2019 - v3 - for app side only
|
|
14
|
+
// May-Nov 2021 - v4 - changes for pro-app
|
|
15
|
+
// Nov 2022 - Register and merge with gateway
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
///////////////////////////
|
|
@@ -18,15 +20,25 @@ import { EventEmitter } from "events";
|
|
|
18
20
|
///////////////////////////
|
|
19
21
|
enum Cmd {
|
|
20
22
|
SetSensorValue = 7,
|
|
23
|
+
Internal = 9, // smartsystem defined (not Duotecno)
|
|
24
|
+
|
|
25
|
+
SetBasicAudio = 159,
|
|
26
|
+
SetExtendedAudio = 208,
|
|
27
|
+
SetAVMatrix = 202,
|
|
28
|
+
|
|
21
29
|
SetSwitch = 163,
|
|
22
30
|
SetDimmer = 162,
|
|
23
31
|
SetControl = 168,
|
|
24
32
|
SetMotor = 182,
|
|
25
33
|
SetSensor = 136,
|
|
34
|
+
|
|
35
|
+
SetDateTime = 170,
|
|
36
|
+
|
|
26
37
|
Login = 214,
|
|
27
38
|
Heartbeat = 215,
|
|
28
39
|
DatabaseInfo = 209,
|
|
29
|
-
SetSchedule = 217
|
|
40
|
+
SetSchedule = 217,
|
|
41
|
+
Register = 224
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
// for Set Switch/Dimmer/Control
|
|
@@ -47,6 +59,16 @@ const reqUnitStatus = 3;
|
|
|
47
59
|
// for Schedule
|
|
48
60
|
const reqSchedule = 218;
|
|
49
61
|
|
|
62
|
+
// for versions et al
|
|
63
|
+
const reqNodeManagement = 220;
|
|
64
|
+
export enum reqNodeAttributes {
|
|
65
|
+
masterSupported = 0,
|
|
66
|
+
isMaster = 4,
|
|
67
|
+
nodeInfo = 5,
|
|
68
|
+
nodeVersion = 6,
|
|
69
|
+
nodeProtocol = 7
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
|
|
51
73
|
export function cmdName(cmd: number | string) {
|
|
52
74
|
return Cmd[cmd] || "cmd"+cmd;
|
|
@@ -88,6 +110,8 @@ export enum Rec {
|
|
|
88
110
|
TimeDateStatus = 71,
|
|
89
111
|
HeartbeatStatus = 72,
|
|
90
112
|
ScheduleStatus = 73,
|
|
113
|
+
NodeMgtInfo = 74,
|
|
114
|
+
Register = 77,
|
|
91
115
|
|
|
92
116
|
// return info from recDBInfo
|
|
93
117
|
Info = 64,
|
|
@@ -207,8 +231,8 @@ export class Unit {
|
|
|
207
231
|
this.name = this.name || this.getSerialNr();
|
|
208
232
|
|
|
209
233
|
// delete the "<" and ">" for power meters
|
|
210
|
-
if (this.extendedType === UnitExtendedType.kPower)
|
|
211
|
-
|
|
234
|
+
// if (this.extendedType === UnitExtendedType.kPower)
|
|
235
|
+
// this.name = this.name.substring(1);
|
|
212
236
|
|
|
213
237
|
// make a name for homekit, without the | but add § is 'specials' to add "sfeer", etc...
|
|
214
238
|
// if the display name is empty make a N[nodeAdr]-U[unitAdr] name.
|
|
@@ -290,12 +314,14 @@ export class Unit {
|
|
|
290
314
|
case UnitType.kDimmer: return "03|" + name;
|
|
291
315
|
case UnitType.kSwitch: return "04|" + name;
|
|
292
316
|
case UnitType.kMood: return "09|" + name;
|
|
293
|
-
case UnitType.kInput: return "
|
|
317
|
+
case UnitType.kInput: return "10|" + name;
|
|
318
|
+
// case UnitType.kPower: return "11|" + name;
|
|
294
319
|
case UnitType.kExtendedAudio: "12|" + name;
|
|
295
|
-
case UnitType.kAudio: return "
|
|
296
|
-
case UnitType.kAV: return "
|
|
297
|
-
case UnitType.kVideo: return "
|
|
320
|
+
case UnitType.kAudio: return "13|" + name;
|
|
321
|
+
case UnitType.kAV: return "14|" + name;
|
|
322
|
+
case UnitType.kVideo: return "15|" + name;
|
|
298
323
|
case UnitType.kIRTX: return "19|" + name;
|
|
324
|
+
|
|
299
325
|
default: return "99|" + name;
|
|
300
326
|
}
|
|
301
327
|
}
|
|
@@ -347,9 +373,9 @@ export class Unit {
|
|
|
347
373
|
//////////////////
|
|
348
374
|
// Temp / Power //
|
|
349
375
|
//////////////////
|
|
350
|
-
if ((this.type === UnitType.kTemperature) &&
|
|
351
|
-
|
|
352
|
-
|
|
376
|
+
//if ((this.type === UnitType.kTemperature) &&
|
|
377
|
+
// ((this.name[0] === "<") || (this.name[0] === ">")))
|
|
378
|
+
// return UnitExtendedType.kPower;
|
|
353
379
|
|
|
354
380
|
|
|
355
381
|
/////////////
|
|
@@ -552,12 +578,55 @@ export const Protocol = {
|
|
|
552
578
|
.join("");
|
|
553
579
|
},
|
|
554
580
|
|
|
581
|
+
makeWord: function(arr: Array<number>, at: number) {
|
|
582
|
+
return arr[at+0] * 256 + arr[at+1];
|
|
583
|
+
},
|
|
584
|
+
makeSignedWord: function(arr: Array<number>, at: number) {
|
|
585
|
+
if (arr[at+0] > 127)
|
|
586
|
+
return (arr[at+0]-255) * 256 +
|
|
587
|
+
(arr[at+1]-255) - 1;
|
|
588
|
+
else
|
|
589
|
+
return arr[at+0] * 256 + arr[at+1];
|
|
590
|
+
},
|
|
555
591
|
makeLong: function(arr: Array<number>, at: number) {
|
|
556
592
|
return arr[at+0] * 256 * 256 * 256 +
|
|
557
593
|
arr[at+1] * 256 * 256 +
|
|
558
594
|
arr[at+2] * 256 +
|
|
559
595
|
arr[at+3];
|
|
560
596
|
},
|
|
597
|
+
makeSigned: function(arr: Array<number>, at: number) {
|
|
598
|
+
if (arr[at+0] > 127)
|
|
599
|
+
return (arr[at+0]-255) * 256 * 256 * 256 +
|
|
600
|
+
(arr[at+1]-255) * 256 * 256 +
|
|
601
|
+
(arr[at+2]-255) * 256 +
|
|
602
|
+
(arr[at+3]-255) - 1;
|
|
603
|
+
else
|
|
604
|
+
return arr[at+0] * 256 * 256 * 256 +
|
|
605
|
+
arr[at+1] * 256 * 256 +
|
|
606
|
+
arr[at+2] * 256 +
|
|
607
|
+
arr[at+3];
|
|
608
|
+
},
|
|
609
|
+
signedToArray: function(value: number) {
|
|
610
|
+
if (value < 0)
|
|
611
|
+
return [256 + Math.floor(value / 256 / 256 / 256),
|
|
612
|
+
256 + Math.floor(value / 256 / 256) % 256,
|
|
613
|
+
256 + Math.floor(value / 256) % 256,
|
|
614
|
+
256 + Math.floor(value) % 256]
|
|
615
|
+
else
|
|
616
|
+
return [Math.floor(value / 256 / 256 / 256),
|
|
617
|
+
Math.floor(value / 256 / 256) % 256,
|
|
618
|
+
Math.floor(value / 256) % 256,
|
|
619
|
+
Math.floor(value) % 256];
|
|
620
|
+
},
|
|
621
|
+
signedWordToArray: function(value: number) {
|
|
622
|
+
if (value < 0)
|
|
623
|
+
return [256 + Math.floor(value / 256) % 256, 256 + Math.floor(value) % 256];
|
|
624
|
+
else
|
|
625
|
+
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
626
|
+
},
|
|
627
|
+
wordToArray: function(value: number) {
|
|
628
|
+
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
629
|
+
},
|
|
561
630
|
|
|
562
631
|
isStatus: function(cmd: number): boolean {
|
|
563
632
|
return (cmd === Rec.Mood) ||
|
|
@@ -617,8 +686,8 @@ export const Protocol = {
|
|
|
617
686
|
socket.write(data+String.fromCharCode(10));
|
|
618
687
|
return WriteError.writeOK;
|
|
619
688
|
|
|
620
|
-
} catch(
|
|
621
|
-
|
|
689
|
+
} catch(e) {
|
|
690
|
+
err("protocol", "error sending through socket " + e.message);
|
|
622
691
|
return WriteError.writeFatal;
|
|
623
692
|
}
|
|
624
693
|
} else {
|
|
@@ -715,6 +784,9 @@ export const Protocol = {
|
|
|
715
784
|
buildRequestSchedule(): Message {
|
|
716
785
|
return [reqSchedule, 0];
|
|
717
786
|
},
|
|
787
|
+
buildRequestNodeMgt(method = reqNodeAttributes.nodeProtocol): Message {
|
|
788
|
+
return [reqNodeManagement, method];
|
|
789
|
+
},
|
|
718
790
|
|
|
719
791
|
getCmdAndMethod: function(unit: Unit, value: number | boolean): CmdSet {
|
|
720
792
|
switch (unit.type) {
|
|
@@ -771,10 +843,10 @@ export const Protocol = {
|
|
|
771
843
|
return [Cmd.SetSensor, 13, node.logicalAddress, unit.logicalAddress, preset];
|
|
772
844
|
},
|
|
773
845
|
buildSetSensor(node: Node, unit: Unit, value: number): Message {
|
|
774
|
-
return [Cmd.SetSensorValue, 10, node.logicalAddress, unit.logicalAddress, UnitType.kTemperature,
|
|
846
|
+
return [Cmd.SetSensorValue, 10, node.logicalAddress, unit.logicalAddress, UnitType.kTemperature, ...this.wordToArray(value)];
|
|
775
847
|
},
|
|
776
848
|
buildSetPreset(node: Node, unit: Unit, preset: number, value: number): Message {
|
|
777
|
-
return [Cmd.SetSensor, 1, node.logicalAddress, unit.logicalAddress, preset,
|
|
849
|
+
return [Cmd.SetSensor, 1, node.logicalAddress, unit.logicalAddress, preset, ...this.wordToArray(value)];
|
|
778
850
|
},
|
|
779
851
|
buildIncDecPreset(node: Node, unit: Unit, inc: boolean): Message {
|
|
780
852
|
return [Cmd.SetSensor, (inc) ? 5 : 6, node.logicalAddress, unit.logicalAddress];
|
|
@@ -788,6 +860,16 @@ export const Protocol = {
|
|
|
788
860
|
return [Cmd.SetSchedule, 0, Math.max(0, Math.min(3, schedule))];
|
|
789
861
|
},
|
|
790
862
|
|
|
863
|
+
buildRegister(register: number, value?: number) {
|
|
864
|
+
if (typeof value != "undefined") {
|
|
865
|
+
// set register
|
|
866
|
+
return [Cmd.Register, 1, ...this.wordToArray(register), ...this.signedToArray(value)];
|
|
867
|
+
} else {
|
|
868
|
+
// get register
|
|
869
|
+
return [Cmd.Register, 2, ...this.wordToArray(register)];
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
|
|
791
873
|
///////////////////
|
|
792
874
|
// Received info //
|
|
793
875
|
///////////////////
|
|
@@ -796,13 +878,13 @@ export const Protocol = {
|
|
|
796
878
|
|
|
797
879
|
if (next.cmd === Rec.Sensor) {
|
|
798
880
|
// sensor -> value
|
|
799
|
-
unit.value = next.message
|
|
881
|
+
unit.value = this.makeWord(next.message, 9); // 10x current temperature
|
|
800
882
|
unit.status = next.message[7]; // 0=idle, 1=heating, 2=cooling
|
|
801
883
|
unit.preset = (next.message[6]) ? next.message[8] : -1; // 0=sun, 1=half sun, 2=moon, 3=half moon, -1 = off
|
|
802
|
-
unit.sun = next.message
|
|
803
|
-
unit.hsun = next.message
|
|
804
|
-
unit.moon = next.message
|
|
805
|
-
unit.hmoon = next.message
|
|
884
|
+
unit.sun = this.makeWord(next.message, 11); // 10x temperature
|
|
885
|
+
unit.hsun = this.makeWord(next.message, 13); // 10x temperature
|
|
886
|
+
unit.moon = this.makeWord(next.message, 15); // 10x temperature
|
|
887
|
+
unit.hmoon = this.makeWord(next.message, 17); // 10x temperature
|
|
806
888
|
debug("protocol", "received status - " + unit.getDisplayName() + ", temperature = " + <number>unit.value / 10.0 + ", sun = " + unit.sun);
|
|
807
889
|
kind = "S";
|
|
808
890
|
|
|
@@ -858,19 +940,13 @@ export const Protocol = {
|
|
|
858
940
|
// only change dim value when state = 1 (ON, PIR ON, DIM STOP) for Dimmers and IRTX + for any setpoint event
|
|
859
941
|
if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
|
|
860
942
|
(next.message[4] === 11)) {
|
|
861
|
-
unit.value = next.message
|
|
943
|
+
unit.value = this.makeWord(next.message, 6);
|
|
862
944
|
}
|
|
863
945
|
|
|
864
946
|
debug("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
|
|
865
947
|
kind = "M";
|
|
866
948
|
}
|
|
867
949
|
|
|
868
|
-
// clear the timer to turn the mood off again.
|
|
869
|
-
//if (unit.resetTimer) {
|
|
870
|
-
// clearInterval(unit.resetTimer);
|
|
871
|
-
// unit.resetTimer = null;
|
|
872
|
-
//}
|
|
873
|
-
|
|
874
950
|
this.alertSubscriber(unit);
|
|
875
951
|
this.emitter.emit('update', unit, kind);
|
|
876
952
|
},
|