UNPKG

iobroker.lovelace

Version:

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

1,185 lines (1,124 loc) 48.8 kB
const utils = require('./../entities/utils'); const adapterData = require('./../dataSingleton'); const { Types } = require('@iobroker/type-detector'); /** * Type-detector found a simple light device, create entity for it. * * @param id {string} - id of the object. * @param control {object} - type-detector control object. * @param name {string} - name of the object. * @param room {string|undefined} - room of the object. * @param func {string|undefined} - function of the object. * @param _obj {ioBroker.Object} - ioBroker object. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param forcedEntityId {string|undefined} - force a specific entity id. * @returns {[{context: {id: string, type: string}, attributes: {friendly_name: string}, entity_id: string}]} - created entity. */ exports.processLight = function (id, control, name, room, func, _obj, objects, forcedEntityId) { const entity = utils.processCommon(name, room, func, _obj, 'light', forcedEntityId); let state = control.states.find(s => s.id && s.name === 'SET'); entity.context.STATE = { setId: null, getId: null }; if (state && state.id) { entity.context.STATE.setId = state.id; entity.context.STATE.getId = state.id; utils.addID2entity(state.id, entity); entity.attributes.color_mode = ONOFF; entity.attributes.supported_color_modes = [ONOFF]; } state = control.states.find(s => s.id && (s.name === 'ON_ACTUAL' || s.name === 'ACTUAL')); if (state && state.id) { entity.context.STATE.getId = state.id; utils.addID2entity(state.id, entity); } return [entity]; }; // Uses color mode! // Possible modes: //const UNKNOWN = "unknown"; const ONOFF = 'onoff'; const BRIGHTNESS = 'brightness'; const COLOR_TEMP = 'color_temp'; const HS = 'hs'; //const XY = "xy"; -> not yet supported by frontend, it seems? const RGB = 'rgb'; const RGBW = 'rgbw'; //const RGBWW = "rgbww"; <- two white??? // // -> fill attribute supported_color_modes // const SUPPORT_EFFECT = 4; //const SUPPORT_FLASH = 8; //const SUPPORT_TRANSITION = 32; const attributesToNullOnOff = [ 'color_mode', 'brightness', 'color_temp', 'color_temp_kelvin', 'hs_color', 'rgb_color', 'rgbw_color', 'xy_color', 'effect', ]; /** * Must delete attributes in an entity if switched off. Store and clear them here on switch off and restore them on switch on. * * @param entity {object} - entity to clear or restore attributes for. */ function clearOrRestoreAttributes(entity) { if (entity.state === 'on') { for (const attr of attributesToNullOnOff) { if (entity.attributes[attr] === null || entity.attributes[attr] === undefined) { //make sure the attribute was not yet read from somewhere else entity.attributes[attr] = entity.context.STATE.storedValues[attr]; } } //adapterData.log.debug(`Stored old values for next switch on: ${JSON.stringify(entity.context.STATE.storedValues)}`); } else { for (const attr of attributesToNullOnOff) { if (entity.attributes[attr] !== undefined) { entity.context.STATE.storedValues[attr] = entity.attributes[attr]; entity.attributes[attr] = null; } } //adapterData.log.debug(`Restored old values from previous switch off: ${JSON.stringify(entity.attributes)}`); } } /** * Find on/off object for light entity from type-detector results. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ function _lightAdvancedAddState(states, objects, entity) { const getState = states.stateRead; //for advanced lights, clear attributes, so frontend doesn't still show them. entity.context.STATE.storedValues = {}; entity.context.STATE.getParser = (entity, attr, state) => { if (!state) { state = { val: null }; } entity.state = state.val ? 'on' : 'off'; clearOrRestoreAttributes(entity); }; //prevent zigbee 'available' to become getId: if ( getState && getState.startsWith('zigbee.') && (getState.endsWith('.available') || getState.endsWith('.device_query')) ) { entity.context.STATE.getId = states.state; } //clear initial attributes clearOrRestoreAttributes(entity); if (states.state) { entity.context.STATE.isBoolean = objects[states.state] && objects[states.state].common && objects[states.state].common.type === 'boolean'; entity.attributes.supported_color_modes.push(ONOFF); return true; } else { return false; } } /** * Add color temperature attribute to entity from states object. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ function _lightAdvancedAddColorTemperature(states, objects, entity) { const iobMaxValueKelvin = 7000; const iobMinValueKelvin = 1000; if (states.color_temp) { const attribute = entity.context.ATTRIBUTES.find(a => a.attribute === 'color_temp'); const tempObj = objects[states.color_temp]; attribute.convert_to_mired = tempObj && tempObj.common ? tempObj.common.unit === 'mired' : false; attribute.getParser = (entity, attr, state) => { let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } if (!state || !state.val) { targetAttributes.color_temp = iobMinValueKelvin; return; } let targetCt = state.val; if (targetCt < 1000 && !attr.convert_to_mired) { attr.convert_to_mired = true; //adapterData.log.warn('Need mired conversion for ' + states.color_temp + ' and did not detect that in setup. Please set unit to "K" in object settings.'); } if (targetCt > 1000 && attr.convert_to_mired) { attr.convert_to_mired = false; } if (attr.convert_to_mired) { targetCt = 1e6 / targetCt; } targetAttributes.color_temp_kelvin = targetCt; targetAttributes.color_temp = 1e6 / targetCt; targetAttributes.color_mode = COLOR_TEMP; }; entity.attributes.max_color_temp_kelvin = (tempObj && tempObj.common && tempObj.common.max) || iobMaxValueKelvin; entity.attributes.min_color_temp_kelvin = (tempObj && tempObj.common && tempObj.common.min) || iobMinValueKelvin; if (attribute.convert_to_mired || entity.attributes.max_color_temp_kelvin < 1000) { attribute.convert_to_mired = true; entity.attributes.max_color_temp_kelvin = tempObj && tempObj.common && tempObj.common.min ? 1e6 / tempObj.common.min : iobMaxValueKelvin; entity.attributes.min_color_temp_kelvin = tempObj && tempObj.common && tempObj.common.max ? 1e6 / tempObj.common.max : iobMinValueKelvin; } if (!entity.attributes.max_color_temp_kelvin || isNaN(entity.attributes.max_color_temp_kelvin)) { entity.attributes.max_color_temp_kelvin = iobMaxValueKelvin; } if (!entity.attributes.min_color_temp_kelvin || isNaN(entity.attributes.min_color_temp_kelvin)) { entity.attributes.min_color_temp_kelvin = iobMinValueKelvin; } adapterData.log.debug(`${entity.entity_id} ct needs mired conversion: ${attribute.convert_to_mired}`); if (entity.attributes.min_color_temp_kelvin > entity.attributes.max_color_temp_kelvin) { //for kelvin conversion, min and max need to be swapped. const max = entity.attributes.min_color_temp_kelvin; entity.attributes.min_color_temp_kelvin = entity.attributes.max_color_temp_kelvin; entity.attributes.max_color_temp_kelvin = max; } if (attribute.convert_to_mired) { entity.attributes.max_mireds = tempObj?.common?.max || 1e6 / entity.attributes.min_color_temp_kelvin; entity.attributes.min_mireds = tempObj?.common?.min || 1e6 / entity.attributes.max_color_temp_kelvin; } else { entity.attributes.max_mireds = 1e6 / entity.attributes.min_color_temp_kelvin; entity.attributes.min_mireds = 1e6 / entity.attributes.max_color_temp_kelvin; } entity.attributes.supported_color_modes.push(COLOR_TEMP); } } /** * Add brightness attribute to entity from states object. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ function _lightAdvancedAddBrightness(states, objects, entity) { if ( !states.brightness && states.state && objects[states.state] && objects[states.state].common && objects[states.state].common.type === 'number' ) { states.brightness = states.state; if (!states.brightnessRead) { states.brightnessRead = states.stateRead; } } if (states.brightness) { const attribute = entity.context.ATTRIBUTES.find(a => a.attribute === 'brightness'); attribute.getParser = (entity, attr, state) => { let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } state = state || { val: 0 }; targetAttributes.brightness = ((state.val - attr.min) / (attr.max - attr.min)) * 255; if (!targetAttributes.color_mode || targetAttributes.color_mode === ONOFF) { targetAttributes.color_mode = BRIGHTNESS; } if (states.state === states.brightness) { entity.state = targetAttributes.brightness > 0 ? 'on' : 'off'; clearOrRestoreAttributes(entity); if (entity.attributes.color_mode === ONOFF) { entity.attributes.color_mode = BRIGHTNESS; } } }; attribute.setId = states.brightness; attribute.getId = states.brightnessRead || states.brightness; attribute.min = (objects[attribute.getId] && objects[attribute.getId].common && objects[attribute.getId].common.min) || 0; attribute.max = (objects[attribute.getId] && objects[attribute.getId].common && objects[attribute.getId].common.max) || 100; entity.attributes.supported_color_modes.push(BRIGHTNESS); if (states.state === states.brightness) { //clear initial attributes clearOrRestoreAttributes(entity); } } } /** * Add hue/sat attribute to entity from states object. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ function _lightAdvancedAddHueAndSat(states, objects, entity) { if (states.hue) { const hue_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'hue'); hue_attr.max = (objects[states.hue] && objects[states.hue].common && objects[states.hue].common.max) || 360; hue_attr.getParser = (entity, attr, state) => { let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } state = state || { val: 0 }; if (!targetAttributes.hs_color) { targetAttributes.hs_color = [0, 100]; } targetAttributes.hs_color[0] = (state.val / attr.max) * 360; targetAttributes.color_mode = HS; }; entity.attributes.supported_color_modes.push(HS); entity.attributes.hs_color = [null, null]; entity.context.STATE.storedValues.hs_color = [0, 100]; } //add saturation as own attribute. Will update saturation values from ioBroker correctly. if (states.saturation) { const sat_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'saturation'); sat_attr.max = (objects[states.saturation] && objects[states.saturation].common && objects[states.saturation].common.max) || 100; if (!states.rgb_color && !states.red) { if (!states.hue) { adapterData.log.warn( `Saturation present but no hue id found for ${states.saturation}. Hue won't work.`, ); return; } sat_attr.getParser = (entity, attr, state) => { let targetAttributes = entity.attributes || {}; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } state = state || { val: 0 }; if (!targetAttributes.hs_color) { targetAttributes.hs_color = [0, 100]; } targetAttributes.hs_color[1] = (state.val / attr.max) * 100; targetAttributes.color_mode = HS; }; } else { sat_attr.getParser = () => {}; //ignore saturation updates. } } else if (states.hue) { adapterData.log.warn(`Hue present but no saturation id found for ${states.hue}. Saturation won't work.`); } } /** * Add RGB attribute to entity from states object. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ async function _lightAdvancedAddRGBSingle(states, objects, entity) { if (states.rgb_color) { const attribute = entity.context.ATTRIBUTES.find(a => a.attribute === 'rgb_color'); attribute.is_rgb_array = false; attribute.is_rgb_string = true; attribute.getParser = (entity, attr, state) => { let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } let str = state ? (state.val || '#000000').toString() : '#000000'; if (str[0] === '#') { str = str.substring(1); } let r, g, b; if (/([0-9]){1,3},([0-9]){1,3},([0-9]){1,3}/.test(str)) { adapterData.log.debug('Have RGB decimal array.'); [r, g, b] = str.split(',').map(v => parseInt(v, 10)); } else { if (!/^[\da-fA-F]{6}/.test(str)) { adapterData.log.error(`Malformed rgb string ${str} expecting six hex digits.`); return; } r = parseInt(str.substring(0, 2), 16); g = parseInt(str.substring(2, 4), 16); b = parseInt(str.substring(4, 6), 16); } targetAttributes.rgb_color = [r, g, b]; targetAttributes.color_mode = RGB; if (states.white) { targetAttributes.color_mode = RGBW; if (!targetAttributes.rgbw_color) { targetAttributes.rgbw_color = [r, g, b, 0]; } targetAttributes.rgbw_color[0] = r; targetAttributes.rgbw_color[1] = g; targetAttributes.rgbw_color[2] = b; } }; //check if the current state is rgb array. const rgbState = await adapterData.adapter.getForeignStateAsync(states.rgb_color); if (rgbState && rgbState.val) { attribute.is_rgb_array = /([0-9]){1,3},([0-9]){1,3},([0-9]){1,3}/.test(rgbState.val.toString()); } entity.attributes.rgb_color = [null, null, null]; entity.context.STATE.storedValues.rgb_color = [0, 0, 0]; entity.attributes.supported_color_modes.push(RGB); } } /** * Add RGB attribute to entity from states object. * * @param states {Record<string, string>} - translation from function to ioBroker ids. * @param objects {Record<string, ioBroker.Object>} - ioBroker objects cache. * @param entity {object} - entity to add the attribute to. */ function _lightAdvancedAddRGB(states, objects, entity) { if (states.red && states.green && states.blue) { const red_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'red'); const green_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'green'); const blue_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'blue'); const rgbGetParser = (index, entity, attr, state) => { let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } if (!targetAttributes.rgb_color) { targetAttributes.rgb_color = [0, 0, 0]; } let val = state ? state.val || 0 : 0; val = (val / attr.max) * 255; targetAttributes.rgb_color[index] = val; if (targetAttributes.rgbw_color) { targetAttributes.rgbw_color[index] = val; } targetAttributes.color_mode = states.white ? RGBW : RGB; }; red_attr.getParser = rgbGetParser.bind(this, 0); green_attr.getParser = rgbGetParser.bind(this, 1); blue_attr.getParser = rgbGetParser.bind(this, 2); red_attr.max = (objects[states.red] && objects[states.red].common && objects[states.red].common.max) || 100; green_attr.max = (objects[states.green] && objects[states.green].common && objects[states.green].common.max) || 100; blue_attr.max = (objects[states.blue] && objects[states.blue].common && objects[states.blue].common.max) || 100; entity.attributes.supported_color_modes.push(RGB); entity.attributes.rgb_color = [null, null, null]; entity.context.STATE.storedValues.rgb_color = [0, 0, 0]; if (states.white) { entity.attributes.rgbw_color = [null, null, null, null]; entity.context.STATE.storedValues.rgbw_color = [0, 0, 0, 0]; } } } /** * Process turn_on data from the frontend and set the right ioBroker states. * * @param data {object} data from frontend that may contain settings for color or color temperature or so. * @param entity {object} entity to process the command for, used to get ioBroker object ids. * @param user {string} user who triggered the command. * @returns {Promise<void>} - resolves when done. */ async function _setLightAdvancedAttributesToIOBStates(data, entity, user) { function NumToHex(num) { num = Math.min(Math.max(Math.round(num), 0), 255); let hex = Number(num).toString(16).toUpperCase(); if (hex.length < 2) { hex = `0${hex}`; } return hex; } if (data.service_data.color_temp) { //will also be false for ct = 0 -> but ct = 0 is not a useful mired value and creates issues with the conversion. let ct = 1e6 / data.service_data.color_temp; entity.attributes.color_temp_kelvin = ct; entity.attributes.color_temp = data.service_data.color_temp; const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'color_temp'); if (attr.convert_to_mired) { ct = data.service_data.color_temp; } entity.attributes.color_mode = COLOR_TEMP; await adapterData.adapter.setForeignStateAsync(attr.getId, ct, false, { user }); } if (data.service_data.color_temp_kelvin) { //will also be false for ct = 0 -> but ct = 0 is not a useful mired value and creates issues with the conversion. let ct = data.service_data.color_temp_kelvin; entity.attributes.color_temp_kelvin = ct; const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'color_temp'); if (attr.convert_to_mired) { ct = 1e6 / ct; } entity.attributes.color_mode = COLOR_TEMP; await adapterData.adapter.setForeignStateAsync(attr.getId, ct, false, { user }); } if (data.service_data.brightness >= 0 && !data.service_data.brightness_pct) { data.service_data.brightness_pct = (data.service_data.brightness / 255) * 100; } if (data.service_data.brightness_pct >= 0) { const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'brightness'); entity.attributes.brightness = (data.service_data.brightness_pct / 100) * 255; if (!entity.context.STATE.isBoolean) { entity.state = data.service_data.brightness_pct > 0 ? 'on' : 'off'; } if (!entity.attributes.color_mode || entity.attributes.color_mode === ONOFF) { entity.attributes.color_mode = BRIGHTNESS; } await adapterData.adapter.setForeignState( attr.setId, (data.service_data.brightness_pct / 100) * (attr.max - attr.min) + attr.min, false, { user }, ); } if (data.service_data.hs_color) { const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'hue'); const attr_Sat = entity.context.ATTRIBUTES.find(a => a.attribute === 'saturation'); entity.attributes.hs_color = data.service_data.hs_color; const [h, s] = data.service_data.hs_color; if (attr) { await adapterData.adapter.setForeignStateAsync(attr.getId, (h / 360) * attr.max, false, { user }); } else { adapterData.log.warn(`No hue for ${entity.entity_id}, can only set saturation.`); } if (attr_Sat) { await adapterData.adapter.setForeignStateAsync(attr_Sat.getId, (s / 100) * attr_Sat.max, false, { user }); } else { adapterData.log.warn(`No saturation for ${entity.entity_id}, can only set hue.`); } entity.attributes.color_mode = HS; } if (data.service_data.rgbw_color) { const attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'white'); await adapterData.adapter.setForeignStateAsync( attr.getId, (data.service_data.rgbw_color[3] / 255) * attr.max, false, { user }, ); data.service_data.rgb_color = data.service_data.rgbw_color; //make sure we set color, too. entity.attributes.color_mode = RGBW; } if (data.service_data.rgb_color) { const rgb_color = entity.context.ATTRIBUTES.find(a => a.attribute === 'rgb_color'); const [r, g, b] = data.service_data.rgb_color; if (rgb_color) { if (!rgb_color.is_rgb_array) { const rgbString = `#${NumToHex(r)}${NumToHex(g)}${NumToHex(b)}`; await adapterData.adapter.setForeignStateAsync(rgb_color.getId, rgbString, false, { user }); } else { const rgbString = `${r},${g},${b}`; await adapterData.adapter.setForeignStateAsync(rgb_color.getId, rgbString, false, { user }); } } else { const red_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'red'); const green_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'green'); const blue_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'blue'); //set r,g,b to single states in ioBroker. rgb is always [0-255] here (from HASS), so scale here. await Promise.all([ adapterData.adapter.setForeignStateAsync(red_attr.getId, (r / 255) * red_attr.max, false, { user }), adapterData.adapter.setForeignStateAsync(green_attr.getId, (g / 255) * green_attr.max, false, { user }), adapterData.adapter.setForeignStateAsync(blue_attr.getId, (b / 255) * blue_attr.max, false, { user }), ]); } entity.attributes.color_mode = !data.service_data.rgbw_color ? RGB : RGBW; } if (data.service_data.effect) { const effect_attr = entity.context.ATTRIBUTES.find(a => a.attributes === 'effect'); let val = effect_attr.states[data.service_data.effect]; if (val === undefined) { val = data.service_data.effect; } await adapterData.adapter.setForeignStateAsync(effect_attr.getId, val, false, { user }); } } /** * handle turn on command for light entity from frontend * * @param entity {object} - entity to find command for. * @param command {object} - command from frontend. * @param data {object} - data from frontend. * @param user {string} - user who triggered the command. * @returns {Promise<void>} - resolves when done */ async function _handleTurnOnCmd(entity, command, data, user) { // if ON/OFF object exists if (entity.context.STATE.setId && entity.context.STATE.getId) { // read actual state const state = await adapterData.adapter.getForeignStateAsync(entity.context.STATE.getId); // if the light is not ON if (!state || !state.val) { // turn ON: await adapterData.adapter.setForeignStateAsync(entity.context.STATE.setId, command.on, false, { user }); } } if (!entity.attributes.color_mode) { entity.attributes.color_mode = ONOFF; } await _setLightAdvancedAttributesToIOBStates(data, entity, user); } /** * Extract relevant ids from the type-detector control object. * Result object has optional members: * state * brightness * color_temp * * hue * saturation * * rgb_color * red * green * blue * white * * @param control {{control: Array<object>, type: string}} - type-detector control object. * @returns {Record<string,object>} - object with state ids. */ function convertControlToStates(control) { function findState(name) { const state = control.states.find(s => s.id && s.name === name); return state ? state.id : undefined; } const states = {}; switch (control.type) { case Types.rgb: case Types.rgbSingle: case Types.hue: case Types.ct: states.state = findState('ON'); states.stateRead = findState('ON_ACTUAL'); //still don't get difference between brightness and dimmer... is a bit confusing in type-detector, too: states.brightness = findState('DIMMER'); if (!states.brightness) { states.brightness = findState('BRIGHTNESS'); } break; case Types.dimmer: states.state = findState('ON_SET'); states.stateRead = findState('ON_ACTUAL'); if (!states.state) { states.state = findState('SET'); if (!states.stateRead) { states.stateRead = findState('ACTUAL'); } } states.brightness = findState('SET'); states.brightnessRead = findState('ACTUAL'); break; case Types.light: states.state = findState('SET'); states.stateRead = findState('ACTUAL'); break; } states.color_temp = findState('TEMPERATURE'); //color stuff: states.hue = findState('HUE'); states.saturation = findState('SATURATION'); states.rgb_color = findState('RGB'); states.red = findState('RED'); states.green = findState('GREEN'); states.blue = findState('BLUE'); states.white = findState('WHITE'); return states; } /** * Fills bare entity from states and objects with all that light entity can do. * * @param states - state ids, either created manually or from convertControlToStates function * @param objects - objects for state ids. * @param entity - bare entity to fill. * @returns {Promise<*[entity]>} - array with entity */ async function fillLightEntityFromStates(states, objects, entity) { utils.fillEntityFromStates(states, entity); //already prefills attributes. entity.attributes.supported_color_modes = []; entity.attributes.color_mode = ONOFF; entity.supported_features = 0; if (!entity.context.COMMANDS) { entity.context.COMMANDS = []; } entity.context.STATE.storedValues = {}; const white_attr = entity.context.ATTRIBUTES.find(a => a.attribute === 'white'); if (states.white) { white_attr.max = (objects[white_attr.getId] && objects[white_attr.getId].common && objects[white_attr.getId].common.max) || 100; entity.attributes.rgbw_color = [ entity.attributes.red, entity.attributes.green, entity.attributes.blue, entity.attributes.white, ]; if (states.red || states.rgb_color) { entity.attributes.supported_color_modes.push(RGBW); white_attr.getParser = (entity, attr, state) => { const val = state ? state.val || 0 : 0; let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } if (!targetAttributes.rgbw_color) { targetAttributes.rgbw_color = [ targetAttributes.rgb_color ? targetAttributes.rgb_color[0] : 0, targetAttributes.rgb_color ? targetAttributes.rgb_color[1] : 0, targetAttributes.rgb_color ? targetAttributes.rgb_color[2] : 0, ]; } targetAttributes.rgbw_color[3] = (val / attr.max) * 255; targetAttributes.color_mode = RGBW; }; } } //fill in on/off state id. _lightAdvancedAddState(states, objects, entity); //fill in color temperature stuff. await _lightAdvancedAddColorTemperature(states, objects, entity); //if there is a "BRIGHTNESS" control, use it to dim the light. await _lightAdvancedAddBrightness(states, objects, entity); //add hue and sat: await _lightAdvancedAddHueAndSat(states, objects, entity); //add rgb. Will only happen, if no hue. await _lightAdvancedAddRGBSingle(states, objects, entity); //add rgb as single states. Will only happen if no hue and no rgbSingle: await _lightAdvancedAddRGB(states, objects, entity); if (states.effect) { const effect_attr = entity.context.ATTRIBUTES.find(a => a.attributes === 'effect'); effect_attr.states = (objects[effect_attr.getId] && objects[effect_attr.getId].common && objects[effect_attr.getId].common.states) || { 0: 'Please', 1: 'Fill', 2: 'States' }; entity.attributes.effect_list = Object.values(effect_attr.states); effect_attr.getParser = (entity, attr, state) => { state = state || { val: 0 }; let targetAttributes = entity.attributes; if (entity.state !== 'on') { targetAttributes = entity.context.STATE.storedValues; } targetAttributes.effect = effect_attr.states[state.val]; }; entity.supported_features |= SUPPORT_EFFECT; } entity.context.COMMANDS.push({ service: 'turn_on', on: true, setId: entity.context.STATE.setId, parseCommand: _handleTurnOnCmd.bind(this), }); if (!entity.context.STATE.isBoolean) { const stateObj = objects[states.state]; entity.context.COMMANDS[0].on = (stateObj && stateObj.common && stateObj.common.max) || 100; entity.context.COMMANDS.push({ service: 'turn_off', off: (stateObj && stateObj.common && stateObj.common.min) || 0, setId: entity.context.STATE.setId, parseCommand: (entity, command, data, user) => { return adapterData.adapter.setForeignStateAsync(command.setId, command.off, false, { user }); }, }); entity.context.STATE.getParser = function (entity, attr, state) { state = state || { val: null }; entity.state = state.val > ((stateObj && stateObj.common && stateObj.common.min) || 0) ? 'on' : 'off'; if (!entity.attributes.color_mode) { entity.attributes.color_mode = ONOFF; } }; } return [entity]; } /** * Create manual light entity. * * @param id - id of "main" object, i.e., state. * @param obj - iobroker object of id param * @param entity - already created entity * @param objects - id object cache * @param custom - custom part of object * @returns {Promise<[entity]>} - array with entity */ exports.processManualEntity = async function (id, obj, entity, objects, custom) { const states = custom.states || { state: id, }; if (obj && obj.common && obj.common.type === 'number') { states.brightness = id; } return fillLightEntityFromStates(states, objects, entity); }; /** * Create Light entity form type-detector detection. * * @param id {string} - id of ioBroker object to create entity from * @param control {object} - type-detector controls object * @param name {string} - name of entity * @param room {string|undefined} - room of entity * @param func {string|undefined} - function of entity * @param _obj {ioBroker.Object} - object of entity * @param objects {Record<string, ioBroker.Object>} - id object cache * @param [forcedEntityId] {string} - optional entity id to force. * @returns {Promise<null|entity[]>} - array of entities or null if no on/off control found. */ exports.processLightAdvanced = async function (id, control, name, room, func, _obj, objects, forcedEntityId) { const states = convertControlToStates(control); if (states.state) { const entity = utils.processCommon(name, func, room, _obj, 'light', forcedEntityId); return fillLightEntityFromStates(states, objects, entity); } else { adapterData.log.debug(`Could not add ${id} of type ${control.type} -> no on/off control found.`); return null; } }; /** * Augment services for light entities. * * @param services {Record<string, object>} - services to augment. */ function augmentServices(services) { services.light = { turn_on: { name: 'Turn on', description: 'Turn on one or more lights and adjust properties of the light, even when they are turned on already.', fields: { transition: { filter: { supported_features: [32], }, selector: { number: { min: 0, max: 300, unit_of_measurement: 'seconds', }, }, name: 'Transition', description: 'Duration it takes to get to next state.', }, rgb_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, selector: { color_rgb: null, }, name: 'Color', description: 'The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue.', }, rgbw_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[255, 100, 100, 50]', selector: { object: null, }, name: 'RGBW-color', description: 'The color in RGBW format. A list of four integers between 0 and 255 representing the values of red, green, blue, and white.', }, rgbww_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[255, 100, 100, 50, 70]', selector: { object: null, }, name: 'RGBWW-color', description: 'The color in RGBWW format. A list of five integers between 0 and 255 representing the values of red, green, blue, cold white, and warm white.', }, hs_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[300, 70]', selector: { object: null, }, name: 'Hue/Sat color', description: 'Color in hue/sat format. A list of two integers. Hue is 0-360 and Sat is 0-100.', }, xy_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[0.52, 0.43]', selector: { object: null, }, name: 'XY-color', description: 'Color in XY-format. A list of two decimal numbers between 0 and 1.', }, color_temp: { filter: { attribute: { supported_color_modes: ['color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, selector: { color_temp: { unit: 'mired', min: 153, max: 500, }, }, name: 'Color temperature', description: 'Color temperature in mireds.', }, kelvin: { filter: { attribute: { supported_color_modes: ['color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, selector: { color_temp: { unit: 'kelvin', min: 2000, max: 6500, }, }, name: 'Color temperature', description: 'Color temperature in Kelvin.', }, brightness: { filter: { attribute: { supported_color_modes: ['brightness', 'color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, selector: { number: { min: 0, max: 255, }, }, name: 'Brightness value', description: 'Number indicating brightness, where 0 turns the light off, 1 is the minimum brightness, and 255 is the maximum brightness.', }, brightness_pct: { filter: { attribute: { supported_color_modes: ['brightness', 'color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, selector: { number: { min: 0, max: 100, unit_of_measurement: '%', }, }, name: 'Brightness', description: 'Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness.', }, }, target: { entity: [ { domain: ['light'], }, ], }, }, turn_off: { name: 'Turn off', description: 'Turn off one or more lights.', fields: { transition: { filter: { supported_features: [32], }, selector: { number: { min: 0, max: 300, unit_of_measurement: 'seconds', }, }, name: 'Transition', description: 'Duration it takes to get to next state.', }, flash: { filter: { supported_features: [8], }, advanced: true, selector: { select: { options: [ { label: 'Long', value: 'long', }, { label: 'Short', value: 'short', }, ], }, }, name: 'Flash', description: 'Tell light to flash, can be either value short or long.', }, }, target: { entity: [ { domain: ['light'], }, ], }, }, toggle: { name: 'Toggle', description: 'Toggles one or more lights, from on to off, or, off to on, based on their current state.', fields: { transition: { filter: { supported_features: [32], }, selector: { number: { min: 0, max: 300, unit_of_measurement: 'seconds', }, }, name: 'Transition', description: 'Duration it takes to get to next state.', }, rgb_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[255, 100, 100]', selector: { color_rgb: null, }, name: 'Color', description: 'The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue.', }, hs_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[300, 70]', selector: { object: null, }, name: 'Hue/Sat color', description: 'Color in hue/sat format. A list of two integers. Hue is 0-360 and Sat is 0-100.', }, xy_color: { filter: { attribute: { supported_color_modes: ['hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, example: '[0.52, 0.43]', selector: { object: null, }, name: 'XY-color', description: 'Color in XY-format. A list of two decimal numbers between 0 and 1.', }, color_temp: { filter: { attribute: { supported_color_modes: ['color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, selector: { color_temp: null, }, name: 'Color temperature', description: 'Color temperature in mireds.', }, kelvin: { filter: { attribute: { supported_color_modes: ['color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, selector: { color_temp: { unit: 'kelvin', min: 2000, max: 6500, }, }, name: 'Color temperature', description: 'Color temperature in Kelvin.', }, brightness: { filter: { attribute: { supported_color_modes: ['brightness', 'color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, advanced: true, selector: { number: { min: 0, max: 255, }, }, name: 'Brightness value', description: 'Number indicating brightness, where 0 turns the light off, 1 is the minimum brightness, and 255 is the maximum brightness.', }, brightness_pct: { filter: { attribute: { supported_color_modes: ['brightness', 'color_temp', 'hs', 'xy', 'rgb', 'rgbw', 'rgbww'], }, }, selector: { number: { min: 0, max: 100, unit_of_measurement: '%', }, }, name: 'Brightness', description: 'Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness.', }, }, target: { entity: [ { domain: ['light'], }, ], }, }, }; } augmentServices(adapterData.services);