Compare commits
	
		
			5 Commits
		
	
	
		
			13bbf41d0b
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fd69b4de1b | ||
| 
						 | 
					d2b9359fee | ||
| 
						 | 
					b5eb470609 | ||
| 
						 | 
					475d238ea3 | ||
| 
						 | 
					c5ed9c9c44 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					node_modules/
 | 
				
			||||||
 | 
					package.json
 | 
				
			||||||
 | 
					package-lock.json
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
// Settings
 | 
					// Settings
 | 
				
			||||||
const ITEMPREFIX = "System_StatefulScene";
 | 
					const ITEMPREFIX = "System_StatefulScene";
 | 
				
			||||||
const DEBOUNCETIME = 3000;
 | 
					const DEBOUNCETIME = 3000;
 | 
				
			||||||
const LOGGER = "org.openhab.js.SceneController";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Logging
 | 
					// Logging
 | 
				
			||||||
const log = Java.type('org.slf4j.LoggerFactory').getLogger(LOGGER);
 | 
					const log = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.js.utils.SceneMgr');
 | 
				
			||||||
 | 
					console.loggerName = 'org.openhab.js.utils.SceneMgr';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Load dependencies
 | 
					// Load dependencies
 | 
				
			||||||
const { TimerMgr } = require('../utils');
 | 
					const { TimerMgr } = require('../utils');
 | 
				
			||||||
@@ -13,14 +13,18 @@ const tm = new TimerMgr();
 | 
				
			|||||||
const { ruleRegistry } = require('@runtime/RuleSupport');
 | 
					const { ruleRegistry } = require('@runtime/RuleSupport');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Functions
 | 
					// Functions
 | 
				
			||||||
class SceneController {
 | 
					class SceneMgr {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor(itemPrefix, debounceTime) {
 | 
				
			||||||
        // Log the initialization message for SceneController.
 | 
					        // Load class properties
 | 
				
			||||||
        log.info('Initialization of SceneController');
 | 
					        this.itemPrefix = itemPrefix;
 | 
				
			||||||
 | 
					        this.debounceTime = debounceTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Log the initialization message for SceneMgr
 | 
				
			||||||
 | 
					        console.log('Initialization of SceneMgr');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initialize the scenes map by reducing the array of scenes into an object with unique IDs as keys.
 | 
					        // Initialize the scenes map by reducing the array of scenes into an object with unique IDs as keys.
 | 
				
			||||||
        this.scenes = this.getScenes().reduce((scenes, scene) => {
 | 
					        this.scenes = this.#getStatefulScenes().reduce((scenes, scene) => {
 | 
				
			||||||
            const sceneUID = scene.getUID();
 | 
					            const sceneUID = scene.getUID();
 | 
				
			||||||
            scenes[sceneUID] = new Scene(scene);
 | 
					            scenes[sceneUID] = new Scene(scene);
 | 
				
			||||||
            return scenes;
 | 
					            return scenes;
 | 
				
			||||||
@@ -48,17 +52,19 @@ class SceneController {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getScenes() {
 | 
					    #getStatefulScenes() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Fetch all rules from ruleRegistry
 | 
				
			||||||
        const allRules = utils.javaSetToJsArray(ruleRegistry.getAll());
 | 
					        const allRules = utils.javaSetToJsArray(ruleRegistry.getAll());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Filter rules relating to stateful scenes depending on the respective tags
 | 
				
			||||||
        return allRules.filter(rule => {
 | 
					        return allRules.filter(rule => {
 | 
				
			||||||
            const ruleTags = utils.javaSetToJsArray(rule.getTags());
 | 
					            const ruleTags = utils.javaSetToJsArray(rule.getTags());
 | 
				
			||||||
            const hasSceneTag = ruleTags.includes("Scene");
 | 
					            const hasSceneTag = ruleTags.includes("Scene");
 | 
				
			||||||
            const hasStatefulTag = ruleTags.includes("Stateful");
 | 
					            const hasStatefulTag = ruleTags.includes("Stateful");
 | 
				
			||||||
            const isEnabled = rules.isEnabled(rule.getUID());
 | 
					            const isEnabled = rules.isEnabled(rule.getUID());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return hasSceneTag && hasStatefulTag && isEnabled;
 | 
					            return hasSceneTag && hasStatefulTag && isEnabled; // Todo: refactoring enabled
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,25 +72,30 @@ class SceneController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Scene {
 | 
					class Scene {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(scene) {
 | 
					    #isActive = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(scene) {
 | 
				
			||||||
 | 
					        // Load class properties
 | 
				
			||||||
        this.scene = scene;
 | 
					        this.scene = scene;
 | 
				
			||||||
        this.sceneUID = this.scene.getUID();
 | 
					        this.sceneUID = this.scene.getUID();
 | 
				
			||||||
        this.sceneName = this.scene.getName();
 | 
					        this.sceneName = this.scene.getName();
 | 
				
			||||||
        this.sceneSwitchItemName = ITEMPREFIX + "_" + this.sceneUID;
 | 
					        this.sceneSwitchItemName = ITEMPREFIX + "_" + this.sceneUID;
 | 
				
			||||||
        this.evaluationRuleUID = "SceneItems" + this.sceneUID;
 | 
					        this.evaluationRuleUID = "SceneItems" + this.sceneUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.info(`Initialize scene ${this.sceneName}`);
 | 
					        // Log the initialization of the scene
 | 
				
			||||||
 | 
					        console.log(`Initialize scene ${this.sceneName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create switchItem for scene if it does not exits
 | 
				
			||||||
        if (!items[this.sceneSwitchItemName]) {
 | 
					        if (!items[this.sceneSwitchItemName]) {
 | 
				
			||||||
            this.createSceneSwitchItem();
 | 
					            this.createSceneSwitchItem();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Create required scene rules for the scene
 | 
				
			||||||
        this.createSceneRules();
 | 
					        this.createSceneRules();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    createSceneSwitchItem() {
 | 
					    createSceneSwitchItem() {
 | 
				
			||||||
        log.info(`Creating scene switch for ${this.sceneSwitchItemName}`);
 | 
					        console.log(`${this.sceneName}: Create scene switch with item name ${this.sceneSwitchItemName}`);
 | 
				
			||||||
        items.addItem({
 | 
					        items.addItem({
 | 
				
			||||||
            name: this.sceneSwitchItemName,
 | 
					            name: this.sceneSwitchItemName,
 | 
				
			||||||
            type: 'Switch',
 | 
					            type: 'Switch',
 | 
				
			||||||
@@ -116,35 +127,21 @@ class Scene {
 | 
				
			|||||||
            id: this.evaluationRuleUID,
 | 
					            id: this.evaluationRuleUID,
 | 
				
			||||||
            overwrite: true
 | 
					            overwrite: true
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        this.disableEvaluationRule();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
    disableEvaluationRule() {
 | 
					 | 
				
			||||||
        // Log the action using template literals for cleaner string interpolation
 | 
					 | 
				
			||||||
        log.debug(`Disable evaluation rule for scene ${this.sceneName}`);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Disable the evaluation rule by setting its enabled state to false
 | 
					 | 
				
			||||||
        rules.setEnabled(this.evaluationRuleUID, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enableEvaluationRule() {
 | 
					 | 
				
			||||||
        // Log the action using template literals for better readability
 | 
					 | 
				
			||||||
        log.info(`Enable evaluation rule for scene ${this.sceneName}`);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Enable the evaluation rule by setting its enabled state to true
 | 
					 | 
				
			||||||
        rules.setEnabled(this.evaluationRuleUID, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    evaluateScene() {
 | 
					    evaluateScene() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const sceneUID = this.scene.getUID();
 | 
					        const sceneUID = this.scene.getUID();
 | 
				
			||||||
        const sceneName = this.scene.getName();
 | 
					 | 
				
			||||||
        const timerUID = "EvaluateScene_" + sceneUID;
 | 
					        const timerUID = "EvaluateScene_" + sceneUID;
 | 
				
			||||||
        const sceneSwitchItemName = ITEMPREFIX + "_" + sceneUID;
 | 
					
 | 
				
			||||||
 | 
					        if (!this.#isActive) {
 | 
				
			||||||
 | 
					            console.debug(`Scene ${this.sceneName}: Skip evaluation because scene is not active`);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const evaluate = () => {
 | 
					        const evaluate = () => {
 | 
				
			||||||
            log.info(`Evaluate scene ${sceneName}`);
 | 
					            log.info(`Evaluate scene ${this.sceneName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const scenceConfig = this.getSceneConfig();
 | 
					            const scenceConfig = this.getSceneConfig();
 | 
				
			||||||
            let conditionsFailed = false;
 | 
					            let conditionsFailed = false;
 | 
				
			||||||
@@ -166,24 +163,19 @@ class Scene {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (conditionsFailed) {
 | 
					            if (conditionsFailed) {
 | 
				
			||||||
                log.info(`Switch off sceneSwitch for scene ${sceneName} because conditions failed`);
 | 
					                log.info(`Switch off sceneSwitch for scene ${this.sceneName} because conditions failed`);
 | 
				
			||||||
                items[sceneSwitchItemName].postUpdate("OFF");
 | 
					                items[this.sceneSwitchItemName].postUpdate("OFF");
 | 
				
			||||||
                this.disableEvaluationRule();
 | 
					                this.#isActive = false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (items[sceneSwitchItemName].state === "OFF") {
 | 
					 | 
				
			||||||
            log.info(`Skip evaluation of scene ${sceneName} because scene is off`);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const timeout = time.ZonedDateTime.now().plusNanos(DEBOUNCETIME * 1000000);
 | 
					        const timeout = time.ZonedDateTime.now().plusNanos(DEBOUNCETIME * 1000000);
 | 
				
			||||||
        if (tm.hasTimer(timerUID) && tm.isActive(timerUID)) {
 | 
					        if (tm.hasTimer(timerUID) && tm.isActive(timerUID)) {
 | 
				
			||||||
            log.info(`Skip evaluation of scene ${sceneName} as evaluation function is already running. Reschedule timer`);
 | 
					            log.info(`Skip evaluation of scene ${this.sceneName} as evaluation function is already running. Reschedule timer`);
 | 
				
			||||||
            tm.reschedule(timerUID, timeout);
 | 
					            tm.reschedule(timerUID, timeout);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            log.info(`Create timer to evaluate scene ${sceneName}`);
 | 
					            log.info(`Create timer to evaluate scene ${this.sceneName}`);
 | 
				
			||||||
            if (tm.hasTimer(timerUID)) {
 | 
					            if (tm.hasTimer(timerUID)) {
 | 
				
			||||||
                tm.cancel(timerUID);
 | 
					                tm.cancel(timerUID);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -233,39 +225,39 @@ class Scene {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                rules.runRule(this.sceneUID);
 | 
					                rules.runRule(this.sceneUID);
 | 
				
			||||||
                java.lang.Thread.sleep(2500);
 | 
					                this.#isActive = true;
 | 
				
			||||||
                this.enableEvaluationRule();
 | 
					 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case "OFF":
 | 
					            case "OFF":
 | 
				
			||||||
                log.info("Deactivate scene " + this.sceneName);
 | 
					                console.debug(`Scene ${this.sceneName}: Deactivate scene`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (this.hasRestoreEnabled()) {
 | 
					                if (this.hasRestoreEnabled()) {
 | 
				
			||||||
                    log.info("Restore is enabled");
 | 
					                    log.debug(`Scene ${this.sceneName}: Restore previous values before scene was activated`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let timestamp = this.switchedOn.minusNanos(DEBOUNCETIME * 1000000)
 | 
					                    let timestamp = this.switchedOn.minusNanos(DEBOUNCETIME * 1000000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    for (const sceneItem of this.getSceneItems()) {
 | 
					                    for (const sceneItemName of this.getSceneItems()) {
 | 
				
			||||||
                        let historicSceneItem = items[sceneItem].persistence.persistedState(timestamp);
 | 
					                        let historicSceneItem = items[sceneItemName].persistence.persistedState(timestamp);
 | 
				
			||||||
                        let historicSceneItemState = historicSceneItem.state
 | 
					
 | 
				
			||||||
                        items[sceneItem].sendCommandIfDifferent(historicSceneItemState);
 | 
					                        if (historicSceneItem !== null) {
 | 
				
			||||||
 | 
					                            items[sceneItemName].sendCommandIfDifferent(historicSceneItem.state);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            console.warn(`Scene ${this.sceneName}: No previous value found for ${sceneItemName}`);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                this.disableEvaluationRule();
 | 
					                this.#isActive = false;
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Load script
 | 
					function getSceneMgr() {
 | 
				
			||||||
const sm = new SceneController();
 | 
					    return new SceneMgr();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Unload script
 | 
					module.exports = {
 | 
				
			||||||
require('@runtime').lifecycleTracker.addDisposeHook(() => {
 | 
					    getSceneMgr
 | 
				
			||||||
    log.info('Deinitialization of SceneController');
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
    sm.purgeUnusedSceneSwitchItems();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tm.cancelAll();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										51
									
								
								utils/helpers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								utils/helpers.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					// Load dependencies
 | 
				
			||||||
 | 
					const { TimerMgr } = require('../utils');
 | 
				
			||||||
 | 
					const tm = new TimerMgr();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function debounceFunction(id, debounceTimeout, func, params) {
 | 
				
			||||||
 | 
					    console.warn('debounce', id, debounceTimeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timeout = time.ZonedDateTime.now().plusNanos(debounceTimeout * 1000000);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (tm.hasTimer(id) && tm.isActive(id)) {
 | 
				
			||||||
 | 
					        log.info(`Skip evaluation of function with id ${id} as function is already running. Reschedule timer`);
 | 
				
			||||||
 | 
					        tm.reschedule(id, timeout);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        log.info(`Create timer to execute function ${id}`);
 | 
				
			||||||
 | 
					        if (tm.hasTimer(id)) {
 | 
				
			||||||
 | 
					            tm.cancel(id);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        tm.create(id, timeout, func, params);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function distanceFrom(locationA, locationB) {
 | 
				
			||||||
 | 
					    const [lat1, lon1] = locationA.split(',').map(Number);
 | 
				
			||||||
 | 
					    const [lat2, lon2] = locationB.split(',').map(Number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const toRadians = (degree) => degree * (Math.PI / 180);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const radLat1 = toRadians(lat1);
 | 
				
			||||||
 | 
					    const radLon1 = toRadians(lon1);
 | 
				
			||||||
 | 
					    const radLat2 = toRadians(lat2);
 | 
				
			||||||
 | 
					    const radLon2 = toRadians(lon2);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (lat1 === lat2 && lon1 === lon2) return '0 m';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dlon = radLon2 - radLon1;
 | 
				
			||||||
 | 
					    const dlat = radLat2 - radLat1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const a = Math.pow(Math.sin(dlat / 2), 2)
 | 
				
			||||||
 | 
					            + Math.cos(radLat1) * Math.cos(radLat2)
 | 
				
			||||||
 | 
					            * Math.pow(Math.sin(dlon / 2), 2);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    const c = 2 * Math.asin(Math.sqrt(a));
 | 
				
			||||||
 | 
					    const r = 6371000; // Radius of earth in meters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return `${(c * r).toFixed()} m`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					    debounceFunction,
 | 
				
			||||||
 | 
					    distanceFrom
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -3,5 +3,7 @@ console.loggerName = "org.openhab.js.utils";
 | 
				
			|||||||
console.log("Loading utils");
 | 
					console.log("Loading utils");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    get TimerMgr() { return require('./TimerMgr.js').getTimerMgr; }
 | 
					    get TimerMgr() { return require('./TimerMgr.js').getTimerMgr; },
 | 
				
			||||||
 | 
					    get SceneMgr() { return require('./SceneMgr.js').getSceneMgr; },
 | 
				
			||||||
 | 
					    get helpers() { return require('./helpers.js') }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user