Radiator Functionality improved

Šī revīzija ir iekļauta:
Dirk Alders 2023-01-10 23:17:47 +01:00
vecāks 0690793ba8
revīzija 045e270a6b
4 mainīti faili ar 178 papildinājumiem un 111 dzēšanām

Parādīt failu

@ -22,21 +22,6 @@ def payload_filter(payload):
return payload.decode("utf-8")
def percent_bar(value):
rv = ""
for i in range(0, 10):
rv += u"\u25ac" if (value - 5) > 10*i else u"\u25ad"
return rv
def green_led():
return colored.fg('green') + "\u2b24" + colored.attr("reset")
def grey_led():
return colored.fg('light_gray') + "\u2b24" + colored.attr("reset")
def command_int_value(value):
try:
return int(value)
@ -51,6 +36,39 @@ def command_float_value(value):
print("You need to give a integer parameter not '%s'" % str(value))
def devicename(topic):
return " - ".join(topic.split('/')[1:])
def percent_bar(value):
rv = ""
for i in range(0, 10):
rv += u"\u25ac" if (value - 5) > 10*i else u"\u25ad"
return rv
def print_light(color, state, topic, description, led=False):
if led is True:
if state is True:
icon = colored.fg('green') + "\u2b24" + color
else:
icon = colored.fg('light_gray') + "\u2b24" + color
else:
icon = u'\u2b24' if state is True else u'\u25ef'
print(color + 10 * ' ' + icon + 9 * ' ' + devicename(topic), description + colored.attr("reset"))
def print_switch(color, state, topic, description):
icon = u'\u25a0' if state is True else u'\u25a1'
print(color + 10 * ' ' + icon + 9 * ' ' + devicename(topic), description + colored.attr("reset"))
def print_percent(color, prefix, perc_value, value_str, topic, description):
if len(prefix) > 1 or len(value_str) > 7:
raise ValueError("Length of prefix (%d) > 1 or length of value_str (%d) > 7" % (len(prefix), len(value_str)))
print(color + prefix + percent_bar(perc_value), value_str + (8 - len(value_str)) * ' ' + devicename(topic), description + colored.attr("reset"))
class base(object):
AUTOSEND = True
COMMANDS = []
@ -226,11 +244,9 @@ class shelly(base):
def print_formatted(self, device, key, value):
if value is not None:
icon = u'\u2b24' if value == "on" else u'\u25ef'
info = (" - %ds" % self.__output_0_auto_off__) if self.__output_0_auto_off__ is not None and value == "on" else ""
channel = "(%s%s)" % (self.names.get(key, key), info)
devicename = " - ".join(self.topic.split('/')[1:])
print(COLOR_SHELLY + 10 * ' ' + icon + 9 * ' ' + devicename + ' ' + channel + colored.attr("reset"))
print_light(COLOR_SHELLY, value == "on", self.topic, channel)
class my_powerplug(base):
@ -293,10 +309,7 @@ class my_powerplug(base):
def print_formatted(self, device, key, value):
if value is not None:
icon = u'\u2b24' if value else u'\u25ef'
channel = "(%s)" % self.names.get(key, "Channel %d" % (int(key) + 1))
devicename = " - ".join(self.topic.split('/')[1:])
print(COLOR_POWERPLUG + 10 * ' ' + icon + 9 * ' ' + devicename + ' ' + channel + colored.attr("reset"))
print_light(COLOR_POWERPLUG, value, self.topic, "(%s)" % self.names.get(key, "Channel %d" % (int(key) + 1)))
class silvercrest_powerplug(base):
@ -340,10 +353,7 @@ class silvercrest_powerplug(base):
def print_formatted(self, device, key, value):
if value is not None:
icon = u'\u2b24' if value == "on" else u'\u25ef'
channel = "(%s)" % self.names.get(key, key)
devicename = " - ".join(self.topic.split('/')[1:])
print(COLOR_POWERPLUG + 10 * ' ' + icon + 9 * ' ' + devicename + ' ' + channel + colored.attr("reset"))
print_light(COLOR_POWERPLUG, value == "on", self.topic, "(%s)" % self.names.get(key, key))
class silvercrest_motion_sensor(base):
@ -372,10 +382,7 @@ class silvercrest_motion_sensor(base):
def print_formatted(self, device, key, value):
if value is not None:
if value:
print(COLOR_MOTION_SENSOR + 10 * ' ' + u'\u2b24' + 9 * ' ' + " - ".join(self.topic.split('/')[1:]) + colored.attr("reset"))
else:
print(COLOR_MOTION_SENSOR + 10 * ' ' + u'\u25ef' + 9 * ' ' + " - ".join(self.topic.split('/')[1:]) + colored.attr("reset"))
print_light(COLOR_MOTION_SENSOR, value, self.topic, "")
class tradfri_light(base):
@ -480,24 +487,19 @@ class tradfri_light(base):
if value is not None:
color = COLOR_LIGHT_ACTIVE
if key == self.KEY_STATE:
if value == "on":
print(color + 10 * ' ' + u'\u2b24' + 9 * ' ' + " - ".join(self.topic.split('/')[1:]) + colored.attr("reset"))
else:
print(color + 10 * ' ' + u'\u25ef' + 9 * ' ' + " - ".join(self.topic.split('/')[1:]) + colored.attr("reset"))
print_light(COLOR_LIGHT_ACTIVE, value == "on", self.topic, "")
self.print_formatted(device, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
self.print_formatted(device, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
if self.data.get(self.KEY_STATE) != "on":
color = COLOR_LIGHT_PASSIVE
if key == self.KEY_BRIGHTNESS:
value = round(value * 100 / 256, 0)
else:
value = round((value - 250) * 100 / 204, 0)
sys.stdout.write(color)
sys.stdout.write('B' if key == gui_light.KEY_BRIGHTNESS else 'C')
sys.stdout.write(percent_bar(value))
sys.stdout.write("%3d%%" % value + 5 * " ")
print(" - ".join(self.topic.split('/')[1:]) + colored.attr("reset"))
perc_value = round(value * 100 / 256, 0) if key == self.KEY_BRIGHTNESS else round((value - 250) * 100 / 204, 0)
print_percent(
COLOR_LIGHT_PASSIVE if self.data.get(self.KEY_STATE) != "on" else COLOR_LIGHT_ACTIVE,
'B' if key == gui_light.KEY_BRIGHTNESS else 'C',
perc_value,
"%3d%%" % perc_value,
self.topic,
""
)
class gui_light(tradfri_light):
@ -576,24 +578,22 @@ class gui_light(tradfri_light):
def print_formatted(self, device, key, value):
if value is not None:
color = COLOR_GUI_ACTIVE
device = " - ".join(self.topic.split('/')[1:])
if key == self.KEY_STATE:
if value == True:
print(color + 10 * ' ' + u'\u25a0' + 9 * ' ' + device + colored.attr("reset"))
else:
print(color + 10 * ' ' + u'\u25a1' + 9 * ' ' + device + colored.attr("reset"))
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "")
elif key == self.KEY_ENABLE:
self.print_formatted(device, self.KEY_BRIGHTNESS, self.data.get(self.KEY_BRIGHTNESS))
self.print_formatted(device, self.KEY_COLOR_TEMP, self.data.get(self.KEY_COLOR_TEMP))
elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
if not self.data.get(self.KEY_ENABLE, False):
color = COLOR_GUI_PASSIVE
value = round(value * 10 if key == self.KEY_COLOR_TEMP else value, 0)
sys.stdout.write(color)
sys.stdout.write('B' if key == self.KEY_BRIGHTNESS else 'C')
sys.stdout.write(percent_bar(value))
print("%3d%%" % value + 5 * " " + device + colored.attr("reset"))
perc_value = round(value * 10 if key == self.KEY_COLOR_TEMP else value, 0)
print_percent(
COLOR_GUI_PASSIVE if not self.data.get(self.KEY_ENABLE, False) else COLOR_GUI_ACTIVE,
'B' if key == self.KEY_BRIGHTNESS else 'C',
perc_value,
"%3d%%" % perc_value,
self.topic,
""
)
elif key == self.KEY_TIMER:
if isinstance(value, (int, float, complex)) and not isinstance(value, bool):
if self.maxvalue is None:
@ -606,13 +606,10 @@ class gui_light(tradfri_light):
self.maxvalue = None
self.last_printed = None
if self.last_printed is None or abs(self.last_printed - perc) >= 4.95:
print(color + 't' + percent_bar(perc) + '%3d%%' % perc + 5 * ' ' + device + ' (%.1f)' % disp_value + colored.attr('reset'))
print_percent(COLOR_GUI_ACTIVE, 't', perc, '%3d%%' % perc, self.topic, '(%.1f)' % disp_value)
self.last_printed = perc
elif key.startswith(self.KEY_LED_X[:-2]):
led = green_led() if value else grey_led()
ledname = '(%s)' % self.led_names.get(key, key)
devicename = ' - '.join(self.topic.split('/')[1:])
print(10 * ' ' + led + 9 * ' ' + COLOR_GUI_ACTIVE + devicename + ' ' + ledname + colored.attr("reset"))
print_light(COLOR_GUI_ACTIVE, value, self.topic, '(%s)' % self.led_names.get(key, key), True)
class tradfri_button(base):
@ -686,10 +683,7 @@ class gui_led_array(base):
print("Unknown key %s in %s" % (targetkey, self.__class__.__name__))
def print_formatted(self, device, key, value):
led = green_led() if value else grey_led()
channel = '(%s)' % self.names.get(key, key)
devicename = ' - '.join(self.topic.split('/')[1:])
print(10 * ' ' + led + 9 * ' ' + COLOR_GUI_ACTIVE + devicename + ' ' + channel + colored.attr("reset"))
print_light(COLOR_GUI_ACTIVE, value, self.topic, '(%s)' % self.names.get(key, key), True)
class remote(base):
@ -750,9 +744,7 @@ class brennenstuhl_radiator_valve(base):
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
sys.stdout.write(COLOR_RADIATOR_VALVE + '\u03d1' + percent_bar(perc))
sys.stdout.write("%4.1f°C" % value + 3 * " ")
print(devicename + colored.attr("reset"))
print_percent(COLOR_RADIATOR_VALVE, '\u03d1', perc, "%4.1f°C" % value, self.topic, "")
class gui_radiator_valve(base):
@ -765,22 +757,31 @@ class gui_radiator_valve(base):
KEY_SETPOINT_TEMP = "setpoint_temp"
KEY_SETPOINT_TO_DEFAULT = "setpoint_to_default"
KEY_BOOST = 'boost'
KEY_AWAY = "away"
KEY_SUMMER = "summer"
KEY_ENABLE = "enable"
#
COMMANDS = [
"get_temperature",
"get_temperature_setpoint", "set_temperature_setpoint",
"trigger_boost", "trigger_setpoint_to_default"
"trigger_boost", "trigger_setpoint_to_default",
"toggle_away", "toggle_summer",
]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self.add_callback(self.KEY_SETPOINT_TEMP, self.print_formatted, None)
self.add_callback(self.KEY_TIMER, self.print_formatted, None)
self.add_callback(self.KEY_AWAY, self.print_formatted, None)
self.add_callback(self.KEY_SUMMER, self.print_formatted, None)
#
self.store_data(**{
self.KEY_TEMPERATURE: 20.7,
self.KEY_SETPOINT_TEMP: 20,
self.KEY_TIMER: 0,
self.KEY_AWAY: False,
self.KEY_SUMMER: False,
self.KEY_ENABLE: True
})
def __rx__(self, client, userdata, message):
@ -815,26 +816,32 @@ class gui_radiator_valve(base):
self.send(self.KEY_BOOST, True)
elif command == self.COMMANDS[4]:
self.send(self.KEY_SETPOINT_TO_DEFAULT, True)
elif command == self.COMMANDS[5]:
self.send(self.KEY_AWAY, not self.data.get(self.KEY_AWAY))
elif command == self.COMMANDS[6]:
self.send(self.KEY_SUMMER, not self.data.get(self.KEY_SUMMER))
def print_formatted(self, device, key, value):
devicename = ' - '.join(self.topic.split('/')[1:])
if key in [self.KEY_TEMPERATURE, self.KEY_SETPOINT_TEMP, self.KEY_TIMER]:
if key in [self.KEY_TEMPERATURE, self.KEY_SETPOINT_TEMP]:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
info = " (Temperature)" if key == self.KEY_TEMPERATURE else " (Setpoint)"
else:
try:
perc = 100 * value / 60
except TypeError:
value = 0
perc = 0
info = " (Timer)"
if key == self.KEY_TIMER:
try:
perc = 100 * value / 60
except TypeError:
value = 0
perc = 0
print_percent(COLOR_GUI_ACTIVE, 'T', perc, "%4.1fmin" % value, self.topic, "(Timer)")
elif key == self.KEY_TEMPERATURE:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
sys.stdout.write(COLOR_GUI_ACTIVE)
if key == self.KEY_TIMER:
sys.stdout.write('T' + percent_bar(perc) + "%4.1fmin" % value + 2 * " ")
else:
sys.stdout.write('\u03d1' + percent_bar(perc) + "%4.1f°C" % value + 3 * " ")
print(devicename + info + colored.attr("reset"))
print_percent(COLOR_GUI_ACTIVE, '\u03d1', perc, "%4.1f°C" % value, self.topic, "(Temperature)")
elif key == self.KEY_SETPOINT_TEMP:
perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
perc = 100 if perc > 100 else perc
perc = 0 if perc < 0 else perc
print_percent(COLOR_GUI_ACTIVE if self.data.get(self.KEY_ENABLE) else COLOR_GUI_PASSIVE,
'\u03d1', perc, "%4.1f°C" % value, self.topic, "(Setpoint)")
elif key == self.KEY_AWAY:
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "(Away Mode)")
elif key == self.KEY_SUMMER:
print_switch(COLOR_GUI_ACTIVE, value, self.topic, "(Summer Mode)")

Parādīt failu

@ -738,8 +738,20 @@ class nodered_gui_radiator(nodered_gui_timer):
KEY_SETPOINT_TEMP = "setpoint_temp"
KEY_SETPOINT_TO_DEFAULT = "setpoint_to_default"
KEY_BOOST = 'boost'
KEY_AWAY = "away"
KEY_SUMMER = "summer"
KEY_ENABLE = "enable"
#
RX_KEYS = [KEY_TEMPERATURE, KEY_SETPOINT_TEMP, KEY_SETPOINT_TO_DEFAULT, KEY_BOOST]
RX_KEYS = [KEY_TEMPERATURE, KEY_SETPOINT_TEMP, KEY_SETPOINT_TO_DEFAULT, KEY_BOOST, KEY_AWAY, KEY_SUMMER]
def __init__(self, mqtt_client, topic):
super().__init__(mqtt_client, topic)
self[self.KEY_ENABLE] = True
self.add_callback(self.KEY_AWAY, None, self.enable_gui)
self.add_callback(self.KEY_SUMMER, None, self.enable_gui)
def enable_gui(self, device, key, data):
self.pack(self.KEY_ENABLE, not self.get(self.KEY_AWAY) and not self.get(self.KEY_SUMMER))
#
# TX
@ -760,6 +772,22 @@ class nodered_gui_radiator(nodered_gui_timer):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_setpoint_temperature(data)
def set_away(self, data):
"""data: [True, False]"""
self.pack(self.KEY_AWAY, data)
def set_away_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_away(data)
def set_summer(self, data):
"""data: [True, False]"""
self.pack(self.KEY_SUMMER, data)
def set_summer_mcb(self, device, key, data):
self.logger.debug("Sending %s with content %s", key, str(data))
self.set_summer(data)
class brennenstuhl_heatingvalve(base):
KEY_LINKQUALITY = "linkquality"

Parādīt failu

@ -31,15 +31,14 @@ class ground_floor_west_floor(room_shelly_silvercrest_light):
super().send_init_message_main_light()
self.main_light_tradfri_2.mqtt_client.send(self.main_light_tradfri_2.topic + "/get", '{"state": ""}')
# radiator valve
self.radiator_function = radiator_function(mqtt_client, config.TOPIC_GFW_MARION_RADIATOR_VALVE_ZIGBEE,
config.TOPIC_GFW_MARION_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_GFW_MARION)
class ground_floor_west_marion(room_shelly):
# http://shelly1l-E8DB84A1E067
def __init__(self, mqtt_client):
super().__init__(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY, config.TOPIC_GFW_MARION_MAIN_LIGHT_GUI)
# radiator valve
self.radiator_function = radiator_function(mqtt_client, config.TOPIC_GFW_MARION_RADIATOR_VALVE_ZIGBEE,
config.TOPIC_GFW_MARION_RADIATOR_VALVE_GUI, config.DEFAULT_TEMPERATURE_GFW_MARION)
class ground_floor_west_dirk(room_shelly_tradfri_light):

Parādīt failu

@ -133,14 +133,16 @@ class circulation_pump(room_shelly):
self.pump_timer -= self.ct.cycle_time / 60
# TODO:
# - improve devices.brennenstuhl_heatingvalve (at least switch on for boost) and use boost in heating_function.boost_mcb
# - implement modules.heating_function
# * Central switch incl. central parameter storage for state (summer mode, away_mode - each switch activation disables the other switch)
class radiator_function(object):
BOOST_TEMPERATURE = 30
AWAY_REDUCTION = 5
SUMMER_TEMPERATURE = 5
def __init__(self, mqtt_client, topic_valve, topic_gui, default_temperature):
self.default_temperature = default_temperature
self.regular_temp_setpoint = self.default_temperature
self.__away_mode__ = False
self.__summer_mode__ = False
#
self.ct = task.periodic(1, self.cyclic_task)
#
@ -149,14 +151,13 @@ class radiator_function(object):
#
self.heating_valve.set_heating_setpoint(self.default_temperature)
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_TEMPERATURE, None, self.gui_heating.set_temperature_mcb)
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_HEATING_SETPOINT, None, self.gui_heating.set_setpoint_temperature_mcb)
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_HEATING_SETPOINT, None, self.store_regular_setpoint)
self.heating_valve.add_callback(devices.brennenstuhl_heatingvalve.KEY_HEATING_SETPOINT, None, self.get_radiator_setpoint)
#
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TEMP, None, self.heating_valve.set_heating_setpoint_mcb)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_BOOST, None, self.boost_mcb)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TO_DEFAULT, None, self.setpoint_to_default_mcb)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TEMP, None, self.cancel_boost)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TO_DEFAULT, None, self.cancel_boost)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TEMP, None, self.set_heating_setpoint)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_BOOST, None, self.boost)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SETPOINT_TO_DEFAULT, None, self.setpoint_to_default)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_AWAY, None, self.away_mode)
self.gui_heating.add_callback(devices.nodered_gui_radiator.KEY_SUMMER, None, self.summer_mode)
#
self.boost_timer = None
#
@ -177,19 +178,51 @@ class radiator_function(object):
self.boost_timer = None
self.gui_heating.set_timer('-')
def store_regular_setpoint(self, device, key, data):
if self.boost_timer is None:
self.regular_temp_setpoint = data
def away_mode(self, device, key, value):
self.__away_mode__ = value
self.gui_heating.set_away(value)
if value is True:
self.__summer_mode__ = False
self.gui_heating.set_summer(False)
self.cancel_boost()
self.heating_valve.set_heating_setpoint(self.default_temperature - self.AWAY_REDUCTION)
else:
self.heating_valve.set_heating_setpoint(self.default_temperature)
def boost_mcb(self, device, key, data):
def summer_mode(self, device, key, value):
self.__summer_mode__ = value
self.gui_heating.set_summer(value)
if value is True:
self.__away_mode__ = False
self.gui_heating.set_away(False)
self.cancel_boost()
self.heating_valve.set_heating_setpoint(self.SUMMER_TEMPERATURE)
else:
self.heating_valve.set_heating_setpoint(self.default_temperature)
def boost(self, device, key, data):
self.cancel_boost()
if self.boost_timer is None:
self.heating_valve.logger.info('Starting boost mode with setpoint %.1f°C.', self.regular_temp_setpoint + 5)
self.heating_valve.logger.info('Starting boost mode with setpoint %.1f°C.', self.BOOST_TEMPERATURE)
self.boost_timer = 15*60
self.heating_valve.set_heating_setpoint(self.regular_temp_setpoint + 5) # TODO: Change setpoint to "on" if possible
self.heating_valve.set_heating_setpoint(self.BOOST_TEMPERATURE)
else:
self.boost_timer += 15 * 60
if self.boost_timer > 60 * 60:
self.boost_timer = 60 * 60
def setpoint_to_default_mcb(self, device, key, data):
def setpoint_to_default(self, device, key, data):
self.heating_valve.set_heating_setpoint(self.default_temperature)
def set_heating_setpoint(self, device, key, data):
self.cancel_boost()
self.heating_valve.set_heating_setpoint(data)
def get_radiator_setpoint(self, device, key, data):
self.gui_heating.set_setpoint_temperature(data)
if self.__away_mode__:
self.away_mode(device, self.gui_heating.KEY_AWAY, True)
if self.__summer_mode__:
self.summer_mode(device, self.gui_heating.KEY_SUMMER, True)
if self.boost_timer is None and not self.__away_mode__ and not self.__summer_mode__:
self.regular_temp_setpoint = data