UNPKG

iobroker.lovelace

Version:

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

271 lines (251 loc) 13.2 kB
const utils = require('./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 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 = null; } } }; entity.attributes.supported_features |= 4; entity.context.COMMANDS.push({ service: 'set_cover_position', setId: state.id, parseCommand: (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.position) / 100) * attr.max + attr.min; if (attr.invert) { value = ((100 - data.service_data.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.')); } }); } }); //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.STATE.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 = false; } //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; };