Enhance Zigbee to NVL integration with button mapping and configuration updates
- Updated documentation to clarify the mapping of Zigbee buttons to specific (room, light) pairs using `switchBindings`. - Improved the Zigbee to NVL function to support both single-device and multi-device payloads, enhancing flexibility in handling actions. - Revised the room configuration to include detailed switch bindings and fallback mechanisms for device identification, streamlining the integration process. This update improves the usability and functionality of the Zigbee integration within Node-RED, facilitating better control of lighting systems.
This commit is contained in:
@@ -1,35 +1,108 @@
|
||||
// Writes to state.rooms.<roomKey>. Room from Zigbee device via global roomConfig.deviceToRoom. See /root/.node-red/room-config.js.
|
||||
// Each button maps to (room, light) via global roomConfig.switchBindings. See /root/.node-red/room-config.js.
|
||||
// Supports single-device payload (action, friendly_name at top level) and multi-device payload (keyed by IEEE address).
|
||||
const actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };
|
||||
const config = global.get('roomConfig');
|
||||
const switchBindings = (config && config.switchBindings) ? config.switchBindings : {};
|
||||
const deviceToRoom = (config && config.deviceToRoom) ? config.deviceToRoom : { 'Office Switch': 'cmd_livingroom' };
|
||||
const friendlyName = (msg.payload && msg.payload.friendly_name) ? msg.payload.friendly_name : '';
|
||||
const ROOM_NAME = deviceToRoom[friendlyName] || 'cmd_livingroom';
|
||||
const payload = msg.payload || {};
|
||||
const actionRaw = (payload.action || payload.click || '').toLowerCase();
|
||||
// Support "1_single", "2_double" etc. (button_number + action) or plain "single", "double"
|
||||
const parts = actionRaw.split('_');
|
||||
let swIndex = null;
|
||||
if (parts.length >= 2) {
|
||||
const buttonNum = parseInt(parts[0], 10);
|
||||
if (buttonNum >= 1 && buttonNum <= 6) swIndex = buttonNum;
|
||||
}
|
||||
if (swIndex == null) swIndex = actionToSwitch[actionRaw];
|
||||
if (swIndex == null && parts.length >= 2) swIndex = actionToSwitch[parts.slice(1).join('_')];
|
||||
if (swIndex == null) swIndex = actionToSwitch[parts[parts.length - 1]];
|
||||
// Optional: map IEEE address to friendly name when multi-device node doesn't provide it. e.g. deviceIdToName: { '0xa4c138a5b9771b05': 'Office Switch' }
|
||||
const deviceIdToName = (config && config.deviceIdToName) ? config.deviceIdToName : {};
|
||||
|
||||
if (swIndex == null) {
|
||||
node.warn('[Zigbee to NVL] unknown action: ' + JSON.stringify(actionRaw) + ' payload=' + JSON.stringify(payload));
|
||||
const payload = msg.payload != null ? msg.payload : {};
|
||||
// Payload can be: single-device { action, friendly_name }, multi-device { "0x...": { ... } }, or plain action string "1_single"
|
||||
let friendlyName = (typeof payload === 'object' && payload.friendly_name != null) ? payload.friendly_name.toString() : '';
|
||||
let actionRaw = (typeof payload === 'string' ? payload : (payload.action || payload.click || msg.action || (payload && payload.action)) || '').toString().toLowerCase();
|
||||
|
||||
// Multi-device payload: prefer the device that has an action AND a configured binding (so we don't pick another device's stale action)
|
||||
if (!actionRaw && typeof payload === 'object' && !Array.isArray(payload)) {
|
||||
const keys = Object.keys(payload);
|
||||
let fallback = null;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const v = payload[key];
|
||||
if (key && key.toLowerCase().indexOf('0x') === 0 && v && typeof v === 'object') {
|
||||
const inner = v.payload || v.message || v.state || v;
|
||||
const a = (inner.action || inner.click || v.action || v.click || '').toString().toLowerCase();
|
||||
if (!a) continue;
|
||||
const name = (v.friendly_name || v.name || (v.item && (v.item.friendly_name || v.item.name)) || deviceIdToName[key] || key).toString();
|
||||
if (switchBindings[name] || deviceToRoom[name]) {
|
||||
actionRaw = a;
|
||||
friendlyName = name;
|
||||
break;
|
||||
}
|
||||
if (!fallback) fallback = { actionRaw: a, friendlyName: name };
|
||||
}
|
||||
}
|
||||
if (!actionRaw && fallback) {
|
||||
actionRaw = fallback.actionRaw;
|
||||
friendlyName = fallback.friendlyName;
|
||||
}
|
||||
}
|
||||
// If still no action, try msg.data or msg.topic (some nodes put action in topic, e.g. "1_single")
|
||||
if (!actionRaw && msg.data && msg.data.action) actionRaw = msg.data.action.toString().toLowerCase();
|
||||
if (!actionRaw && typeof msg.topic === 'string' && /^\d_[a-z_]+$/.test(msg.topic.trim())) actionRaw = msg.topic.trim().toLowerCase();
|
||||
// If payload was the action string, get friendly_name from topic e.g. zigbee2mqtt/Office Switch/action
|
||||
if (actionRaw && !friendlyName && typeof msg.topic === 'string') {
|
||||
const parts = msg.topic.split('/');
|
||||
if (parts.length >= 2) friendlyName = parts[1].trim(); // zigbee2mqtt/Office Switch/action → Office Switch
|
||||
}
|
||||
|
||||
if (!actionRaw) {
|
||||
node.warn('[Zigbee to NVL] no action in payload. Keys: ' + (typeof payload === 'object' ? Object.keys(payload).join(', ') : 'n/a'));
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts = actionRaw.split('_');
|
||||
let buttonIndex = null;
|
||||
if (parts.length >= 2) {
|
||||
const n = parseInt(parts[0], 10);
|
||||
if (n >= 1 && n <= 6) buttonIndex = n;
|
||||
}
|
||||
if (buttonIndex == null) buttonIndex = actionToSwitch[actionRaw];
|
||||
if (buttonIndex == null && parts.length >= 2) buttonIndex = actionToSwitch[parts.slice(1).join('_')];
|
||||
if (buttonIndex == null) buttonIndex = actionToSwitch[parts[parts.length - 1]];
|
||||
|
||||
if (buttonIndex == null) {
|
||||
node.warn('[Zigbee to NVL] unknown action: ' + JSON.stringify(actionRaw));
|
||||
return null;
|
||||
}
|
||||
|
||||
node.warn('[Zigbee to NVL] action=' + actionRaw + ' buttonIndex=' + buttonIndex + ' device=' + friendlyName);
|
||||
|
||||
// Prefer switchBindings by friendly name; fallback by device ID (IEEE) if we have switchBindingsByDeviceId
|
||||
const bindingByDeviceId = (config && config.switchBindingsByDeviceId) ? config.switchBindingsByDeviceId : null;
|
||||
let targets = [];
|
||||
let binding = switchBindings[friendlyName] && switchBindings[friendlyName][buttonIndex];
|
||||
if ((binding === undefined || binding === null) && bindingByDeviceId) {
|
||||
const deviceId = typeof payload === 'object' && Object.keys(payload).some(function (k) { return k.indexOf('0x') === 0; })
|
||||
? Object.keys(payload).find(function (k) { return k.indexOf('0x') === 0; })
|
||||
: null;
|
||||
if (deviceId) binding = bindingByDeviceId[deviceId] && bindingByDeviceId[deviceId][buttonIndex];
|
||||
}
|
||||
if (binding !== undefined && binding !== null) {
|
||||
targets = Array.isArray(binding) ? binding : [binding];
|
||||
targets = targets.filter(function (t) { return t && t.room && t.light >= 1 && t.light <= 6; });
|
||||
}
|
||||
if (targets.length === 0) {
|
||||
const room = deviceToRoom[friendlyName] || 'cmd_livingroom';
|
||||
targets = [{ room: room, light: buttonIndex }];
|
||||
node.warn('[Zigbee to NVL] no switchBindings for ' + friendlyName + ' btn' + buttonIndex + ', using fallback: ' + room + ' light ' + buttonIndex);
|
||||
}
|
||||
|
||||
if (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });
|
||||
const state = flow.get('nvlInState');
|
||||
if (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};
|
||||
state.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;
|
||||
const clearList = [];
|
||||
|
||||
for (const t of targets) {
|
||||
const key = 'zigbee_sw' + t.light;
|
||||
if (!state.rooms[t.room]) state.rooms[t.room] = {};
|
||||
state.rooms[t.room][key] = true;
|
||||
clearList.push({ room: t.room, key: key });
|
||||
}
|
||||
|
||||
flow.set('nvlInState', state);
|
||||
|
||||
node.warn('[Zigbee to NVL] ' + ROOM_NAME + ' zigbee_sw' + swIndex + '=true (action=' + actionRaw + '), buildAndSend + zigbeeClear');
|
||||
node.warn('[Zigbee to NVL] ' + (friendlyName || 'device') + ' btn' + buttonIndex + ' → ' + targets.map(function (t) { return t.room + ' L' + t.light; }).join(', '));
|
||||
|
||||
msg.payload = { buildAndSend: true };
|
||||
msg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };
|
||||
msg.zigbeeClear = clearList;
|
||||
return msg;
|
||||
|
||||
Reference in New Issue
Block a user