homebridge-ratgdo 2.1.2 → 2.2.0
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/dist/ratgdo-device.d.ts +0 -2
- package/dist/ratgdo-device.js +21 -47
- package/dist/ratgdo-device.js.map +1 -1
- package/dist/ratgdo-options.d.ts +4 -15
- package/dist/ratgdo-options.js +2 -93
- package/dist/ratgdo-options.js.map +1 -1
- package/dist/ratgdo-platform.d.ts +4 -4
- package/dist/ratgdo-platform.js +22 -31
- package/dist/ratgdo-platform.js.map +1 -1
- package/homebridge-ui/public/index.html +20 -1
- package/homebridge-ui/public/lib/featureoptions.js +366 -0
- package/homebridge-ui/public/lib/webUi.mjs +155 -0
- package/homebridge-ui/public/{ratgdo-featureoptions.mjs → lib/webui-featureoptions.mjs} +104 -142
- package/homebridge-ui/server.js +3 -39
- package/package.json +13 -13
- package/dist/ratgdo-mqtt.d.ts +0 -19
- package/dist/ratgdo-mqtt.js +0 -155
- package/dist/ratgdo-mqtt.js.map +0 -1
- package/homebridge-ui/public/lib/featureoptions.mjs +0 -200
- package/homebridge-ui/public/ui.mjs +0 -144
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-ratgdo",
|
|
3
3
|
"displayName": "Homebridge Ratgdo",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "HomeKit integration for LiftMaster and Chamberlain garage door openers, without requiring myQ.",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"repository": {
|
|
@@ -18,31 +18,31 @@
|
|
|
18
18
|
},
|
|
19
19
|
"main": "dist/index.js",
|
|
20
20
|
"scripts": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"build": "npm run clean && tsc",
|
|
22
|
+
"build-ui": "shx mkdir -p homebridge-ui/public/lib && shx cp \"node_modules/homebridge-plugin-utils/dist/ui/**/*.@(js|mjs)\" homebridge-ui/public/lib",
|
|
23
|
+
"clean": "shx rm -rf dist homebridge-ui/public/lib && npm run build-ui",
|
|
24
|
+
"lint": "eslint eslint.config.mjs src/**.ts homebridge-ui/*.js homebridge-ui/public/**/*.mjs",
|
|
25
|
+
"postpublish": "npm run clean",
|
|
24
26
|
"prepublishOnly": "npm run lint && npm run build"
|
|
25
27
|
},
|
|
26
28
|
"keywords": [
|
|
27
29
|
"homebridge-plugin"
|
|
28
30
|
],
|
|
29
31
|
"devDependencies": {
|
|
30
|
-
"@stylistic/eslint-plugin": "^1.
|
|
32
|
+
"@stylistic/eslint-plugin": "^2.1.0",
|
|
31
33
|
"@types/eventsource": "^1.1.15",
|
|
32
|
-
"@types/node": "^20.12.
|
|
33
|
-
"eslint": "
|
|
34
|
-
"homebridge": "^1.8.
|
|
35
|
-
"
|
|
36
|
-
"rimraf": "^5.0.5",
|
|
37
|
-
"ts-node": "^10.9.2",
|
|
34
|
+
"@types/node": "^20.12.12",
|
|
35
|
+
"eslint": "8.57.0",
|
|
36
|
+
"homebridge": "^1.8.2",
|
|
37
|
+
"shx": "^0.3.4",
|
|
38
38
|
"typescript": "^5.4.5",
|
|
39
|
-
"typescript-eslint": "^7.
|
|
39
|
+
"typescript-eslint": "^7.9.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@adobe/fetch": "^4.1.2",
|
|
43
43
|
"@homebridge/plugin-ui-utils": "^1.0.3",
|
|
44
44
|
"bonjour-service": "^1.2.1",
|
|
45
45
|
"eventsource": "^2.0.2",
|
|
46
|
-
"
|
|
46
|
+
"homebridge-plugin-utils": "^1.0.0"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/dist/ratgdo-mqtt.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { RatgdoAccessory } from "./ratgdo-device.js";
|
|
3
|
-
import { RatgdoPlatform } from "./ratgdo-platform.js";
|
|
4
|
-
export declare class RatgdoMqtt {
|
|
5
|
-
private config;
|
|
6
|
-
private isConnected;
|
|
7
|
-
private log;
|
|
8
|
-
private mqtt;
|
|
9
|
-
private platform;
|
|
10
|
-
private subscriptions;
|
|
11
|
-
constructor(platform: RatgdoPlatform);
|
|
12
|
-
private configure;
|
|
13
|
-
publish(accessory: RatgdoAccessory, topic: string, message: string): void;
|
|
14
|
-
subscribe(accessory: RatgdoAccessory, topic: string, callback: (cbBuffer: Buffer) => void): void;
|
|
15
|
-
subscribeGet(accessory: RatgdoAccessory, topic: string, type: string, getValue: () => string): void;
|
|
16
|
-
subscribeSet(accessory: RatgdoAccessory, topic: string, type: string, setValue: (value: string) => void): void;
|
|
17
|
-
unsubscribe(accessory: RatgdoAccessory, topic: string): void;
|
|
18
|
-
private expandTopic;
|
|
19
|
-
}
|
package/dist/ratgdo-mqtt.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
-
*
|
|
3
|
-
* ratgdo-mqtt.ts: MQTT connectivity class for Ratgdo.
|
|
4
|
-
*/
|
|
5
|
-
import mqtt from "mqtt";
|
|
6
|
-
import { RATGDO_MQTT_RECONNECT_INTERVAL } from "./settings.js";
|
|
7
|
-
export class RatgdoMqtt {
|
|
8
|
-
config;
|
|
9
|
-
isConnected;
|
|
10
|
-
log;
|
|
11
|
-
mqtt;
|
|
12
|
-
platform;
|
|
13
|
-
subscriptions;
|
|
14
|
-
constructor(platform) {
|
|
15
|
-
this.config = platform.config;
|
|
16
|
-
this.isConnected = false;
|
|
17
|
-
this.log = platform.log;
|
|
18
|
-
this.mqtt = null;
|
|
19
|
-
this.platform = platform;
|
|
20
|
-
this.subscriptions = {};
|
|
21
|
-
if (!this.config.mqttUrl) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
this.configure();
|
|
25
|
-
}
|
|
26
|
-
// Connect to the MQTT broker.
|
|
27
|
-
configure() {
|
|
28
|
-
// Try to connect to the MQTT broker and make sure we catch any URL errors.
|
|
29
|
-
try {
|
|
30
|
-
this.mqtt = mqtt.connect(this.config.mqttUrl, { reconnectPeriod: RATGDO_MQTT_RECONNECT_INTERVAL * 1000, rejectUnauthorized: false });
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
if (error instanceof Error) {
|
|
34
|
-
switch (error.message) {
|
|
35
|
-
case "Missing protocol":
|
|
36
|
-
this.log.error("MQTT Broker: Invalid URL provided: %s.", this.config.mqttUrl);
|
|
37
|
-
break;
|
|
38
|
-
default:
|
|
39
|
-
this.log.error("MQTT Broker: Error: %s.", error.message);
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// We've been unable to even attempt to connect. It's likely we have a configuration issue - we're done here.
|
|
45
|
-
if (!this.mqtt) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// Notify the user when we connect to the broker.
|
|
49
|
-
this.mqtt.on("connect", () => {
|
|
50
|
-
this.isConnected = true;
|
|
51
|
-
// Magic incantation to redact passwords.
|
|
52
|
-
const redact = /^(?<pre>.*:\/{0,2}.*:)(?<pass>.*)(?<post>@.*)/;
|
|
53
|
-
this.log.info("Connected to MQTT broker: %s (topic: %s).", this.config.mqttUrl.replace(redact, "$<pre>REDACTED$<post>"), this.config.mqttTopic);
|
|
54
|
-
});
|
|
55
|
-
// Notify the user when we've disconnected.
|
|
56
|
-
this.mqtt.on("close", () => {
|
|
57
|
-
if (this.isConnected) {
|
|
58
|
-
this.isConnected = false;
|
|
59
|
-
// Magic incantation to redact passwords.
|
|
60
|
-
const redact = /^(?<pre>.*:\/{0,2}.*:)(?<pass>.*)(?<post>@.*)/;
|
|
61
|
-
this.log.info("Disconnected from MQTT broker: %s.", this.config.mqttUrl.replace(redact, "$<pre>REDACTED$<post>"));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
// Process inbound messages and pass it to the right message handler.
|
|
65
|
-
this.mqtt.on("message", (topic, message) => {
|
|
66
|
-
if (this.subscriptions[topic]) {
|
|
67
|
-
this.subscriptions[topic](message);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
// Notify the user when there's a connectivity error.
|
|
71
|
-
this.mqtt.on("error", (error) => {
|
|
72
|
-
switch (error.code) {
|
|
73
|
-
case "ECONNREFUSED":
|
|
74
|
-
this.log.error("MQTT Broker: Connection refused (url: %s). Will retry again in %s minute%s.", this.config.mqttUrl, RATGDO_MQTT_RECONNECT_INTERVAL / 60, RATGDO_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
|
|
75
|
-
break;
|
|
76
|
-
case "ECONNRESET":
|
|
77
|
-
this.log.error("MQTT Broker: Connection reset (url: %s). Will retry again in %s minute%s.", this.config.mqttUrl, RATGDO_MQTT_RECONNECT_INTERVAL / 60, RATGDO_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
|
|
78
|
-
break;
|
|
79
|
-
case "ENOTFOUND":
|
|
80
|
-
this.mqtt?.end(true);
|
|
81
|
-
this.log.error("MQTT Broker: Hostname or IP address not found. (url: %s).", this.config.mqttUrl);
|
|
82
|
-
break;
|
|
83
|
-
default:
|
|
84
|
-
this.log.error("MQTT Broker: %s (url: %s). Will retry again in %s minute%s.", error, this.config.mqttUrl, RATGDO_MQTT_RECONNECT_INTERVAL / 60, RATGDO_MQTT_RECONNECT_INTERVAL / 60 > 1 ? "s" : "");
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
// Publish an MQTT event to a broker.
|
|
90
|
-
publish(accessory, topic, message) {
|
|
91
|
-
const expandedTopic = this.expandTopic(accessory.device.mac, topic);
|
|
92
|
-
// No valid topic returned, we're done.
|
|
93
|
-
if (!expandedTopic) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
accessory.log.debug("MQTT publish: %s Message: %s.", expandedTopic, message);
|
|
97
|
-
// By default, we publish as: ratgdo/mac/event/name
|
|
98
|
-
this.mqtt?.publish(expandedTopic, message);
|
|
99
|
-
}
|
|
100
|
-
// Subscribe to an MQTT topic.
|
|
101
|
-
subscribe(accessory, topic, callback) {
|
|
102
|
-
const expandedTopic = this.expandTopic(accessory.device.mac, topic);
|
|
103
|
-
// No valid topic returned, we're done.
|
|
104
|
-
if (!expandedTopic) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
accessory.log.debug("MQTT subscribe: %s.", expandedTopic);
|
|
108
|
-
// Add to our callback list.
|
|
109
|
-
this.subscriptions[expandedTopic] = callback;
|
|
110
|
-
// Tell MQTT we're subscribing to this event. By default, we subscribe as: ratgdo/mac/event/name.
|
|
111
|
-
this.mqtt?.subscribe(expandedTopic);
|
|
112
|
-
}
|
|
113
|
-
// Subscribe to a specific MQTT topic and publish a value on a get request.
|
|
114
|
-
subscribeGet(accessory, topic, type, getValue) {
|
|
115
|
-
// Return the current status of a given sensor.
|
|
116
|
-
this.platform.mqtt?.subscribe(accessory, topic + "/get", (message) => {
|
|
117
|
-
const value = message.toString().toLowerCase();
|
|
118
|
-
// Only publish if we receive a true value.
|
|
119
|
-
if (value !== "true") {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
// Publish our value and inform the user.
|
|
123
|
-
this.platform.mqtt?.publish(accessory, topic, getValue());
|
|
124
|
-
accessory.log.info("MQTT: %s status published.", type);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
// Subscribe to a specific MQTT topic and set a value on a set request.
|
|
128
|
-
subscribeSet(accessory, topic, type, setValue) {
|
|
129
|
-
// Return the current status of a given sensor.
|
|
130
|
-
this.platform.mqtt?.subscribe(accessory, topic + "/set", (message) => {
|
|
131
|
-
const value = message.toString().toLowerCase();
|
|
132
|
-
// Set our value and inform the user.
|
|
133
|
-
setValue(value);
|
|
134
|
-
accessory.log.info("MQTT: set message received for %s: %s.", type, value);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
// Unsubscribe to an MQTT topic.
|
|
138
|
-
unsubscribe(accessory, topic) {
|
|
139
|
-
const expandedTopic = this.expandTopic(accessory.device.mac, topic);
|
|
140
|
-
// No valid topic returned, we're done.
|
|
141
|
-
if (!expandedTopic) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
delete this.subscriptions[expandedTopic];
|
|
145
|
-
}
|
|
146
|
-
// Expand a topic to a unique, fully formed one.
|
|
147
|
-
expandTopic(mac, topic) {
|
|
148
|
-
// No accessory, we're done.
|
|
149
|
-
if (!mac) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
return this.config.mqttTopic + "/" + mac + "/" + topic;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
//# sourceMappingURL=ratgdo-mqtt.js.map
|
package/dist/ratgdo-mqtt.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ratgdo-mqtt.js","sourceRoot":"","sources":["../src/ratgdo-mqtt.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
-
*
|
|
3
|
-
* featureoptions.mjs: Feature option webUI base class.
|
|
4
|
-
*/
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
export class FeatureOptions {
|
|
8
|
-
|
|
9
|
-
controller;
|
|
10
|
-
|
|
11
|
-
featureOptionGroups;
|
|
12
|
-
featureOptionList;
|
|
13
|
-
optionsList;
|
|
14
|
-
|
|
15
|
-
constructor() {
|
|
16
|
-
|
|
17
|
-
this.featureOptionGroups = {};
|
|
18
|
-
this.featureOptionList = {};
|
|
19
|
-
this.controller = null;
|
|
20
|
-
this.optionsList = [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Abstract method to be implemented by subclasses to render the feature option webUI.
|
|
24
|
-
async showUI() {
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Is this feature option set explicitly?
|
|
28
|
-
isOptionSet(featureOption, deviceMac) {
|
|
29
|
-
|
|
30
|
-
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + featureOption + (!deviceMac ? "" : "\\." + deviceMac) + "$", "gi");
|
|
31
|
-
return this.optionsList.filter(x => optionRegex.test(x)).length ? true : false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Is a feature option globally enabled?
|
|
35
|
-
isGlobalOptionEnabled(featureOption, defaultState) {
|
|
36
|
-
|
|
37
|
-
featureOption = featureOption.toUpperCase();
|
|
38
|
-
|
|
39
|
-
// Test device-specific options.
|
|
40
|
-
return this.optionsList.some(x => x === ("ENABLE." + featureOption)) ? true :
|
|
41
|
-
(this.optionsList.some(x => x === ("DISABLE." + featureOption)) ? false : defaultState
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Is a feature option enabled at the device or global level. This function does not traverse the scoping hierarchy.
|
|
46
|
-
isDeviceOptionEnabled(featureOption, mac, defaultState) {
|
|
47
|
-
|
|
48
|
-
if(!mac) {
|
|
49
|
-
|
|
50
|
-
return this.isGlobalOptionEnabled(featureOption, defaultState);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
featureOption = featureOption.toUpperCase();
|
|
54
|
-
mac = mac.toUpperCase();
|
|
55
|
-
|
|
56
|
-
// Test device-specific options.
|
|
57
|
-
return this.optionsList.some(x => x === ("ENABLE." + featureOption + "." + mac)) ? true :
|
|
58
|
-
(this.optionsList.some(x => x === ("DISABLE." + featureOption + "." + mac)) ? false : defaultState
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Is a value-centric feature option enabled at the device or global level. This function does not traverse the scoping hierarchy.
|
|
63
|
-
isOptionValueSet(featureOption, deviceMac) {
|
|
64
|
-
|
|
65
|
-
const optionRegex = new RegExp("^Enable\\." + featureOption + (!deviceMac ? "" : "\\." + deviceMac) + "\\.([^\\.]+)$", "gi");
|
|
66
|
-
|
|
67
|
-
return this.optionsList.filter(x => optionRegex.test(x)).length ? true : false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get the value of a value-centric feature option.
|
|
71
|
-
getOptionValue(featureOption, deviceMac) {
|
|
72
|
-
|
|
73
|
-
const optionRegex = new RegExp("^Enable\\." + featureOption + (!deviceMac ? "" : "\\." + deviceMac) + "\\.([^\\.]+)$", "gi");
|
|
74
|
-
|
|
75
|
-
// Get the option value, if we have one.
|
|
76
|
-
for(const option of this.optionsList) {
|
|
77
|
-
|
|
78
|
-
const regexMatch = optionRegex.exec(option);
|
|
79
|
-
|
|
80
|
-
if(regexMatch) {
|
|
81
|
-
|
|
82
|
-
return regexMatch[1];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Is a feature option enabled at the device or global level. It does traverse the scoping hierarchy.
|
|
90
|
-
isOptionEnabled(featureOption, deviceMac) {
|
|
91
|
-
|
|
92
|
-
const defaultState = this.featureOptionList[featureOption]?.default ?? true;
|
|
93
|
-
|
|
94
|
-
if(deviceMac) {
|
|
95
|
-
|
|
96
|
-
// Device level check.
|
|
97
|
-
if(this.isDeviceOptionEnabled(featureOption, deviceMac, defaultState) !== defaultState) {
|
|
98
|
-
|
|
99
|
-
return !defaultState;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Controller level check.
|
|
103
|
-
if(this.isDeviceOptionEnabled(featureOption, this.controller, defaultState) !== defaultState) {
|
|
104
|
-
|
|
105
|
-
return !defaultState;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Global check.
|
|
110
|
-
if(this.isGlobalOptionEnabled(featureOption, defaultState) !== defaultState) {
|
|
111
|
-
|
|
112
|
-
return !defaultState;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Return the default.
|
|
116
|
-
return defaultState;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Return the scope level of a feature option.
|
|
120
|
-
optionScope(featureOption, deviceMac, defaultState, isOptionValue = false) {
|
|
121
|
-
|
|
122
|
-
// Scope priority is always: device, controller, global.
|
|
123
|
-
|
|
124
|
-
// If we have a value-centric feature option, our lookups are a bit different.
|
|
125
|
-
if(isOptionValue) {
|
|
126
|
-
|
|
127
|
-
if(deviceMac) {
|
|
128
|
-
|
|
129
|
-
if(this.isOptionValueSet(featureOption, deviceMac)) {
|
|
130
|
-
|
|
131
|
-
return "device";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if(this.isOptionValueSet(featureOption, this.controller)) {
|
|
135
|
-
|
|
136
|
-
return "controller";
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if(this.isOptionValueSet(featureOption)) {
|
|
141
|
-
|
|
142
|
-
return "global";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return "none";
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if(deviceMac) {
|
|
149
|
-
|
|
150
|
-
// Let's see if we've set it at the device-level.
|
|
151
|
-
if((this.isDeviceOptionEnabled(featureOption, deviceMac, defaultState) !== defaultState) || this.isOptionSet(featureOption, deviceMac)) {
|
|
152
|
-
|
|
153
|
-
return "device";
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Now let's test the controller level.
|
|
157
|
-
if((this.isDeviceOptionEnabled(featureOption, this.controller, defaultState) !== defaultState) || this.isOptionSet(featureOption, this.controller)) {
|
|
158
|
-
|
|
159
|
-
return "controller";
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Finally, let's test the global level.
|
|
164
|
-
if((this.isGlobalOptionEnabled(featureOption, defaultState) !== defaultState) || this.isOptionSet(featureOption)) {
|
|
165
|
-
|
|
166
|
-
return "global";
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Option isn't set to a non-default value.
|
|
170
|
-
return "none";
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// Return the color hinting for a given option's scope.
|
|
174
|
-
optionScopeColor(featureOption, deviceMac, defaultState, isOptionValue) {
|
|
175
|
-
|
|
176
|
-
switch(this.optionScope(featureOption, deviceMac, defaultState, isOptionValue)) {
|
|
177
|
-
|
|
178
|
-
case "device":
|
|
179
|
-
|
|
180
|
-
return "text-info";
|
|
181
|
-
break;
|
|
182
|
-
|
|
183
|
-
case "controller":
|
|
184
|
-
|
|
185
|
-
return "text-success";
|
|
186
|
-
break;
|
|
187
|
-
|
|
188
|
-
case "global":
|
|
189
|
-
|
|
190
|
-
return deviceMac ? "text-warning" : "text-info";
|
|
191
|
-
break;
|
|
192
|
-
|
|
193
|
-
default:
|
|
194
|
-
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return null;
|
|
199
|
-
};
|
|
200
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/* Copyright(C) 2017-2024, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
-
*
|
|
3
|
-
* ui.mjs: Ratgdo webUI.
|
|
4
|
-
*/
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
import { ratgdoFeatureOptions } from "./ratgdo-featureoptions.mjs";
|
|
8
|
-
|
|
9
|
-
// Keep a list of all the feature options and option groups.
|
|
10
|
-
const featureOptions = new ratgdoFeatureOptions();
|
|
11
|
-
|
|
12
|
-
// Show the first run user experience if we don't have valid login credentials.
|
|
13
|
-
async function showFirstRun () {
|
|
14
|
-
|
|
15
|
-
const buttonFirstRun = document.getElementById("firstRun");
|
|
16
|
-
const serverPortInfo = document.getElementById("serverPortInfo");
|
|
17
|
-
|
|
18
|
-
serverPortInfo.innerHTML = (featureOptions.currentConfig[0].port ?? "18830");
|
|
19
|
-
|
|
20
|
-
// First run user experience.
|
|
21
|
-
buttonFirstRun.addEventListener("click", async () => {
|
|
22
|
-
|
|
23
|
-
// Show the beachball while we setup.
|
|
24
|
-
homebridge.showSpinner();
|
|
25
|
-
|
|
26
|
-
// Get the list of devices the plugin knows about.
|
|
27
|
-
const ratgdoDevices = await homebridge.getCachedAccessories();
|
|
28
|
-
|
|
29
|
-
// Sort it for posterity.
|
|
30
|
-
ratgdoDevices?.sort((a, b) => {
|
|
31
|
-
|
|
32
|
-
const aCase = (a.displayName ?? "").toLowerCase();
|
|
33
|
-
const bCase = (b.displayName ?? "").toLowerCase();
|
|
34
|
-
|
|
35
|
-
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Create our UI.
|
|
39
|
-
document.getElementById("pageFirstRun").style.display = "none";
|
|
40
|
-
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
41
|
-
featureOptions.showUI();
|
|
42
|
-
|
|
43
|
-
// All done. Let the user interact with us, although in practice, we shouldn't get here.
|
|
44
|
-
// homebridge.hideSpinner();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
document.getElementById("pageFirstRun").style.display = "block";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Show the main plugin configuration tab.
|
|
51
|
-
function showSettings () {
|
|
52
|
-
|
|
53
|
-
// Show the beachball while we setup.
|
|
54
|
-
homebridge.showSpinner();
|
|
55
|
-
|
|
56
|
-
// Create our UI.
|
|
57
|
-
document.getElementById("menuHome").classList.remove("btn-elegant");
|
|
58
|
-
document.getElementById("menuHome").classList.add("btn-primary");
|
|
59
|
-
document.getElementById("menuFeatureOptions").classList.remove("btn-elegant");
|
|
60
|
-
document.getElementById("menuFeatureOptions").classList.add("btn-primary");
|
|
61
|
-
document.getElementById("menuSettings").classList.add("btn-elegant");
|
|
62
|
-
document.getElementById("menuSettings").classList.remove("btn-primary");
|
|
63
|
-
|
|
64
|
-
document.getElementById("pageSupport").style.display = "none";
|
|
65
|
-
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
66
|
-
|
|
67
|
-
homebridge.showSchemaForm();
|
|
68
|
-
|
|
69
|
-
// All done. Let the user interact with us.
|
|
70
|
-
homebridge.hideSpinner();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Show the support tab.
|
|
74
|
-
function showSupport() {
|
|
75
|
-
|
|
76
|
-
// Show the beachball while we setup.
|
|
77
|
-
homebridge.showSpinner();
|
|
78
|
-
homebridge.hideSchemaForm();
|
|
79
|
-
|
|
80
|
-
// Create our UI.
|
|
81
|
-
document.getElementById("menuHome").classList.add("btn-elegant");
|
|
82
|
-
document.getElementById("menuHome").classList.remove("btn-primary");
|
|
83
|
-
document.getElementById("menuFeatureOptions").classList.remove("btn-elegant");
|
|
84
|
-
document.getElementById("menuFeatureOptions").classList.add("btn-primary");
|
|
85
|
-
document.getElementById("menuSettings").classList.remove("btn-elegant");
|
|
86
|
-
document.getElementById("menuSettings").classList.add("btn-primary");
|
|
87
|
-
|
|
88
|
-
document.getElementById("pageSupport").style.display = "block";
|
|
89
|
-
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
90
|
-
|
|
91
|
-
// All done. Let the user interact with us.
|
|
92
|
-
homebridge.hideSpinner();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Launch our webUI.
|
|
96
|
-
async function launchWebUI() {
|
|
97
|
-
|
|
98
|
-
// Retrieve the current plugin configuration.
|
|
99
|
-
featureOptions.currentConfig = await homebridge.getPluginConfig();
|
|
100
|
-
|
|
101
|
-
// Add our event listeners to animate the UI.
|
|
102
|
-
menuHome.addEventListener("click", () => showSupport());
|
|
103
|
-
menuFeatureOptions.addEventListener("click", () => featureOptions.showUI());
|
|
104
|
-
menuSettings.addEventListener("click", () => showSettings());
|
|
105
|
-
|
|
106
|
-
// Get the list of devices the plugin knows about.
|
|
107
|
-
const ratgdoDevices = await homebridge.getCachedAccessories();
|
|
108
|
-
|
|
109
|
-
// If we've got Ratgdo devices detected, we launch our feature option UI. Otherwise, we launch our first run UI.
|
|
110
|
-
if(featureOptions.currentConfig.length && ratgdoDevices?.length) {
|
|
111
|
-
|
|
112
|
-
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
113
|
-
featureOptions.showUI();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// If we have no configuration, let's create one.
|
|
118
|
-
if(!featureOptions.currentConfig.length) {
|
|
119
|
-
|
|
120
|
-
featureOptions.currentConfig.push({ name: "Ratgdo" });
|
|
121
|
-
} else if(!("name" in featureOptions.currentConfig[0])) {
|
|
122
|
-
|
|
123
|
-
// If we haven't set the name, let's do so now.
|
|
124
|
-
featureOptions.currentConfig[0].name = "Ratgdo";
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Update the plugin configuration and launch the first run UI.
|
|
128
|
-
await homebridge.updatePluginConfig(featureOptions.currentConfig);
|
|
129
|
-
showFirstRun();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Fire off our UI, catching errors along the way.
|
|
133
|
-
try {
|
|
134
|
-
|
|
135
|
-
launchWebUI();
|
|
136
|
-
} catch(err) {
|
|
137
|
-
|
|
138
|
-
// If we had an error instantiating or updating the UI, notify the user.
|
|
139
|
-
homebridge.toast.error(err.message, "Error");
|
|
140
|
-
} finally {
|
|
141
|
-
|
|
142
|
-
// Always leave the UI in a usable place for the end user.
|
|
143
|
-
homebridge.hideSpinner();
|
|
144
|
-
}
|