@homebridge-plugins/homebridge-govee 10.12.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/CHANGELOG.md +1937 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/config.schema.json +1727 -0
- package/eslint.config.js +49 -0
- package/lib/connection/aws.js +174 -0
- package/lib/connection/ble.js +208 -0
- package/lib/connection/cert/AmazonRootCA1.pem +20 -0
- package/lib/connection/http.js +240 -0
- package/lib/connection/lan.js +284 -0
- package/lib/device/cooler-single.js +300 -0
- package/lib/device/dehumidifier-H7150.js +182 -0
- package/lib/device/dehumidifier-H7151.js +157 -0
- package/lib/device/diffuser-H7161.js +117 -0
- package/lib/device/diffuser-H7162.js +117 -0
- package/lib/device/fan-H7100.js +274 -0
- package/lib/device/fan-H7101.js +330 -0
- package/lib/device/fan-H7102.js +274 -0
- package/lib/device/fan-H7105.js +503 -0
- package/lib/device/fan-H7106.js +274 -0
- package/lib/device/fan-H7111.js +335 -0
- package/lib/device/heater-single.js +300 -0
- package/lib/device/heater1a.js +353 -0
- package/lib/device/heater1b.js +616 -0
- package/lib/device/heater2.js +838 -0
- package/lib/device/humidifier-H7140.js +224 -0
- package/lib/device/humidifier-H7141.js +257 -0
- package/lib/device/humidifier-H7142.js +522 -0
- package/lib/device/humidifier-H7143.js +157 -0
- package/lib/device/humidifier-H7148.js +157 -0
- package/lib/device/humidifier-H7160.js +446 -0
- package/lib/device/ice-maker-H7162.js +46 -0
- package/lib/device/index.js +105 -0
- package/lib/device/kettle.js +269 -0
- package/lib/device/light-switch.js +86 -0
- package/lib/device/light.js +617 -0
- package/lib/device/outlet-double.js +121 -0
- package/lib/device/outlet-single.js +172 -0
- package/lib/device/outlet-triple.js +160 -0
- package/lib/device/purifier-H7120.js +336 -0
- package/lib/device/purifier-H7121.js +336 -0
- package/lib/device/purifier-H7122.js +449 -0
- package/lib/device/purifier-H7123.js +411 -0
- package/lib/device/purifier-H7124.js +411 -0
- package/lib/device/purifier-H7126.js +296 -0
- package/lib/device/purifier-H7127.js +296 -0
- package/lib/device/purifier-H712C.js +296 -0
- package/lib/device/purifier-single.js +119 -0
- package/lib/device/sensor-button.js +22 -0
- package/lib/device/sensor-contact.js +22 -0
- package/lib/device/sensor-leak.js +87 -0
- package/lib/device/sensor-monitor.js +190 -0
- package/lib/device/sensor-presence.js +53 -0
- package/lib/device/sensor-thermo.js +144 -0
- package/lib/device/sensor-thermo4.js +55 -0
- package/lib/device/switch-double.js +121 -0
- package/lib/device/switch-single.js +95 -0
- package/lib/device/switch-triple.js +160 -0
- package/lib/device/tap-single.js +108 -0
- package/lib/device/template.js +43 -0
- package/lib/device/tv-single.js +84 -0
- package/lib/device/valve-single.js +155 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +814 -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 +433 -0
- package/lib/homebridge-ui/server.js +10 -0
- package/lib/index.js +8 -0
- package/lib/platform.js +1967 -0
- package/lib/utils/colour.js +564 -0
- package/lib/utils/constants.js +579 -0
- package/lib/utils/custom-chars.js +225 -0
- package/lib/utils/eve-chars.js +68 -0
- package/lib/utils/functions.js +117 -0
- package/lib/utils/lang-en.js +131 -0
- package/package.json +75 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { inherits } from 'node:util'
|
|
2
|
+
|
|
3
|
+
export default class {
|
|
4
|
+
constructor(api) {
|
|
5
|
+
this.hapServ = api.hap.Service
|
|
6
|
+
this.hapChar = api.hap.Characteristic
|
|
7
|
+
this.uuids = {
|
|
8
|
+
/* deprecated
|
|
9
|
+
bluetooth: 'E964F001-079E-48FF-8F27-9C2605A29F52'
|
|
10
|
+
bluetoothConn: 'E964F002-079E-48FF-8F27-9C2605A29F52'
|
|
11
|
+
musicMode: 'E964F003-079E-48FF-8F27-9C2605A29F52'
|
|
12
|
+
*/
|
|
13
|
+
colourMode: 'E964F004-079E-48FF-8F27-9C2605A29F52',
|
|
14
|
+
musicMode: 'E964F005-079E-48FF-8F27-9C2605A29F52',
|
|
15
|
+
musicModeTwo: 'E964F006-079E-48FF-8F27-9C2605A29F52',
|
|
16
|
+
scene: 'E964F007-079E-48FF-8F27-9C2605A29F52',
|
|
17
|
+
sceneTwo: 'E964F008-079E-48FF-8F27-9C2605A29F52',
|
|
18
|
+
diyMode: 'E964F009-079E-48FF-8F27-9C2605A29F52',
|
|
19
|
+
diyModeTwo: 'E964F010-079E-48FF-8F27-9C2605A29F52',
|
|
20
|
+
sceneThree: 'E964F011-079E-48FF-8F27-9C2605A29F52',
|
|
21
|
+
sceneFour: 'E964F012-079E-48FF-8F27-9C2605A29F52',
|
|
22
|
+
diyModeThree: 'E964F013-079E-48FF-8F27-9C2605A29F52',
|
|
23
|
+
diyModeFour: 'E964F014-079E-48FF-8F27-9C2605A29F52',
|
|
24
|
+
segmented: 'E964F015-079E-48FF-8F27-9C2605A29F52',
|
|
25
|
+
segmentedTwo: 'E964F016-079E-48FF-8F27-9C2605A29F52',
|
|
26
|
+
segmentedThree: 'E964F017-079E-48FF-8F27-9C2605A29F52',
|
|
27
|
+
segmentedFour: 'E964F018-079E-48FF-8F27-9C2605A29F52',
|
|
28
|
+
videoMode: 'E964F019-079E-48FF-8F27-9C2605A29F52',
|
|
29
|
+
videoModeTwo: 'E964F020-079E-48FF-8F27-9C2605A29F52',
|
|
30
|
+
nightLight: 'E964F021-079E-48FF-8F27-9C2605A29F52',
|
|
31
|
+
displayLight: 'E964F022-079E-48FF-8F27-9C2605A29F52',
|
|
32
|
+
}
|
|
33
|
+
const self = this
|
|
34
|
+
this.ColourMode = function ColourMode() {
|
|
35
|
+
self.hapChar.call(this, 'Colour Mode', self.uuids.colourMode)
|
|
36
|
+
this.setProps({
|
|
37
|
+
format: api.hap.Formats.BOOL,
|
|
38
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
39
|
+
})
|
|
40
|
+
this.value = this.getDefaultValue()
|
|
41
|
+
}
|
|
42
|
+
this.MusicMode = function MusicMode() {
|
|
43
|
+
self.hapChar.call(this, 'Music Mode', self.uuids.musicMode)
|
|
44
|
+
this.setProps({
|
|
45
|
+
format: api.hap.Formats.BOOL,
|
|
46
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
47
|
+
})
|
|
48
|
+
this.value = this.getDefaultValue()
|
|
49
|
+
}
|
|
50
|
+
this.MusicModeTwo = function MusicModeTwo() {
|
|
51
|
+
self.hapChar.call(this, 'Music Mode 2', self.uuids.musicModeTwo)
|
|
52
|
+
this.setProps({
|
|
53
|
+
format: api.hap.Formats.BOOL,
|
|
54
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
55
|
+
})
|
|
56
|
+
this.value = this.getDefaultValue()
|
|
57
|
+
}
|
|
58
|
+
this.Scene = function Scene() {
|
|
59
|
+
self.hapChar.call(this, 'Scene', self.uuids.scene)
|
|
60
|
+
this.setProps({
|
|
61
|
+
format: api.hap.Formats.BOOL,
|
|
62
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
63
|
+
})
|
|
64
|
+
this.value = this.getDefaultValue()
|
|
65
|
+
}
|
|
66
|
+
this.SceneTwo = function SceneTwo() {
|
|
67
|
+
self.hapChar.call(this, 'Scene 2', self.uuids.sceneTwo)
|
|
68
|
+
this.setProps({
|
|
69
|
+
format: api.hap.Formats.BOOL,
|
|
70
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
71
|
+
})
|
|
72
|
+
this.value = this.getDefaultValue()
|
|
73
|
+
}
|
|
74
|
+
this.SceneThree = function SceneThree() {
|
|
75
|
+
self.hapChar.call(this, 'Scene 3', self.uuids.sceneThree)
|
|
76
|
+
this.setProps({
|
|
77
|
+
format: api.hap.Formats.BOOL,
|
|
78
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
79
|
+
})
|
|
80
|
+
this.value = this.getDefaultValue()
|
|
81
|
+
}
|
|
82
|
+
this.SceneFour = function SceneFour() {
|
|
83
|
+
self.hapChar.call(this, 'Scene 4', self.uuids.sceneFour)
|
|
84
|
+
this.setProps({
|
|
85
|
+
format: api.hap.Formats.BOOL,
|
|
86
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
87
|
+
})
|
|
88
|
+
this.value = this.getDefaultValue()
|
|
89
|
+
}
|
|
90
|
+
this.DiyMode = function DiyMode() {
|
|
91
|
+
self.hapChar.call(this, 'DIY Mode', self.uuids.diyMode)
|
|
92
|
+
this.setProps({
|
|
93
|
+
format: api.hap.Formats.BOOL,
|
|
94
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
95
|
+
})
|
|
96
|
+
this.value = this.getDefaultValue()
|
|
97
|
+
}
|
|
98
|
+
this.DiyModeTwo = function DiyModeTwo() {
|
|
99
|
+
self.hapChar.call(this, 'DIY Mode 2', self.uuids.diyModeTwo)
|
|
100
|
+
this.setProps({
|
|
101
|
+
format: api.hap.Formats.BOOL,
|
|
102
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
103
|
+
})
|
|
104
|
+
this.value = this.getDefaultValue()
|
|
105
|
+
}
|
|
106
|
+
this.DiyModeThree = function DiyModeThree() {
|
|
107
|
+
self.hapChar.call(this, 'DIY Mode 3', self.uuids.diyModeThree)
|
|
108
|
+
this.setProps({
|
|
109
|
+
format: api.hap.Formats.BOOL,
|
|
110
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
111
|
+
})
|
|
112
|
+
this.value = this.getDefaultValue()
|
|
113
|
+
}
|
|
114
|
+
this.DiyModeFour = function DiyModeFour() {
|
|
115
|
+
self.hapChar.call(this, 'DIY Mode 4', self.uuids.diyModeFour)
|
|
116
|
+
this.setProps({
|
|
117
|
+
format: api.hap.Formats.BOOL,
|
|
118
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
119
|
+
})
|
|
120
|
+
this.value = this.getDefaultValue()
|
|
121
|
+
}
|
|
122
|
+
this.Segmented = function Segmented() {
|
|
123
|
+
self.hapChar.call(this, 'Segmented', self.uuids.segmented)
|
|
124
|
+
this.setProps({
|
|
125
|
+
format: api.hap.Formats.BOOL,
|
|
126
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
127
|
+
})
|
|
128
|
+
this.value = this.getDefaultValue()
|
|
129
|
+
}
|
|
130
|
+
this.SegmentedTwo = function SegmentedTwo() {
|
|
131
|
+
self.hapChar.call(this, 'Segmented 2', self.uuids.segmentedTwo)
|
|
132
|
+
this.setProps({
|
|
133
|
+
format: api.hap.Formats.BOOL,
|
|
134
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
135
|
+
})
|
|
136
|
+
this.value = this.getDefaultValue()
|
|
137
|
+
}
|
|
138
|
+
this.SegmentedThree = function SegmentedThree() {
|
|
139
|
+
self.hapChar.call(this, 'Segmented 3', self.uuids.segmentedThree)
|
|
140
|
+
this.setProps({
|
|
141
|
+
format: api.hap.Formats.BOOL,
|
|
142
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
143
|
+
})
|
|
144
|
+
this.value = this.getDefaultValue()
|
|
145
|
+
}
|
|
146
|
+
this.SegmentedFour = function SegmentedFour() {
|
|
147
|
+
self.hapChar.call(this, 'Segmented 4', self.uuids.segmentedFour)
|
|
148
|
+
this.setProps({
|
|
149
|
+
format: api.hap.Formats.BOOL,
|
|
150
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
151
|
+
})
|
|
152
|
+
this.value = this.getDefaultValue()
|
|
153
|
+
}
|
|
154
|
+
this.VideoMode = function VideoMode() {
|
|
155
|
+
self.hapChar.call(this, 'Video Mode', self.uuids.videoMode)
|
|
156
|
+
this.setProps({
|
|
157
|
+
format: api.hap.Formats.BOOL,
|
|
158
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
159
|
+
})
|
|
160
|
+
this.value = this.getDefaultValue()
|
|
161
|
+
}
|
|
162
|
+
this.VideoModeTwo = function VideoModeTwo() {
|
|
163
|
+
self.hapChar.call(this, 'Video Mode 2', self.uuids.videoModeTwo)
|
|
164
|
+
this.setProps({
|
|
165
|
+
format: api.hap.Formats.BOOL,
|
|
166
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
167
|
+
})
|
|
168
|
+
this.value = this.getDefaultValue()
|
|
169
|
+
}
|
|
170
|
+
this.NightLight = function NightLight() {
|
|
171
|
+
self.hapChar.call(this, 'Night Light', self.uuids.nightLight)
|
|
172
|
+
this.setProps({
|
|
173
|
+
format: api.hap.Formats.BOOL,
|
|
174
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
175
|
+
})
|
|
176
|
+
this.value = this.getDefaultValue()
|
|
177
|
+
}
|
|
178
|
+
this.DisplayLight = function DisplayLight() {
|
|
179
|
+
self.hapChar.call(this, 'Display Light', self.uuids.displayLight)
|
|
180
|
+
this.setProps({
|
|
181
|
+
format: api.hap.Formats.BOOL,
|
|
182
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.WRITE, api.hap.Perms.NOTIFY],
|
|
183
|
+
})
|
|
184
|
+
this.value = this.getDefaultValue()
|
|
185
|
+
}
|
|
186
|
+
inherits(this.ColourMode, this.hapChar)
|
|
187
|
+
inherits(this.MusicMode, this.hapChar)
|
|
188
|
+
inherits(this.MusicModeTwo, this.hapChar)
|
|
189
|
+
inherits(this.Scene, this.hapChar)
|
|
190
|
+
inherits(this.SceneTwo, this.hapChar)
|
|
191
|
+
inherits(this.SceneThree, this.hapChar)
|
|
192
|
+
inherits(this.SceneFour, this.hapChar)
|
|
193
|
+
inherits(this.DiyMode, this.hapChar)
|
|
194
|
+
inherits(this.DiyModeTwo, this.hapChar)
|
|
195
|
+
inherits(this.DiyModeThree, this.hapChar)
|
|
196
|
+
inherits(this.DiyModeFour, this.hapChar)
|
|
197
|
+
inherits(this.Segmented, this.hapChar)
|
|
198
|
+
inherits(this.SegmentedTwo, this.hapChar)
|
|
199
|
+
inherits(this.SegmentedThree, this.hapChar)
|
|
200
|
+
inherits(this.SegmentedFour, this.hapChar)
|
|
201
|
+
inherits(this.VideoMode, this.hapChar)
|
|
202
|
+
inherits(this.VideoModeTwo, this.hapChar)
|
|
203
|
+
inherits(this.NightLight, this.hapChar)
|
|
204
|
+
inherits(this.DisplayLight, this.hapChar)
|
|
205
|
+
this.ColourMode.UUID = this.uuids.colourMode
|
|
206
|
+
this.MusicMode.UUID = this.uuids.musicMode
|
|
207
|
+
this.MusicModeTwo.UUID = this.uuids.musicModeTwo
|
|
208
|
+
this.Scene.UUID = this.uuids.scene
|
|
209
|
+
this.SceneTwo.UUID = this.uuids.sceneTwo
|
|
210
|
+
this.SceneThree.UUID = this.uuids.sceneThree
|
|
211
|
+
this.SceneFour.UUID = this.uuids.sceneFour
|
|
212
|
+
this.DiyMode.UUID = this.uuids.diyMode
|
|
213
|
+
this.DiyModeTwo.UUID = this.uuids.diyModeTwo
|
|
214
|
+
this.DiyModeThree.UUID = this.uuids.diyModeThree
|
|
215
|
+
this.DiyModeFour.UUID = this.uuids.diyModeFour
|
|
216
|
+
this.Segmented.UUID = this.uuids.segmented
|
|
217
|
+
this.SegmentedTwo.UUID = this.uuids.segmentedTwo
|
|
218
|
+
this.SegmentedThree.UUID = this.uuids.segmentedThree
|
|
219
|
+
this.SegmentedFour.UUID = this.uuids.segmentedFour
|
|
220
|
+
this.VideoMode.UUID = this.uuids.videoMode
|
|
221
|
+
this.VideoModeTwo.UUID = this.uuids.videoModeTwo
|
|
222
|
+
this.NightLight.UUID = this.uuids.nightLight
|
|
223
|
+
this.DisplayLight.UUID = this.uuids.displayLight
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { inherits } from 'node:util'
|
|
2
|
+
|
|
3
|
+
export default class {
|
|
4
|
+
constructor(api) {
|
|
5
|
+
this.hapServ = api.hap.Service
|
|
6
|
+
this.hapChar = api.hap.Characteristic
|
|
7
|
+
this.uuids = {
|
|
8
|
+
currentConsumption: 'E863F10D-079E-48FF-8F27-9C2605A29F52',
|
|
9
|
+
voltage: 'E863F10A-079E-48FF-8F27-9C2605A29F52',
|
|
10
|
+
electricCurrent: 'E863F126-079E-48FF-8F27-9C2605A29F52',
|
|
11
|
+
lastActivation: 'E863F11A-079E-48FF-8F27-9C2605A29F52',
|
|
12
|
+
}
|
|
13
|
+
const self = this
|
|
14
|
+
this.CurrentConsumption = function CurrentConsumption() {
|
|
15
|
+
self.hapChar.call(this, 'Current Consumption', self.uuids.currentConsumption)
|
|
16
|
+
this.setProps({
|
|
17
|
+
format: api.hap.Formats.UINT16,
|
|
18
|
+
unit: 'W',
|
|
19
|
+
maxValue: 100000,
|
|
20
|
+
minValue: 0,
|
|
21
|
+
minStep: 1,
|
|
22
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.NOTIFY],
|
|
23
|
+
})
|
|
24
|
+
this.value = this.getDefaultValue()
|
|
25
|
+
}
|
|
26
|
+
this.Voltage = function Voltage() {
|
|
27
|
+
self.hapChar.call(this, 'Voltage', self.uuids.voltage)
|
|
28
|
+
this.setProps({
|
|
29
|
+
format: api.hap.Formats.FLOAT,
|
|
30
|
+
unit: 'V',
|
|
31
|
+
maxValue: 100000000000,
|
|
32
|
+
minValue: 0,
|
|
33
|
+
minStep: 1,
|
|
34
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.NOTIFY],
|
|
35
|
+
})
|
|
36
|
+
this.value = this.getDefaultValue()
|
|
37
|
+
}
|
|
38
|
+
this.ElectricCurrent = function ElectricCurrent() {
|
|
39
|
+
self.hapChar.call(this, 'Electric Current', self.uuids.electricCurrent)
|
|
40
|
+
this.setProps({
|
|
41
|
+
format: api.hap.Formats.FLOAT,
|
|
42
|
+
unit: 'A',
|
|
43
|
+
maxValue: 100000000000,
|
|
44
|
+
minValue: 0,
|
|
45
|
+
minStep: 0.1,
|
|
46
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.NOTIFY],
|
|
47
|
+
})
|
|
48
|
+
this.value = this.getDefaultValue()
|
|
49
|
+
}
|
|
50
|
+
this.LastActivation = function LastActivation() {
|
|
51
|
+
self.hapChar.call(this, 'Last Activation', self.uuids.lastActivation)
|
|
52
|
+
this.setProps({
|
|
53
|
+
format: api.hap.Formats.UINT32,
|
|
54
|
+
unit: api.hap.Units.SECONDS,
|
|
55
|
+
perms: [api.hap.Perms.READ, api.hap.Perms.NOTIFY],
|
|
56
|
+
})
|
|
57
|
+
this.value = this.getDefaultValue()
|
|
58
|
+
}
|
|
59
|
+
inherits(this.CurrentConsumption, this.hapChar)
|
|
60
|
+
inherits(this.Voltage, this.hapChar)
|
|
61
|
+
inherits(this.ElectricCurrent, this.hapChar)
|
|
62
|
+
inherits(this.LastActivation, this.hapChar)
|
|
63
|
+
this.CurrentConsumption.UUID = this.uuids.currentConsumption
|
|
64
|
+
this.Voltage.UUID = this.uuids.voltage
|
|
65
|
+
this.ElectricCurrent.UUID = this.uuids.electricCurrent
|
|
66
|
+
this.LastActivation.UUID = this.uuids.lastActivation
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
|
|
4
|
+
import NodeRSA from 'node-rsa'
|
|
5
|
+
import pem from 'pem'
|
|
6
|
+
|
|
7
|
+
const base64ToHex = base64 => Buffer.from(base64, 'base64').toString('hex')
|
|
8
|
+
|
|
9
|
+
const hexToBase64 = hex => Buffer.from(hex, 'hex').toString('base64')
|
|
10
|
+
|
|
11
|
+
const cenToFar = temp => Math.round(((temp * 9) / 5 + 32) * 10) / 10
|
|
12
|
+
|
|
13
|
+
const farToCen = temp => Math.round(((temp - 32) * 5) / 9)
|
|
14
|
+
|
|
15
|
+
function generateCodeFromHexValues(hexValues, returnAsHexBuffer = false) {
|
|
16
|
+
const cmdSection = Buffer.from(hexValues.flat())
|
|
17
|
+
const padSection = Buffer.from(Array.from({ length: 19 - cmdSection.length }).fill(0))
|
|
18
|
+
const noXSection = Buffer.concat([cmdSection, padSection])
|
|
19
|
+
let checksum = 0
|
|
20
|
+
Object.values(noXSection).forEach((i) => {
|
|
21
|
+
checksum ^= i
|
|
22
|
+
})
|
|
23
|
+
const chkSection = Buffer.from([checksum])
|
|
24
|
+
const finalBuffer = Buffer.concat([noXSection, chkSection])
|
|
25
|
+
return returnAsHexBuffer
|
|
26
|
+
? finalBuffer
|
|
27
|
+
: finalBuffer.toString('base64')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function generateRandomString(length) {
|
|
31
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
32
|
+
let nonce = ''
|
|
33
|
+
while (nonce.length < length) {
|
|
34
|
+
nonce += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
35
|
+
}
|
|
36
|
+
return nonce
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const getTwoItemPosition = (array, part) => array[part - 1]
|
|
40
|
+
|
|
41
|
+
const hasProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
|
|
42
|
+
|
|
43
|
+
const hexToDecimal = hex => Number.parseInt(hex, 16)
|
|
44
|
+
|
|
45
|
+
const hexToTwoItems = hex => hex.match(/.{1,2}/g)
|
|
46
|
+
|
|
47
|
+
const nearestHalf = num => Math.round(num * 2) / 2
|
|
48
|
+
|
|
49
|
+
function parseDeviceId(deviceId) {
|
|
50
|
+
return deviceId
|
|
51
|
+
.toString()
|
|
52
|
+
.toUpperCase()
|
|
53
|
+
.replace(/[^A-F0-9:]+/g, '')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseError(err, hideStack = []) {
|
|
57
|
+
let toReturn = err.message
|
|
58
|
+
if (err?.stack?.length > 0 && !hideStack.includes(err.message)) {
|
|
59
|
+
const stack = err.stack.split('\n')
|
|
60
|
+
if (stack[1]) {
|
|
61
|
+
toReturn += stack[1].replace(' ', '')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return toReturn
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function pfxToCertAndKey(pfxPath, p12Password) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
pem.readPkcs12(fs.readFileSync(pfxPath), { p12Password }, (err, cert) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
reject(err)
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const key = new NodeRSA(cert.key)
|
|
75
|
+
resolve({
|
|
76
|
+
cert: cert.cert,
|
|
77
|
+
key: key.exportKey('pkcs8'),
|
|
78
|
+
})
|
|
79
|
+
} catch (error) {
|
|
80
|
+
reject(error)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sleep(ms) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
setTimeout(resolve, ms)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function statusToActionCode(statusCode) {
|
|
93
|
+
const choppedCode = `33${statusCode.slice(2, -2)}`
|
|
94
|
+
const choppedArray = hexToTwoItems(choppedCode)
|
|
95
|
+
const hexValues = choppedArray.map(byte => `0x${byte}`)
|
|
96
|
+
const generatedCode = generateCodeFromHexValues(hexValues)
|
|
97
|
+
return Buffer.from(generatedCode, 'base64').toString('hex')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
base64ToHex,
|
|
102
|
+
cenToFar,
|
|
103
|
+
farToCen,
|
|
104
|
+
generateCodeFromHexValues,
|
|
105
|
+
generateRandomString,
|
|
106
|
+
getTwoItemPosition,
|
|
107
|
+
hasProperty,
|
|
108
|
+
hexToBase64,
|
|
109
|
+
hexToDecimal,
|
|
110
|
+
hexToTwoItems,
|
|
111
|
+
nearestHalf,
|
|
112
|
+
parseDeviceId,
|
|
113
|
+
parseError,
|
|
114
|
+
pfxToCertAndKey,
|
|
115
|
+
sleep,
|
|
116
|
+
statusToActionCode,
|
|
117
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
abnormalMessage: 'too many logins (max 30 per 24 hours), please contact Govee customer service',
|
|
3
|
+
accNotFound: 'accessory not found',
|
|
4
|
+
accTokenFail: 'could not use existing access token as',
|
|
5
|
+
accTokenFromCache: 'retrieved access token from cache',
|
|
6
|
+
accTokenNoExist: 'does not exist, this is normally not an issue',
|
|
7
|
+
accTokenStoreErr: 'could not store access token as',
|
|
8
|
+
accTokenUserChange: 'username has changed',
|
|
9
|
+
alDisabled: 'adaptive lighting disabled due to significant colour change',
|
|
10
|
+
alDisabledScene: 'adaptive lighting disabled due to scene change',
|
|
11
|
+
awsEventClose: 'close event',
|
|
12
|
+
awsEventConnect: 'connect event',
|
|
13
|
+
awsEventError: 'error event',
|
|
14
|
+
awsEventMessage: 'message event',
|
|
15
|
+
awsEventOffline: 'offline event',
|
|
16
|
+
awsEventReconnect: 'reconnect event',
|
|
17
|
+
available: 'client enabled',
|
|
18
|
+
availableWithDevices: n => `client enabled and found ${n} device(s)`,
|
|
19
|
+
beta: 'You are using a beta version of the plugin - you will experience more logging than normal',
|
|
20
|
+
brand: 'Govee',
|
|
21
|
+
bleMacNoSupp: 'not supported on mac devices',
|
|
22
|
+
bleNonControl: 'will be visible but uncontrollable as BLE not available',
|
|
23
|
+
bleNoPackage: 'required hardware/packages not available',
|
|
24
|
+
bleScanDisabled: 'sync for thermo-hygrometer sensors disabled as',
|
|
25
|
+
bleScanNoStart: 'could not start sync as',
|
|
26
|
+
bleScanNoStop: 'could not stop sync as',
|
|
27
|
+
bleScanUnknown: 'reading received for unknown accessory',
|
|
28
|
+
bleStart: 'scanning started',
|
|
29
|
+
bleStop: 'scanning stopped',
|
|
30
|
+
bleTimeout: 'BLE took too long',
|
|
31
|
+
bleWrongState: 'BLE adapter not in correct state',
|
|
32
|
+
cfgDef: 'is not a valid number so using default of',
|
|
33
|
+
cfgDup: 'will be ignored since another entry with this ID already exists',
|
|
34
|
+
cfgIgn: 'is not configured correctly so ignoring',
|
|
35
|
+
cfgIgnItem: 'has an invalid entry which will be ignored',
|
|
36
|
+
cfgItem: 'Config entry',
|
|
37
|
+
cfgLow: 'is set too low so increasing to',
|
|
38
|
+
cfgRmv: 'is unused and can be removed',
|
|
39
|
+
cfgQts: 'should not have quotes around its entry',
|
|
40
|
+
clientBusy: 'Skipping sync as client is busy sending updates',
|
|
41
|
+
cmdNotAWS: 'command not supported via AWS',
|
|
42
|
+
cmdNotBLE: 'command not supported via BLE',
|
|
43
|
+
complete: '✓ Setup complete',
|
|
44
|
+
curAirQual: 'current air quality',
|
|
45
|
+
curAmp: 'current amperage',
|
|
46
|
+
curBatt: 'current battery',
|
|
47
|
+
curBright: 'current brightness',
|
|
48
|
+
curColour: 'current colour',
|
|
49
|
+
curCool: 'current cooling',
|
|
50
|
+
curDisplay: 'current display',
|
|
51
|
+
curFault: 'current fault',
|
|
52
|
+
curHeat: 'current heating',
|
|
53
|
+
curHumi: 'current humidity',
|
|
54
|
+
curIP: 'current IP',
|
|
55
|
+
curLeak: 'current leak',
|
|
56
|
+
curLight: 'current light',
|
|
57
|
+
curLock: 'current lock',
|
|
58
|
+
curMode: 'current mode',
|
|
59
|
+
curOcc: 'current occupancy',
|
|
60
|
+
curPM25: 'current PM2.5',
|
|
61
|
+
curScene: 'current scene',
|
|
62
|
+
curSpeed: 'current speed',
|
|
63
|
+
curState: 'current state',
|
|
64
|
+
curSwing: 'current swing',
|
|
65
|
+
curTarg: 'current target',
|
|
66
|
+
curTemp: 'current temperature',
|
|
67
|
+
curVolt: 'current voltage',
|
|
68
|
+
curWatt: 'current wattage',
|
|
69
|
+
devAdd: 'has been added to Homebridge',
|
|
70
|
+
devInit: 'initialised with id',
|
|
71
|
+
devInitOpts: 'initialising with options',
|
|
72
|
+
devMaySupp: 'is not currently supported, do create a Github issue with the following info',
|
|
73
|
+
devNoBlePackage: 'will not be added as BLE is disabled in the config',
|
|
74
|
+
devNotAdd: 'could not be added to Homebridge as',
|
|
75
|
+
devNotConf: 'could not be configured as',
|
|
76
|
+
devNotInit: 'could not be initialised as',
|
|
77
|
+
devNotRef: 'could not be refreshed as',
|
|
78
|
+
devNotRemove: 'could not be removed from Homebridge as',
|
|
79
|
+
devNotRet: 'skipping sync as device status not retrievable',
|
|
80
|
+
devNotUpdated: 'could not be updated as',
|
|
81
|
+
devRemove: 'has been removed from Homebridge',
|
|
82
|
+
disableClient: 'disabling client as',
|
|
83
|
+
disabledInConfig: 'disabled in config',
|
|
84
|
+
disabling: 'Disabling plugin',
|
|
85
|
+
hbVersionFail: 'Your version of Homebridge is too low - please update to v1.6',
|
|
86
|
+
heaterSimNoSensor: 'you must configure a device ID that provides a temperature reading',
|
|
87
|
+
httpRetry: 'Unable to reach Govee, retrying in 30 seconds',
|
|
88
|
+
initialised: 'Plugin initialised. Setting up accessories...',
|
|
89
|
+
initialising: 'Initialising plugin',
|
|
90
|
+
invalidJson: 'incoming message contains invalid JSON',
|
|
91
|
+
iotFileNoExist: 'iot certificate does not exist',
|
|
92
|
+
labelNo: 'no',
|
|
93
|
+
labelYes: 'yes',
|
|
94
|
+
lanCmdSent: 'command sent to',
|
|
95
|
+
lanDevNotFound: 'device not found in LAN list',
|
|
96
|
+
lanDevRemoved: 'removed device due to connection error',
|
|
97
|
+
lanFoundDevice: 'found device',
|
|
98
|
+
lanParseError: 'could not parse message',
|
|
99
|
+
lanReqError: 'could not request device status as',
|
|
100
|
+
lanServerStarted: 'server started listening on',
|
|
101
|
+
lanUnkDevice: 'received update from unknown device',
|
|
102
|
+
loginSuccess: 'login successful',
|
|
103
|
+
logoutFail: 'could not log out of Govee account as',
|
|
104
|
+
needHTTPClient: 'requires HTTP client',
|
|
105
|
+
newScene: 'new scene code',
|
|
106
|
+
noCreds: 'username and/or password not provided',
|
|
107
|
+
noConnMethod: 'no connection method available, see https://shorturl.at/eCN04 for more info',
|
|
108
|
+
noDevices: 'no data received from Govee server whilst obtaining devices',
|
|
109
|
+
noDevs: 'No devices found to initialise',
|
|
110
|
+
noExtFunc: 'no externalUpdate function to process incoming update',
|
|
111
|
+
notAWSConn: 'not connected to AWS',
|
|
112
|
+
notAWSSent: 'not using AWS connection as',
|
|
113
|
+
notBLESent: 'not using BLE connection as',
|
|
114
|
+
notLANSent: 'not using LAN connection as',
|
|
115
|
+
noOpenssl: 'See https://github.com/bwp91/homebridge-govee/wiki/OpenSSL for more info about installing OpenSSL',
|
|
116
|
+
noToken: 'no data received from Govee server whilst obtaining token',
|
|
117
|
+
noTokenExists: 'no account token has been retrieved',
|
|
118
|
+
offlineBTConn: 'has been reported [disconnected] via BLE',
|
|
119
|
+
onlineBT: 'has been reported [discoverable] via BLE',
|
|
120
|
+
onlineBTConn: 'has been reported [connected] via BLE',
|
|
121
|
+
pluginNotConf: 'Plugin has not been configured',
|
|
122
|
+
receivingUpdate: 'receiving update',
|
|
123
|
+
sendingUpdate: 'sending update',
|
|
124
|
+
skippingAL: 'skipping adaptive lighting update as device is off or kelvin is same as before',
|
|
125
|
+
storageSetupErr: 'Could not setup storage client as',
|
|
126
|
+
storageWriteErr: 'could not save accessory to file as',
|
|
127
|
+
syncFail: 'sync failed as',
|
|
128
|
+
viaAL: 'via adaptive lighting',
|
|
129
|
+
welcome: 'This plugin has been made with ♥ by bwp91, please consider a ☆ on GitHub if you are finding it useful!',
|
|
130
|
+
unknownCommand: 'unknown command in payload received',
|
|
131
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@homebridge-plugins/homebridge-govee",
|
|
3
|
+
"alias": "Govee",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "10.12.1",
|
|
6
|
+
"description": "Homebridge plugin to integrate Govee devices into HomeKit.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "bwp91",
|
|
9
|
+
"email": "bwp91@icloud.com"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"funding": [
|
|
13
|
+
{
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/bwp91"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "kofi",
|
|
19
|
+
"url": "https://ko-fi.com/bwp91"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "patreon",
|
|
23
|
+
"url": "https://www.patreon.com/bwp91"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "paypal",
|
|
27
|
+
"url": "https://www.paypal.me/BenPotter"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"homepage": "https://github.com/bwp91/homebridge-govee",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/bwp91/homebridge-govee.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/bwp91/homebridge-govee/issues"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"homebridge",
|
|
40
|
+
"homebridge-plugin",
|
|
41
|
+
"homebridge-govee",
|
|
42
|
+
"hoobs",
|
|
43
|
+
"hoobs-plugin",
|
|
44
|
+
"homekit",
|
|
45
|
+
"siri",
|
|
46
|
+
"govee"
|
|
47
|
+
],
|
|
48
|
+
"main": "lib/index.js",
|
|
49
|
+
"engines": {
|
|
50
|
+
"homebridge": "^1.6.0 || ^2.0.0-beta.0",
|
|
51
|
+
"node": "^18.20.4 || ^20.18.0 || ^22.9.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"lint": "eslint . --fix",
|
|
55
|
+
"rebuild": "rm -rf package-lock.json && rm -rf node_modules && npm install"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@homebridge/plugin-ui-utils": "^1.0.3",
|
|
59
|
+
"aws-iot-device-sdk": "^2.2.15",
|
|
60
|
+
"axios": "^1.7.7",
|
|
61
|
+
"node-persist": "^4.0.3",
|
|
62
|
+
"node-rsa": "^1.1.1",
|
|
63
|
+
"p-queue": "^8.0.1",
|
|
64
|
+
"pem": "^1.14.8"
|
|
65
|
+
},
|
|
66
|
+
"optionalDependencies": {
|
|
67
|
+
"@abandonware/bluetooth-hci-socket": "0.5.3-12",
|
|
68
|
+
"@abandonware/noble": "1.9.2-25",
|
|
69
|
+
"govee-bt-client": "^1.0.15"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@antfu/eslint-config": "^3.7.3",
|
|
73
|
+
"eslint": "^9.12.0"
|
|
74
|
+
}
|
|
75
|
+
}
|