2023-06-10 23:02:34 +03:00

196 lines
7.1 KiB
Python

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