2

I managed to create a Python script to accept magnetic card swipes from a USB-based reader. It seems to work as expected. Part of the routine is a curl command to push the data to another server. Since the Ubuntu server won't have a monitor/keyboard/mouse my only way of responding to the user is with sound from the server's speaker.

Usually, the curl command gets a standard 200 response from the other application server. Sometimes, however, it gets a 500 error response and I need to communicate something back to the user to alert them that "this time didn't work, please scan your card again".

A simple mechanism is to beep once for success and nothing for failure. As written and run from a terminal, it works. As scheduled from rc.local, the system beep doesn't sound.

...
args = 'card=' + trackone.group(2)
r = requests.get('http://apiserver/api/', args)
if r.status_code == 200:
    # First attempt which doesn't work from rc.local
    # print("\a")
    # Second attempt, wrap the beep in a shell script
    os.system("sh /home/myuser/beep.sh")
else
    print(r.status_code)

And I attempted to create a simple shell script beep.sh:

#!/bin/bash
echo "\a"

As mentioned, both attempts work from the terminal but not as run from root under rc.local control. And I know that the API is being called since I can see the log entries.

The line in rc.local looks like this:

python '/home/myuser/scancards.py'

Is there a way to allow this command--as run within the init.d process--to be able to beep the speaker?

4 Answers4

2

Install beep with sudo apt install beep (use apt-get instead of apt for earlier versions than 16.04)

Since beep supports different frequencies you could simply beep -f "$r.statuscode" or beep once for yesbeep -r1and twice for nobeep -r2`.

If for some unknown reason you are unable to get more than one beep you can utilize the -f switch to adjust the frequency to get for instance a lower tone for no and a higher tone for yes.

Examples:

No: beep -f 250

Yes: beep -f 2500

Yes, I know it's a short answer, but sometimes that's all that is required.

further research indicates that you will have to comment out blacklist pcspkr in /etc/modprobe.d/blacklist.conf to get this to work. you may also have to set the suid bit on beep to get this to work (I used sudo chmod 4755 /usr/bin/beep as I don't have concerns about others playing with beep on this system) you may wish to use groups to adjust permissions so that only worthy users can execute beep.

Note: As @JdeBP points out in his answer you may have to open a file descriptor to a terminal device

Sources: man beep

Getting the PC speaker to beep

http://pubs.opengroup.org/onlinepubs/9699919799/

Elder Geek
  • 36,023
  • 25
  • 98
  • 183
  • My hunch is it will only beep in terminal mode like the echo '\a' he is using does now. But it doesn't hurt to try and I've been wrong more times than I care to remember. – WinEunuuchs2Unix Oct 12 '16 at 22:06
  • My dell laptop had an annoying beep after upgrade to Ubuntu 16.04 even though pcspkr has been blacklisted for a decade: [http://askubuntu.com/questions/829258/turn-off-motherboard-pc-speaker-beep-in-ubuntu-16-04-regression]. My first guess for OP was pcspkr was blacklisted until he said it worked in Terminal but not in rc.local. And no you can't have my 2012 Dell Laptop :D – WinEunuuchs2Unix Oct 12 '16 at 23:28
  • Thanks (upticked and marked as answer). I'd already installed beep but that didn't work—it was the blacklist and the file attributes, presumably, plus a reboot that allows this to sound the speaker now. – Justin Case Oct 17 '16 at 16:17
  • I'm glad you got it working! ;-) – Elder Geek Oct 17 '16 at 16:49
  • Interestingly-enough, it only ever produces a single default beep regardless of what interesting arguments or number of repetitions I try. I'm going to just run with the single-beep solution for now and I'll return to the nice-to-have version later when this is in production. Thanks. – Justin Case Oct 17 '16 at 16:51
  • Insure that there is no space between -r and the number of beeps you desire. Thats the likely culprit. :-) – Elder Geek Oct 17 '16 at 19:54
1

as run within the init.d process

There's no such thing as an "init.d process".

rc.local is part of an rc system that has been superseded three times. That system has been superseded by van Smoorenburg rc, upstart (a decade ago), and (as of Ubuntu version 15) systemd. What you're using is a third-iteration backwards compatibility shim.

On systemd operating systems such as Ubuntu versions 15 and later, the shim is a systemd service, named rc-local.service. You can find out its service definition with

systemctl cat rc-local.service

As you can see, it is not defined as attaching the service to a terminal device. The service process is not run with a controlling terminal, and its standard input and output are not connected to a terminal.

That python code and shell script do not "beep the PC speaker". They write character #7 to their standard outputs. It just so happens when you are running them interactively in your login session that the device that is their standard output interprets character #7 as an instruction to make a noise. Redirect the interactive command's standard output to /dev/null and observe how silent the code becomes.

Which is why the fact that the rc.local (very) backwards compatibility shim has no connection to a terminal device is important.

You can solve this with the beep utility. That attempts (if invoked appropriately) to explicitly open a file descriptor to a terminal device, and send character #7 to (or use console or evdev ioctl()s on) that device rather than simply assuming that standard output is a terminal device.

But it's worth thinking hard about not using rc.local, too.

Further reading

JdeBP
  • 3,959
  • Thanks for the informative answer (upticked as noob). I'd installed beep earlier but couldn't get that to work. (See my remaining comments on this thread.) – Justin Case Oct 17 '16 at 16:15
0

To beep the pc speaker from /etc/rc.local Python script via Linux console, you could use console_ioctl(4): KDMKTONE, KIOCSOUND:

#!/usr/bin/env python
import os    
from fcntl import ioctl

CLOCK_TICK_RATE = 1193180 # magic https://github.com/johnath/beep/blob/0d790fa45777896749a885c3b93b2c1476d59f20/beep.c#L31-L49
KDMKTONE = 0x4B30   # generate tone include/uapi/linux/kd.h#L25

def beep(console_fd, frequency=440, length_millis=200):
    period = CLOCK_TICK_RATE // frequency
    ioctl(console_fd, KDMKTONE, (length_millis << 16) | period) # start beeping
    # return immediately

beep(console_fd=os.open('/dev/tty0', os.O_RDONLY | os.O_NOCTTY)) # I'm [G]root

See beep.py. It works because /etc/rc.local is executed by root. You can run it as an ordinary user if you own the terminal e.g., in Ctrl+Alt+F1 virtual console (press Alt+F7 to switch back into your GUI window manager).

Make sure that pcspkr kernel module is not blacklisted (comment out the default):

$ sudo sed -i 's/^blacklist pcspkr/#blacklist pcspkr/' /etc/modprobe.d/blacklist.conf

Make sure that it can be loaded without any errors:

$ sudo modprobe pcspkr

Make sure /etc/rc.local is executable:

$ sudo chmod +x /etc/rc.local

print('\a') (writing U+0007 BELL* character to stdout) leads to different things depending on your environment.

It may play bell.ogg sample uploaded using the pactl command (for PulseAudio sound server):

$ pactl upload-sample /usr/share/sounds/ubuntu/stereo/bell.ogg bell.ogg

It is likely that PulseAudio does not use the pc speaker to play the sound.


On my Ubuntu 16.04 system, print('\a') from /etc/rc.local writes #007 to /var/log/syslog and beeps the pc speaker (systemctl cat rc-local.service shows StandardOutput=journal+console i.e., the output goes to the log file and the console).

jfs
  • 4,008
  • Good answer (upticked as noob). I installed pulseaudio-utils since I thought it has potential but given that this is Ubuntu server there don't appear to be any ogg files on the system as you might have with a desktop install. I'll chase down some of those and see if this works on a later version of the project. Thanks. – Justin Case Oct 17 '16 at 16:24
  • @JustinCase to be clear, the Python code (at the top of the answer) does not require to install anything (except python itself). – jfs Oct 17 '16 at 16:29
  • Noted. I created a beep.py and from the console and from the script, they work as expected. I tried bringing the code into my script and it works. I attempted to repeat the beep() command three times and it only beeps a single time. Note that all of my (somewhat success) attempts with all these suggestions are producing only a single/default beep. I think I should just run with that and let it go. – Justin Case Oct 17 '16 at 16:49
  • @JustinCase: you should probably ask a separated question about repeating the beep specifically because It works for me: I've just added a loop for _ in range(5): \n time.sleep(1) \n beep(...) and it beeps 5 times with 1 second interval. – jfs Oct 17 '16 at 22:37
0

To beep the pc speaker from a Python script, you could use Linux evdev API:

#!/usr/bin/env python
import ctypes
import math
import os
import time


EV_SND = 0x12  # linux/input-event-codes.h
SND_TONE = 0x2  # ditto
time_t = suseconds_t = ctypes.c_long

class Timeval(ctypes.Structure):
    _fields_ = [('tv_sec', time_t),       # seconds
                ('tv_usec', suseconds_t)] # microseconds

class InputEvent(ctypes.Structure):
    _fields_ = [('time', Timeval),
                ('type', ctypes.c_uint16),
                ('code', ctypes.c_uint16),
                ('value', ctypes.c_int32)]


frequency = 440  # Hz, A440 in ISO 16
device = "/dev/input/by-path/platform-pcspkr-event-spkr"
pcspkr_fd = os.open(device, os.O_WRONLY)  # root! + modprobe pcspkr
fsec, sec = math.modf(time.time())  # current time
ev = InputEvent(time=Timeval(tv_sec=int(sec), tv_usec=int(fsec * 1000000)),
                type=EV_SND,
                code=SND_TONE,
                value=frequency)
os.write(pcspkr_fd, ev)  # start beep
try:
    time.sleep(0.2)  # 200 milliseconds
finally:
    ev.value = 0  # stop
    os.write(pcspkr_fd, ev)

Run beep-evdev.py as a root. /etc/rc.local is being run by root and therefore the script should work as is.

If file /dev/input/by-path/platform-pcspkr-event-spkr doesn't exists, make sure that pcspkr module is loaded:

root# modprobe pcspkr

The script has no other dependencies except for python itself.

jfs
  • 4,008