@homebridge-plugins/homebridge-ewelink 12.3.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/CHANGELOG.md +1363 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/config.schema.json +1757 -0
- package/eslint.config.js +15 -0
- package/lib/connection/api.js +508 -0
- package/lib/connection/http.js +304 -0
- package/lib/connection/lan.js +318 -0
- package/lib/connection/ws.js +414 -0
- package/lib/device/curtain.js +324 -0
- package/lib/device/diffuser.js +473 -0
- package/lib/device/fan.js +349 -0
- package/lib/device/group.js +106 -0
- package/lib/device/humidifier.js +199 -0
- package/lib/device/index.js +61 -0
- package/lib/device/light-cct.js +333 -0
- package/lib/device/light-dimmer.js +257 -0
- package/lib/device/light-rgb-cct.js +809 -0
- package/lib/device/light-rgb.js +272 -0
- package/lib/device/motor.js +363 -0
- package/lib/device/outlet-multi.js +417 -0
- package/lib/device/outlet-single-inched.js +409 -0
- package/lib/device/outlet-single.js +384 -0
- package/lib/device/panel.js +382 -0
- package/lib/device/rf-bridge.js +321 -0
- package/lib/device/rf-button.js +125 -0
- package/lib/device/rf-sensor.js +173 -0
- package/lib/device/sensor-ambient.js +311 -0
- package/lib/device/sensor-contact.js +140 -0
- package/lib/device/sensor-temp-humi.js +182 -0
- package/lib/device/simulation/blind.js +253 -0
- package/lib/device/simulation/cooler.js +312 -0
- package/lib/device/simulation/door.js +253 -0
- package/lib/device/simulation/doorbell.js +190 -0
- package/lib/device/simulation/garage-eachen.js +235 -0
- package/lib/device/simulation/garage-four.js +176 -0
- package/lib/device/simulation/garage-od-switch.js +36 -0
- package/lib/device/simulation/garage-one.js +395 -0
- package/lib/device/simulation/garage-two.js +394 -0
- package/lib/device/simulation/gate-one.js +327 -0
- package/lib/device/simulation/heater.js +389 -0
- package/lib/device/simulation/index.js +75 -0
- package/lib/device/simulation/light-fan.js +258 -0
- package/lib/device/simulation/lock-eachen.js +163 -0
- package/lib/device/simulation/lock-one.js +366 -0
- package/lib/device/simulation/p-button.js +223 -0
- package/lib/device/simulation/purifier.js +289 -0
- package/lib/device/simulation/rf-blind.js +192 -0
- package/lib/device/simulation/rf-door.js +180 -0
- package/lib/device/simulation/rf-window.js +180 -0
- package/lib/device/simulation/sensor-hidden.js +105 -0
- package/lib/device/simulation/sensor-leak.js +123 -0
- package/lib/device/simulation/sensor-visible.js +231 -0
- package/lib/device/simulation/sensor.js +300 -0
- package/lib/device/simulation/switch-valve.js +351 -0
- package/lib/device/simulation/tap-one.js +283 -0
- package/lib/device/simulation/tap-two.js +266 -0
- package/lib/device/simulation/th-cooler.js +443 -0
- package/lib/device/simulation/th-dehumidifier.js +424 -0
- package/lib/device/simulation/th-heater.js +449 -0
- package/lib/device/simulation/th-humidifier.js +424 -0
- package/lib/device/simulation/th-thermostat.js +611 -0
- package/lib/device/simulation/tv.js +252 -0
- package/lib/device/simulation/valve-four.js +232 -0
- package/lib/device/simulation/valve-one.js +345 -0
- package/lib/device/simulation/valve-two.js +340 -0
- package/lib/device/simulation/window.js +253 -0
- package/lib/device/switch-man.js +106 -0
- package/lib/device/switch-mate.js +82 -0
- package/lib/device/switch-multi.js +411 -0
- package/lib/device/switch-single-inched.js +332 -0
- package/lib/device/switch-single.js +307 -0
- package/lib/device/template.js +43 -0
- package/lib/device/thermostat.js +286 -0
- package/lib/device/zigbee/index.js +27 -0
- package/lib/device/zigbee/light-cct.js +263 -0
- package/lib/device/zigbee/light-dimmer.js +148 -0
- package/lib/device/zigbee/light-rgb-cct.js +400 -0
- package/lib/device/zigbee/motor.js +301 -0
- package/lib/device/zigbee/sensor-ambient.js +177 -0
- package/lib/device/zigbee/sensor-contact.js +137 -0
- package/lib/device/zigbee/sensor-leak.js +119 -0
- package/lib/device/zigbee/sensor-motion.js +119 -0
- package/lib/device/zigbee/sensor-occupancy.js +119 -0
- package/lib/device/zigbee/sensor-smoke.js +96 -0
- package/lib/device/zigbee/switch-stateless.js +104 -0
- package/lib/device/zigbee/thermostat.js +323 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +813 -0
- package/lib/fakegato/fakegato-storage.js +108 -0
- package/lib/fakegato/fakegato-timer.js +125 -0
- package/lib/fakegato/uuid.js +27 -0
- package/lib/homebridge-ui/public/index.html +365 -0
- package/lib/homebridge-ui/server.js +46 -0
- package/lib/index.js +7 -0
- package/lib/node-dns-sd/LICENSE +21 -0
- package/lib/node-dns-sd/README.md +613 -0
- package/lib/node-dns-sd/lib/dns-sd-classes.json +6 -0
- package/lib/node-dns-sd/lib/dns-sd-composer.js +83 -0
- package/lib/node-dns-sd/lib/dns-sd-parser.js +344 -0
- package/lib/node-dns-sd/lib/dns-sd-types.json +52 -0
- package/lib/node-dns-sd/lib/dns-sd.js +580 -0
- package/lib/platform.js +2156 -0
- package/lib/utils/colour.js +552 -0
- package/lib/utils/constants.js +589 -0
- package/lib/utils/custom-chars.js +33 -0
- package/lib/utils/eve-chars.js +130 -0
- package/lib/utils/functions.js +42 -0
- package/lib/utils/lang-en.js +160 -0
- package/lib/utils/lang-fr.js +163 -0
- package/lib/utils/lang-th.js +160 -0
- package/lib/utils/lang.js +5 -0
- package/package.json +75 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const hostname = os.hostname().split('.')[0]
|
|
6
|
+
|
|
7
|
+
export default class {
|
|
8
|
+
constructor(params) {
|
|
9
|
+
if (!params) {
|
|
10
|
+
params = {}
|
|
11
|
+
}
|
|
12
|
+
this.writers = []
|
|
13
|
+
this.log = params.log || {}
|
|
14
|
+
if (!this.log) {
|
|
15
|
+
this.log = () => {}
|
|
16
|
+
}
|
|
17
|
+
this.addingWriter = false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
addWriter(service, params) {
|
|
21
|
+
if (!this.addingWriter) {
|
|
22
|
+
this.addingWriter = true
|
|
23
|
+
if (!params) {
|
|
24
|
+
params = {}
|
|
25
|
+
}
|
|
26
|
+
this.log('[%s] FGS addWriter().', service.accessoryName)
|
|
27
|
+
const newWriter = {
|
|
28
|
+
service,
|
|
29
|
+
callback: params.callback,
|
|
30
|
+
fileName: `${hostname}_${service.accessoryName}_persist.json`,
|
|
31
|
+
}
|
|
32
|
+
const onReady = typeof params.onReady === 'function' ? params.onReady : () => {}
|
|
33
|
+
newWriter.storageHandler = fs
|
|
34
|
+
newWriter.path = params.path || path.join(os.homedir(), '.homebridge')
|
|
35
|
+
this.writers.push(newWriter)
|
|
36
|
+
this.addingWriter = false
|
|
37
|
+
onReady()
|
|
38
|
+
} else {
|
|
39
|
+
setTimeout(() => this.addWriter(service, params), 100)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getWriter(service) {
|
|
44
|
+
return this.writers.find(ele => ele.service === service)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_getWriterIndex(service) {
|
|
48
|
+
return this.writers.findIndex(ele => ele.service === service)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getWriters() {
|
|
52
|
+
return this.writers
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
delWriter(service) {
|
|
56
|
+
const index = this._getWriterIndex(service)
|
|
57
|
+
this.writers.splice(index, 1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
write(params) {
|
|
61
|
+
if (!this.writing) {
|
|
62
|
+
this.writing = true
|
|
63
|
+
const writer = this.getWriter(params.service)
|
|
64
|
+
const callBack = typeof params.callback === 'function'
|
|
65
|
+
? params.callback
|
|
66
|
+
: typeof writer.callback === 'function'
|
|
67
|
+
? writer.callback
|
|
68
|
+
: () => {}
|
|
69
|
+
const fileLoc = path.join(writer.path, writer.fileName)
|
|
70
|
+
this.log(
|
|
71
|
+
'[%s] FGS write file [%s] [%s].',
|
|
72
|
+
params.service.accessoryName,
|
|
73
|
+
fileLoc,
|
|
74
|
+
params.data.substr(1, 80),
|
|
75
|
+
)
|
|
76
|
+
writer.storageHandler.writeFile(fileLoc, params.data, 'utf8', (...args) => {
|
|
77
|
+
this.writing = false
|
|
78
|
+
callBack(args)
|
|
79
|
+
})
|
|
80
|
+
} else {
|
|
81
|
+
setTimeout(() => this.write(params), 100)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
read(params) {
|
|
86
|
+
const writer = this.getWriter(params.service)
|
|
87
|
+
const callBack = typeof params.callback === 'function'
|
|
88
|
+
? params.callback
|
|
89
|
+
: typeof writer.callback === 'function'
|
|
90
|
+
? writer.callback
|
|
91
|
+
: () => {}
|
|
92
|
+
const fileLoc = path.join(writer.path, writer.fileName)
|
|
93
|
+
this.log('[%s] FGS read file [%s].', params.service.accessoryName, fileLoc)
|
|
94
|
+
writer.storageHandler.readFile(fileLoc, 'utf8', callBack)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
remove(params) {
|
|
98
|
+
const writer = this.getWriter(params.service)
|
|
99
|
+
const callBack = typeof params.callback === 'function'
|
|
100
|
+
? params.callback
|
|
101
|
+
: typeof writer.callback === 'function'
|
|
102
|
+
? writer.callback
|
|
103
|
+
: () => {}
|
|
104
|
+
const fileLoc = path.join(writer.path, writer.fileName)
|
|
105
|
+
this.log('[%s] FGS delete file [%s].', params.service.accessoryName, fileLoc)
|
|
106
|
+
writer.storageHandler.unlink(fileLoc, callBack)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export default class {
|
|
2
|
+
constructor(params) {
|
|
3
|
+
if (!params) {
|
|
4
|
+
params = {}
|
|
5
|
+
}
|
|
6
|
+
this.subscribedServices = []
|
|
7
|
+
this.minutes = params.minutes || 10
|
|
8
|
+
this.intervalID = null
|
|
9
|
+
this.running = false
|
|
10
|
+
this.log = params.log || {}
|
|
11
|
+
if (!this.log) {
|
|
12
|
+
this.log = () => {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
subscribe(service, callback) {
|
|
17
|
+
this.log('[%s] FGT new subscription.', service.accessoryName)
|
|
18
|
+
const newService = {
|
|
19
|
+
service,
|
|
20
|
+
callback,
|
|
21
|
+
backLog: [],
|
|
22
|
+
previousBackLog: [],
|
|
23
|
+
previousAvrg: {},
|
|
24
|
+
}
|
|
25
|
+
this.subscribedServices.push(newService)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSubscriber(service) {
|
|
29
|
+
return this.subscribedServices.find(el => el.service === service)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_getSubscriberIndex(service) {
|
|
33
|
+
return this.subscribedServices.findIndex(el => el.service === service)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getSubscribers() {
|
|
37
|
+
return this.subscribedServices
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
unsubscribe(service) {
|
|
41
|
+
const index = this._getSubscriberIndex(service)
|
|
42
|
+
this.subscribedServices.splice(index, 1)
|
|
43
|
+
if (this.subscribedServices.length === 0 && this.running) {
|
|
44
|
+
this.stop()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
start() {
|
|
49
|
+
this.log('Starting global FGT [%s minutes].', this.minutes)
|
|
50
|
+
if (this.running) {
|
|
51
|
+
this.stop()
|
|
52
|
+
}
|
|
53
|
+
this.running = true
|
|
54
|
+
this.intervalID = setInterval(this.executeCallbacks.bind(this), this.minutes * 60 * 1000)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
stop() {
|
|
58
|
+
this.log('Stopping global FGT.')
|
|
59
|
+
clearInterval(this.intervalID)
|
|
60
|
+
this.running = false
|
|
61
|
+
this.intervalID = null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
executeCallbacks() {
|
|
65
|
+
this.log('FGT executeCallbacks().')
|
|
66
|
+
if (this.subscribedServices.length !== 0) {
|
|
67
|
+
for (const s in this.subscribedServices) {
|
|
68
|
+
if (Object.prototype.hasOwnProperty.call(this.subscribedServices, s)) {
|
|
69
|
+
const service = this.subscribedServices[s]
|
|
70
|
+
if (typeof service.callback === 'function') {
|
|
71
|
+
service.previousAvrg = service.callback({
|
|
72
|
+
backLog: service.backLog,
|
|
73
|
+
previousAvrg: service.previousAvrg,
|
|
74
|
+
timer: this,
|
|
75
|
+
immediate: false,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
executeImmediateCallback(service) {
|
|
84
|
+
this.log('[%s] FGT executeImmediateCallback().', service.accessoryName)
|
|
85
|
+
if (typeof service.callback === 'function' && service.backLog.length) {
|
|
86
|
+
service.callback({
|
|
87
|
+
backLog: service.backLog,
|
|
88
|
+
timer: this,
|
|
89
|
+
immediate: true,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
addData(params) {
|
|
95
|
+
const data = params.entry
|
|
96
|
+
const { service } = params
|
|
97
|
+
const immediateCallback = params.immediateCallback || false
|
|
98
|
+
this.log(
|
|
99
|
+
'[%s] FGT addData() [%s] immediate [%s].',
|
|
100
|
+
service.accessoryName,
|
|
101
|
+
data,
|
|
102
|
+
immediateCallback,
|
|
103
|
+
)
|
|
104
|
+
if (immediateCallback) {
|
|
105
|
+
this.getSubscriber(service).backLog[0] = data
|
|
106
|
+
} else {
|
|
107
|
+
this.getSubscriber(service).backLog.push(data)
|
|
108
|
+
}
|
|
109
|
+
if (immediateCallback) {
|
|
110
|
+
this.executeImmediateCallback(this.getSubscriber(service))
|
|
111
|
+
}
|
|
112
|
+
if (!this.running) {
|
|
113
|
+
this.start()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
emptyData(service) {
|
|
118
|
+
this.log('[%s] FGT emptyData().', service.accessoryName)
|
|
119
|
+
const source = this.getSubscriber(service)
|
|
120
|
+
if (source.backLog.length) {
|
|
121
|
+
source.previousBackLog = source.backLog
|
|
122
|
+
}
|
|
123
|
+
source.backLog = []
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// https://github.com/homebridge/HAP-NodeJS/blob/master/src/lib/util/uuid.ts
|
|
2
|
+
|
|
3
|
+
function isValid(UUID) {
|
|
4
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
5
|
+
return uuidRegex.test(UUID)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function toLongFormUUID(uuid, base = '-0000-1000-8000-0026BB765291') {
|
|
9
|
+
const shortRegex = /^[0-9a-f]{1,8}$/i
|
|
10
|
+
if (isValid(uuid)) {
|
|
11
|
+
return uuid.toUpperCase()
|
|
12
|
+
}
|
|
13
|
+
if (!shortRegex.test(uuid)) {
|
|
14
|
+
throw new TypeError('uuid was not a valid UUID or short form UUID')
|
|
15
|
+
}
|
|
16
|
+
if (!isValid(`00000000${base}`)) {
|
|
17
|
+
throw new TypeError('base was not a valid base UUID')
|
|
18
|
+
}
|
|
19
|
+
return ((`00000000${uuid}`).substr(-8) + base).toUpperCase()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toShortFormUUID(uuid, base = '-0000-1000-8000-0026BB765291') {
|
|
23
|
+
uuid = toLongFormUUID(uuid, base)
|
|
24
|
+
return uuid.substr(0, 8)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { toLongFormUUID, toShortFormUUID }
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
<p class="text-center">
|
|
2
|
+
<img
|
|
3
|
+
src="https://user-images.githubusercontent.com/43026681/101325266-63126600-3863-11eb-9382-4a2924f0e540.png"
|
|
4
|
+
alt="homebridge-ewelink logo"
|
|
5
|
+
style="width: 60%;"
|
|
6
|
+
/>
|
|
7
|
+
</p>
|
|
8
|
+
<div id="pageIntro" class="text-center" style="display: none;">
|
|
9
|
+
<p class="lead">Thank you for installing <strong>homebridge-ewelink</strong></p>
|
|
10
|
+
<p>
|
|
11
|
+
You will need to enter your eWeLink username and password on the next page
|
|
12
|
+
</p>
|
|
13
|
+
<button type="button" class="btn btn-primary" id="introContinue">
|
|
14
|
+
Continue →
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
<div
|
|
18
|
+
id="menuWrapper"
|
|
19
|
+
class="btn-group w-100 mb-0"
|
|
20
|
+
role="group"
|
|
21
|
+
aria-label="UI Menu"
|
|
22
|
+
style="display: none;"
|
|
23
|
+
>
|
|
24
|
+
<button type="button" class="btn btn-primary ml-0" id="menuSettings">
|
|
25
|
+
Settings
|
|
26
|
+
</button>
|
|
27
|
+
<button type="button" class="btn btn-primary" id="menuDevices">
|
|
28
|
+
My Devices
|
|
29
|
+
</button>
|
|
30
|
+
<button type="button" class="btn btn-primary mr-0" id="menuHome">
|
|
31
|
+
Support
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
id="disabledBanner"
|
|
36
|
+
class="alert alert-secondary mb-0 mt-3"
|
|
37
|
+
role="alert"
|
|
38
|
+
style="display: none;"
|
|
39
|
+
>
|
|
40
|
+
Plugin is currently disabled
|
|
41
|
+
<button id="disabledEnable" type="button" class="btn btn-link p-0 m-0 float-right">
|
|
42
|
+
Enable
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
<div id="pageDevices" class="mt-4" style="display: none;">
|
|
46
|
+
<div id="deviceInfo">
|
|
47
|
+
<form>
|
|
48
|
+
<div class="form-group">
|
|
49
|
+
<select class="form-control" id="deviceSelect"></select>
|
|
50
|
+
</div>
|
|
51
|
+
</form>
|
|
52
|
+
<table class="table w-100" id="deviceTable" style="display: none;">
|
|
53
|
+
<thead>
|
|
54
|
+
<tr class="table-active">
|
|
55
|
+
<th scope="col" style="width: 40%;">Device Name</th>
|
|
56
|
+
<th scope="col" style="width: 60%;" id="displayName"></th>
|
|
57
|
+
</tr>
|
|
58
|
+
</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
<tr>
|
|
61
|
+
<th scope="row">Cloud Status</th>
|
|
62
|
+
<td id="reachableWAN"></td>
|
|
63
|
+
</tr>
|
|
64
|
+
<tr>
|
|
65
|
+
<th scope="row">LAN Status</th>
|
|
66
|
+
<td id="reachableLAN"></td>
|
|
67
|
+
</tr>
|
|
68
|
+
<tr>
|
|
69
|
+
<th scope="row">LAN IP</th>
|
|
70
|
+
<td id="lanIP"></td>
|
|
71
|
+
</tr>
|
|
72
|
+
<tr>
|
|
73
|
+
<th scope="row">Homebridge ID</th>
|
|
74
|
+
<td id="hbDeviceId"></td>
|
|
75
|
+
</tr>
|
|
76
|
+
<tr>
|
|
77
|
+
<th scope="row">eWeLink ID</th>
|
|
78
|
+
<td id="eweDeviceId"></td>
|
|
79
|
+
</tr>
|
|
80
|
+
<tr>
|
|
81
|
+
<th scope="row">Brand</th>
|
|
82
|
+
<td id="eweBrandName"></td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<th scope="row">Model (UIID)</th>
|
|
86
|
+
<td id="eweModel"></td>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr>
|
|
89
|
+
<th scope="row">Firmware</th>
|
|
90
|
+
<td id="eweFirmware"></td>
|
|
91
|
+
</tr>
|
|
92
|
+
<tr>
|
|
93
|
+
<th scope="row">MAC Address</th>
|
|
94
|
+
<td id="eweMacAddress"></td>
|
|
95
|
+
</tr>
|
|
96
|
+
<tr>
|
|
97
|
+
<th scope="row">Shared Device</th>
|
|
98
|
+
<td id="eweShared"></td>
|
|
99
|
+
</tr>
|
|
100
|
+
<tr>
|
|
101
|
+
<td colspan="2" style="text-align: center" id="imgIcon"></td>
|
|
102
|
+
</tr>
|
|
103
|
+
</tbody>
|
|
104
|
+
</table>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div id="pageSupport" class="mt-4" style="display: none;">
|
|
108
|
+
<p class="text-center lead">Thank you for using <strong>homebridge-ewelink</strong></p>
|
|
109
|
+
<p class="text-center">The links below will take you to our GitHub wiki</p>
|
|
110
|
+
<h4>Setup</h4>
|
|
111
|
+
<ul>
|
|
112
|
+
<li>
|
|
113
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Installation" target="_blank"
|
|
114
|
+
>Installation</a
|
|
115
|
+
>
|
|
116
|
+
</li>
|
|
117
|
+
<li>
|
|
118
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Configuration" target="_blank"
|
|
119
|
+
>Configuration</a
|
|
120
|
+
>
|
|
121
|
+
</li>
|
|
122
|
+
<li>
|
|
123
|
+
<a href="https://github.com/homebridge/homebridge/wiki/How-to-Install-Alternate-Plugin-Versions" target="_blank"
|
|
124
|
+
>Beta Version</a
|
|
125
|
+
>
|
|
126
|
+
</li>
|
|
127
|
+
<li>
|
|
128
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Node-Version" target="_blank"
|
|
129
|
+
>Node Version</a
|
|
130
|
+
>
|
|
131
|
+
</li>
|
|
132
|
+
</ul>
|
|
133
|
+
<h4>Features</h4>
|
|
134
|
+
<ul>
|
|
135
|
+
<li>
|
|
136
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Supported-Devices" target="_blank"
|
|
137
|
+
>Supported Devices</a
|
|
138
|
+
>
|
|
139
|
+
</li>
|
|
140
|
+
<li>
|
|
141
|
+
<a
|
|
142
|
+
href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Accessory-Simulations"
|
|
143
|
+
target="_blank"
|
|
144
|
+
>Accessory Simulations</a
|
|
145
|
+
>
|
|
146
|
+
</li>
|
|
147
|
+
<li>
|
|
148
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Connection-Methods" target="_blank"
|
|
149
|
+
>Connection Methods</a
|
|
150
|
+
>
|
|
151
|
+
</li>
|
|
152
|
+
<li>
|
|
153
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Internal-API" target="_blank"
|
|
154
|
+
>Internal API</a
|
|
155
|
+
>
|
|
156
|
+
</li>
|
|
157
|
+
</ul>
|
|
158
|
+
<h4>Help/About</h4>
|
|
159
|
+
<ul>
|
|
160
|
+
<li>
|
|
161
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Common-Errors" target="_blank"
|
|
162
|
+
>Common Errors</a
|
|
163
|
+
>
|
|
164
|
+
</li>
|
|
165
|
+
<li>
|
|
166
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/issues/new/choose" target="_blank"
|
|
167
|
+
>Support Request</a
|
|
168
|
+
>
|
|
169
|
+
</li>
|
|
170
|
+
<li>
|
|
171
|
+
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/blob/latest/CHANGELOG.md" target="_blank"
|
|
172
|
+
>Changelog</a
|
|
173
|
+
>
|
|
174
|
+
</li>
|
|
175
|
+
<li>
|
|
176
|
+
<a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a>
|
|
177
|
+
</li>
|
|
178
|
+
</ul>
|
|
179
|
+
<h4>Credits</h4>
|
|
180
|
+
<ul>
|
|
181
|
+
<li>
|
|
182
|
+
To the original plugin maintainer:
|
|
183
|
+
<a href="https://github.com/gbro115" target="_blank">@gbro115</a>.
|
|
184
|
+
</li>
|
|
185
|
+
<li>
|
|
186
|
+
To successive contributors:
|
|
187
|
+
<a href="https://github.com/MrTomAsh" target="_blank">@MrTomAsh</a> and
|
|
188
|
+
<a href="https://github.com/howanghk" target="_blank">@howanghk</a> for
|
|
189
|
+
<a href="https://github.com/howanghk/homebridge-ewelink" target="_blank"
|
|
190
|
+
>homebridge-ewelink-max</a
|
|
191
|
+
>.
|
|
192
|
+
</li>
|
|
193
|
+
<li>
|
|
194
|
+
To the creators/contributors of
|
|
195
|
+
<a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.
|
|
196
|
+
</li>
|
|
197
|
+
<li>
|
|
198
|
+
To the creators/contributors of
|
|
199
|
+
<a href="https://github.com/simont77/fakegato-history" target="_blank">Fakegato</a>:
|
|
200
|
+
<a href="https://github.com/simont77" target="_blank">@simont77</a> and
|
|
201
|
+
<a href="https://github.com/NorthernMan54" target="_blank">@NorthernMan54</a>.
|
|
202
|
+
</li>
|
|
203
|
+
<li>
|
|
204
|
+
To the creator of the awesome plugin header logo:
|
|
205
|
+
<a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.
|
|
206
|
+
</li>
|
|
207
|
+
<li>To all users who have shared their devices to enable functionality.</li>
|
|
208
|
+
</ul>
|
|
209
|
+
|
|
210
|
+
<h4>Disclaimer</h4>
|
|
211
|
+
<ul>
|
|
212
|
+
<li>
|
|
213
|
+
I am in no way affiliated with eWeLink nor any of the device brands (like Sonoff) and this
|
|
214
|
+
plugin is a personal project that I maintain in my free time.
|
|
215
|
+
</li>
|
|
216
|
+
<li>
|
|
217
|
+
Use this plugin entirely at your own risk - please see licence for more information.
|
|
218
|
+
</li>
|
|
219
|
+
</ul>
|
|
220
|
+
</div>
|
|
221
|
+
<script>
|
|
222
|
+
;(async () => {
|
|
223
|
+
try {
|
|
224
|
+
const currentConfig = await homebridge.getPluginConfig()
|
|
225
|
+
showIntro = () => {
|
|
226
|
+
const introContinue = document.getElementById('introContinue')
|
|
227
|
+
introContinue.addEventListener('click', () => {
|
|
228
|
+
homebridge.showSpinner()
|
|
229
|
+
document.getElementById('pageIntro').style.display = 'none'
|
|
230
|
+
document.getElementById('menuWrapper').style.display = 'inline-flex'
|
|
231
|
+
showSettings()
|
|
232
|
+
homebridge.hideSpinner()
|
|
233
|
+
})
|
|
234
|
+
document.getElementById('pageIntro').style.display = 'block'
|
|
235
|
+
}
|
|
236
|
+
showDevices = async () => {
|
|
237
|
+
homebridge.showSpinner()
|
|
238
|
+
homebridge.hideSchemaForm()
|
|
239
|
+
document.getElementById('menuHome').classList.remove('btn-elegant')
|
|
240
|
+
document.getElementById('menuHome').classList.add('btn-primary')
|
|
241
|
+
document.getElementById('menuDevices').classList.add('btn-elegant')
|
|
242
|
+
document.getElementById('menuDevices').classList.remove('btn-primary')
|
|
243
|
+
document.getElementById('menuSettings').classList.remove('btn-elegant')
|
|
244
|
+
document.getElementById('menuSettings').classList.add('btn-primary')
|
|
245
|
+
document.getElementById('pageSupport').style.display = 'none'
|
|
246
|
+
document.getElementById('pageDevices').style.display = 'block'
|
|
247
|
+
const cachedAccessories =
|
|
248
|
+
typeof homebridge.getCachedAccessories === 'function'
|
|
249
|
+
? await homebridge.getCachedAccessories()
|
|
250
|
+
: await homebridge.request('/getCachedAccessories')
|
|
251
|
+
if (cachedAccessories.length > 0) {
|
|
252
|
+
cachedAccessories.sort((a, b) => {
|
|
253
|
+
return a.displayName.toLowerCase() > b.displayName.toLowerCase()
|
|
254
|
+
? 1
|
|
255
|
+
: b.displayName.toLowerCase() > a.displayName.toLowerCase()
|
|
256
|
+
? -1
|
|
257
|
+
: 0
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
const deviceSelect = document.getElementById('deviceSelect')
|
|
261
|
+
deviceSelect.innerHTML = ''
|
|
262
|
+
cachedAccessories.forEach(a => {
|
|
263
|
+
const option = document.createElement('option')
|
|
264
|
+
option.text = a.displayName
|
|
265
|
+
option.value = a.context.hbDeviceId
|
|
266
|
+
deviceSelect.add(option)
|
|
267
|
+
})
|
|
268
|
+
showDeviceInfo = async hbDeviceId => {
|
|
269
|
+
homebridge.showSpinner()
|
|
270
|
+
const thisAcc = cachedAccessories.find(x => x.context.hbDeviceId === hbDeviceId)
|
|
271
|
+
const context = thisAcc.context
|
|
272
|
+
document.getElementById('displayName').innerHTML = thisAcc.displayName
|
|
273
|
+
document.getElementById('reachableWAN').innerHTML = context.reachableWAN
|
|
274
|
+
? '<i class="fas fa-circle mr-1 green-text"></i> Online'
|
|
275
|
+
: '<i class="fas fa-circle mr-1 red-text"></i> Offline'
|
|
276
|
+
document.getElementById('reachableLAN').innerHTML = context.reachableLAN
|
|
277
|
+
? '<i class="fas fa-circle mr-1 green-text"></i> Online'
|
|
278
|
+
: '<i class="fas fa-circle mr-1 red-text"></i> Offline'
|
|
279
|
+
document.getElementById('lanIP').innerHTML = context.ip || 'N/A'
|
|
280
|
+
document.getElementById('hbDeviceId').innerHTML = context.hbDeviceId || 'N/A'
|
|
281
|
+
document.getElementById('eweDeviceId').innerHTML = context.eweDeviceId || 'N/A'
|
|
282
|
+
document.getElementById('eweBrandName').innerHTML = context.eweBrandName || 'N/A'
|
|
283
|
+
document.getElementById('eweFirmware').innerHTML = context.firmware || 'N/A'
|
|
284
|
+
document.getElementById('eweMacAddress').innerHTML = context.macAddress || 'N/A'
|
|
285
|
+
document.getElementById('eweModel').innerHTML =
|
|
286
|
+
(context.eweModel || 'N/A') + ' (' + (context.eweUIID || 'N/A') + ')'
|
|
287
|
+
document.getElementById('eweShared').innerHTML = context.eweShared
|
|
288
|
+
? 'Yes (by ' + context.eweShared + ')'
|
|
289
|
+
: 'No'
|
|
290
|
+
document.getElementById('imgIcon').innerHTML = context.eweBrandLogo
|
|
291
|
+
? '<img src="' + context.eweBrandLogo + '" style="width: 150px;">'
|
|
292
|
+
: ''
|
|
293
|
+
document.getElementById('deviceTable').style.display = 'inline-table'
|
|
294
|
+
homebridge.hideSpinner()
|
|
295
|
+
}
|
|
296
|
+
deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
|
|
297
|
+
if (cachedAccessories.length > 0) {
|
|
298
|
+
showDeviceInfo(cachedAccessories[0].context.hbDeviceId)
|
|
299
|
+
} else {
|
|
300
|
+
const option = document.createElement('option')
|
|
301
|
+
option.text = 'No Devices'
|
|
302
|
+
deviceSelect.add(option)
|
|
303
|
+
deviceSelect.disabled = true
|
|
304
|
+
}
|
|
305
|
+
homebridge.hideSpinner()
|
|
306
|
+
}
|
|
307
|
+
showSupport = () => {
|
|
308
|
+
homebridge.showSpinner()
|
|
309
|
+
homebridge.hideSchemaForm()
|
|
310
|
+
document.getElementById('menuHome').classList.add('btn-elegant')
|
|
311
|
+
document.getElementById('menuHome').classList.remove('btn-primary')
|
|
312
|
+
document.getElementById('menuDevices').classList.remove('btn-elegant')
|
|
313
|
+
document.getElementById('menuDevices').classList.add('btn-primary')
|
|
314
|
+
document.getElementById('menuSettings').classList.remove('btn-elegant')
|
|
315
|
+
document.getElementById('menuSettings').classList.add('btn-primary')
|
|
316
|
+
document.getElementById('pageSupport').style.display = 'block'
|
|
317
|
+
document.getElementById('pageDevices').style.display = 'none'
|
|
318
|
+
homebridge.hideSpinner()
|
|
319
|
+
}
|
|
320
|
+
showSettings = () => {
|
|
321
|
+
homebridge.showSpinner()
|
|
322
|
+
document.getElementById('menuHome').classList.remove('btn-elegant')
|
|
323
|
+
document.getElementById('menuHome').classList.add('btn-primary')
|
|
324
|
+
document.getElementById('menuDevices').classList.remove('btn-elegant')
|
|
325
|
+
document.getElementById('menuDevices').classList.add('btn-primary')
|
|
326
|
+
document.getElementById('menuSettings').classList.add('btn-elegant')
|
|
327
|
+
document.getElementById('menuSettings').classList.remove('btn-primary')
|
|
328
|
+
document.getElementById('pageSupport').style.display = 'none'
|
|
329
|
+
document.getElementById('pageDevices').style.display = 'none'
|
|
330
|
+
homebridge.showSchemaForm()
|
|
331
|
+
homebridge.hideSpinner()
|
|
332
|
+
}
|
|
333
|
+
showDisabledBanner = () => {
|
|
334
|
+
document.getElementById('disabledBanner').style.display = 'block'
|
|
335
|
+
}
|
|
336
|
+
enablePlugin = async () => {
|
|
337
|
+
homebridge.showSpinner()
|
|
338
|
+
document.getElementById('disabledBanner').style.display = 'none'
|
|
339
|
+
currentConfig[0].disablePlugin = false
|
|
340
|
+
await homebridge.updatePluginConfig(currentConfig)
|
|
341
|
+
await homebridge.savePluginConfig()
|
|
342
|
+
homebridge.hideSpinner()
|
|
343
|
+
}
|
|
344
|
+
menuHome.addEventListener('click', () => showSupport())
|
|
345
|
+
menuDevices.addEventListener('click', () => showDevices())
|
|
346
|
+
menuSettings.addEventListener('click', () => showSettings())
|
|
347
|
+
disabledEnable.addEventListener('click', () => enablePlugin())
|
|
348
|
+
if (currentConfig.length) {
|
|
349
|
+
document.getElementById('menuWrapper').style.display = 'inline-flex'
|
|
350
|
+
showSettings()
|
|
351
|
+
if (currentConfig[0].disablePlugin) {
|
|
352
|
+
showDisabledBanner()
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
currentConfig.push({ name: 'eWeLink' })
|
|
356
|
+
await homebridge.updatePluginConfig(currentConfig)
|
|
357
|
+
showIntro()
|
|
358
|
+
}
|
|
359
|
+
} catch (err) {
|
|
360
|
+
homebridge.toast.error(err.message, 'Error')
|
|
361
|
+
} finally {
|
|
362
|
+
homebridge.hideSpinner()
|
|
363
|
+
}
|
|
364
|
+
})()
|
|
365
|
+
</script>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils'
|
|
3
|
+
|
|
4
|
+
class PluginUiServer extends HomebridgePluginUiServer {
|
|
5
|
+
constructor() {
|
|
6
|
+
super()
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
A native method getCachedAccessories() was introduced in config-ui-x v4.37.0
|
|
10
|
+
The following is for users who have a lower version of config-ui-x
|
|
11
|
+
*/
|
|
12
|
+
this.onRequest('/getCachedAccessories', async () => {
|
|
13
|
+
try {
|
|
14
|
+
// Define the plugin and create the array to return
|
|
15
|
+
const plugin = 'homebridge-ewelink'
|
|
16
|
+
const devicesToReturn = []
|
|
17
|
+
|
|
18
|
+
// The path and file of the cached accessories
|
|
19
|
+
const accFile = `${this.homebridgeStoragePath}/accessories/cachedAccessories`
|
|
20
|
+
|
|
21
|
+
// Check the file exists
|
|
22
|
+
if (fs.existsSync(accFile)) {
|
|
23
|
+
// Read the cached accessories file
|
|
24
|
+
let cachedAccessories = await fs.promises.readFile(accFile)
|
|
25
|
+
|
|
26
|
+
// Parse the JSON
|
|
27
|
+
cachedAccessories = JSON.parse(cachedAccessories)
|
|
28
|
+
|
|
29
|
+
// We only want the accessories for this plugin
|
|
30
|
+
cachedAccessories
|
|
31
|
+
.filter(accessory => accessory.plugin === plugin)
|
|
32
|
+
.forEach(accessory => devicesToReturn.push(accessory))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Return the array
|
|
36
|
+
return devicesToReturn
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Just return an empty accessory list in case of any errors
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
this.ready()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
(() => new PluginUiServer())()
|
package/lib/index.js
ADDED