iobroker.zigbee2mqtt 0.2.0 → 2.1.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/README.md +35 -0
- package/admin/i18n/de/translations.json +22 -6
- package/admin/i18n/en/translations.json +20 -4
- package/admin/i18n/es/translations.json +20 -4
- package/admin/i18n/fr/translations.json +20 -4
- package/admin/i18n/it/translations.json +20 -4
- package/admin/i18n/nl/translations.json +20 -4
- package/admin/i18n/pl/translations.json +20 -4
- package/admin/i18n/pt/translations.json +20 -4
- package/admin/i18n/ru/translations.json +20 -4
- package/admin/i18n/zh-cn/translations.json +20 -4
- package/admin/jsonConfig.json +131 -9
- package/io-package.json +59 -7
- package/lib/check.js +36 -0
- package/lib/colors.js +5 -3
- package/lib/deviceController.js +234 -0
- package/lib/exposes.js +90 -45
- package/lib/messages.js +39 -0
- package/lib/mqttServerController.js +42 -0
- package/lib/rgb.js +42 -17
- package/lib/states.js +41 -19
- package/lib/statesController.js +138 -0
- package/lib/utils.js +19 -17
- package/lib/websocketController.js +84 -0
- package/lib/z2mController.js +80 -0
- package/main.js +130 -387
- package/package.json +11 -7
- package/lib/groups.js +0 -68
package/lib/check.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function checkConfig(config, log) {
|
|
2
|
+
const checkAPIOptions = {
|
|
3
|
+
legacy_api_enabled: config.advanced.legacy_api != false,
|
|
4
|
+
legacy_availability_payload_enabled: config.advanced.legacy_availability_payload != false,
|
|
5
|
+
device_legacy_enabled: config.device_options.legacy != false
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
if (Object.values(checkAPIOptions).filter(x => x == true).length > 0) {
|
|
9
|
+
log.error('===================================================');
|
|
10
|
+
log.error('===================================================');
|
|
11
|
+
if (checkAPIOptions.legacy_api_enabled == true) {
|
|
12
|
+
log.error('Legacy api is activated, so the adapter can not work correctly!!!');
|
|
13
|
+
log.error('Please add the following lines to your Zigbee2MQTT configuration.yaml:');
|
|
14
|
+
log.error('legacy_api: false');
|
|
15
|
+
log.error('');
|
|
16
|
+
}
|
|
17
|
+
if (checkAPIOptions.legacy_availability_payload_enabled == true) {
|
|
18
|
+
log.error('Legacy Availability Payload is activated, thus the adapter cannot represent the availability of the devices!!!');
|
|
19
|
+
log.error('Please add the following lines to your Zigbee2MQTT configuration.yaml:');
|
|
20
|
+
log.error('legacy_availability_payload: false');
|
|
21
|
+
log.error('');
|
|
22
|
+
}
|
|
23
|
+
if (checkAPIOptions.device_legacy_enabled == true) {
|
|
24
|
+
log.error('Device Legacy Payload is activated, therefore the adapter may process the states of the devices correctly!!!');
|
|
25
|
+
log.error('Please add the following lines to your Zigbee2MQTT configuration.yaml:');
|
|
26
|
+
log.error('device_options:');
|
|
27
|
+
log.error(' legacy: false');
|
|
28
|
+
}
|
|
29
|
+
log.error('===================================================');
|
|
30
|
+
log.error('===================================================');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
checkConfig: checkConfig,
|
|
36
|
+
};
|
package/lib/colors.js
CHANGED
|
@@ -453,6 +453,8 @@ function NamedColorToRGB(name) {
|
|
|
453
453
|
return { r: 0, g: 128, b: 255 };
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
exports
|
|
457
|
-
|
|
458
|
-
|
|
456
|
+
module.exports = {
|
|
457
|
+
NamedColorToRGB,
|
|
458
|
+
NamedColorToRGBstring,
|
|
459
|
+
ParseColor,
|
|
460
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const states = require('./states').states;
|
|
2
|
+
const defineDeviceFromExposes = require('./exposes').defineDeviceFromExposes;
|
|
3
|
+
const utils = require('./utils');
|
|
4
|
+
const colors = require('./colors.js');
|
|
5
|
+
const rgb = require('./rgb.js');
|
|
6
|
+
const createCache = {};
|
|
7
|
+
|
|
8
|
+
class DeviceController {
|
|
9
|
+
constructor(adapter, deviceCache, groupCache, config) {
|
|
10
|
+
this.adapter = adapter;
|
|
11
|
+
this.groupCache = groupCache;
|
|
12
|
+
this.deviceCache = deviceCache;
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async createDeviceDefinitions(exposes) {
|
|
17
|
+
utils.clearArray(this.deviceCache);
|
|
18
|
+
for (const expose of exposes) {
|
|
19
|
+
if (expose.definition != null) {
|
|
20
|
+
// search for scenes in the endpoints and build them into an array
|
|
21
|
+
let scenes = [];
|
|
22
|
+
for (const key in expose.endpoints) {
|
|
23
|
+
if (expose.endpoints[key].scenes) {
|
|
24
|
+
scenes = scenes.concat(expose.endpoints[key].scenes);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// if the device is already present in the cache, remove it
|
|
28
|
+
this.removeDeviceByIeee(this.deviceCache, expose.ieee_address);
|
|
29
|
+
defineDeviceFromExposes(this.deviceCache, expose.friendly_name, expose.ieee_address, expose.definition, expose.power_source, scenes, this.config);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async defineGroupDevice(groupID, ieee_address, scenes) {
|
|
36
|
+
const newDevice = {
|
|
37
|
+
id: groupID,
|
|
38
|
+
ieee_address: ieee_address,
|
|
39
|
+
icon: undefined,
|
|
40
|
+
states: [
|
|
41
|
+
states.state,
|
|
42
|
+
states.brightness,
|
|
43
|
+
//states.color,
|
|
44
|
+
states.brightness_move,
|
|
45
|
+
states.colortemp_move,
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const color = {
|
|
50
|
+
id: 'color',
|
|
51
|
+
prop: 'color',
|
|
52
|
+
name: 'Color',
|
|
53
|
+
icon: undefined,
|
|
54
|
+
role: 'level.color.rgb',
|
|
55
|
+
write: true,
|
|
56
|
+
read: true,
|
|
57
|
+
type: 'string',
|
|
58
|
+
setter: (value) => {
|
|
59
|
+
let xy = [0, 0];
|
|
60
|
+
const rgbcolor = colors.ParseColor(value);
|
|
61
|
+
xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
|
|
62
|
+
return {
|
|
63
|
+
x: xy[0],
|
|
64
|
+
y: xy[1]
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
getter: payload => {
|
|
68
|
+
if (payload.color_mode != 'xy' && this.config.colorTempSyncColor == false) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
if (payload.color && payload.color.x && payload.color.y) {
|
|
72
|
+
const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
|
|
73
|
+
return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
|
|
74
|
+
} else {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
newDevice.states.push(color);
|
|
82
|
+
|
|
83
|
+
const colortemp = {
|
|
84
|
+
id: 'colortemp',
|
|
85
|
+
prop: 'color_temp',
|
|
86
|
+
name: 'Color temperature',
|
|
87
|
+
icon: undefined,
|
|
88
|
+
role: 'level.color.temperature',
|
|
89
|
+
write: true,
|
|
90
|
+
read: true,
|
|
91
|
+
type: 'number',
|
|
92
|
+
min: this.config.useKelvin == true ? utils.miredKelvinConversion(500) : 150,
|
|
93
|
+
max: this.config.useKelvin == true ? utils.miredKelvinConversion(150) : 500,
|
|
94
|
+
unit: this.config.useKelvin == true ? 'K' : 'mired',
|
|
95
|
+
setter: (value) => {
|
|
96
|
+
return utils.toMired(value);
|
|
97
|
+
},
|
|
98
|
+
getter: (payload) => {
|
|
99
|
+
if (payload.color_mode != 'color_temp') {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (this.config.useKelvin == true) {
|
|
103
|
+
return utils.miredKelvinConversion(payload.color_temp);
|
|
104
|
+
} else {
|
|
105
|
+
return payload.color_temp;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
newDevice.states.push(colortemp);
|
|
112
|
+
|
|
113
|
+
// Create buttons for scenes
|
|
114
|
+
for (const scene of scenes) {
|
|
115
|
+
const sceneSate = {
|
|
116
|
+
id: `scene_${scene.id}`,
|
|
117
|
+
prop: `scene_recall`,
|
|
118
|
+
name: scene.name,
|
|
119
|
+
icon: undefined,
|
|
120
|
+
role: 'button',
|
|
121
|
+
write: true,
|
|
122
|
+
read: true,
|
|
123
|
+
type: 'boolean',
|
|
124
|
+
setter: (value) => (value) ? scene.id : undefined
|
|
125
|
+
};
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
newDevice.states.push(sceneSate);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// if the device is already present in the cache, remove it
|
|
131
|
+
this.removeDeviceByIeee(this.groupCache, ieee_address);
|
|
132
|
+
this.groupCache.push(newDevice);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async createGroupDefinitions(exposes) {
|
|
136
|
+
utils.clearArray(this.groupCache);
|
|
137
|
+
for (const expose of exposes) {
|
|
138
|
+
await this.defineGroupDevice(expose.friendly_name, `group_${expose.id}`, expose.scenes);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async createOrUpdateDevices() {
|
|
143
|
+
for (const device of this.groupCache.concat(this.deviceCache)) {
|
|
144
|
+
const deviceName = device.id == device.ieee_address ? '' : device.id;
|
|
145
|
+
if (!createCache[device.ieee_address] || createCache[device.ieee_address].common.name != deviceName) {
|
|
146
|
+
const deviceObj = {
|
|
147
|
+
type: 'device',
|
|
148
|
+
common: {
|
|
149
|
+
name: deviceName,
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
native: {}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (!device.ieee_address.includes('group_')) {
|
|
156
|
+
deviceObj.common.statusStates = {
|
|
157
|
+
onlineId: `${this.adapter.name}.${this.adapter.instance}.${device.ieee_address}.available`
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//@ts-ignore
|
|
162
|
+
await this.adapter.extendObjectAsync(device.ieee_address, deviceObj);
|
|
163
|
+
createCache[device.ieee_address] = deviceObj;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Here it is checked whether the scenes match the current data from z2m.
|
|
167
|
+
// If necessary, scenes are automatically deleted from ioBroker.
|
|
168
|
+
const sceneStates = await this.adapter.getStatesAsync(`${device.ieee_address}.scene_*`);
|
|
169
|
+
const sceneIDs = Object.keys(sceneStates);
|
|
170
|
+
for (const sceneID of sceneIDs) {
|
|
171
|
+
const stateID = sceneID.split('.')[3];
|
|
172
|
+
if (device.states.find(x => x.id == stateID) == null) {
|
|
173
|
+
this.adapter.delObject(sceneID);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const state of device.states) {
|
|
178
|
+
if (!createCache[device.ieee_address][state.id] || createCache[device.ieee_address][state.id].name != state.name) {
|
|
179
|
+
const iobState = await this.copyAndCleanStateObj(state);
|
|
180
|
+
await this.adapter.extendObjectAsync(`${device.ieee_address}.${state.id}`, {
|
|
181
|
+
type: 'state',
|
|
182
|
+
common: iobState,
|
|
183
|
+
native: {},
|
|
184
|
+
});
|
|
185
|
+
createCache[device.ieee_address][state.id] = state.name;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async renameDeviceInCache(messageObj) {
|
|
192
|
+
const renamedDevice = this.groupCache.concat(this.deviceCache).find(x => x.id == messageObj.payload.data.from);
|
|
193
|
+
if (renamedDevice) {
|
|
194
|
+
renamedDevice.id = messageObj.payload.data.to;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async processRemoveEvent(messageObj) {
|
|
199
|
+
if (messageObj.payload && messageObj.payload.type == 'device_leave') {
|
|
200
|
+
this.adapter.setStateAsync(`${messageObj.payload.data.ieee_address}.available`, false, true);
|
|
201
|
+
this.adapter.extendObject(`${messageObj.payload.data.ieee_address}`, { common: { name: 'Device removed!', } });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
removeDeviceByIeee(devices, ieee_address) {
|
|
206
|
+
const idx = devices.findIndex(x => x.ieee_address == ieee_address);
|
|
207
|
+
if (idx > -1) {
|
|
208
|
+
devices.splice(idx, 1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async copyAndCleanStateObj(state) {
|
|
213
|
+
const iobState = { ...state };
|
|
214
|
+
const blacklistedKeys = [
|
|
215
|
+
'setter',
|
|
216
|
+
'setterOpt',
|
|
217
|
+
'getter',
|
|
218
|
+
'setattr',
|
|
219
|
+
'readable',
|
|
220
|
+
'writable',
|
|
221
|
+
'isOption',
|
|
222
|
+
'inOptions',
|
|
223
|
+
'isEvent',
|
|
224
|
+
];
|
|
225
|
+
for (const blacklistedKey of blacklistedKeys) {
|
|
226
|
+
delete iobState[blacklistedKey];
|
|
227
|
+
}
|
|
228
|
+
return iobState;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
DeviceController
|
|
234
|
+
};
|
package/lib/exposes.js
CHANGED
|
@@ -7,6 +7,8 @@ const rgb = require('./rgb');
|
|
|
7
7
|
const utils = require('./utils');
|
|
8
8
|
const colors = require('./colors');
|
|
9
9
|
|
|
10
|
+
const blacklistSimulatedBrightness = ['MFKZQ01LM'];
|
|
11
|
+
|
|
10
12
|
function genState(expose, role, name, desc) {
|
|
11
13
|
let state;
|
|
12
14
|
const readable = true; //expose.access > 0;
|
|
@@ -36,8 +38,7 @@ function genState(expose, role, name, desc) {
|
|
|
36
38
|
|
|
37
39
|
if (readable) {
|
|
38
40
|
state.getter = (payload) => (payload[propName] === (expose.value_on || 'ON'));
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
+
} else {
|
|
41
42
|
state.getter = (_payload) => (undefined);
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -119,7 +120,7 @@ function genState(expose, role, name, desc) {
|
|
|
119
120
|
return state;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
function createFromExposes(deviceID, ieee_address, definitions, power_source, scenes,
|
|
123
|
+
function createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config) {
|
|
123
124
|
const states = [];
|
|
124
125
|
// make the different (set and get) part of state is updatable if different exposes is used for get and set
|
|
125
126
|
// as example:
|
|
@@ -159,8 +160,7 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
return states.push(state);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
163
|
+
} else {
|
|
164
164
|
|
|
165
165
|
if ((state.readable) && (!states[stateExists].readable)) {
|
|
166
166
|
states[stateExists].read = state.read;
|
|
@@ -190,8 +190,7 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
190
190
|
}
|
|
191
191
|
delete states[stateExists].prop;
|
|
192
192
|
}
|
|
193
|
-
}
|
|
194
|
-
else if (state.hasOwnProperty('prop')) {
|
|
193
|
+
} else if (state.hasOwnProperty('prop')) {
|
|
195
194
|
states[stateExists].prop = state.prop;
|
|
196
195
|
}
|
|
197
196
|
states[stateExists].readable = true;
|
|
@@ -218,16 +217,14 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
218
217
|
// or remove it
|
|
219
218
|
if (((!state.hasOwnProperty('isOption')) || (state.isOptions === false)) && (states[stateExists].hasOwnProperty('isOption'))) {
|
|
220
219
|
delete states[stateExists].isOption;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
220
|
+
} else {
|
|
223
221
|
states[stateExists].isOption = state.isOption;
|
|
224
222
|
}
|
|
225
223
|
|
|
226
224
|
// use new `setattr` or `prop` as `setattr`
|
|
227
225
|
if (state.hasOwnProperty('setattr')) {
|
|
228
226
|
states[stateExists].setattr = state.setattr;
|
|
229
|
-
}
|
|
230
|
-
else if (state.hasOwnProperty('prop')) {
|
|
227
|
+
} else if (state.hasOwnProperty('prop')) {
|
|
231
228
|
states[stateExists].setattr = state.prop;
|
|
232
229
|
}
|
|
233
230
|
|
|
@@ -320,22 +317,24 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
320
317
|
write: true,
|
|
321
318
|
read: true,
|
|
322
319
|
type: 'number',
|
|
323
|
-
min:
|
|
324
|
-
max:
|
|
325
|
-
unit: useKelvin == true ? 'K' : 'mired',
|
|
320
|
+
min: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_max) : prop.value_min,
|
|
321
|
+
max: config.useKelvin == true ? utils.miredKelvinConversion(prop.value_min) : prop.value_max,
|
|
322
|
+
unit: config.useKelvin == true ? 'K' : 'mired',
|
|
326
323
|
setter: (value) => {
|
|
327
324
|
return utils.toMired(value);
|
|
328
325
|
},
|
|
329
|
-
setterOpt: (_value, options) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
},
|
|
326
|
+
// setterOpt: (_value, options) => {
|
|
327
|
+
// const hasTransitionTime = options && options.hasOwnProperty('transition_time');
|
|
328
|
+
// const transitionTime = hasTransitionTime ? options.transition_time : 0;
|
|
329
|
+
// return { ...options, transition: transitionTime };
|
|
330
|
+
// },
|
|
334
331
|
getter: (payload) => {
|
|
335
|
-
if (
|
|
336
|
-
return
|
|
332
|
+
if (payload.color_mode != 'color_temp') {
|
|
333
|
+
return undefined;
|
|
337
334
|
}
|
|
338
|
-
|
|
335
|
+
if (config.useKelvin == true) {
|
|
336
|
+
return utils.miredKelvinConversion(payload.color_temp);
|
|
337
|
+
} else {
|
|
339
338
|
return payload.color_temp;
|
|
340
339
|
}
|
|
341
340
|
},
|
|
@@ -368,12 +367,10 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
368
367
|
};
|
|
369
368
|
|
|
370
369
|
},
|
|
371
|
-
setterOpt: (_value, options) => {
|
|
372
|
-
const hasTransitionTime = options && options.hasOwnProperty('transition_time');
|
|
373
|
-
const transitionTime = hasTransitionTime ? options.transition_time : 0;
|
|
374
|
-
return { ...options, transition: transitionTime };
|
|
375
|
-
},
|
|
376
370
|
getter: payload => {
|
|
371
|
+
if (payload.color_mode != 'xy' && config.colorTempSyncColor == false) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
377
374
|
if (payload.color && payload.color.hasOwnProperty('x') && payload.color.hasOwnProperty('y')) {
|
|
378
375
|
const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
|
|
379
376
|
return '#' + utils.decimalToHex(colorval[0]) + utils.decimalToHex(colorval[1]) + utils.decimalToHex(colorval[2]);
|
|
@@ -576,6 +573,10 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
576
573
|
state = statesDefs.temperature;
|
|
577
574
|
break;
|
|
578
575
|
|
|
576
|
+
case 'device_temperature':
|
|
577
|
+
state = statesDefs.device_temperature;
|
|
578
|
+
break;
|
|
579
|
+
|
|
579
580
|
case 'humidity':
|
|
580
581
|
state = statesDefs.humidity;
|
|
581
582
|
break;
|
|
@@ -596,6 +597,24 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
596
597
|
state = statesDefs.load_power;
|
|
597
598
|
break;
|
|
598
599
|
|
|
600
|
+
case 'current':
|
|
601
|
+
state = statesDefs.load_current;
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case 'voltage':
|
|
605
|
+
state = statesDefs.voltage;
|
|
606
|
+
if (power_source == 'Battery') {
|
|
607
|
+
state = statesDefs.battery_voltage;
|
|
608
|
+
}
|
|
609
|
+
if (expose.unit == 'mV') {
|
|
610
|
+
state.getter = payload => payload.voltage / 1000;
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
|
|
614
|
+
case 'energy':
|
|
615
|
+
state = statesDefs.energy;
|
|
616
|
+
break;
|
|
617
|
+
|
|
599
618
|
default:
|
|
600
619
|
state = genState(expose);
|
|
601
620
|
break;
|
|
@@ -622,7 +641,6 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
622
641
|
if (hasHold && hasRelease && actionName.includes('release')) continue;
|
|
623
642
|
// is hold state ?
|
|
624
643
|
if (hasHold && hasRelease && actionName.includes('hold')) {
|
|
625
|
-
const releaseActionName = actionName.replace('hold', 'release');
|
|
626
644
|
state = {
|
|
627
645
|
id: actionName.replace(/\*/g, ''),
|
|
628
646
|
prop: 'action',
|
|
@@ -631,8 +649,20 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
631
649
|
role: 'button',
|
|
632
650
|
write: false,
|
|
633
651
|
read: true,
|
|
652
|
+
def: false,
|
|
634
653
|
type: 'boolean',
|
|
635
|
-
getter:
|
|
654
|
+
getter: (payload) => {
|
|
655
|
+
if (payload.action === actionName) {
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
if (payload.action === actionName.replace('hold', 'release')) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
if (payload.action === `${actionName}_release`) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
return undefined;
|
|
665
|
+
},
|
|
636
666
|
};
|
|
637
667
|
} else {
|
|
638
668
|
state = {
|
|
@@ -644,12 +674,30 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
644
674
|
write: false,
|
|
645
675
|
read: true,
|
|
646
676
|
type: 'boolean',
|
|
647
|
-
|
|
677
|
+
def: false,
|
|
648
678
|
isEvent: true,
|
|
679
|
+
getter: payload => (payload.action === actionName) ? true : undefined,
|
|
649
680
|
};
|
|
650
681
|
}
|
|
651
682
|
pushToStates(state, expose.access);
|
|
652
683
|
}
|
|
684
|
+
if (!blacklistSimulatedBrightness.includes(definitions.model)) {
|
|
685
|
+
pushToStates({
|
|
686
|
+
id: 'simulated_brightness',
|
|
687
|
+
prop: 'brightness',
|
|
688
|
+
name: 'Simulated brightness',
|
|
689
|
+
icon: undefined,
|
|
690
|
+
role: 'level.dimmer',
|
|
691
|
+
write: true,
|
|
692
|
+
read: true,
|
|
693
|
+
type: 'number',
|
|
694
|
+
unit: '%',
|
|
695
|
+
def: 0,
|
|
696
|
+
getter: payload => {
|
|
697
|
+
return utils.bulbLevelToAdapterLevel(payload.brightness);
|
|
698
|
+
},
|
|
699
|
+
}, 1);
|
|
700
|
+
}
|
|
653
701
|
state = null;
|
|
654
702
|
break;
|
|
655
703
|
}
|
|
@@ -730,13 +778,14 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
730
778
|
case 'running_mode':
|
|
731
779
|
pushToStates(statesDefs.climate_running_mode, prop.access);
|
|
732
780
|
break;
|
|
733
|
-
default:
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
781
|
+
default:
|
|
782
|
+
{
|
|
783
|
+
if (prop.name.includes('heating_setpoint')) {
|
|
784
|
+
pushToStates(genState(prop, 'level.temperature'), prop.access);
|
|
785
|
+
} else {
|
|
786
|
+
pushToStates(genState(prop), prop.access);
|
|
787
|
+
}
|
|
738
788
|
}
|
|
739
|
-
}
|
|
740
789
|
break;
|
|
741
790
|
}
|
|
742
791
|
}
|
|
@@ -769,13 +818,11 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
769
818
|
st.getter = payload => {
|
|
770
819
|
if ((payload.hasOwnProperty(expose.property)) && (payload[expose.property] !== null) && payload[expose.property].hasOwnProperty(prop.property)) {
|
|
771
820
|
return !isNaN(payload[expose.property][prop.property]) ? payload[expose.property][prop.property] : undefined;
|
|
772
|
-
}
|
|
773
|
-
else {
|
|
821
|
+
} else {
|
|
774
822
|
return undefined;
|
|
775
823
|
}
|
|
776
824
|
};
|
|
777
|
-
}
|
|
778
|
-
else {
|
|
825
|
+
} else {
|
|
779
826
|
st.getter = _payload => { return undefined; };
|
|
780
827
|
}
|
|
781
828
|
pushToStates(st, prop.access);
|
|
@@ -819,15 +866,13 @@ function createFromExposes(deviceID, ieee_address, definitions, power_source, sc
|
|
|
819
866
|
return newDevice;
|
|
820
867
|
}
|
|
821
868
|
|
|
822
|
-
function defineDeviceFromExposes(devices, deviceID, ieee_address, definitions, power_source, scenes,
|
|
823
|
-
// if the device is already present in the cache, remove it
|
|
824
|
-
utils.removeDeviceByIeee(devices, ieee_address);
|
|
869
|
+
function defineDeviceFromExposes(devices, deviceID, ieee_address, definitions, power_source, scenes, config) {
|
|
825
870
|
if (definitions.hasOwnProperty('exposes')) {
|
|
826
|
-
const newDevice = createFromExposes(deviceID, ieee_address, definitions, power_source, scenes,
|
|
871
|
+
const newDevice = createFromExposes(deviceID, ieee_address, definitions, power_source, scenes, config);
|
|
827
872
|
devices.push(newDevice);
|
|
828
873
|
}
|
|
829
874
|
}
|
|
830
875
|
|
|
831
876
|
module.exports = {
|
|
832
877
|
defineDeviceFromExposes: defineDeviceFromExposes,
|
|
833
|
-
};
|
|
878
|
+
};
|
package/lib/messages.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
async function adapterInfo(config, log) {
|
|
2
|
+
log.info('================================= Adapter Config =================================');
|
|
3
|
+
log.info(`|| Zigbee2MQTT Frontend Scheme: ${config.webUIScheme}`);
|
|
4
|
+
log.info(`|| Zigbee2MQTT Frontend Server: ${config.webUIServer}`);
|
|
5
|
+
log.info(`|| Zigbee2MQTT Frontend Port: ${config.webUIPort}`);
|
|
6
|
+
log.info(`|| Zigbee2MQTT Connection Type: ${config.connectionType}`);
|
|
7
|
+
if (config.connectionType == 'ws') {
|
|
8
|
+
log.info(`|| Zigbee2MQTT Websocket Server: ${config.wsServerIP}`);
|
|
9
|
+
log.info(`|| Zigbee2MQTT Websocket Port: ${config.wsServerPort}`);
|
|
10
|
+
log.info(`|| Zigbee2MQTT Websocket Dummy MQTT-Server: ${config.dummyMqtt ? 'activated' : 'deactivated'}`);
|
|
11
|
+
if (config.dummyMqtt == true) {
|
|
12
|
+
log.info(`|| Zigbee2MQTT Dummy MQTT IP-Bind: ${config.mqttServerIPBind}`);
|
|
13
|
+
log.info(`|| Zigbee2MQTT Dummy MQTT Port: ${config.mqttServerPort}`);
|
|
14
|
+
}
|
|
15
|
+
} else if (config.connectionType == 'exmqtt') {
|
|
16
|
+
log.info(`|| Zigbee2MQTT Externanl MQTT Server: ${config.externalMqttServerIP}`);
|
|
17
|
+
log.info(`|| Zigbee2MQTT Externanl MQTT Port: ${config.externalMqttServerPort}`);
|
|
18
|
+
} else if (config.connectionType == 'intmqtt') {
|
|
19
|
+
log.info(`|| Zigbee2MQTT Internal MQTT IP-Bind: ${config.mqttServerIPBind}`);
|
|
20
|
+
log.info(`|| Zigbee2MQTT Internal MQTT Port: ${config.mqttServerPort}`);
|
|
21
|
+
}
|
|
22
|
+
log.info(`|| Zigbee2MQTT Debug Log: ${config.debugLogEnabled ? 'activated' : 'deactivated'}`);
|
|
23
|
+
log.info(`|| Proxy Zigbee2MQTT Logs to ioBroker Logs: ${config.proxyZ2MLogs ? 'activated' : 'deactivated'}`);
|
|
24
|
+
log.info(`|| Use Kelvin: ${config.useKelvin ? 'yes' : 'no'}`);
|
|
25
|
+
log.info('==================================================================================');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function zigbee2mqttInfo(payload, log) {
|
|
29
|
+
log.info('============================ Zigbee2MQTT Information =============================');
|
|
30
|
+
log.info(`|| Zigbee2MQTT Version: ${payload.version} `);
|
|
31
|
+
log.info(`|| Coordinator type: ${payload.coordinator.type} Version: ${payload.coordinator.meta.revision} Serial: ${payload.config.serial.port}`);
|
|
32
|
+
log.info(`|| Network panid ${payload.network.pan_id} channel: ${payload.network.channel} ext_pan_id: ${payload.network.extended_pan_id}`);
|
|
33
|
+
log.info('==================================================================================');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
adapterInfo,
|
|
38
|
+
zigbee2mqttInfo,
|
|
39
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const core = require('@iobroker/adapter-core');
|
|
2
|
+
const Aedes = require('aedes');
|
|
3
|
+
const net = require('net');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MqttServerController {
|
|
7
|
+
constructor(adapter) {
|
|
8
|
+
this.adapter = adapter;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async createMQTTServer() {
|
|
12
|
+
try {
|
|
13
|
+
const NedbPersistence = require('aedes-persistence-nedb');
|
|
14
|
+
const db = new NedbPersistence({ path: `${core.getAbsoluteInstanceDataDir(this.adapter)}/mqttData`, prefix: '' });
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const aedes = Aedes({ persistence: db });
|
|
17
|
+
const mqttServer = net.createServer(aedes.handle);
|
|
18
|
+
mqttServer.listen(this.adapter.config.mqttServerPort, this.adapter.config.mqttServerIPBind, () => {
|
|
19
|
+
this.adapter.log.info(`Statring MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`);
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
this.adapter.log.error(err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async createDummyMQTTServer() {
|
|
27
|
+
try {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
const aedes = Aedes();
|
|
30
|
+
const mqttServer = net.createServer(aedes.handle);
|
|
31
|
+
mqttServer.listen(this.adapter.config.mqttServerPort, this.adapter.config.mqttServerIPBind, () => {
|
|
32
|
+
this.adapter.log.info(`Statring DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`);
|
|
33
|
+
});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
this.adapter.log.error(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
MqttServerController
|
|
42
|
+
};
|