UNPKG

iobroker.myq

Version:
476 lines (418 loc) 11.7 kB
'use strict'; const utils = require('@iobroker/adapter-core'); // Get common adapter utils const ioBLib = require('@strathcole/iob-lib').ioBLib; const { myQApi } = require("@hjdhjd/myq"); let MyQ; const adapterName = require('./package.json').name.split('.').pop(); const deviceAttributes = { online: { sect: 'info', name: 'Device is online', type: 'boolean', role: 'indicator' }, door_state: { sect: 'states', name: 'Door state', type: 'string', role: 'value.door' }, light_state: { sect: 'states', name: 'Light state', type: 'boolean', role: 'indicator.light' }, is_unattended_open_allowed: { sect: 'info', name: 'Allow unattended open', type: 'boolean', role: 'indicator' }, is_unattended_close_allowed: { sect: 'info', name: 'Allow unattended close', type: 'boolean', role: 'indicator' }, name: { sect: 'info', name: 'Device Name', type: 'string', role: 'text' }, lear: { sect: 'states', name: 'Learn mode', type: 'boolean', role: 'indicator' }, physical_devices: { sect: 'info', name: 'Connected devices', type: 'number', role: 'value.info' }, firmware_version: { sect: 'info', name: 'Firmware version', type: 'string', role: 'text' }, updated_date: { sect: 'info', name: 'Last update', type: 'number', role: 'date' }, last_status: { sect: 'info', name: 'Last status', type: 'number', role: 'date' }, last_update: { sect: 'info', name: 'Last update', type: 'number', role: 'date' }, passthrough_interval: { sect: 'info', name: 'Passthrough interval', type: 'string', role: 'text' }, invalid_shutout_period: { sect: 'info', name: 'Interval', type: 'string', role: 'text' }, invalid_credential_window: { sect: 'info', name: 'Interval', type: 'string', role: 'text' }, door_ajar_interval: { sect: 'info', name: 'Interval', type: 'string', role: 'text' }, close: { sect: 'info', name: 'Close uri', type: 'string', role: 'text' }, open: { sect: 'info', name: 'Open uri', type: 'string', role: 'text' }, on: { sect: 'info', name: 'Turn on uri', type: 'string', role: 'text' }, off: { sect: 'info', name: 'Shut off uri', type: 'string', role: 'text' } }; let adapter; var deviceUsername; var devicePassword; let polling; let pollingTime; function startAdapter(options) { options = options || {}; Object.assign(options, { name: 'myq' }); adapter = new utils.Adapter(options); ioBLib.init(adapter); adapter.on('unload', function(callback) { if(polling) { clearTimeout(polling); } adapter.setState('info.connection', false, true); callback(); }); adapter.on('stateChange', function(id, state) { // Warning, state can be null if it was deleted try { adapter.log.debug('stateChange ' + id + ' ' + JSON.stringify(state)); if(!id) { return; } if(state && id.substr(0, adapter.namespace.length + 1) !== adapter.namespace + '.') { return; } id = id.substring(adapter.namespace.length + 1); // remove instance name and id if(state && state.ack) { return; } state = state.val; adapter.log.debug("id=" + id); if('undefined' !== typeof state && null !== state) { processStateChange(id, state); } } catch(e) { adapter.log.info("Error processing stateChange: " + e); } }); adapter.on('ready', function() { if(!adapter.config.username) { adapter.log.warn('[START] Username not set'); } else if(!adapter.config.password) { adapter.log.warn('[START] Password not set'); } else { adapter.log.info('[START] Starting myq adapter'); adapter.getForeignObject('system.config', (err, obj) => { if (obj && obj.native && obj.native.secret) { //noinspection JSUnresolvedVariable adapter.config.password = ioBLib.decrypt(obj.native.secret, adapter.config.password); } else { //noinspection JSUnresolvedVariable adapter.config.password = ioBLib.decrypt('Zgfr56gFe87jJOM', adapter.config.password); } main(); }); } }); return adapter; } function doRefreshDevices(callback) { MyQ.refreshDevices().then(function(result) { adapter.log.debug('Result of refresh devices is ' + result); if(result && MyQ.accounts.length) { adapter.setState('info.connection', true, true); callback(); } else { adapter.setState('info.connection', false, true); adapter.log.error('Could not get device info.'); if(!MyQ.accounts.length) { adapter.log.error('It seems that login failed. Either the server was unreachable or you used wrong credential. Please activate debug log to check.'); } setTimeout(function() { adapter.log.info('Retrying refresh devices (30 seconds past)'); doRefreshDevices(callback); }, 30000); } }).catch(function(error) { console.error(error); }); } function main() { deviceUsername = adapter.config.username; devicePassword = adapter.config.password; pollingTime = adapter.config.pollinterval || 10000; if(pollingTime < 5000) { pollingTime = 5000; } adapter.log.info('[INFO] Configured polling interval: ' + pollingTime); adapter.log.debug('[START] Started Adapter'); adapter.subscribeStates('*'); MyQ = new myQApi(deviceUsername, devicePassword, adapter.log); doRefreshDevices(function() { pollStates(); }); } function pollStates() { adapter.log.debug('Starting state polling'); if(polling) { clearTimeout(polling); polling = null; } ioBLib.setOrUpdateObject('devices', 'Devices', 'channel', function() { doRefreshDevices(function() { processDeviceStates(MyQ.devices); polling = setTimeout(function() { pollStates(); }, pollingTime); }); }); } function processDeviceStates(devices) { for(let i = 0; i < devices.length; i++) { processDeviceState(devices[i]); } } function getmyqDeviceAttribute(device, key) { if(!device || !device.state) { return null; } if(!device.state[key]) { return null; } return { value: device.state[key], updated: device.state['last_update'] }; } function processDeviceState(device) { if(!device.serial_number) { adapter.log.warn('Serial number of device missing.'); adapter.log.debug(JSON.stringify(device)); return; } let objId = 'devices.' + device.serial_number; let objName = getmyqDeviceAttribute(device, 'name'); if(!objName || !objName.value) { objName = { value: device.serial_number }; } ioBLib.setOrUpdateObject(objId, objName.value, 'device', function() { // process attributes if(device.created_date) { //ioBLib.setOrUpdateState(objId + '.info.RegistrationDateTime', 'RegistrationDateTime', (new Date(device.created_date)).getTime(), '', 'number', 'date'); } //ioBLib.setOrUpdateState(objId + '.info.myqDeviceTypeId', 'myq device type', device.myqDeviceTypeId, '', 'string', 'text'); ioBLib.setOrUpdateState(objId + '.info.device_type', 'myq device type', device.device_type, '', 'string', 'text'); ioBLib.setOrUpdateState(objId + '.info.serial_number', 'Serial number', device.serial_number, '', 'string', 'text'); let doorState = getmyqDeviceAttribute(device, 'door_state'); if(null !== doorState) { //ioBLib.setOrUpdateState(objId + '.states.moving', 'Door moving', (doorState.value == '4' || doorState.value == '5' || doorState.value == '8' ? true : false), '', 'boolean', 'indicator.moving'); ioBLib.setOrUpdateState(objId + '.commands.open', 'Open door', false, '', 'boolean', 'button.open'); ioBLib.setOrUpdateState(objId + '.commands.close', 'Close door', false, '', 'boolean', 'button.close'); } else if(null !== getmyqDeviceAttribute(device, 'light_state')) { ioBLib.setOrUpdateState(objId + '.commands.on', 'Switch on', false, '', 'boolean', 'button.on'); ioBLib.setOrUpdateState(objId + '.commands.off', 'Switch off', false, '', 'boolean', 'button.off'); } let attr; let attrValue; for(let attr in device.state) { attrValue = getmyqDeviceAttribute(device, attr); if(null !== attrValue) { let origvalue = attrValue.value; let attrId = attr; attr = { 'name': attr, 'sect': 'info', 'type': typeof origvalue, 'role': 'text', 'states': null }; if(attr['type'] === 'number' && attrValue.value.match(/^[1-9][0-9]*(\.[0-9]+)?$/)) { if(attrValue.value.indexOf('.') > -1) { attrValue.value = parseFloat(attrValue.value); } else { attrValue.value = parseInt(attrValue.value, 10); } attr['role'] = 'value'; } else if(attr['type'] === 'boolean') { attr['role'] = 'indicator'; } if(deviceAttributes[attr.name]) { let tmp = deviceAttributes[attr.name]; attr['type'] = tmp['type']; attr['role'] = tmp['role']; attr['name'] = tmp['name']; } if(!attrValue.value && attrValue.value !== 0 && attrValue.value !== false) { adapter.log.warn('Value of ' + attrId + ' is now empty, but was ' + JSON.stringify(origvalue)); } if(attr['role'] === 'date') { attrValue.value = (new Date(attrValue.value)).getTime(); } if(!attr['states']) { attr['states'] = null; } // attribute exists ioBLib.setOrUpdateState(objId + '.' + attr['sect'] + '.' + attrId, attr['name'], attrValue.value, '', attr['type'], attr['role'], attr['states']); } } }); } function processStateChange(id, value) { adapter.log.debug('StateChange: ' + JSON.stringify([id, value])); if(id.match(/\.commands\.(open|close)$/)) { let matches = id.match(/^devices\.([^\.]+)\.commands\.(open|close)$/); if(!matches) { adapter.log.warn('Could not process state id ' + id); return; } let deviceId = matches[1]; let cmd = matches[2]; if(!deviceId) { adapter.log.warn('Found no valid device id in state ' + id); return; } let MyQDevice = MyQ.getDevice(deviceId); if(!MyQDevice) { adapter.log.warn('Could not find device ' + deviceId + ' in devices list.'); return; } if(cmd === 'open') { cmd = 'open'; } else { cmd = 'close'; } MyQ.execute(MyQDevice, cmd).then(function(result) { adapter.log.info('Cmd ' + cmd + ' sent to ' + deviceId + ' result is ' + result); }).catch(function(error) { console.error(error); }); adapter.setState(id, false, true); if(polling) { clearTimeout(polling); } polling = setTimeout(function() { pollStates(); }, 2000); } else if(id.match(/\.commands\.(on|off)$/)) { let matches = id.match(/^devices\.([^\.]+)\.commands\.(on|off)$/); if(!matches) { adapter.log.warn('Could not process state id ' + id); return; } let deviceId = matches[1]; let cmd = matches[2]; if(!deviceId) { adapter.log.warn('Found no valid device id in state ' + id); return; } if(cmd === 'on') { cmd = 'on'; } else { cmd = 'off'; } let MyQDevice = MyQ.getDevice(deviceId); if(!MyQDevice) { adapter.log.warn('Could not find device ' + deviceId + ' in devices list.'); return; } MyQ.executeLightState(MyQDevice, cmd).then(function(result) { if(!result) { adapter.log.warn('Failed switch ' + cmd + ' lamp ' + deviceId + ': ' + JSON.stringify(error)); } }).catch(function(error) { adapter.log.warn('Failed switch ' + cmd + ' lamp ' + deviceId + ': ' + JSON.stringify(error)); }); adapter.setState(id, false, true); if(polling) { clearTimeout(polling); } polling = setTimeout(function() { pollStates(); }, 2000); } else { adapter.log.warn('Unknown id for StateChange with ack=false: ' + id); } return; } // If started as allInOne/compact mode => return function to create instance if(module && module.parent) { module.exports = startAdapter; } else { // or start the instance directly startAdapter(); } // endElse