rcmpd - Controlling MPD (Music Player Daemon) with an IR Remote Control

Table of contents

Introduction

I'm using a headless Raspberry Pi as audio player. It is connected to my ancient ;-) Denon amplifier via analog cinch cables.

The operating system on the Raspberry Pi is a stock Raspberry Pi OS Buster. Up to 2018 I used to use a specialized operating system image (Volumio) for that but no longer.

Under the hood there is the MPD (Music Player Daemon) working.

There are a lot of MPD client programs out there, I like ncmpcpp (CLI) and Cantata (GUI) the most these days.

But in this case I wanted to control my music player without an additional computer, smartphone or such.
Just a plain old infrared remote control (RC) was my goal - with as many features as possible.

Searching the web I didn't find a client matching my ideas.

So I created one by myself - and rcmpd was born.

rcmpd is written as a bash script with very little requirements and should work on most Linuxes.
(For now I've tested it on the above mentioned Raspberry Pi OS only.)

I used bash as programming language to make it as easy as possible for others to understand and modify rcmpd by themselves.

Features

Standard functions

Of course there are the usual, obvious commands:

  • Power (shutdown)
  • Mute
  • Volume+/-
  • toggle repeat and random
  • Pause
  • Play/Stop
  • Next/Previous track
  • 1st .. 10th track

And a not so obvious:

  • do something with the current songs title/artist

    Currently it gets saved in a file (appended, to be precise).
    That is intended to remember a nice song for later examination,
    especially useful when listening to radio streams.

    Of course there are more actions thinkable, e.g. mail it somewhere ...

But there is more! Please read on.

Speech output

Inspired by a maker's discussion about a "very simple Raspberry Pi Radio"
I implemented Text-to-speech outputs to get a more comfortable user experience.

There are some informations spoken and it's possible to get some commands "echoed" in words.

I'm using espeak (http://espeak.sourceforge.net) for text-to-speech, but it would be possible to use other software too.
Google's API has much better sound quality, but I wanted to have it working offline.
Both ways are now included and can be switched by setting variable tts_type.
  • speak info: current time
  • speak info: collection (available playlists)
  • speak info: volume and state of "repeat"
  • speak info: playlist
  • speak info: channel/album
  • speak info: current track/song
  • toggle audible commands on/off

Simple Playlist features

Additionally there are the following playlist features:

  • next/previous playlist
  • load playlist "A", "B", "C"
  • skip through all available playlists with the buttons

Compound keys

All of the above functions are reachable with a single button press.

To extend the possibilities I implemented compound keys.

  1. They are started and stopped with the OK button.
  2. Between those two OK's there are the letters A, B, C and D, the digits 0-9 and some more keys allowed.
  3. Every key needs to follow the one before within 5 seconds.

Enhanced Playlist features

  • If a playlist has more than ten entries (reachable via buttons 1-9,0) it's possible to select higher ones with compound keys.

    E.g. entry 27 could be started with:

    OK 2 7 OK
    
  • You can load more playlists directly by combining the letters A, B, C, D with the digits to playlists like "A7", "AC1" or "B126".

  • The list of defined/available playlists and the contents of the current one could be printed on a printer with:

    OK MENU EPG OK
    

    More about printing see below.

Playlist preparation

To make the direct loading of playlists work, the playlists in question have to be prepared a bit:

Using a MPD client on my PC I'm predefining MPD playlists which I would like to manage with the RC with a special naming convention.

Example:

A ~ Various Artists - The ultimate songs
B
B1 -- Angel Hearts - The first album
C24 ~ Alltime favourites
Runaways - Hot!
The lonely boys - Chicago

All the playlists starting with

  • a name consisting only of letters A,B,C,D and optional digits
  • or a prefix of that form, followed by a devider ('~' (a tilde) or '--' (two dashes))

can now be loaded with the RC! (The first four in the example above.)

So the above example playlists can be loaded by:

A

B

OK B 1 OK

OK C 2 4 OK
The two example ones without the special prefix/name can't be loaded directly that way.
But of course they can be reached by skipping to them via Channel+ / Channel-.

Power button and sleep timer

The Power button initiates a "shutown -h now" (that is a power off (on a Raspberry Pi only sort of...)).
Pressing it again, any delayed shutdown gets cancelled.

Using a compound key with Power and then one or more digits, a sleep timer with the given time in minutes is started.

For example set a sleep timer of 30 minutes:

OK POWER 3 0 OK
That means: The system will shut down then.
Anyway: This delayed shutdown can be cancelled with Power again as already mentioned.

Volume control

Additionally to the Vol+/- in steps of 5% the volume can be set to 100% by:

OK VOL+ VOL+ OK

to 50% by either of:

OK VOL+ VOL- OK
OK VOL- VOL+ OK

and to 0% by:

OK VOL- VOL- OK

Calling external commands

If there are external programs/scripts existing named like rcmpd.A (B,C,D,0-9) they can be called by:

OK MENU A OK

Printing a short summary of keys

A short reference of the possible keys can be printed on a printer with:

OK MENU MENU OK

This is the same list that is shown if rcmpd is called without any parameters.

To increase the WAF (Woman Acceptance Factor) I added those infos in German as well now.

To switch to German help you might edit a config value in the script,
but it's better to control it from outside:

Add a line like the following to your ~/.bashrc file:

export RCMPD_LANGUAGE=DE

To activate it just logoff and login again or type the above in your current shell session.

More about printing

The optional printing features are implemented by producing Postscript output which is converted to PDF and sent to the printer.

The most current output file is stored in /tmp/rcmpd-printout.pdf (configured as variable PRINTOUT in the script).

So it's possible to use the file for other purposes as well.

Here are two example prints, the short manual and playlist infos.

example printout: short manual example printout: playlist infos

(Click to see larger picture.)

Example function assignment on real remote controls

This topic is showing the assignments I configured for my own IR Remote Controls.

That is a simple text description here, not the configuration itself! More about the real configuration follows.

  1. an RC with many buttons and a complete function assignment
  2. a much simpler RC with only basic functions

Both can be used in parallel of course.

Pinnacle RC1144201

Pinnacle_RC1144201
Power        shutdown / cancel delayed shutdown / set sleep timer
Mute         mute / unmute
Menu         toggle audible command descriptions on/off / call external scripts / print keys
TV           speak info: current time
EPG          speak info: collection (available playlists) / print playlists
Vol+ Vol-    increase/decrease volume (in 5% steps)
Ch+ Ch-      next/previous playlist
Pinnacle     speak info: volume and state of "repeat"
A  B  C      load playlist "A", "B", "C"
D            speak info: playlist
Back         speak info: channel/album
Loop         toggle "repeat"
Fullscreen   speak info: current track/song
Pause        toggle pause/play
Play  Stop   start, stop playback / stop currently spoken text
Rew  FFwd    seek 30 seconds back/forward
Skip+ Skip-  next/previous track (within playlist)
Record       save current song info (title/artist) in '/home/pi/rcmpd.record'
1-9, 0       1st .. 10th track (within playlist)

OK           start/stop multiple-key commands (build from A,B,C,D,0-9, POWER, MENU, EPG, VOL)

Denon RC-126

https://ziemski.net/rcmpd/Denon_RC-126s.jpg

with only these commands:

KEY_POWER                      shutdown in 1 minute / cancel shutdown / set sleep timer
KEY_VOLUMEUP KEY_VOLUMEDOWN    increase/decrease volume (in 5% steps)
KEY_CHANNELUP=KEY_NEXT         next/previous track (within playlist)
KEY_CHANNELDOWN=KEY_PREVIOUS
KEY_SEARCH=KEY_SCREEN          speak info: current track/song
KEY_MUTE=KEY_PAUSE             toggle pause/play
KEY_MODE=KEY_STOP              stop

Download

I used to use Bitbucket for hosting the files for rcmpd in a Mercurial repository.
Unfortunately Bitbucket (Atlassian) stopped their support for the Mercurial version control system in 2020.

So I created a new repo on Github: https://github.com/gchriz/rcmpd/.

Issues and comments can be posted there.

The most current version of rcmpd can easily be downloaded from Github as rcmpd-main.zip
(a zip file with rcmpd itself and some documentation files).

How it works - technically

Here I would like to describe how the parts are playing together. Additional infos are shown in the installation section.

  1. First there is the software LIRC. It

    • knows about the codes the remote control sends
    • and listens to the IR receiver all the time when started.

    In LIRC's configuration file(s) lircd.conf and/or preferred lircd.conf.d/*.conf there are the used remote control(s) defined.

    Shortened example:

    begin remote
    
      name  Pinnacle_RC1144201
      ...
    
          begin codes
              power                    0x6E2D
              menu                     0x6EE7
              mute                     0x6E1E
              stop                     0x6E2B
              play                     0x6ED1
              pause                    0x6E2C
              ...
    

    You can see the name of the RC and some of the known buttons.

  2. LIRC provides - beneath others - a simple program irexec.

    irexec is able to communicate with the above running lircd and gets infos about a pressed button.

    To know what to do with that button info it has a configuration file too: .lircrc.

    That configuration file maps button-presses to actions, for example starting a program.

    In my case I map all buttons to rcmpd - with an appropriate parameter to distinguish them.

    Shortened example:

    begin
        prog   = irexec
        remote = Pinnacle_RC1144201
        button = power
        config = /home/pi/bin/rcmpd power
        repeat = 0
    end
    
    begin
        prog   = irexec
        remote = Pinnacle_RC1144201
        button = pause
        config = /home/pi/bin/rcmpd pause
        repeat = 0
    end
    

    You can see the matching name of the RC(!) and the matching buttons as well.

    So on every button press rcmpd gets called with the appropriate function as parameter.

    That file can be crafted manually or automaticall, described a bit later.

    To get this working irexec needs to run in background and wait for button infos.

  3. Finally within rcmpd there is a table or block for every used remote control.

    This is your individual setup - part 1

    Example:

    #*********************************************************************
    # You need to define the choosen keys of your remote controls here!
    #
    # Used for the creation of .lircrc (and as reference for humans...)
    #
    # Need to match the remote's names and the button names from the files
    # /etc/lirc/lircd.conf and/or /etc/lirc/lircd.conf.d/*.conf!
    # Edit the current ones below, add or replace with your own RC(s).
    #*********************************************************************
    
    define_remotes()
    {
      REMOTES=(
    
      [Pinnacle_RC1144201]="
            mute power
            menu tv epg
            a b c d
            vol+ vol-
            pinnacle
            ch+ ch-
            ok
            back loop
            fullscreen
            pause play stop
            ffwd rew
            skip+ skip-
            rec
            1 2 3 4 5 6 7 8 9 0
            cancel
      "
    
      # The definition below uses an "aliasing" feature:
      # If an RC sends a button X but you want rcmpd to behave like button Y
      # then write X=Y
    
      [DENON_RC_126]="
            KEY_POWER
            KEY_VOLUMEDOWN
            KEY_VOLUMEUP
            KEY_CHANNELUP=KEY_NEXT
            KEY_CHANNELDOWN=KEY_PREVIOUS
            KEY_SEARCH=KEY_SCREEN
            KEY_MUTE=KEY_PAUSE
            KEY_MODE=KEY_STOP
      "
    
      )
    }
    

    Later on in rcmpd there is function button_menu()

    That is your individual setup - part 2 - if really needed

    There are the mappings from "button" to internal function where the commands are executed then:

    #-------------------------------------------------------------
    # The assignments of RC buttons to commands in this program.
    # Here you can do your "setup".
    #-------------------------------------------------------------
    
    MUTE|KEY_MUTE)            cmd_mute ;;
    VOL+|KEY_VOLUMEUP)        cmd_volume_up ;;
    VOL-|KEY_VOLUMEDOWN)      cmd_volume_down ;;
    
    PLAY|KEY_PLAY)            cmd_play ;;
    STOP|KEY_STOP)            cmd_stop ;;
    PAUSE|KEY_PAUSE)          cmd_pause ;;
    LOOP|KEY_MEDIA_REPEAT)    cmd_toggle_repeat ;;
    
    SKIP+|KEY_NEXT)           cmd_next_track ;;
    SKIP-|KEY_PREVIOUS)       cmd_previous_track ;;
    0|1|2|3|4|5|6|7|8|9)      cmd_digit $btn ;;
    KEY_0|KEY_1|KEY_2|KEY_3|KEY_4|KEY_5|KEY_6|KEY_7|KEY_8|KEY_9)  cmd_digit $btn ;;
    CH+|KEY_CHANNELUP)        cmd_next_playlist ;;
    CH-|KEY_CHANNELDOWN)      cmd_previous_playlist ;;
    A|B|C|KEY_A|KEY_B|KEY_C)  cmd_ABC $btn ;;
    
    REC|RECORD|KEY_RECORD)    cmd_record ;;
    
    MENU|KEY_MENU)            cmd_toggle_speech ;;
    
    EPG|KEY_EPG)              cmd_info_collection ;;  # of playlists
    D|KEY_D)                  cmd_info_playlist ;;    # current playlist
    BACK|KEY_BACK)            cmd_info_station ;;     # respective "album"
    FULLSCREEN|KEY_SCREEN)    cmd_info_track ;;
    PINNACLE)                 cmd_info_state ;;
    TV|KEY_TV)                cmd_info_time ;;
    
    POWER|KEY_POWER)          cmd_poweroff ;;
    #-------------------------------------------------------------
    

Hardware requirements

Software requirements

Note

Optional patch for paps

The original paps package (creating Postscript for printing) in version 0.6.8-6 doesn't have a --title option for filling the header on the printed document with own text.

It shows the filename - or the string "stdin" - instead.

Since I really wanted to be able to place variable text in the header I created a patch for that. It's described at https://www.ziemski.net/paps

But of course rcmpd is working with the unpatched paps as well - just without the individual header.

Installation

  1. Save rcmpd into a directory available via $PATH (e.g. in ~/bin).

  2. Make it executable, e.g. with chmod u+x rcmpd

  3. execute rcmpd --checkinstall to do a base installation check

  4. Install-and-configure-LIRC-for-your-RC (see below)

  5. in file rcmpd itself

    • in function define_remotes() adapt the keys of your remote control(s) you want to use
    • in function help_keys() adapt the help text describing the keys
    • in function button_menu() optionally adapt to your needs (and key names...)
  6. Create an appropriate ~/.lircrc file for handling the RC commands

    It needs to match your RC (name and buttons) and the commands in rcmpd.
    It's possible to create it manually, but it's easier to use

    rcmpd --createlircrc

  7. (Re)start irexec to let it know about the new ~/.lircrc: rcmpd --restartirexec

  8. Test it in your shell by rcmpd <command> with <command> being any of the defined commands above.


If all works fine you should make irexec autostart.

That may be done via an entry in /etc/rc.local like this:

su - pi -c "/usr/bin/irexec --daemon /home/pi/.lircrc"

Or if playback should start immediately:

su - pi -c "/usr/bin/irexec --daemon /home/pi/.lircrc ; mpc play"

You should use a "normal" user (here: pi) for that.

Usage

rcmpd is easy to call.

The first iexample is the normal operation format and the others are for installation/configuration purposes:

rcmpd <command> ...     manages a command, e.g. from an infrared RC buttonpress

rcmpd --checkinstall    checks some aspects of the install (prerequisites)
rcmpd --createlircrc    creates /home/pi/.lircrc if not existing
rcmpd --restartirexec   restarts irexec after changes on .lircrc
rcmpd --showsonginfos   shows the "recorded" infos in '/home/pi/rcmpd.record'
rcmpd --listplaylists   lists all playlists and the contents of current one

rcmpd                   without any parameters this usage info and a short summary of keys is shown

Install and configure LIRC for your RC

Some infos regarding LIRC on a Raspberry Pi

If the infrared receiver is connected to the Raspberry Pi via GPIO pins (and not via USB) the Raspberry Pi needs to know the choosen GPIO.

That can be done in /boot/config.txt:

# Uncomment this to enable infrared communication.
dtoverlay=gpio-ir,gpio_pin=17

Next the software part:

Install the lirc package:

sudo apt install lirc

The above installation of lirc creates some configuration files in /etc/lirc/.

At least on Raspberry Pi's it's a good idea to change driver and device in lirc_options.conf:

Orignal:
   driver = devinput
   device = auto

New:
   driver = default
   device = /dev/lirc0

Since we don't use the devinput driver the matching config file in /etc/lirc/lircd.conf.d/ should be disabled by renaming it:

cd /etc/lirc/lircd.conf.d
sudo mv devinput.lircd.conf devinput.lircd.conf.dist

The configuration files for the remote controls

Now check if there is already a LIRC configuration file for your RC, for example here: http://lirc.sourceforge.net/remotes

and put it as a nicely named *.conf file into the directory:

/etc/lirc/lircd.conf.d/
If there is no conf file available for your RC yet:
create an appropriate .conf file yourself (by recording button presses)::
sudo irrecord --list-namespace sudo irrecord -d /dev/lirc0 /etc/lirc/lircd.conf.d/this_special_remote.conf

Finally (re)start lirc:

sudo systemctl restart lircd

and test your RC:

irw

==> press some keys on the RC and watch your shell window ... Stop irw with <Ctrl-C>

If there are technical problems you might want to use the following command for further testing:

mode2 -d /dev/lirc0

Infos about Text-to-speech (TTS) with espeak or Google API