Initial commit of files
This commit is contained in:
34
equipmentMgr.js
Normal file
34
equipmentMgr.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
console.loggerName = 'js.equipmentMgr';
|
||||||
|
console.log('Load equipmentMgr');
|
||||||
|
|
||||||
|
const {
|
||||||
|
equipment,
|
||||||
|
watch
|
||||||
|
} = require('../utils');
|
||||||
|
|
||||||
|
|
||||||
|
const eMgr = new Object();
|
||||||
|
|
||||||
|
for (let equipmentItem of items.getItems().filter(element => { return element.semantics.isEquipment })) {
|
||||||
|
//for (let equipmentItem of items.getItems().filter(element => { return (element.semantics.isEquipment == true) && (element.semantics.equipment == null) })) {
|
||||||
|
// Fetch equipment type
|
||||||
|
let equipmentType = equipmentItem.tags.filter(element => Object.keys(equipment).includes(element));
|
||||||
|
if (equipmentType.length > 1) {
|
||||||
|
equipmentType.splice(equipmentType.indexOf('Equipment'), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize equipment class
|
||||||
|
eMgr[equipmentItem.name] = new equipment[equipmentType](equipmentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.shared.put('eMgr', eMgr);
|
||||||
|
|
||||||
|
require('@runtime').lifecycleTracker.addDisposeHook(() => {
|
||||||
|
console.log('Deinitialization of equipmentMgr');
|
||||||
|
|
||||||
|
for (let equipmentItemName of Object.keys(eMgr)) {
|
||||||
|
eMgr[equipmentItemName].gc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
67
utils/equipment.js
Normal file
67
utils/equipment.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
console.loggerName = 'js.equipment';
|
||||||
|
console.log('Load equipment modules');
|
||||||
|
|
||||||
|
class Equipment {
|
||||||
|
constructor(equipmentItem) {
|
||||||
|
console.info('Initialization of eqipment ' + equipmentItem.name + ' with type ' + this.constructor.name);
|
||||||
|
|
||||||
|
// Set equipmentItem
|
||||||
|
this.equipmentItem = equipmentItem;
|
||||||
|
|
||||||
|
// Set stateItem, toDo: error when no stateItem existing
|
||||||
|
this.stateItem = items.getItem(this.equipmentItem.name + '_State');
|
||||||
|
|
||||||
|
// Initialization of properties
|
||||||
|
this.name = this.equipmentItem.name;
|
||||||
|
this.watch = new Object();
|
||||||
|
}
|
||||||
|
|
||||||
|
gc() {
|
||||||
|
console.log('Denitialization of eqipment ' + this.name);
|
||||||
|
|
||||||
|
// Delete all watchObjects
|
||||||
|
for (let watchItem of Object.keys(this.watch)) {
|
||||||
|
this.watch[watchItem].deleteAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Irrigation extends Equipment {
|
||||||
|
constructor(equipmentItem) {
|
||||||
|
super(equipmentItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IrrigationValve extends Equipment {
|
||||||
|
constructor(equipmentItem) {
|
||||||
|
super(equipmentItem);
|
||||||
|
|
||||||
|
this.watch['state'] = new watch.Watch(this.stateItem.name);
|
||||||
|
this.autoOff = this.watch['state'].add({
|
||||||
|
targetState: 'ON',
|
||||||
|
alertFunc: () => { this.stateItem.sendCommand('OFF'); },
|
||||||
|
alertDelay: 'PT1M'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TowelRadiator extends Equipment {
|
||||||
|
constructor(equipmentItem) {
|
||||||
|
super(equipmentItem);
|
||||||
|
|
||||||
|
this.watch['state'] = new watch.Watch(this.stateItem.name);
|
||||||
|
this.autoOff = this.watch['state'].add({
|
||||||
|
targetState: 'ON',
|
||||||
|
alertFunc: () => { this.stateItem.sendCommand('OFF'); },
|
||||||
|
alertDelay: 'PT30M'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Equipment,
|
||||||
|
Irrigation,
|
||||||
|
IrrigationValve,
|
||||||
|
TowelRadiator
|
||||||
|
};
|
||||||
7
utils/index.js
Normal file
7
utils/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
console.loggerName = 'js.utils';
|
||||||
|
console.log('Load utils')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get equipment() { return require('./equipment') },
|
||||||
|
get watch() { return require('./watch') },
|
||||||
|
}
|
||||||
288
utils/watch.js
Normal file
288
utils/watch.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
console.loggerName = 'js.watch';
|
||||||
|
console.log('Load watch module');
|
||||||
|
|
||||||
|
class Watch {
|
||||||
|
|
||||||
|
#item
|
||||||
|
#watchItemName
|
||||||
|
#watchObjects = new Object();
|
||||||
|
|
||||||
|
constructor(itemName) {
|
||||||
|
console.log('Create new Watch instance for ' + itemName);
|
||||||
|
|
||||||
|
// Check if item to watch is existing
|
||||||
|
this.#item = items.getItem(itemName, true);
|
||||||
|
if (this.#item == null) {
|
||||||
|
throw('Item ' + itemName + ' not existing');
|
||||||
|
}
|
||||||
|
this.#watchItemName = itemName;
|
||||||
|
|
||||||
|
// Create watch rule
|
||||||
|
this.#createWatchRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(params) {
|
||||||
|
// Generate watchUUID
|
||||||
|
let watchUUID = utils.randomUUID();
|
||||||
|
|
||||||
|
// Validate config for watchObject
|
||||||
|
if (!this.validateWatchConfig(params)) {
|
||||||
|
console.warn('Failed to add watch object for ' + this.#watchItemName + ' because no valid config was provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default values if no config provided
|
||||||
|
let operator = (params['operator'] !== undefined) ? params['operator'] : '==';
|
||||||
|
|
||||||
|
// Create watch object and return UUID
|
||||||
|
console.log('Add watch object for item ' + this.#watchItemName + ' with state ' + params['targetState'] + ' and operator ' + operator + ' with UUID ' + watchUUID);
|
||||||
|
this.#watchObjects[watchUUID] = {
|
||||||
|
targetState: this.#stateToValue(params['targetState']),
|
||||||
|
operator: operator,
|
||||||
|
alertFunc: params['alertFunc'],
|
||||||
|
alertDelay: (params['alertDelay'] !== undefined) ? params['alertDelay'] : 0,
|
||||||
|
alertRepeat: (params['alertRepeat'] !== undefined) ? params['alertRepeat'] : 0,
|
||||||
|
initialAlertFunc: (params['initialAlertFunc'] !== undefined) ? params['initialAlertFunc'] : '',
|
||||||
|
endAlertFunc: (params['endAlertFunc'] !== undefined) ? params['endAlertFunc'] : '',
|
||||||
|
hysteresis: (params['hysteresis'] !== undefined) ? this.#stateToValue(params['hysteresis']) : '',
|
||||||
|
alert: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if watchObject is already triggered by currentState
|
||||||
|
this.#checkAlertState(watchUUID);
|
||||||
|
|
||||||
|
// return id of watchObject
|
||||||
|
return watchUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(watchUUID) {
|
||||||
|
console.log('Delete watch object for item ' + this.#watchItemName + ' with watchUUID ' + watchUUID);
|
||||||
|
|
||||||
|
// End repeatAlertTimer if existing
|
||||||
|
if (this.#watchObjects[watchUUID].hasOwnProperty('repeatAlertTimer') && this.#watchObjects[watchUUID].repeatAlertTimer.isActive()) {
|
||||||
|
console.log('Cancel repeatAlertTimer for ' + watchUUID);
|
||||||
|
this.#watchObjects[watchUUID].repeatAlertTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End startAlertTimer if existing
|
||||||
|
if (this.#watchObjects[watchUUID].hasOwnProperty('startAlertTimer') && this.#watchObjects[watchUUID].startAlertTimer.isActive()) {
|
||||||
|
console.log('Cancel startAlertTimer for ' + watchUUID);
|
||||||
|
this.#watchObjects[watchUUID].startAlertTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.#watchObjects[watchUUID];
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAll() {
|
||||||
|
for (let watchUUID of Object.keys(this.#watchObjects)) {
|
||||||
|
this.delete(watchUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateWatchConfig(params) {
|
||||||
|
|
||||||
|
if (params['targetState'] === undefined) {
|
||||||
|
console.error('No targetState set');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (params['alertFunc'] === undefined) {
|
||||||
|
console.error('No alertFunc set');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if config is valid
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
#applyHysteresis(currentState, targetState, hysteresis) {
|
||||||
|
console.log('Applying hysteresis with: ' + currentState + ', ' + targetState + ', ' + hysteresis);
|
||||||
|
let delta = Math.abs(targetState - currentState);
|
||||||
|
console.log(delta);
|
||||||
|
|
||||||
|
if (delta < hysteresis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#checkAlertState(watchUUID) {
|
||||||
|
let currentState = this.#stateToValue(this.#item.state);
|
||||||
|
|
||||||
|
// Do comparison
|
||||||
|
if (this.#compare(currentState, this.#watchObjects[watchUUID].targetState, this.#watchObjects[watchUUID].operator)) { // Comparison successful
|
||||||
|
console.log('State ' + currentState + ' is ' + this.#watchObjects[watchUUID].operator + ' ' + this.#watchObjects[watchUUID].targetState + ' triggered by ' + watchUUID);
|
||||||
|
if (this.#watchObjects[watchUUID].alert == true) { // Comparison successful and alert is already active
|
||||||
|
this.#rescheduleAlert(watchUUID);
|
||||||
|
} else { // Comparison successful and alert is not active
|
||||||
|
// Start alert
|
||||||
|
this.#startAlert(watchUUID);
|
||||||
|
}
|
||||||
|
} else if (this.#watchObjects[watchUUID].alert == true) { // Comparison failed but alert is active
|
||||||
|
// Skip if currentState fails hysteresis test
|
||||||
|
if (this.#watchObjects[watchUUID].hysteresis != '' && !this.#applyHysteresis(currentState, this.#watchObjects[watchUUID].targetState, this.#watchObjects[watchUUID].hysteresis)) {
|
||||||
|
console.log('CurrentState is still in boundaries provided by hysteresis value');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// End alert
|
||||||
|
this.#endAlert(watchUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#compare(a, b, operator) {
|
||||||
|
|
||||||
|
// Hint: a = currentState, b = targetState
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
case '==':
|
||||||
|
return (a == b);
|
||||||
|
break;
|
||||||
|
case '!=':
|
||||||
|
return !(a == b);
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
return (a > b);
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
return (a < b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#createWatchRule() {
|
||||||
|
// Create openHAB rule
|
||||||
|
console.log('Create openHAB watch rule for item ' + this.#watchItemName);
|
||||||
|
let ruleID = rules.JSRule({
|
||||||
|
name: 'Watch rule for ' + this.#watchItemName,
|
||||||
|
triggers: [triggers.ItemStateChangeTrigger(this.#watchItemName)],
|
||||||
|
execute: (event) => { this.#processStateChange(event) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endAlert(watchUUID) {
|
||||||
|
console.log('End alert for watchObject ' + watchUUID + ' triggered');
|
||||||
|
this.#watchObjects[watchUUID].alert = false;
|
||||||
|
|
||||||
|
// Run end alert function if existing
|
||||||
|
if (this.#watchObjects[watchUUID]. endAlertFunc != '') {
|
||||||
|
console.log('Run end alert function for watchObject ' + watchUUID);
|
||||||
|
this.#watchObjects[watchUUID]. endAlertFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End repeatAlertTimer if existing
|
||||||
|
if (this.#watchObjects[watchUUID].hasOwnProperty('repeatAlertTimer') && this.#watchObjects[watchUUID].repeatAlertTimer.isActive()) {
|
||||||
|
console.log('Cancel repeatAlertTimer');
|
||||||
|
this.#watchObjects[watchUUID].repeatAlertTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// End startAlertTimer if startAlert started timer with delay
|
||||||
|
if (this.#watchObjects[watchUUID].startAlertTimer.isActive()) {
|
||||||
|
console.log('Cancel startAlertTimer');
|
||||||
|
this.#watchObjects[watchUUID].startAlertTimer.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#processStateChange(event) {
|
||||||
|
// Skip if function is triggered without openHAB event
|
||||||
|
if (event === undefined || event.eventType === undefined) {
|
||||||
|
console.warn('ProcessStateChange triggered without openHAB event');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Processing state ' + this.#item.state + ' for ' + this.#watchItemName);
|
||||||
|
|
||||||
|
// Iterate through watchObjetcs // todo: rework to only fetch UUID
|
||||||
|
for (let [watchUUID, watchObject] of Object.entries(this.#watchObjects)) {
|
||||||
|
this.#checkAlertState(watchUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#rescheduleAlert(watchUUID) {
|
||||||
|
console.log('Subsequent alert for ' + watchUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#startAlert(watchUUID) {
|
||||||
|
console.log('Initial alert for watchObject ' + watchUUID + ' triggered');
|
||||||
|
|
||||||
|
// Set alertFunc as inital alert function if no initialAlertFunc is set
|
||||||
|
let initialAlertFunc = this.#watchObjects[watchUUID].alertFunc;
|
||||||
|
if (this.#watchObjects[watchUUID].initialAlertFunc != '') {
|
||||||
|
initialAlertFunc = this.#watchObjects[watchUUID].initialAlertFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create timer to run initial alert rule
|
||||||
|
this.#watchObjects[watchUUID].alert = true;
|
||||||
|
this.#watchObjects[watchUUID].startAlertTimer = actions.ScriptExecution.createTimer('startAlert ' + watchUUID,
|
||||||
|
time.toZDT(this.#watchObjects[watchUUID].alertDelay),
|
||||||
|
() => {
|
||||||
|
console.log('Run initial alert function for watchObject ' + watchUUID);
|
||||||
|
initialAlertFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create timer to run repeat alert rule if required
|
||||||
|
if (this.#watchObjects[watchUUID].alertRepeat != '') {
|
||||||
|
console.log('wiederholter alert notwendig');
|
||||||
|
this.#watchObjects[watchUUID].repeatAlertTimer = actions.ScriptExecution.createTimer('repeatAlarm ' + watchUUID,
|
||||||
|
time.toZDT(this.#watchObjects[watchUUID].alertDelay + this.#watchObjects[watchUUID].alertRepeat),
|
||||||
|
() => {
|
||||||
|
console.log('Run repeat alert function for watchObject ' + watchUUID);
|
||||||
|
this.#watchObjects[watchUUID].alertFunc();
|
||||||
|
this.#watchObjects[watchUUID].repeatAlertTimer.reschedule(time.ZonedDateTime.now().plusSeconds(this.#watchObjects[watchUUID].alertRepeat));
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#stateToValue(state) {
|
||||||
|
console.log('Converting value ' + state + ' of type ' + typeof state);
|
||||||
|
if(typeof state === 'string') {
|
||||||
|
if(state.includes(' ')) {
|
||||||
|
try {
|
||||||
|
console.log('Quantity: ' + state);
|
||||||
|
return Quantity(state)
|
||||||
|
} catch(e) {
|
||||||
|
// do nothing, leave it as a String
|
||||||
|
console.log('Not a Quantity but has a space, leaving as a string: ' + state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!isNaN(state)) {
|
||||||
|
console.log('Number: ' + state)
|
||||||
|
return Number.parseFloat(state);
|
||||||
|
}
|
||||||
|
else if(state == 'UnDefType' || state == 'NULL' || state == 'UNDEF') {
|
||||||
|
console.log('UnDefType: ' + state);
|
||||||
|
return 'UnDefType';
|
||||||
|
}
|
||||||
|
console.log('String: ' + state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
else if(state instanceof DecimalType || state instanceof PercentType) {
|
||||||
|
console.log('DecimalType or PercentType: ' + state);
|
||||||
|
return state.floatValue();
|
||||||
|
}
|
||||||
|
else if(state instanceof QuantityType) {
|
||||||
|
console.log('QuantityType: ' + state);
|
||||||
|
return Quantity(state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('String: ' + state);
|
||||||
|
return state.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Watch,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
let test = new Watch('GB_TowelRadiator_State');
|
||||||
|
test.add({
|
||||||
|
targetState: 'OFF',
|
||||||
|
alertFunc: () => { console.log('Alle Achtung!'); },
|
||||||
|
});
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user