homebridge-smartsystem 7.1.26 → 7.2.2
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/duotecno/types.js +4 -0
- package/duotecno/udpConfig.js +90 -0
- package/package.json +1 -1
- package/server/proxy.js +11 -5
- package/server/smartapp.js +87 -6
- package/server/views/master-detail.ejs +10 -0
- package/server/views/nav.ejs +3 -3
- package/server/views/proxy.ejs +4 -2
package/duotecno/types.js
CHANGED
|
@@ -20,7 +20,9 @@ exports.kEmptyProxy = {
|
|
|
20
20
|
cloudPort: 5097,
|
|
21
21
|
masterAddress: "",
|
|
22
22
|
masterPort: 5001,
|
|
23
|
+
masterPortEnabled: false,
|
|
23
24
|
masterConfigPort: 8080,
|
|
25
|
+
masterConfigPortEnabled: false,
|
|
24
26
|
installationId: "",
|
|
25
27
|
nodeNumber: 0xff,
|
|
26
28
|
httpEnabled: false,
|
|
@@ -441,7 +443,9 @@ exports.Sanitizers = {
|
|
|
441
443
|
c.cloudPort = (config === null || config === void 0 ? void 0 : config.cloudPort) || exports.kEmptyProxy.cloudPort;
|
|
442
444
|
c.masterAddress = (config === null || config === void 0 ? void 0 : config.masterAddress) || ((_a = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _a === void 0 ? void 0 : _a.address) || "";
|
|
443
445
|
c.masterPort = (config === null || config === void 0 ? void 0 : config.masterPort) || ((_b = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _b === void 0 ? void 0 : _b.port) || 5001;
|
|
446
|
+
c.masterPortEnabled = !!(config === null || config === void 0 ? void 0 : config.masterPortEnabled);
|
|
444
447
|
c.masterConfigPort = (config === null || config === void 0 ? void 0 : config.masterConfigPort) || exports.kEmptyProxy.masterConfigPort;
|
|
448
|
+
c.masterConfigPortEnabled = !!(config === null || config === void 0 ? void 0 : config.masterConfigPortEnabled);
|
|
445
449
|
c.installationId = (config === null || config === void 0 ? void 0 : config.installationId) || exports.kEmptyProxy.installationId;
|
|
446
450
|
c.nodeNumber = (typeof (config === null || config === void 0 ? void 0 : config.nodeNumber) === "number") ? config.nodeNumber : exports.kEmptyProxy.nodeNumber;
|
|
447
451
|
c.httpEnabled = !!(config === null || config === void 0 ? void 0 : config.httpEnabled);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.setHomeServerCfg = exports.UDP_CONFIG_PORT = void 0;
|
|
13
|
+
// Minimal client for Duotecno's "UDP config tool" protocol -- just enough to
|
|
14
|
+
// push a new home-server (master) password/port to a master over UDP.
|
|
15
|
+
//
|
|
16
|
+
// Wire format reverse-engineered from the real `dtudpconfigLIB` source (see
|
|
17
|
+
// the Duotecno Designer project's src/services/udpConfig/protocol.ts for the
|
|
18
|
+
// full writeup and provenance notes). Devices listen on UDP port 8002.
|
|
19
|
+
// ASCII framing: `[b0,b1,...]` + LF, every byte its decimal string,
|
|
20
|
+
// comma-separated. Strings are length-prefixed (one length byte, then that
|
|
21
|
+
// many byte-per-char, no null terminator). Multi-byte ints are MSB-first.
|
|
22
|
+
const dgram = require("dgram");
|
|
23
|
+
exports.UDP_CONFIG_PORT = 8002;
|
|
24
|
+
const CMD_FC_APPCONFIG = 189;
|
|
25
|
+
const CMD_EV_APPCONFIG = 15;
|
|
26
|
+
const ATTR_HOMESRVCFG_SET = 2;
|
|
27
|
+
// Always written as "closed + open protocol enabled", matching the legacy
|
|
28
|
+
// config tool -- it's a read-only display value, not a user-editable input.
|
|
29
|
+
const HOMESRVCFG_PROTOCOL_FLAGS = 0x03;
|
|
30
|
+
function encodePacket(bytes) {
|
|
31
|
+
return Buffer.from(`[${bytes.join(",")}]\n`, "ascii");
|
|
32
|
+
}
|
|
33
|
+
function decodePacket(buf) {
|
|
34
|
+
const str = buf.toString("ascii").trim();
|
|
35
|
+
const match = /^\[([^\]]*)\]/.exec(str);
|
|
36
|
+
if (!match || !match[1])
|
|
37
|
+
return [];
|
|
38
|
+
return match[1].split(",").map((s) => parseInt(s.trim(), 10));
|
|
39
|
+
}
|
|
40
|
+
function stringToBytes(s) {
|
|
41
|
+
return [s.length, ...Array.from(s).map((c) => c.charCodeAt(0))];
|
|
42
|
+
}
|
|
43
|
+
function u16ToBytes(v) {
|
|
44
|
+
return [(v >> 8) & 0xff, v & 0xff];
|
|
45
|
+
}
|
|
46
|
+
/** Sends one unicast packet to ip:8002 and waits for a matching reply, retrying on timeout (UDP can drop packets). */
|
|
47
|
+
function request(ip, packetBytes, expectCmd, expectMethod, timeoutMs = 1500, retries = 2) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const socket = dgram.createSocket({ type: "udp4", reuseAddr: true });
|
|
50
|
+
let attemptsLeft = retries;
|
|
51
|
+
let timer;
|
|
52
|
+
const cleanup = () => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
socket.close();
|
|
55
|
+
};
|
|
56
|
+
socket.on("error", (err) => {
|
|
57
|
+
cleanup();
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
socket.on("message", (msg) => {
|
|
61
|
+
const data = decodePacket(msg);
|
|
62
|
+
if (data[0] === expectCmd && data[1] === expectMethod) {
|
|
63
|
+
cleanup();
|
|
64
|
+
resolve(data);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
function send() {
|
|
68
|
+
socket.send(encodePacket(packetBytes), exports.UDP_CONFIG_PORT, ip);
|
|
69
|
+
timer = setTimeout(() => {
|
|
70
|
+
if (attemptsLeft-- > 0)
|
|
71
|
+
send();
|
|
72
|
+
else {
|
|
73
|
+
cleanup();
|
|
74
|
+
reject(new Error(`No UDP config reply from ${ip} (cmd ${expectCmd}/${expectMethod})`));
|
|
75
|
+
}
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
}
|
|
78
|
+
socket.bind(0, send);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** Pushes a new home-server password/TCP-port to a master over the UDP config protocol (port 8002). */
|
|
82
|
+
function setHomeServerCfg(ip, password, ipPort, timeoutMs) {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
const packet = [CMD_FC_APPCONFIG, ATTR_HOMESRVCFG_SET, ...stringToBytes(password), ...u16ToBytes(ipPort), ...u16ToBytes(HOMESRVCFG_PROTOCOL_FLAGS)];
|
|
85
|
+
const reply = yield request(ip, packet, CMD_EV_APPCONFIG, ATTR_HOMESRVCFG_SET, timeoutMs);
|
|
86
|
+
return reply[2] === 1;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
exports.setHomeServerCfg = setHomeServerCfg;
|
|
90
|
+
//# sourceMappingURL=udpConfig.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.2.2",
|
|
5
5
|
"description": "SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
package/server/proxy.js
CHANGED
|
@@ -90,7 +90,9 @@ function setProxyConfig(c) {
|
|
|
90
90
|
config.cloudPort = c.cloudPort || types_1.kEmptyProxy.cloudPort;
|
|
91
91
|
config.masterAddress = c.masterAddress || types_1.kEmptyProxy.masterAddress;
|
|
92
92
|
config.masterPort = c.masterPort || types_1.kEmptyProxy.masterPort;
|
|
93
|
+
config.masterPortEnabled = !!c.masterPortEnabled;
|
|
93
94
|
config.masterConfigPort = c.masterConfigPort || types_1.kEmptyProxy.masterConfigPort;
|
|
95
|
+
config.masterConfigPortEnabled = !!c.masterConfigPortEnabled;
|
|
94
96
|
config.installationId = c.installationId || types_1.kEmptyProxy.installationId;
|
|
95
97
|
config.nodeNumber = (typeof c.nodeNumber === "number") ? c.nodeNumber : types_1.kEmptyProxy.nodeNumber;
|
|
96
98
|
config.httpEnabled = !!c.httpEnabled;
|
|
@@ -194,21 +196,25 @@ function cleanStart(restart = false) {
|
|
|
194
196
|
if (restart) {
|
|
195
197
|
log(`[PROXY] → [CLOUD] Restarting proxy...`);
|
|
196
198
|
let connectionsStarted = 0;
|
|
197
|
-
// Start connection for the master device's raw protocol port if an installation ID is configured
|
|
198
|
-
//
|
|
199
|
-
// gateway's own (configurable) nodeNumber.
|
|
200
|
-
if (config.installationId && config.masterAddress) {
|
|
199
|
+
// Start connection for the master device's raw protocol port if an installation ID is configured
|
|
200
|
+
// and this tunnel is explicitly enabled (off by default). Masters always register under the
|
|
201
|
+
// fixed master node number (kMasterNodeNumber), not the gateway's own (configurable) nodeNumber.
|
|
202
|
+
if (config.installationId && config.masterAddress && config.masterPortEnabled) {
|
|
201
203
|
const masterId = makeDeviceId(config, exports.kMasterNodeNumber, "tcp", config.masterPort);
|
|
202
204
|
log(`[PROXY] Starting proxy for master device: ${masterId}`);
|
|
203
205
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, masterId);
|
|
204
206
|
connectionsStarted++;
|
|
205
207
|
}
|
|
208
|
+
else if (config.installationId && config.masterAddress) {
|
|
209
|
+
log(`[PROXY] → [CLOUD] Master TCP tunnel disabled, skipping master connection.`);
|
|
210
|
+
}
|
|
206
211
|
else if (config.installationId) {
|
|
207
212
|
log(`[PROXY] → [CLOUD] Installation ID configured but no masterAddress, skipping master connection.`);
|
|
208
213
|
}
|
|
209
214
|
// Start a proxy for the master's Config API port -- this is an HTTP API, so it's tagged
|
|
210
215
|
// "http" rather than "tcp" even though the tunnel itself just relays raw bytes either way.
|
|
211
|
-
|
|
216
|
+
// Also off by default, only started when explicitly enabled.
|
|
217
|
+
if (config.installationId && config.masterAddress && config.masterConfigPort && config.masterConfigPortEnabled) {
|
|
212
218
|
const configId = makeDeviceId(config, exports.kMasterNodeNumber, "http", config.masterConfigPort);
|
|
213
219
|
log(`[PROXY] Starting proxy for Config API: ${configId} -> ${config.masterAddress}:${config.masterConfigPort}`);
|
|
214
220
|
// All connections use the same cloud port for WebSocket multiplexing
|
package/server/smartapp.js
CHANGED
|
@@ -27,6 +27,7 @@ const somfy = require("./somfy");
|
|
|
27
27
|
const fs_1 = require("fs");
|
|
28
28
|
const overkiz_1 = require("./overkiz");
|
|
29
29
|
const config_1 = require("../duotecno/config");
|
|
30
|
+
const udpConfig_1 = require("../duotecno/udpConfig");
|
|
30
31
|
const HB_1 = require("./HB");
|
|
31
32
|
const HA_API_1 = require("./HA-API");
|
|
32
33
|
const proxy_1 = require("./proxy");
|
|
@@ -74,7 +75,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
74
75
|
// get some configurated params
|
|
75
76
|
const configuredPort = this.config.port || 80;
|
|
76
77
|
this.password = platform.config.password || this.config.password || "";
|
|
77
|
-
this.user = platform.config.user || this.config.user || "
|
|
78
|
+
this.user = platform.config.user || this.config.user || "duotecno";
|
|
78
79
|
// Set up multi-port listening if port is not 80
|
|
79
80
|
// Note: Port 80 requires root/sudo on Unix-like systems
|
|
80
81
|
// For development on macOS/Linux without sudo, use only configured port
|
|
@@ -391,7 +392,9 @@ function SmartSocketInit(message){
|
|
|
391
392
|
config.cloudPort = context.getParam({ name: "cloudPort", type: "integer" });
|
|
392
393
|
config.masterAddress = context.getParam({ name: "masterAddress", type: "string" });
|
|
393
394
|
config.masterPort = context.getParam({ name: "masterPort", type: "integer" });
|
|
395
|
+
config.masterPortEnabled = !!context.getParam({ name: "masterPortEnabled", type: "string" });
|
|
394
396
|
config.masterConfigPort = context.getParam({ name: "masterConfigPort", type: "integer" });
|
|
397
|
+
config.masterConfigPortEnabled = !!context.getParam({ name: "masterConfigPortEnabled", type: "string" });
|
|
395
398
|
config.installationId = context.getParam({ name: "installationId", type: "string" });
|
|
396
399
|
config.nodeNumber = context.getParam({ name: "nodeNumber", type: "string" })
|
|
397
400
|
? parseInt(context.getParam({ name: "nodeNumber", type: "string" }), 16) : proxy_1.kEmptyProxy.nodeNumber;
|
|
@@ -794,8 +797,22 @@ function SmartSocketInit(message){
|
|
|
794
797
|
yield this.informOverkizUpDown(u, link);
|
|
795
798
|
return;
|
|
796
799
|
}
|
|
797
|
-
// dimmer
|
|
798
|
-
//
|
|
800
|
+
// A dimmer (or, for a Screen/Awning target, a mood carrying a numeric
|
|
801
|
+
// macro payload -- the same "set to X%" trick previously used to park a
|
|
802
|
+
// screen/awning at a specific height) linked to a Screen/Awning sets its
|
|
803
|
+
// position; linked to a Light it sets intensity instead. Dispatch on the
|
|
804
|
+
// Overkiz device's own type, not just the Duotecno unit's type, since a
|
|
805
|
+
// dimmer unit is now also how a real Overkiz dimmer (Light) gets driven.
|
|
806
|
+
const targetUiClass = this.power.overkiz.uiClass(link.accId);
|
|
807
|
+
const isPositional = (targetUiClass === "Screen") || (targetUiClass === "Awning");
|
|
808
|
+
if (isPositional && (u.isDimmer() || u.isMood())) {
|
|
809
|
+
yield this.informOverkizPosition(u, link);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (!u.isDimmer())
|
|
813
|
+
return;
|
|
814
|
+
// dt2hbvalue reports the raw 0-100 intensity directly, no real/virtual
|
|
815
|
+
// distinction to worry about here (unlike up/down above).
|
|
799
816
|
const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue, link.min, link.max);
|
|
800
817
|
if ((typeof hbValue === "undefined") || (hbValue === link.hbValue))
|
|
801
818
|
return;
|
|
@@ -809,6 +826,32 @@ function SmartSocketInit(message){
|
|
|
809
826
|
}
|
|
810
827
|
});
|
|
811
828
|
}
|
|
829
|
+
// Sets a Screen/Awning to a specific position (0-100) from a dimmer's raw
|
|
830
|
+
// value, or from a mood unit's value when Duotecno programming sends it a
|
|
831
|
+
// numeric macro payload (the same mechanism dimmers use -- a mood isn't
|
|
832
|
+
// normally more than an on/off trigger, but if its macro carries a value
|
|
833
|
+
// it ends up in unit.value exactly like a dimmer's does). Our scale is
|
|
834
|
+
// 0=off/closed, 100=on/fully open (matching how dimmer links always
|
|
835
|
+
// worked here); Overkiz's closure/deployment scale is the opposite
|
|
836
|
+
// (0=open, 100=closed), so invert on the way out.
|
|
837
|
+
informOverkizPosition(u, link) {
|
|
838
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
839
|
+
if (!this.power.overkiz)
|
|
840
|
+
return;
|
|
841
|
+
const raw = (typeof u.value === "boolean") ? (u.value ? 100 : 0) : Math.round(u.value);
|
|
842
|
+
const position = Math.max(0, Math.min(100, raw));
|
|
843
|
+
if (position === link.hbValue)
|
|
844
|
+
return;
|
|
845
|
+
try {
|
|
846
|
+
link.hbValue = position;
|
|
847
|
+
(0, logger_1.log)("smartapp", "-> Setting Overkiz position for: " + link.accName + " to " + position + "% of Duotecno unit: " + u.getDisplayName());
|
|
848
|
+
yield this.power.overkiz.setClosure(link.accId, 100 - position);
|
|
849
|
+
}
|
|
850
|
+
catch (e) {
|
|
851
|
+
(0, logger_1.err)("smartapp", "informOverkizPosition: failed for " + link.accName + " -> " + e.toString());
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
812
855
|
// A Duotecno up/down (kSwitchingMotor) unit reports status two different
|
|
813
856
|
// ways depending on how it's wired in the Duotecno programming:
|
|
814
857
|
// - a "virtual button" duoswitch macro event -> unit.value = 10 (stop) /
|
|
@@ -888,10 +931,27 @@ function SmartSocketInit(message){
|
|
|
888
931
|
if (!this.power.overkiz || (this.power.overkiz === this.attachedOverkiz))
|
|
889
932
|
return;
|
|
890
933
|
this.attachedOverkiz = this.power.overkiz;
|
|
891
|
-
this.power.overkiz.emitter.on("state", (deviceURL, unitState) => {
|
|
934
|
+
this.power.overkiz.emitter.on("state", (deviceURL, unitState, closure) => {
|
|
892
935
|
for (const link of this.links) {
|
|
893
936
|
if ((link.type !== types_1.LinkType.kOverkiz) || (link.accId !== deviceURL) || !link.unit)
|
|
894
937
|
continue;
|
|
938
|
+
if (link.unit.isDimmer()) {
|
|
939
|
+
// position-sourced link: reflect Overkiz's position back as a
|
|
940
|
+
// dimmer value. Overkiz's closure/deployment scale is the opposite
|
|
941
|
+
// of ours (0=open/100=closed vs our 0=off/100=on) -- invert back.
|
|
942
|
+
const position = Math.max(0, Math.min(100, Math.round(100 - closure)));
|
|
943
|
+
if (position === link.dtValue)
|
|
944
|
+
continue;
|
|
945
|
+
link.dtValue = position;
|
|
946
|
+
(0, logger_1.log)("smartapp", "-> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + position + " from Overkiz position " + closure + " of: " + link.accName);
|
|
947
|
+
link.unit.setState(position);
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
// a mood can only be triggered, not set to an arbitrary value, so a
|
|
951
|
+
// mood-sourced position link is Duotecno -> Overkiz only; nothing to
|
|
952
|
+
// reflect back here. Only an up/down unit gets the state below.
|
|
953
|
+
if (!link.unit.isUpDown())
|
|
954
|
+
continue;
|
|
895
955
|
if (unitState === link.dtValue)
|
|
896
956
|
continue; // no repeat
|
|
897
957
|
link.dtValue = unitState;
|
|
@@ -1674,7 +1734,7 @@ function SmartSocketInit(message){
|
|
|
1674
1734
|
let message;
|
|
1675
1735
|
try {
|
|
1676
1736
|
if (context.action === "new") {
|
|
1677
|
-
return this.ejs("masterDetail", context, { config: types_1.Sanitizers.masterConfig(null), registers: [], nodes: [] });
|
|
1737
|
+
return this.ejs("masterDetail", context, { config: types_1.Sanitizers.masterConfig(null), registers: [], nodes: [], loggedIn: false });
|
|
1678
1738
|
}
|
|
1679
1739
|
else if (context.action === "list") {
|
|
1680
1740
|
return this.ejs("unitList", context, { masters: this.system.masters });
|
|
@@ -1686,7 +1746,7 @@ function SmartSocketInit(message){
|
|
|
1686
1746
|
context.getMaster("id");
|
|
1687
1747
|
const master = this.system.findMaster(context["masterAddress"], context["masterPort"]);
|
|
1688
1748
|
if (master)
|
|
1689
|
-
return this.ejs("masterDetail", context, { nodes: master.nodes, config: master.getConfig(), registers: master.registers });
|
|
1749
|
+
return this.ejs("masterDetail", context, { nodes: master.nodes, config: master.getConfig(), registers: master.registers, loggedIn: master.isLoggedIn });
|
|
1690
1750
|
else
|
|
1691
1751
|
message = "Error: Master not found";
|
|
1692
1752
|
}
|
|
@@ -1713,6 +1773,27 @@ function SmartSocketInit(message){
|
|
|
1713
1773
|
else
|
|
1714
1774
|
message = "Error: Master not found";
|
|
1715
1775
|
}
|
|
1776
|
+
else if (context.action === "changepw") {
|
|
1777
|
+
const master = this.system.findMaster(context.getParam(kAddressOld), context.getParam(kPortOld));
|
|
1778
|
+
if (!master)
|
|
1779
|
+
message = "Error: Master not found";
|
|
1780
|
+
else if (!master.isLoggedIn)
|
|
1781
|
+
message = "Error: not logged in to this master -> refused to change its password";
|
|
1782
|
+
else {
|
|
1783
|
+
const address = context.getParam(kAddress);
|
|
1784
|
+
const port = context.getParam(kPort);
|
|
1785
|
+
const password = context.getParam(kPassword);
|
|
1786
|
+
if (yield (0, udpConfig_1.setHomeServerCfg)(address, password, port)) {
|
|
1787
|
+
const updated = yield this.system.addMaster(context.getParam(kAddressOld), context.getParam(kPortOld), { address, port, password, name: context.getParam(kName),
|
|
1788
|
+
active: context.getParam(kActive) != "N", nodenames: {} });
|
|
1789
|
+
this.updateNodes(updated, context.params.nodes || "", context.params);
|
|
1790
|
+
this.system.writeConfig();
|
|
1791
|
+
message = "Password changed on master and saved";
|
|
1792
|
+
}
|
|
1793
|
+
else
|
|
1794
|
+
message = "Error: master refused the new password";
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1716
1797
|
}
|
|
1717
1798
|
catch (e) {
|
|
1718
1799
|
message = e.toString();
|
|
@@ -45,6 +45,16 @@
|
|
|
45
45
|
<label for="password">Password</label>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
|
+
<% if (typeof nodes !== "undefined") { %>
|
|
49
|
+
<div class="row">
|
|
50
|
+
<div class="col s12">
|
|
51
|
+
<button class="btn waves-effect waves-light" type="submit" name="action" value="changepw" <%= loggedIn ? '' : 'disabled' %>>Change Password on Master</button>
|
|
52
|
+
<% if (!loggedIn) { %>
|
|
53
|
+
<span class="grey-text"><small>Only available while logged in to this master.</small></span>
|
|
54
|
+
<% } %>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<% } %>
|
|
48
58
|
<div class="row">
|
|
49
59
|
<div class="col s6">
|
|
50
60
|
Active
|
package/server/views/nav.ejs
CHANGED
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
<li <% if (request === "proxy") { %>class="active"<% } %>><a href="<%= basePath %>/proxy">Proxy</a></li>
|
|
47
47
|
<li><div class="divider"></div></li>
|
|
48
48
|
<li class="subheader">Linking</li>
|
|
49
|
-
<li <% if (request === "switches") { %>class="active"<% } %>><a href="<%= basePath %>/switches">Http
|
|
50
|
-
<li <% if (request === "links") { %>class="active"<% } %>><a href="<%= basePath %>/links">Accessory
|
|
49
|
+
<li <% if (request === "switches") { %>class="active"<% } %>><a href="<%= basePath %>/switches">Http</a></li>
|
|
50
|
+
<li <% if (request === "links") { %>class="active"<% } %>><a href="<%= basePath %>/links">Accessory</a></li>
|
|
51
51
|
<li><div class="divider"></div></li>
|
|
52
52
|
<li class="subheader">Energy</li>
|
|
53
|
-
<li <% if (request === "devices") { %>class="active"<% } %>><a href="<%= basePath %>/devices">
|
|
53
|
+
<li <% if (request === "devices") { %>class="active"<% } %>><a href="<%= basePath %>/devices">Julix</a></li>
|
|
54
54
|
<% if (hasSmappee) { %><li <% if ((request === "power") && (type === "smappee")) { %>class="active"<% } %>><a href="<%= basePath %>/power?type=smappee">Smappee</a></li><% } %>
|
|
55
55
|
<% if (hasP1) { %><li <% if ((request === "power") && (type === "p1")) { %>class="active"<% } %>><a href="<%= basePath %>/power?type=p1">P1 Meter</a></li><% } %>
|
|
56
56
|
<% if (hasShelly) { %><li <% if ((request === "power") && (type === "shelly")) { %>class="active"<% } %>><a href="<%= basePath %>/power?type=shelly">Shelly PM</a></li><% } %>
|
package/server/views/proxy.ejs
CHANGED
|
@@ -144,7 +144,8 @@
|
|
|
144
144
|
<div class="col s6">
|
|
145
145
|
<p>
|
|
146
146
|
<label>
|
|
147
|
-
<input type="checkbox" class="filled-in"
|
|
147
|
+
<input type="checkbox" class="filled-in" id="masterPortEnabled" name="masterPortEnabled" value="Y"
|
|
148
|
+
<%= config.masterPortEnabled ? "checked" : "" %> />
|
|
148
149
|
<span><strong>Websocket</strong></span>
|
|
149
150
|
</label>
|
|
150
151
|
</p>
|
|
@@ -157,7 +158,8 @@
|
|
|
157
158
|
<div class="col s6">
|
|
158
159
|
<p>
|
|
159
160
|
<label>
|
|
160
|
-
<input type="checkbox" class="filled-in"
|
|
161
|
+
<input type="checkbox" class="filled-in" id="masterConfigPortEnabled" name="masterConfigPortEnabled" value="Y"
|
|
162
|
+
<%= config.masterConfigPortEnabled ? "checked" : "" %> />
|
|
161
163
|
<span><strong>Config port</strong></span>
|
|
162
164
|
</label>
|
|
163
165
|
</p>
|