class/sensor-handler.js
const BrowserSensorWatcher = require('./browser-sensor-watcher');
let rawSensorWatcher;
/**
* Main class for handling sensors.
* This class allows get, watch, etc. of sensors.
*/
class SensorHandler {
constructor(env='browser'){
//Override with updateSensorListeners
if(env === 'browser'){
rawSensorWatcher = this.rawSensorWatcher = new BrowserSensorWatcher();
}
//SensorState mapped by sensor name
this.sensors = {
//
};
//Sensor counts stored accross start and stop watching to make sure async starting and stopping doesn't cause bugs.
this.sensorCreationCounts = {
//
};
}
/**
* Get sensor once. Should stop watching sensor if newly made.
*
* @param {string} sensorName
*/
get(sensorName){
let self = this;
return new Promise((resolve, reject)=>{
let sensorCreationCount = self._getSensorCreationCount() + 1;
let handle = (sensorState)=>{
//resolve(sensorState);
//Only stop if no new watches happened.
if(!(self._getSensorCreationCount() > sensorCreationCount)){
self.stop(sensorName)
.then(()=>{
resolve(sensorState);
});
}else{
resolve(sensorState);
}
};
self.watch(sensorName, {
events: {
data: handle
}
});
});
}
/**
* Watch sensor from RawSensorWatcher. Ignores duplicates.
* options: {
* events: {
* data: function(data){}
* }
* }
*
* @param {string} sensorName
* @param {object} options optional options
* @return {promise} promise resolving SensorState
*/
watch(sensorName, options={}){
const self = this;
//data required(use console log if nothing)
if(!options.events){options.events = {};}
if(!options.events.data){options.events.data = this._getDefaultDataEvent(sensorName);}
return new Promise((resolve, reject)=>{
const onResolve = (sensorState)=>{
self._incSensorCreationCount(sensorName);
resolve(sensorState);
};
if(self.sensors[sensorName]){
return onResolve(self.sensors[sensorName]);
}
if(rawSensorWatcher.sensorHandles[sensorName]){
rawSensorWatcher.sensorHandles[sensorName].start(options)
.then(function(startReturnData){
let sensorState = self._setSensorState(sensorName, {
options: options,
startReturnData: startReturnData,
stop: rawSensorWatcher.sensorHandles[sensorName].stop//Set to state because may implement multiple watching later.
});
return onResolve(sensorState);
})
.catch(reject);
}else{
return reject(new Error('No raw sensor: ' + sensorName));
}
});
}
/**
* Watches all available sensors.
*
* @return {promise} Resolves array of SensorStates. However allows for failing returning null.
*/
watchAll(){
let possibleSensors = rawSensorWatcher.sensorHandles;
let promises = [];
for(let key in possibleSensors){
if(key === 'test'){continue;}
let p = new Promise((resolve, reject)=>{
this.watch(key)
.then(resolve)
.catch(()=>{
resolve(null);
});
});
promises.push(p);
}
return Promise.all(promises);
}
/**
* Stops watching sensor.
*
* @param {string} sensorName
* @return {promise}
*/
stop(sensorName){
let self = this;
return new Promise((resolve, reject)=>{
let sensor = self.sensors[sensorName];
if(!sensor){
reject(new Error('No sensor: ' + sensorName));
}
if(sensor.stop){
sensor.stop(...sensor.startReturnData)
.then(()=>{
delete self.sensors[sensorName];
console.log('sensor stop success: ' + sensorName);
resolve(sensor);
})
.catch(reject);
}else{
reject(new Error('No sensor stop handle: ' + sensorName));
}
});
}
/**
* Stops watching all.
*
* @return {promise}
*/
stopAll(){
let promises = [];
for(let key in this.sensors){
promises.push(this.stop(key));
}
return Promise.all(promises);
}
/**
* Adds sensor event. Multiple at same time ok.
*
* @param {string} sensorName
* @param {string} eventName
* @param {function} handle
* @return {boolean} success/failure
*/
addSensorEvent(sensorName, eventName, handle){
//Require started
let sensor = this.sensors[sensorName];
if(!sensor){
return false;
}
let events = sensor.events;
if(!events[eventName]){
events[eventName] = [];
}
events[eventName].push(handle);
return true;
}
/**
* Removes sensor event. Only removes handle specified.
*
* @param {string} sensorName
* @param {string} eventName
* @param {function} handle
* @return {boolean} success/failure
*/
removeSensorEvent(sensorName, eventName, handle){
//No sensor or event
let sensor = this.sensors[sensorName];
if(!sensor || !sensor.events[eventName]){
return false;
}
let event = sensor.events[eventName];
let index = event.indexOf(handle);
if(index >= 0){
event.splice(index, 1);
return true;
}else{
return false;
}
}
/**
* Update registered sensor listeners.
* Pass handle with one argument.
* MUST return.
* Dynamic updating during watching may result in bugs.
*
* @param {Function} handle handle taking object map of registered SensorListeners
*/
updateSensorListeners(callback){
this.rawSensorWatcher = callback(this.rawSensorWatcher);
}
/**
* Gets usable sensor names
*
* @return {array} sensor names
*/
getMappedSensorNames(){
return Object.keys(this.rawSensorWatcher.sensorHandles);
}
/**
* Current state of sensor
* @return {SensorState}
*/
_SensorState(){
return {
isSensorState: true,
name: '',
stop: null,//Copied over from RawSensorWatcher object for better management.
startReturnData: null,
options: {},
events: {
data: null
}
};
}
/**
* Sets sensor state. Requires settings.events.
*
* @param {string} sensorName
* @param {object} settings
*/
_setSensorState(sensorName, settings={}){
console.log('_setSensorState: ', sensorName, settings);
let sensorState;
if(this.sensors[sensorName]){
sensorState = this.sensors[sensorName];
}else{
sensorState = this._SensorState();
sensorState.name = sensorName;
this.sensors[sensorName] = sensorState;
let key;
for(key in settings){
sensorState[key] = settings[key];
}
//Events
let options = settings.options;
if(options.events){
for(key in options.events){
this.addSensorEvent(sensorName, key, options.events[key]);
}
}
console.log('added sensor state: ', sensorState);
}
return sensorState;
}
/**
* Gets number of times sensor created
*
* @param {String} sensorName
* @return {Number} sensor creation count
*/
_getSensorCreationCount(sensorName){
return (!!this.sensorCreationCounts[sensorName]) ? this.sensorCreationCounts[sensorName] : 0;
}
/**
* Increment sensor creation count
*
* @param {String} sensorName
* @return {Number} sensor creation count
*/
_incSensorCreationCount(sensorName){
if(!Number.isInteger(this.sensorCreationCounts[sensorName])){
this.sensorCreationCounts[sensorName] = 0;
}
return ++this.sensorCreationCounts[sensorName];
}
/**
* Data event used when no event to handle sensor data is provided.
* Stringifies data and logs.
*
* @param {String} sensorName
* @return {Function}
*/
_getDefaultDataEvent(sensorName){
return (data)=>{
if(data && typeof data === 'object'){
let oldData = data;
let cache = [];
data = JSON.stringify(data, function(key, val){
if(val && typeof val === 'object'){
//Ignore duplicates
if(cache.indexOf(val) >= 0){
return;
}
cache.push(val);
}
return val;
});
cache = null;
}
console.log('sensor: ' + sensorName, String(new Date()), data);
}
}
}
module.exports = SensorHandler;