@NikM you should take advantage of the UoM / Dimensioned items. I rewrote your script below. Note we prefer snake_case in Ruby.
basic_power_consumption , forecast_1h, calculation, etc, are QuantityType, and technically you shouldn’t round / truncate the values. You only want to do that during the display and that’s where the format() comes in.
# Ensure that all relevant items below are declared as
# Number:Power for power
# Number:Energy for energy
rule "eMobility - Stop" do
received_command TestSwitch
run do
logger.info "Start"
basic_power_consumption = 300 | "kWh"
forecast_1h = WeatherLocalHours01SolarHarvest.state - basic_power_consumption
logger.info "Value #{forecast_1h}"
time_since = Time.now.min.minutes
average_solar_power = RCT_Power_Solar.average_since(time_since)
calculation = average_solar_power / 60 * minute
logger.info "Calculation #{calculation.format("%.2f %unit%")}"
forecast_1h = (5 | "kWh") - calculation
logger.info "Result #{forecast_1h.format("%.2f %unit%")}"
end
end
All my items are without UoM. I like my values without dimensions. They only get units while displaying. So its blank and easy to handle.
Actually I started openhab with UoM, but it was always more complicated with so without.
Thats will be a hard job for me to use snake_case, I used CamelCase since years… But I will try for the variables in Ruby. Thanks for the hint.
So all in all you think the subtraction is not working, because I dont use UoM?
One further question: What exact is the technical reason I should not round a value, where such a hugh accuracy is not needed?
For me it seems easier to use .round one time, than twice .format(“%.2f %unit%”) to display the value in a more readable…
You can use camelCase if you prefer, Ruby doesn’t care. The snake_case is just by convention.
You can also work without UoM if you prefer, although there should be no reason to avoid UoM because in Ruby it’s just as easy either way.
edit: I’m actually not sure why you got the error NullPointerException here. It seems to be a Java issue. Your initial suspicion about BigDecimals might be true. Can you create a reproduceable test for me to try?
Also, the convention with Ruby is to use 2 spaces for indentation. You could set up vscode to auto format your code using rubocop on file save. I use rubocop-daemon to make it fast (otherwise it takes several seconds every time you save).
It’s more of a philosophical preference, I guess. However, it is much preferable working with UoM vs plain number for the flexibility in dealing with different units, especially kW vs W, kWh vs Wh, etc.
As I started with openhab years ago, I had often problems in the RulesDSL with UoM. So I decided to implement with blank values.
But maybe it was my unexpirience with this.
I’m coming from C and C#…
I think I will give it a try once again…
Does it automatically handle for example kW and W in a calculation? No need to divide the kW value by 1000?
I will take a look for a test case next week, I was able to reproduce with the value from the influx db.
And I will also take a look for automatic formatting.
The whole idea of QuantityType is that each variable contains its unit, any operation involving QuantityType takes the units into consideration and performs automatic unit conversion as necessary for you.
This means you can have variable_a of 10 W + variable_b of 1 kW and when you add them together you’ll correctly get 1.01 kW or 1010 W, and not 11. Equally, when working with Temperatures for example, you can add and compare Celsius and Fahrenheit without having to first manually perform the unit conversion yourself. It is really very convenient to use.
11:30:06.981 [ERROR] [yobj.OpenHAB.DSL.Rules.AutomationRule] - wrong number of arguments (given 1, expected 0) (ArgumentError)
In rule: Lighting generic
uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:83:in `method_missing'
/etc/openhab/automation/jsr223/ruby/lights/motionSonsors.rb:12:in `block in <main>'
Here is my whole rule. If there is motion detected a member of groupMotionsensorPresences will switch to ON and turn the depending lights in the room ON
rule "Lighting generic" do
changed groupMotionsensorPresences.members
run do |event|
next if GetRoomSymbols(event.item.name) != "AZ"
light = items["groupLight_#{GetRoomSymbols(event.item.name)}_switches"]
logger.info "Switched light in #{GetRoomSymbols(event.item.name)} to #{event.state}"
case event.state
when ON then light.on
when OFF then light.off for: 1.minutes
end
end
not_if { |event| items["groupLight_#{GetRoomSymbols(event.item.name)}_switches"].nil? }
#otherwise { |event| logger.error "No matching light found for closet door #{event.item.name}" }
end
# Example of presence items: Motionsensor_BZ_1_presence
def GetRoomSymbols(itemName)
return itemName.gsub("Motionsensor_", "").gsub("_presence", "").gsub(/\d/, "").gsub("_", "")
end
Sorry, I’m not able to reproduce the scenario with constant values of BigDecimals. It’s only getting the error above, if I calculate with the persistant value from the database.
btw you could DRY your code a little and I’m not sure if your logic is correct in regards to the duration… I’d think you would want to have the timer on the ON command, not the off command? unless you wanted to turn off the light 1 minute AFTER the motion sensor changed to off? If that’s the case, you’d also want to cancel that timer when you receive another ON motion state before that timer expired, otherwise your light will “randomly” turn off.
If you could explain exactly what you wanted, I could post a sample code
The only thing I really want to know, is: Why is the
“when XY then DO for: 10.minutes”-pattern not working as expected?
But, yes. I want the lights to turn OFF after 1 minute, if there is no presence anymore.
Thanks for the offer. But you don’t have to write an example especially for me.
rule 'Closet Door Lights' do
changed TestSwitch
run do |event|
case event.state
when ON then groupLight_AZ_switches.on for: 1.minutes
when OFF then groupLight_AZ_switches.off
end
end
end
Results in the following error, if TestSwitch changed to ON:
06:51:41.389 [ERROR] [yobj.OpenHAB.DSL.Rules.AutomationRule] - wrong number of arguments (given 1, expected 0) (ArgumentError)
In rule: Closet Door Lights
uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/delegate.rb:83:in `method_missing'
/etc/openhab/automation/jsr223/ruby/test.rb:5:in `block in <main>'
Your syntax (GroupItem.on for: 1.minute) should work on the latest version of the library (5.0 - currently on RC version). It has been a while since I used version 4.x, so I’m not sure if that should work there.
This is working. The error occurs only if I use GroupItems with the mentioned timer-pattern. Switching GroupItems to ON/OFF is no problem.
Another question to timers. What could be the id of a timer?
Looks like there are only symbols allowed? I’m facing some troubles using a string as a timer id.
Maybe thats because a string-instance is not unique?
I could imagine to have 2 timers in a rule. Therefore I like to use something like:
timer_one = "#{event.item}_1" # trying hard to use snake_case ;-)
timer_two = "#{event.item}_2"
timers[timer_one] = after 30.seconds do
# dimm lights
timers[timer_two] = after 30.seconds do
# turn lights off
end
end
I know, that I could reschedule a timer to only use one timer, but I have no better example…
timer ids can be anything: strings, symbols, array, object, hash, anything that exists in Ruby world can be used. Even a class can be used!
But the way you’re using timers[] is incorrect. You don’t assign to timers[] like that. It is a special object that acts like a hash so you can access timer ids.
You create a timer with an id using after like this:
after(30.seconds, id: "yourid") do
# do stuff here
end
timers["yourid"].cancel
also timer_one = "#{event.item}_1" is wrong here. It should be timer_one = "#{event.item.name}_1"
There has been a big change in 5.0 in the interpretation of Item objects to avoid ambiguities like this.
Finally, here’s how I would rewrite your rule:
rule "Lighting generic" do
changed groupMotionsensorPresences.members, to: ON
run do |event|
room_code = getRoomSymbols(event.item.name)
next if room_code != "AZ"
lights = items["groupLight_#{room_code}_switches"]
next unless lights
logger.info "Switched light in #{room_code} to #{event.state}"
lights.all_members.ensure.on
after(1.minute, id: room_code) { lights.all_members.ensure.off }
end
end
# Example of presence items: Motionsensor_BZ_1_presence
def getRoomSymbols(itemName)
itemName.gsub("Motionsensor_", "").gsub("_presence", "").gsub(/\d+/, "").gsub("_", "") # no need for "return" here
# Alternative implementation (if the pattern allows)
# itemName.split("_")[1]
end
Your lights will turn on and stay on as long as movements were detected, and only turn off once no movements were detected for 1 minute.
Please test it, I’m not 100% sure if the ensure syntax above would work as I don’t have this version installed. Let me know if you encountered any errors.
Could you explain the bigger picture here, so I can make a better suggestion? How does it fit in the overall rule, what triggers it etc. Do you want to make the lights brighter under a certain conditions, and dim them otherwise?
The reason I need to know is that you might want to cancel some of those timers when the other conditions were met, so your lights don’t dim or turn off by mistake because you hadn’t cancelled those timers.
@allowed_rooms = ["AZ", "BZ", "F2"]
################################
# Lighting with motions sensors
rule "Lighting generic" do
changed groupMotionsensorPresences.members
run do |event|
room_code = getRoomCode(event.item.name)
# Excape if room is not allowed to be switched
next unless @allowed_rooms.include? room_code
enabled = items["Motionsensors_#{room_code}_enable"] #A switch to turn off motion detection
dimmer = items["groupLight_#{room_code}_dimmers"]
light = items["groupLight_#{room_code}_switches"]
light_current_value = items["groupMotionsensors_#{room_code}_light"] # including brightness values, no light is needed if its bright enough
light_on_value = items["Motionsensors_#{room_code}_lightOnValue"]
timer_duration = items["Motionsensors_#{room_code}_timerDuration"].to_i.seconds # NumberItem to set the individual timer duration
case event.state
when ON
if timers[room_code]
timers[room_code]&.cancel
dimmer.command(100)
logger.info "LIGHTS-MOTION-CONTROL: Canceled timer after presence is detected"
end
if light.off? && enabled.on? && light_on_value >= light_current_value
dimmer.command(100)
logger.info "LIGHTS-MOTION-CONTROL: Switched light in #{room_code} to ON (dimmer=#{dimmer}), presence is detected."
end
logger.info "LIGHTS-MOTION-CONTROL: timer #{timer_duration}s, enabled #{enabled}, light_on_value #{light_on_value}, light_current_value #{light_current_value}"
when OFF
if light.on? && enabled.on?
after(timer_duration, id: room_code) do # Use the triggering item as the timer ID
if light.on? && enabled.on? && items["groupMotionsensors_#{room_code}_presence"].off?
if dimmer != 50
dimmer.command(50)
logger.info "LIGHTS-MOTION-CONTROL: Dimm light in #{room_code}, no presence anymore."
timers[room_code]&.reschedule 10.seconds
else
light.off
logger.info "LIGHTS-MOTION-CONTROL: Switched light in #{room_code} to OFF, no presence anymore."
timers[room_code]&.cancel
end
end
end
end
end
end
end
# Example of presence items: Motionsensor_BZ_1_presence
def getRoomCode(item_name)
return item_name.split("_")[1]
end
The idea is to use the motion sensors for presence detection. If there is presence detected turn on the light in the depending room.
If the motion sensor updates his state for presence to OFF a timer should start to dimm the lights to a lower value. After this a second or a rescheduled timer should turn the lights completely OFF.
If In the time the timers are active a presence is redetected by the motion sensor the timer should be canceled.
If have up and running this with RulesDSL and for each single motion sensor/room in hunderds of code lines. So if I’m going to refactor, I will make it more generic.
Seems like it not going into this code, if presence is redetected:
when ON
if timers[room_code] #light.on? && enabled.on?
timers[room_code]&.cancel
dimmer.command(getDimmerValue(room_code))
logger.info "LIGHTS-MOTION-CONTROL: Canceled timer after presence is detected"
end
Thanks for the good explanation to timers in your last post. I have updated this rule with your good advices.
ALLOWED_ROOMS = %w[AZ BZ F2]
################################
# Lighting with motions sensors
rule "Lighting generic" do
changed groupMotionsensorPresences.members, to: ON
run do |event|
room_code = get_room_code(event.item.name)
# Escape if room is not allowed to be switched
next unless ALLOWED_ROOMS.include? room_code
enabled = items["Motionsensors_#{room_code}_enable"] # A switch to turn off motion detection
next unless enabled.on?
dimmer = items["groupLight_#{room_code}_dimmers"]
light = items["groupLight_#{room_code}_switches"]
light_current_value = items["groupMotionsensors_#{room_code}_light"]&.state # including brightness values, no light is needed if its bright enough
light_on_value = items["Motionsensors_#{room_code}_lightOnValue"]&.state
timer_duration = items["Motionsensors_#{room_code}_timerDuration"]&.state&.to_i&.seconds || 3.minutes # NumberItem to set the individual timer duration
next unless light_on_value >= light_current_value
dimmer.command(100)
logger.info "LIGHTS-MOTION-CONTROL: Canceled timer after presence is detected"
after(timer_duration, id: room_code) do |timer|
next unless light.on?
next unless enabled.on?
if dimmer.state == 50
light.off
logger.info "LIGHTS-MOTION-CONTROL: Switched light in #{room_code} to OFF, no presence anymore."
else
dimmer.command(50)
logger.info "LIGHTS-MOTION-CONTROL: Dimm light in #{room_code}, no presence anymore."
timer.reschedule 10.seconds
end
end
end
end
# Example of presence items: Motionsensor_BZ_1_presence
def get_room_code(item_name)
item_name.split("_")[1]
end
Works and I recognize that I still have something to learn.
But it’s behavior is not like a charme.
the motion sensors sometimes updates the presence status very slow. In some cases it could be, that there will be no update for 60-90s.
If the rule now dimms down the light, because the timer ran out, there is no chance to brighten the lights up again. Thats the reason for me to start the timer with the presence status turning to OFF.
In this case the light dimms down and if you are in the room, u still have to move and the light will be dimm up again.