diff --git a/Dockerfile b/Dockerfile index 3df60c1..ed1cf6d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.12-alpine # Install socat -RUN apk add --no-cache socat +RUN apk add --no-cache socat nano # Set the working directory in the container WORKDIR /usr/src/app diff --git a/src/entrypoint.sh b/src/entrypoint.sh index bbee83c..5f17177 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -3,7 +3,7 @@ # Function to handle SIGTERM cleanup() { echo "INFO:SeplosBMS:Container stopped, cleaning up and exiting..." - # Send SIGTERM to the Python script + pkill socat kill -s SIGTERM $! wait $! } @@ -11,23 +11,40 @@ cleanup() { # Trap SIGTERM and call cleanup function trap cleanup SIGTERM -# start socat for master if RS485_MASTER_REMOTE_IP and RS485_MASTER_REMOTE_PORT are set +start_socat() { + local ip=$1 + local port=$2 + local device=$3 + echo "Starting socat for device $device at ${ip}:${port}" + socat pty,link=$device,raw tcp:$ip:$port,retry,interval=.2,forever & + socat_pid=$! + sleep 2 # Give socat time to establish the connection + if ! kill -0 $socat_pid 2>/dev/null; then + echo "ERROR: Failed to start socat for device $device" + return 1 + fi + echo "socat started successfully for device $device" +} + +# Start socat for master and slaves 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 & + start_socat "$RS485_MASTER_REMOTE_IP" "$RS485_MASTER_REMOTE_PORT" "/tmp/vcom0" || { + echo "Failed to start socat for master" + exit 1 + } 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 & + start_socat "$RS485_SLAVES_REMOTE_IP" "$RS485_SLAVES_REMOTE_PORT" "/tmp/vcom1" || { + echo "Failed to start socat for slaves" + exit 1 + } fi -# start the script -echo "start the script" +echo "Starting the script" -# run the script in background +# Run the Python script in the background python fetch_bms_data.py > /proc/1/fd/1 2> /proc/1/fd/2 & -# wait for the script to exit -wait $! \ No newline at end of file +# Wait for any process to exit +wait $! diff --git a/src/fetch_bms_data.py b/src/fetch_bms_data.py index e226dcc..e3d96bc 100644 --- a/src/fetch_bms_data.py +++ b/src/fetch_bms_data.py @@ -87,7 +87,7 @@ def get_config_value(var_name, return_type=str) -> int | float | bool | str | No # Logging setup and config - logging.basicConfig() + logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO) logger = logging.getLogger("SeplosBMS") if get_config_value("LOGGING_LEVEL").upper() == "ERROR": @@ -108,12 +108,19 @@ def get_config_value(var_name, return_type=str) -> int | float | bool | str | No MQTT_TOPIC = get_config_value("MQTT_TOPIC") MQTT_UPDATE_INTERVAL = get_config_value("MQTT_UPDATE_INTERVAL", return_type=int) + def on_mqtt_connect(client, userdata, flags, rc): + if rc == 0: + logger.info("Connected to MQTT (%s:%s, user: %s)", MQTT_HOST, MQTT_PORT, MQTT_USERNAME) + else: + logger.error("Failed to connect to MQTT Broker (%s:%s, user: %s): %s ", MQTT_HOST, MQTT_PORT, MQTT_USERNAME, rc) + + def on_mqtt_message(client, userdata, msg): + logger.info(f"MQTT message received: {msg.topic} {msg.payload}") + mqtt_client = mqtt.Client() mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) - mqtt_client.on_connect = logger.info( - "mqtt connected (%s:%s, user: %s)", - MQTT_HOST, MQTT_PORT, MQTT_USERNAME - ) + mqtt_client.on_connect = on_mqtt_connect + mqtt_client.on_message = on_mqtt_message # Home Assistant auto-discovery config @@ -1121,30 +1128,34 @@ def read_serial_data(self): # fetch battery-pack Telemetry and Telesignalization data i = 0 while True: - current_battery_pack = battery_packs[i]["pack_instance"] - current_address = battery_packs[i]["address"] - - # fetch battery_pack_data - current_battery_pack_data = current_battery_pack.read_serial_data() - - # if battery_pack_data has changed, update mqtt stats payload - if current_battery_pack_data: - logger.info("Pack%s:Sending updated stats to mqtt.", current_address) - mqtt_client.publish(f"{MQTT_TOPIC}/pack-{current_address}/sensors", json.dumps({ - **current_battery_pack_data, - "last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }, indent=4)) - else: - logger.info("Pack-%s:Data not changed, skipping mqtt update.", current_address) - - logger.info("Sending online status to mqtt") - mqtt_client.publish(f"{MQTT_TOPIC}/availability", "online", retain=False) - - # 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) - i = 0 + try: + current_battery_pack = battery_packs[i]["pack_instance"] + current_address = battery_packs[i]["address"] + + # fetch battery_pack_data + current_battery_pack_data = current_battery_pack.read_serial_data() + + # if battery_pack_data has changed, update mqtt stats payload + if current_battery_pack_data: + logger.info("Pack%s:Sending updated stats to mqtt.", current_address) + mqtt_client.publish(f"{MQTT_TOPIC}/pack-{current_address}/sensors", json.dumps({ + **current_battery_pack_data, + "last_update": datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }, indent=4)) + else: + logger.info("Pack-%s:Data not changed, skipping mqtt update.", current_address) + + logger.info("Sending online status to mqtt") + mqtt_client.publish(f"{MQTT_TOPIC}/availability", "online", retain=False) + + # 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) + i = 0 + except Exception as e: + logger.error(f"Error in main loop: {e}") + time.sleep(10) # catch exceptions related to the initial connection to the serial port except serial.SerialException as e: