Hello
I am ripping my hair out. So here I have a lux sensor and some light the ideea is simple when it gets dark turn some lights on only once then at 11pm turn some lights off and in the morning at sunrise turn the remaining lights off but send the commands only once not everytime the lux changes. This is what I done till now:
var { rules, triggers, items, time, log } = require('openhab');
rules.JSRule({
name: "Control Lights Based on Lux Levels with Astro Times",
description: "Turns on lights at dawn and off at dusk based on lux readings and astro sunrise/sunset times",
triggers: [
triggers.ItemStateChangeTrigger("Weather_Station_lux_total"),
triggers.ItemStateChangeTrigger("Sunrise_Start_Time"),
triggers.ItemStateChangeTrigger("Sunrise_End_Time"),
triggers.ItemStateChangeTrigger("Sunset_Start_Time"),
triggers.ItemStateChangeTrigger("Sunset_End_Time")
],
execute: (event) => {
var lux = Quantity(items.getItem("Weather_Station_lux_total").state);
var sunriseStart = time.toZDT(items.getItem("Sunrise_Start_Time").state);
var sunriseEnd = time.toZDT(items.getItem("Sunrise_End_Time").state);
var sunsetStart = time.toZDT(items.getItem("Sunset_Start_Time").state);
var sunsetEnd = time.toZDT(items.getItem("Sunset_End_Time").state);
var now = time.ZonedDateTime.now();
var itemsToControl = [
"Gate_outside_spots_ground", "Gate_small_outside_spots_ground",
"Front_door_outside_Light1", "Terrace_back_outside_Light1"
];
if (lux.lessThan(Quantity('80 lx')) && now.isAfter(sunsetStart) && now.isBefore(sunsetEnd)) {
console.warn(`Lux reading: ${lux}`);
console.warn(`Current time: ${now}`);
console.warn(`Sunrise start time: ${sunriseStart}`);
console.warn(`Sunrise end time: ${sunriseEnd}`);
console.warn(`Sunset start time: ${sunsetStart}`);
console.warn(`Sunset end time: ${sunsetEnd}`);
console.warn("Lux is below 80 and current time is between sunset start and sunset end. Turning on the lights.");
itemsToControl.forEach(item => {
if (items.getItem(item).state !== "ON") {
items.getItem(item).sendCommand("ON");
console.warn(`Sent ON command to ${item}`);
}
});
} else if (lux.greaterThan(Quantity('100 lx')) && now.isAfter(sunriseStart) && now.isBefore(sunriseEnd)) {
console.warn(`Lux reading: ${lux}`);
console.warn(`Current time: ${now}`);
console.warn(`Sunrise start time: ${sunriseStart}`);
console.warn(`Sunrise end time: ${sunriseEnd}`);
console.warn(`Sunset start time: ${sunsetStart}`);
console.warn(`Sunset end time: ${sunsetEnd}`);
console.warn("Lux is above 100 and current time is between sunrise start and sunrise end. Turning off the lights.");
itemsToControl.forEach(item => {
if (items.getItem(item).state !== "OFF") {
items.getItem(item).sendCommand("OFF");
console.warn(`Sent OFF command to ${item}`);
}
});
} else {
console.info("No action required based on current lux and time conditions.");
}
}
});
rules.JSRule({
name: "Turn Off Lights at 11 PM",
description: "Turns off specific lights at 11 PM",
triggers: [
triggers.TimeOfDayTrigger("23:00:00")
],
execute: () => {
console.warn("It's 11 PM. Checking lights to turn off.");
var itemsToTurnOff = [
"Front_door_outside_Light1",
"Terrace_side_outside_Light1"
];
itemsToTurnOff.forEach(item => {
if (items.getItem(item).state !== "OFF") {
items.getItem(item).sendCommand("OFF");
console.warn(`Sent OFF command to ${item}`);
} else {
console.warn(`Skipping OFF command for ${item} as it is already OFF.`);
}
});
}
});
For me the problem is it keeps getting triggered every time the lux changes and my mind cannot comprehend how to make it trigger once for 80lux or 100lux any help ? I was thinking to store the action in something then the next run check if that action was performed is that the proper way of doing it ? I mean there must be a simpler way of doing all this…
Even though the lux changes, doesn’t your rule only send the command if the light isn’t already in the same state? So even though your rule gets triggered multiple times, it only sends the command once, right?
This is not a problem. This is how OH works by design. An event happens (trigger) and the rule does something in response. Part of that response is to decide what to do or whether there is anything that needs to be done. Even making that decision requires rule code to run which requires the rule to trigger.
In managed rules there is a concept of “Conditions”. These are comparisons or even code that runs to determine whether the rule’s actions should run or not. There you could put some tests (e.g. lights ON and lux >= 80 and lights OFF and lux < 80). Then the actions (the equivalent of the “execute” part of a JSRule) will only run when the conditions are true. But even with conditions, the rules will still trigger every time the lux changes.
Since JSRule doesn’t support conditions like that, you need to use an if statement inside your script action instead, which is exactly what you’ve done.
Anything along these lines is going to make the rule far more complex and do nothing more than what you are doing now.
There are some features in openhab-js and OH itself that you could be using to make this code a little easier to write and understand but for the most part what you are doing now is pretty standard.
Some ideas for improvement:
Don’t you want to use the triggers.DateTimeTrigger('Sunrise_Start_Time') trigger? You don’t want this rule to run when the Item’s state changes, which happens around midnight when Astro recalculates the new times for the current day. You want to trigger the rule at Sunrise/Sunset to see if you need to adjust the lights. Even better would be to trigger the rule using the Astro event Channel.
Where possible it’s better to use const for variables that are not changed, let for variables that can change in the rule. Only use var where you’ve no other options and you won’t encounter that situation unless you are creating managed rules.
const lux = items.Weather_Station_lux_total.quantityState; // the Item already carries the state as a Quantity
const sunriseStart = time.toZDT(items.Sunrise_Start_Time); // time.toZDT can convert the Item itself, no need to get the state yourself
const now = time.toZDT(); // when passed nothing, time.toZDT() returns now`
Instead of creating a list hard coded in the rule, put these Items in a Group:Switch:OR(ON, OFF). You can send a command to the Group and it will get sent to all the Items. Another option to to create a Scene and you can run the Scene from this rule. In fact, if you had a Scene or a Group and a managed rule with conditions you wouldn’t even need to write any code at all to implement this behavior.
openhab-js has items[item].sendCommandIfDifferent('ON'); so you don’t have to explicitly test before commanding the Item. If your Items are in a Group like described above, the for loop and the if statement can be replaced with just items.LuxLights.sendCommandIfDifferent(ON).
As a managed rule it would look something like this:
trigger the rule on changes to the lux Item and at sunrise start and sunset start (I don’t see any reason why the end times are useful here) or, use the event Channels trigger from the Astro Thing to trigger the rule on the START events.
Add a Script condition which would look something like this:
var lux = items.Weather_Station_lux_total.quantityState
var onThresh = Quantity('80 lx');
var offThresh = Quantity('100 lx');
var now = time.toZDT();
// It's dark and after sunset and the lights are not already ON
(lux.lessThan(onThresh) && now.isAfter(items.Sunset_Start_Time) && items.LuxLights.state != 'ON')
// it's not dark and after sunrise and the lights are not already OFF
|| (lux.greaterThan(offThresh) && now.isAfter(items.Sunrise_Start_Time) && items.LuxLights.state != 'OFF');
The Rule action then becomes (assuming the Items are in a Group)
@rlkoshak@jimtng I don’t know if you guys have heard of this but basically it measures the lux and has a simple contact sunset on sunrise off:
How would I replicate the functionality of that using openhab and a lux reading ? For me the problem is that lux level goes back from 0 to whatever in the morning Wich is normal but how to ignore it during that period.
OH is event driven. The event that drives it is the lux changing. You ignore it exactly like you implemented it above with an if statement.
These lines are how you ignore the lux. And “ignoring” is a misnomer. You are not and never are ignoring the changes in lux. Instead, every time the lux changes you reassess whether there is something that needs to be done or not.
You still need the lux Item to trigger the rule unless you only care about the lights turning ON/OFF exactly at sunrise and sunset. Note that as you’ve shown the code, if, for example, sunrise happens and the current lux is still < 100 lx the lights will never turn OFF for that day.
I’m not sure I wholly understand the problem. But if you want to ignore a change from 0 to something else, add that to your if statements.
Final note, this if statement can be replaced with items[itemName].sendCommandIfDifferent(newCmd);
So in layman terms. A lux sensor can be from 0 lx till around let’s say 100000 lx. Now in the evening it will go towards 0 and in the morning goes from 0 till what I mentioned before.
The problem I want to solve is to have an item that updated to on when it’s evening and off in the morning.
Now that device that I mentioned early works like a charm you set min lux max lux debounce for example 15 minutes so if the lux fluctuates and also earlier off commands for example don’t keep the lights on all night.
I know you helped me last time when you created the open door and I also know you make the best templates to handle basic logic for home automation.
I believe in you
Oh, so you want to implement hysteresis which requires a large change from the threshold before it counts. That’s easy enough. There are lots of different approaches you can use:
Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9] : you can set a threshold (e.g. 100 lx) and an amount that it has to change below (e.g. 20 lx) that the sensor would have to read to end the alert. The rule template will call a rule you implement where you provide the “what to do”.
There is a hysteresis profile you can put on the Channel providing the lux linked to a Switch Item. You can specify a threshold (e.g. 100 lx) and a hysteresis (e.g. 20 lx) and the Switch will be ON when the value exceeds the threshold but won’t turn off until the value drops at least 5 below the threshold. There are other properties to control the behavior. You’d then use the Switch to see whether you need to turn ON/OFF the light.
You can create a version of Filter Profile which basically implements a hystersis on the values coming from the Channel before it ever gets to the Item. The new value has to be at least 20 lx greater or lower that the last value sent to the Item in order for the Item to update with the new value.
In your rule, the first time the lux exceeds a threshold, record the lux reading in the cache or in another Item. Then ignore subsequent updates until the change is greater than the hysteresis. It’s basically the same code as you’ll find in 3 only you’d adapt that to a rule instead of a transform.