Compare commits
18 Commits
e299b96d15
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd69b4de1b | ||
|
|
d2b9359fee | ||
|
|
b5eb470609 | ||
|
|
475d238ea3 | ||
|
|
c5ed9c9c44 | ||
|
|
13bbf41d0b | ||
|
|
de8e708088 | ||
|
|
fa635f46d9 | ||
|
|
4a7185a1a5 | ||
|
|
b9e5c22184 | ||
|
|
47a862780d | ||
|
|
fd10e13238 | ||
|
|
6d7d00fcd3 | ||
|
|
2571f9c01e | ||
|
|
0c5566a7eb | ||
|
|
27446b13b8 | ||
|
|
47d6b80511 | ||
|
|
33e5781ee8 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
@@ -1,25 +1,33 @@
|
|||||||
// Settings
|
// Settings
|
||||||
const ITEMPREFIX = "System_StatefulScene";
|
const ITEMPREFIX = "System_StatefulScene";
|
||||||
const DEBOUNCETIME = 3000;
|
const DEBOUNCETIME = 3000;
|
||||||
const LOGGER = "org.openhab.js.SceneMgr";
|
|
||||||
|
|
||||||
// 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');
|
||||||
const { ruleRegistry } = require('@runtime/RuleSupport');
|
|
||||||
|
|
||||||
const tm = new TimerMgr();
|
const tm = new TimerMgr();
|
||||||
|
|
||||||
|
const { ruleRegistry } = require('@runtime/RuleSupport');
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
class SceneMgr {
|
class SceneMgr {
|
||||||
|
|
||||||
constructor() {
|
constructor(itemPrefix, debounceTime) {
|
||||||
this.scenes = this.getScenes().reduce((accumulator, scene) => {
|
// Load class properties
|
||||||
|
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.
|
||||||
|
this.scenes = this.#getStatefulScenes().reduce((scenes, scene) => {
|
||||||
const sceneUID = scene.getUID();
|
const sceneUID = scene.getUID();
|
||||||
accumulator[sceneUID] = new Scene(scene);
|
scenes[sceneUID] = new Scene(scene);
|
||||||
return accumulator;
|
return scenes;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,17 +52,19 @@ class SceneMgr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,28 +72,35 @@ class SceneMgr {
|
|||||||
|
|
||||||
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 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]) {
|
||||||
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',
|
||||||
label: `Switch for stateful scene ${this.sceneName}`,
|
label: `Switch for stateful scene ${this.sceneName}`,
|
||||||
tags: ['Stateful', 'Control']
|
tags: ['Stateful', 'Control', 'Scene']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,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;
|
||||||
@@ -160,24 +163,22 @@ 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)) {
|
||||||
|
tm.cancel(timerUID);
|
||||||
|
}
|
||||||
tm.create(timerUID, timeout, evaluate);
|
tm.create(timerUID, timeout, evaluate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,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 SceneMgr();
|
return new SceneMgr();
|
||||||
|
}
|
||||||
|
|
||||||
// Unload script
|
module.exports = {
|
||||||
require('@runtime').lifecycleTracker.addDisposeHook(() => {
|
getSceneMgr
|
||||||
log.info('Deinitialization of SceneMgr.js');
|
};
|
||||||
|
|
||||||
sm.purgeUnusedSceneSwitchItems();
|
|
||||||
|
|
||||||
tm.cancelAll();
|
|
||||||
});
|
|
||||||
@@ -9,32 +9,31 @@ class TimerMgr {
|
|||||||
log.info('Initialization of TimerMgr helper class');
|
log.info('Initialization of TimerMgr helper class');
|
||||||
}
|
}
|
||||||
|
|
||||||
create(id, timeout, func) {
|
create(id, timeout, func, ...params) {
|
||||||
if (this.hasTimer(id)) {
|
if (this.hasTimer(id)) {
|
||||||
log.error(`Timer with id ${id} already exists`);
|
this.cancel(id);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
log.debug("Create timer with id " + id);
|
log.debug("Create timer with id " + id);
|
||||||
this.#timers[id] = actions.ScriptExecution.createTimer(id, timeout, func);
|
this.#timers[id] = actions.ScriptExecution.createTimer(id, timeout, func, ...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(identifier) {
|
cancel(id) {
|
||||||
// Return if no timer with the respactive identifier is available
|
// Return if no timer with the respactive id is available
|
||||||
if (!this.#timers.hasOwnProperty(identifier)) {
|
if (!this.hasTimer(id)) {
|
||||||
log.debug(`No timer with identifier ${identifier} available to cancel`);
|
log.warn(`No timer with id ${id} available to cancel`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if timer is active
|
// Check if timer is active
|
||||||
if (!this.#timers[identifier].isActive()) {
|
if (!this.#timers[id].isActive()) {
|
||||||
log.debug(`Timer with identifier ${identifier} not running. Cancel anyway`);
|
log.debug(`Timer with id ${id} not running. Cancel anyway`);
|
||||||
} else {
|
} else {
|
||||||
log.debug(`Cancel timer with identifier ${identifier}`);
|
log.debug(`Cancel timer with identifier ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel timer
|
// Cancel timer
|
||||||
this.#timers[identifier].cancel();
|
this.#timers[id].cancel();
|
||||||
delete this.#timers[identifier];
|
delete this.#timers[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTimer(id) {
|
hasTimer(id) {
|
||||||
@@ -66,6 +65,10 @@ class TimerMgr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reschedule(id, timeout) {
|
reschedule(id, timeout) {
|
||||||
|
if (!this.hasTimer(id)) {
|
||||||
|
log.warn(`Cannot reschedule non-existent timer with id ${id}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.#timers[id].reschedule(timeout);
|
this.#timers[id].reschedule(timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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