Skip to content

Commit

Permalink
Merge pull request #7 from jwillmer/main
Browse files Browse the repository at this point in the history
Improve resiliency of the code
  • Loading branch information
Privatecoder authored Jun 6, 2024
2 parents d08d3e4 + b62fd5d commit 38aaf5f
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
41 changes: 29 additions & 12 deletions src/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,48 @@
# 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 $!
}

# 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 $!
# Wait for any process to exit
wait $!
69 changes: 40 additions & 29 deletions src/fetch_bms_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 38aaf5f

Please sign in to comment.