I have a microcontroller running Linux distro based on Debian.
I have a Nextion screen connected to it via UART on /dev/ttyMOD1 port, I have a script which uses stty and gets data from screen using cat and outputs it to mqtt topics. When I run script from shell itself or if I restart service after system has booted and I'm able to connect via ssh it works perfectly, but when system starts up, cat task inside of service doesn't work for some reason, only after service has been restarted.
Script and service files have +x permissions for user. I need help to fix cat not working on startup.
Text for script and service:
serial.service
[Unit]
Description=Serial script on startup
Requires=wb-mqtt-serial.service
After=wb-mqtt-serial.service
[Service]
User=root
WorkingDirectory=/mnt/data/root
ExecStart=/mnt/data/root/serial.sh
Restart=on-failure
Type=simple
[Install]
WantedBy=multi-user.target
serial.sh
#!/bin/bash
mqtt-delete-retained "/devices/screen/#"
stty -F /dev/ttyMOD1 ospeed 9600 ispeed 9600 raw clocal -parenb -echo cs8
CR="$(echo -e '\r')"
exec 4<> /dev/ttyMOD1
/bin/cat <&4 | while :
do
IFS="$CR" read -r line
case "$line" in quit*)
break
;;
*)
if [[ -n "$line" ]]; then
mosquitto_pub -t /devices/screen/controls/raw/meta/type -r -m text
mosquitto_pub -t /devices/screen/controls/raw -r -m "$line"
fi
;;
esac
done
The most obvious way to get your problem is if the service tries to start up before ttyMOD1
is usable; this would cause cat
to fail, but because your loop keeps going even if read
fails, the process on the right-hand side of the pipe operator doesn't know that cat
failed and continues trying to run, preventing the Restart=on-failure
directive in your service configuration from having any effect.
You can add some extra dependencies and ordering directives to your systemd service definition:
[Unit]
BindsTo=dev-ttyMOD1.device
Requires=mnt-data.mount
After=dev-ttyMOD1.device
After=mnt-data.mount
...to tell systemd to start it later, at a point when it's more likely to succeed (you may also want After=network-online.target
if the mqtt commands won't work without networking). (The BindTo=
tells systemd that if the device goes away and comes back, your service will need to be restarted).
To ensure that your system can recover if the script is run before dependencies are met, consider:
#!/usr/bin/env bash
die() { rc=$?; echo "$*" >&2; exit $(( rc ? rc : 1 )); }
mqtt-delete-retained "/devices/screen/#"
exec 4<>/dev/ttyMOD1 || die "could not open serial device"
stty ospeed 9600 ispeed 9600 raw clocal -parenb -echo cs8 <&4 \
|| die "could not configure serial device"
while IFS=$'\r' read -r line <&4; do
case "$line" in
quit*)
break
;;
*)
if [[ -n "$line" ]]; then
mosquitto_pub -t /devices/screen/controls/raw/meta/type -r -m text
mosquitto_pub -t /devices/screen/controls/raw -r -m "$line"
fi
;;
esac
done
Note:
read
fails -- that way systemd can restart the service, instead of leaving it dead-but-failing if the device isn't opened, has its connection lost (or, in the original code, if cat
exited for any reason).stty
or the attempt to open the TTY fails; again, this prevents the script getting stuck in a broken-but-still-running state.cat
at all here, but instead read
ing direct from the device.