UNPKG

iobroker.lovelace

Version:

With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI

448 lines (428 loc) 17.7 kB
const utils = require('./../entities/utils'); const adapterData = require('./../dataSingleton'); function addCommand(entity, control, searchString, serviceName, featureNumber, additionalParsing) { const state = control.states.find(s => s.id && s.name === searchString); if (state && state.id) { const command = { service: serviceName, setId: state.id, parseCommand: (entity, command, params, user) => new Promise((resolve, reject) => { if (typeof additionalParsing === 'function') { additionalParsing(entity, command, params, user); } adapterData.adapter.setForeignState(command.setId, true, false, { user }, err => err ? reject(err) : resolve(), ); }), }; entity.context.COMMANDS.push(command); entity.attributes.supported_features |= featureNumber; return true; } return false; } function handleSetAndTiltCommand(entity, command, data, user) { return new Promise((resolve, reject) => { if (data.service_data.position >= 0) { let value = (data.service_data.position / 100) * entity.context.STATE.max + entity.context.STATE.min; if (entity.context.STATE.invert) { value = ((100 - data.service_data.position) / 100) * entity.context.STATE.max + entity.context.STATE.min; } adapterData.adapter.setForeignState(command.setId, value, false, { user }, err => err ? reject(err) : resolve(), ); } else if (data.service_data.tilt_position >= 0) { const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'tilt'); if (attr && attr.setId) { let value = (data.service_data.tilt_position / 100) * attr.max + attr.min; if (attr.invert) { value = ((100 - data.service_data.tilt_position) / 100) * attr.max + attr.min; } adapterData.adapter.setForeignState(attr.setId, value, false, { user }, err => err ? reject(err) : resolve(), ); } else { reject(new Error('No setId for tilt cover.')); } } else { reject(new Error('No matching service data in set_cover_position.')); } }); } function addBlindLevel(entities, control, objects, name, id, room, func, _obj, forcedEntityId) { const state = control.states.find(s => s.id && s.name === name); const entity = entities[0]; if (state && state.id) { const common = objects[state.id] && objects[state.id].common ? objects[state.id].common : {}; if (state && state.id) { //also create input_number - slider to be backwards compatibel: const entitySlider = utils.processCommon( entity.attributes.friendly_name, room, func, _obj, 'input_number', forcedEntityId, ); entities.push(entitySlider); entitySlider.context.STATE = { setId: state.id, getId: state.id }; entitySlider.attributes.icon = 'mdi:window-shutter'; entitySlider.attributes.mode = 'slider'; entitySlider.attributes.min = common.min !== undefined ? common.min : 0; entitySlider.attributes.max = common.max !== undefined ? common.max : 100; entitySlider.attributes.step = common.step || 1; utils.addID2entity(state.id, entitySlider); //fill state with %: entity.context.STATE.setId = state.id; entity.context.STATE.getId = state.id; utils.addID2entity(state.id, entity); entity.context.STATE.max = common.max || 100; entity.context.STATE.min = common.min || 0; entity.context.STATE.getParser = (entity, attr, state) => { if (!state) { state = { val: null }; } if (state.val != null) { let position = ((state.val - entity.context.STATE.min) / entity.context.STATE.max) * 100.0; let isClosed = state.val === entity.context.STATE.min; let isOpened = state.val === entity.context.STATE.max; if (entity.context.STATE.invert) { position = 100 - position; isClosed = state.val === entity.context.STATE.max; isOpened = state.val === entity.context.STATE.min; } entity.attributes.current_position = position; if (isClosed) { entity.state = 'closed'; } else if (isOpened) { entity.state = 'open'; } else { entity.state = String(position); } } }; entity.attributes.supported_features |= 4; entity.context.COMMANDS.push({ service: 'set_cover_tilt_position', setId: state.id, parseCommand: handleSetAndTiltCommand, }); entity.context.COMMANDS.push({ service: 'set_cover_position', setId: state.id, parseCommand: handleSetAndTiltCommand, }); //need to define toggle here, because default handling does not make much sense for cover. entity.context.COMMANDS.push({ service: 'toggle', setId: state.id, parseCommand: (entity, command, data, user) => { return new Promise((resolve, reject) => { const up = entity.context.COMMANDS.find(a => a.service === 'open_cover'); const down = entity.context.COMMANDS.find(a => a.service === 'close_cover'); if (entity.state === 'open' || entity.state === 'opening') { if (down) { adapterData.adapter.setForeignState(down.setId, true, false, { user }, err => err ? reject(err) : resolve(), ); } else { adapterData.adapter.setForeignState( command.setId, entity.context.STATE.invert ? entity.context.STATE.min : entity.context.STATE.max, false, { user }, err => (err ? reject(err) : resolve()), ); } } else { if (up) { adapterData.adapter.setForeignState(up.setId, true, false, { user }, err => err ? reject(err) : resolve(), ); } else { adapterData.adapter.setForeignState( command.setId, entity.context.STATE.invert ? entity.context.STATE.max : entity.context.STATE.min, false, { user }, err => (err ? reject(err) : resolve()), ); } } }); }, }); //support get/set in two different states, but invert needs to match! const getState = control.states.find(s => s.id && s.name === name.replace('SET', 'ACTUAL')); if (getState && getState.id) { entity.context.STATE.getId = getState.id; utils.addID2entity(getState.id, entity); entitySlider.context.STATE.getId = getState.id; utils.addID2entity(getState.id, entitySlider); } } return true; } return false; } function addTiltLevel(entity, control, objects, name) { //support for tilt %: const state = control.states.find(s => s.id && s.name === name); if (state && state.id) { const common = objects[state.id] && objects[state.id].common ? objects[state.id].common : {}; entity.context.ATTRIBUTES = [ { attribute: 'tilt', getId: state.id, setId: state.id, min: common.min || 0, max: common.max || 100, getParser: (entity, attr, state) => { if (!state) { state = { val: null }; } if (state.val != null) { let position = ((state.val - attr.min) / attr.max) * 100.0; if (attr.invert) { position = 100 - position; } entity.attributes.current_tilt_position = position; } }, }, ]; utils.addID2entity(state.id, entity); entity.attributes.supported_features |= 128; const getState = control.states.find(s => s.id && s.name === name.replace('SET', 'ACTUAL')); if (getState && getState.id) { entity.context.ATTRIBUTES.find(a => a.attribute === 'tilt').getId = getState.id; utils.addID2entity(getState.id, entity); } return true; } return false; } exports.processBlind = function (id, control, name, room, func, _obj, objects, forcedEntityId) { const entity = utils.processCommon(name, room, func, _obj, 'cover', forcedEntityId); /* cover device classes. DEVICE_CLASS_AWNING = "awning" DEVICE_CLASS_BLIND = "blind" DEVICE_CLASS_CURTAIN = "curtain" DEVICE_CLASS_DAMPER = "damper" DEVICE_CLASS_DOOR = "door" DEVICE_CLASS_GARAGE = "garage" DEVICE_CLASS_GATE = "gate" DEVICE_CLASS_SHADE = "shade" DEVICE_CLASS_SHUTTER = "shutter" DEVICE_CLASS_WINDOW = "window" supported features: SUPPORT_OPEN = 1 SUPPORT_CLOSE = 2 SUPPORT_SET_POSITION = 4 SUPPORT_STOP = 8 SUPPORT_OPEN_TILT = 16 SUPPORT_CLOSE_TILT = 32 SUPPORT_STOP_TILT = 64 SUPPORT_SET_TILT_POSITION = 128 attributes: current_position //for shade it seems 0 = open, i.e. up, 100 = closed, i.e. down. HASS says otherway round. current_tilt_position position tilt_position */ entity.context.COMMANDS = []; entity.attributes.device_class = 'blind'; //use blind, because now I added tilt support ;-) entity.context.STATE = { setId: null, getId: null }; entity.attributes.supported_features = 0; entity.attributes.icon = 'mdi:window-shutter'; adapterData.log.debug(`Creating blind of type ${control.type} for ${id}`); const entities = [entity]; if (addBlindLevel(entities, control, objects, 'SET', id, room, func, _obj, forcedEntityId)) { entity.context.STATE.invert = !!adapterData.adapter.config.blindsInvert; } if (addTiltLevel(entity, control, objects, 'TILT_SET')) { const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'tilt'); attr.invert = !!adapterData.adapter.config.blindsInvert; } //add commands, if present: addCommand(entity, control, 'STOP', 'stop_cover', 8); const haveOpen = addCommand(entity, control, 'OPEN', 'open_cover', 1, entity => (entity.state = 'opening')); const haveClose = addCommand(entity, control, 'CLOSE', 'close_cover', 2, entity => (entity.state = 'closing')); addCommand(entity, control, 'TILT_OPEN', 'open_cover_tilt', 16); addCommand(entity, control, 'TILT_CLOSE', 'close_cover_tilt', 32); addCommand(entity, control, 'TILT_STOP', 'stop_cover_tilt', 64); //use % setting for up/down in case there are no buttons: if (!haveOpen) { const command = { service: 'open_cover', setId: entity.context.STATE.setId, parseCommand: (entity, command, params, user) => new Promise((resolve, reject) => { entity.state = 'opening'; adapterData.adapter.setForeignState( command.setId, entity.context.STATE.invert ? entity.context.STATE.min : entity.context.STATE.max, false, { user }, err => (err ? reject(err) : resolve()), ); }), }; entity.context.COMMANDS.push(command); entity.attributes.supported_features |= 1; } if (!haveClose) { const command = { service: 'close_cover', setId: entity.context.STATE.setId, parseCommand: (entity, command, params, user) => new Promise((resolve, reject) => { entity.state = 'closing'; adapterData.adapter.setForeignState( command.setId, entity.context.STATE.invert ? entity.context.STATE.max : entity.context.STATE.min, false, { user }, err => (err ? reject(err) : resolve()), ); }), }; entity.context.COMMANDS.push(command); entity.attributes.supported_features |= 2; } return entities; }; function augmentServices(services) { services.cover = { set_cover_tilt_position: { name: 'Set tilt position', description: 'Sets the tilt position of a cover entity.', fields: { tilt_position: { required: true, selector: { number: null, }, name: 'Tilt position', description: 'The target tilt position to set.', }, }, target: { entity: [ { domain: ['cover'], }, ], }, }, set_cover_position: { name: 'Set position', description: 'Sets the position of a cover entity.', fields: { position: { required: true, selector: { number: null, }, name: 'Position', description: 'The target position to set.', }, }, target: { entity: [ { domain: ['cover'], }, ], }, }, toggle: { name: 'Toggle cover status', description: 'Toggles the status of a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, open_cover: { name: 'Open cover', description: 'Opens a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, close_cover: { name: 'Close cover', description: 'Closes a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, stop_cover: { name: 'Stop cover', description: 'Stops a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, open_cover_tilt: { name: 'Open tilt', description: 'Opens the tilt of a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, close_cover_tilt: { name: 'Close tilt', description: 'Closes the tilt of a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, stop_cover_tilt: { name: 'Stop tilt', description: 'Stops the tilt of a cover entity.', fields: {}, target: { entity: [ { domain: ['cover'], }, ], }, }, }; } augmentServices(adapterData.services);