linuxbashsystemd

systemd service running shell script doesn't run cat task on startup


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

Solution

  • 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: