2

I wrote a command in swift that uses the internal bluetooth low energy module of my Raspberry PI 4. This command must be launched as superuser.
Everything works perfectly if I launch the executable from the terminal, but I'm struggling to make it launch automatically after the system startup.
I'm trying to use the systemd, following this guide . I've created my punch.service inside the /lib/systemd/system/ and followed the step to enable it. The software is running after startup and I can tell it because I'm also using the GPIO to command some leds, but the raspberry doesn't seems to advertise and is not available to accept connections.
If I kill the process and launch the executable again it starts to advertise too. My service file looks like this:

[Unit]
Description=Punch service
After=bluetooth.target
After=multi-user.target

[Service]
ExecStart=/home/pi/Desktop/RaspberryBLE/PunchBLE/.build/release/RaspberryBLE

[Install]
WantedBy=bluetooth.target
WantedBy=multi-user.target

I suppose that this will tell the system to launch the executable after bluetooth service are up and running, but it doesn't seems so.
Doesn't matter if startup the raspberry on desktop or CLI with autologin. Am I missing something? Is there a way to make it sure that my service is launched as last?

1
  • 1
    BTW -- I know you already have an accepted answer, so this is more of an "in future" -- you can get some information about what happened with a service via systemctl status [service name sans '.service'].
    – goldilocks
    Oct 25, 2022 at 16:40

2 Answers 2

2

systemd will give you some flexibility, but if you want to get something running right away - while you fiddle with systemd - you might consider a cron job. Here's a simple approoach:

  1. As user pi (or any user with sudo privileges, edit the root crontab as follows:
$ sudo crontab -e
  1. Once the root crontab is open in your editor, add a line similar to this:
@reboot sleep 30; /path/to/your/executable >> /home/pi/mybtcronlog.txt 2>&1
  1. Reboot to test

How it works:

  1. Any job started in the root crontab will run as the superuser (root) - (Hint: Don't ever use sudo in a cron job.)

  2. The crontab is simply the schedule table used by cron to start jobs. See man crontab for syntax & crontab guru if you need help with the schedule expression. @reboot is a special schedule, so you won't likely need any help with that.

The crontab entry shown above tells cron to run two commands sequentially:

  • sleep for 30 seconds, and then
  • run your executable bluetooth app. - give it the full path to the exe

Unlike systemd, cron has no knowledge of the status of system servicesNOTE required in your program. We compensate for that by sleeping for 30 seconds to give the system time to get BT started. Nothing magic about 30 secs - it may take 5 seconds, or it may take 2 minutes - depending on your system.

  • >> is a redirect meaning that any text output will go to the designated file: /home/pi/mybtcronlog.txt
  • 2>&1 redirects stderr to stdout - both streams will now appear in your log file, giving you some feedback in the event something goes wrong.
  1. Run sudo reboot to restart your system & test your cron job.

NOTE: In the paradox that is systemd design logic, the cron daemon is actually started/controlled by a systemd unit. It therefore seems odd that the systemd designers did not provide a method to allow cron users to communicate their job's service dependencies. systemd is useful in many respects, but it can become rather complex for what would seem to be simple tasks; e.g. why can't systemd start cron as the last service during startup?

13
  • 1
    Thank you for the explanation, I've tried and just by putting 60 seconds I was able to make it work!!! Weird stuff about systemd :-(
    – Andrea
    Oct 25, 2022 at 5:30
  • @Andrea: Yes, it's rather deep. I don't understand it well enough to explain why, but I've come to believe that designs that yield a difficult user experience are not great designs. But I suppose it's as good as we're going to get for a while.
    – Seamus
    Oct 25, 2022 at 6:32
  • "In the paradox that is systemd design logic, the cron daemon is actually started/controlled by a systemd unit: -> Yes, but there's nothing paradoxical about it because: 1) Processes cannot start themselves, 2) The kernel only starts 1 process, init, which on contemporary GNU/Linux systems is impemented by systemd. Cron runs like any other kind of background service, that's what init is for (hence it is the common ancestor of every running process, pid 1). If it wasn't started by init/systemd, it would have to be started by something started by systemd.
    – goldilocks
    Oct 25, 2022 at 13:37
  • "It therefore seems odd that the systemd designers did not provide a method to allow cron users to communicate their job's service dependencies." -> It does, to the same extent that it does for other services. Of course, that is more about the cron daemon itself rather than anything it starts on its own -- which cron could have an interface like systemd to organize tasks in a hierarchy of dependencies (or it could query systemd about service states), but many people would be upset if it suddenly sprouted features of that sort.
    – goldilocks
    Oct 25, 2022 at 13:48
  • So (and I am not knocking your answer, just trying to defang some confusion about OS fundamentals), what's "paradoxical" ;) ;) is to say, "I would like a dependency based boot service like systemd, but I do not want to use systemd". Kind of like saying you don't want to use Chrome as a web browser, but you then gripe about how all the other web browsers don't do the things Chrome does. A bad analogy in the sense that cron and systemd are much more dissimilar than a set of web browsers -- but note there are alternate init systems available to use (please don't act on that tho, lol).
    – goldilocks
    Oct 25, 2022 at 13:53
2

Can't promise this will solve your problem, but...

I suppose that this will tell the system to launch the executable after bluetooth service are up and running, but it doesn't seems so

Something to keep in mind with system services is that it is very easy for the system to determine and depend upon when a service starts, but that is very narrowly defined in the sense that it will often be distinct from the not as simple and objectively definable "up and running" -- which may be easy to define in words for a specific service, but there are no checkpoints of that sort in general (there are some good reasons for this but I'll refrain from the tangent).

The take-away is that, in general, all the various depends/wants etc. are based on when processes start. With the systemd "targets", which group services, the relationship between core services in a target does provide a bit of checkpoint-esque ordering refinement, but even without the added complication of stage 1, 2, 3 etc for every process this can be a bit delicate.

So, this seems to me wrong-headed:

After=bluetooth.target
After=multi-user.target
[...]
WantedBy=bluetooth.target
WantedBy=multi-user.target

As per man systemd.unit:

It is a common pattern to include a unit name in both the After= and Wants= options, in which case the unit listed will be started before the unit that is configured with these options.

But, as per the Forward and reverse unit properties table in that man page, Wants is the reverse of WantedBy. So you are sort of saying that this service should start after bluetooth.target (and multi-user.target), but that this service should start when those targets start. It creates a sort of circular or non-sensical dependency.

WRT WantedBy, this is just what indicates where and if the service should be included when you enable it. The simplest and most straight-forward way to use it is to just use default.target. This means it will be included in the list of thing to start without nailing it down too specifically -- that can be fine tuned with dependencies on more specialized units/targets.

Obviously you need bluetooth working, and if you read through the bit on Wants=, Requires=, and After, something more appropriate might be:

[Unit]
Description=Punch service
Requires=bluetooth.service
After=bluetooth.service

[Service]
ExecStart=/home/pi/Desktop/RaspberryBLE/PunchBLE/.build/release/RaspberryBLE

[Install]
WantedBy=default.target

Notice I've used bluetooth.service there, not bluetooth.target. This is because if you look, by default the bluetooth.target only includes one service, bluetooth.service, and since that is an actual service that starts a real executable, it is likely to be a more specific relation.

1
  • thank you for your amazing explanation that shed some light about systemd, but unfortunately didn't solve the issue
    – Andrea
    Oct 25, 2022 at 5:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.