EDIT: code refactor to make it easier to read / follow
I’ve been testing out my JRuby rule to automate my pool heater so it only uses power from my PV / solar generated output, and not draw from the grid. I have a Fronius inverter + smart meter and using the Fronius binding, I can get the grid import (positive number) / export (negative) data.
I also control my Chlorinator timing / power in another rule, based on the sun elevation which changes according to the season. The pool pump is connected to a “logic or” circuit, so it will turn on when at least one of the chlorinator or the heat pump is on.
Here’s my current rule to control the pool heat pump:
require 'openhab'
TARGET_TEMPERATURE = QuantityType.new('30 °C')
SET_TEMPERATURE = QuantityType.new('36 °C')
#
# Run the pool heater on a strict time based schedule
# Set the Target Temperature to normal
# Turn on the heater earlier and keep it on if there's plenty of solar power outside the schedule
#
def run_scheduled_heating
PoolHeater_Set_Temp.ensure << TARGET_TEMPERATURE
PoolHeater_Boost.ensure.on
if @avg_grid_export > '3kW'
PoolHeater_Power.ensure.on
else
case TimeOfDay.now
when '9am'..'3pm' then PoolHeater_Power.ensure.on
when ('4pm'..) then PoolHeater_Power.ensure.off
end
end
end
def update_power_stats
@avg_solar_power = Solar_AC_Power.average_since(15.minutes, :influxdb) || QuantityType.new("0 W")
@avg_grid_draw = Solar_Grid_Power.average_since(1.minutes, :influxdb)
@avg_grid_draw60 = Solar_Grid_Power.average_since(60.minutes, :influxdb)
@avg_grid_export = @avg_grid_draw.negate
end
#
# Decide whether the heater needs to be turned on
#
# @return [Boolean] true if the heater is on, false otherwise
#
def power_on
return true if PoolHeater_Power.on?
return false unless TimeOfDay.now.between?('7am'..'3pm')
return false unless @avg_solar_power.positive?
return false unless @avg_grid_export > '3 kW' || Pool_Chlorinator.on?
PoolHeater_Power.ensure.on
true
end
#
# Send an OFF command when necessary, but always returns true
#
# @return [Boolean] Always returns true
#
def turn_off_heater
PoolHeater_Power.ensure.off
true
end
#
# Decide whether the heater needs to be turned off
#
# @return [Boolean] true if the heater is turned off, false/nil otherwise
#
def power_off
return turn_off_heater if TimeOfDay.now >= '6pm'
return turn_off_heater if TimeOfDay.now > '4pm' && @avg_grid_draw > '1 kW'
return turn_off_heater unless @avg_solar_power.positive?
return turn_off_heater if @avg_grid_draw60 && @avg_grid_draw60 > '2 kW'
end
#
# Adaptively return the target temperature.
# Lower the target temp on a day with low solar production
#
# @return [QuantityType] The adjusted target temperature
#
def target_temperature
return TARGET_TEMPERATURE - '2 °C' if @avg_grid_draw60.nil? || @avg_grid_draw60 > '2 kW' || @avg_solar_power < '1 kW'
TARGET_TEMPERATURE
end
#
# Returns true if the target temp has been reached. Also adjust the set temp
#
# @return [Boolean] true if the pool's current temp is the same or more than the target temp
#
def target_temp_reached?
result = PoolHeater_Current_Temp >= target_temperature
PoolHeater_Set_Temp.ensure << (result ? target_temperature : SET_TEMPERATURE)
result
end
#
# Adjust the heater's Boost mode on/off depending on excess solar power
#
def power_saving_mode
return unless @avg_grid_draw
return if target_temp_reached?
if PoolHeater_Boost.on?
PoolHeater_Boost.off if @avg_grid_draw > '2 kW'
elsif @avg_grid_export > '1 kW'
PoolHeater_Boost.on
end
end
rule 'Pool: Heater Control' do
every 5.minutes
between '9am'..'7pm'
on_start
run do
update_power_stats
if PoolHeater_Power_Saving.on?
power_saving_mode if power_on && !power_off
else
run_scheduled_heating
end
end
end
To plot the pool temperature, I only persist the data when the pool heater is on. When it’s off overnight, the water isn’t circulating and it will only sense the temperature of the water inside the unit. So I use this rule:
rule 'Pool: Persist pool temp' do
changed PoolHeater_Current_Temp
only_if PoolHeater_Power
run { PoolHeater_Current_Temp.persist(:influxdb) }
end