From d996c2c10ba5b71eb4f7685ffe2509ff5759da17 Mon Sep 17 00:00:00 2001 From: Knuti_in_Paese Date: Wed, 1 Feb 2023 21:42:51 +0100 Subject: [PATCH 1/6] RPI:using pyRF24 on Debian 11 bullseye environment known RF24 lib can not installed on Debian 11 bullseye 64 bit operating system now, system try to import RF24 nor pyrf24 --- tools/rpi/README.md | 10 ++++++++++ tools/rpi/ahoy.yml.example | 2 +- tools/rpi/hoymiles/__init__.py | 14 +++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/tools/rpi/README.md b/tools/rpi/README.md index bc8b23c0..75b5c1e2 100644 --- a/tools/rpi/README.md +++ b/tools/rpi/README.md @@ -78,6 +78,16 @@ cd examples_linux/ python3 getting_started.py # to test and see whether RF24 class can be loaded as module in python correctly ``` + +``` for bullseye - Debian 11 on 64 bit operating system +[ $(lscpu | grep Architecture | awk '{print $2}') != "aarch64" ]] && echo "Not a 64 bit architecture for this step!" + +git clone --recurse-submodules https://github.com/nRF24/pyRF24.git +cd pyRF24 +python -m pip install . -v # this step takes about 5 minutes! +``` + + If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully. Required python modules diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example index fb033f80..928374de 100644 --- a/tools/rpi/ahoy.yml.example +++ b/tools/rpi/ahoy.yml.example @@ -31,7 +31,7 @@ ahoy: QoS: 0 Retain: True last_will: - topic: Appelweg_PV/114181807700 # defaults to 'hoymiles/{serial}' + topic: hoymiles/114172220003 # defaults to 'hoymiles/{serial}' payload: "LAST-WILL-MESSAGE: Please check my HOST and Process!" # Influx2 output diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py index 74ec9fd8..eeb7e7de 100644 --- a/tools/rpi/hoymiles/__init__.py +++ b/tools/rpi/hoymiles/__init__.py @@ -11,9 +11,21 @@ import re from datetime import datetime import logging import crcmod -from RF24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 from .decoders import * +try: + # OSI Layer 2 driver for nRF24L01 on Arduino & Raspberry Pi/Linux Devices + # https://github.com/nRF24/RF24.git + from RF24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 +except ModuleNotFoundError: + try: + # Repo for pyRF24 package + # https://github.com/nRF24/pyRF24.git + from pyrf24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 + except ModuleNotFoundError: + print("Module for RF24 not found - exit") + exit() + f_crc_m = crcmod.predefined.mkPredefinedCrcFun('modbus') f_crc8 = crcmod.mkCrcFun(0x101, initCrc=0, xorOut=0) From 0d552e3007976689463aa5cbed86822736c93cd3 Mon Sep 17 00:00:00 2001 From: Knuti_in_Paese Date: Thu, 2 Feb 2023 14:21:30 +0100 Subject: [PATCH 2/6] RPI:error handling while getting corruppted data extended error handling while getting corruppted data on 64 bit operating system (bullseye) lots of currupted data are reseived on Debian 11 OS. So we have to check the data length before using strict.unpack --- tools/rpi/README.md | 13 +++++++------ tools/rpi/hoymiles/__init__.py | 8 ++++---- tools/rpi/hoymiles/__main__.py | 3 ++- tools/rpi/hoymiles/decoders/__init__.py | 18 +++++++++++++----- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/tools/rpi/README.md b/tools/rpi/README.md index 75b5c1e2..36b08f79 100644 --- a/tools/rpi/README.md +++ b/tools/rpi/README.md @@ -78,8 +78,12 @@ cd examples_linux/ python3 getting_started.py # to test and see whether RF24 class can be loaded as module in python correctly ``` +If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully. -``` for bullseye - Debian 11 on 64 bit operating system + +for Debian 11 (bullseye) 64 bit operating system +------------------------------------------------- +```code [ $(lscpu | grep Architecture | awk '{print $2}') != "aarch64" ]] && echo "Not a 64 bit architecture for this step!" git clone --recurse-submodules https://github.com/nRF24/pyRF24.git @@ -87,15 +91,12 @@ cd pyRF24 python -m pip install . -v # this step takes about 5 minutes! ``` - -If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully. - Required python modules ----------------------- Some modules are not installed by default on a RaspberryPi, therefore add them manually: -``` +```code pip install crcmod pyyaml paho-mqtt SunTimes ``` @@ -122,7 +123,7 @@ Python parameters The application describes itself -``` +```code python3 -m hoymiles --help usage: hoymiles [-h] -c [CONFIG_FILE] [--log-transactions] [--verbose] diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py index eeb7e7de..a754ff6d 100644 --- a/tools/rpi/hoymiles/__init__.py +++ b/tools/rpi/hoymiles/__init__.py @@ -170,15 +170,15 @@ class ResponseDecoder(ResponseDecoderFactory): model = self.inverter_model command = self.request_command - c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f") - logging.info(f'{c_datetime} model_decoder: {model}Decode{command.upper()}') + if HOYMILES_DEBUG_LOGGING: + c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f") + logging.info(f'{c_datetime} model_decoder: {model}Decode{command.upper()}') model_decoders = __import__('hoymiles.decoders') if hasattr(model_decoders, f'{model}Decode{command.upper()}'): device = getattr(model_decoders, f'{model}Decode{command.upper()}') else: - if HOYMILES_DEBUG_LOGGING: - device = getattr(model_decoders, 'DebugDecodeAny') + device = getattr(model_decoders, 'DebugDecodeAny') return device(self.response, time_rx=self.time_rx, diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index 97773ed1..35943eb4 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -179,8 +179,8 @@ def poll_inverter(inverter, dtu_ser, do_init, retries): # Handle the response data if any if response: - c_datetime = datetime.now() if hoymiles.HOYMILES_DEBUG_LOGGING: + c_datetime = datetime.now() logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response)) # prepare decoder object @@ -195,6 +195,7 @@ def poll_inverter(inverter, dtu_ser, do_init, retries): # get decoder object result = decoder.decode() if hoymiles.HOYMILES_DEBUG_LOGGING: + c_datetime = datetime.now() logging.info(f'{c_datetime} Decoded: {result.__dict__()}') # check decoder object for output diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index b46dbe48..e27b502d 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -110,6 +110,9 @@ class StatusResponse(Response): :rtype: tuple """ size = struct.calcsize(fmt) + if (len(self.response) < base+size): + logging.error(f'base: {base} size: {size} len: {len(self.response)} fmt: {fmt} rep: {self.response}') + return [0] return struct.unpack(fmt, self.response[base:base+size]) @property @@ -331,10 +334,12 @@ class EventsResponse(UnknownResponse): logging.debug(' '.join([f'{byte:02x}' for byte in chunk]) + ': ') - opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6]) - a_text = self.alarm_codes.get(a_code, 'N/A') - - logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}') + if (len(chunk[0:6]) == 6): + opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6]) + a_text = self.alarm_codes.get(a_code, 'N/A') + logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}') + else: + logging.error(f'length of chunk must be greater or equal 6 bytes: {chunk}') dbg = '' for fmt in ['BBHHHHH']: @@ -362,7 +367,10 @@ class HardwareInfoResponse(UnknownResponse): """ Base values, availabe in each __dict__ call """ responce_info = self.response - logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}') + if (len(responce_info) >= 16): + logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}') + else: + logging.error(f'wrong length of HardwareInfoResponse: {responce_info}') fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10]) From 892f554ff54025af530219ec17e2260f19660af4 Mon Sep 17 00:00:00 2001 From: Knuti_in_Paese Date: Sat, 4 Feb 2023 11:41:29 +0100 Subject: [PATCH 3/6] RPI:finer tuned debug logging Description for prep RF24 and pyrf24 on debian 11 (bullseye) 64 bit OS --- tools/rpi/README.md | 52 +++++++++++++++++++++++-- tools/rpi/ahoy.service | 7 ++-- tools/rpi/hoymiles/__init__.py | 29 +++++++++++--- tools/rpi/hoymiles/__main__.py | 22 +++++++---- tools/rpi/hoymiles/decoders/__init__.py | 28 +++++++------ tools/rpi/hoymiles/outputs.py | 15 +++++-- 6 files changed, 118 insertions(+), 35 deletions(-) diff --git a/tools/rpi/README.md b/tools/rpi/README.md index 36b08f79..12b6f5ef 100644 --- a/tools/rpi/README.md +++ b/tools/rpi/README.md @@ -81,14 +81,58 @@ python3 getting_started.py # to test and see whether RF24 class can be loaded as If there are no error messages on the last step, then the NRF24 Wrapper has been installed successfully. -for Debian 11 (bullseye) 64 bit operating system -------------------------------------------------- +Building RF24 Wrapper for Debian 11 (bullseye) 64 bit operating system +---------------------------------------------------------------------- +The description above does not work on Debian 11 (bullseye) 64 bit operating system. +There are 2 possible sollutions to install the RF24 Wrapper. + + * `1. solution:` ```code -[ $(lscpu | grep Architecture | awk '{print $2}') != "aarch64" ]] && echo "Not a 64 bit architecture for this step!" +sudo apt install cmake git python3-dev libboost-python-dev python3-pip python3-rpi.gpio + +sudo ln -s $(ls /usr/lib/$(ls /usr/lib/gcc | \ + head -1)/libboost_python3*.so | \ + tail -1) /usr/lib/$(ls /usr/lib/gcc | \ + head -1)/libboost_python3.so + +git clone https://github.com/nRF24/RF24.git +cd RF24 + +rm -rf build Makefile.inc +./configure --driver=SPIDEV +``` + * edit `Makefile.inc` with your prefered editor e.g. nano or vi + old: +```code + CPUFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard + CFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -Ofast -Wall -pthread +``` + new: +```code + CPUFLAGS= + CFLAGS=-Ofast -Wall -pthread +``` + * continue with +```code +make +sudo make install + +cd pyRF24 +rm -r ./build/ ./dist/ ./RF24.egg-info/ ./__pycache__/ #just to make sure there is no old stuff +python3 -m pip install --upgrade pip +python3 -m pip install . +python3 -m pip list #watch for RF24 module - if its there its installed +``` + + + + * `2. solution:` +```code +sudo apt install git python3-dev libboost-python-dev python3-pip python3-rpi.gpio git clone --recurse-submodules https://github.com/nRF24/pyRF24.git cd pyRF24 -python -m pip install . -v # this step takes about 5 minutes! +python3 -m pip install . -v # this step takes about 5 minutes on my RPI-4 ! ``` Required python modules diff --git a/tools/rpi/ahoy.service b/tools/rpi/ahoy.service index 394bc09e..4af9ea89 100644 --- a/tools/rpi/ahoy.service +++ b/tools/rpi/ahoy.service @@ -6,11 +6,10 @@ # WorkingDirectory (absolute path to your private ahoy dir) # To change other config parameter, please consult systemd documentation # -# To activate this service, create a link, enable and start the ahoy.service -# $ mkdir -p $HOME/.config/systemd/user -# $ ln -sf $(pwd)/ahoy/tools/rpi/ahoy.service -t $HOME/.config/systemd/user +# To activate this service, create a link with enable and start the ahoy.service +# $ mkdir -p $HOME/.config/systemd/user +# $ systemctl --user enable $(pwd)/ahoy/tools/rpi/ahoy.service # $ systemctl --user status ahoy -# $ systemctl --user enable ahoy # $ systemctl --user start ahoy # $ systemctl --user status ahoy # diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py index a754ff6d..210bed65 100644 --- a/tools/rpi/hoymiles/__init__.py +++ b/tools/rpi/hoymiles/__init__.py @@ -12,18 +12,26 @@ from datetime import datetime import logging import crcmod from .decoders import * +from os import environ try: # OSI Layer 2 driver for nRF24L01 on Arduino & Raspberry Pi/Linux Devices # https://github.com/nRF24/RF24.git from RF24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 -except ModuleNotFoundError: + if environ.get('TERM') is not None: + print('Using python Module: RF24') +except ModuleNotFoundError as e: + if environ.get('TERM') is not None: + print(f'{e} - try to use module: RF24') try: # Repo for pyRF24 package # https://github.com/nRF24/pyRF24.git from pyrf24 import RF24, RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_250KBPS, RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16 - except ModuleNotFoundError: - print("Module for RF24 not found - exit") + if environ.get('TERM') is not None: + print(f'{e} - Using python Module: pyrf24') + except ModuleNotFoundError as e: + if environ.get('TERM') is not None: + print(f'{e} - exit') exit() f_crc_m = crcmod.predefined.mkPredefinedCrcFun('modbus') @@ -171,8 +179,19 @@ class ResponseDecoder(ResponseDecoderFactory): command = self.request_command if HOYMILES_DEBUG_LOGGING: - c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f") - logging.info(f'{c_datetime} model_decoder: {model}Decode{command.upper()}') + if command.upper() == '01': + model_desc = "Firmware version / date" + elif command.upper() == '02': + model_desc = "Inverter generic events log" + elif command.upper() == '0B': + model_desc = "mirco-inverters status data" + elif command.upper() == '0C': + model_desc = "mirco-inverters status data" + elif command.upper() == '11': + model_desc = "Inverter generic events log" + elif command.upper() == '12': + model_desc = "Inverter major events log" + logging.info(f'model_decoder: {model}Decode{command.upper()} - {model_desc}') model_decoders = __import__('hoymiles.decoders') if hasattr(model_decoders, f'{model}Decode{command.upper()}'): diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index 35943eb4..00608a44 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -174,12 +174,13 @@ def poll_inverter(inverter, dtu_ser, do_init, retries): response = com.get_payload() payload_ttl = 0 except Exception as e_all: - logging.error(f'Error while retrieving data: {e_all}') + if hoymiles.HOYMILES_TRANSACTION_LOGGING: + logging.error(f'Error while retrieving data: {e_all}') pass # Handle the response data if any if response: - if hoymiles.HOYMILES_DEBUG_LOGGING: + if hoymiles.HOYMILES_TRANSACTION_LOGGING: c_datetime = datetime.now() logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response)) @@ -195,8 +196,7 @@ def poll_inverter(inverter, dtu_ser, do_init, retries): # get decoder object result = decoder.decode() if hoymiles.HOYMILES_DEBUG_LOGGING: - c_datetime = datetime.now() - logging.info(f'{c_datetime} Decoded: {result.__dict__()}') + logging.info(f'Decoded: {result.__dict__()}') # check decoder object for output if isinstance(result, hoymiles.decoders.StatusResponse): @@ -282,6 +282,12 @@ def init_logging(ahoy_config): lvl = logging.WARNING elif level == 'ERROR': lvl = logging.ERROR + elif level == 'FATAL': + lvl = logging.FATAL + if hoymiles.HOYMILES_TRANSACTION_LOGGING and hoymiles.HOYMILES_DEBUG_LOGGING: + lvl = logging.DEBUG + if not hoymiles.HOYMILES_TRANSACTION_LOGGING and not hoymiles.HOYMILES_DEBUG_LOGGING: + lvl = logging.INFO logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl) if __name__ == '__main__': @@ -309,15 +315,15 @@ if __name__ == '__main__': logging.error(f'Failed to load config file {global_config.config_file}: {e_yaml}') sys.exit(1) - # read AHOY configuration file and prepare logging - ahoy_config = dict(cfg.get('ahoy', {})) - init_logging(ahoy_config) - if global_config.log_transactions: hoymiles.HOYMILES_TRANSACTION_LOGGING=True if global_config.verbose: hoymiles.HOYMILES_DEBUG_LOGGING=True + # read AHOY configuration file and prepare logging + ahoy_config = dict(cfg.get('ahoy', {})) + init_logging(ahoy_config) + # Prepare for multiple transceivers, makes them configurable for radio_config in ahoy_config.get('nrf', [{}]): hmradio = hoymiles.HoymilesNRF(**radio_config) diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index e27b502d..ff277dbd 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -99,6 +99,7 @@ class StatusResponse(Response): frequency = None powerfactor = None event_count = None + unpack_error = False def unpack(self, fmt, base): """ @@ -111,6 +112,7 @@ class StatusResponse(Response): """ size = struct.calcsize(fmt) if (len(self.response) < base+size): + self.unpack_error = True logging.error(f'base: {base} size: {size} len: {len(self.response)} fmt: {fmt} rep: {self.response}') return [0] return struct.unpack(fmt, self.response[base:base+size]) @@ -196,7 +198,8 @@ class StatusResponse(Response): data['event_count'] = self.event_count data['time'] = self.time_rx - return data + if not self.unpack_error: + return data class UnknownResponse(Response): """ @@ -334,12 +337,13 @@ class EventsResponse(UnknownResponse): logging.debug(' '.join([f'{byte:02x}' for byte in chunk]) + ': ') - if (len(chunk[0:6]) == 6): - opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6]) - a_text = self.alarm_codes.get(a_code, 'N/A') - logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}') - else: + if (len(chunk[0:6]) < 6): logging.error(f'length of chunk must be greater or equal 6 bytes: {chunk}') + return + + opcode, a_code, a_count, uptime_sec = struct.unpack('>BBHH', chunk[0:6]) + a_text = self.alarm_codes.get(a_code, 'N/A') + logging.debug(f' uptime={timedelta(seconds=uptime_sec)} a_count={a_count} opcode={opcode} a_code={a_code} a_text={a_text}') dbg = '' for fmt in ['BBHHHHH']: @@ -366,12 +370,15 @@ class HardwareInfoResponse(UnknownResponse): def __dict__(self): """ Base values, availabe in each __dict__ call """ + data = super().__dict__() responce_info = self.response - if (len(responce_info) >= 16): - logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}') - else: - logging.error(f'wrong length of HardwareInfoResponse: {responce_info}') + if (len(self.response) != 16): + logging.error(f'HardwareInfoResponse: data length should be 16 bytes - measured {len(self.response)} bytes') + logging.error(f'HardwareInfoResponse: data: {self.response}') + return data + + logging.info(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", self.response[0:16])}') fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10]) fw_version_maj = int((fw_version / 10000)) @@ -385,7 +392,6 @@ class HardwareInfoResponse(UnknownResponse): f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\ f'HW revision {hw_id}') - data = super().__dict__() data['FW_ver_maj'] = fw_version_maj data['FW_ver_min'] = fw_version_min data['FW_ver_pat'] = fw_version_pat diff --git a/tools/rpi/hoymiles/outputs.py b/tools/rpi/hoymiles/outputs.py index 8fb55f3e..e4754fbc 100644 --- a/tools/rpi/hoymiles/outputs.py +++ b/tools/rpi/hoymiles/outputs.py @@ -9,6 +9,7 @@ import socket import logging from datetime import datetime, timezone from hoymiles.decoders import StatusResponse, HardwareInfoResponse +from hoymiles import HOYMILES_TRANSACTION_LOGGING, HOYMILES_DEBUG_LOGGING class OutputPluginFactory: def __init__(self, **params): @@ -277,9 +278,10 @@ class VzInverterOutput: self.channels = dict() for channel in config.get('channels', []): - uid = channel.get('uid') + uid = channel.get('uid', None) ctype = channel.get('type') - if uid and ctype: + # if uid and ctype: + if ctype: self.channels[ctype] = uid def store_status(self, data, session): @@ -330,10 +332,17 @@ class VzInverterOutput: def try_publish(self, ts, ctype, value): if not ctype in self.channels: - logging.warning(f'ctype \"{ctype}\" not found in ahoy.yml') + if HOYMILES_DEBUG_LOGGING: + logging.warning(f'ctype \"{ctype}\" not found in ahoy.yml') return + uid = self.channels[ctype] url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}' + if uid == None: + if HOYMILES_DEBUG_LOGGING: + logging.warning(f'ctype \"{ctype}\" has no configured uid-value in ahoy.yml') + return + try: r = self.session.get(url) if r.status_code == 404: From 9a0bee831d33aaecd7c707fa1f8d08f672f76c14 Mon Sep 17 00:00:00 2001 From: Knuti_in_Paese Date: Sat, 4 Feb 2023 16:40:50 +0100 Subject: [PATCH 4/6] RPi:specify README.md and collect data from EventsResponse --- tools/rpi/README.md | 26 ++++++++++++++++++------- tools/rpi/hoymiles/decoders/__init__.py | 15 ++++++++++---- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tools/rpi/README.md b/tools/rpi/README.md index 12b6f5ef..13758b08 100644 --- a/tools/rpi/README.md +++ b/tools/rpi/README.md @@ -84,9 +84,14 @@ If there are no error messages on the last step, then the NRF24 Wrapper has been Building RF24 Wrapper for Debian 11 (bullseye) 64 bit operating system ---------------------------------------------------------------------- The description above does not work on Debian 11 (bullseye) 64 bit operating system. -There are 2 possible sollutions to install the RF24 Wrapper. +Please check first, if you have Debian 11 (bullseye) 64 bit operating system installed: + - `uname -a` search for aarch64 + - `lsb_release -d` + - `cat /etc/debian_version` - * `1. solution:` +There are 2 possible solutions to install the RF24 wrapper: + + * `1. Solution:` ```code sudo apt install cmake git python3-dev libboost-python-dev python3-pip python3-rpi.gpio @@ -101,13 +106,13 @@ cd RF24 rm -rf build Makefile.inc ./configure --driver=SPIDEV ``` - * edit `Makefile.inc` with your prefered editor e.g. nano or vi - old: + # edit `Makefile.inc` with your prefered editor e.g. nano or vi + - old: ```code CPUFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard CFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -Ofast -Wall -pthread ``` - new: + - new: ```code CPUFLAGS= CFLAGS=-Ofast -Wall -pthread @@ -125,8 +130,7 @@ python3 -m pip list #watch for RF24 module - if its there its installed ``` - - * `2. solution:` + * `2. Solution:` ```code sudo apt install git python3-dev libboost-python-dev python3-pip python3-rpi.gpio @@ -135,6 +139,14 @@ cd pyRF24 python3 -m pip install . -v # this step takes about 5 minutes on my RPI-4 ! ``` +If you have problems with your radio module from ahoi, +e.g.: cannot interpret received data, +please try to reduce the speed of the radio module! +Add the following line to your ahoy.yml configuration file in "nrf" section: +* `spispeed: 600000` + + + Required python modules ----------------------- diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index ff277dbd..fa80a3b4 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -327,9 +327,9 @@ class EventsResponse(UnknownResponse): #logging.debug(' payload has valid modbus crc') self.response = self.response[:-2] - status = struct.unpack('>H', self.response[:2])[0] - a_text = self.alarm_codes.get(status, 'N/A') - logging.info (f' Inverter status: {a_text} ({status})') + self.status = struct.unpack('>H', self.response[:2])[0] + self.a_text = self.alarm_codes.get(self.status, 'N/A') + logging.info (f' Inverter status: {self.a_text} ({self.status})') chunk_size = 12 for i_chunk in range(2, len(self.response), chunk_size): @@ -350,6 +350,14 @@ class EventsResponse(UnknownResponse): dbg += f' {fmt:7}: ' + str(struct.unpack('>' + fmt, chunk)) logging.debug(dbg) + def __dict__(self): + """ Base values, availabe in each __dict__ call """ + + data = super().__dict__() + data['inv_stat_num'] = self.status + data['inv_stat_txt'] = self.a_text + return data + class HardwareInfoResponse(UnknownResponse): def __init__(self, *args, **params): super().__init__(*args, **params) @@ -371,7 +379,6 @@ class HardwareInfoResponse(UnknownResponse): """ Base values, availabe in each __dict__ call """ data = super().__dict__() - responce_info = self.response if (len(self.response) != 16): logging.error(f'HardwareInfoResponse: data length should be 16 bytes - measured {len(self.response)} bytes') From 57bc46191c4d93d02c9bec3d223d20f1a9acc2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knuti=5Fin=5FP=C3=A4se?= <122045840+PaeserBastelstube@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:25:58 +0100 Subject: [PATCH 5/6] RPi: README.md format one new section --- tools/rpi/README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tools/rpi/README.md b/tools/rpi/README.md index 13758b08..d1829706 100644 --- a/tools/rpi/README.md +++ b/tools/rpi/README.md @@ -91,7 +91,7 @@ Please check first, if you have Debian 11 (bullseye) 64 bit operating system ins There are 2 possible solutions to install the RF24 wrapper: - * `1. Solution:` +**__1. Solution:__** ```code sudo apt install cmake git python3-dev libboost-python-dev python3-pip python3-rpi.gpio @@ -106,18 +106,19 @@ cd RF24 rm -rf build Makefile.inc ./configure --driver=SPIDEV ``` - # edit `Makefile.inc` with your prefered editor e.g. nano or vi - - old: -```code - CPUFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard - CFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -Ofast -Wall -pthread -``` - - new: -```code - CPUFLAGS= - CFLAGS=-Ofast -Wall -pthread -``` - * continue with +> _edit `Makefile.inc` with your prefered editor e.g. nano or vi_ +> +> old: +>```code +> CPUFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard +> CFLAGS=-marm -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -Ofast -Wall -pthread +>``` +> new: +>```code +> CPUFLAGS= +> CFLAGS=-Ofast -Wall -pthread +>``` +_continue now_ ```code make sudo make install @@ -130,7 +131,7 @@ python3 -m pip list #watch for RF24 module - if its there its installed ``` - * `2. Solution:` +**__2. Solution:__** ```code sudo apt install git python3-dev libboost-python-dev python3-pip python3-rpi.gpio @@ -139,11 +140,10 @@ cd pyRF24 python3 -m pip install . -v # this step takes about 5 minutes on my RPI-4 ! ``` -If you have problems with your radio module from ahoi, -e.g.: cannot interpret received data, -please try to reduce the speed of the radio module! -Add the following line to your ahoy.yml configuration file in "nrf" section: -* `spispeed: 600000` +If you have problems with your radio module from ahoi, e.g.: cannot interpret received data, +please try to reduce the speed of your radio module! +Add the following parameter to your ahoy.yml configuration file in "nrf" section: +`spispeed: 600000` (0.6 MHz) @@ -247,7 +247,7 @@ Todo - Ability to talk to multiple inverters - MQTT gateway - understand channel hopping -- configurable polling interval +- ~~configurable polling interval~~ done: interval ist configurable in ahoy.yml - commands - picture of setup! - python module From 6b3af717fbb3b42239f6de7d04930c8698321eb5 Mon Sep 17 00:00:00 2001 From: Knuti_in_Paese Date: Tue, 14 Feb 2023 10:34:22 +0100 Subject: [PATCH 6/6] RPi:(new)DTU-name,Disco-handler,ext.Error-handling,sun2mqtt Add disconnect handler for influx and volkszaehler. Change spec. Informations on ahoy.service and ahoy.yml.example. Extented Error handling. Send sun-rise and sun-set information to MQTT. --- tools/rpi/ahoy.service | 3 +- tools/rpi/ahoy.yml.example | 13 ++--- tools/rpi/hoymiles/__main__.py | 47 ++++++++++++++---- tools/rpi/hoymiles/decoders/__init__.py | 17 ++++++- tools/rpi/hoymiles/outputs.py | 63 ++++++++++++++++++++----- 5 files changed, 112 insertions(+), 31 deletions(-) diff --git a/tools/rpi/ahoy.service b/tools/rpi/ahoy.service index 4af9ea89..c7be5bb2 100644 --- a/tools/rpi/ahoy.service +++ b/tools/rpi/ahoy.service @@ -6,8 +6,7 @@ # WorkingDirectory (absolute path to your private ahoy dir) # To change other config parameter, please consult systemd documentation # -# To activate this service, create a link with enable and start the ahoy.service -# $ mkdir -p $HOME/.config/systemd/user +# To activate this service, enable and start ahoy.service # $ systemctl --user enable $(pwd)/ahoy/tools/rpi/ahoy.service # $ systemctl --user status ahoy # $ systemctl --user start ahoy diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example index 928374de..9301067f 100644 --- a/tools/rpi/ahoy.yml.example +++ b/tools/rpi/ahoy.yml.example @@ -31,7 +31,7 @@ ahoy: QoS: 0 Retain: True last_will: - topic: hoymiles/114172220003 # defaults to 'hoymiles/{serial}' + topic: my_DTU_name # Name of DTU - default: hoymiles/{DTU-serial} payload: "LAST-WILL-MESSAGE: Please check my HOST and Process!" # Influx2 output @@ -96,6 +96,7 @@ ahoy: dtu: serial: 99978563001 + name: my_DTU_name inverters: - name: 'balkon' @@ -103,14 +104,14 @@ ahoy: txpower: 'low' # txpower per inverter (min,low,high,max) mqtt: send_raw_enabled: false # allow inject debug data via mqtt - topic: 'hoymiles/114172221234' # defaults to '{inverter-name}/{serial}' + topic: 'hoymiles/114172220003' # defaults to '{inverter-name}/{serial}' strings: # list all available strings - s_name: 'String 1 left' # String 1 name - s_maxpower: 395 # String 1 max power in Wp + s_maxpower: 395 # String 1 max power in inverter - s_name: 'String 2 right' # String 2 name - s_maxpower: 400 # String 2 max power in Wp + s_maxpower: 400 # String 2 max power in inverter - s_name: 'String 3 up' # String 3 name - s_maxpower: 405 # String 3 max power in Wp + s_maxpower: 405 # String 3 max power in inverter - s_name: 'String 4 down' # String 4 name - s_maxpower: 410 # String 4 max power in Wp + s_maxpower: 410 # String 4 max power in inverter diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index 00608a44..7de4a1a2 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -33,6 +33,12 @@ def signal_handler(sig_num, frame): if mqtt_client: mqtt_client.disco() + if influx_client: + influx_client.disco() + + if volkszaehler_client: + volkszaehler_client.disco() + sys.exit(0) signal(SIGINT, signal_handler) # Interrupt from keyboard (CTRL + C) @@ -75,7 +81,6 @@ class SunsetHandler: else: logging.info('Sunset disabled.') - def checkWaitForSunrise(self): if not self.suntimes: return @@ -94,6 +99,23 @@ class SunsetHandler: time.sleep(time_to_sleep) logging.info (f'Woke up...') + def sun_status2mqtt(self, dtu_ser, dtu_name): + if not mqtt_client: + return + local_sunrise = self.suntimes.riselocal(datetime.now()).strftime("%d.%m.%YT%H:%M") + local_sunset = self.suntimes.setlocal(datetime.now()).strftime("%d.%m.%YT%H:%M") + local_zone = self.suntimes.setlocal(datetime.now()).tzinfo._key + if self.suntimes: + mqtt_client.info2mqtt({'topic' : f'{dtu_name}/{dtu_ser}'}, \ + {'dis_night_comm' : 'True', \ + 'local_sunrise' : local_sunrise, \ + 'local_sunset' : local_sunset, + 'local_zone' : local_zone}) + else: + mqtt_client.sun_info2mqtt({'sun_topic': f'{dtu_name}/{dtu_ser}'}, \ + {'dis_night_comm': 'False'}) + + def main_loop(ahoy_config): """Main loop""" inverters = [ @@ -101,7 +123,9 @@ def main_loop(ahoy_config): if not inverter.get('disabled', False)] sunset = SunsetHandler(ahoy_config.get('sunset')) - dtu_ser = ahoy_config.get('dtu', {}).get('serial') + dtu_ser = ahoy_config.get('dtu', {}).get('serial', None) + dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu') + sunset.sun_status2mqtt(dtu_ser, dtu_name) loop_interval = ahoy_config.get('interval', 1) try: @@ -112,6 +136,11 @@ def main_loop(ahoy_config): t_loop_start = time.time() for inverter in inverters: + if not 'name' in inverter: + inverter['name'] = 'hoymiles' + if not 'serial' in inverter: + logging.error("No inverter serial number found in ahoy.yml - exit") + sys.exit(999) if hoymiles.HOYMILES_DEBUG_LOGGING: logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}') poll_inverter(inverter, dtu_ser, do_init, 3) @@ -122,8 +151,6 @@ def main_loop(ahoy_config): if time_to_sleep > 0: time.sleep(time_to_sleep) - except KeyboardInterrupt: - sys.exit() except Exception as e: logging.fatal('Exception catched: %s' % e) logging.fatal(traceback.print_exc()) @@ -284,11 +311,11 @@ def init_logging(ahoy_config): lvl = logging.ERROR elif level == 'FATAL': lvl = logging.FATAL - if hoymiles.HOYMILES_TRANSACTION_LOGGING and hoymiles.HOYMILES_DEBUG_LOGGING: - lvl = logging.DEBUG - if not hoymiles.HOYMILES_TRANSACTION_LOGGING and not hoymiles.HOYMILES_DEBUG_LOGGING: - lvl = logging.INFO + if hoymiles.HOYMILES_TRANSACTION_LOGGING: + lvl = logging.DEBUG logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl) + dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu') + logging.info(f'start logging for {dtu_name} with level: {logging.root.level}') if __name__ == '__main__': parser = argparse.ArgumentParser(description='Ahoy - Hoymiles solar inverter gateway', prog="hoymiles") @@ -330,14 +357,14 @@ if __name__ == '__main__': # create MQTT - client object mqtt_client = None - mqtt_config = ahoy_config.get('mqtt', {}) + mqtt_config = ahoy_config.get('mqtt', None) if mqtt_config and not mqtt_config.get('disabled', False): from .outputs import MqttOutputPlugin mqtt_client = MqttOutputPlugin(mqtt_config) # create INFLUX - client object influx_client = None - influx_config = ahoy_config.get('influxdb', {}) + influx_config = ahoy_config.get('influxdb', None) if influx_config and not influx_config.get('disabled', False): from .outputs import InfluxOutputPlugin influx_client = InfluxOutputPlugin( diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index fa80a3b4..bb32fb07 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -155,6 +155,7 @@ class StatusResponse(Response): s_exists = False string_id = len(strings) string = {} + string['name'] = self.inv_strings[string_id]['s_name'] for key in self.string_keys: prop = f'dc_{key}_{string_id}' if hasattr(self, prop): @@ -329,7 +330,7 @@ class EventsResponse(UnknownResponse): self.status = struct.unpack('>H', self.response[:2])[0] self.a_text = self.alarm_codes.get(self.status, 'N/A') - logging.info (f' Inverter status: {self.a_text} ({self.status})') + logging.info (f'Inverter status: {self.a_text} ({self.status})') chunk_size = 12 for i_chunk in range(2, len(self.response), chunk_size): @@ -489,6 +490,8 @@ class Hm300Decode0B(StatusResponse): """ String 1 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[0]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property @@ -561,6 +564,8 @@ class Hm600Decode0B(StatusResponse): """ String 1 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[0]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property @@ -588,6 +593,8 @@ class Hm600Decode0B(StatusResponse): """ String 2 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[1]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3) @property @@ -668,6 +675,8 @@ class Hm1200Decode0B(StatusResponse): """ String 1 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[0]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3) @property @@ -695,6 +704,8 @@ class Hm1200Decode0B(StatusResponse): """ String 2 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[1]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3) @property @@ -722,6 +733,8 @@ class Hm1200Decode0B(StatusResponse): """ String 3 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[2]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3) @property @@ -749,6 +762,8 @@ class Hm1200Decode0B(StatusResponse): """ String 4 irratiation in percent """ if self.inv_strings is None: return None + if self.inv_strings[3]['s_maxpower'] == 0: + return 0.00 return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3) @property diff --git a/tools/rpi/hoymiles/outputs.py b/tools/rpi/hoymiles/outputs.py index e4754fbc..11971a85 100644 --- a/tools/rpi/hoymiles/outputs.py +++ b/tools/rpi/hoymiles/outputs.py @@ -40,6 +40,7 @@ class InfluxOutputPlugin(OutputPluginFactory): def __init__(self, url, token, **params): """ Initialize InfluxOutputPlugin + https://influxdb-client.readthedocs.io/en/stable/api.html#influxdbclient The following targets must be present in your InfluxDB. This does not automatically create anything for You. @@ -69,8 +70,12 @@ class InfluxOutputPlugin(OutputPluginFactory): self._org = params.get('org', '') self._measurement = params.get('measurement', f'inverter,host={socket.gethostname()}') - client = InfluxDBClient(url, token, bucket=self._bucket) - self.api = client.write_api() + with InfluxDBClient(url, token, bucket=self._bucket) as self.client: + self.api = self.client.write_api() + + def disco(self, **params): + self.client.close() # Shutdown the client + return def store_status(self, response, **params): """ @@ -103,6 +108,9 @@ class InfluxOutputPlugin(OutputPluginFactory): # InfluxDB requires nanoseconds ctime = int(utctime.timestamp() * 1e9) + if HOYMILES_DEBUG_LOGGING: + logging.info(f'InfluxDB: utctime: {utctime}') + # AC Data phase_id = 0 for phase in data['phases']: @@ -136,6 +144,9 @@ class InfluxOutputPlugin(OutputPluginFactory): data_stack.append(f'{measurement},type=YieldToday value={data["yield_today"]/1000:.3f} {ctime}') data_stack.append(f'{measurement},type=Efficiency value={data["efficiency"]:.2f} {ctime}') + if HOYMILES_DEBUG_LOGGING: + #logging.debug(f'INFLUX data to DB: {data_stack}') + pass self.api.write(self._bucket, self._org, data_stack) class MqttOutputPlugin(OutputPluginFactory): @@ -197,6 +208,12 @@ class MqttOutputPlugin(OutputPluginFactory): def disco(self, **params): self.client.loop_stop() # Stop loop self.client.disconnect() # disconnect + return + + def info2mqtt(self, mqtt_topic, mqtt_data): + for mqtt_key in mqtt_data: + self.client.publish(f'{mqtt_topic["topic"]}/{mqtt_key}', mqtt_data[mqtt_key], self.qos, self.ret) + return def store_status(self, response, **params): """ @@ -210,13 +227,18 @@ class MqttOutputPlugin(OutputPluginFactory): """ data = response.__dict__() - topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}' + topic = params.get('topic', None) + if not topic: + topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}' + + if HOYMILES_DEBUG_LOGGING: + logging.info(f'MQTT-topic: {topic} data-type: {type(response)}') if isinstance(response, StatusResponse): # Global Head if data['time'] is not None: - self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"), self.qos, self.ret) + self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%YT%H:%M:%S"), self.qos, self.ret) # AC Data phase_id = 0 @@ -234,12 +256,16 @@ class MqttOutputPlugin(OutputPluginFactory): string_id = 0 string_sum_power = 0 for string in data['strings']: - self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'], self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000, self.qos, self.ret) - self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation'], self.qos, self.ret) + if 'name' in string: + string_name = string['name'].replace(" ","_") + else: + string_name = string_id + self.client.publish(f'{topic}/emeter-dc/{string_name}/voltage', string['voltage'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/current', string['current'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/power', string['power'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldDay', string['energy_daily'], self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/YieldTotal', string['energy_total']/1000, self.qos, self.ret) + self.client.publish(f'{topic}/emeter-dc/{string_name}/Irradiation', string['irradiation'], self.qos, self.ret) string_id = string_id + 1 string_sum_power += string['power'] @@ -297,6 +323,9 @@ class VzInverterOutput: ts = int(round(data['time'].timestamp() * 1000)) + if HOYMILES_DEBUG_LOGGING: + logging.info(f'Volkszaehler-Timestamp: {ts}') + # AC Data phase_id = 0 for phase in data['phases']: @@ -329,6 +358,7 @@ class VzInverterOutput: if data['yield_today'] is not None: self.try_publish(ts, f'yield_today', data['yield_today']) self.try_publish(ts, f'efficiency', data['efficiency']) + return def try_publish(self, ts, ctype, value): if not ctype in self.channels: @@ -340,9 +370,12 @@ class VzInverterOutput: url = f'{self.baseurl}/data/{uid}.json?operation=add&ts={ts}&value={value}' if uid == None: if HOYMILES_DEBUG_LOGGING: - logging.warning(f'ctype \"{ctype}\" has no configured uid-value in ahoy.yml') + logging.debug(f'ctype \"{ctype}\" has no configured uid-value in ahoy.yml') return + if HOYMILES_DEBUG_LOGGING: + logging.debug(f'VZ-url: {url}') + try: r = self.session.get(url) if r.status_code == 404: @@ -353,6 +386,7 @@ class VzInverterOutput: raise ValueError(f'Transmit result {url}') except ConnectionError as e: raise ValueError(f'Could not connect VZ-DB {type(e)} {e.keys()}') + return class VolkszaehlerOutputPlugin(OutputPluginFactory): def __init__(self, config, **params): @@ -373,13 +407,17 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory): exit(1) self.session = requests.Session() - self.inverters = dict() + self.inverters = dict() for inverterconfig in config.get('inverters', []): serial = inverterconfig.get('serial') output = VzInverterOutput(inverterconfig, self.session) self.inverters[serial] = output + def disco(self, **params): + self.session.close() # closing the connection + return + def store_status(self, response, **params): """ Publish StatusResponse object @@ -404,3 +442,4 @@ class VolkszaehlerOutputPlugin(OutputPluginFactory): output.store_status(data, self.session) except ValueError as e: logging.warning('Could not send data to volkszaehler instance: %s' % e) + return