243 lines
9.3 KiB
JavaScript
243 lines
9.3 KiB
JavaScript
console.loggerName = 'js.watch';
|
|
console.log('Load watch module');
|
|
|
|
class Watch {
|
|
|
|
#watchItem
|
|
#watchObjects = new Object();
|
|
|
|
#timers = new timer.Timer();
|
|
|
|
constructor(watchItem) {
|
|
|
|
// Fetch item if provided itemName is a string
|
|
if (typeof watchItem === 'string' || watchItem instanceof String) {
|
|
this.#watchItem = items[watchItem];
|
|
} else {
|
|
this.#watchItem = watchItem;
|
|
}
|
|
|
|
// Throw error if item is not existing
|
|
if (this.#watchItem === null) {
|
|
throw(`Item ${watchItem} not existing`);
|
|
}
|
|
|
|
console.log(`Create new Watch instance for ${this.#watchItem.name}`);
|
|
|
|
// 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.#watchItem.name} 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.#watchItem.name} with state ${params['targetState']} and operator ${operator} with UUID ${watchUUID}`);
|
|
this.#watchObjects[watchUUID] = {
|
|
targetState: 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) ? lib.convertValue(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.#watchItem.name} with watchUUID ${watchUUID}`);
|
|
|
|
// End repeatAlertTimer if existing
|
|
this.#timers.cancel('repeatAlarm ' + watchUUID);
|
|
|
|
// End startAlertTimer if existing
|
|
this.#timers.cancel('startAlert ' + watchUUID);
|
|
|
|
delete this.#watchObjects[watchUUID];
|
|
|
|
// Delete watchRule if no more watchObjects are present
|
|
if (Object.keys(this.#watchObjects).length == 0) {
|
|
console.debug(`Remove openHAB watch rule for item ${this.#watchItem.name}`);
|
|
rules.removeRule(this.watchRuleID);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
console.debug(`Check if item is in alert state for watchObject ${watchUUID}`)
|
|
|
|
// Convert currentState for comparison
|
|
let currentState = lib.convertValue(this.#watchItem.state);
|
|
|
|
// Do comparison
|
|
if (lib.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);
|
|
}
|
|
}
|
|
|
|
#createWatchRule() {
|
|
// Create openHAB rule
|
|
console.log(`Create openHAB watch rule for item ${this.#watchItem.name}`);
|
|
this.watchRuleID = String(utils.randomUUID());
|
|
rules.JSRule({
|
|
id: this.watchRuleID,
|
|
name: 'Watch rule for ' + this.#watchItem.name,
|
|
triggers: [triggers.ItemStateChangeTrigger(this.#watchItem.name)],
|
|
execute: (event) => { this.#processItemEvent(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
|
|
this.#timers.cancel('repeatAlarm ' + watchUUID);
|
|
|
|
// End startAlertTimer if startAlert started timer with delay
|
|
this.#timers.cancel('startAlert ' + watchUUID);
|
|
}
|
|
|
|
#processItemEvent(event) {
|
|
// Skip if function is triggered without openHAB event
|
|
if (event === undefined || event.eventType === undefined) {
|
|
console.warn(`ProcessItemEvent triggered without openHAB event`);
|
|
return;
|
|
}
|
|
|
|
console.log(`Processing state ${event.newState} for ${event.itemName}`);
|
|
|
|
// 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;
|
|
}
|
|
|
|
let alertDelay = this.#watchObjects[watchUUID].alertDelay;
|
|
let alertRepeat = this.#watchObjects[watchUUID].alertRepeat;
|
|
let alertDelayAbsolute = 0;
|
|
|
|
// Set alert state
|
|
this.#watchObjects[watchUUID].alert = true;
|
|
|
|
// Execute initial alert function or create timer to run initial alert rule if required
|
|
if (alertDelay == '') {
|
|
console.log(`Run initial alert function for watchObject ${watchUUID}`);
|
|
initialAlertFunc();
|
|
} else {
|
|
console.log(`Shedule initial alert function for watchObject ${watchUUID} with delay setting ${alertDelay}`);
|
|
|
|
let alertDelayZDT = time.toZDT(alertDelay);
|
|
alertDelayAbsolute = (alertDelayZDT.getMillisFromNow() / 1000);
|
|
|
|
this.#timers.create('startAlert ' + watchUUID,
|
|
alertDelayZDT,
|
|
() => {
|
|
console.log('Run initial alert function for watchObject ' + watchUUID);
|
|
initialAlertFunc();
|
|
}
|
|
);
|
|
}
|
|
|
|
// Create timer to run repeat alert rule if required
|
|
if (alertRepeat != '') {
|
|
console.log(`Shedule repeat alert function for watchObject ${watchUUID} with repeat setting ${alertRepeat}`);
|
|
this.#timers.create('repeatAlarm ' + watchUUID,
|
|
time.toZDT(alertRepeat).plusSeconds(alertDelayAbsolute),
|
|
() => {
|
|
console.log('Run repeat alert function for watchObject ' + watchUUID);
|
|
this.#watchObjects[watchUUID].alertFunc();
|
|
this.#watchObjects[watchUUID].repeatAlertTimer.reschedule(time.toZDT(alertRepeat));
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
Watch,
|
|
};
|