Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve resiliency of the code #7

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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