Browse Source

RPi:print HardwareInfoResponse on MQTT channel

print HardwareInfoResponse on MQTT channel
check: HardwareInfoResponse does not print on VZ
pull/603/head
Knuti_in_Päse 2 years ago
parent
commit
67ed21ae2a
  1. 92
      tools/rpi/hoymiles/__main__.py
  2. 24
      tools/rpi/hoymiles/outputs.py

92
tools/rpi/hoymiles/__main__.py

@ -81,8 +81,8 @@ def main_loop(ahoy_config):
sunset = SunsetHandler(ahoy_config.get('sunset')) sunset = SunsetHandler(ahoy_config.get('sunset'))
dtu_ser = ahoy_config.get('dtu', {}).get('serial') dtu_ser = ahoy_config.get('dtu', {}).get('serial')
loop_interval = ahoy_config.get('interval', 1) loop_interval = ahoy_config.get('interval', 1)
try: try:
do_init = True do_init = True
while True: while True:
@ -92,7 +92,7 @@ def main_loop(ahoy_config):
for inverter in inverters: for inverter in inverters:
if hoymiles.HOYMILES_DEBUG_LOGGING: if hoymiles.HOYMILES_DEBUG_LOGGING:
logging.debug(f'Poll inverter {inverter["serial"]}') logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
poll_inverter(inverter, dtu_ser, do_init, 3) poll_inverter(inverter, dtu_ser, do_init, 3)
do_init = False do_init = False
@ -161,6 +161,8 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
c_datetime = datetime.now() c_datetime = datetime.now()
if hoymiles.HOYMILES_DEBUG_LOGGING: if hoymiles.HOYMILES_DEBUG_LOGGING:
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response)) logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
# prepare decoder object
decoder = hoymiles.ResponseDecoder(response, decoder = hoymiles.ResponseDecoder(response,
request=com.request, request=com.request,
inverter_ser=inverter_ser, inverter_ser=inverter_ser,
@ -168,20 +170,22 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
dtu_ser=dtu_ser, dtu_ser=dtu_ser,
strings=inverter_strings strings=inverter_strings
) )
result = decoder.decode()
if isinstance(result, hoymiles.decoders.StatusResponse):
data = result.__dict__()
# get decoder object
result = decoder.decode()
if hoymiles.HOYMILES_DEBUG_LOGGING: if hoymiles.HOYMILES_DEBUG_LOGGING:
logging.debug(f'{c_datetime} Decoded: {result.__dict__()}') logging.debug(f'{c_datetime} Decoded: {result.__dict__()}')
# check decoder object for output
if isinstance(result, hoymiles.decoders.StatusResponse):
data = result.__dict__()
if 'event_count' in data: if 'event_count' in data:
if event_message_index[inv_str] < data['event_count']: if event_message_index[inv_str] < data['event_count']:
event_message_index[inv_str] = data['event_count'] event_message_index[inv_str] = data['event_count']
command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.AlarmData, alarm_id=event_message_index[inv_str])) command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.AlarmData, alarm_id=event_message_index[inv_str]))
if mqtt_client: if mqtt_client:
# mqtt_send_status(mqtt_client, inverter_ser, data, topic=inverter.get('mqtt', {}).get('topic', None))
mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None)) mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None))
if influx_client: if influx_client:
@ -190,49 +194,11 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
if volkszaehler_client: if volkszaehler_client:
volkszaehler_client.store_status(result) volkszaehler_client.store_status(result)
def mqtt_send_status(broker, inverter_ser, data, topic=None): # check decoder object for output
""" if isinstance(result, hoymiles.decoders.HardwareInfoResponse):
Publish StatusResponse object if mqtt_client:
mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None))
:param paho.mqtt.client.Client broker: mqtt-client instance
:param str inverter_ser: inverter serial
:param hoymiles.StatusResponse data: decoded inverter StatusResponse
:param topic: custom mqtt topic prefix (default: hoymiles/{inverter_ser})
:type topic: str
"""
if not topic:
topic = f'hoymiles/{inverter_ser}'
# Global Head
if data['time'] is not None:
broker.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
# AC Data
phase_id = 0
for phase in data['phases']:
broker.publish(f'{topic}/emeter/{phase_id}/power', phase['power'])
broker.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'])
broker.publish(f'{topic}/emeter/{phase_id}/current', phase['current'])
broker.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'])
phase_id = phase_id + 1
# DC Data
string_id = 0
for string in data['strings']:
broker.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'])
broker.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'])
broker.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
broker.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'])
broker.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000)
string_id = string_id + 1
# Global
if data['powerfactor'] is not None:
broker.publish(f'{topic}/pf', data['powerfactor'])
broker.publish(f'{topic}/frequency', data['frequency'])
broker.publish(f'{topic}/temperature', data['temperature'])
if data['energy_total'] is not None:
broker.publish(f'{topic}/total', data['energy_total']/1000)
def mqtt_on_command(client, userdata, message): def mqtt_on_command(client, userdata, message):
""" """
@ -321,29 +287,27 @@ if __name__ == '__main__':
logging.error('Failed to load config file {global_config.config_file}: {e_yaml}') logging.error('Failed to load config file {global_config.config_file}: {e_yaml}')
sys.exit(1) sys.exit(1)
# read AHOY configuration file and prepare logging
ahoy_config = dict(cfg.get('ahoy', {})) ahoy_config = dict(cfg.get('ahoy', {}))
init_logging(ahoy_config) init_logging(ahoy_config)
# Prepare for multiple transceivers, makes them configurable (currently
# only one supported)
for radio_config in ahoy_config.get('nrf', [{}]):
hmradio = hoymiles.HoymilesNRF(**radio_config)
event_message_index = {}
command_queue = {}
mqtt_command_topic_subs = []
if global_config.log_transactions: if global_config.log_transactions:
hoymiles.HOYMILES_TRANSACTION_LOGGING=True hoymiles.HOYMILES_TRANSACTION_LOGGING=True
if global_config.verbose: if global_config.verbose:
hoymiles.HOYMILES_DEBUG_LOGGING=True hoymiles.HOYMILES_DEBUG_LOGGING=True
# Prepare for multiple transceivers, makes them configurable
for radio_config in ahoy_config.get('nrf', [{}]):
hmradio = hoymiles.HoymilesNRF(**radio_config)
# create MQTT - client object
mqtt_client = None mqtt_client = None
mqtt_config = ahoy_config.get('mqtt', {}) mqtt_config = ahoy_config.get('mqtt', {})
if mqtt_config and not mqtt_config.get('disabled', False): if mqtt_config and not mqtt_config.get('disabled', False):
from .outputs import MqttOutputPlugin from .outputs import MqttOutputPlugin
mqtt_client = MqttOutputPlugin(mqtt_config) mqtt_client = MqttOutputPlugin(mqtt_config)
# create INFLUX - client object
influx_client = None influx_client = None
influx_config = ahoy_config.get('influxdb', {}) influx_config = ahoy_config.get('influxdb', {})
if influx_config and not influx_config.get('disabled', False): if influx_config and not influx_config.get('disabled', False):
@ -355,23 +319,24 @@ if __name__ == '__main__':
bucket=influx_config.get('bucket', None), bucket=influx_config.get('bucket', None),
measurement=influx_config.get('measurement', 'hoymiles')) measurement=influx_config.get('measurement', 'hoymiles'))
# create VOLKSZAEHLER - client object
volkszaehler_client = None volkszaehler_client = None
volkszaehler_config = ahoy_config.get('volkszaehler', {}) volkszaehler_config = ahoy_config.get('volkszaehler', {})
if volkszaehler_config and not volkszaehler_config.get('disabled', False): if volkszaehler_config and not volkszaehler_config.get('disabled', False):
from .outputs import VolkszaehlerOutputPlugin from .outputs import VolkszaehlerOutputPlugin
volkszaehler_client = VolkszaehlerOutputPlugin( volkszaehler_client = VolkszaehlerOutputPlugin(volkszaehler_config)
volkszaehler_config)
event_message_index = {}
command_queue = {}
mqtt_command_topic_subs = []
g_inverters = [g_inverter.get('serial') for g_inverter in ahoy_config.get('inverters', [])]
for g_inverter in ahoy_config.get('inverters', []): for g_inverter in ahoy_config.get('inverters', []):
g_inverter_ser = g_inverter.get('serial') g_inverter_ser = g_inverter.get('serial')
inv_str = str(g_inverter_ser) inv_str = str(g_inverter_ser)
command_queue[inv_str] = [] command_queue[inv_str] = []
event_message_index[inv_str] = 0 event_message_index[inv_str] = 0
#
# Enables and subscribe inverter to mqtt /command-Topic # Enables and subscribe inverter to mqtt /command-Topic
#
if mqtt_client and g_inverter.get('mqtt', {}).get('send_raw_enabled', False): if mqtt_client and g_inverter.get('mqtt', {}).get('send_raw_enabled', False):
topic_item = ( topic_item = (
str(g_inverter_ser), str(g_inverter_ser),
@ -380,5 +345,6 @@ if __name__ == '__main__':
mqtt_client.subscribe(topic_item[1]) mqtt_client.subscribe(topic_item[1])
mqtt_command_topic_subs.append(topic_item) mqtt_command_topic_subs.append(topic_item)
logging.info(f'Starting main_loop with inverter(s) {g_inverters}') # start main-loop
main_loop(ahoy_config) main_loop(ahoy_config)

24
tools/rpi/hoymiles/outputs.py

@ -8,7 +8,7 @@ Hoymiles output plugin library
import socket import socket
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from hoymiles.decoders import StatusResponse from hoymiles.decoders import StatusResponse, HardwareInfoResponse
try: try:
from influxdb_client import InfluxDBClient from influxdb_client import InfluxDBClient
@ -185,12 +185,11 @@ class MqttOutputPlugin(OutputPluginFactory):
:raises ValueError: when response is not instance of StatusResponse :raises ValueError: when response is not instance of StatusResponse
""" """
if not isinstance(response, StatusResponse):
raise ValueError('Data needs to be instance of StatusResponse')
data = response.__dict__() data = response.__dict__()
topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}' topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}'
if isinstance(response, StatusResponse):
# Global Head # Global Head
if data['time'] is not None: if data['time'] is not None:
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S")) self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
@ -225,6 +224,18 @@ class MqttOutputPlugin(OutputPluginFactory):
if data['energy_total'] is not None: if data['energy_total'] is not None:
self.client.publish(f'{topic}/total', data['energy_total']/1000) self.client.publish(f'{topic}/total', data['energy_total']/1000)
elif isinstance(response, HardwareInfoResponse):
self.client.publish(f'{topic}/Firmware/Version',\
f'{data["FW_ver_maj"]}.{data["FW_ver_min"]}.{data["FW_ver_pat"]}')
self.client.publish(f'{topic}/Firmware/Build_at',\
f'{data["FW_build_dd"]}/{data["FW_build_mm"]}/{data["FW_build_yy"]}T{data["FW_build_HH"]}:{data["FW_build_MM"]}')
self.client.publish(f'{topic}/Firmware/HWPartId', f'{data["FW_HW_ID"]}')
else:
raise ValueError('Data needs to be instance of StatusResponse or a instance of HardwareInfoResponse')
try: try:
import requests import requests
import time import time
@ -237,6 +248,7 @@ class VzInverterOutput:
self.serial = config.get('serial') self.serial = config.get('serial')
self.baseurl = config.get('url', 'http://localhost/middleware/') self.baseurl = config.get('url', 'http://localhost/middleware/')
self.channels = dict() self.channels = dict()
for channel in config.get('channels', []): for channel in config.get('channels', []):
uid = channel.get('uid') uid = channel.get('uid')
ctype = channel.get('type') ctype = channel.get('type')
@ -286,6 +298,7 @@ class VzInverterOutput:
if data['energy_total'] is not None: if data['energy_total'] is not None:
self.try_publish(ts, f'total', data['energy_total']) self.try_publish(ts, f'total', data['energy_total'])
def try_publish(self, ts, ctype, value): def try_publish(self, ts, ctype, value):
if not ctype in self.channels: if not ctype in self.channels:
return return
@ -307,6 +320,7 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
self.session = requests.Session() self.session = requests.Session()
self.inverters = dict() self.inverters = dict()
for inverterconfig in config.get('inverters', []): for inverterconfig in config.get('inverters', []):
serial = inverterconfig.get('serial') serial = inverterconfig.get('serial')
output = VzInverterOutput(inverterconfig, self.session) output = VzInverterOutput(inverterconfig, self.session)
@ -320,6 +334,8 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory):
:raises ValueError: when response is not instance of StatusResponse :raises ValueError: when response is not instance of StatusResponse
""" """
# check decoder object for output
if not isinstance(response, StatusResponse): if not isinstance(response, StatusResponse):
raise ValueError('Data needs to be instance of StatusResponse') raise ValueError('Data needs to be instance of StatusResponse')

Loading…
Cancel
Save