mirror of
https://github.com/lumapu/ahoy.git
synced 2025-07-15 15:35:14 +02:00
RPi:print HardwareInfoResponse on MQTT channel
print HardwareInfoResponse on MQTT channel check: HardwareInfoResponse does not print on VZ
This commit is contained in:
parent
32366bad59
commit
67ed21ae2a
2 changed files with 82 additions and 100 deletions
|
@ -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,71 +170,35 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
||||||
dtu_ser=dtu_ser,
|
dtu_ser=dtu_ser,
|
||||||
strings=inverter_strings
|
strings=inverter_strings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# get decoder object
|
||||||
result = decoder.decode()
|
result = decoder.decode()
|
||||||
|
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||||
|
logging.debug(f'{c_datetime} Decoded: {result.__dict__()}')
|
||||||
|
|
||||||
|
# check decoder object for output
|
||||||
if isinstance(result, hoymiles.decoders.StatusResponse):
|
if isinstance(result, hoymiles.decoders.StatusResponse):
|
||||||
|
|
||||||
data = result.__dict__()
|
data = result.__dict__()
|
||||||
|
|
||||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
|
||||||
logging.debug(f'{c_datetime} Decoded: {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:
|
||||||
influx_client.store_status(result)
|
influx_client.store_status(result)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -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,45 +185,56 @@ 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)}'
|
||||||
|
|
||||||
# Global Head
|
if isinstance(response, StatusResponse):
|
||||||
if data['time'] is not None:
|
|
||||||
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
|
|
||||||
|
|
||||||
# AC Data
|
# Global Head
|
||||||
phase_id = 0
|
if data['time'] is not None:
|
||||||
for phase in data['phases']:
|
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
|
||||||
self.client.publish(f'{topic}/emeter/{phase_id}/power', phase['power'])
|
|
||||||
self.client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'])
|
|
||||||
self.client.publish(f'{topic}/emeter/{phase_id}/current', phase['current'])
|
|
||||||
self.client.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'])
|
|
||||||
phase_id = phase_id + 1
|
|
||||||
|
|
||||||
# DC Data
|
# AC Data
|
||||||
string_id = 0
|
phase_id = 0
|
||||||
for string in data['strings']:
|
for phase in data['phases']:
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'])
|
self.client.publish(f'{topic}/emeter/{phase_id}/power', phase['power'])
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'])
|
self.client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'])
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
|
self.client.publish(f'{topic}/emeter/{phase_id}/current', phase['current'])
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'])
|
self.client.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'])
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000)
|
phase_id = phase_id + 1
|
||||||
if 'irradiation' in string:
|
|
||||||
self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation'])
|
|
||||||
string_id = string_id + 1
|
|
||||||
|
|
||||||
# Global
|
# DC Data
|
||||||
if data['powerfactor'] is not None:
|
string_id = 0
|
||||||
self.client.publish(f'{topic}/pf', data['powerfactor'])
|
for string in data['strings']:
|
||||||
self.client.publish(f'{topic}/frequency', data['frequency'])
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'])
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'])
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'])
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000)
|
||||||
|
if 'irradiation' in string:
|
||||||
|
self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation'])
|
||||||
|
string_id = string_id + 1
|
||||||
|
|
||||||
self.client.publish(f'{topic}/Temp', data['temperature'])
|
# Global
|
||||||
if data['energy_total'] is not None:
|
if data['powerfactor'] is not None:
|
||||||
self.client.publish(f'{topic}/total', data['energy_total']/1000)
|
self.client.publish(f'{topic}/pf', data['powerfactor'])
|
||||||
|
self.client.publish(f'{topic}/frequency', data['frequency'])
|
||||||
|
|
||||||
|
self.client.publish(f'{topic}/Temp', data['temperature'])
|
||||||
|
if data['energy_total'] is not None:
|
||||||
|
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
|
||||||
|
@ -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…
Add table
Add a link
Reference in a new issue