node-red-contrib-ewelink-cube
Version:
Node-RED integration with eWeLink Cube
259 lines (228 loc) • 11.1 kB
JavaScript
const axios = require('axios');
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
const {
API_URL_CONTROL_DEVICE,
API_URL_CONTROL_CAPABILITIES_DEVICE,
EVENT_NODE_RED_ERROR,
API_URL_ENABLE_SECURITY_MODE,
API_URL_ONE_CLICK_DISARMING,
API_URL_SET_GATEWAY_MUTE,
API_URL_CANCEL_GATEWAY_MUTE,
API_URL_SET_SYSTEM_VOLUME,
API_URL_HARDWARE_SPEAKER,
API_URL_CANCEL_GATEWAY_ALARM,
} = require('./utils/const');
const _ = require('lodash');
const { frontEndDataToV2StateData, v2StateDataToV1StateData, v2StateDataToRealStateData } = require('./utils/stateTransform');
const gatewayInfoMap = require('./utils/gatewayInfoMap');
const { sleepMillisecond } = require('./utils/tools');
module.exports = function (RED) {
function ControlDevicesNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.on('input', async () => {
try {
const server = config.server.trim();
const device = config.device;
if (!server) {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: 'control-device: no server' });
return;
}
if (!device) {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: 'control-device: no device' });
return;
}
node.log(`node config: ${JSON.stringify(config)}`);
const params = JSON.parse(JSON.stringify(config));
console.log('config--------------------', config);
const versionStateData = await generateState(params, config.server);
console.log('generateState--------------', JSON.stringify(versionStateData, null, 2));
// 缺数据
if ((params.list === 'null' || params.list === '') && !params.v2Data) {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: `control-device: ${RED._('control-device.message.node_execution_failed')}` });
return;
}
if (_.isEmpty(versionStateData.state) && _.isEmpty(versionStateData.capabilities) && _.isEmpty(versionStateData.ihost)) {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: `control-device: ${RED._('control-device.message.node_execution_failed')}` });
return;
}
let data = {
id: config.server,
deviceId: config.device,
state: {},
capabilities: [],
};
data.state = versionStateData.state;
data.capabilities = versionStateData.capabilities;
node.log('DEVICE CONTROL DATA : ' + JSON.stringify(data));
if (!_.isEmpty(versionStateData.state)) {
singleSendState(data, node);
}
if (!_.isEmpty(versionStateData.capabilities)) {
singleSendCapability(data, node);
}
if (!_.isEmpty(versionStateData.ihost)) {
const errorFlag = controlIhost(versionStateData.ihost, config.server, node);
if (!errorFlag) {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: `control-device: ${RED._('control-device.message.node_execution_failed')}` });
}
}
} catch (error) {
node.log(`control node error => ${error}`);
}
});
}
async function generateState(params, serverId) {
const stateVersion = params.v2Data ? 2 : 1;
const ihostVersion = gatewayInfoMap.get(serverId)?.isV2 ? 2 : 1;
let versionStateData = { state: null, capabilities: null, ihost: null };
console.log('stateVersion-----', stateVersion, 'ihostVersion-----', ihostVersion);
if (stateVersion === 1 && ihostVersion === 1) {
versionStateData = frontEndDataToV2StateData(JSON.parse(params.list));
versionStateData = v2StateDataToV1StateData(versionStateData);
}
if (stateVersion === 1 && ihostVersion === 2) {
versionStateData = frontEndDataToV2StateData(JSON.parse(params.list));
}
if (stateVersion === 2 && ihostVersion === 1) {
versionStateData = v2StateDataToV1StateData(JSON.parse(params.v2Data));
}
if (stateVersion === 2 && ihostVersion === 2) {
versionStateData = JSON.parse(params.v2Data);
}
versionStateData = await v2StateDataToRealStateData(versionStateData, params.device, serverId);
return versionStateData;
}
// 逐次发送 state(Send one after another)
async function singleSendState(data, node) {
for (const key in data.state) {
const sendData = _.pick(data, ['id', 'deviceId', 'state']);
sendData.state = JSON.stringify(_.pick(data.state, key));
if (key === 'toggle') {
if (data.state[key] === 'allKeepStateError') {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: `control-device: ${RED._('control-device.message.node_execution_failed')}` });
continue;
}
}
// 防止并发设备响应不了(Prevent concurrent devices from failing to respond)
await sleepMillisecond(50);
commonAxiosRequest(sendData, API_URL_CONTROL_DEVICE, node);
}
}
// 逐次发送 capability(Send one after another)
async function singleSendCapability(data, node) {
for (const capability of data.capabilities) {
const sendData = _.pick(data, ['id', 'deviceId', 'capabilities']);
sendData.capabilities = JSON.stringify([capability]);
await sleepMillisecond(50);
commonAxiosRequest(sendData, API_URL_CONTROL_CAPABILITIES_DEVICE, node);
}
}
/** check ihost params data */
function controlIhost(ihostParams, id, myNode) {
// {"type":"ihost","deviceId":"ihost","ihostParams":{"ModeType":"alarmBell","securityMode":"",
// "systemVolume":"","systemVolumeValue":"","alarmSound":"alert3","alarmVolume":"56",
// "armedTime":"customize","minute":"15","second":"18"}}
let checkFlag = true;
const ihostData = JSON.parse(JSON.stringify(ihostParams));
if (!ihostData.ModeType) {
return (checkFlag = false);
}
/** control security mode */
if (ihostData.ModeType === 'securityMode') {
if (!ihostData.securityMode) {
return (checkFlag = false);
}
/** assamble data */
if (ihostData.securityMode === 'disarmed') {
commonAxiosRequest({ id }, API_URL_ONE_CLICK_DISARMING, myNode);
} else {
commonAxiosRequest({ id, sid: ihostData.securityMode }, API_URL_ENABLE_SECURITY_MODE, myNode);
}
return checkFlag;
}
/** control system volume*/
if (ihostData.ModeType === 'systemVloume') {
if (!ihostData.systemVolume) {
return (checkFlag = false);
}
/** gateway mute */
if (ihostData.systemVolume === 'disabled') {
commonAxiosRequest({ id }, API_URL_SET_GATEWAY_MUTE, myNode);
}
/** cancel gateway mute */
if (ihostData.systemVolume === 'enable') {
commonAxiosRequest({ id }, API_URL_CANCEL_GATEWAY_MUTE, myNode);
/** system volume */
if (typeof ihostData.systemVolumeValue === 'number') {
commonAxiosRequest({ id, volume: ihostData.systemVolumeValue }, API_URL_SET_SYSTEM_VOLUME, myNode);
}
}
if (ihostData.systemVolume === 'mute') {
commonAxiosRequest({ id }, API_URL_CANCEL_GATEWAY_ALARM, myNode);
}
return checkFlag;
}
/** control alarm bell */
if (ihostData.ModeType === 'alarmBell') {
if (!ihostData.alarmSound || typeof ihostData.alarmVolume !== 'number' || !ihostData.armedTime) {
return (checkFlag = false);
}
let time = null;
if (ihostData.armedTime === 'keepRinging') {
time = -1;
}else{
if (ihostData.minute || ihostData.second) {
time = Number(ihostData.minute) * 60 + Number(ihostData.second);
}
if (time == 0) {
myNode.status({ text: 'control-device.message.alarm_cant_be_zero' });
return;
}
}
const params = {
type: 'play_sound',
sound: {
name: ihostData.alarmSound,
volume: Number(ihostData.alarmVolume),
countdown: time,
},
};
commonAxiosRequest({ id, params }, API_URL_HARDWARE_SPEAKER, myNode);
return checkFlag;
}
return checkFlag;
}
function commonAxiosRequest(params, url, myNode) {
myNode.log('commonAxiosRequest data :' + JSON.stringify(params));
axios
.post(`http://127.0.0.1:1880${url}`, params)
.then((res) => {
// Add status
if (res.data.error === 501) {
myNode.status({ fill: 'red', shape: 'ring', text: 'control-device.message.connect_fail' });
return;
}
if (res.data.error === 0) {
myNode.status({ text: '' });
} else {
RED.comms.publish(EVENT_NODE_RED_ERROR, { msg: `control-device: ${RED._('control-device.message.node_execution_failed')}` });
}
myNode.log('control device response ===> ' + JSON.stringify(res.data));
let capabilityObj = {};
if (!_.isEmpty(params.state)) {
const state = JSON.parse(params.state);
capabilityObj = { capability: Object.keys(state)[0] };
}
if (!_.isEmpty(params.capabilities)) {
const capabilities = JSON.parse(params.capabilities);
capabilityObj = { capability: capabilities[0].capability };
}
myNode.send({ payload: _.merge(capabilityObj, res.data) });
})
.catch((error) => {
myNode.error(error);
});
}
RED.nodes.registerType('control-device', ControlDevicesNode);
};