UNPKG

node-red-contrib-xmihome

Version:

Node-RED nodes for controlling Xiaomi Mi Home devices using the xmihome library.

123 lines (122 loc) 11.9 kB
<script type="text/javascript"> var N=window.RED,_=60000,M=null,U=0,V=[];function j(){if(this.deviceType==="msg")return!0;if(this.deviceType==="json")return W(JSON.parse(this.device||"{}"))!==void 0;return!1}function W(z){if(z.address&&z.token)return"miio";else if(z.mac&&z.model)return"bluetooth";else if(z.id)return"cloud";else if(z.address)return"miio";else if(z.mac)return"bluetooth"}function q(z){let F=z.name||z.model||z.mac||z.address||z.id||"Unknown Device",G=[];if(z.model&&F!==z.model)G.push(z.model);if(z.mac)G.push(z.mac);else if(z.address)G.push(z.address);else if(z.id)G.push(`DID: ${z.id}`);if(G.length>0)F+=` (${G.join(", ")})`;return F}function Y(z){let F=$("#discovered-msg"),G=($("#node-input-settings").val().toString()||"").replace("_ADD_","");if(!G){F.text(M._("device.label.discoveredDeviceConfig")).show(),O();return}F.text(M._("device.label.discoveredDeviceLoading")).show(),$("#node-input-discovered-device, #node-button-refresh-devices").prop("disabled",!0),$.getJSON(`xmihome/${G}/devices?force=${!!z}`).done(function(J){F.hide(),console.log(`[xmihome-device] Loaded ${J.devices?.length||0} discovered devices for node ${M.id}.`),O(J.devices),U=J.timestamp||0}).fail(function(J,K,Q){F.text(`Failed to load devices: ${K}`).show(),O(),U=0,console.error(`[xmihome-device] Error fetching discovered devices for node ${M.id}: ${K}`,Q,J)}).always(function(){$("#node-input-discovered-device, #node-button-refresh-devices").prop("disabled",!1)})}function O(z){let F=$("#node-input-discovered-device");if(F.empty(),!z||z.length===0)return;V=z,z.sort((G,J)=>{let K=G.name||G.model||"",Q=J.name||J.model||"";return K.localeCompare(Q)}),F.append($("<option>",{value:"",text:"-- Select a device --",disabled:!0})),z.forEach((G)=>{let J=q(G),K=JSON.stringify(G);F.append($("<option>",{value:K,text:J}))}),F.val("")}function Z(z){let F=JSON.parse(z||$("#node-input-device").typedInput("value")||"{}");return $(".device-config input").val(""),Object.entries(F).forEach(([G,J])=>$("#node-input-device-"+G).val(J)),B(null,F),F}function B(z,F){let G=$("#node-input-deviceIdType");if(F)G.val(W(F));let J=G.val();if(J)$(".device-config").hide(),$(`.device-config:not([class*="device-config-"]), .device-config-${J}`).show();else $(".device-config").show();$("#node-button-open-model").prop("disabled",J==="bluetooth")}function A(z){let F=$("#node-input-device");switch(z.target.id){case"node-input-deviceSource":{let G=$("#node-device-typedinput-container"),J=$("#node-device-discovered-container");switch(z.target.value){case"input":{if(G.show(),J.hide(),F.typedInput("type")!=="json")F.typedInput("type","json"),F.typedInput("value","{}");Z();break}case"discovered":{if(G.hide(),J.show(),V.length===0||Date.now()-U>_)Y();else O(V);break}case"msg":{G.hide(),J.hide(),F.typedInput("type","msg"),F.typedInput("value","device");break}}break}case"node-input-discovered-device":{F.typedInput("type","json"),F.typedInput("value",z.target.value),$("#node-input-deviceSource").val("input").change();break}default:{let G=JSON.parse(F.typedInput("value")||"{}"),J=z.target.id.split("-").pop(),K=z.target.value;G[J]=K,F.typedInput("value",JSON.stringify(G));break}}}function L(z){let F=z.target.value;$("#node-config-row-property").toggle(F!=="getProperties"),$("#node-config-row-value").toggle(["setProperty","callAction","callMethod"].includes(F))}function X(z){let F=$("#node-input-property"),G=$("#node-property-typedinput-container");switch(z.target.value){case"input":{G.show(),F.typedInput("type","str"),F.typedInput("value","");break}case"msg":{G.hide(),F.typedInput("type","msg"),F.typedInput("value","property");break}}}function E(z){let F=$("#node-input-device-model").val().toString().trim(),G="https://home.miot-spec.com/";if(F!=="")G+=`spec/${F}`;window.open(G,"_blank")}N.nodes.registerType("xmihome-device",{category:"Xiaomi MiHome",defaults:{settings:{value:null,required:!0,type:"xmihome-config"},name:{value:""},device:{value:"{}",validate:j},deviceType:{value:"json"},action:{value:"getProperty",required:!0},property:{value:""},propertyType:{value:"str"},value:{value:""},valueType:{value:"str"},topic:{value:"topic"},topicType:{value:"msg"}},icon:"font-awesome/fa-power-off",inputs:1,outputs:2,color:"#00BC9C",paletteLabel:"Device",label:function(){return this.name||this.action||"Device"},outputLabels:["Command Result / Notifications","Connection Events"],oneditprepare:function(){if(M=this,$("#node-input-device").typedInput({typeField:"#node-input-deviceType",types:["json","msg"]}),$("#node-input-property").typedInput({typeField:"#node-input-propertyType",types:["str","msg","flow","global","json"]}),$("#node-input-value").typedInput({typeField:"#node-input-valueType",types:["str","msg","flow","global","num","bool","json","date","jsonata"]}),$("#node-input-topic").typedInput({typeField:"#node-input-topicType",types:["str","msg","flow","global"]}),$("#node-input-deviceSource, #node-input-discovered-device, .device-config input").on("change",A),$("#node-button-refresh-devices").on("click",Y),this.deviceType==="json")Z(this.device);else if(this.deviceType==="msg"&&this.device==="device")$("#node-input-deviceSource").val("msg").change();if($("#node-input-deviceIdType").on("change",B),$("#node-button-open-model").on("click",E),$("#node-input-action").on("change",L),$("#node-input-propertySource").on("change",X),this.propertyType==="msg"&&this.property==="property")$("#node-input-propertySource").val("msg").change()}}); </script> <script type="text/html" data-template-name="xmihome-device"> <div class="form-row"> <label for="node-input-settings"><i class="fa fa-cog"></i> <span data-i18n="device.label.settings"></span></label> <input id="node-input-settings" /> </div> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="device.label.name"></span></label> <input type="text" id="node-input-name" data-i18n="[placeholder]device.placeholder.name" /> </div> <fieldset> <legend data-i18n="device.label.device"></legend> <div class="form-row" id="node-config-row-device"> <label for="node-input-deviceSource"><i class="fa fa-cogs"></i> <span data-i18n="device.label.source"></span></label> <select id="node-input-deviceSource" style="width: 70%"> <option value="input" data-i18n="device.label.deviceSourceInput"></option> <option value="discovered" data-i18n="device.label.deviceSourceDiscovered"></option> <option value="msg">msg.device</option> </select> <div hidden> <input type="text" id="node-input-device" /> <input type="hidden" id="node-input-deviceType" /> </div> </div> <div id="node-device-typedinput-container"> <div class="form-row"> <label for="node-input-deviceIdType"><i class="fa fa-id-badge"></i> <span data-i18n="device.label.deviceIdType"></span></label> <select id="node-input-deviceIdType" style="width: 70%"> <option value="" data-i18n="device.label.deviceIdTypeFull"></option> <option value="cloud">Cloud ID (DID)</option> <option value="miio">MiIO (IP & Token)</option> <option value="bluetooth">Bluetooth (MAC)</option> </select> </div> <div class="form-row device-config device-config-cloud"> <label for="node-input-device-id"><i class="fa fa-cloud"></i> <span data-i18n="device.label.deviceId"></span></label> <input type="text" id="node-input-device-id" data-i18n="[placeholder]device.placeholder.deviceId" /> </div> <div class="form-row device-config device-config-miio"> <label for="node-input-device-address"><i class="fa fa-wifi"></i> <span data-i18n="device.label.deviceAddress"></span></label> <input type="text" id="node-input-device-address" data-i18n="[placeholder]device.placeholder.deviceAddress" /> </div> <div class="form-row device-config device-config-bluetooth"> <label for="node-input-device-mac"><i class="fa fa-bluetooth"></i> <span data-i18n="device.label.deviceMac"></span></label> <input type="text" id="node-input-device-mac" data-i18n="[placeholder]device.placeholder.deviceMac" /> </div> <div class="form-row device-config device-config-miio device-config-bluetooth"> <label for="node-input-device-token"> <i class="fa fa-key"></i> <span data-i18n="device.label.deviceToken"></span> <small class="device-config device-config-bluetooth" data-i18n="device.label.deviceTokenHint"></small> </label> <input type="text" id="node-input-device-token" data-i18n="[placeholder]device.placeholder.deviceToken" /> </div> <div class="form-row device-config"> <label for="node-input-device-model"> <i class="fa fa-cube"></i> <span data-i18n="device.label.deviceModel"></span> <small data-i18n="device.label.deviceModelHint"></small> </label> <div style="width: 70%; display: inline-flex;"> <input type="text" id="node-input-device-model" data-i18n="[placeholder]device.placeholder.deviceModel" style="flex-grow: 1" /> <button id="node-button-open-model" class="red-ui-button" style="margin-left: 10px"> <i class="fa fa-external-link"></i> </button> </div> <div style="font-size: 0.8em; color: #888;" data-i18n="device.label.deviceModelHelp"></div> </div> </div> <div class="form-row" id="node-device-discovered-container" hidden> <label for="node-input-discovered-device"><i class="fa fa-list"></i> <span data-i18n="device.label.discoveredDevice"></span></label> <div style="width: 70%; display: inline-flex;"> <select id="node-input-discovered-device" style="flex-grow: 1"></select> <button id="node-button-refresh-devices" class="red-ui-button" style="margin-left: 10px"> <i class="fa fa-refresh"></i> </button> </div> <div style="font-size: 0.8em; color: #888;" id="discovered-msg"></div> </div> </fieldset> <fieldset> <legend data-i18n="device.label.action"></legend> <div class="form-row"> <label for="node-input-action"><i class="fa fa-tasks"></i> <span data-i18n="device.label.action"></span></label> <select id="node-input-action" style="width: 70%"> <option value="getProperties" data-i18n="device.label.actionGetAll"></option> <option value="getProperty" data-i18n="device.label.actionGet"></option> <option value="setProperty" data-i18n="device.label.actionSet"></option> <option value="callAction" data-i18n="device.label.actionCall"></option> <option value="callMethod" data-i18n="device.label.actionCallMethod"></option> <option value="subscribe" data-i18n="device.label.actionSubscribe"></option> <option value="unsubscribe" data-i18n="device.label.actionUnsubscribe"></option> </select> </div> <div class="form-row" id="node-config-row-property"> <label for="node-input-propertySource"><i class="fa fa-cogs"></i> <span data-i18n="device.label.property"></span></label> <select id="node-input-propertySource" style="width: 110px; margin-right: 5px;"> <option value="input" data-i18n="device.label.propertySourceInput"></option> <option value="msg">msg.property</option> </select> <span id="node-property-typedinput-container"> <input type="text" id="node-input-property" data-i18n="[placeholder]device.placeholder.property" style="width: calc(70% - 120px)" /> <input type="hidden" id="node-input-propertyType" /> </span> </div> <div class="form-row" id="node-config-row-value"> <label for="node-input-value"><i class="fa fa-sign-in"></i> <span data-i18n="device.label.value"></span></label> <input type="text" id="node-input-value" style="width: 70%" /> <input type="hidden" id="node-input-valueType" /> </div> </fieldset> <fieldset> <legend data-i18n="device.label.output"></legend> <div class="form-row"> <label for="node-input-topic"><i class="fa fa-envelope-o"></i> <span data-i18n="device.label.topic"></span></label> <input type="text" id="node-input-topic" data-i18n="[placeholder]device.placeholder.topic" style="width: 70%" /> <input type="hidden" id="node-input-topicType" /> </div> </fieldset> </script>