243 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			9.4 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 for ${this.#watchItem.name} triggered without openHAB event`);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        console.log(`Processing state ${this.#watchItem.state} for ${this.#watchItem.name}`);
 | 
						|
 | 
						|
        // 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,
 | 
						|
};
 |