Skip to content

Commit

Permalink
Timer, Motion, Light, Room, test updates
Browse files Browse the repository at this point in the history
  • Loading branch information
grahamj committed Oct 7, 2023
1 parent bdaea24 commit f87257a
Show file tree
Hide file tree
Showing 25 changed files with 206 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"eslint": "./node_modules/.bin/eslint src/**/*.js --quiet",
"eslint-fix": "./node_modules/.bin/eslint src/**/*.js --fix",
"test": "npm run test_unit",
"test_unit": "./node_modules/mocha/bin/mocha.js test/**/*.spec.js"
"test_unit": "./node_modules/mocha/bin/mocha.js test/**/*.spec.js"
},
"author": "Graham J",
"dependencies": {
Expand Down
24 changes: 17 additions & 7 deletions src/class/base/BooleanEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,17 @@ const priv = Symbol('private');
class BooleanEntity extends Entity {

constructor(config) {
super({
...config,
});
super(config);
this[priv] = {
highHandlers: [],
lowHandlers: [],
toggleHandlers: [],
};
super.onStateChange(() => {
if(this.state === 'on' && this.previousState === 'off') {
this[priv].highHandlers.forEach((handler) => handler(this));
this[priv].toggleHandlers.forEach((handler) => handler(this));
this.triggerHigh();
} else if(this.state === 'off' && this.previousState === 'on') {
this[priv].lowHandlers.forEach((handler) => handler(this));
this[priv].toggleHandlers.forEach((handler) => handler(this));
this.triggerLow();
}
});
}
Expand All @@ -36,6 +32,20 @@ class BooleanEntity extends Entity {
this[priv].toggleHandlers.push(handler);
}

triggerHigh() {
this[priv].highHandlers.forEach((handler) => handler(this));
this.triggerToggle();
}

triggerLow() {
this[priv].lowHandlers.forEach((handler) => handler(this));
this.triggerToggle();
}

triggerToggle() {
this[priv].toggleHandlers.forEach((handler) => handler(this));
}

}

module.exports = BooleanEntity;
10 changes: 5 additions & 5 deletions src/class/base/Entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const priv = Symbol('private');
class Entity {

constructor(config = {}) {
Joi.assert(config, Joi.object({
identifier: Joi.string().required(),
entityId: Joi.string().required(),
domain: Joi.string().required(),
}).unknown());
this[priv] = {
stateChangeHandlers: [],
};
Expand All @@ -20,11 +25,6 @@ class Entity {
lastUpdate: undefined,
event: undefined,
});
Joi.assert(config, Joi.object({
identifier: Joi.string().required(),
entityId: Joi.string().required(),
domain: Joi.string().required(),
}).unknown());
const { identifier, domain, entityId } = config;
Object.assign(this, {
identifier,
Expand Down
20 changes: 20 additions & 0 deletions src/class/domain/Light.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ class Light extends Entity {
this.onLow(handler);
}

async turnOn() {
return super.callService({
domain: 'light',
service: 'turn_on',
target: {
entity_id: this.entityId,
},
});
}

async turnOff() {
return super.callService({
domain: 'light',
service: 'turn_off',
target: {
entity_id: this.entityId,
},
});
}

}

module.exports = Light;
33 changes: 33 additions & 0 deletions src/class/domain/Timer.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
const Joi = require('joi');
const Entity = require('../base/Entity.js');

const priv = Symbol('private');

class Timer extends Entity {

constructor(config) {
super({
...config,
domain: 'timer',
});
this[priv] = {
startHandlers: [],
stopHandlers: [],
};
super.onStateChange(() => {
if(this.state === 'active' && this.previousState === 'idle') {
this.triggerStart();
} else if(this.state === 'idle' && this.previousState === 'active') {
this.triggerStop();
}
});
}

onStart(handler) {
this[priv].startHandlers.push(handler);
}

onStop(handler) {
this[priv].stopHandlers.push(handler);
}

triggerStart() {
this[priv].startHandlers.forEach((handler) => handler(this));
}

triggerStop() {
this[priv].stopHandlers.forEach((handler) => handler(this));
}

async start(seconds) {
Expand Down Expand Up @@ -42,6 +71,10 @@ class Timer extends Entity {
});
}

isRunning() {
return this.state === 'active';
}

}

module.exports = Timer;
40 changes: 37 additions & 3 deletions src/class/group/Room.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
const Joi = require('joi');
const Light = require('../domain/Light');
const Door = require('../subdomain/Door');
const Window = require('../subdomain/Window');
const Motion = require('../subdomain/Motion');
const Timer = require('../domain/Timer');
const Alexa = require('../subdomain/Alexa');
const Pico = require('../thirdparty/Pico');

class Room {

constructor(config = {}) {
Object.assign(this, {
identifier: undefined,
entityMap: new Map(),
configMap: new Map(),
lightMap: new Map(),
doorMap: new Map(),
windowMap: new Map(),
motionMap: new Map(),
voiceMap: new Map(),
picoMap: new Map(),
timerMap: new Map(),
presenceTimer: undefined,
});
this.configure(config);
}
Expand All @@ -17,15 +33,33 @@ class Room {
}));
this.identifier = config.identifier;
if(config.entities) {
config.entities.forEach(this.addEntity.bind(this));
config.entities.forEach((entity) => this.addEntity(entity));
}
}

addEntity(entity) {
addEntity(entity, config = {}) {
Joi.assert(entity, Joi.object({
identifier: Joi.string().required(),
}).unknown());
}).unknown().required());
Joi.assert(config, Joi.object().unknown());
this.entityMap.set(entity.identifier, entity);
this.configMap.set(entity.identifier, config);
if(entity instanceof Light) {
this.lightMap.set(entity.identifier, entity);
} else if(entity instanceof Door) {
this.doorMap.set(entity.identifier, entity);
} else if(entity instanceof Window) {
this.windowMap.set(entity.identifier, entity);
} else if(entity instanceof Motion) {
this.motionMap.set(entity.identifier, entity);
} else if(entity instanceof Alexa) {
this.voiceMap.set(entity.identifier, entity);
} else if(entity instanceof Pico) {
this.picoMap.set(entity.identifier, entity);
} else if(entity instanceof Timer) {
this.timerMap.set(entity.identifier, entity);
if(config.presence) this.presenceTimer = entity;
}
}

getEntity(identifier) {
Expand Down
22 changes: 21 additions & 1 deletion src/class/subdomain/Motion.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,34 @@ const BinarySensor = require('../domain/BinarySensor.js');

class Motion extends BinarySensor {

onMotion(handler) {
onDetect(handler) {
return super.onHigh(handler);
}

onClear(handler) {
return super.onLow(handler);
}

triggerHigh() {
this.triggerDetect();
}

triggerLow() {
this.triggerClear();
}

triggerDetect() {
if(this.latch && !this.latchInterval) {
this.latchInterval = setInterval(() => this.triggerDetect(), Math.floor(this.latch / 4));
}
super.triggerHigh();
}

triggerClear() {
if(this.latchInterval) clearInterval(this.latchInterval);
super.triggerLow();
}

}

module.exports = Motion;
3 changes: 2 additions & 1 deletion test/unit/class/base/BooleanEntity.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('BooleanEntity class', () => {
let BooleanEntity;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/base/Entity.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Entity base class', () => {
let Entity;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/BinarySensor.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('BinarySensor class', () => {
let BinarySensor;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/Button.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Button class', () => {
let Button;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/Counter.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Counter class', () => {
let Counter;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/InputBoolean.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('InputBoolean class', () => {
let InputBoolean;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/InputButton.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('InputButton class', () => {
let InputButton;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/InputText.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('InputText class', () => {
let InputText;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/Light.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Light class', () => {
let Light;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/MediaPlayer.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('MediaPlayer class', () => {
let MediaPlayer;
Expand Down
3 changes: 2 additions & 1 deletion test/unit/class/domain/Sensor.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Sensor class', () => {
let Sensor;
Expand Down
20 changes: 16 additions & 4 deletions test/unit/class/domain/Timer.spec.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
const sinon = require('sinon');
const { expect } = require('chai');
const proxyquire = require('proxyquire').noPreserveCache();
const proxyquire = require('proxyquire')
.noPreserveCache().noCallThru();

describe('Timer class', () => {
let Timer;
let constructorStub;
let callServiceStub;
let onStateChangeStub;

beforeEach(() => {
constructorStub = sinon.stub();
callServiceStub = sinon.stub();
class Entity {
onStateChangeStub = sinon.stub();
callServiceStub = sinon.stub();

const Entity = class {
constructor(...args) {
this.entityId = args[0].entityId;
constructorStub(...args);
}
}
Entity.prototype.callService = callServiceStub;

onStateChange(...args) {
onStateChangeStub(...args);
}

callService(...args) {
callServiceStub(...args);
}
};
Timer = proxyquire('../../../../src/class/domain/Timer', {
'../base/Entity.js': Entity,
});
Expand Down
Loading

0 comments on commit f87257a

Please sign in to comment.