more work on the specification and functionality of base classes, added StateUpdateManager utility, to simplify partial state updates

This commit is contained in:
jangxx 2019-01-26 01:19:25 +01:00
parent 58891b99f4
commit 5d58715922
9 changed files with 138 additions and 5 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

@ -10,6 +10,10 @@ class HCColorlamp extends HCControlBase {
return "colorlamp"; return "colorlamp";
} }
get effects() {
return [];
}
turnOn() { turnOn() {
return Promise.reject(); return Promise.reject();
} }
@ -26,7 +30,19 @@ class HCColorlamp extends HCControlBase {
return Promise.reject(); return Promise.reject();
} }
changeColor(hue, sat, light) { /**
* Sets the color to the values given in the colors object
* Also accepts partial values (only update hue and sat for example)
* @param {Object} color
* @param {Number} color.hue Hue
* @param {Number} color.sat Saturation
* @param {Number} color.l Lightness
*/
changeColor(color) {
return Promise.reject();
}
setEffect(id) {
return Promise.reject(); return Promise.reject();
} }
} }

View File

@ -9,8 +9,12 @@ class HCControlBase extends EventEmitter {
constructor(configuration, state) { constructor(configuration, state) {
super(); super();
if (!configuration.name) throw new Error("Device name is missing");
this._configuration = configuration; this._configuration = configuration;
this._state = state; this._state = state;
this._name = this._configuration.name;
} }
get state() { get state() {
@ -21,6 +25,10 @@ class HCControlBase extends EventEmitter {
return "none"; return "none";
} }
get name() {
return this._name;
}
/** /**
* Updates the state by polling the controlled device * Updates the state by polling the controlled device
* @returns A promise which resolves when the state has been pulled * @returns A promise which resolves when the state has been pulled

92
StateUpdateManager.js Normal file
View File

@ -0,0 +1,92 @@
/**
* Optional utility classes can use when states need to be partially updated
*/
class StateUpdateManager {
constructor(initialState) {
this._highestConfirmedId = 0;
this._currentId = 0;
this._nextId = 1;
this._updateHistory = [{ id: 0, state: initialState }];
}
get highestConfirmedId() {
return this._highestConfirmedId;
}
/**
* Returns the latest (unconfirmed state)
*/
get state() {
return this._updateHistory.find(su => su.id == this._currentId).state;
}
/**
* Returns the latest confirmed state
*/
get confirmedState() {
return this._updateHistory.find(su => su.id == this._highestConfirmedId).state;
}
/**
* Registers the potential update of the state
* @param {Object} state
* @returns {Number} id which is used to confirm or reject the update later
*/
registerUpdate(state) {
let updateId = this._nextId;
this._nextId += 1;
this._currentId = updateId; // until rejected, this is the most recent id
this._updateHistory.push({
id: updateId,
state
});
return updateId;
}
/**
* Add a new state and instantly confirm it
* @param {Object} state
*/
insertConfirmedState(state) {
let suid = this.registerUpdate();
this.confirmUpdate(suid);
}
/**
* Confirm the update with the specified id
* Also remove all updates with lower ids, as these are now confirmed to be outdated
* @param {Number} uid
*/
confirmUpdate(uid) {
if (uid < this._highestConfirmedId) return; // already outdated
this._highestConfirmedId = uid;
// remove history entries with lower ids
this._updateHistory = this._updateHistory.filter(su => su.id >= this._highestConfirmedId);
}
/**
* Rejects the update with the specified id
* If the id is outdated it is ignored
* Otherwise it is thrown out, and the currentId decremented if required
* @param {Number} uid
*/
rejectUpdate(uid) {
if (uid < this._highestConfirmedId) return; // already outdated
// remove element from history
this._updateHistory = this._updateHistory.filter(su => su.id != uid);
if (this._currentId == uid) {
// set current id to highest known uid
this._currentId = this._updateHistory.reduce((highest, su) => (su.id > highest) ? su.id : highest, -1);
}
}
}
module.exports = StateUpdateManager;

View File

@ -1,4 +1,5 @@
module.exports = { module.exports = {
HCColorLamp: require('./ColorlampBase'), HCColorLamp: require('./ColorlampBase'),
HCSwitch: require('./SwitchBase'), HCSwitch: require('./SwitchBase'),
StateUpdateManager: require('./StateUpdateManager'),
}; };

5
package-lock.json generated
View File

@ -16,6 +16,11 @@
"requires": { "requires": {
"is-plain-obj": "1.1.0" "is-plain-obj": "1.1.0"
} }
},
"node-object-hash": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/node-object-hash/-/node-object-hash-1.4.1.tgz",
"integrity": "sha512-JQVqSM5/mOaUoUhCYR0t1vgm8RFo7qpJtPvnoFCLeqQh1xrfmr3BCD3nGBnACzpIEF7F7EVgqGD3O4lao/BY/A=="
} }
} }
} }

View File

@ -13,6 +13,7 @@
"author": "Jan Scheiper", "author": "Jan Scheiper",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"merge-options": "^1.0.1" "merge-options": "^1.0.1",
"node-object-hash": "^1.4.1"
} }
} }

View File

@ -1,3 +1,5 @@
const objectHash = require('node-object-hash');
class BaseState { class BaseState {
constructor(cloneObj) { constructor(cloneObj) {
} }
@ -6,12 +8,16 @@ class BaseState {
return {}; return {};
} }
get hash() {
return objectHash(this.asObj);
}
static get default() { static get default() {
return {}; return {};
} }
clone() { clone() {
return this(this.asObj); return new this.constructor(this.asObj);
} }
} }

View File

@ -13,6 +13,7 @@ class ColorlampState extends BaseState {
sat: cloneState.color.sat, sat: cloneState.color.sat,
l: cloneState.color.l l: cloneState.color.l
}; };
this.effect = cloneState.effect;
} }
static get default() { static get default() {
@ -23,7 +24,8 @@ class ColorlampState extends BaseState {
hue: 0, hue: 0,
sat: 0, sat: 0,
l: 0 l: 0
} },
effect: "none"
}; };
} }
@ -35,7 +37,8 @@ class ColorlampState extends BaseState {
hue: this.color.hue, hue: this.color.hue,
sat: this.color.sat, sat: this.color.sat,
l: this.color.l l: this.color.l
} },
effect: this.effect
}; };
} }
} }