I’m using Openhab now for years, already modelled my smart devices in custom JSON and create a lot of item, things and KNX configuration by an app I wrote.
But still there is a lot of topics I like to integrate.
Unfortunatelly I’m blocking myself for months due to unsatisfiing rule/automations. And therefore I’m looking for a good approach. Hope you can help me here.
I’ve got 2 big rules and adding a third showed a problem I cannot solve (nicely):
First rule: Lightbulb in living room. Currently controlled by time and brightness.
Sevond rule: roller shutter control for sun ‘protection’. Controlled by many different weather parameters and the open/close state of the window.
Now I’ld like to add a third rule to simulate presence.
A seperate third rule (without any communication items) would result in conflicts: rule A switches on, rule B switches off, rule A on etc.
Do you have a best practice to solve that conflicts?
(Statemachine for each actor? String item with current need of each rule and priorization rule? One complete rule per item? …)
This shouldn’t be a big rule. It’s rather simple. What makes this big?
Not without seeing the rules themselves and the relevant Items.
Best practice is going to depend on information not provided so .
What you describe seems pretty simple but the devil is in the details.
One important thing to realize about rules is they are triggered by events, not state. Events are momentary and they are independent.
For example, if you have a single rule that triggers when Foo changes from ON to OFF and when Bar changes, even if Foo is already OFF the rule will trigger every time Bar changes.
With the limited amount of information provided it seems like you are triggering the rule based on all sorts of changes and not double checking all the relevant states before doing something
Thank you for your answer. I hope that you might help me with the provided information below. To allow a more continuous reading I’ll add the rules below.
Now, having rule 1 (lightbulb) and rule 2 (rollershutter) I like to add a vacation mode that controls rollershutter and light (basically simulating presence). These rules will control the same items and I’m looking for a clever method to implement my needs / handle conflicts.
Right now I’ve got 3 approaches. I’ll go from the most favorized to the lowest:
Create 1 string item (or for each to be controlled item another one?). This new item contains a “list of requests” (e.g. [“heatprevention”, “vacation”]). A new rule needs to be introduced triggered by change of this item. This rule will evaluated the entries in “list of request” and act according a priorization. The business logic rules (rule 1 and rule 2 in this example) will be changed to write the request into the “list of request” instead of controlling the item itself.
Add a new item “vacation mode” and evaluate it (if … then … else) in each rule (rule 1 and rule 2). The big disadvantage is that I have to change each rule every time a new “mode” will be introduced.
Some approach of a statemachine. Each item will have a state. But to be honest I didn’t thought this through.
This is a separate problem I’ld just like to add it for sake of completeness:
Just to be complete: I’m reaching out for a little more complex solution that will also model some kind of “locking” e.g. after locking against automatic changes for a certain time after manual control.
My current favorite solution is to write a timestamp-item name-combination in a string item in each automation rule (when controlling this item). If last stored timestamp for this item equals .history.lastUpdate() then automatic control should continue otherwise is “locked”. After a certain time / mid of night the timesetamp-item-combination will be deleted.
Here is the first rule. This is the rule for the light in living room. As you might interpret brightness is modelled by sunset and rollershutter status.
rules.JSRule({
id: "r_SteckdosenStehlampeWZ",
name: "SteckdosenStehlampeWZ",
description: "Diese Regel schaltet SteckdosenStehlampeWZ automatisch.",
triggers: [triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZGesamtItem'),triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZSuedseiteItem'),triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZWestseiteItem'),triggers.ItemStateUpdateTrigger('OneCallAPIweatherandforecast_Current_Sunset'),triggers.GenericCronTrigger("0 */10 * * * ? *"),triggers.ItemStateUpdateTrigger('JonasHandyPingOnlineState'),triggers.ItemStateUpdateTrigger('SvenjaHandyPingOnlineState'),triggers.ItemStateUpdateTrigger('SteckdosenStehlampeWZAutomatikSchalter')],
execute: (event) => {
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.r_SteckdosenStehlampeWZ');
// Additem für Automatismusdeaktivierungsitem, wenn nicht vorhanden.
var automatismus = items.getItem('SteckdosenStehlampeWZAutomatikSchalter', true);
if (automatismus == null){
items.addItem({
type: 'String',
name: 'SteckdosenStehlampeWZAutomatikSchalter',
label: 'Schaltet den Automatismus SteckdosenStehlampeWZ An/Aus',
category: 'light',
groups: ['light','powerplug','Switch'],
tags: ['Lightbulb','Switch','automaticSwitch'],
});
items['SteckdosenStehlampeWZAutomatikSchalter'].sendCommand('ON');
}
if (items['SteckdosenStehlampeWZAutomatikSchalter'].state != 'ON'){
return;
}
var currentTime = new Date();
//logger.warn("currentTime: " + currentTime);
// Abfragevariablen definieren (Zur Übersichtlichkeit)
var sunsetDate = new Date (items['OneCallAPIweatherandforecast_Current_Sunset'].state);
var sunriseDate = new Date (items['OneCallAPIweatherandforecast_Current_Sunrise'].state);
var rollershutterAreClosed = items['RollaedenBeschattungWZGesamtItem'].state >= 70 || items['RollaedenBeschattungWZSuedseiteItem'].state >= 70 || items['RollaedenBeschattungWZWestseiteItem'].state >= 70;
//Vor Sunrise und nach sunset
//switch on
// morgens
var currentHours = currentTime.getHours();
var sunriseAndDelay = new Date(sunriseDate.getTime() + 180000);
var sunsetAndDelay = new Date(sunsetDate.getTime() - 180000);
logger.warn("currentHours :" + currentHours );
logger.warn("sunriseAndDelay :" + sunriseAndDelay );
logger.warn("sunsetAndDelay :" + sunsetAndDelay );
logger.warn("rollershutterAreClosed :" + rollershutterAreClosed );
if ((currentHours > 6) && ((currentTime < sunriseAndDelay) || ((rollershutterAreClosed)&&(currentHours < 8)))){
items['SteckdoseAnAusWZSuedwest1Item'].sendCommand('ON');
}
//abends
else if((currentTime > sunsetAndDelay)|| ((rollershutterAreClosed)&&(currentHours > 17))){
items['SteckdoseAnAusWZSuedwest1Item'].sendCommand('ON');
}else {
// wenn nicht an, dann wohl aus
items['SteckdoseAnAusWZSuedwest1Item'].sendCommand('OFF');
}
}
});
The second one is a lot more complicated, but in the end it’s just controlling the rollershutter for heat prevention.
If somebody decides to use this rule: There is at least one workaround integrated. The one I remember is that I execute only once a rollershutter closing in a 10 min time frame - therefore the cron trigger needs to stay on */10. This is a workaround because I did not implement the “locking after manual control”, see above.
rules.JSRule({
id: "r_Sonnenhitzeschutz",
name: "Sonnenhitzeschutz",
description: "Diese Regel schaltet Sonnenhitzeschutz automatisch.",
triggers: [triggers.GenericCronTrigger("0 */10 * * * ? *"),triggers.ItemStateUpdateTrigger('SonnenhitzeschutzAutomatikSchalter')],
execute: (event) => {
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.r_Sonnenhitzeschutz');
// Additem für Automatismusdeaktivierungsitem, wenn nicht vorhanden.
var automatismus = items.getItem('SonnenhitzeschutzAutomatikSchalter', true);
if (automatismus == null){
items.addItem({
type: 'String',
name: 'SonnenhitzeschutzAutomatikSchalter',
label: 'Schaltet den Automatismus Sonnenhitzeschutz An/Aus',
category: 'light',
groups: ['rollershutter','beschattung','Switch'],
tags: ['rollershutter','Switch','automaticSwitch'],
});
items['SonnenhitzeschutzAutomatikSchalter'].sendCommand('ON');
}
if (items['SonnenhitzeschutzAutomatikSchalter'].state != 'ON'){
return;
}
var runtime = require('@runtime'); var sunriseItemName = "OneCallAPIweatherandforecast_Current_Sunrise";
var sunsetItemName = "OneCallAPIweatherandforecast_Current_Sunset";
var currentDate = new Date();
var startTime = new Date();
startTime.setHours(7,30, 0);
var stopTime = new Date();
stopTime.setHours(19,0, 0);
main();
function main () {
//Uhrzeitcheck ("nicht in der Nacht")
if (!checkIfTimeIsAcceptableForMoving()){
logger.warn("debug: checkIfTimeIsAcceptableForMoving is false (=abort)");
return;
}
moveRollerShutterIfClosed();
}
function checkIfTimeIsAcceptableForMoving(){
logger.warn("debug: startTime is: " + startTime.toString());
logger.warn("debug: stopTime is: " + stopTime.toString());
if(currentDate > startTime && currentDate < stopTime){
logger.warn("debug: Uhrzeit ist akzeptabel:" + currentDate);
return true;
}
logger.warn("debug: Uhrzeit ist nicht akzeptabel:" + currentDate);
return false;
};
function checkIfClosureIsNeeded(){
// Wenn aktuelle Temperatur größer als 18°C
// aktuelle Temperatur OneCallAPIweatherandforecast_Current_Apparenttemperature
var currentTemp = Quantity(items["OneCallAPIweatherandforecast_Current_Apparenttemperature"].state).float;
// Wenn Maxtemperatur über 25 °C ist
// Maxtemperatur OneCallAPIweatherandforecast_ForecastToday_Maxtemperature
var maxTemp = Quantity(items["OneCallAPIweatherandforecast_ForecastToday_Maxtemperature"].state).float;
// Wenn sehr bewölkt, muss nicht geschlossen werden Wolken (deutlich über 90%)
// berücksichtigt aktuell und in einer Stunde:
// Bewölkung aktuell: OneCallAPIweatherandforecast_Current_Cloudiness
// Bewölkung in 1 h: OneCallAPIweatherandforecast_ForecastHours01_Cloudiness
var currentCloudiness = parseFloat(items["OneCallAPIweatherandforecast_Current_Cloudiness"].state.toString());
var oneHourForecastCloudiness = parseFloat(items["OneCallAPIweatherandforecast_ForecastHours01_Cloudiness"].state.toString());
// Aktueller UV-Index.
// Ab 3 muss zugemacht werden.
// Quelle: https://www.who.int/news-room/questions-and-answers/item/radiation-the-ultraviolet-(uv)-index
var UVIndex = items["OneCallAPIweatherandforecast_ForecastToday_Uvindex"].state;
/*logger.warn("debug: currentTemp is " + items["OneCallAPIweatherandforecast_Current_Apparenttemperature"].state);
logger.warn("debug: maxTemp is " + maxTemp);
logger.warn("debug: currentCloudiness is " + currentCloudiness);
logger.warn("debug: oneHourForecastCloudiness is " + oneHourForecastCloudiness);
logger.warn("debug: UVIndex is " + uVIndex);
*/
if (UVIndex > 5 || maxTemp >= 20){
logger.warn("debug: Beschattungsautomatik ist gewünscht. UVIndex ist " + UVIndex);
return true;
}
if (currentTemp < 20-10){
logger.warn("debug: Beschattungsautomatik ist nicht gewünscht. Temp ist " + currentTemp);
return false;
}
if(maxTemp < 20){
logger.warn("debug: Beschattungsautomatik ist nicht gewünscht. maxTemp ist " + maxTemp);
return false;
}
if ((currentCloudiness + oneHourForecastCloudiness)/2 > 0.90){
logger.warn("debug: Beschattungsautomatik ist nicht gewünscht. currentCloudiness ist " + currentCloudiness + ". oneHourForecastCloudiness ist " + oneHourForecastCloudiness + ". (currentCloudiness + oneHourForecastCloudiness)/2 ist " + (currentCloudiness + oneHourForecastCloudiness)/2);
return false;
}
logger.warn("debug: Beschattungsautomatik ist gewünscht.");
return true;
}
function moveRollerShutterIfClosed(){
//disinguish directions to move
// up
var sunrise = new Date(items[sunriseItemName].state);
var sunset = new Date(items[sunsetItemName].state);
var diff = sunset.getTime() - sunrise.getTime();
var maxTempCompensationFaktor = Quantity(items["OneCallAPIweatherandforecast_ForecastToday_Maxtemperature"].state).float
if(maxTempCompensationFaktor > 20){
//Für jedes Grad über 20°C 10 Min früher und länger.
maxTempCompensationFaktor = (maxTempCompensationFaktor- 20)*1000*60*10;
} else {
maxTempCompensationFaktor = 0;
}
var eastStartClose = startTime;
var eastStopClose = new Date(sunrise.getTime() + 3.5*diff/10 + maxTempCompensationFaktor);
var southStartClose = new Date(Math.max(sunrise.getTime() + 3*diff/10 - maxTempCompensationFaktor, startTime.getTime()));
var southStopClose = new Date(sunrise.getTime() + 8*diff/10 + maxTempCompensationFaktor);
var westStartClose = new Date(sunrise.getTime() + 6.5*diff/10 - maxTempCompensationFaktor);
var westStopClose = new Date(Math.min(stopTime.getTime(), sunrise.getTime() + 8.5*diff/10+ maxTempCompensationFaktor));
var closureIsNeeded = false;
// "Wenns warm wird"
closureIsNeeded = checkIfClosureIsNeeded();
logger.warn("currentDate: " + currentDate.toString());
//logger.warn("diff: " + diff.toString());
logger.warn("eastStartClose: " + eastStartClose.toString());
logger.warn("eastStopClose: " + eastStopClose.toString());
logger.warn("southStartClose: " + southStartClose.toString());
logger.warn("southStopClose: " + southStopClose.toString());
logger.warn("westStartClose: " + westStartClose.toString());
logger.warn("westStopClose: " + westStopClose.toString());
var up = []
// down
var down = []
//if (currentDate.getTime() > eastStartClose.getTime() && currentDate.getTime() < eastStopClose.getTime()){
if (currentDate.getTime() > eastStartClose.getTime() && currentDate.getTime() < eastStartClose.getTime() + 600000){
down.push("ost");
//} else if (currentDate.getTime() >= eastStopClose.getTime()){
} else if ((currentDate.getTime() >= eastStopClose.getTime()) && (currentDate.getTime() < eastStopClose.getTime() + 600000)){
up.push("ost");
}
//if (currentDate.getTime() > southStartClose.getTime() && currentDate.getTime() < southStopClose.getTime()){
if (currentDate.getTime() > southStartClose.getTime() && currentDate.getTime() < southStartClose.getTime() + 600000){
down.push("sued");
//} else if (currentDate.getTime() >= southStopClose.getTime()){
} else if ((currentDate.getTime() >= southStopClose.getTime()) && (currentDate.getTime() < southStopClose.getTime() + 600000)){
up.push("sued");
}
//if (currentDate.getTime() > westStartClose.getTime() && currentDate.getTime() < westStopClose.getTime()){
if (currentDate.getTime() > westStartClose.getTime() && currentDate.getTime() < westStartClose.getTime() + 600000){
down.push("west");
//} else if (currentDate.getTime() >= westStopClose.getTime()){
} else if ((currentDate.getTime() >= westStopClose.getTime()) && (currentDate.getTime() < westStopClose.getTime() + 600000)){
up.push("west");
}
logger.warn("up muss: " + up.toString());
logger.warn("down muss: " + down.toString());
//Auswertung der einzelnen Rolläden in Raum Esszimmer.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktEsszimmerSuedseiteItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungEsszimmerSuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungEsszimmerSuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungEsszimmerSuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
//Direction: west
//Auswertung der einzelnen Rolläden in Raum Gast.
//Direction: nord
if(items["FensterkontaktFensterkontaktGastNordseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktGastNordseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("nord")){
if(items["RollaedenBeschattungGastNordseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungGastNordseiteItem"].sendCommand(85);
}
}
} else if (up.includes("nord")){
items["RollaedenBeschattungGastNordseiteItem"].sendCommand(0);
}
}
//Direction: sued
//Direction: ost
//Direction: west
if(items["FensterkontaktFensterkontaktGastWestseiteItem"].state.toString() == "CLOSED"){
if (down.includes("west")){
if(items["RollaedenBeschattungGastWestseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungGastWestseiteItem"].sendCommand(85);
}
}
} else if (up.includes("west")){
items["RollaedenBeschattungGastWestseiteItem"].sendCommand(0);
}
}
//Auswertung der einzelnen Rolläden in Raum Kueche.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktKuecheSuedseiteItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungKuecheSuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungKuecheSuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungKuecheSuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
if(items["FensterkontaktFensterkontaktKuecheOstseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktKuecheOstseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("ost")){
if(items["RollaedenBeschattungKuecheOstseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungKuecheOstseiteItem"].sendCommand(85);
}
}
} else if (up.includes("ost")){
items["RollaedenBeschattungKuecheOstseiteItem"].sendCommand(0);
}
}
//Direction: west
//Auswertung der einzelnen Rolläden in Raum WC.
//Direction: nord
if(items["FensterkontaktFensterkontaktWCNordseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktWCNordseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("nord")){
if(items["RollaedenBeschattungWCNordseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungWCNordseiteItem"].sendCommand(85);
}
}
} else if (up.includes("nord")){
items["RollaedenBeschattungWCNordseiteItem"].sendCommand(0);
}
}
//Direction: sued
//Direction: ost
//Direction: west
//Auswertung der einzelnen Rolläden in Raum WZ.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktWZSuedseiteItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungWZSuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungWZSuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungWZSuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
//Direction: west
if(items["FensterkontaktFensterkontaktWZWestseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktWZWestseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("west")){
if(items["RollaedenBeschattungWZWestseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungWZWestseiteItem"].sendCommand(85);
}
}
} else if (up.includes("west")){
items["RollaedenBeschattungWZWestseiteItem"].sendCommand(0);
}
}
//Auswertung der einzelnen Rolläden in Raum Ankleide.
//Direction: nord
if(items["FensterkontaktFensterkontaktAnkleideNordseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktAnkleideNordseiteRechtsItem"].state.toString() == "CLOSED"){
}
//Direction: sued
//Direction: ost
//Direction: west
//Auswertung der einzelnen Rolläden in Raum K1.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktK1SuedseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktK1SuedseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungK1SuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungK1SuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungK1SuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
//Direction: west
if(items["FensterkontaktFensterkontaktK1WestseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktK1WestseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("west")){
if(items["RollaedenBeschattungK1WestseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungK1WestseiteItem"].sendCommand(85);
}
}
} else if (up.includes("west")){
items["RollaedenBeschattungK1WestseiteItem"].sendCommand(0);
}
}
//Auswertung der einzelnen Rolläden in Raum K2.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktK2SuedseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktK2SuedseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungK2SuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungK2SuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungK2SuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
if(items["FensterkontaktFensterkontaktK2OstseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktK2OstseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("ost")){
if(items["RollaedenBeschattungK2OstseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungK2OstseiteItem"].sendCommand(85);
}
}
} else if (up.includes("ost")){
items["RollaedenBeschattungK2OstseiteItem"].sendCommand(0);
}
}
//Direction: west
//Auswertung der einzelnen Rolläden in Raum K3.
//Direction: nord
//Direction: sued
if(items["FensterkontaktFensterkontaktK3SuedseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktK3SuedseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("sued")){
if(items["RollaedenBeschattungK3SuedseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungK3SuedseiteItem"].sendCommand(85);
}
}
} else if (up.includes("sued")){
items["RollaedenBeschattungK3SuedseiteItem"].sendCommand(0);
}
}
//Direction: ost
//Direction: west
//Auswertung der einzelnen Rolläden in Raum Schlafen.
//Direction: nord
if(items["FensterkontaktFensterkontaktSchlafenNordseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktSchlafenNordseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("nord")){
if(items["RollaedenBeschattungSchlafenNordseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungSchlafenNordseiteItem"].sendCommand(85);
}
}
} else if (up.includes("nord")){
items["RollaedenBeschattungSchlafenNordseiteItem"].sendCommand(0);
}
}
//Direction: sued
//Direction: ost
//Direction: west
if(items["FensterkontaktFensterkontaktSchlafenWestseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktSchlafenWestseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("west")){
if(items["RollaedenBeschattungSchlafenWestseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungSchlafenWestseiteItem"].sendCommand(85);
}
}
} else if (up.includes("west")){
items["RollaedenBeschattungSchlafenWestseiteItem"].sendCommand(0);
}
}
//Auswertung der einzelnen Rolläden in Raum Bad.
//Direction: nord
if(items["FensterkontaktFensterkontaktBadNordseiteLinksItem"].state.toString() == "CLOSED" && items["FensterkontaktFensterkontaktBadNordseiteRechtsItem"].state.toString() == "CLOSED"){
if (down.includes("nord")){
if(items["RollaedenBeschattungBadNordseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungBadNordseiteItem"].sendCommand(85);
}
}
} else if (up.includes("nord")){
items["RollaedenBeschattungBadNordseiteItem"].sendCommand(0);
}
}
//Direction: sued
//Direction: ost
if(items["FensterkontaktFensterkontaktBadOstseiteItem"].state.toString() == "CLOSED"){
if (down.includes("ost")){
if(items["RollaedenBeschattungBadOstseiteItem"].state <= 60){
if (closureIsNeeded){
items["RollaedenBeschattungBadOstseiteItem"].sendCommand(85);
}
}
} else if (up.includes("ost")){
items["RollaedenBeschattungBadOstseiteItem"].sendCommand(0);
}
}
//Direction: west
} }
});
[As I already stated it’s generated programmatically]
Create an Item to represent vacation mode. When it’s on the above rule template will similate presence merely by playing back the states your system did seven days ago? It’s better than random because it’s your actual recorded activities.
Then you’d have a rule that triggers when vacation mode turns on that disables the other two rules and when it turns off it enables the other two rules.
State machines and all the rest is way over complicated for this.
I don’t understand why there are so many triggers. And you usually do not want to mix a periodic polling trigger with a bunch of Item updated triggers. Furthermore, you only need to do something if these Items change so they should be using the changed trigger.
This rule is probably almost constantly running.
If you want it to run every second, just use the cron trigger. If you want it to run when something changes, remove the cron trigger and make all the Item triggers run on changes, not just updates.
With so many triggers it’s no wonder there are interactions between rules.
Also, since this is fully automated, can’t you just let it run as is for vacation mode? The whole point of vacation mode is to make it look like the home is occupied. Changing the behavior of the home when you are away becomes a signal that you are away.
Why not using the built in time library?
var currentTime = time.toZDT();
var sunsetDate = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunset);
var sunriseDate = time.toZDT(items.OneCallAPIweatherandforecast_Current_Runrise);
var currentHours = currentTime.hour();
var sunriseAndDelay = sunriseDate.toLocalTime.plusMinutes(3);
var sunsetAndDelay = sunsetDate.toLocalTime.minusMinutes(3);
In fact the whole rule can be simplified to:
rules.JSRule({
id: "r_SteckdosenStehlampeWZ",
name: "SteckdosenStehlampeWZ",
description: "Diese Regel schaltet SteckdosenStehlampeWZ automatisch.",
triggers: [triggers.GenericCronTrigger("0 */10 * * * ? *")],
execute: (event) => {
console.name = 'org.openhab.rule.r_SteckdosenStehlampeWZ'; // use the console instead of a custom logger
// Assuming MainUI, you can create a widget with an action that enables or disables the rule
// directly instead of using a separate Item. You can also enable/disable rules from other rules.
// If for some reason you need to have these Items, put all this code into a library which can be
// reused across all your rules instead of adding duplicate code to each rule.
let now = time.toZDT();
let sunrisePlusDelay = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunrise).plusMinutes(3);
let sunsetPlusDelay = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunset).minusMinutes(3);
// put the rollarshutters into a Group with a MIN aggregation function, I've called it RollershuttersGroup here
let rollershuttersAreClosed = items.RollarshuttersGroup.numericState >= 70;
let command = "OFF";
console.warn("now: " + now.toLocalTime());
console.warn("sunrise: " + sunrisePlusDelay.toLocalTime());
console.warn("sunset: " + sunsetPlusDelay.toLocalTime());
console.warn("rollershuttersAreClosed: " + rollarShuttersAreClosed);
if(now.isBetween("06:00", sunrisePlusDelay)) command = "ON"
else if (rollershuttersAreClosed && now.isAfter(time.toZDT("08:00"))) command = "ON";
else if(now.idBefore(sunsetPlusDelay)) command = "ON";
else if (rollershuttersAreClosed && now.isBefore(time.toZDT("17:00"))) command = "ON":
items.SteckdoseAnAusWZSuedwest1Item.sendCommandIfDifferent(command);
}
});
There is no reason to trigger this rule based on this Item. I’m surprised this rule even loads and parses since this Item is created by this rule in the first place. It should throw errors when this Item doesn’t exist.
If that created Item triggers the first rule, you should remove it there too.
You never use this and you should not be importing it. For JS Scripting use the openhab-js interface instead of accessing the JSR223 API directly. Everything you need to do is available to you by default.
Is this some AI generated code? It has that overly complicated look to it that usually comes from trying to use a chatbot to generate code.
Though if this works maybe not as chatbots have a really hard time writing rules for OH.
If you use the built in time libraries this whole function gets replaced with time.toZDT()isBetween("07:30", "19:00");. Though why not just configure the cron trigger to only trigger the rule between 07:30 and 19:00 in the first place. Then you don’t need to check for this in the rule itself.
As far as I can tell this adds nothing but extra complexity to this rule.
Note that you do not need to define all the functions inline in the rule like this. These can be declared in the same file outside the rule.
Use items.OneCallAPIweatherandforecast_Current_Apparenttemperature.quantityState to work with units or items.OneCallAPIweatherandforecast_Current_Apparenttemperature.numericState to work with primitive numbers.
This should be a function you call with the Item name as an argument. Avoid repeated code. Using Groups, Item metadata perhaps or maps in the rule to map the rollershutters to the windows and the directions you could shrink this rule by more than half.
If you use the Astro binding, you can base this on the sun angle instead of the time of day. Then the rollershutters will adjust themselves as the sun changes position over the seasons.
Anyway, seeing the rules themselves my suggestions are:
use either cron or Item state changes to trigger the rules
these feel overly complicated for what they do
these look like a strange combination of Nashorn JS and JS Scripting. Make sure to review the JS Scripting docs as you are missing out on a lot of stuff that’s already done for you to make your code simpler.
I don’t see any reason either of these rules should behave differently to simulate presence. Just let them run when you are not there. That’s the great thing about automation, it works the same whether you are there or not.
But if you do want to override the behaviors and simulate presence differently from how these would behave when you are actually there, disable these two rules and enable a presence simulation rule.
I have to admit when I read your comment I imaged you freaking out like “this dumb idiot misused and completely raped the very basic foundations of OH rule system” …and it made me giggle.
But I really appreciate that you took the time and not only answering my initial question (basically another rule to deactivate potential conflicting rules) but revieweing the complete rules.
Thank you!
Due to short time (sleeping time, small kids may keep us awake) I’ll limit this comment to few aspects:
-[general] I teached this stuff myself and really did not understand it. And still I don’t. As you can see I made a lot of mistakes-actually I was glad it worked as intended at some point. But still I’m looking forward to work in (in the code and into my brain) your feedback.
[Presence Simu] Yesterday was the first time I saw this presence simulation you are linking. Even if it is imho a sophisticated solution, I came to the result true presence simulation won’t match my requirements: “night behaviour absent != night behaviour present” ,e.g. rollershutter away from street need to close earlier like min(sunset, 20:00). Although I did not checked it in detail.
[Background] The rule has been generated by a small application I wrote. I modelled my house in JSON (based on OH semantic model), added few informations about KNX group structure and created the KNX groupadresses, the OH item-files, thing-files and rule-files. Therefore the regular structure.
I enjoy your elaborated feedback and I’m looking forward to go through details soon!
I’ld just like you to know that I got halfway through your comment. Thank you so far. It helped a lot. For many things I cannot remember (mostly due to incompetence) why I did it that way.
Regarding your feedback to the first rule:
I’ve updated
the variable definitions (according your proposal) and
the logic (according your proposal
the set of triggers (few were introduced as the beginning of the vacation mode dilemma)
including time library
from your proposals, but I’ve stayed
on the mixed trigger (rollershutter + time + sunset/rise)
the trigger with the item that will be created (automatic switch)
The second rule and the general feedback will be checked in few days.
Back to the main question (don’t get me wrong: I appreciate your feedback but don’t want to waste your time on my personal lack of scripting ability and further issues): If I read it correctly your only proposal is to use the presence simulation.
Sorry to let you know that my favorite is still usage of an item that will hold (or similar):
the itemname
the requested modes, e.g. “heat prevention” or “vacation”
probably the last automatic mode change (modified within the rules)
Below the updated first rule:
The sendCommandIfDifferent is out commented since I want to test it before going life.
rules.JSRule({
id: "r_SteckdosenStehlampeWZ",
name: "SteckdosenStehlampeWZ",
description: "Diese Regel schaltet SteckdosenStehlampeWZ automatisch.",
triggers: [triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZGesamtItem'),triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZSuedseiteItem'),triggers.ItemStateUpdateTrigger('RollaedenBeschattungWZWestseiteItem'),triggers.ItemStateUpdateTrigger('OneCallAPIweatherandforecast_Current_Sunset'),triggers.GenericCronTrigger("0 */10 * * * ? *"),triggers.ItemStateUpdateTrigger('JonasHandyPingOnlineState'),triggers.ItemStateUpdateTrigger('SvenjaHandyPingOnlineState'),triggers.ItemStateUpdateTrigger('SteckdosenStehlampeWZAutomatikSchalter')],
execute: (event) => {
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.r_SteckdosenStehlampeWZ');
// Additem für Automatismusdeaktivierungsitem, wenn nicht vorhanden.
var automatismus = items.getItem('SteckdosenStehlampeWZAutomatikSchalter', true);
if (automatismus == null){
items.addItem({
type: 'String',
name: 'SteckdosenStehlampeWZAutomatikSchalter',
label: 'Schaltet den Automatismus SteckdosenStehlampeWZ An/Aus',
category: 'light',
groups: ['light','powerplug','Switch'],
tags: ['Lightbulb','Switch','automaticSwitch'],
});
items['SteckdosenStehlampeWZAutomatikSchalter'].sendCommand('ON');
}
if (items['SteckdosenStehlampeWZAutomatikSchalter'].state != 'ON'){
return;
}
// Ansatz aus https://community.openhab.org/t/avoid-rule-conflicts-modelling-of-rules/164250/4
var currentTime2 = time.toZDT();
var sunsetDate2 = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunset);
var sunriseDate2 = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunrise);
var currentHours2 = currentTime2.hour();
var sunriseAndDelay2 = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunset).plusMinutes(15);
var sunsetAndDelay2 = time.toZDT(items.OneCallAPIweatherandforecast_Current_Sunrise).minusMinutes(15);
// den hier könnte ich nochmal aufräumen. Z.B. die Summe beider Werte
var rollershutterAreClosed2 = items['RollaedenBeschattungWZGesamtItem'].state >= 65 || items['RollaedenBeschattungWZSuedseiteItem'].state >= 65 || items['RollaedenBeschattungWZWestseiteItem'].state >= 65;
console.warn("now: " + currentTime2 .toLocalTime());
console.warn("sunriseAndDelay2: " + sunriseAndDelay2.toLocalTime());
console.warn("sunsetAndDelay2: " + sunsetAndDelay2.toLocalTime());
console.warn("rollershutterAreClosed2: " + rollershutterAreClosed2);
let command = "OFF"
if(currentTime2.isBetweenTimes("06:00", sunriseAndDelay2)) command = "ON";
else if (rollershutterAreClosed2 && currentTime2.isAfter(time.toZDT("08:00"))) command = "ON";
else if (now.isBefore(sunsetAndDelay2)) command = "ON";
else if (rollershutterAreClosed2 && currentTime2.isBefore(time.toZDT("17:00"))) command = "ON";
logger.warn("ich möchte SteckdoseAnAusWZSuedwest1Item auf "+ command + " setzen")
// items.SteckdoseAnAusWZSuedwest1Item.sendCommandIfDifferent(command);
// bis hier
var rollershutterAreClosed = items['RollaedenBeschattungWZGesamtItem'].state >= 65 || items['RollaedenBeschattungWZSuedseiteItem'].state >= 65 || items['RollaedenBeschattungWZWestseiteItem'].state >= 65;
}
});
rules.JSRule({
id: "r_SteckdosenStehlampeWZAutomatikSchalter",
name: "SteckdosenStehlampeWZAutomatikSchalter",
description: "Diese Regel schaltet SteckdosenStehlampeWZAutomatikSchalter automatisch.",
triggers: [triggers.ItemStateUpdateTrigger('SteckdosenStehlampeWZAutomatikSchalter')],
execute: (event) => {
var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.r_SteckdosenStehlampeWZAutomatikSchalter');
//Timer Callbackfunction
if (items['SteckdosenStehlampeWZAutomatikSchalter'].state != 'ON'){
var timeoutId = setTimeout(timerCallbackFunction,21600000, "SteckdosenStehlampeWZAutomatikSchalter");
//logger.warn('Timeout wird gestartet');
}
function timerCallbackFunction(itemName){
//logger.warn('callback wird aufgerufen');
if (items[itemName].state != 'ON'){
//logger.warn('Itemstate wird geändert');
items[itemName].sendCommand('ON');
}
}
}
});
First, this feels like an XY Problem. You are asking to solve a problem in a very specific way when some other approach is better over all. To address this:
This is fully automated. Why do something different for presence simulation in the first place? Just leave it fully automated and the home behaves the same way whether you are home or not. Changing the behavior of the home only when you are not home becomes a signal to the observant malicious actor that you are not home. Your presence simulation does the opposite of what you desire.
If you do want presence simulation, disable the automation rules while simulating presence, so only the presence simulations are running when you are away. Implement the presence simulations in different rules.
It’s hard to beat simulation presence by playing back the actual events recorded when you were home. So you don’t really have to implement the presence simulations if you don’t want to.
2 and 3 go together or 2 can be used on your own with a separate simulation rule you write.
The second is to address the loop on the rules are they are written. To put it bluntly, it’s impossible. These rules are too complicated and there isn’t enough information to solve that problem. My recommendations about the rule triggers are directly related to this. They are ways to simplify the rules so that there is even a glimmer of hope of figuring out why the loop happens. Though simplification will likely eliminate the loop on it’s own.
This rule is almost constantly running because it is triggering on updates to soooo many Items. The Item triggers should at least be changes instead of updates. There’s nothing to do when nothing changed. And that’s why the cron trigger is pointless. If nothing changed there’s nothing to do.
You almost never need both a cron trigger and Item triggers. When the Items change the rule will run to adjust to what changed. Using both Item triggers and cron triggers increases the likelihood that you have conflicts like the one described in the OP.
You should pretty much never command or update an Item within a rule that will cause the same rule to trigger again. Put another way, you should not mess with the Items that are used to trigger the rule. That is likely to cause a loop.
My recommendations with the triggers are not just stylistic. They are concrete recommendations for you to have any hope of ever solving the looping problem, assuming you ignore alternative approaches.