refactor(SceneController): rename SceneMgr to SceneController
This commit is contained in:
		
							
								
								
									
										271
									
								
								SceneController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								SceneController.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
// Settings
 | 
			
		||||
const ITEMPREFIX = "System_StatefulScene";
 | 
			
		||||
const DEBOUNCETIME = 3000;
 | 
			
		||||
const LOGGER = "org.openhab.js.SceneController";
 | 
			
		||||
 | 
			
		||||
// Logging
 | 
			
		||||
const log = Java.type('org.slf4j.LoggerFactory').getLogger(LOGGER);
 | 
			
		||||
 | 
			
		||||
// Load dependencies
 | 
			
		||||
const { TimerMgr } = require('../utils');
 | 
			
		||||
const tm = new TimerMgr();
 | 
			
		||||
 | 
			
		||||
const { ruleRegistry } = require('@runtime/RuleSupport');
 | 
			
		||||
 | 
			
		||||
// Functions
 | 
			
		||||
class SceneController {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        // Log the initialization message for SceneController.
 | 
			
		||||
        log.info('Initialization of SceneController');
 | 
			
		||||
 | 
			
		||||
        // 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) => {
 | 
			
		||||
            const sceneUID = scene.getUID();
 | 
			
		||||
            scenes[sceneUID] = new Scene(scene);
 | 
			
		||||
            return scenes;
 | 
			
		||||
        }, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isSceneEnabled(sceneUID) {
 | 
			
		||||
        try {
 | 
			
		||||
            return rules.isEnabled(sceneUID);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    purgeUnusedSceneSwitchItems() {
 | 
			
		||||
        log.debug("Purge unused scene switch items");
 | 
			
		||||
 | 
			
		||||
        for (const sceneSwitchItem of items.getItemsByTag("Stateful")) {
 | 
			
		||||
            const ruleUID = sceneSwitchItem.name.split("_")[2];
 | 
			
		||||
 | 
			
		||||
            if (!this.isSceneEnabled(ruleUID)) {
 | 
			
		||||
                log.debug("Remove unused scene switch item " + sceneSwitchItem.name);
 | 
			
		||||
                items.removeItem(sceneSwitchItem);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getScenes() {
 | 
			
		||||
 | 
			
		||||
        const allRules = utils.javaSetToJsArray(ruleRegistry.getAll());
 | 
			
		||||
 | 
			
		||||
        return allRules.filter(rule => {
 | 
			
		||||
            const ruleTags = utils.javaSetToJsArray(rule.getTags());
 | 
			
		||||
            const hasSceneTag = ruleTags.includes("Scene");
 | 
			
		||||
            const hasStatefulTag = ruleTags.includes("Stateful");
 | 
			
		||||
            const isEnabled = rules.isEnabled(rule.getUID());
 | 
			
		||||
 | 
			
		||||
            return hasSceneTag && hasStatefulTag && isEnabled;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Scene {
 | 
			
		||||
 | 
			
		||||
    constructor(scene) {
 | 
			
		||||
 | 
			
		||||
        this.scene = scene;
 | 
			
		||||
        this.sceneUID = this.scene.getUID();
 | 
			
		||||
        this.sceneName = this.scene.getName();
 | 
			
		||||
        this.sceneSwitchItemName = ITEMPREFIX + "_" + this.sceneUID;
 | 
			
		||||
        this.evaluationRuleUID = "SceneItems" + this.sceneUID;
 | 
			
		||||
 | 
			
		||||
        log.info(`Initialize scene ${this.sceneName}`);
 | 
			
		||||
 | 
			
		||||
        if (!items[this.sceneSwitchItemName]) {
 | 
			
		||||
            this.createSceneSwitchItem();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this.createSceneRules();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createSceneSwitchItem() {
 | 
			
		||||
        log.info(`Creating scene switch for ${this.sceneSwitchItemName}`);
 | 
			
		||||
        items.addItem({
 | 
			
		||||
            name: this.sceneSwitchItemName,
 | 
			
		||||
            type: 'Switch',
 | 
			
		||||
            label: `Switch for stateful scene ${this.sceneName}`,
 | 
			
		||||
            tags: ['Stateful', 'Control', 'Scene']
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createSceneRules() {
 | 
			
		||||
 | 
			
		||||
        rules.JSRule({
 | 
			
		||||
            name: "Switch rule for stateful scene " + this.sceneName,
 | 
			
		||||
            description: "Scene switch for " + this.sceneName + " received command",
 | 
			
		||||
            triggers: [triggers.ItemCommandTrigger(this.sceneSwitchItemName)],
 | 
			
		||||
            execute: (event) => {
 | 
			
		||||
                this.switchScene(event.receivedCommand);
 | 
			
		||||
            },
 | 
			
		||||
            id: "StatefulSwitch" + this.sceneUID,
 | 
			
		||||
            overwrite: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        rules.JSRule({
 | 
			
		||||
            name: "Evaluation rule for stateful scene " + this.sceneName,
 | 
			
		||||
            description: "Evaluate stateful scene " + this.sceneName + " if scene items have changed",
 | 
			
		||||
            triggers: this.getSceneItems().map(itemName => triggers.ItemStateChangeTrigger(itemName)),
 | 
			
		||||
            execute: () => {
 | 
			
		||||
                this.evaluateScene();
 | 
			
		||||
            },
 | 
			
		||||
            id: this.evaluationRuleUID,
 | 
			
		||||
            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() {
 | 
			
		||||
 | 
			
		||||
        const sceneUID = this.scene.getUID();
 | 
			
		||||
        const sceneName = this.scene.getName();
 | 
			
		||||
        const timerUID = "EvaluateScene_" + sceneUID;
 | 
			
		||||
        const sceneSwitchItemName = ITEMPREFIX + "_" + sceneUID;
 | 
			
		||||
 | 
			
		||||
        const evaluate = () => {
 | 
			
		||||
            log.info(`Evaluate scene ${sceneName}`);
 | 
			
		||||
 | 
			
		||||
            const scenceConfig = this.getSceneConfig();
 | 
			
		||||
            let conditionsFailed = false;
 | 
			
		||||
 | 
			
		||||
            for (const [itemName, targetState] of Object.entries(scenceConfig)) {
 | 
			
		||||
 | 
			
		||||
                const itemType = items[itemName].type;
 | 
			
		||||
                const currentState = (itemType === "Dimmer" || itemType === "Number") 
 | 
			
		||||
                    ? Math.round(items[itemName].numericState) 
 | 
			
		||||
                    : items[itemName].state;
 | 
			
		||||
 | 
			
		||||
                if (currentState == targetState) {
 | 
			
		||||
                    log.info("Compare " + itemName + " (type: " + itemType + ") with currenState " + currentState + " and targetState " + targetState + " successfull");
 | 
			
		||||
                }  else {
 | 
			
		||||
                    log.info("Compare " + itemName + " (type: " + itemType + ") with currenState " + currentState + " and targetState " + targetState + " failed");
 | 
			
		||||
                    conditionsFailed = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (conditionsFailed) {
 | 
			
		||||
                log.info(`Switch off sceneSwitch for scene ${sceneName} because conditions failed`);
 | 
			
		||||
                items[sceneSwitchItemName].postUpdate("OFF");
 | 
			
		||||
                this.disableEvaluationRule();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
        if (tm.hasTimer(timerUID) && tm.isActive(timerUID)) {
 | 
			
		||||
            log.info(`Skip evaluation of scene ${sceneName} as evaluation function is already running. Reschedule timer`);
 | 
			
		||||
            tm.reschedule(timerUID, timeout);
 | 
			
		||||
        } else {
 | 
			
		||||
            log.info(`Create timer to evaluate scene ${sceneName}`);
 | 
			
		||||
            if (tm.hasTimer(timerUID)) {
 | 
			
		||||
                tm.cancel(timerUID);
 | 
			
		||||
            }
 | 
			
		||||
            tm.create(timerUID, timeout, evaluate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSceneItems() {
 | 
			
		||||
        // Retrieve scene configuration and return its keys as an array
 | 
			
		||||
        return Object.keys(this.getSceneConfig());
 | 
			
		||||
    }
 | 
			
		||||
         
 | 
			
		||||
    getSceneConfig() {
 | 
			
		||||
        log.debug(`Get configuration for scene ${this.sceneName}`);
 | 
			
		||||
 | 
			
		||||
        const sceneConfig = {};
 | 
			
		||||
 | 
			
		||||
        for (const action of this.scene.getActions()) {
 | 
			
		||||
            const actionConfig = utils.javaMapToJsMap(action.getConfiguration());
 | 
			
		||||
            sceneConfig[actionConfig.get("itemName")] = actionConfig.get("command");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return sceneConfig;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasRestoreEnabled() {
 | 
			
		||||
 | 
			
		||||
        const ruleTags = utils.javaSetToJsArray(this.scene.getTags());
 | 
			
		||||
        const hasRestoreTag = ruleTags.includes("Restore");
 | 
			
		||||
 | 
			
		||||
        return hasRestoreTag;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switchScene(switchCommand) {
 | 
			
		||||
 
 | 
			
		||||
        log.info("Scene " + this.sceneName + " received command " + switchCommand);
 | 
			
		||||
 | 
			
		||||
        switch(switchCommand) {
 | 
			
		||||
            case "ON":
 | 
			
		||||
                log.info("Activate scene " + this.sceneName);
 | 
			
		||||
 | 
			
		||||
                if (this.hasRestoreEnabled()) {
 | 
			
		||||
                    log.info("Restore is enabled")
 | 
			
		||||
                    this.switchedOn = time.ZonedDateTime.now();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                rules.runRule(this.sceneUID);
 | 
			
		||||
                java.lang.Thread.sleep(2500);
 | 
			
		||||
                this.enableEvaluationRule();
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case "OFF":
 | 
			
		||||
                log.info("Deactivate scene " + this.sceneName);
 | 
			
		||||
 | 
			
		||||
                if (this.hasRestoreEnabled()) {
 | 
			
		||||
                    log.info("Restore is enabled");
 | 
			
		||||
 | 
			
		||||
                    let timestamp = this.switchedOn.minusNanos(DEBOUNCETIME * 1000000)
 | 
			
		||||
 | 
			
		||||
                    for (const sceneItem of this.getSceneItems()) {
 | 
			
		||||
                        let historicSceneItem = items[sceneItem].persistence.persistedState(timestamp);
 | 
			
		||||
                        let historicSceneItemState = historicSceneItem.state
 | 
			
		||||
                        items[sceneItem].sendCommandIfDifferent(historicSceneItemState);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                this.disableEvaluationRule();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load script
 | 
			
		||||
const sm = new SceneController();
 | 
			
		||||
 | 
			
		||||
// Unload script
 | 
			
		||||
require('@runtime').lifecycleTracker.addDisposeHook(() => {
 | 
			
		||||
    log.info('Deinitialization of SceneController');
 | 
			
		||||
 | 
			
		||||
    sm.purgeUnusedSceneSwitchItems();
 | 
			
		||||
 | 
			
		||||
    tm.cancelAll();
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user