Привязка systemd сервиса к устройству

В одном из постов про поворотное устройство я вскользь упомянул настройку rotctrld. rotctrld - это небольшой демон, который открывает TCP порт и позволяет отправлять команды в tty-устройство. Я написал небольшой systemd конфиг, чтобы демон стартовал и рестартовал во время загрузки raspberrypi.

[Unit]
Description=rotctrld Service

[Service]
WorkingDirectory=/home/pi/r2cloud/
ExecStart=rotctld --model=1401 --port=4533 --listen-addr=127.0.0.1 --rot-file=/dev/ttyUSB0
Restart=always
User=pi
Group=pi

[Install]
WantedBy=multi-user.target

Однако, со временем стали всплывать достаточно неприятные моменты в обслуживании. Например, поворотное устройство иногда определялось как /dev/ttyUSB1. Тогда приходилось либо перетыкать его в другой порт, либо логиниться на устройство, менять конфиг systemd и рестартовать.

Или вот ещё один пример, при старте системы устройство не подключено. systemd пытается стартовать демон несколько раз и, несмотря на Restart=always, отчаивается. Даже если я подключил устройство позднее, демон уже может не работать. Чтобы это решить, я сначала подключал устройство, а затем стартовал raspberrypi.

Это всё крайне неудобно, поэтому я решил разобраться с этим.

UDEV

Для того чтобы устройство определялось под одним и тем же именем, нужно привязать идентификатор USB устройства к названию девайса.

Идентификатор USB устройства можно получить с помощью команды lsusb:

pi@rasp-bullseye:~ $ lsusb
Bus 001 Device 004: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port / Mobile Action MA-8910P
Bus 001 Device 003: ID 0424:ec00 Microchip Technology, Inc. (formerly SMSC) SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Microchip Technology, Inc. (formerly SMSC) SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Поворотное устройство определяется как Prolific Technology, Inc. PL2303 Serial Port с идентификатором 067b:2303.

Далее необходимо создать конфиг udev /etc/udev/rules.d/99-usb-rotator.rules, который содержит следующее:

SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="ttyRotator", TAG+="systemd", ENV{SYSTEMD_WANTS}="rotctrld.service"

В нём написано, что нужно создать symlink /dev/ttyRotator для устройства 067b:2303. Ещё в этом правиле написано стартовать rotctrld.service, при появлении устройства. Крайне полезная вещь!

systemd

Последним штрихом будет остановка сервиса, если устройство отключено. Для этого необходимо добавить следующую строчку в rotctrld сервис:

[unit]
...
StopWhenUnneeded=true

А ещё необходимо переключить сервис на использование симлинка:

ExecStart=rotctld --model=1401 --port=4533 --listen-addr=127.0.0.1 --rot-file=/dev/ttyRotator

Результаты

В результате при подключении поворотного устройства в логах будет следующее:

May 21 21:36:29 rasp-bullseye kernel: pl2303 1-1.2:1.0: pl2303 converter detected
May 21 21:36:29 rasp-bullseye kernel: usb 1-1.2: pl2303 converter now attached to ttyUSB0
May 21 21:36:29 rasp-bullseye mtp-probe[849]: checking bus 1, device 6: "/sys/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2"
May 21 21:36:29 rasp-bullseye mtp-probe[849]: bus: 1, device: 6 was not an MTP device
May 21 21:36:29 rasp-bullseye systemd[1]: Started rotctrld Service.

А при отключении:

May 21 21:36:04 rasp-bullseye kernel: usb 1-1.2: USB disconnect, device number 5
May 21 21:36:04 rasp-bullseye kernel: pl2303 ttyUSB1: pl2303 converter now disconnected from ttyUSB1
May 21 21:36:04 rasp-bullseye kernel: pl2303 1-1.2:1.0: device disconnected
May 21 21:36:04 rasp-bullseye systemd[1]: Stopping rotctrld Service...
May 21 21:36:04 rasp-bullseye systemd[1]: rotctrld.service: Succeeded.
May 21 21:36:04 rasp-bullseye systemd[1]: Stopped rotctrld Service.

Казалось бы, небольшие изменения в конфигурации, но насколько они делают жизнь проще! А ещё я крайне удивлён мощью systemd. Несмотря на всю критику, он позволяет сделать подобную функциональность всего с помощью пары строчек в конфигурации.