Thing.js
-Thing.js is meant to be an extremely light weight IoT-framework. Loosely inspired by W3C web of things framework, a thing is an object that has:
-
-- Metadata
-- Properties
-- Actions
-- Events
-
-Thing.js exports a single class 'Thing,' which is an extension of the Node.js EventEmitter Class, and basic methods for:
+Thing.js exports a single class 'Thing,' which is an extension of the Node.js EventEmitter Class and basic methods for:
- Updating properties
-- Calling actions
-- Emiting events
-- Setting up event listeners
+- Calling methods
+- Emiting events for either of the above.
Full documentation available here.
-For example of how this can be used in an IoT stack, checkout Grow.js and Grow-IoT.
+For example of how this can be used in an IoT stack, checkout Grow.js which is used to connect devices (or purely software things) to a Grow-IoT instance.
Install
npm install Thing.js
-Example
-var Thing = require('Thing.js');
+Usage
+const Thing = require('Thing.js');
-var Light = new Thing({
+const Light = new Thing({
name: 'Light',
desription: 'An LED light with a basic on/off api.',
username: 'jakehart',
+ // These are setable and getable by the api.
properties: {
- state: 'off',
- lightconditions: function () {
- // Properties can be updated by the API.
- // Note: property functions should return a value.
- // When using actual hardware you might use this function to get the
- // state of a pin.
- return null;
- }
+ state: null,
+ },
+ start: function () {
+ console.log('Thing initialized, this code runs first');
},
- actions: {
- turn_light_on: {
- name: 'On', // Display name for the action
- description: 'Turns the light on.', // Optional description
- schedule: 'at 9:00am', // Optional scheduling using later.js
- function: function () {
- // The implementation of the action.
- console.log('light on');
- Light.set('state', 'on');
- }
- },
- turn_light_off: {
- name: 'off',
- schedule: 'at 8:30pm',
- function: function () {
- console.log('light off');
- Light.set('state', 'off');
- }
- },
- light_data: {
- name: 'Log light data',
- type: 'light',
- template: 'sensor',
- schedule: 'every 1 second',
- function: function () {
- console.log("Log light data.")
- }
- }
+ turn_light_on: function () {
+ console.log('light on');
+ Light.set('state', 'on');
},
- events: {
- check_light_data: {
- name: 'Check light data',
- on: 'turn_light_on', // Adds Listener for action event.
- function: function () {
- console.log('this event listener is called when the light is turned on.');
- }
- }
- }
-},
-function start () {
- // Optional callback function.
- return;
+ turn_light_off: function () {
+ console.log('light off');
+ Light.set('state', 'off');
+ }
});
-console.log(Light.get(state));
-// logs 'off'
+Light.on('turn_light_on', function() {
+ console.log('Light turned on.')
+});
Light.call('turn_light_on');
-// logs 'Light on.'
-// logs 'this event listener is called when the light is turned on.'
-
-console.log(Light.get(state));
-// logs 'on'
-Please open issues or PRs with thoughts || suggestions || proposals.
Developing
Code is written in ES6, and compiled using rollup. Full documentation is available here.
npm run build
builds the library.
diff --git a/lib/Thing.js b/lib/Thing.js
index 58bc36c..00b1f7b 100644
--- a/lib/Thing.js
+++ b/lib/Thing.js
@@ -18,79 +18,9 @@ class Thing extends EventEmitter {
_.extend(this, config);
}
- if (!_.isUndefined(this.events)) {
- _.each(this.events, (event, key, list) => {
- if (!_.isUndefined(event.on)) {
- this.on(event.on, () => {
- if (!_.isUndefined(event.rule)) {
- if (event.rule.condition() === true) {
- event.rule.consequence();
- }
- } else {
- event.function();
- }
- });
- }
- });
- }
-
- if (!_.isUndefined(this.properties)) {
- for (var property in this.properties) {
- // If the property is a function we initialize it.
- if (typeof this.properties[property] === 'function') {
- // Note this function should return property value.
- this.properties[property] = this.properties[property]()
- }
- }
- }
-
- // Callback is optional. May be used for a start function.
- if (!_.isUndefined(callback)) {
- callback();
- }
- }
-
- /**
- * Get an action object by key
- * @param {String} ID The key of the action object you want.
- * @returns {Object}
- */
- getAction (ID) {
- let action = {};
- _.each(this.actions, (value, key, list) => {
- if (key === ID) {
- return action = value;
- } else if (this.actions[key].id === ID) {
- return action = value;
- }
- });
-
- if (_.isEmpty(action)) {
- return false;
- } else {
- return action;
- }
- }
-
- /**
- * Get event object by key
- * @param {String} ID The key / id of the event object you want.
- * @returns {Object}
- */
- getEvent (ID) {
- let event = {}
- _.each(this.events, (value, key, list) => {
- if (key === ID) {
- return event = value;
- } else if (this.events[key].id === ID) {
- return event = value;
- }
- });
-
- if (_.isEmpty(event)) {
- return false;
- } else {
- return event;
+ // What would be other useful default methods?
+ if (!_.isUndefined(this.start)) {
+ this.start();
}
}
@@ -100,75 +30,36 @@ class Thing extends EventEmitter {
* @param {String} value The value to update the property to.
* @param {String} key Optional. Use to update the property of an event or action.
*/
- set (property, value, key) {
- if (_.isUndefined(key)) {
- this.properties[property] = value;
- this.emit('property-updated');
- }
- else {
- // what if they both have the same key?
- let action = this.getAction(key);
- let event = this.getEvent(key);
- if (action) {
- action[property] = value;
- } else if (event) {
- event[property] = value;
- }
- this.emit('property-updated');
- }
+ set (key, value) {
+ this.properties[key] = value;
+ this.emit('property-updated', key);
}
/* Get a property by key.
* @param {String} property
* @returns {String} key Optional. Use to get an event or action property.
*/
- get (property, key) {
- if (_.isUndefined(key)) {
- return this.properties[property];
- } else {
- let action = this.getAction(key);
- let event = this.getEvent(key);
- if (action) {
- return action[property];
- }
- if (event) {
- return event[property];
- }
- }
+ get (key) {
+ return this.properties[key];
}
/**
- * Calls a registered action or event function, emits event if the the action has an 'event'
+ * Calls a method, emits event
* property defined.
* @param {String} key The id of the action / event to call.
* @param {Object} options Optional, options to call with the function.
*/
call (key, options) {
try {
- let action = this.getAction(key);
- let event = this.getEvent(key);
-
- if (action) {
- if (!_.isUndefined(options)) {
- var output = action.function(options);
- }
- else {
- var output = action.function();
- }
- this.emit(key);
+ if (!_.isUndefined(options)) {
+ var output = this[key](options);
}
-
- else if (event) {
- if (!_.isUndefined(options)) {
- var output = event.function(options);
- }
- else {
- var output = event.function();
- }
- this.emit(key);
+ else {
+ var output = this[key]();
}
+ this.emit(key, options);
- // We return any returns of called functions for testing.
+ // We return any returns of called functions.
if (!_.isUndefined(output)) {
return output;
}
diff --git a/package.json b/package.json
index cf39f99..c339d3d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Thing.js",
- "version": "0.2.7",
+ "version": "0.3.0",
"description": "Create thing objects with properties, actions, and events. Use for IoT devices or even living things like plants.",
"main": "dist/Thing.umd.js",
"jsnext:main": "dist/Thing.es6.js",
diff --git a/test/test-thing.js b/test/test-thing.js
new file mode 100644
index 0000000..60cb28d
--- /dev/null
+++ b/test/test-thing.js
@@ -0,0 +1,107 @@
+const Thing = require('../dist/Thing.umd');
+const _ = require('underscore');
+
+global.expect = require('chai').expect;
+
+(function setup () {
+ beforeEach(function() {
+
+ global.thing = {
+ // Meta data
+ uuid: null,
+ token: null,
+ name: 'Dr. Dose', // The display name for the thing.
+ desription: 'Dr. Dose keeps your pH balanced.',
+
+ // Properties can be updated by the API, Metadata cannot.
+ properties: {
+ state: null,
+ duration: 2000,
+ eC_reading: null,
+ pH_reading: null
+ },
+
+ start: function () {
+ // Maybe emit an event instead?
+ return 'Dr. Dose initialized.';
+ },
+
+ acid: function (duration) {
+ return 'acid';
+ },
+
+ base: function (duration) {
+ return 'base';
+ },
+
+ nutrient: function (duration) {
+ return 'nutrient: ' + duration;
+ },
+
+ ec_data: function () {
+ return 'ec_data';
+ },
+
+ ph_data: function () {
+ return 'ph_data';
+ }
+ }
+ });
+
+ afterEach(function() {
+ delete global.thing;
+ });
+})();
+
+
+describe('Thing test', () => {
+ // Do we really need a new thing every time?
+ beforeEach(() => {
+ global.testThing = new Thing(thing);
+ });
+
+ it('should have cloned metadata', () => {
+ expect(testThing.token).to.equal(null);
+ expect(testThing.uuid).to.equal(null);
+ });
+
+ describe('PROPERTIES', () => {
+ it('should get a property', () => {
+ expect(testThing.get('duration')).to.equal(2000);
+ });
+
+ it('should set a property', () => {
+ testThing.set('duration', 3000);
+ expect(testThing.get('duration')).to.equal(3000);
+ });
+
+ it('should emit an event when a property is set', () => {
+ var event = false;
+ testThing.on('property-updated', () => {
+ return event = true;
+ });
+ testThing.set('duration', 5000);
+ expect(testThing.get('duration')).to.equal(5000);
+ expect(event).to.equal(true);
+ });
+ });
+
+ describe('Methods:', () => {
+ it('should be able to call a method.', () => {
+ expect(testThing.call('acid')).to.equal('acid');
+ });
+
+ it('should emit an event when a method is called', () => {
+ var event = false;
+ testThing.on('acid', () => {
+ return event = true;
+ });
+ testThing.call('acid');
+ expect(event).to.equal(true);
+ });
+ });
+
+ afterEach(() => {
+ delete global.testThing;
+ });
+});
diff --git a/test/thing-test.js b/test/thing-test.js
deleted file mode 100644
index f74fe44..0000000
--- a/test/thing-test.js
+++ /dev/null
@@ -1,162 +0,0 @@
-const Thing = require('../dist/Thing.umd');
-const _ = require('underscore');
-
-global.expect = require('chai').expect;
-
-(function setup () {
- beforeEach(function() {
-
- global.thing = {
- name: 'Light', // The display name for the thing.
- id: 'Light',
- username: 'YourUsernameHere', // The username of the account you want this device to be added to.
- properties: { // These can be updated by the API.
- state: 'off',
- lightconditions: function () {
- return 'unset';
- }
- },
- actions: { // a list of action objects with keys
- turn_light_on: {
- name: 'On', // Display name for the action
- description: 'Turns the light on.', // Optional description
- schedule: 'at 9:00am', // Optional scheduling using later.js
- event: 'Light turned on', // Optional event to emit when called.
- function: function () {
- // The implementation of the action.
- return 'Light on.';
- }
- },
- turn_light_off: {
- name: 'off',
- schedule: 'at 8:30pm',
- event: 'Light turned off',
- function: function () {
- return 'Light off.';
- }
- },
- light_data: {
- name: 'Log light data', // Events get a display name like actions
- type: 'light', // Currently need for visualization component... HACK.
- template: 'sensor',
- schedule: 'every 1 second', // Events should have a schedule option that determines how often to check for conditions.
- function: function () {
- return 10;
- }
- }
- },
- events: {
- dark: {
- name: 'It\'s dark.',
- on: 'light_data', // Hook into an action.
- function: function () {
- return;
- }
- },
- light: {
- name: 'It\'s light.',
- on: 'light_data',
- function: function () {
- return;
- }
- }
- }
- }
- });
-
- afterEach(function() {
- delete global.thing;
- });
-})();
-
-
-describe('Thing test', () => {
- beforeEach(() => {
- global.testThing = new Thing(thing);
- });
-
- it('should have cloned metadata', () => {
- expect(testThing.name).to.equal('Light');
- expect(testThing.id).to.equal('Light');
- expect(testThing.username).to.equal('YourUsernameHere');
- });
-
- describe('ACTIONS', () => {
- it('should register actions in the config object', () => {
- expect(_.allKeys(testThing.actions).length).to.equal(3);
- });
-
- it('should return the right action object when given an action id.', () => {
- var action = testThing.getAction('light_data')
- expect(action.name).to.equal('Log light data');
- });
-
- it('should be able to call a registered action.', () => {
- expect(testThing.call('turn_light_on')).to.equal('Light on.');
- });
-
- it('should get an action property', () => {
- expect(testThing.get('name', 'turn_light_on')).to.equal('On');
- });
-
- it('should set an action property', () => {
- testThing.set('name', 'Robert', 'turn_light_on');
- expect(testThing.get('name', 'turn_light_on')).to.equal('Robert');
- });
-
- it('should emit an event when an action is called', () => {
- var event = false;
- testThing.on('turn_light_on', () => {
- return event = true;
- });
- testThing.call('turn_light_on');
- expect(event).to.equal(true);
- });
- });
-
- describe('EVENTS', () => {
- it('should register events in the config object', () => {
- expect(_.allKeys(testThing.events).length).to.equal(2);
- });
-
- it('should get an event property', () => {
- expect(testThing.get('name', 'dark')).to.equal('It\'s dark.');
- });
-
- it('should set an event property', () => {
- testThing.set('name', 'Robert', 'dark');
- expect(testThing.get('name', 'dark')).to.equal('Robert');
- });
-
- it('should return the right event object when given an id.', () => {
- var component = testThing.getEvent('dark');
- expect(component.name).to.equal('It\'s dark.');
- });
- });
-
- describe('PROPERTIES', () => {
- it('should initialize correctly', () => {
- expect(testThing.get('lightconditions')).to.equal('unset');
- });
-
- it('should set a property', () => {
- testThing.set('lightconditions', 'dark');
- expect(testThing.get('lightconditions')).to.equal('dark');
- });
-
- it('should emit an event when a property is set', () => {
- var event = false;
- testThing.on('property-updated', () => {
- return event = true;
- });
- testThing.set('lightconditions', 'light');
- expect(testThing.get('lightconditions')).to.equal('light');
- expect(event).to.equal(true);
- });
-
- });
-
- afterEach(() => {
- delete global.testThing;
- });
-});