iobroker.sensate
Version:
Connector to the Sensate Platform
424 lines (384 loc) • 11 kB
JavaScript
"use strict";
/*
* Created with @iobroker/create-adapter v1.23.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require("@iobroker/adapter-core");
const request = require('request');
class Sensate extends utils.Adapter {
/**
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: "sensate",
});
this.on("ready", this.onReady.bind(this));
this.on("objectChange", this.onObjectChange.bind(this));
this.on("stateChange", this.onStateChange.bind(this));
// this.on("message", this.onMessage.bind(this));
this.on("unload", this.onUnload.bind(this));
this.loopVar = null;
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Initialize your adapter here
// The adapters config (in the instance object everything under the attribute "native") is accessible via
// this.config:
const self = this;
if(this.config.accessToken==null || this.config.listKey==null)
{
self.log.error("Please provide an accessToken and listKey!");
return;
}
else
{
self.getDataList(self.config.accessToken, self.config.listKey, self.config.tempUnit)
this.loopVar = setInterval(function() {
self.getDataList(self.config.accessToken, self.config.listKey, self.config.tempUnit)
}, 30*1000);
}
// /*
// For every state in the system there has to be also an object of type state
// Here a simple template for a boolean variable named "testVariable"
// Because every adapter instance uses its own unique namespace variable names can't collide with other adapters variables
// */
// await this.setObjectAsync("testVariable", {
// type: "state",
// common: {
// name: "testVariable",
// type: "boolean",
// role: "indicator",
// read: true,
// write: true,
// },
// native: {},
// });
//
// // in this template all states changes inside the adapters namespace are subscribed
// this.subscribeStates("*");
//
// /*
// setState examples
// you will notice that each setState will cause the stateChange event to fire (because of above subscribeStates cmd)
// */
// // the variable testVariable is set to true as command (ack=false)
// await this.setStateAsync("testVariable", true);
//
// // same thing, but the value is flagged "ack"
// // ack should be always set to true if the value is received from or acknowledged from the target system
// await this.setStateAsync("testVariable", { val: true, ack: true });
//
// // same thing, but the state is deleted after 30s (getState will return null afterwards)
// await this.setStateAsync("testVariable", { val: true, ack: true, expire: 30 });
//
// // examples for the checkPassword/checkGroup functions
// let result = await this.checkPasswordAsync("admin", "iobroker");
// this.log.info("check user admin pw iobroker: " + result);
//
// result = await this.checkGroupAsync("admin", "admin");
// this.log.info("check group user admin group admin: " + result);
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
*/
onUnload(callback) {
try {
this.log.info("cleaned everything up...");
if(this.loopVar!=null)
clearInterval(this.loopVar);
callback();
} catch (e) {
callback();
}
}
/**
* Is called if a subscribed object changes
* @param {string} id
* @param {ioBroker.Object | null | undefined} obj
*/
onObjectChange(id, obj) {
if (obj) {
// The object was changed
this.log.info(`object ${id} changed: ${JSON.stringify(obj)}`);
} else {
// The object was deleted
this.log.info(`object ${id} deleted`);
}
}
/**
* Is called if a subscribed state changes
* @param {string} id
* @param {ioBroker.State | null | undefined} state
*/
onStateChange(id, state) {
if (state) {
// The state was changed
this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
} else {
// The state was deleted
this.log.info(`state ${id} deleted`);
}
}
/**
* This function is called periodically (every 30 sec) to get sensor updates from the Sensate Api
* Reference: https://api.sensate.io
* @param {string} accessToken
* @param {string} listKey
* @param {string} tempUnit
*/
getDataList(accessToken, listKey, tempUnit) {
const self = this;
this.log.debug('Refresh Data List...');
accessToken = escape(accessToken);
listKey = escape(listKey);
var url = 'https://api.sensate.io/v1/data/live/list?accessToken='+accessToken+'&listKey='+listKey;
if(tempUnit!=null)
url = url + '&tempUnit='+tempUnit;
request(
{
url: url,
json: true,
time: true,
timeout: 5000
},
(error, response, content) => {
if (response) {
if(response.statusCode==401)
{
self.log.error("Invalid Sensor API Key and/or ListKey");
return;
}
else if(response.statusCode==200)
{
for (var key in content) {
if (content.hasOwnProperty(key)) {
var group = "Unknown";
if(content[key].category!=null)
group = content[key].category;
self.setObjectNotExists(group, {
type: 'device',
common: {
name: group
},
native: {}
});
const channel = group+"."+key;
self.setObjectNotExists(channel, {
type: 'channel',
common: {
name: group+':'+content[key].name
},
native: {}
});
self.setObjectNotExists(channel + '.id', {
type: 'state',
common: {
name: group+':'+content[key].name+':id',
type: 'string',
role: 'text'
},
native: {}
});
this.setState(channel + '.id', {val: key, ack: true});
self.setObjectNotExists(channel + '.shortName', {
type: 'state',
common: {
name: group+':'+content[key].name+':shortName',
type: 'string',
role: 'text'
},
native: {}
});
this.setState(channel + '.shortName', {val: content[key].shortName, ack: true});
self.setObjectNotExists(channel + '.name', {
type: 'state',
common: {
name: group+':'+content[key].name+':name',
type: 'string',
role: 'text'
},
native: {}
});
this.setState(channel + '.name', {val: content[key].name, ack: true});
var unit = '?';
var role = 'value'
switch(content[key].sensorType) {
case 'TEMPERATURE':
role = 'value.temperature';
break;
case 'RELATIVE_HUMIDITY':
role = 'value.humidity';
break;
case 'PRESSURE':
role = 'value.pressure';
break;
case 'ALTITUDE':
case 'LUMINOUS_FLUX':
case 'PH':
case 'AIRQUALITY':
case 'RAW':
default:
break;
}
switch(content[key].dataUnit)
{
case 'AMPERE':
unit = "A";
break;
case 'AMPEREHOURS':
unit = "Ah";
break;
case 'BAR':
unit = "bar";
break;
case 'CELSIUS':
unit = "°C";
role = 'value.temperature';
break;
case 'FAHRENHEIT':
unit = "°F";
role = 'value.temperature';
break;
case 'HEKTOPASCAL':
unit = "hPa";
break;
case 'KELVIN':
unit = "K";
role = 'value.temperature';
break;
case 'KOHM':
unit = "kΩ";
break;
case 'LUMEN':
unit = "lum";
break;
case 'LUX':
unit = "lux";
break;
case 'METER':
unit = "m";
break;
case 'MILLIAMPERE':
unit = "mA";
break;
case 'MILLIAMPEREHOURS':
unit = "mAh";
break;
case 'MILLIVOLT':
unit = "mV";
break;
case 'NONE':
unit = "";
break;
case 'OHM':
unit = "Ω";
break;
case 'PASCAL':
unit = "Pa";
break;
case 'PERCENT':
unit = "%";
break;
case 'PPM':
unit = "ppm";
break;
case 'UNKNOWN':
unit = "?";
break;
case 'VOLT':
unit = "V";
break;
case 'WATT':
unit = "W";
break;
case 'WATTHOURS':
unit = "Wh";
break;
default:
break;
}
self.setObject(channel + '.value', {
type: 'state',
common: {
name: group+':'+content[key].name+':value',
type: 'number',
role: role,
unit: unit,
read: true,
write: false
},
native: {}
});
self.setState(channel + '.value', {val: content[key].lastData.value, ack: true});
self.setObjectNotExists(channel + '.upToDate', {
type: 'state',
common: {
name: group+':'+content[key].name+':upToDate',
type: 'boolean',
role: 'value',
read: true,
write: false
},
native: {}
});
self.setState(channel + '.upToDate', {val: content[key].upToDate, ack: true});
self.setObjectNotExists(channel + '.dateTime', {
type: 'state',
common: {
name: group+':'+content[key].name+':dateTime',
type: 'string',
role: 'date',
read: true,
write: false
},
native: {}
});
self.setState(channel + '.dateTime', {val: Date.parse(content[key].lastData.dateTime), ack: true});
}
}
self.setObjectNotExists('responseTime', {
type: 'state',
common: {
name: 'Service response time',
type: 'number',
role: 'value',
unit: 'ms',
read: true,
write: false
},
native: {}
});
self.setState('responseTime', {val: parseInt(response.timingPhases.total), ack: true});
}
else if(response.statusCode==304) {
self.log.info("No Sensor Update required");
}
else
{
self.log.warn("Unexpected Return Status: "+response.statusCode);
return;
}
} else if (error) {
self.log.info(error);
}
}
);
}
}
// @ts-ignore parent is a valid property on module
if (module.parent) {
// Export the constructor in compact mode
/**
* @param {Partial<ioBroker.AdapterOptions>} [options={}]
*/
module.exports = (options) => new Sensate(options);
} else {
// otherwise start the instance directly
new Sensate();
}