UNPKG

homebridge-orbit-irrigation

Version:

Orbit Irrigation System platform plugin for [Homebridge](https://github.com/nfarina/homebridge).

732 lines (704 loc) 18.3 kB
//Pulbic site https://techsupport.orbitbhyve.com 'use strict' let axios = require('axios') let ws = require('ws') let reconnectingwebsocket = require('reconnecting-websocket') let endpoint = 'https://api.orbitbhyve.com/v1' let WS_endpoint = 'wss://api.orbitbhyve.com/v1/events' let maxPingInterval = 30000 let minPingInterval = 20000 let pingInterval = Math.floor(Math.random() * (maxPingInterval - minPingInterval)) + minPingInterval // Websocket get's timed out after 30s, will set a random value between 20 and 30 class OrbitAPI { constructor(platform, log) { this.log = log this.platform = platform this.wsp = new WebSocketProxy(platform, log) } async getToken(email, password) { // Get token try { this.log.debug('Retrieving API key') let response = await axios({ method: 'post', baseURL: endpoint, url: '/session', headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-app-id': 'Bhyve Dashboard' }, data: { session: { email: email, password: password } }, responseType: 'json' }).catch(err => { this.log.error('Error getting API key %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else { this.log.warn('%s - %s', err.name, err.code) return new Error('no network') } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get token response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving API key \n%s', err) } } async getDevices(token, userId) { // Get the device details try { this.log.debug('Retrieving devices') let response = await axios({ method: 'get', baseURL: endpoint, url: '/devices?user=' + userId, headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, responseType: 'json' }).catch(err => { this.log.error('Error getting devices %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return err } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get devices response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving devices \n%s', err) } } async getDevice(token, device) { // Get the device details try { this.log.debug('Retrieving device') let response = await axios({ method: 'get', baseURL: endpoint, url: '/devices/' + device, headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, responseType: 'json' }).catch(err => { this.log.error('Error getting device %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return err } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get device response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving device \n%s', err) } } async getMeshes(token, meshId) { // Get mesh details try { this.log.debug('Retrieving mesh info') let response = await axios({ method: 'get', baseURL: endpoint, url: '/meshes/' + meshId, headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, responseType: 'json' }).catch(err => { this.log.error('Error getting mesh info %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return 'err' } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get mesh info response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving mesh info \n%s', err) } } async getNetworkTopologies(token, networkTopologyId) { // Get mesh details try { this.log.debug('Retrieving network topology info') let response = await axios({ method: 'get', baseURL: endpoint, url: '/network_topologies/' + networkTopologyId, headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, responseType: 'json' }).catch(err => { this.log.error('Error getting network topologies info %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return err } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get network topology info response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving network topologies info \n%s', err) } } async getDeviceGraph(token, userId) { // Get device graph details try { this.log.debug('Retrieving device graph info') let response = await axios({ method: 'post', baseURL: endpoint, url: '/graph2', headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, data: { query: [ 'devices', { user_id: userId }, 'id', 'name', 'address', 'location_name', 'type', 'hardware_version', 'firmware_version', 'mac_address', 'is_connected', 'mesh_id' ] }, responseType: 'json' }).catch(err => { this.log.error('Error getting graph %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return err } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get device graph response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving graph \n%s', err) } } async getTimerPrograms(token, device) { // Get mesh details try { this.log.debug('Retrieving schedules') let response = await axios({ method: 'get', baseURL: endpoint, url: '/sprinkler_timer_programs', headers: { 'Content-Type': 'application/json', 'User-Agent': `${PluginName}/${PluginVersion}`, 'orbit-api-key': token, 'orbit-app-id': 'Bhyve Dashboard' }, params: { device_id: device.id }, responseType: 'json' }).catch(err => { this.log.error('Error getting scheduled %s', err.message) this.log.debug(JSON.stringify(err, null, 2)) if (err.response) { this.log.warn(JSON.stringify(err.response.data, null, 2)) return err.response.data } else if (err.code) { this.log.warn(err.code) return err } else { this.log.warn('Error %s', err.name) return err } }) if (response.status == 200) { if (this.platform.showAPIMessages) { this.log.debug('get timer programs response', JSON.stringify(response.data, null, 2)) } return response.data } } catch (err) { this.log.error('Error retrieving schedules \n%s', err) } } startZone(token, device, station, runTime) { try { this.log.debug('startZone', device.id, station, runTime) this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: 'manual', device_id: device.id, stations: [ { station: station, run_time: runTime } ] }) ) .catch(err => { this.log.error('Error starting zone \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } startSchedule(token, device, program) { try { this.log.debug('startZone', device.id, program) this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: 'manual', device_id: device.id, program: program }) ) .catch(err => { this.log.error('Error starting zone \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } stopZone(token, device) { try { this.log.debug('stopZone') this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: 'manual', device_id: device.id, timestamp: new Date().toISOString() }) ) .catch(err => { this.log.error('Error starting zone \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } startMultipleZone(token, device, runTime) { try { let body = [] device.zones.forEach(zone => { zone.enabled = true // need orbit version of enabled if (zone.enabled) { body.push({ station: zone.station, run_time: runTime }) } }) this.log.debug('multiple zone run data', JSON.stringify(body, null, 2)) this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: 'manual', device_id: device.id, stations: body }) ) .catch(err => { this.log.error('Error starting multiple zone \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } stopDevice(token, device) { try { this.log.debug('stopZone') this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: 'manual', device_id: device.id, //stations: [], timestamp: new Date().toISOString() }) ) .catch(err => { this.log.error('Error stopping device \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } deviceStandby(token, device, mode) { try { this.log.debug('standby') this.wsp .connect(token, device) .then(ws => ws.send({ event: 'change_mode', mode: mode, device_id: device.id, timestamp: new Date().toISOString() }) ) .catch(err => { this.log.error('Error setting standby \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } openConnection(token, device) { try { this.log.debug('Opening WebSocket Connection') this.wsp .connect(token, device) //.then(ws => // ws.send({ // event: 'app_connection', // orbit_session_token: token // }) //) .catch(err => { this.log.error('Error opening connection \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } onMessage(token, device, listener) { try { this.log.debug('Adding Event Listener for %s', device.name) this.wsp .connect(token, device) .then(ws => ws.addEventListener('message', msg => { listener(msg.data, device.id) }) ) .catch(err => { this.log.error('Error configuring listener \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } sync(token, device) { try { this.log.debug('Syncing device %s info', device.name) this.wsp .connect(token, device) .then(ws => ws.send({ event: 'sync', device_id: device.id }) ) .catch(err => { this.log.error('Error syncing data \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } identify(token, device) { try { this.log.debug('Identify device %s info', device.name) this.wsp .connect(token, device) .then(ws => ws.send({ event: 'fs_identify', device_id: device.id, identify_time_ms: 5000 }) ) .catch(err => { this.log.error('Error identify data \n%s', err) }) } catch (err) { this.log.error('something went wrong \n%s', err) } } } module.exports = OrbitAPI class WebSocketProxy { constructor(platform, log) { this.rws = null this.ping = null this.log = log this.platform = platform } async connect(token, device) { try { if (this.rws) { this.log.debug('ready state', this.rws.readyState) switch (this.rws.readyState) { case ws.CONNECTING: return this.rws case ws.OPEN: return this.rws case ws.CLOSING: this.rws.reconnect() return this.rws case ws.CLOSED: this.rws.reconnect() return this.rws } } return new Promise((resolve, reject) => { try { let options = { WebSocket: ws, maxReconnectionDelay: 10000, //64000 minReconnectionDelay: Math.floor(1000 + Math.random() * 4000), reconnectionDelayGrowFactor: 1.3, minUptime: 5000, connectionTimeout: 4000, //10000 maxRetries: Infinity, maxEnqueuedMessages: 1, //Infinity startClosed: false, debug: false } this.rws = new reconnectingwebsocket(WS_endpoint, [], options) // Intercept send events for logging let origSend = this.rws.send.bind(this.rws) this.rws.send = (data, options, callback) => { switch (this.rws.readyState) { case ws.CONNECTING: setTimeout(() => { //this.log.debug(data) this.rws.send(data) }, 1000); break case ws.OPEN: if (typeof data === 'object') { data = JSON.stringify(data, null, 2) } if (this.platform.showOutgoingMessages) { //this.log.debug(JSON.parse(data).event) if (JSON.parse(data).event != 'ping') { this.log.debug('sending outgoing message %s', data) } } origSend(data, options, callback) break case ws.CLOSING: break case ws.CLOSED: this.log.info('connection not opened') break } } // Ping this.ping = setInterval(() => { switch (this.rws.readyState) { case ws.CONNECTING: clearInterval(this.ping) break case ws.OPEN: this.rws.send({event: 'ping'}) break case ws.CLOSING: break case ws.CLOSED: clearInterval(this.ping) break } }, pingInterval) this.rws.onopen = event => { try { this.rws.send({ event: 'app_connection', orbit_session_token: token, }) this.log.debug( 'connection open', JSON.stringify( { type: event.type }, null, 2 ) ) this.log.info('WebSocket opened') resolve(this.rws) } catch (err) { this.log.error('Error with open event \n%s', err) } } this.rws.onclose = event => { try { this.log.debug( 'connection closed', JSON.stringify( { type: event.type, wasClean: event.wasClean, code: event.code, reason: event.reason }, null, 2 ) ) if (event.code == 1000 || event.code == 1005 || event.code == 1006) { this.log.info('WebSocket closed') this.log.warn('Devices will not sync until WebSocket connection is restored.') } } catch (err) { this.log.error('Error with close event \n%s', err) } } this.rws.onmessage = msg => { try { if (this.platform.showIncomingMessages) { this.log.debug('incoming message', JSON.parse(msg.data)) } } catch { this.log.error('Error with ,essage event \n%s', err) } } this.rws.onerror = event => { try { this.log.debug('ready state', this.rws.readyState) this.log.debug('connection error', event.error) switch (this.rws.readyState) { case ws.CONNECTING: this.log.debug('WebSocket connecting') break case ws.OPEN: this.log.debug('WebSocket opened') break case ws.CLOSING: this.log.debug('WebSocket closing') break case ws.CLOSED: this.log.debug('WebSocket closed') break } reject(event) } catch (err) { this.log.error('Error with error event \n%s', err) } } } catch (error) { this.log.error('caught', error.message) if (this.rws) { //check if connected this.log.warn('error, closing connection') this.rws.close((1000), ('Session terminated by client')) //this.rws.close() try { clearInterval(this.ping) this.rws = null } catch (err) { this.log.error('Error closing connection \n%s', err) } } } }) } catch (err) { this.log.error('Opps', err) } } }