johnny-five-electron
Version:
Temporary fork to support Electron (to be deprecated)
1,027 lines (902 loc) • 56.1 kB
JavaScript
var MockFirmata = require("./util/mock-firmata"),
five = require("../lib/johnny-five.js"),
events = require("events"),
sinon = require("sinon"),
_ = require("lodash"),
Board = five.Board,
Sensor = five.Sensor;
function newBoard() {
var io = new MockFirmata();
var board = new Board({
io: io,
debug: false,
repl: false
});
io.emit("connect");
io.emit("ready");
return board;
}
function restore(target) {
for (var prop in target) {
if (Array.isArray(target[prop])) {
continue;
}
if (target[prop] != null && typeof target[prop].restore === "function") {
target[prop].restore();
}
if (typeof target[prop] === "object") {
restore(target[prop]);
}
}
}
function getShape(sensor) {
return {
id: sensor.id,
mode: sensor.mode,
freq: sensor.freq,
range: sensor.range,
limit: sensor.limit,
threshold: sensor.threshold,
isScaled: sensor.isScaled,
pin: sensor.pin,
state: sensor.state
};
}
exports["Sensor - Analog"] = {
setUp: function(done) {
this.board = newBoard();
this.clock = sinon.useFakeTimers();
this.analogRead = sinon.spy(MockFirmata.prototype, "analogRead");
this.sensor = new Sensor({
pin: "A1",
board: this.board
});
// Complete visible property information expected for the above sensor instance,
// excluding the 'external' references for the board and io properties.
this.defShape = {
id: this.sensor.id,
mode: this.sensor.io.MODES.ANALOG,
freq: 25,
range: [0, 1023],
limit: null,
threshold: 1,
isScaled: false,
pin: 1,
state: {
booleanBarrier: 512,
scale: null,
value: 0,// Starts at null, but gets updated before first checks
freq: 25
}
};
// Methods expected to be found on the prototype for sensor instances
this.methods = [
"constructor",
"within",
"scale",
"scaleTo",
"booleanAt"
];
// All properties expected to be found (directly) on any sensor instance
this.members = {
id: { type: "string" },
pin: { type: "number" },
mode: { type: "number" },
freq: { type: "number" },
range: { type: "object" },
threshold: { type: "number" },
isScaled: { type: "boolean" },
raw: { type: "object" }, // defined property that returns var inited to null
analog: { type: "object" }, // defined property
constrained: { type: "object" }, // defined property
boolean: { type: "boolean" }, // defined property always true or false
scaled: { type: "object" }, // defined property
value: { type: "object" }, // defined property
state: { type: "object" }, // defined (for test mode) property
board: { type: "object" },
io: { type: "object" },
limit: { type: "object" } // null initial value
};
done();
},// ./setUp: function(done)
tearDown: function(done) {
Board.purge();
restore(this);
done();
},// ./tearDown: function(done)
shape: function(test) {
var propsActual, propsExpected, methodsActual;
propsActual = Object.getOwnPropertyNames(this.sensor);
propsExpected = Object.getOwnPropertyNames(this.members);
methodsActual = Object.getOwnPropertyNames(Object.getPrototypeOf(this.sensor));
test.expect(3 + 3 * (this.methods.length + propsExpected.length));
// Verify that all of the expected prototype functions and properties exist for the instance
this.methods.forEach(function(proto) {
test.ok(methodsActual.includes(proto), "missing '" + proto + "' sensor prototype method");
}, this);
propsExpected.forEach(function(property) {
test.ok(propsActual.includes(property), "missing '" + property + "' sensor instance property");
}, this);
// Make sure that all of the existing instance properties and prototype methods are actually expected, and the correct datatype
propsActual.forEach(function(property) {
test.ok(propsExpected.includes(property), "found unexpected '" + property + "' sensor instance member");
test.ok(propsExpected.includes(property) && typeof this.sensor[property] === this.members[property].type,
"Unexpected datatype '" + typeof this.sensor[property]+ "' found for '" + property + "' property");
}, this);
methodsActual.forEach(function(proto) {
test.ok(this.methods.includes(proto), "found unexpected '" + proto + "' sensor prototype method");
test.strictEqual(typeof this.sensor[proto], "function", "Unexected datatype found for '" + proto + "' method");
}, this);
// Check that the 'standard' component properties reference the expected objects
test.strictEqual(this.sensor.board, this.board, "Expected to be the mock board");
test.strictEqual(this.sensor.io, this.board.io, "Expected to be the same io as the mock board");
// See if the visible instance properties match the expected default values
test.deepEqual(getShape(this.sensor), this.defShape, "sensor instance properties should match default shape values");
test.done();
},// ./shape: function(test)
emitter: function(test) {
test.expect(1);
test.ok(this.sensor instanceof events.EventEmitter);
test.done();
},// ./emitter: function(test)
data: function(test) {
var tickAccum, tickDelta, spy = sinon.spy();
test.expect(4);
// Make sure that no event is emitted before the end of the initial interval is reached
this.sensor.on("data", spy);
tickAccum = 0;
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// accumulated (elapsed) time is one tick (ms) before the end of the first interval
tickAccum += tickDelta;
test.ok(!spy.called, "tick " + tickAccum + ": data event handler should not be called until tick " + this.defShape.freq);
// Make sure that an event is emitted when the initial interval ends
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the first (freq) interval
tickAccum += tickDelta;
test.ok(spy.calledOnce, "tick " + tickAccum + ": data event handler should have been called first time at tick " + this.defShape.freq);
// Make sure no additional event is emitted before the end of the next interval
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now one tick before the end of the second interval
tickAccum += tickDelta;
test.ok(spy.calledOnce, "tick" + tickAccum + ": data event handler should not be called again until tick " + (this.defShape.freq * 2));
// Make sure the next event is emitted at the end of the second interval
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the second interval
tickAccum += tickDelta;
test.ok(spy.calledTwice, "tick " + tickAccum + ": data event handler should be called second time at tick " + (this.defShape.freq * 2));
test.done();
},// ./data: function(test)
filtered: function(test) {
var callback = this.analogRead.args[0][1],
dataSpy = sinon.spy(),
chgSpy = sinon.spy(),
tickDelta, tickAccum, spyCall, raw, filtered;
test.expect(41);
this.sensor.on("data", dataSpy);
this.sensor.on("change", chgSpy);
// Check that the default noise filtering calculates the median value, while the individual reads track the raw values
tickAccum = 0;
raw = 100;
callback(raw);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 102;
callback(raw);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 101;
callback(raw);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 103;
callback(raw);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
tickDelta = this.defShape.freq - tickAccum - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick (ms) before the end of the first (freq) event throttling interval
tickAccum += tickDelta;
raw = 104;
callback(raw);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
test.ok(!dataSpy.called, "tick " + tickAccum + ": data event handler should not be called until tick " + this.defShape.freq);
test.ok(!chgSpy.called, "tick " + tickAccum + ": change event handler should not be called until tick " + this.defShape.freq);
// Make sure that the events are emitted, with the median of the raw read values, at the end of the interval
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the first (freq) event throttling interval
tickAccum += tickDelta;
// Median of values sent through callback since previous data event
filtered = 102;
test.ok(dataSpy.calledOnce, "tick " + tickAccum + ": data event handler should be called at tick " + this.defShape.freq);
test.ok(chgSpy.calledOnce, "tick " + tickAccum + ": change event handler should be called at tick " + this.defShape.freq);
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum + ": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum + ": sensor value property expected to be the last value (" + raw + ") injected");
// Check the arguments and context provided for the emitted events
spyCall = dataSpy.getCall(0);
test.strictEqual(spyCall.args[0], null, "data event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "data event 'this' parameter expected to be source sensor object");
spyCall = chgSpy.getCall(0);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
// Check for non-integer median value (when even number of data points and odd delta between middle two)
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
callback(202);
this.clock.tick(tickDelta);
tickAccum += tickDelta;
callback(206);
this.clock.tick(tickDelta);
tickAccum += tickDelta;
callback(201);
tickDelta = this.defShape.freq * 2 - tickAccum - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the second interval
tickAccum += tickDelta;
raw = 203;
callback(raw);
// Check that no event is emitted before the end of the next interval
test.ok(dataSpy.calledOnce, "tick " + tickAccum + ": data event handler should not be called again until tick " + this.defShape.freq * 2);
test.ok(chgSpy.calledOnce, "tick " + tickAccum + ": change event handler should not be called again until tick " + this.defShape.freq * 2);
filtered = 202.5; // Median of values sent through callback (last === 102) (avg(102,103))
// Check that events are emitted at the end of the second interval
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the second event throttling interval
tickAccum += tickDelta;
test.ok(dataSpy.calledTwice, "tick " + tickAccum + ": data event handler should be called again at tick " + this.defShape.freq * 2);
test.ok(chgSpy.calledTwice, "tick " + tickAccum + ": change event handler should be called again at tick " + this.defShape.freq * 2);
test.strictEqual(this.sensor.raw, raw, "sensor raw property expected to be the last value (" + raw + ") read (injected)");
test.strictEqual(this.sensor.value, raw, "sensor value property expected to be the last value (" + raw + ") read (injected)");
// Check that both events provide the correct median value and context
spyCall = dataSpy.getCall(1);
test.strictEqual(spyCall.args[0], null, "data event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "data event 'this' parameter expected to be source sensor object");
spyCall = chgSpy.getCall(1);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
// Check that no events are emitted before the end of the next throttling interval
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the third interval
tickAccum += tickDelta;
test.ok(dataSpy.calledTwice, "tick " + tickAccum + ": data event handler should not be called again until tick " + this.defShape.freq * 3);
test.ok(chgSpy.calledTwice, "tick " + tickAccum + ": change event handler should not be called again until at least tick " + this.defShape.freq * 3);
// check that only the data event is emitted when no new values are read during the interval
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the third interval
tickAccum += tickDelta;
test.ok(dataSpy.calledThrice, "tick " + tickAccum + ": data event handler should be called again at tick " + this.defShape.freq * 3);
test.ok(chgSpy.calledTwice, "tick " + tickAccum + ": change event handler should not be called at " + this.defShape.freq * 3 + " without new value");
test.strictEqual(this.sensor.raw, raw, "sensor raw property expected to be the last value (" + raw + ") read (injected)");
test.strictEqual(this.sensor.value, raw, "sensor value property expected to be the last value (" + raw + ") read (injected)");
// Check that the data event has the same filtered value as the previous event
spyCall = dataSpy.getCall(2);
test.strictEqual(spyCall.args[1], filtered, "data event value expected to be still the median (" + filtered + ") value");
test.done();
},// ./filtered: function(test)
change: function(test) {
var callback = this.analogRead.args[0][1],
spy = sinon.spy(),
tickAccum, tickDelta, chgValue;
test.expect(8);
this.sensor.on("change", spy);
// Make sure that no change event is emitted before the end of the first (freq) throttling interval
tickAccum = 0;
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// accumulated (elapsed) time is one tick (ms) before the end of the first interval
tickAccum += tickDelta;
chgValue = 1023;
callback(chgValue);
// Reading a data value should not (immediately) cause an event to be emitted
test.ok(!spy.called, "tick " + tickAccum + ": change event handler should not be called until tick " + this.defShape.freq);
// Make sure that a change event is emitted at the end of the throttling interval
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed (fake) time is at the end of the first (event throttling) interval
tickAccum += tickDelta;
test.ok(spy.calledOnce, "tick " + tickAccum + ": change event handler should have been called first time at tick " + this.defShape.freq);
test.strictEqual(spy.getCall(0).args[1], chgValue, "first change event value expected to be " + chgValue);
// Make sure that no change event is emitted before the end of the next throttling interval
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is one tick before the end of the second throttling interval
tickAccum += tickDelta;
// duplicate of previous data value
chgValue = 1023;
callback(chgValue);
test.ok(spy.calledOnce, "tick " + tickAccum + ": change event handler should not be called again until at least tick " + this.defShape.freq * 2);
// Make sure that no change event is emitted when the reading does not change
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the second event throttling interval
tickAccum += tickDelta;
test.ok(spy.calledOnce, "tick " + tickAccum + ": change event handler should not be called without a new data value");
// Make sure that no change event is emitted before the end of the next throttling interval
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
// different value
chgValue = 512;
callback(chgValue);
test.ok(spy.calledOnce, "tick" + tickAccum + ": change event handler should not be called again until tick " + (this.defShape.freq * 3));
// Make sure that a different (greater than threshold) value change emits a new change event
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the third event throttling interval
tickAccum += tickDelta;
test.ok(spy.calledTwice, "tick " + tickAccum + ": change event handler should be called second time at tick " + (this.defShape.freq * 3));
test.strictEqual(spy.getCall(1).args[1], chgValue, "second change event value expected to be " + chgValue);
test.done();
},// ./change: function(test)
// Tests to check that the thresholds are handled correctly to control when change events get emitted
threshold: function(test) {
var callback = this.analogRead.args[0][1],
spy = sinon.spy(),
tickDelta, tickAccum, spyCall, raw, filtered, newShape;
test.expect(50);
this.sensor.on("change", spy);
test.strictEqual(this.sensor.threshold, 1, "Following tests assume a (default) threshold of 1");
tickAccum = 0;
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
// An initial reference value to base threshold checks against
raw = 512;
filtered = raw; // last value = null
callback(raw);
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the first (freq) event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
// Change event should always be triggered after first interval (at tick this.defShape.freq)
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": change event handler should be called first time at tick " + this.defShape.freq);
// Verify the arguments and context for the call to the event handler
spyCall = spy.getCall(0);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered,
"change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor),
"change event 'this' parameter expected to be source sensor object");
// Check that no new change event is emitted when the (filtered) value changes (up) by (just) less than the threshold
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 512;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 513;
// last filtered value = 512
filtered = 512.5;
callback(raw);
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the second event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 2 + "; new median within threshold");
// Check that a change event is emitted when the new filtered value is right on the threshold boundary
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 511;
// last emitted value = 512
filtered = raw;
callback(raw);
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the third event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 2, "tick " + tickAccum +
": change event handler should be called at tick " + this.defShape.freq * 3 + "; new median on threshold boundary");
// Verify the arguments and context for the call to the event handler
spyCall = spy.getCall(1);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
// Check that no new change event is emitted when the (filtered) value changes (down) by (just) less than the threshold
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 500;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 521;
// last emitted value = 511
filtered = 510.5;
callback(raw);
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the fourth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 2, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 4);
// Check that setting a new threshold property value changes the boundaries for when change events are emitted
newShape = _.cloneDeep(this.defShape);
newShape.threshold = 0.5;
// Any (normal) change should trigger a change event
this.sensor.threshold = newShape.threshold;
// Setting a new threshold value should change (only) the threshold property of the instance
test.deepEqual(newShape, getShape(this.sensor), "sensor instance properties should match new shape values");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 511;
// last emitted value = 511
filtered = raw;
callback(raw);
// Check that no new event is emitted when the filtered value does not change
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the fifth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 2, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 5 + "; new median same as last");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 501;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 522;
// last emitted value = 511
filtered = 511.5;
callback(raw);
// Check that a change event is emitted with a filtered value on the new upper threshold boundary
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the sixth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 3, "tick " + tickAccum +
": change event handler should be called at tick " + this.defShape.freq * 6 + "; on threshold boundary");
// Verify the arguments and context for the call to the event handler
spyCall = spy.getCall(2);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
// Only changes of 10 or more should trigger a change event
newShape.threshold = 10;
this.sensor.threshold = newShape.threshold;
test.deepEqual(newShape, getShape(this.sensor), "sensor instance properties should match new shape values");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 520;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 522;
// last emitted value = 511.5
filtered = 521;
callback(raw);
// Check that upward changes of (just) less than the new threshold do not emit a change event
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the seventh event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 3, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 7 + "; median change less than threshold");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 497;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 507;
// last emitted value = 511.5
filtered = 502;
callback(raw);
// Check that downward changes of (just) less than the new threshold do not emit a change event
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the eighth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 3, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 8 + " since change does not exceed threshold");
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 520;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
raw = 527;
// last emitted value = 511.5
filtered = 523.5;
callback(raw);
// Check that changes above the (upper) threshold emit a change event
tickDelta = this.defShape.freq - 2;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the ninth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 4, "tick " + tickAccum +
": change event handler should be called at tick " + this.defShape.freq * 9 + " since change threshold exceeded");
// Verify the arguments and context for the call to the event handler
spyCall = spy.getCall(3);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
// do a new read at the end of the current interval, after the event has been emitted.
raw = 515;
callback(raw);
// last emitted value = 523.5
filtered = 515;
// Check that a downward change of less than the current threshold does not emit a change event
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the tenth event throttling interval
tickAccum += tickDelta;
test.strictEqual(this.sensor.raw, raw, "tick " + tickAccum +
": sensor raw property expected to be the last value (" + raw + ") injected");
test.strictEqual(this.sensor.value, raw, "tick " + tickAccum +
": sensor value property expected to be the last value (" + raw + ") injected");
test.strictEqual(spy.callCount, 4, "tick " + tickAccum +
": change event handler should not be called at tick " + this.defShape.freq * 10 + " since change does not exceed threshold");
// Changes of 5 or more should trigger a change event
newShape.threshold = 5;
this.sensor.threshold = newShape.threshold;
test.deepEqual(newShape, getShape(this.sensor), "sensor instance properties should match new shape values");
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the eleventh event throttling interval
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 5, "tick " + tickAccum +
": change event handler should be called at tick " + this.defShape.freq * 11 +
"; changed threshold moves existing filtered value outside range");
// Verify the arguments and context for the call to the event handler
spyCall = spy.getCall(4);
test.strictEqual(spyCall.args[0], null, "change event err argument expected to be null");
test.strictEqual(spyCall.args[1], filtered, "change event value expected to be the median (" + filtered + ") value");
test.ok(spyCall.calledOn(this.sensor), "change event 'this' parameter expected to be source sensor object");
test.done();
},// ./threshold: function(test)
id: function(test) {
var newShape, newId;
test.expect(3);
newShape = _.cloneDeep(this.defShape);
newId = "test sensor id";
newShape.id = newId;
this.sensor.id = newId;
test.deepEqual(newShape, getShape(this.sensor), "sensor instance properties should match shape with new id");
newId = "1234";
this.sensor.id = newId;
test.strictEqual(this.sensor.id, newId, "id specified as string \"1234\"");
newId = 1234;
this.sensor.id = newId;
test.strictEqual(this.sensor.id, newId, "id specified as numeric 1234");
test.done();
},// ./id: function(test)
limit: function(test) {
var callback = this.analogRead.args[0][1],
dataSpy = sinon.spy(),
limitSpy = sinon.spy(),
lowerSpy = sinon.spy(),
upperSpy = sinon.spy(),
newShape, raw, filtered, tickDelta, tickAccum, lowerLimit, upperLimit;
test.expect(46);
this.sensor.on("data", dataSpy);
this.sensor.on("limit", limitSpy);
this.sensor.on("limit:lower", lowerSpy);
this.sensor.on("limit:upper", upperSpy);
test.strictEqual(this.sensor.limit, null, "sensor limit property should default to null value");
// Check that no limit events are emitted while no limit is configured (low value)
tickAccum = 0;
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the first event throttling interval
tickAccum += tickDelta;
raw = 0;
filtered = raw;
callback(raw);
test.strictEqual(dataSpy.callCount + limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 0,
"tick " + tickAccum + ": no event handlers should not be called until tick " + this.defShape.freq);
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the first event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 1, "tick " + tickAccum +
": data event handler should be called first time at tick " + this.defShape.freq);
test.strictEqual(dataSpy.getCall(0).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.strictEqual(limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 0,
"tick " + tickAccum + ": no limit event handlers should be called while limit is null");
// Check that no limit events are emitted while no limit is configured (high value)
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the second event throttling interval
tickAccum += tickDelta;
raw = 1023;
filtered = raw;
callback(raw);
test.strictEqual(dataSpy.callCount + limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 1,
"tick " + tickAccum + ": no more event handlers should not be called until tick " + this.defShape.freq * 2);
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the second event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 2, "tick " + tickAccum +
": data event handler should be called again time at tick " + this.defShape.freq * 2);
test.strictEqual(dataSpy.getCall(1).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.strictEqual(limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 0,
"tick " + tickAccum + ": no limit event handlers should be called while limit is null");
newShape = _.cloneDeep(this.defShape);
// test.deepEqual(this.defShape, newShape);//DBG verify that deep copy is working
// test.deepStrictEqual(this.defShape, newShape);//DBG verify that deep copy is working
// newShape.limit = "junk";
// newShape.limit = {test: "junk"};
// newShape.limit = {0: "junk"};
// newShape.limit = 123;
// Check that setting limit boundaries changes the instance shape to match
lowerLimit = 0;
upperLimit = 1023;
newShape.limit = [lowerLimit, upperLimit];
this.sensor.limit = [lowerLimit, upperLimit];
test.deepEqual(getShape(this.sensor), newShape, "sensor instance properties should match shape with new limit");
lowerLimit = 100;
upperLimit = 101;
newShape.limit = [lowerLimit, upperLimit];
this.sensor.limit = [lowerLimit, upperLimit];
test.deepEqual(getShape(this.sensor), newShape, "sensor instance properties should match shape with new limit");
lowerLimit = 450;
upperLimit = 550;
newShape.limit = [lowerLimit, upperLimit];
this.sensor.limit = [lowerLimit, upperLimit];
test.deepEqual(getShape(this.sensor), newShape, "sensor instance properties should match shape with new limit");
// Check that a value just above the lower limit does not emit any limit events
raw = 450;
callback(raw);
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the third event throttling interval
tickAccum += tickDelta;
raw = 451;
filtered = 450.5;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the third event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 3, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(dataSpy.getCall(2).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.strictEqual(limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 0,
"tick " + tickAccum + ": no limit event handlers should be called while value is within the limits");
// Check that a value just above the lower limit does not emit any limit events
raw = 550;
callback(raw);
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
// elapsed time is now 1 tick before the end of the fourth event throttling interval
tickAccum += tickDelta;
raw = 549;
filtered = 549.5;
callback(raw);
tickDelta = 1;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the fourth event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 4, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(dataSpy.getCall(3).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.strictEqual(limitSpy.callCount + lowerSpy.callCount + upperSpy.callCount, 0,
"tick " + tickAccum + ": no limit event handlers should be called while value is within the limits");
// check that a value matching the lower limit emits limit and limit:lower events
raw = 450;
filtered = raw;
callback(raw);
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the fifth event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 5, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(limitSpy.callCount, 1, "tick " + tickAccum +
": limit event handler should be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(lowerSpy.callCount, 1, "tick " + tickAccum +
": limit:lower event handler should be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(upperSpy.callCount, 0, "tick " + tickAccum +
": limit:upper event handler should not be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(dataSpy.getCall(4).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.deepEqual(limitSpy.getCall(0).args[1], { boundary: "lower", value: filtered },
"limit event value expected to be the lower boundary with the median (" + filtered + ") value");
test.strictEqual(lowerSpy.getCall(0).args[1], filtered, "limit:lower event value expected to be the median (" + filtered + ") value");
// check that a value matching the upper limit emits limit and limit:upper events
raw = 550;
filtered = raw;
callback(raw);
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the sixth event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 6, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(limitSpy.callCount, 2, "tick " + tickAccum +
": limit event handler should be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(lowerSpy.callCount, 1, "tick " + tickAccum +
": limit:lower event handler should not be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(upperSpy.callCount, 1, "tick " + tickAccum +
": limit:upper event handler should be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(dataSpy.getCall(5).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.deepEqual(limitSpy.getCall(1).args[1], { boundary: "upper", value: filtered },
"limit event value expected to be the lower boundary with the median (" + filtered + ") value");
test.strictEqual(upperSpy.getCall(0).args[1], filtered, "limit:upper event value expected to be the median (" + filtered + ") value");
// check that a very low value emits limit and limit:lower events
raw = 0;
filtered = raw;
callback(raw);
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the seventh event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 7, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(limitSpy.callCount, 3, "tick " + tickAccum +
": limit event handler should be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(lowerSpy.callCount, 2, "tick " + tickAccum +
": limit:lower event handler should be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(upperSpy.callCount, 1, "tick " + tickAccum +
": limit:upper event handler should not be called at " + this.defShape.freq + " ticks; value at lower limit");
test.strictEqual(dataSpy.getCall(6).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.deepEqual(limitSpy.getCall(2).args[1], { boundary: "lower", value: filtered },
"limit event value expected to be the lower boundary with the median (" + filtered + ") value");
test.strictEqual(lowerSpy.getCall(1).args[1], filtered, "limit:lower event value expected to be the median (" + filtered + ") value");
// check that a very high value emits limit and limit:upper events
raw = 1023;
filtered = raw;
callback(raw);
tickDelta = this.defShape.freq;
this.clock.tick(tickDelta);
// elapsed time is now at the end of the eighth event throttling interval
tickAccum += tickDelta;
test.strictEqual(dataSpy.callCount, 8, "tick " + tickAccum +
": data event handler should be called every multiple of " + this.defShape.freq + " ticks");
test.strictEqual(limitSpy.callCount, 4, "tick " + tickAccum +
": limit event handler should be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(lowerSpy.callCount, 2, "tick " + tickAccum +
": limit:lower event handler should not be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(upperSpy.callCount, 2, "tick " + tickAccum +
": limit:upper event handler should be called at " + this.defShape.freq + " ticks; value at upper limit");
test.strictEqual(dataSpy.getCall(7).args[1], filtered, "data event value expected to be the median (" + filtered + ") value");
test.deepEqual(limitSpy.getCall(3).args[1], { boundary: "upper", value: filtered },
"limit event value expected to be the lower boundary with the median (" + filtered + ") value");
test.strictEqual(upperSpy.getCall(1).args[1], filtered, "limit:upper event value expected to be the median (" + filtered + ") value");
test.done();
},// ./limit: function(test)
freq: function(test) {
var spy = sinon.spy(),
newShape, newFreq, tickDelta, tickAccum;
test.expect(10);
this.sensor.on("data", spy);
test.deepEqual(this.defShape, getShape(this.sensor), "sensor instance properties should match default shape values");
// Make sure that the data event does not get emitted (first time) until the initial (default)
// interval (currently 25 ms === 25 ticks) has passed.
tickAccum = 0;
// One less tick than the expected time to emit a data event
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 0, "tick " + tickAccum +
": data event handler should not be called first time until tick " + this.defShape.freq);
// As above, many message in this (and other) test start with the accumluated (tick) time, then have
// another time at the end. The first is the actual (fake) elapsed time. The second is the the time
// point boundary being used as a reference. Have both simplifies debugging the testing code, to get
// the specified time deltas correct.
// Make sure that a data event does get emitted at the time specified by the initial (default)
// freq interval.
tickDelta = 1;
this.clock.tick(tickDelta);
// Acumulated ticks are now up to the default freq interval
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": data event handler should be called first time at tick " + this.defShape.freq);
// After explicitly setting the frep property value of the existing instance, the shape (properties)
// of the instance should match the default shape, with (only) the freq property value and the
// clone in the state information, updated to match the specified value.
newShape = _.cloneDeep(this.defShape);
newFreq = 35;
newShape.freq = newFreq;
newShape.state.freq = newFreq ;
this.sensor.freq = newFreq;
test.deepEqual(getShape(this.sensor), newShape,
"sensor instance properties should match shape with new freq");
// Make sure that the next (and following) emitted data events are based on the new interval
// new interval is larger than the original, so first check is that no event is emitted at the
// initial interval: check just before, then again at the old interval time point.
tickDelta = this.defShape.freq - 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": data event handler should not be called second time until tick " + (this.defShape.freq + newFreq));
tickDelta = 1;
this.clock.tick(tickDelta);
// Accumulated ticks are now up to 2 times the initial freq interval value
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": data event handler should not be called second time until tick " + (this.defShape.freq + newFreq));
// The above 2 checks are not needed to check for correct operation. They are helpful when
// something is wrong to narrow down where the problem is likely to be.
// Check that no new event has been emitted one time step (tick) before it is expected to be emitted;
tickDelta = newFreq - this.defShape.freq - 1;
this.clock.tick(tickDelta);
// Accumulated ticks are now up to 1 before the new interval ms after the new interval was set.
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 1, "tick " + tickAccum +
": data event handler should not be called second time until tick " + (this.defShape.freq + newFreq));
// Make sure that a new data event is emitted after the (new) specifed interval has passed
tickDelta = 1;
this.clock.tick(tickDelta);
// Accumulated ticks are now up to (at) the new inteval after the new interval was set.
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 2, "tick " + tickAccum +
": data event handler should be called second time at tick " + (this.defShape.freq + newFreq));
// Make sure that no new event is emitted before the end of the next (new) interval
tickDelta = newFreq - 1;
this.clock.tick(tickDelta);
tickAccum += tickDelta;
test.strictEqual(spy.callCount, 2, "tick " + tickAccum +
": data event handler should not b