UNPKG

@theopenweb/js-sensor

Version:

JavaScript sensor library for native input sensors.

389 lines (338 loc) 9.82 kB
const BrowserSensorWatcher = require('./browser-sensor-watcher'); /** * @type {BrowserSensorWatcher} */ let rawSensorWatcher; /** * @typedef {import('../types/index').SensorState} SensorState * @typedef {import('../types/index').SensorEvents} SensorEvents * @typedef {import('../types/index').WatchOptions} WatchOptions * @typedef {import('../types/index').SensorStateSettings} SensorStateSettings */ /** * 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 * @type {Object<string, SensorState>} */ this.sensors = { // }; /** * Sensor counts stored accross start and stop watching to make sure async starting and stopping doesn't cause bugs. * @type {Object<string, number>} */ this.sensorCreationCounts = { // }; } /** * Get sensor once. Should stop watching sensor if newly made. * * @param {string} sensorName * @return {Promise<SensorState>} */ get(sensorName) { let self = this; return new Promise((resolve) => { let sensorCreationCount = self._getSensorCreationCount() + 1; /** * @param {SensorState} sensorState */ 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 {Partial<WatchOptions>} options optional options * @return {Promise<SensorState>} Returns: 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) => { /** * @param {SensorState} sensorState */ 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<SensorState[]>} 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) => { this.watch(key) .then(resolve) .catch(() => { resolve(null); }); }); promises.push(p); } return Promise.all(promises); } /** * Stops watching sensor. * * @param {string} sensorName * @return {Promise<SensorState>} */ 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. */ stopAll() { /** * @type {Promise<SensorState>[]} */ const 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} callback handle taking object map of registered SensorListeners */ updateSensorListeners(callback) { this.rawSensorWatcher = callback(this.rawSensorWatcher); } /** * Gets usable sensor names * * Returns: 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 {SensorStateSettings} settings */ _setSensorState(sensorName, settings = {}) { console.log('_setSensorState: ', sensorName, settings); /** * @type {SensorState|undefined} */ let sensorState; if (this.sensors[sensorName]) { sensorState = this.sensors[sensorName]; } else { sensorState = this._SensorState(); sensorState.name = sensorName; this.sensors[sensorName] = sensorState; for (let key in settings) { sensorState[key] = settings[key]; } //Events let options = settings.options; if (options.events) { for (let 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 */ _getDefaultDataEvent(sensorName) { /** * @param {any} data */ return (data) => { if (data && typeof data === 'object') { // let oldData = data; /** * @type {any[]} */ 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 = []; // deref } console.log('sensor: ' + sensorName, String(new Date()), data); } } } module.exports = SensorHandler;