1

I have a python script which, when a GPIO button is pressed, launches an arecord process. This records audio from a USB microphone (plughw:CARD=GS3,DEV=0) and pipes it to another program for modulation and eventually transmission via rpitx.

I spent a long time debugging the script from the terminal and have gotten it to work flawlessly. However, when I launch it from a systemd service (whether manually or on startup), the arecord process fails with the following error:

tx.py[9204]: arecord: main:828: audio open error: Device or resource busy

Again, this never occurs when I run the command manually from the CLI (as root or any other user). But, the goal is to have this script run at startup, as I will not be able to access the Pi with a mouse and keyboard. Here is my current code:

/etc/systemd/system/tx.service

[Unit]
Description="Service for detecting and starting RF transmissions"

[Service]
TimeoutStartSec=0
ExecStart=/home/pi/tx.py

[Install]
WantedBy=multi-user.target

/home/pi/tx.py

#!/usr/bin/python
import commands
import os.path
import RPi.GPIO as GPIO
import subprocess
import time

FREQ_PATH = "/home/pi/frequency.txt"
MODE_PATH = "/home/pi/mode.txt"
PS_DELAY = 0.25
RX_FLAG = "/home/pi/rx_flag"

def ConvertFreq(freq):
    return str(int(freq) / 1000)

def GetFileText(path):
    with open(path, "r") as file:
        return file.read().replace("\n", "")

def IsActive(name):
    output = commands.getoutput("systemctl is-active " + name)
    return output == "active"

def IsRunning(name):
    output = commands.getoutput("ps -a")
    return name in output

def main():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setwarnings(False)
    GPIO.setup(37, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    while True:
        if GPIO.input(37) == 0 and IsRunning("rpitx") == False:  # Button is pressed, so start transmitting
            print ""
            print "Starting transmission..."
            if IsActive("rx"):
                print "Stopping RX service..."
                subprocess.Popen("sudo systemctl stop rx; sudo touch " + RX_FLAG, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)  # The purpose of the last two arguments is to mute the subprocess output
            mode = GetFileText(MODE_PATH)  # Retrieve which mode to use
            freq = ConvertFreq(GetFileText(FREQ_PATH))  # Retrieve which frequency to use
            if mode == "am":
                subprocess.Popen("arecord -c1 -r 48000 -D plughw:CARD=GS3,DEV=0 -f S16_LE - | csdr convert_s16_f | csdr gain_ff 1.0 | csdr dsb_fc | csdr add_dcoffset_cc | rpitx -i- -m IQFLOAT -f " + freq, shell=True)  # PROBLEM OCCURS HERE! Didn't mute the output so we can debug
            print "Transmitting on " + freq + " kHz with mode " + mode.upper()
        elif GPIO.input(37) == 1 and IsRunning("rpitx"):  # Button no longer pressed, end transmission
            print "Ending transmission..."
            subprocess.Popen("sudo killall arecord", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            if os.path.isfile(RX_FLAG):
                print "Restarting RX service..."
                subprocess.Popen("sudo systemctl restart rx; sudo rm " + RX_FLAG, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            print "Transmission ended"
        time.sleep(PS_DELAY)
    GPIO.cleanup()
    quit()

if __name__ == "__main__":
    main()

Output of systemctl status tx.service

● tx.service - "Service for detecting and starting RF transmissions"
   Loaded: loaded (/etc/systemd/system/tx.service; disabled; vendor preset: enabled)
   Active: active (running) since Tue 2019-08-06 23:05:59 EDT; 8s ago
 Main PID: 9204 (tx.py)
    Tasks: 2 (limit: 2077)
   Memory: 3.6M
   CGroup: /system.slice/tx.service
           └─9204 /usr/bin/python /home/pi/tx.py

Aug 06 23:06:04 Jeep tx.py[9204]: End of file
Aug 06 23:06:05 Jeep tx.py[9204]: Warning : rpitx V2 is only to try to be compatible with version 1
Aug 06 23:06:05 Jeep tx.py[9204]: arecord: main:828: audio open error: Device or resource busy
Aug 06 23:06:05 Jeep tx.py[9204]: End of file
Aug 06 23:06:05 Jeep tx.py[9204]: arecord: main:828: audio open error: Device or resource busy
Aug 06 23:06:05 Jeep tx.py[9204]: Warning : rpitx V2 is only to try to be compatible with version 1
Aug 06 23:06:05 Jeep tx.py[9204]: End of file
Aug 06 23:06:05 Jeep tx.py[9204]: Warning : rpitx V2 is only to try to be compatible with version 1
Aug 06 23:06:05 Jeep tx.py[9204]: arecord: main:828: audio open error: Device or resource busy
Aug 06 23:06:05 Jeep tx.py[9204]: End of file

(The output repeats because rpitx never runs [a result of arecord not running]. Therefore, the script keeps running the same code.)

Thinking it was an issue with systemd, I tried launching it with an entry in rc.local, but the same behavior resulted. I have tried running the service with different users, groups, and by changing all commands to include sudo; also to no avail. Additionally, I checked to make sure no other processes were using the USB microphone at the time (and nothing was).

To me, this seems like an issue due to the environment the script is executed in (since it works from the CLI but not from systemd). Are there special privileges a systemd service needs to access the USB microphone? It is being run in a sandbox? Are multiple instances of the service running? Are any of the settings in systemctl show tx.service required for this to work? Is there anything blatantly obvious that I'm overlooking?

1 Answer 1

2

First of all, your script should exit with code 1 if it fails. So systemd will stop to execute the script and does not waste performance for nothing.

You are right with you assumption that you do not match the environment to run the script. You have to know that systemd does not provide an environment by default. That is by definition to always have a defined startup. Therefore are extended options to define an environment. Look at man systemd.exec. So you Unit file is by far to simple.

You have to examine what services the script needs to run properly. Now it will start as soon as possible, just one of the very first services. Of course then it will not find the services running it needs. You have to define to start it After=needed_other.service.

Here is a suggestion of a Unit file to start with:

[Unit]
Description="Service for detecting and starting RF transmissions"
After=multi-user.target

[Service]
User=pi
WorkingDirectory=~
#ExecStartPre=/usr/bin/env
ExecStart=/home/pi/tx.py

[Install]
WantedBy=multi-user.target

For setting environment variables you can use Environment= or EnvironmentFile=. For debugging you can uncomment #ExecStartPre=/usr/bin/env to show what environment the script has. Its output you will find in the journal with:

rpi ~$ journalctl -b -e
2
  • Thanks for the quick response. I played around with it and tested your suggestions for most of today, but I could not get the script to work as I posted it. After digging through the service logs, though, I found that the first call to arecord was successful and did not get the error (it just got buried in the logs). The subsequent problem was that it takes a few seconds for the rpitx process to start, so the program continued to spawn more arecord processes which failed when trying to access the mic. ... Commented Aug 7, 2019 at 21:19
  • ... Instead of checking if the rpitx process is running, I changed the script to have a boolean flag that indicates it so there is no delay/waiting for processes. I will still leave the question open, though, because we do not know exactly why the old script worked fine in the CLI and not systemd. Commented Aug 7, 2019 at 21:22

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.