From 42fc31f687ad7bf8500897955000849a4e9ad734 Mon Sep 17 00:00:00 2001 From: Privatecoder <45964815+Privatecoder@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:59:38 +0100 Subject: [PATCH] improve code and docker image to handle reading of master and slave packs in a single container --- README.md | 295 +++++++++++++++++++++++++----------------- src/config.ini | 17 ++- src/entrypoint.sh | 20 ++- src/fetch_bms_data.py | 101 +++++++++------ 4 files changed, 255 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index c5f1f13..454b400 100644 --- a/README.md +++ b/README.md @@ -30,81 +30,133 @@ This works for both, Masters via the splitter (Baud 9600) and Slaves to an empty 3. Modify the `config.ini` and edit its settings to your needs (**alternatively**: configure everything via ENV-vars) 4. Run the Docker Image, for example like this: -- For the master pack: `docker run -itd -e RS485_REMOTE_IP="192.168.1.200" -e RS485_REMOTE_PORT="4196" -v $(pwd)/config-master.ini:/usr/src/app/config.ini --name seplos-mqtt-master privatecoder/seplos-mqtt-remote-rs485:v1.2.3` +- For 1 master and 1 slave, i.e. two packs using ENV-vars: -- For the slaves: `docker run -itd -e RS485_REMOTE_IP="192.168.1.201" -e RS485_REMOTE_PORT="4196" -v $(pwd)/config-slaves.ini:/usr/src/app/config.ini --name seplos-mqtt-slaves privatecoder/seplos-mqtt-remote-rs485:v1.2.3` +``` +docker run -itd \ + -e RS485_MASTER_REMOTE_IP="192.168.1.200" \ + -e RS485_MASTER_REMOTE_PORT="4196" \ + -e RS485_SLAVES_REMOTE_IP="192.168.1.201" \ + -e RS485_SLAVES_REMOTE_PORT="4196" \ + -e FETCH_MASTER=true \ + -e NUMBER_OF_SLAVES=1 \ + -e MQTT_HOST=192.168.1.100 \ + -e MQTT_USERNAME=seplos-mqtt \ + -e MQTT_PASSWORD=my-secret-password \ + --name seplos-mqtt-rs485 \ + privatecoder/seplos-mqtt-remote-rs485:v1.2.3 +``` + +- For 1 master and 1 slave, i.e. two packs using config.ini (in which `FETCH_MASTER` is set to `true`): + +``` +docker run -itd \ + -e RS485_MASTER_REMOTE_IP="192.168.1.200" \ + -e RS485_MASTER_REMOTE_PORT="4196" \ + -e RS485_SLAVES_REMOTE_IP="192.168.1.201" \ + -e RS485_SLAVES_REMOTE_PORT="4196" \ + -v $(pwd)/config-master.ini:/usr/src/app/config.ini \ + --name seplos-mqtt-rs485 \ + privatecoder/seplos-mqtt-remote-rs485:v1.2.3 +``` + +- To run the script without socat / remote RS485 but local connections, don't set the `RS485_MASTER_REMOTE_IP` and `RS485_SLAVES_REMOTE_IP` ENV-vars, i.e: + +``` +docker run -itd \ + -e FETCH_MASTER=true \ + -e NUMBER_OF_SLAVES=1 \ + -e MQTT_HOST=192.168.1.100 \ + -e MQTT_USERNAME=seplos-mqtt \ + -e MQTT_PASSWORD=my-secret-password \ + --name seplos-mqtt-rs485 \ + privatecoder/seplos-mqtt-remote-rs485:v1.2.3 +``` + +**or** + +``` +docker run -itd \ + -v $(pwd)/config-master.ini:/usr/src/app/config.ini \ + --name seplos-mqtt-rs485 \ + privatecoder/seplos-mqtt-remote-rs485:v1.2.3 +``` Available ENV-vars are: -`RS485_REMOTE_IP` -`RS485_REMOTE_PORT` +`RS485_MASTER_REMOTE_IP` +`RS485_MASTER_REMOTE_PORT` + +`RS485_SLAVES_REMOTE_IP` +`RS485_SLAVES_REMOTE_PORT` -`MQTT_HOST` -`MQTT_PORT` -`MQTT_USERNAME` -`MQTT_PASSWORD` -`MQTT_TOPIC` -`MQTT_UPDATE_INTERVAL` +`MQTT_HOST` (default: `192.168.1.100`) +`MQTT_PORT` (default: `1883`) +`MQTT_USERNAME` (default: `seplos-mqtt`) +`MQTT_PASSWORD` (default: `my-secret-password`) +`MQTT_TOPIC` (default: `seplos`) +`MQTT_UPDATE_INTERVAL` (default: `0`) -`ONLY_MASTER` -`NUMBER_OF_PACKS` -`MIN_CELL_VOLTAGE` -`MAX_CELL_VOLTAGE` +`FETCH_MASTER` (default: `false`) +`NUMBER_OF_SLAVES` (default: `1`) +`MIN_CELL_VOLTAGE` (default: `2.500`) +`MAX_CELL_VOLTAGE` (default: `3.650`) -`SERIAL_INTERFACE` -`SERIAL_BAUD_RATE` +`MASTER_SERIAL_INTERFACE` (default: `/tmp/vcom0`) +`SLAVES_SERIAL_INTERFACE` (default: `/tmp/vcom1`) -`LOGGING_LEVEL` +`LOGGING_LEVEL` (default: `info`) -Set `RS485_REMOTE_IP` and `RS485_REMOTE_PORT` to start the docker image with socat, binding your remote RS485 device´s RS485 port locally to `vcom0` (used by default in this script). -Not defining those will just start the script, however `SERIAL_INTERFACE` must match your existing serial-device – either passed to the container directly or using the privileged-flag (not recommended). +Set `RS485_MASTER_REMOTE_IP`, `RS485_MASTER_REMOTE_PORT`, `RS485_SLAVES_REMOTE_IP` and `RS485_SLAVES_REMOTE_PORT` starts the docker image with socat, binding your remote RS485 device´s RS485 ports locally to `vcom0` (master) and `vcom1` (slaves) (used by default in this script). +Not defining those will just start the script, however `MASTER_SERIAL_INTERFACE` and `SLAVES_SERIAL_INTERFACE` must match your existing serial-devices – either passed to the container directly or using the privileged-flag (not recommended). MQTT messages published by the script will look like this: ``` { + "last_update": "2024-02-02 11:39:08", "telemetry": { "min_cell_voltage": 2.5, "max_cell_voltage": 3.65, "min_pack_voltage": 40.0, "max_pack_voltage": 58.4, - "voltage_cell_1": 3.27, - "voltage_cell_2": 3.285, - "voltage_cell_3": 3.27, - "voltage_cell_4": 3.269, - "voltage_cell_5": 3.267, - "voltage_cell_6": 3.27, - "voltage_cell_7": 3.27, - "voltage_cell_8": 3.269, - "voltage_cell_9": 3.266, - "voltage_cell_10": 3.271, - "voltage_cell_11": 3.269, - "voltage_cell_12": 3.282, - "voltage_cell_13": 3.27, - "voltage_cell_14": 3.271, - "voltage_cell_15": 3.268, - "voltage_cell_16": 3.268, - "average_cell_voltage": 3.271, - "lowest_cell": 9, - "lowest_cell_voltage": 3.266, - "highest_cell": 2, - "highest_cell_voltage": 3.285, - "delta_cell_voltage": 0.019, - "cell_temperature_1": 11.7, - "cell_temperature_2": 10.9, - "cell_temperature_3": 10.7, - "cell_temperature_4": 11.5, - "ambient_temperature": 16.0, - "components_temperature": 12.0, - "dis_charge_current": 0.0, - "total_pack_voltage": 52.34, - "dis_charge_power": 0.0, + "voltage_cell_1": 3.339, + "voltage_cell_2": 3.34, + "voltage_cell_3": 3.34, + "voltage_cell_4": 3.34, + "voltage_cell_5": 3.339, + "voltage_cell_6": 3.342, + "voltage_cell_7": 3.342, + "voltage_cell_8": 3.34, + "voltage_cell_9": 3.342, + "voltage_cell_10": 3.343, + "voltage_cell_11": 3.34, + "voltage_cell_12": 3.341, + "voltage_cell_13": 3.34, + "voltage_cell_14": 3.346, + "voltage_cell_15": 3.343, + "voltage_cell_16": 3.342, + "average_cell_voltage": 3.341, + "lowest_cell": 1, + "lowest_cell_voltage": 3.339, + "highest_cell": 14, + "highest_cell_voltage": 3.346, + "delta_cell_voltage": 0.007, + "cell_temperature_1": 11.5, + "cell_temperature_2": 11.0, + "cell_temperature_3": 10.9, + "cell_temperature_4": 11.6, + "ambient_temperature": 16.5, + "components_temperature": 12.3, + "dis_charge_current": 1.46, + "total_pack_voltage": 53.46, + "dis_charge_power": 78.052, "rated_capacity": 280.0, "battery_capacity": 280.0, - "residual_capacity": 58.88, - "soc": 21.0, - "cycles": 6, + "residual_capacity": 192.94, + "soc": 68.9, + "cycles": 7, "soh": 100.0, - "port_voltage": 52.36 + "port_voltage": 53.48 }, "telesignalization": { "voltage_warning_cell_1": "normal", @@ -181,9 +233,9 @@ MQTT messages published by the script will look like this: "equalization_cell_15": "off", "equalization_cell_16": "off", "discharge": "off", - "charge": "off", + "charge": "on", "floating_charge": "off", - "standby": "on", + "standby": "off", "power_off": "off", "disconnection_cell_1": "normal", "disconnection_cell_2": "normal", @@ -216,78 +268,81 @@ MQTT messages published by the script will look like this: 1. Clone the project 2. Make sure to have Python v3.10 or later installed -3. Edit `config.ini` in `src` to your needs (to connect your remote RS485 device, bind it for example to `/tmp/vcom0` using socat like `socat pty,link=/tmp/vcom0,raw tcp:192.168.1.200:4196,retry,interval=.2,forever &` or something similar) +3. Edit `config.ini` in `src` to your needs (to connect your remote RS485 devices, bind them for example to `/tmp/vcom0` and `/tmp/vcom1` using socat like `socat pty,link=/tmp/vcom0,raw tcp:192.168.1.200:4196,retry,interval=.2,forever &` and `socat pty,link=/tmp/vcom1,raw tcp:192.168.1.201:4196,retry,interval=.2,forever &` or something similar) 4. Run the script, i.e. `python fetch_bms_data.py` Its output will look like this (`LOGGING` `LEVEL` set to `info`): ``` +INFO:SeplosBMS:Fetch data for Battery Pack 1 INFO:SeplosBMS:Battery-Pack 1 Telemetry Feedback: { - "min_pack_voltage": 46.4, - "max_pack_voltage": 55.2, - "voltage_cell_1": 3.255, - "voltage_cell_2": 3.255, - "voltage_cell_3": 3.255, - "voltage_cell_4": 3.255, - "voltage_cell_5": 3.253, - "voltage_cell_6": 3.258, - "voltage_cell_7": 3.259, - "voltage_cell_8": 3.256, - "voltage_cell_9": 3.258, - "voltage_cell_10": 3.261, - "voltage_cell_11": 3.255, - "voltage_cell_12": 3.26, - "voltage_cell_13": 3.257, - "voltage_cell_14": 3.264, - "voltage_cell_15": 3.262, - "voltage_cell_16": 3.259, - "average_cell_voltage": 3.258, - "lowest_cell": 5, - "lowest_cell_voltage": 3.253, - "highest_cell": 14, - "highest_cell_voltage": 3.264, - "delta_cell_voltage": 0.011, - "cell_temperature_1": 9.7, - "cell_temperature_2": 9.1, - "cell_temperature_3": 9.1, - "cell_temperature_4": 9.8, - "ambient_temperature": 14.8, - "components_temperature": 10.8, - "dis_charge_current": 0.0, - "total_pack_voltage": 52.12, - "dis_charge_power": 0.0, + "min_cell_voltage": 2.5, + "max_cell_voltage": 3.65, + "min_pack_voltage": 40.0, + "max_pack_voltage": 58.4, + "voltage_cell_1": 3.342, + "voltage_cell_2": 3.341, + "voltage_cell_3": 3.34, + "voltage_cell_4": 3.341, + "voltage_cell_5": 3.341, + "voltage_cell_6": 3.343, + "voltage_cell_7": 3.34, + "voltage_cell_8": 3.343, + "voltage_cell_9": 3.336, + "voltage_cell_10": 3.341, + "voltage_cell_11": 3.344, + "voltage_cell_12": 3.34, + "voltage_cell_13": 3.356, + "voltage_cell_14": 3.343, + "voltage_cell_15": 3.344, + "voltage_cell_16": 3.338, + "average_cell_voltage": 3.342, + "lowest_cell": 9, + "lowest_cell_voltage": 3.336, + "highest_cell": 13, + "highest_cell_voltage": 3.356, + "delta_cell_voltage": 0.02, + "cell_temperature_1": 11.7, + "cell_temperature_2": 11.1, + "cell_temperature_3": 11.0, + "cell_temperature_4": 11.7, + "ambient_temperature": 16.3, + "components_temperature": 12.3, + "dis_charge_current": 1.44, + "total_pack_voltage": 53.47, + "dis_charge_power": 76.997, "rated_capacity": 280.0, "battery_capacity": 280.0, - "residual_capacity": 44.46, - "soc": 15.8, - "cycles": 7, + "residual_capacity": 195.8, + "soc": 69.9, + "cycles": 6, "soh": 100.0, - "port_voltage": 52.15 + "port_voltage": 53.5 } INFO:SeplosBMS:Battery-Pack 1 Telesignalization feedback: { - "warning_cell_1": "normal", - "warning_cell_2": "normal", - "warning_cell_3": "normal", - "warning_cell_4": "normal", - "warning_cell_5": "normal", - "warning_cell_6": "normal", - "warning_cell_7": "normal", - "warning_cell_8": "normal", - "warning_cell_9": "normal", - "warning_cell_10": "normal", - "warning_cell_11": "normal", - "warning_cell_12": "normal", - "warning_cell_13": "normal", - "warning_cell_14": "normal", - "warning_cell_15": "normal", - "warning_cell_16": "normal", - "warning_cell_temperature_1": "normal", - "warning_cell_temperature_2": "normal", - "warning_cell_temperature_3": "normal", - "warning_cell_temperature_4": "normal", - "ambient_temperature_warnings": "normal", - "component_temperature_warnings": "normal", - "dis_charging_current_warnings": "normal", - "pack_voltage_warnings": "normal", + "voltage_warning_cell_1": "normal", + "voltage_warning_cell_2": "normal", + "voltage_warning_cell_3": "normal", + "voltage_warning_cell_4": "normal", + "voltage_warning_cell_5": "normal", + "voltage_warning_cell_6": "normal", + "voltage_warning_cell_7": "normal", + "voltage_warning_cell_8": "normal", + "voltage_warning_cell_9": "normal", + "voltage_warning_cell_10": "normal", + "voltage_warning_cell_11": "normal", + "voltage_warning_cell_12": "normal", + "voltage_warning_cell_13": "normal", + "voltage_warning_cell_14": "normal", + "voltage_warning_cell_15": "normal", + "voltage_warning_cell_16": "normal", + "cell_temperature_warning_1": "normal", + "cell_temperature_warning_2": "normal", + "cell_temperature_warning_3": "normal", + "cell_temperature_warning_4": "normal", + "ambient_temperature_warning": "normal", + "component_temperature_warning": "normal", + "dis_charging_current_warning": "normal", + "pack_voltage_warning": "normal", "voltage_sensing_failure": "normal", "temp_sensing_failure": "normal", "current_sensing_failure": "normal", @@ -338,9 +393,9 @@ INFO:SeplosBMS:Battery-Pack 1 Telesignalization feedback: { "equalization_cell_15": "off", "equalization_cell_16": "off", "discharge": "off", - "charge": "off", + "charge": "on", "floating_charge": "off", - "standby": "on", + "standby": "off", "power_off": "off", "disconnection_cell_1": "normal", "disconnection_cell_2": "normal", diff --git a/src/config.ini b/src/config.ini index ff43f2f..79b66a8 100644 --- a/src/config.ini +++ b/src/config.ini @@ -13,19 +13,18 @@ MQTT_TOPIC = seplos MQTT_UPDATE_INTERVAL = 0 [BMS] -# setting ONLY_MASTER = true fetches the masters data only, i.e. one pack -ONLY_MASTER = false -# setting NUMBER_OF_PACKS is only relevant if ONLY_MASTER is false -NUMBER_OF_PACKS = 2 +# FETCH_MASTER = true fetches data from masters and slaves +FETCH_MASTER = false +# NUMBER_OF_SLAVES sets number of slaves to fetch data for without counting the master +NUMBER_OF_SLAVES = 1 # set min cell voltage -MIN_CELL_VOLTAGE = 2.900 +MIN_CELL_VOLTAGE = 2.500 # set max cell voltage -MAX_CELL_VOLTAGE = 3.450 +MAX_CELL_VOLTAGE = 3.650 [SERIAL] -SERIAL_INTERFACE = /tmp/vcom0 -# set SERIAL_BAUD_RATE to 9600 for the master-pack and 19200 for slaves -SERIAL_BAUD_RATE = 19200 +MASTER_SERIAL_INTERFACE = /tmp/vcom0 +SLAVES_SERIAL_INTERFACE = /tmp/vcom1 [LOGGING] # available modes are debug, error and info diff --git a/src/entrypoint.sh b/src/entrypoint.sh index 3f9781c..ef5a050 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -1,9 +1,17 @@ #!/bin/sh -# check if RS485_REMOTE_IP and RS485_REMOTE_PORT are set, start socat and then the script -if [ -n "$RS485_REMOTE_IP" ] && [ -n "$RS485_REMOTE_PORT" ]; then - socat pty,link=/tmp/vcom0,raw tcp:$RS485_REMOTE_IP:$RS485_REMOTE_PORT,retry,interval=.2,forever & python fetch_bms_data.py -# start only the script for locally connected RS485 devices -else - python fetch_bms_data.py +# start socat for master if RS485_MASTER_REMOTE_IP and RS485_MASTER_REMOTE_PORT are set +if [ -n "$RS485_MASTER_REMOTE_IP" ] && [ -n "$RS485_MASTER_REMOTE_PORT" ]; then + echo "starting socat for master rs485 vcom ${RS485_MASTER_REMOTE_IP}:${RS485_MASTER_REMOTE_PORT}" + socat pty,link=/tmp/vcom0,raw tcp:$RS485_MASTER_REMOTE_IP:$RS485_MASTER_REMOTE_PORT,retry,interval=.2,forever & fi + +# start socat for slaves if RS485_SLAVES_REMOTE_IP and RS485_SLAVES_REMOTE_PORT are set´ +if [ -n "$RS485_SLAVES_REMOTE_IP" ] && [ -n "$RS485_SLAVES_REMOTE_PORT" ]; then + echo "starting socat for slaves rs485 vcom ${RS485_SLAVES_REMOTE_IP}:${RS485_SLAVES_REMOTE_PORT}" + socat pty,link=/tmp/vcom1,raw tcp:$RS485_SLAVES_REMOTE_IP:$RS485_SLAVES_REMOTE_PORT,retry,interval=.2,forever & +fi + +# start the script +echo "start the script" +python fetch_bms_data.py \ No newline at end of file diff --git a/src/fetch_bms_data.py b/src/fetch_bms_data.py index f8df0bc..8ce8d94 100644 --- a/src/fetch_bms_data.py +++ b/src/fetch_bms_data.py @@ -3,6 +3,7 @@ import logging import configparser import time +from datetime import datetime import json import serial from serial.serialutil import SerialException @@ -44,11 +45,7 @@ def get_config_value(var_name, return_type=str) -> int | float | bool | str | No return None # BMS config - - # when ONLY_MASTER is True, data will only be fetched for one pack (0) - ONLY_MASTER = get_config_value("ONLY_MASTER", return_type=bool) - # when ONLY_MASTER is False, data will be fetched for NUMBER_OF_PACKS (1-n) - NUMBER_OF_PACKS = get_config_value("NUMBER_OF_PACKS", return_type=int) + # set min and max cell-voltage as this cannot be read from the BMS MIN_CELL_VOLTAGE = get_config_value("MIN_CELL_VOLTAGE", return_type=float) MAX_CELL_VOLTAGE = get_config_value("MAX_CELL_VOLTAGE", return_type=float) @@ -81,16 +78,21 @@ def get_config_value(var_name, return_type=str) -> int | float | bool | str | No mqtt_client.on_connect = logger.info("mqtt connected ({}:{}, user: {})".format(MQTT_HOST, MQTT_PORT, MQTT_USERNAME)) # Serial Interface config and setup (set to 9600 for Master and 19200 for Slaves) + + # fetch master, i.e. pack-0 when FETCH_MASTER == True + FETCH_MASTER = get_config_value("FETCH_MASTER", return_type=bool) + # fetch number of slave packs, i.e. number of packs excluding the master + NUMBER_OF_SLAVES = get_config_value("NUMBER_OF_SLAVES", return_type=int) + MASTER_SERIAL_INTERFACE = get_config_value("MASTER_SERIAL_INTERFACE") + SLAVES_SERIAL_INTERFACE = get_config_value("SLAVES_SERIAL_INTERFACE") - SERIAL_INTERFACE = get_config_value("SERIAL_INTERFACE") - SERIAL_BAUD_RATE = get_config_value("SERIAL_BAUD_RATE", return_type=int) - - serial_instance = None + serial_master_instance = None + serial_slaves_instance = None # Debug output of env-var settings - logger.debug(f"SERIAL_INTERFACE: {SERIAL_INTERFACE}") - logger.debug(f"SERIAL_BAUD_RATE: {SERIAL_BAUD_RATE}") + logger.debug(f"MASTER_SERIAL_INTERFACE: {MASTER_SERIAL_INTERFACE}") + logger.debug(f"SLAVES_SERIAL_INTERFACE: {SLAVES_SERIAL_INTERFACE}") logger.debug(f"MQTT_HOST: {MQTT_HOST}") logger.debug(f"MQTT_PORT: {MQTT_PORT}") @@ -99,8 +101,8 @@ def get_config_value(var_name, return_type=str) -> int | float | bool | str | No logger.debug(f"MQTT_TOPIC: {MQTT_TOPIC}") logger.debug(f"MQTT_UPDATE_INTERVAL: {MQTT_UPDATE_INTERVAL}") - logger.debug(f"ONLY_MASTER: {ONLY_MASTER}") - logger.debug(f"NUMBER_OF_PACKS: {NUMBER_OF_PACKS}") + logger.debug(f"FETCH_MASTER: {FETCH_MASTER}") + logger.debug(f"NUMBER_OF_SLAVES: {NUMBER_OF_SLAVES}") logger.debug(f"MIN_CELL_VOLTAGE: {MIN_CELL_VOLTAGE}") logger.debug(f"MAX_CELL_VOLTAGE: {MAX_CELL_VOLTAGE}") @@ -869,7 +871,7 @@ def decode_telemetry_feedback_frame(self, data) -> dict: return telemetry_feedback # read data for given battery_pack address from serial interface - def read_serial_data(self, serial_instance): + def read_serial_data(self, serial_master_instance): logger.info("Fetch data for Battery Pack {}".format(self.pack_address)) # json object to store status and alarm response values @@ -879,8 +881,8 @@ def read_serial_data(self, serial_instance): } # flush interface in- and output - serial_instance.flushOutput() - serial_instance.flushInput() + serial_master_instance.flushOutput() + serial_master_instance.flushInput() # calculate request telemetry command (0x42) for the current pack_address telemetry_command = self.encode_cmd(address=self.pack_address, cid2=0x42) @@ -891,10 +893,10 @@ def read_serial_data(self, serial_instance): while True: # (re-)send telemetry_command to the serial port until a response is received if telemetry_command_iteration == 1 or telemetry_command_iteration % 5 == 0: - serial_instance.write(telemetry_command) + serial_master_instance.write(telemetry_command) # set EOL to \r - raw_data = serial_instance.read_until(b'\r') + raw_data = serial_master_instance.read_until(b'\r') data = raw_data[13 : -5] # check if data is valid frame @@ -913,10 +915,10 @@ def read_serial_data(self, serial_instance): while True: # (re-)send telesignalization_command to the serial port until a response is received if telesignalization_command_iteration == 1 or telesignalization_command_iteration % 5 == 0: - serial_instance.write(telesignalization_command) + serial_master_instance.write(telesignalization_command) # set EOL to \r - raw_data = serial_instance.read_until(b'\r') + raw_data = serial_master_instance.read_until(b'\r') data = raw_data[13 : -5] # check if data is valid frame @@ -943,9 +945,11 @@ def read_serial_data(self, serial_instance): logger.error(f"MQTTException occurred: {e}") sys.exit(1) - # connect serial interface + # connect serial interfaces try: - serial_instance = serial.Serial(SERIAL_INTERFACE, SERIAL_BAUD_RATE) + if FETCH_MASTER == True: + serial_master_instance = serial.Serial(port=MASTER_SERIAL_INTERFACE, baudrate=9600) + serial_slaves_instance = serial.Serial(port=SLAVES_SERIAL_INTERFACE, baudrate=19200) except SerialException as e: logger.error(f"SerialException occurred: {e}") sys.exit(1) @@ -953,29 +957,37 @@ def read_serial_data(self, serial_instance): # array of battery-pack objects battery_packs = [] - # fill battery_packs array with one (ONLY_MASTER = true) or multiple pack(s) - if ONLY_MASTER: - battery_packs.append({"address": 0, "instance": SeplosBatteryPack(0)}) - else: - for i in range(1, NUMBER_OF_PACKS + 1): - address = int(f'0x{i:02x}', 16) - instance = SeplosBatteryPack(address) - battery_packs.append({"address": address, "instance": instance}) + # fill battery_packs array with master- and slave-packs + if FETCH_MASTER: + battery_packs.append({ "pack_instance": SeplosBatteryPack(0), "address": 0, "serial_instance": serial_master_instance }) + + for i in range(1, NUMBER_OF_SLAVES + 1): + address = int(f'0x{i:02x}', 16) + pack_instance = SeplosBatteryPack(address) + battery_packs.append({ "pack_instance": pack_instance, "address": address, "serial_instance": serial_slaves_instance }) # fetch battery-pack Telemetry and Telesignalization data i = 0 while True: - current_battery_pack = battery_packs[i]["instance"] - stats = current_battery_pack.read_serial_data(serial_instance) + current_battery_pack = battery_packs[i]["pack_instance"] + address = battery_packs[i]["address"] + serial_instance = battery_packs[i]["serial_instance"] + + # fetch battery_pack_data + battery_pack_data = current_battery_pack.read_serial_data(serial_instance) + + stats = { "last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S') } + + # if battery_pack_data has changed, update mqtt stats payload + if battery_pack_data: + stats.update(battery_pack_data) - if stats: - logger.info("Sending stats to MQTT") - topic = f"{MQTT_TOPIC}/pack-{i + 1 if not ONLY_MASTER else 0}/sensors" - mqtt_client.publish(topic, json.dumps(stats, indent=4)) - else: - logger.info("Stats have not changed. No update required.") + # send stats to mqtt + logger.info("Sending stats to MQTT") + topic = f"{MQTT_TOPIC}/pack-{address}/sensors" + mqtt_client.publish(topic, json.dumps(stats, indent=4)) - # query all packs again after defined time + # query all packs again in continuous loop or with pre-defined wait interval after each circular run i += 1 if i >= len(battery_packs): time.sleep(MQTT_UPDATE_INTERVAL) @@ -992,9 +1004,12 @@ def read_serial_data(self, serial_instance): mqtt_client.disconnect() mqtt_client.loop_stop() - # close serial connection if open - if serial_instance: - logger.info("Closing serial connection") - serial_instance.close() + # close serial connections if open + if serial_master_instance is not None: + logger.info("Closing serial connection to master") + serial_master_instance.close() + if serial_slaves_instance is not None: + logger.info("Closing serial connection to slaves") + serial_slaves_instance.close() logger.info("Exiting the program.") sys.exit(0)