UNPKG

node-red-contrib-ewelink-cube

Version:

Node-RED integration with eWeLink Cube

773 lines (727 loc) 33.8 kB
<script type="text/javascript"> function delItem(Dom) { $(Dom).parents('#main-area').remove(); // saveAddtionState(); } function saveAddtionState() { let saveData = ''; const ADDTION_STATE_ARRAY = '.node-input-state-class'; const NODE_SAVE_DATA = '#node-input-list'; $(ADDTION_STATE_ARRAY).each(function (index, item) { if ($(this).val()) { if (index !== $(ADDTION_STATE_ARRAY).length - 1) { saveData += $(this).val() + ','; } else { saveData += $(this).val(); } } }); $(NODE_SAVE_DATA).val(saveData); } (function () { const DOM_ID_INPUT_DEVICE = '#node-input-device'; const DOM_ID_INPUT_CATEGORY = '#node-input-category'; const DOM_ID_INPUT_SERVER = '#node-input-server'; const DOM_ID_INPUT_STATE = '#node-input-state'; const DOM_ID_INPUT_SAVE_DATA = '#node-input-list'; const NODE_INPUT_STATE_CLASS = '.node-input-state-class' const SERVER_EMPTY = '_ADD_'; const ADD_ITEM = 'ADD_ITEM'; const INIT_ITEM = 'INIT_ITEM'; let tempList = []; let outer_node = null; function renderOptions(optionList) { $(DOM_ID_INPUT_DEVICE).get(0).options.length = 0; if (!optionList.length || optionList.length < 1) return ''; var optionStr = '<option selected="selected" disabled="disabled" style="display:none" value=""></option><option value="all">All</option>'; for (const item of optionList) { optionStr += '<option' + ' value=' + item.serial_number + '>' + (item.name || item.manufacturer + item.display_category) + '</option>'; } return optionStr; } function getDeviceList(node) { const server = $(DOM_ID_INPUT_SERVER).val(); $.ajax({ type: 'POST', url: 'ewelink-cube-api-v1/get-device-list', contentType: 'application/json; charset=utf-8', data: JSON.stringify({ id: server }), success(res) { if (res.error === 0) { tempList = []; let deviceList = res.data.device_list; for (const item of deviceList) { let params = { serial_number: item.serial_number, display_category: item.display_category, name: item.name, manufacturer: item.manufacturer, state: item.state, capabilities:item.capabilities }; tempList.push(params); } tempList.push({ serial_number: 'ihost', display_category: 'ihost', name: 'ihost', manufacturer: 'ihost', state: 'ihost', capabilities: [], tags: null, }) renderDeviceOption(node); const deviceVal = $(DOM_ID_INPUT_DEVICE).val(); if (deviceVal && deviceVal !== 'all')renderStateOption(node); getBridgeName(); $('#state').show(); if (deviceVal === 'all') { $(DOM_ID_INPUT_STATE).get(0).options.length = 0; $(DOM_ID_INPUT_STATE).append('<option selected="selected" disabled="disabled" style="display:none" value=""></option><option value="all">All</option>'); $(DOM_ID_INPUT_STATE).val(node.state); $('#state').hide(); } node.list && $(NODE_INPUT_STATE_CLASS).remove() && $('#main-area').eq(0).nextAll().remove() && renderAddtionState(); }else{ if(server && server!==SERVER_EMPTY){ RED.notify(`${node._('event-state.message.connect_fail')}`, { type: 'error' }); } } }, error(error) { if(server && server!==SERVER_EMPTY){ RED.notify(`${node._('event-state.message.connect_fail')}`, { type: 'error' }); } }, }); } function renderDeviceOption(node) { const deviceOption = renderOptions(tempList); $(DOM_ID_INPUT_DEVICE).append(deviceOption); $(DOM_ID_INPUT_DEVICE).val(node.device); } function renderStateOption(node) { $(DOM_ID_INPUT_STATE).get(0).options.length = 0; let template = getStateOption(INIT_ITEM).template; $(DOM_ID_INPUT_STATE).append(template); //Compatibility with old version toggle capability const device = tempList.find((item)=>item.serial_number === $(DOM_ID_INPUT_DEVICE).val()); if(node.state === 'toggle' && device.display_category !== 'thermostat'){ $(DOM_ID_INPUT_STATE).append( `<option selected="selected" disabled="disabled" value="toggle">${outer_node._('event-state.state.toggle')}</option>` ) } node.state && $(DOM_ID_INPUT_STATE).val(node.state); } function addItem() { let { stateLength , template } = getStateOption(ADD_ITEM); if($(DOM_ID_INPUT_STATE).val() === 'all'){ console.log('all'); return; } if ($('#state').children().length >= stateLength) { RED.notify('Limit exceeded', { type: 'error' }); return; } if($(DOM_ID_INPUT_DEVICE).val() === 'ihost'){ console.log('ihost'); template = ` <option selected="selected" disabled="disabled" style="display:none" value="" label=""></option> <option value="arm_state">${outer_node._('event-state.state.Arm_disarm')}</option> <option value="alarm_state">${outer_node._('event-state.state.trigger_alarm')}</option> ` } $('#state').append( `<div id="main-area"> <div class="right-area" style="margin-top:10px"> <select class="node-input-state-class" name="node-input-state" style="width:310px" onChange="saveAddtionState()"> ${template} </select> <button class="del-item-btn" onclick="delItem(this)" style="background-color: #FF335D;color: white">-</button> </div> </div> ` ); } function renderAddtionState() { const stateData = $(DOM_ID_INPUT_SAVE_DATA).val(); if (!stateData) return; const addtionList = stateData.split(','); addtionList.forEach((item,index) => { $('#state').append( `<div id="main-area"> <div class="right-area" style="margin-top:10px"> <select class="node-input-state-class" name="node-input-state" style="width:310px" onChange="saveAddtionState()"> ${getStateOption(INIT_ITEM).template} </select> <button class="del-item-btn" onclick="delItem(this)" style="background-color: #FF335D;color: white">-</button> </div> </div> ` ); }); const device = tempList.find((item)=>item.serial_number === $(DOM_ID_INPUT_DEVICE).val()); console.log('device=============>',device); $(NODE_INPUT_STATE_CLASS).each(function (index, item) { // Compatibility with old version toggle capability if(addtionList[index] === 'toggle' && device.display_category !== 'thermostat'){ $('.node-input-state-class').append( `<option selected="selected" disabled="disabled" value="toggle">${outer_node._('event-state.state.toggle')}</option>` ) } $(this).val(addtionList[index]); }); } function getStateOption(type) { if($(DOM_ID_INPUT_DEVICE).val() === 'ihost'){ return { template:` <option selected="selected" disabled="disabled" style="display:none" value="" label=""></option> <option value="all">All</option> <option value="arm_state">${outer_node._('event-state.state.Arm_disarm')}</option> <option value="alarm_state">${outer_node._('event-state.state.trigger_alarm')}</option> `, stateLength:2 }; } const STATE_LIST = { 'power': { id: 0, name: outer_node._('event-state.state.power'), value: 'power', }, 'toggle': { id: 1, name: outer_node._('event-state.state.toggle'), value: 'toggle', }, 'brightness': { id: 2, name: outer_node._('event-state.state.brightness'), value: 'brightness', }, 'color-temperature': { id: 3, name: outer_node._('event-state.state.color_temperature'), value: 'color-temperature', }, 'color-rgb': { id: 4, name: outer_node._('event-state.state.color_rgb'), value: 'color-rgb', }, 'percentage': { id: 5, name: outer_node._('event-state.state.percentage'), value: 'percentage', }, 'motor-control': { id: 6, name: outer_node._('event-state.state.motor_control'), value: 'motor-control', }, 'motor-reverse': { id: 7, name: outer_node._('event-state.state.motor_reverse'), value: 'motor-reverse', }, 'motor-clb': { id: 9, name: outer_node._('event-state.state.motor_clb'), value: 'motor-clb', }, 'detect': { id: 10, name: outer_node._('event-state.state.detect'), value: 'detect', }, 'battery': { id: 11, name: outer_node._('event-state.state.battery'), value: 'battery', }, 'press': { id: 12, name: outer_node._('event-state.state.press'), value: 'press', }, 'rssi': { id: 13, name: outer_node._('event-state.state.rssi'), value: 'rssi', }, 'temperature':{ id: 14, name: outer_node._('event-state.state.temperature'), value: 'temperature', }, 'humidity':{ id: 15, name: outer_node._('event-state.state.humidity'), value: 'humidity', }, 'multi-press':{ id: 16, name: outer_node._('event-state.state.multi_press'), value: 'multi-press', }, 'startup':{ id:17, name:outer_node._('event-state.state.startup'), value:'startup' }, 'inching':{ id:18, name:outer_node._('event-state.state.inching'), value:'inching' }, 'toggle-inching':{ id:19, name:outer_node._('event-state.state.toggle_inching'), value:'toggle-inching' }, 'moisture':{ id:20, name:outer_node._('event-state.state.moisture'), value:'moisture' }, 'barometric-pressure':{ id:21, name:outer_node._('event-state.state.barometric_pressure'), value:'barometric-pressure' }, 'wind-direction':{ id:22, name:outer_node._('event-state.state.wind_direction'), value:'wind-direction' }, 'rainfall':{ id:23, name:outer_node._('event-state.state.rainfall'), value:'rainfall' }, 'illumination':{ id:24, name:outer_node._('event-state.state.illumination'), value:'illumination' }, 'illumination-level':{ id:25, name:outer_node._('event-state.state.illumination_level'), value:'illumination-level' }, 'ultraviolet-index':{ id:26, name:outer_node._('event-state.state.ultraviolet_index'), value:'ultraviolet-index' }, 'co2':{ id:27, name:outer_node._('event-state.state.co2'), value:'co2' }, 'power-consumption':{ id:28, name:outer_node._('event-state.state.power_consumption'), value:'power-consumption' }, 'voltage':{ id:29, name:outer_node._('event-state.state.voltage'), value:'voltage' }, 'electric-power':{ id:30, name:outer_node._('event-state.state.electric_power'), value:'electric-power' }, 'electric-current':{ id:31, name:outer_node._('event-state.state.electric_current'), value:'electric-current' }, 'mode':{ id:32, name:outer_node._('event-state.state.mode'), value:'mode' }, 'thermostat-mode-detect':{ id:33, name:outer_node._('event-state.state.thermostat_mode_detect'), value:'thermostat-mode-detect' }, 'identify':{ id:34, name:outer_node._('event-state.state.identify'), value:'identify' }, 'tamper-alert':{ id:35, name:outer_node._('event-state.state.tamper_alert'), value:'tamper-alert' }, 'detect-hold':{ id:36, name:outer_node._('event-state.state.detect_hold'), value:'detect-hold' }, 'threshold-breaker':{ id:37, name:outer_node._('event-state.state.threshold_breaker'), value:'threshold-breaker' }, 'fault':{ id:38, name:outer_node._('event-state.state.fault'), value:'fault' }, 'lqi':{ id:39, name:outer_node._('event-state.state.lqi'), value:'lqi' }, 'toggle-startup':{ id:40, name:outer_node._('event-state.state.toggle_startup'), value:'toggle-startup' }, 'power-percentage':{ id:41, name:outer_node._('event-state.state.power_percentage'), value:'power-percentage' }, 'eco':{ id:42, name:outer_node._('event-state.state.eco'), value:'eco' }, 'anti-direct-blow':{ id:43, name:outer_node._('event-state.state.anti_direct_blow'), value:'anti-direct-blow' }, 'horizontal-swing':{ id:44, name:outer_node._('event-state.state.horizontal_swing'), value:'horizontal-swing' }, 'vertical-swing':{ id:45, name:outer_node._('event-state.state.vertical_swing'), value:'vertical-swing' }, 'window-detection':{ id:46, name:outer_node._('event-state.state.window_detection'), value:'window-detection' }, 'child-lock':{ id:47, name:outer_node._('event-state.state.child_lock'), value:'child-lock' }, 'pm25':{ id:48, name:outer_node._('event-state.state.pm25'), value:'pm25' }, 'voc-index':{ id:49, name:outer_node._('event-state.state.voc_index'), value:'voc-index' }, 'gas':{ id:50, name:outer_node._('event-state.state.gas'), value:'gas' }, 'smoke':{ id:51, name:outer_node._('event-state.state.smoke'), value:'smoke' }, 'contact':{ id:52, name:outer_node._('event-state.state.contact'), value:'contact' }, 'motion':{ id:53, name:outer_node._('event-state.state.motion'), value:'motion' }, 'water-leak':{ id:54, name:outer_node._('event-state.state.water_leak'), value:'water-leak' }, }; let abilityList = []; let stateLength = 0; for (const ele of tempList) { if (ele.serial_number === $(DOM_ID_INPUT_DEVICE).val()) { abilityList.push(ele.state); } } let template = '<option selected="selected" disabled="disabled" style="display:none" value="" label=""></option>' + (type === INIT_ITEM ? '<option value="all">All</option>' : ''); if (abilityList.length > 0) { // filter special device let stateList = stateFilter( Object.keys(abilityList[0])); for (const item of stateList) { //The toggle capability needs to be split and processed separately if(STATE_LIST[item] && STATE_LIST[item].name && item !== 'toggle'){ template += `<option value="${item}">${STATE_LIST[item].name}</option>`; } } const params = { stateList, outer_node, template, abilityList }; template = spacialDeviceAlias(params).template; stateLength = template.split('</option>').length - 1 ; // stateLength = spacialDeviceAlias(params).stateLength; } return { template, stateLength }; } function getBridgeName(node){ const server = $('#node-input-server').val(); let data = { ip:server, }; $.ajax({ type: 'POST', url: 'ewelink-cube-api-v1/bridge', contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), success(res) { if (res.error === 0) { ihostName = res.data.name; $(DOM_ID_INPUT_DEVICE).children().each(function() { if($(this).text() === 'ihost'){ $(this).text(ihostName); } }); } }, error(error) {}, }); } /** Special Equipment Filtration Capabilities */ function stateFilter(stateList){ const device = tempList.find((item)=>item.serial_number === $(DOM_ID_INPUT_DEVICE).val()); if(device){ const capabilityList = Object.values((device.capabilities).map((item) => item.capability)); if(device.display_category === 'switch' && capabilityList.includes('humidity') && capabilityList.includes('temperature')){ return stateList.filter((item) => item !== 'mode'); } } return stateList; } /** Special devices handle the ability of states, such as changing multi-channel devices from monitoring toggle capabilities to monitoring specific channels*/ function spacialDeviceAlias(params){ const device = tempList.find((item)=>item.serial_number === $(DOM_ID_INPUT_DEVICE).val()); if(device){ const capabilityList = Object.values((device.capabilities).map((item) => item.capability)); // 1、Fan light distinguishing light switch and gear position if(device.display_category === 'fanLight'){ if(params.stateList.includes('toggle')){ const toggleValue = params.abilityList[0].toggle; const toggleNumber = Object.keys(toggleValue).length; for(let i = 0 ;i < toggleNumber ; i++){ params.template += `<option value="toggle${i+1}">${i === 0 ? params.outer_node._('event-state.state.light') : params.outer_node._('event-state.state.fan')}</option>`; } } if(params.stateList.includes('mode')){ params.template += `<option value="mode">${params.outer_node._('event-state.state.mode')}</option>`; } return params; } //2、Multichannel device splitting ['switch','plug'].includes(device.display_category) if( params.stateList.includes('toggle') && device.display_category !== 'thermostat'){ const toggleValue = params.abilityList[0].toggle; const toggleNumber = Object.keys(toggleValue).length; for(let i = 0 ;i < toggleNumber ; i++){ params.template += `<option value="toggle${i+1}">${params.outer_node._('event-state.state.Channel') + ''+params.outer_node._(`event-state.state.${(i+1)}`) + params.outer_node._(`event-state.state.Status`)}</option>`; } return params; } // 3、trv device; if(device.display_category === 'thermostat'){ // 之所以要循环,因为能力在state中不一定存在,能力变化后才会出现在state中; for(const capabilityName of params.stateList){ /** "thermostat"能力包含模式能力和状态能力 */ if(capabilityName === "thermostat"){ const thermostat = params.abilityList[0].thermostat; const thermostaCapalist = Object.keys(thermostat); thermostaCapalist.forEach((i)=>{ params.template += `<option value="${i}">${params.outer_node._(`event-state.state.${i}`)}</option>`; // render mode and status; }); } /** 温控阀的toggle能力特殊处理,通道1代表童锁,通道2代表开窗检测*/ if(capabilityName === 'toggle'){ const toggle = params.abilityList[0].toggle; const toggleCapalist = Object.keys(toggle); toggleCapalist.forEach((i)=>{ params.template += `<option value="${i}">${params.outer_node._(`event-state.state.${i}`)}</option>`; // render child_lock and open_windows_detect }); } /** 目标温度和霜降温度 三个模式对应三个目标温度,防霜冻温度就是防霜冻模式下的目标温度 */ if(capabilityName === 'thermostat-target-setpoint'){ const targetSetpoint = params.abilityList[0]['thermostat-target-setpoint']; const targetSetpointCapalist = Object.keys(targetSetpoint); targetSetpointCapalist.forEach((i)=>{ params.template += `<option value="${i}">${params.outer_node._(`event-state.state.${i}`)}</option>`; // render target temp in different mode }); } const batteryOption = `<option value="battery">${params.outer_node._(`event-state.state.battery`)}</option>`; const rssiOption = `<option value="rssi">${params.outer_node._(`event-state.state.rssi`)}</option>`; params.template = params.template.replace(batteryOption,'').replace(rssiOption,''); } // 温度能力在trv叫当前温度; params.template = params.template.replace('温度','当前温度'); return params; } if(device.display_category === 'lightStrip'){ if(params.abilityList[0].mode){ params.template += `<option value="mode">${params.outer_node._(`event-state.lightStrip.mode`)}</option>`; } } } return params; } RED.nodes.registerType('event-state', { category: 'eWeLink Cube', color: '#A4B9FC', defaults: { name: { value: '', }, server: { value: '', required: true, type: 'api-server', }, list: { value: '', }, category: { value: '', }, device: { value: '', }, state: { value: '', }, }, inputs: 0, outputs: 1, icon: 'bridge.svg', label() { return this.name || 'event-state'; }, oneditprepare() { const node = this; outer_node = node; const server = $(DOM_ID_INPUT_SERVER).val(); $('#node-input-server').on('change', () => { if (server && server !== SERVER_EMPTY) { getDeviceList(node); } }); $(DOM_ID_INPUT_DEVICE).on('focus', () => { getDeviceList(node); }); $(DOM_ID_INPUT_DEVICE).on('change', () => { $('#state #main-area').eq(0).nextAll().remove(); $(DOM_ID_INPUT_STATE).get(0).options.length = 0; const { template } = getStateOption(INIT_ITEM); $(DOM_ID_INPUT_STATE).append(template); $('#state').show(); if ($(DOM_ID_INPUT_DEVICE).val() === 'all') { $('#state').hide(); $(DOM_ID_INPUT_CATEGORY).val(''); }else{ const device = tempList.find((item)=>item.serial_number === $(DOM_ID_INPUT_DEVICE).val()); device && $(DOM_ID_INPUT_CATEGORY).val(device.display_category); } }); $(DOM_ID_INPUT_STATE).on('change', () => { if ($(DOM_ID_INPUT_STATE).val() === 'all') { $('#state #main-area').eq(0).nextAll().remove(); $(DOM_ID_INPUT_SAVE_DATA).val(''); } }); $('#add-item-btn').on('click', addItem); $(DOM_ID_INPUT_SERVER).on('change', () => { $(DOM_ID_INPUT_DEVICE).get(0).options.length = 0; $(DOM_ID_INPUT_DEVICE).val(''); $(DOM_ID_INPUT_STATE).get(0).options.length = 0; $(DOM_ID_INPUT_STATE).val(''); $('#state #main-area').eq(0).nextAll().remove(); }); // RED.events.on('deploy', () => { console.log('new flow deployed')}); }, oneditsave() { saveAddtionState(); } }); })(); </script> <script type="text/html" data-template-name="event-state"> <div class="form-row"> <label for="node-input-name" data-i18n="event-state.label.name"></label> <input type="text" id="node-input-name" placeholder="Name" /> </div> <div class="form-row" style="position:relative"> <span class="require">*</span> <label for="node-input-server" data-i18n="event-state.label.server"></label> <input type="text" id="node-input-server" placeholder="server" /> </div> <!-- style="display:none" --> <div class="form-row" style="display:none;width:0;height:0"> <label for="node-input-list"> List </label> <input type="text" id="node-input-list" /> </div> <div class="form-row" style="display:none;width:0;height:0"> <label for="node-input-category">category</label> <input id="node-input-category" placeholder="category" /> </div> <div class="form-row"> <label for="node-input-device" data-i18n="event-state.label.device"></label> <select id="node-input-device" placeholder="Device" style="width:70%"></select> </div> <div class="form-row" id="state"> <div id="main-area"> <label for="node-input-state" data-i18n="event-state.label.state" style="text-align:left"></label> <div style="display: inline-block;"> <select placeholder="Select State" id="node-input-state" name="node-input-state" style="width:310px"></select> <button class="add-item-btn" id="add-item-btn" style="background-color: #333BFF">+</button> </div> </div> </div> </script> <style> #main-area{ text-align: left; } .right-area { display: inline-block; margin-left: 104px; } .add-item-btn, .del-item-btn { display: inline-block; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; border: none; color: white; font-size: 20px; } .require { position: absolute; left: -8px; top: 10px; color: red; font-size: 20px; } </style>