import time import json import datetime try: import inverterd except: pass from typing import Optional from .._module import MqttModule from .._node import MqttNode from .._payload import MqttPayload, bit_field try: from homekit.database import InverterDatabase except: pass _mult_10 = lambda n: int(n*10) _div_10 = lambda n: n/10 MODULE_NAME = 'MqttInverterModule' STATUS_TOPIC = 'status' GENERATION_TOPIC = 'generation' class MqttInverterStatusPayload(MqttPayload): # 46 bytes FORMAT = 'IHHHHHHBHHHHHBHHHHHHHH' PACKER = { 'grid_voltage': _mult_10, 'grid_freq': _mult_10, 'ac_output_voltage': _mult_10, 'ac_output_freq': _mult_10, 'battery_voltage': _mult_10, 'battery_voltage_scc': _mult_10, 'battery_voltage_scc2': _mult_10, 'pv1_input_voltage': _mult_10, 'pv2_input_voltage': _mult_10 } UNPACKER = { 'grid_voltage': _div_10, 'grid_freq': _div_10, 'ac_output_voltage': _div_10, 'ac_output_freq': _div_10, 'battery_voltage': _div_10, 'battery_voltage_scc': _div_10, 'battery_voltage_scc2': _div_10, 'pv1_input_voltage': _div_10, 'pv2_input_voltage': _div_10 } time: int grid_voltage: float grid_freq: float ac_output_voltage: float ac_output_freq: float ac_output_apparent_power: int ac_output_active_power: int output_load_percent: int battery_voltage: float battery_voltage_scc: float battery_voltage_scc2: float battery_discharge_current: int battery_charge_current: int battery_capacity: int inverter_heat_sink_temp: int mppt1_charger_temp: int mppt2_charger_temp: int pv1_input_power: int pv2_input_power: int pv1_input_voltage: float pv2_input_voltage: float # H mppt1_charger_status: bit_field(0, 16, 2) mppt2_charger_status: bit_field(0, 16, 2) battery_power_direction: bit_field(0, 16, 2) dc_ac_power_direction: bit_field(0, 16, 2) line_power_direction: bit_field(0, 16, 2) load_connected: bit_field(0, 16, 1) class MqttInverterGenerationPayload(MqttPayload): # 8 bytes FORMAT = 'II' time: int wh: int class MqttInverterModule(MqttModule): _status_poll_freq: int _generation_poll_freq: int _inverter: Optional[inverterd.Client] _database: Optional[InverterDatabase] _gen_prev: float def __init__(self, status_poll_freq=0, generation_poll_freq=0): super().__init__(tick_interval=status_poll_freq) self._status_poll_freq = status_poll_freq self._generation_poll_freq = generation_poll_freq # this defines whether this is a publisher or a subscriber if status_poll_freq > 0: self._inverter = inverterd.Client() self._inverter.connect() self._inverter.format(inverterd.Format.SIMPLE_JSON) self._database = None else: self._inverter = None self._database = InverterDatabase() self._gen_prev = 0 def on_connect(self, mqtt: MqttNode): super().on_connect(mqtt) if not self._inverter: mqtt.subscribe_module(STATUS_TOPIC, self) mqtt.subscribe_module(GENERATION_TOPIC, self) def tick(self): if not self._inverter: return # read status now = time.time() try: raw = self._inverter.exec('get-status') except inverterd.InverterError as e: self._logger.error(f'inverter error: {str(e)}') # TODO send to server return data = json.loads(raw)['data'] status = MqttInverterStatusPayload(time=round(now), **data) self._mqtt_node_ref.publish(STATUS_TOPIC, status.pack()) # read today's generation stat now = time.time() if self._gen_prev == 0 or now - self._gen_prev >= self._generation_poll_freq: self._gen_prev = now today = datetime.date.today() try: raw = self._inverter.exec('get-day-generated', (today.year, today.month, today.day)) except inverterd.InverterError as e: self._logger.error(f'inverter error: {str(e)}') # TODO send to server return data = json.loads(raw)['data'] gen = MqttInverterGenerationPayload(time=round(now), wh=data['wh']) self._mqtt_node_ref.publish(GENERATION_TOPIC, gen.pack()) def handle_payload(self, mqtt: MqttNode, topic: str, payload: bytes) -> Optional[MqttPayload]: home_id = 1 # legacy compat if topic == STATUS_TOPIC: s = MqttInverterStatusPayload.unpack(payload) self._database.add_status(home_id=home_id, client_time=s.time, grid_voltage=int(s.grid_voltage*10), grid_freq=int(s.grid_freq * 10), ac_output_voltage=int(s.ac_output_voltage * 10), ac_output_freq=int(s.ac_output_freq * 10), ac_output_apparent_power=s.ac_output_apparent_power, ac_output_active_power=s.ac_output_active_power, output_load_percent=s.output_load_percent, battery_voltage=int(s.battery_voltage * 10), battery_voltage_scc=int(s.battery_voltage_scc * 10), battery_voltage_scc2=int(s.battery_voltage_scc2 * 10), battery_discharge_current=s.battery_discharge_current, battery_charge_current=s.battery_charge_current, battery_capacity=s.battery_capacity, inverter_heat_sink_temp=s.inverter_heat_sink_temp, mppt1_charger_temp=s.mppt1_charger_temp, mppt2_charger_temp=s.mppt2_charger_temp, pv1_input_power=s.pv1_input_power, pv2_input_power=s.pv2_input_power, pv1_input_voltage=int(s.pv1_input_voltage * 10), pv2_input_voltage=int(s.pv2_input_voltage * 10), mppt1_charger_status=s.mppt1_charger_status, mppt2_charger_status=s.mppt2_charger_status, battery_power_direction=s.battery_power_direction, dc_ac_power_direction=s.dc_ac_power_direction, line_power_direction=s.line_power_direction, load_connected=s.load_connected) return s elif topic == GENERATION_TOPIC: gen = MqttInverterGenerationPayload.unpack(payload) self._database.add_generation(home_id, gen.time, gen.wh) return gen