From 3c0a86e922b52fac6c970f3d2d381d787e5fe83f Mon Sep 17 00:00:00 2001 From: gytisgreitai Date: Tue, 30 Apr 2019 07:27:08 +0300 Subject: [PATCH] initial version --- .gitignore | 4 + Makefile | 6 + USBCAN.py | 150 ++++++++++ main.py | 52 ++++ mapping.py | 497 ++++++++++++++++++++++++++++++++++ openhab/comfoair-action.items | 2 + openhab/comfoair-pow.items | 11 + openhab/comfoair-pow.rules | 29 ++ openhab/comfoair.rules | 74 +++++ openhab/comofair.items | 44 +++ openhab/items.py | 34 +++ openhab/ventilationLevel.map | 4 + requirements.txt | 1 + 13 files changed, 908 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 USBCAN.py create mode 100644 main.py create mode 100644 mapping.py create mode 100644 openhab/comfoair-action.items create mode 100644 openhab/comfoair-pow.items create mode 100644 openhab/comfoair-pow.rules create mode 100644 openhab/comfoair.rules create mode 100644 openhab/comofair.items create mode 100644 openhab/items.py create mode 100644 openhab/ventilationLevel.map create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5430f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +.vscode +openhab/heatpump-pow.items +openhab/heatpump-pow.rules \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb03346 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +deploy: + scp *.py 192.168.3.4:/opt/zehnder-can-mqtt +ohitems: + cd openhab && python3 items.py > comofair.items + +.PHONY: deploy ohitems diff --git a/USBCAN.py b/USBCAN.py new file mode 100644 index 0000000..f63d623 --- /dev/null +++ b/USBCAN.py @@ -0,0 +1,150 @@ +import serial +from time import sleep +import logging +import sys + +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logger = logging.getLogger('comfoair-can') + +class CN1FAddr: + def __init__(self, SrcAddr, DstAddr, Address, MultiMsg, A8000, A10000, SeqNr): + self.SrcAddr = SrcAddr + self.DstAddr = DstAddr + self.Address = Address + self.MultiMsg = MultiMsg + self.A8000 = A8000 + self.A10000 = A10000 + self.SeqNr = SeqNr + + def id(self): + addr = 0x0 + addr |= self.SrcAddr << 0 + addr |= self.DstAddr << 6 + + addr |= self.Address <<12 + addr |= self.MultiMsg<<14 + addr |= self.A8000 <<15 + addr |= self.A10000 <<16 + addr |= self.SeqNr <<17 + addr |= 0x1F <<24 + + return addr + def hex(self): + return hex(self.id())[2:] + + def bytes(self): + return bytes.fromhex(self.hex()) + +class CANInterface: + + START_BYTE_1 = 0x55 + START_BYTE_2 = 0XAA + + COMFOAIR_ADDRESS = 1 + + send_sequence_nr = 0 + + def __init__(self, device, baudrate): + self.device = device + self.baudrate = baudrate + + def open(self): + self.sp = serial.Serial(self.device, self.baudrate) + self._send_magic_init_packet() + + def read(self, callback): + frame = bytearray() + while True: + if self.sp.in_waiting != 0: + new_byte = self._get_single_byte() + if new_byte == self.START_BYTE_1: + next_byte = self._get_single_byte() + if next_byte == self.START_BYTE_2: + id = frame[1:5] + data =frame[5:] + id_hex = str() + format(int.from_bytes(id, byteorder='little'), '#10X') + pdid = (int(id_hex, 16)>>14)&0x7ff + frame = bytearray() + callback(pdid, data) + else: + frame.append(new_byte) + frame.append(next_byte) + else: + frame.append(new_byte) + else: + sleep(1) + + def send(self, data): + num_bytes = len(data) + can_id = CN1FAddr(0x11, self.COMFOAIR_ADDRESS, 1, num_bytes>8, 0, 1, self.send_sequence_nr) + self.send_sequence_nr = (self.send_sequence_nr + 1)&0x3 + if len(data) > 8: + datagrams = int(len(data)/7) + if len(data) == datagrams*7: + datagrams -= 1 + for n in range(datagrams): + self._write_to_can(can_id.bytes(), [n]+data[n*7:n*7+7]) + n+=1 + restlen = len(data)-n*7 + self._write_to_can(can_id.bytes(), [n|0x80]+data[n*7:n*7+restlen]) + else: + self._write_to_can(can_id.bytes(), data) + + #----- private + def _get_single_byte(self): + return int.from_bytes(self.sp.read(size=1), byteorder='little') + + + def _write_to_can(self, id, data): + num_bytes=len(data) + send_buf = bytearray([0xAA,0xE0|num_bytes,]) + for byte in reversed(id): + send_buf.append(byte) + for byte in data: + send_buf.append(byte) + send_buf.append(0x55) + self.sp.write(send_buf) + + + def _send_magic_init_packet(self): + send_buf = bytearray() + send_buf.append(0xAA) + send_buf.append(0x55) + #Pack mystery byte + send_buf.append(0x12) + #Pack byte indicating CAN bus speed + send_buf.append(0x09) + #Pack frame type byte + #use extended + send_buf.append(0x02) + #Filter not supported + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + #Mask not supported + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + #Hardcode mode to Normal? Set to 0x01 to get loopback mode + send_buf.append(0x00) + #Send magic byte (may have to be 0x01?) + send_buf.append(0x01) + #Send more magic bytes + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + send_buf.append(0x00) + + #Calculate checksum + checksum = 0 + for idx in range(0,18): + checksum += int(send_buf[idx]) + checksum = checksum % 255 + + send_buf.append(checksum) + self.sp.write(send_buf) + logger.info('init config done') + + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..44ea36d --- /dev/null +++ b/main.py @@ -0,0 +1,52 @@ +import paho.mqtt.client as mqtt +import logging +import sys + +from USBCAN import CANInterface +import mapping +from time import sleep + +def on_mqtt_connect(client, userdata, flags, rc): + logger.info('ubscribing to comfoair/action') + client.subscribe('comfoair/action') + +def on_mqtt_message(client, userdata, msg): + action =str(msg.payload.decode('utf-8')) + logger.info('got mqtt message %s %s', msg.topic, action) + if action in mapping.commands: + command = mapping.commands[action] + logger.info('action ok, executing: %s', action) + try: + for i in range(3): + can.send(command) + sleep(2) + except Exception as e: + logger.error('failed in send %s', e) + else: + logger.error('action not found %s', action) + +def process_can_message(pdid, data): + if pdid in mapping.data: + pdid_config = mapping.data[pdid] + value = pdid_config.get('transformation')(data) + if pdid_config.get('ok'): + name = pdid_config.get('name') + mqtt_client.publish('comfoair/status/' + name, value) + else: + logger.info('not ok, not pushing %s %s', pdid, value) + elif pdid != 0: + logger.info('pid not found %s %s', pdid, data) + +logger = logging.getLogger('comfoair-main') +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) + +logger.info('starting up') +mqtt_client = mqtt.Client() +mqtt_client.connect('192.168.3.4', 1883, 60) +mqtt_client.on_connect = on_mqtt_connect +mqtt_client.on_message = on_mqtt_message +mqtt_client.loop_start() + +can = CANInterface('/dev/ttyUSB0', 2000000) +can.open() +can.read(process_can_message) diff --git a/mapping.py b/mapping.py new file mode 100644 index 0000000..72e3dc3 --- /dev/null +++ b/mapping.py @@ -0,0 +1,497 @@ +from struct import unpack + +def transform_temperature(value: list) -> float: + parts = bytes(value[0:2]) + word = unpack(' float: + parts = value[0:2] + word = unpack(' float: + word = 0 + for n in range(len(value)): + word += value[n]<<(n*8) + return word + + +# 8415 0601 00000000 100e0000 01 Set ventilation mode: supply only for 1 hour +# 8515 0601 Set ventilation mode: balance mode +# 8415 0301 00000000 ffffffff 00 Set temperature profile: normal +# 8415 0301 00000000 ffffffff 01 Set temperature profile: cool +# 8415 0301 00000000 ffffffff 02 Set temperature profile: warm +# 8415 0201 00000000 100e0000 01 Set bypass: activated for 1 hour +# 8415 0201 00000000 100e0000 02 Set bypass: deactivated for 1 hour +# 8515 0201 Set bypass: auto +# 031d 0104 00 Set sensor ventilation: temperature passive: off +# 031d 0104 01 Set sensor ventilation: temperature passive: auto only +# 031d 0104 02 Set sensor ventilation: temperature passive: on +# 031d 0106 00 Set sensor ventilation: humidity comfort: off +# 031d 0106 01 Set sensor ventilation: humidity comfort: auto only +# 031d 0106 02 Set sensor ventilation: humidity comfort: on +# 031d 0107 00 Set sensor ventilation: humidity protection: off +# 031d 0107 01 Set sensor ventilation: humidity protection: auto +# 031d 0107 02 Set sensor ventilation: humidity protection: on +commands = { + 'ventilation_level_0': [0x84, 0x15, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], + 'ventilation_level_1': [0x84, 0x15, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01], + 'ventilation_level_2': [0x84, 0x15, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02], + 'ventilation_level_3': [0x84, 0x15, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03], + 'boost_10_min': [0x84, 0x15, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], + 'boost_20_min': [0x84, 0x15, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x04, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], + 'boost_30_min': [0x84, 0x15, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], + 'boost_60_min': [0x84, 0x15, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0E, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00], + 'boost_end': [0x85, 0x15, 0x01, 0x06], + 'auto': [0x85, 0x15, 0x08, 0x01], + 'manual': [0x84, 0x15, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01], +} + +data = { + 16: { + "name": "z_unknown_NwoNode", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 17: { + "name": "z_unknown_NwoNode", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 18: { + "name": "z_unknown_NwoNode", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 65: { + "name": "ventilation_level", + "ok" : True, + "unit": "level", + "transformation": lambda x: int(x[0]) + }, + 66: { + "name": "bypass_state", + "ok" : True, + "unit": "0=auto,1=open,2=close", + "transformation": lambda x: float(x[0]) + }, + 81: { + "name": "timer_1", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 82: { + "name": "timer_2", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 83: { + "name": "timer_3", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 84: { + "name": "timer_4", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 85: { + "name": "timer_5", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 86: { + "name": "timer_6", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 87: { + "name": "timer_7", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 88: { + "name": "timer_8", + "ok" : True, + "unit": "s", + "transformation": transform_any + }, + 96: { + "name": "bypass ??? ValveMsg", + "ok" : False, + "unit": "unknown", + "transformation": transform_any + }, + 97: { + "name": "bypass_b_status", + "ok" : True, + "unit": "unknown", + "transformation": transform_air_volume + }, + 98: { + "name": "bypass_a_status", + "ok" : True, + "unit": "unknown", + "transformation": transform_air_volume + }, + 115: { + "name": "fan_exhaust_enabled", + "ok" : True, + "unit": "", + "transformation": transform_any + }, + 116: { + "name": "fan_supply_enabled", + "ok" : True, + "unit": "", + "transformation": transform_any + }, + 117: { + "name": "fan_exhaust_duty", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 118: { + "name": "fan_supply_duty", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 119: { + "name": "fan_exhaust_flow", + "ok" : True, + "unit": "m3", + "transformation": transform_air_volume + }, + 120: { + "name": "fan_supply_flow", + "ok" : True, + "unit": "m3", + "transformation": transform_air_volume + }, + 121: { + "name": "fan_exhaust_speed", + "ok" : True, + "unit": "rpm", + "transformation": transform_air_volume + }, + 122: { + "name": "fan_supply_speed", + "ok" : True, + "unit": "rpm", + "transformation": transform_air_volume + }, + 128: { + "name": "power_consumption_ventilation", + "ok" : True, + "unit": "W", + "transformation": lambda x: float(x[0]) + }, + 129: { + "name": "power_consumption_year_to_date", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 130: { + "name": "power_consumption_total_from_start", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 144: { + "name": "power_consumption_preheater_year_to_date", + "ok" : True, + "unit": "kWh", + "transformation": transform_any + }, + 145: { + "name": "power_consumption_preheater_from_start", + "ok" : True, + "unit": "kWh", + "transformation": transform_any + }, + 146: { + "name": "power_consumption_preheater_current", + "ok" : True, + "unit": "W", + "transformation": transform_any + }, + 192: { + "name": "days_until_next_filter_change", + "ok" : True, + "unit": "days", + "transformation": transform_air_volume + }, + 208: { + "name": "z_Unknown_TempHumConf", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 209: { + "name" : "rmot", + "ok" : True, + "unit":"°C", + "transformation":transform_temperature + }, + 210: { + "name": "z_Unknown_TempHumConf", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 211: { + "name": "z_Unknown_TempHumConf", + "ok" : False, + "unit": "", + "transformation": transform_any + }, + 212: { + "name": "target_temperature", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 213: { + "name": "power_avoided_heating_actual", + "ok" : True, + "unit": "W", + "transformation": transform_any + }, + 214: { + "name": "power_avoided_heating_year_to_date", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 215: { + "name": "power_avoided_heating_from_start", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 216: { + "name": "power_avoided_cooling_actual", + "ok" : True, + "unit": "W", + "transformation": transform_any + }, + 217: { + "name": "power_avoided_cooling_year_to_date", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 218: { + "name": "power_avoided_cooling_from_start", + "ok" : True, + "unit": "kWh", + "transformation": transform_air_volume + }, + 219: { + "name": "power_preheater_target", + "ok" : True, + "unit": "W", + "transformation": transform_any + }, + 220: { + "name": "air_supply_temperature_before_preheater", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 221: { + "name": "air_supply_temperature", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 222: { + "name": "z_Unknown_TempHumConf", + "unit": "", + "transformation": transform_any + }, + 224: { + "name": "z_Unknown_VentConf", + "unit": "", + "transformation": transform_any + }, + 225: { + "name": "z_Unknown_VentConf", + "unit": "", + "transformation": transform_any + }, + 226: { + "name": "z_Unknown_VentConf", + "unit": "", + "transformation": transform_any + }, + 227: { + "name": "bypass_open_percentage", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 228: { + "name": "frost_disbalance", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 229: { + "name": "z_Unknown_VentConf", + "unit": "", + "transformation": transform_any + }, + 230: { + "name": "z_Unknown_VentConf", + "unit": "", + "transformation": transform_any + }, + + 256: { + "name": "z_Unknown_NodeConf", + "unit": "unknown", + "transformation": transform_any + }, + 257: { + "name": "z_Unknown_NodeConf", + "unit": "unknown", + "transformation": transform_any + }, + + 273: { + "name": "temperature_unknown", + "unit": "°C", + "transformation": transform_temperature + }, + 274: { + "name": "air_extract_temperature", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 275: { + "name": "air_exhaust_temperature", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 276: { + "name": "air_outdoor_temperature_before_preheater", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 277: { + "name": "air_outdoor_temperature", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 278: { + "name": "air_supply_temperature_2", + "ok" : True, + "unit": "°C", + "transformation": transform_temperature + }, + 289: { + "name": "z_unknown_HumSens", + "unit": "", + "transformation": transform_any + }, + 290: { + "name": "air_extract_humidity", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 291: { + "name": "air_exhaust_humidity", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 292: { + "name": "air_outdoor_humidity_before_preheater", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 293: { + "name": "air_outdoor_humidity", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + 294: { + "name": "air_supply_humidity", + "ok" : True, + "unit": "%", + "transformation": lambda x: float(x[0]) + }, + + 305: { + "name": "pressure_exhaust", + "ok" : True, + "unit": "Pa", + "transformation": transform_any + }, + 306: { + "name": "pressure_supply", + "ok" : True, + "unit": "Pa", + "transformation": transform_any + }, + + 369: { + "name": "z_Unknown_AnalogInput", + "unit": "V?", + "transformation": transform_any + }, + 370: { + "name": "z_Unknown_AnalogInput", + "unit": "V?", + "transformation": transform_any + }, + 371: { + "name": "z_Unknown_AnalogInput", + "unit": "V?", + "transformation": transform_any + }, + 372: { + "name": "z_Unknown_AnalogInput", + "unit": "V?", + "transformation": transform_any + }, + 400: { + "name": "z_Unknown_PostHeater_ActualPower", + "unit": "W", + "transformation": transform_any + }, + 401: { + "name": "z_Unknown_PostHeater_ThisYear", + "unit": "kWh", + "transformation": transform_any + }, + 402: { + "name": "z_Unknown_PostHeater_Total", + "unit": "kWh", + "transformation": transform_any + }, +#00398041 unknown 0 0 0 0 0 0 0 0 +} diff --git a/openhab/comfoair-action.items b/openhab/comfoair-action.items new file mode 100644 index 0000000..3c8db84 --- /dev/null +++ b/openhab/comfoair-action.items @@ -0,0 +1,2 @@ +String ComfAir_Q350 "Comfair Q350" { mqtt=">[mosquitto:comfair/action:command:*:default]" } +Number ComfAir_Ventilation_Level_Knob "Ventilation Level [%d]" { mqtt="<[mosquitto:comfoair/status/ventilation_level:state:default]", mqtt=">[mosquitto:comfoair/action:command:*:MAP(ventilationLevel.map)]" } \ No newline at end of file diff --git a/openhab/comfoair-pow.items b/openhab/comfoair-pow.items new file mode 100644 index 0000000..fe74c09 --- /dev/null +++ b/openhab/comfoair-pow.items @@ -0,0 +1,11 @@ +Number ComfoAir_Energy_Meter_Current_W "Current ComfoAir power usage [%d W]" (gPersistEveryMin) { mqtt="<[mosquitto:pow/sonoff/tele/hrv1/SENSOR:state:JSONPATH($.ENERGY.Power)]" } +Number ComfoAir_Energy_Meter_Today_KWH "Today's ComfoAir power usage [%.3f KWh]" { mqtt="<[mosquitto:pow/sonoff/tele/hrv1/SENSOR:state:JSONPATH($.ENERGY.Today)]" } +Number ComfoAir_Energy_Meter_Yesterday_KWH "Yesterday's ComfoAir power usage [%.3f KWh]" { mqtt="<[mosquitto:pow/sonoff/tele/hrv1/SENSOR:state:JSONPATH($.ENERGY.Yesterday)]" } +Number ComfoAir_Energy_Meter_Total_KWH "Total ComfoAir power usage [%.3f KWh]" { mqtt="<[mosquitto:pow/sonoff/tele/hrv1/SENSOR:state:JSONPATH($.ENERGY.Total)]" } +Number ComfoAir_Energy_Meter_Period "ComfoAir power usage Since Last Period [%.3f Wh]" (gPersistEveryMinute){ mqtt="<[mosquitto:pow/sonoff/tele/hrv1/SENSOR:state:JSONPATH($.ENERGY.Period)]" } +Switch ComfoAir_Energy_Meter_Switch "ComfoAir MAINS Power" { mqtt=">[mosquitto:pow/sonoff/cmnd/hrv1/Energy_MeterER:command:*:default],<[mosquitto:pow/sonoff/cmnd/hrv1/Energy_MeterER:state:default]" } + +Number ComfoAir_Energy_Meter_LastDay "ComfoAir last day total power" (gPersistEveryChange) +Number ComfoAir_Energy_Meter_ThisMonth "ComfoAir last month total power" (gPersistEveryChange) +Number ComfoAir_Energy_Meter_ThisYear "ComfoAir last year total power" (gPersistEveryChange) +Number ComfoAir_Energy_Meter_Today "ComfoAir current day total power" \ No newline at end of file diff --git a/openhab/comfoair-pow.rules b/openhab/comfoair-pow.rules new file mode 100644 index 0000000..ad18256 --- /dev/null +++ b/openhab/comfoair-pow.rules @@ -0,0 +1,29 @@ +rule "Persist energy for given day" +when + Time cron "57 59 23 * * ? *" + +then + ComfoAir_Energy_Meter_LastDay.postUpdate(ComfoAir_Energy_Meter_Today.state as DecimalType) + ComfoAir_Energy_Meter_Today.postUpdate(0) +end + +rule "Persist energy for longer periods" +when + Time cron "0 0 0 * * ? *" + +then + ComfoAir_Energy_Meter_ThisMonth.postUpdate(ComfoAir_Energy_Meter_LastDay.sumSince(now.withDayOfMonth(1).withTimeAtStartOfDay())) + ComfoAir_Energy_Meter_ThisYear.postUpdate(ComfoAir_Energy_Meter_LastDay.sumSince(now.withMonthOfYear(1).withDayOfMonth(1).withTimeAtStartOfDay())) +end + +rule "Update ComfoAir Energy_Meter Totals for the day" +when + Item ComfoAir_Energy_Meter_Period received update +then + val period = ComfoAir_Energy_Meter_Period.state as Number + var Number forToday = 0.0 + if (ComfoAir_Energy_Meter_Today.state != NULL) { + forToday = ComfoAir_Energy_Meter_Today.state as Number + } + ComfoAir_Energy_Meter_Today.postUpdate(period+forToday) +end \ No newline at end of file diff --git a/openhab/comfoair.rules b/openhab/comfoair.rules new file mode 100644 index 0000000..cf3ba8d --- /dev/null +++ b/openhab/comfoair.rules @@ -0,0 +1,74 @@ +rule "Set venilation level 2 if bathroom hudmity > 80" +when + Item Sensors_Thermo_Bathroom_Humidity changed +then + if (Sensors_Thermo_Bathroom_Humidity.state < 80) { + logWarn("comfoair", "bathroom humidity: level not high enough", Sensors_Thermo_Bathroom_Humidity.state) + return + } + if (ComfoAir_Ventilation_Level.state > 1 ) { + logWarn("comfoair", "bathroom humidity: level higher than 2", ComfoAir_Ventilation_Level.state) + return + } + sendCommand(ComfAir_Q350, 'ventilation_level_2') + createTimer(now.plusMinutes(60)) [ + if (ComfoAir_Ventilation_Level.state == 2) { + logWarn("comfoair", "bathroom humidity: timer expired, resetting") + sendCommand(ComfAir_Q350, 'ventilation_level_1') + } + ] +end + +rule "Set venilation level 2 early in the morning on weekdays" +when + Time cron " 0 45 5 ? * MON,TUE,WED,THU,FRI *" +then + if (Paradox_Main_Status.state == ON ) { + return + } + if (ComfoAir_Ventilation_Level.state > 1 ) { + return + } + sendCommand(ComfAir_Q350, 'ventilation_level_2') + createTimer(now.plusMinutes(90)) [ + if (ComfoAir_Ventilation_Level.state == 2) { + sendCommand(ComfAir_Q350, 'ventilation_level_1') + } + ] +end + +rule "Set venilation level 2 early in the morning on weekends" +when + Time cron " 0 45 6 ? * SAT,SUN *" +then + if (Paradox_Main_Status.state == ON ) { + return + } + if (ComfoAir_Ventilation_Level.state > 1 ) { + return + } + sendCommand(ComfAir_Q350, 'ventilation_level_2') + createTimer(now.plusMinutes(90)) [ + if (ComfoAir_Ventilation_Level.state == 2) { + sendCommand(ComfAir_Q350, 'ventilation_level_1') + } + ] +end + +rule "Set to Away after 90 min when Alarm was turned on" +when + Item Paradox_Main_Status changed to ON +then + createTimer(now.plusMinutes(90)) [ + if (Paradox_Main_Status.state == ON) { + sendCommand(ComfAir_Q350, 'ventilation_level_0') + } + ] +end + +rule "Set to 1 speed when alarm was turned off" +when + Item Paradox_Main_Status changed to OFF +then + sendCommand(ComfAir_Q350, 'ventilation_level_1') +end diff --git a/openhab/comofair.items b/openhab/comofair.items new file mode 100644 index 0000000..32ad858 --- /dev/null +++ b/openhab/comofair.items @@ -0,0 +1,44 @@ +Number ComfoAir_Ventilation_Level "Ventilation Level [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/ventilation_level:state:default]" } +Number ComfoAir_Bypass_State "Bypass State [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/bypass_state:state:default]" } +Number ComfoAir_Bypass_B_Status "Bypass B Status [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/bypass_b_status:state:default]" } +Number ComfoAir_Bypass_A_Status "Bypass A Status [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/bypass_a_status:state:default]" } +Number ComfoAir_Fan_Exhaust_Enabled "Fan Exhaust Enabled [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_exhaust_enabled:state:default]" } +Number ComfoAir_Fan_Supply_Enabled "Fan Supply Enabled [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_supply_enabled:state:default]" } +Number ComfoAir_Fan_Exhaust_Duty "Fan Exhaust Duty [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_exhaust_duty:state:default]" } +Number ComfoAir_Fan_Supply_Duty "Fan Supply Duty [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_supply_duty:state:default]" } +Number ComfoAir_Fan_Exhaust_Flow "Fan Exhaust Flow [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_exhaust_flow:state:default]" } +Number ComfoAir_Fan_Supply_Flow "Fan Supply Flow [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_supply_flow:state:default]" } +Number ComfoAir_Fan_Exhaust_Speed "Fan Exhaust Speed [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_exhaust_speed:state:default]" } +Number ComfoAir_Fan_Supply_Speed "Fan Supply Speed [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/fan_supply_speed:state:default]" } +Number ComfoAir_Power_Consumption_Ventilation "Power Consumption Ventilation [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_ventilation:state:default]" } +Number ComfoAir_Power_Consumption_Year_To_Date "Power Consumption Year To Date [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_year_to_date:state:default]" } +Number ComfoAir_Power_Consumption_Total_From_Start "Power Consumption Total From Start [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_total_from_start:state:default]" } +Number ComfoAir_Power_Consumption_Preheater_Year_To_Date "Power Consumption Preheater Year To Date [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_preheater_year_to_date:state:default]" } +Number ComfoAir_Power_Consumption_Preheater_From_Start "Power Consumption Preheater From Start [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_preheater_from_start:state:default]" } +Number ComfoAir_Power_Consumption_Preheater_Current "Power Consumption Preheater Current [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_consumption_preheater_current:state:default]" } +Number ComfoAir_Days_Until_Next_Filter_Change "Days Until Next Filter Change [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/days_until_next_filter_change:state:default]" } +Number ComfoAir_Rmot "Rmot [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/rmot:state:default]" } +Number ComfoAir_Target_Temperature "Target Temperature [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/target_temperature:state:default]" } +Number ComfoAir_Power_Avoided_Heating_Actual "Power Avoided Heating Actual [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_heating_actual:state:default]" } +Number ComfoAir_Power_Avoided_Heating_Year_To_Date "Power Avoided Heating Year To Date [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_heating_year_to_date:state:default]" } +Number ComfoAir_Power_Avoided_Heating_From_Start "Power Avoided Heating From Start [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_heating_from_start:state:default]" } +Number ComfoAir_Power_Avoided_Cooling_Actual "Power Avoided Cooling Actual [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_cooling_actual:state:default]" } +Number ComfoAir_Power_Avoided_Cooling_Year_To_Date "Power Avoided Cooling Year To Date [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_cooling_year_to_date:state:default]" } +Number ComfoAir_Power_Avoided_Cooling_From_Start "Power Avoided Cooling From Start [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_avoided_cooling_from_start:state:default]" } +Number ComfoAir_Power_Preheater_Target "Power Preheater Target [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/power_preheater_target:state:default]" } +Number ComfoAir_Air_Supply_Temperature_Before_Preheater "Air Supply Temperature Before Preheater [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_supply_temperature_before_preheater:state:default]" } +Number ComfoAir_Air_Supply_Temperature "Air Supply Temperature [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_supply_temperature:state:default]" } +Number ComfoAir_Bypass_Open_Percentage "Bypass Open Percentage [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/bypass_open_percentage:state:default]" } +Number ComfoAir_Frost_Disbalance "Frost Disbalance [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/frost_disbalance:state:default]" } +Number ComfoAir_Air_Extract_Temperature "Air Extract Temperature [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_extract_temperature:state:default]" } +Number ComfoAir_Air_Exhaust_Temperature "Air Exhaust Temperature [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_exhaust_temperature:state:default]" } +Number ComfoAir_Air_Outdoor_Temperature_Before_Preheater "Air Outdoor Temperature Before Preheater [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_outdoor_temperature_before_preheater:state:default]" } +Number ComfoAir_Air_Outdoor_Temperature "Air Outdoor Temperature [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_outdoor_temperature:state:default]" } +Number ComfoAir_Air_Supply_Temperature_2 "Air Supply Temperature 2 [%.0f °C]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_supply_temperature_2:state:default]" } +Number ComfoAir_Air_Extract_Humidity "Air Extract Humidity [%d%%]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_extract_humidity:state:default]" } +Number ComfoAir_Air_Exhaust_Humidity "Air Exhaust Humidity [%d%%]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_exhaust_humidity:state:default]" } +Number ComfoAir_Air_Outdoor_Humidity_Before_Preheater "Air Outdoor Humidity Before Preheater [%d%%]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_outdoor_humidity_before_preheater:state:default]" } +Number ComfoAir_Air_Outdoor_Humidity "Air Outdoor Humidity [%d%%]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_outdoor_humidity:state:default]" } +Number ComfoAir_Air_Supply_Humidity "Air Supply Humidity [%d%%]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/air_supply_humidity:state:default]" } +Number ComfoAir_Pressure_Exhaust "Pressure Exhaust [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/pressure_exhaust:state:default]" } +Number ComfoAir_Pressure_Supply "Pressure Supply [%d]" (gPersistEveryMin) { mqtt="<[mosquitto:comfoair/status/pressure_supply:state:default]" } diff --git a/openhab/items.py b/openhab/items.py new file mode 100644 index 0000000..db3aed3 --- /dev/null +++ b/openhab/items.py @@ -0,0 +1,34 @@ +import sys +sys.path.append("..") + +from mapping import data + + +def get_friendly_name(name): + parts = name.split('_') + for i in range(len(parts)): + parts[i] = parts[i].capitalize() + return ' '.join(parts) + +def get_id(name): + return '_'.join(get_friendly_name(name).split(' ')) + +def get_format(name, unit): + if 'humidity' in name: + return '%d%%' + if 'temperature' in name: + return '%.0f °C' + return '%d' + +for key in data: + val = data[key] + name = val.get('name') + if not val.get('ok'): + continue + if 'timer' in name: + continue + id = get_id(name) + friendly_name = get_friendly_name(name) + format = get_format(name, val.get('unit')) + item = 'Number ComfoAir_{} "{} [{}]" (gPersistEveryMin) {{ mqtt="<[mosquitto:comfoair/status/{}:state:default]" }}'.format(id, friendly_name, format, name) + print(item) \ No newline at end of file diff --git a/openhab/ventilationLevel.map b/openhab/ventilationLevel.map new file mode 100644 index 0000000..62caa22 --- /dev/null +++ b/openhab/ventilationLevel.map @@ -0,0 +1,4 @@ +0=ventilation_level_0 +1=ventilation_level_1 +2=ventilation_level_2 +3=ventilation_level_3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d173f65 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +paho-mqtt \ No newline at end of file