Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ubisys: Allow to configure device inputs using templates #1658

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 193 additions & 6 deletions converters/toZigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -2249,22 +2249,209 @@ const converters = {
key: ['configure_device_setup'],
convertSet: async (entity, key, value, meta) => {
const devMgmtEp = meta.device.getEndpoint(232);
if (value.hasOwnProperty('inputConfigurations')) {

if (value.hasOwnProperty('input_configurations')) {
// example: [0, 0, 0, 0]
await devMgmtEp.write('manuSpecificUbisysDeviceSetup',
{'inputConfigurations': {elementType: 'data8', elements: value.inputConfigurations}});
{'inputConfigurations': {elementType: 'data8', elements: value.input_configurations}});
}
if (value.hasOwnProperty('inputActions')) {

if (value.hasOwnProperty('input_actions')) {
// example (default for C4): [[0,13,1,6,0,2], [1,13,2,6,0,2], [2,13,3,6,0,2], [3,13,4,6,0,2]]
await devMgmtEp.write('manuSpecificUbisysDeviceSetup',
{'inputActions': {elementType: 'octetStr', elements: value.inputActions}});
{'inputActions': {elementType: 'octetStr', elements: value.input_actions}});
}

if (value.hasOwnProperty('input_action_templates')) {
const templateTypes = {
// source: "ZigBee Device Physical Input Configurations Integrator’s Guide"
// (can be obtained directly from ubisys upon request)
'toggle': {
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x06, 0x00, 0x02],
],
},
'toggle_switch': {
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x06, 0x00, 0x02],
[input, 0x03, endpoint, 0x06, 0x00, 0x02],
],
},
'on_off_switch': {
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x06, 0x00, 0x01],
[input, 0x03, endpoint, 0x06, 0x00, 0x00],
],
},
'on': {
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x06, 0x00, 0x01],
],
},
'off': {
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x06, 0x00, 0x00],
],
},
'dimmer_single': {
getInputActions: (input, endpoint, template) => {
const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
const moveRate = template.rate || 50;
return [
[input, 0x07, endpoint, 0x06, 0x00, 0x02],
[input, 0x86, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
[input, 0xC6, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
[input, 0x0B, endpoint, 0x08, 0x00, 0x03],
];
},
},
'dimmer_double': {
doubleInputs: true,
getInputActions: (inputs, endpoint, template) => {
const moveUpCmd = template.no_onoff || template.no_onoff_up ? 0x01 : 0x05;
const moveDownCmd = template.no_onoff || template.no_onoff_down ? 0x01 : 0x05;
const moveRate = template.rate || 50;
return [
[inputs[0], 0x07, endpoint, 0x06, 0x00, 0x01],
[inputs[0], 0x06, endpoint, 0x08, 0x00, moveUpCmd, 0x00, moveRate],
[inputs[0], 0x0B, endpoint, 0x08, 0x00, 0x03],
[inputs[1], 0x07, endpoint, 0x06, 0x00, 0x00],
[inputs[1], 0x06, endpoint, 0x08, 0x00, moveDownCmd, 0x01, moveRate],
[inputs[1], 0x0B, endpoint, 0x08, 0x00, 0x03],
];
},
},
'cover': {
cover: true,
doubleInputs: true,
getInputActions: (inputs, endpoint) => [
[inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
[inputs[0], 0x07, endpoint, 0x02, 0x01, 0x02],
[inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
[inputs[1], 0x07, endpoint, 0x02, 0x01, 0x02],
],
},
'cover_switch': {
cover: true,
doubleInputs: true,
getInputActions: (inputs, endpoint) => [
[inputs[0], 0x0D, endpoint, 0x02, 0x01, 0x00],
[inputs[0], 0x03, endpoint, 0x02, 0x01, 0x02],
[inputs[1], 0x0D, endpoint, 0x02, 0x01, 0x01],
[inputs[1], 0x03, endpoint, 0x02, 0x01, 0x02],
],
},
'cover_up': {
cover: true,
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x02, 0x01, 0x00],
],
},
'cover_down': {
cover: true,
getInputActions: (input, endpoint) => [
[input, 0x0D, endpoint, 0x02, 0x01, 0x01],
],
},
'scene': {
scene: true,
getInputActions: (input, endpoint, groupId, sceneId) => [
[input, 0x07, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
],
getInputActions2: (input, endpoint, groupId, sceneId) => [
[input, 0x06, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
],
},
'scene_switch': {
scene: true,
getInputActions: (input, endpoint, groupId, sceneId) => [
[input, 0x0D, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
],
getInputActions2: (input, endpoint, groupId, sceneId) => [
[input, 0x03, endpoint, 0x05, 0x00, 0x05, groupId & 0xff, groupId >> 8, sceneId],
],
},
};

// first input
let input = 0;
// first client endpoint - depends on actual device
let endpoint = {'S1': 2, 'S2': 3, 'D1': 2, 'J1': 2, 'C4': 1}[meta.mapped.model];
// default group id
let groupId = 0;

const templates = Array.isArray(value.input_action_templates) ? value.input_action_templates :
[value.input_action_templates];
let resultingInputActions = [];
for (const template of templates) {
const templateType = templateTypes[template.type];
if (!templateType) {
throw new Error(`input_action_templates: Template type '${template.type}' is not valid ` +
`(valid types: ${Object.keys(templateTypes)})`);
}

if (template.hasOwnProperty('input')) {
input = template.input;
}
if (template.hasOwnProperty('endpoint')) {
endpoint = template.endpoint;
}
// C4 cover endpoints only start at 5
if (templateType.cover && meta.mapped.model === 'C4' && endpoint < 5) {
endpoint += 4;
}

let inputActions;
if (!templateType.doubleInputs) {
if (!templateType.scene) {
// single input, no scene(s)
inputActions = templateType.getInputActions(input, endpoint, template);
} else {
// scene(s) (always single input)
if (!template.hasOwnProperty('scene_id')) {
throw new Error(`input_action_templates: Need an attribute 'scene_id' for '${template.type}'`);
}
if (template.hasOwnProperty('group_id')) {
groupId = template.group_id;
}
inputActions = templateType.getInputActions(input, endpoint, groupId, template.scene_id);

if (template.hasOwnProperty('scene_id_2')) {
if (template.hasOwnProperty('group_id_2')) {
groupId = template.group_id_2;
}
inputActions = inputActions.concat(templateType.getInputActions2(input, endpoint, groupId,
template.scene_id_2));
}
}
} else {
// double inputs
input = template.hasOwnProperty('inputs') ? template.inputs : [input, input + 1];
inputActions = templateType.getInputActions(input, endpoint, template);
}
resultingInputActions = resultingInputActions.concat(inputActions);

meta.logger.warn(`ubisys: Using input(s) ${input} and endpoint ${endpoint} for '${template.type}'.`);
// input might by now be an array (in case of double inputs)
input = (Array.isArray(input) ? Math.max(...input) : input) + 1;
endpoint += 1;
}

meta.logger.debug(`ubisys: input_actions to be sent to '${meta.options.friendlyName}': ` +
JSON.stringify(resultingInputActions));
await devMgmtEp.write('manuSpecificUbisysDeviceSetup',
{'inputActions': {elementType: 'octetStr', elements: resultingInputActions}});
}

// re-read effective settings and dump them to the log
converters.ubisys_device_setup.convertGet(entity, key, meta);
},

convertGet: async (entity, key, meta) => {
const log = (json) => {
const log = (dataRead) => {
meta.logger.warn(
`ubisys: Device setup read for '${meta.options.friendlyName}': ${JSON.stringify(json)}`);
`ubisys: Device setup read for '${meta.options.friendlyName}': ${JSON.stringify(utils.toSnakeCase(dataRead))}`);
};
const devMgmtEp = meta.device.getEndpoint(232);
log(await devMgmtEp.read('manuSpecificUbisysDeviceSetup', ['inputConfigurations']));
Expand Down
32 changes: 32 additions & 0 deletions converters/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,36 @@ const sleepMs = async (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

function toSnakeCase(value) {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
const keySnakeCase = toSnakeCase(key);
if (key !== keySnakeCase) {
value[keySnakeCase] = value[key];
delete value[key];
}
}
return value;
} else {
return value.replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()).replace(/^_/, '').replace('_i_d', '_id');
}
}

function toCamelCase(value) {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
const keyCamelCase = toCamelCase(key);
if (key !== keyCamelCase) {
value[keyCamelCase] = value[key];
delete value[key];
}
}
return value;
} else {
return value.replace(/_([a-z])/g, (x, y) => y.toUpperCase());
}
}

module.exports = {
rgbToXY,
hexToXY,
Expand All @@ -315,4 +345,6 @@ module.exports = {
getMetaValue,
filterObject,
sleepMs,
toSnakeCase,
toCamelCase,
};