Use a media keyboard with your pHat Beat, can it be done?

Is there a way to use a wireless keyboard, namely the media keys, with the pHat Beat Pirate radio setup? I plugged my mini WIFI keyboard dongle in but nothing happens when I press volume up down etc. I’m running the Full Raspbian, its just set to boot to command line instead of desktop GUI. This would be a really nice add on feature as those little side buttons are a bit tricky to push. If it had worked, my next step would have been to plug my FLIRC in and use a remote control to change channels and adjust the volume etc.

Your best bet is to- and this is slightly less complicated than it sounds- use Python USB to grab the keyboard or remote control device and act upon the button-presses directly.

This is a good place to start looking into how to do that: https://learn.pimoroni.com/tutorial/robots/controlling-your-robot-wireless-keyboard

And, if I’m right in thinking the FLIRC is a USB device, (I’ve never used one!) you should be able to use the same trick and script it to do anything you like.

Thanks, yes the FLIRC is a USB device. As far as the Pi or PC is concerned its a USB keyboard. Its a programmable IR receiver. It learns the codes your remote sends out and lets you map them to a key press. this may work out even better than I planed. Some time down the road I plan on building a rover project and want to control it with a second Pi via WIFI or Blue Tooth. I have an Explorer pHat mounted to a Pi Zero W, just don’t have the rest, yet. Thanks again.

I’m getting an “unindent does not match any outer indentation level” error at the “try” line when running the py file to find the key codes? I took the indent out and then it errored at the “print control” line, missing parenthesis I think was the error?

Is this because I did a sudo python3 setup.py install. instead of sudo python setup.py install? I’m running it from idle3.

I edited it to this and it now passes the check module function in idle3. Will run it on my Pi latter on tonight to see what happens.

import usb.core
import usb.util
import time

USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS

USB_VENDOR = 0x1997 # Rii
USB_PRODUCT = 0x2433 # Mini Wireless Keyboard

dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)

endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

while True:
control = None

try:
control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
print (control)
except:
pass

time.sleep(0.01) # Let CTRL+C actually exit

I can’t find the code tags?

I got the file to run Ok, and I’m getting codes when I press the up, down, left , right, and OK. Nothing happens though when I press the media keys on the left side of the keyboard? This min keyboard is branded Cana Kit but I got the same ID codes as the Rii used in that tutorial. This keyboard is identical with identical layout. I’d really like to use the media keys if possible?

I’m guessing the media keys are on a different endpoint.

If you edit the line that looks like a family of owls, you might be able to find them. If I remember correctly, there are methods available for enumerating endpoints, too.

I got codes from my FLIRC that I think I can use. I’m struggling to figure out how to edit the file for the explorer pHat to work with my pHat Beat though. Here is what I have. Are there code tags I should be using? I ran it once as sudo from command line and one whole bar of LED’s light up on the pHat Beat. Didn’t seem too work, hard to tell things went funcky and now all I get is very low scratchy sounds from the speakers. I’m reimaging my SD card as I type this to start all over again. I’m getting close to throwing in the towel on this one. Is there a python file that starts the Pirate radio on boot up? Is that maybe the file I should be editing instead of creating a new one?

#!/usr/bin/env python

import usb.core
import usb.util
import phatbeat
import time

USB_VENDOR = 0x20a0
USB_PRODUCT = 0x0001

USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS

BTN_CHANDN = 182
BTN_CHANUP = 181
BTN_VOLUMEUP = 234
BTN_VOLUMEDN = 223
BTN_MUTE = 205
BTN_POWER = 1

dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

while True:
control = None
try:
control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
print(control)
except:
pass

if control != None:
    if BTN_VOLUMEDN in control:
        phatbeat.BTN_VOLDN()

    if BTN_VOLUMEUP in control:
        phatbeat.BTN_VOLUP()

    if BTN_CHANDN in control:
        phatbeat.BTN_REWIND()
        
    if BTN_CHANUP in control:
        phatbeat.BTN_FASTFWD()
        
    if BTN_POWER in control:
        phatbeat.BTN_ONOFF()

    #if BTN_EXIT in control:
        #exit()

time.sleep(0.02)

The script you’re looking for is probably this: https://github.com/pimoroni/phat-beat/blob/master/projects/vlc-radio/phatbeatd/usr/bin/phatbeatd

It’s best left as-is, though, since it’s a fairly obtuse script not meant to be an editable example.

The relevant parts to understand are the socket interface for controlling VLC, which you can build into your script. Importing the phatbeat library wont work, since that’s just for reading buttons and doesn’t provide control over any of the software on your Pi.

import socket
import re

VLC_HOST = "127.0.0.1"
VLC_PORT = 9294

class VLC():

    def __init__(self, host="127.0.0.1", port=9294):
        self.host = host
        self.port = port
        self.current_stream = None
        self.current_state = None
        self.connecting = False
        self.socket = None

    def send(self, command):
        if self.connecting:
            return False

        print("Sending command: {}".format(command))
        command_string = command + "\n"

        if sys.version_info[0] >= 3:
            command_string = command_string.encode("utf-8")

        try:
            self.socket.send(command_string)

        except socket.error:
            print("Failed to send command to VLC") 
            if self.connect():
                self.send(command)

    def recv(self, length):
        value = self.socket.recv(8192)

        if sys.version_info[0] >= 3:
            value = value.decode("utf-8")

        return value

    def communicate(self, command, response_length=8192):
        self.send(command)
        return self.recv(response_length)

    def get_current_stream(self):
        self.send("status")
        status = self.recv(8192)

        result = re.search("input:\ (.*)\ ", status)
        state = re.search("state\ (.*)\ ", status)

        if state is not None:
            self.current_state = state.group(1)

        if result is not None:
            self.current_stream = result.group(1)
        else:
            self.current_stream = None

        return self.current_stream

    def connect(self):
        if self.connecting:
            return

        self.connecting = True

        if self.socket is not None:
            self.socket.close()

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        for attempt in range(10):
            try:
                print("Attempting to connect to VLC on {host}:{port}".format(host=self.host, port=self.port))
                self.socket.connect((self.host, self.port))
                print("Connection successful!")
                self.connecting = False
                return True

            except socket.error:
                time.sleep(1)

        print("Connection failed!")
        self.connecting = False
        return False

vlc = VLC(host=VLC_HOST, port=VLC_PORT)

if vlc.connect():
    # Your setup code here

And once all of the above has set up a connection to VLC, you can use the following commands to control it:

vlc.communicate("voldown") # Turn the volume down one step
vlc.communicate("volup") # Turn the volume up one step
vlc.communicate("next") #  Next track
vlc.communicate("prev") # Prev track
vlc.communicate("pause") # Pause/Play

Note: To paste indented code, surround it with three backticks:

```
Like so!
```

Ok, thanks. This is turning out to be a lot more work than I thought. I’ll have a look see, but I think it might be above my skill level.

It wont be by the time you’ve finished ;)

Just sat down with a cup of Java to have a go at it. Making a backup image of my SD card first though. Not knowing what I’m doing hasn’t stopped me before. ;)

1 Like

Ok this is what I have so far. For now I just do a sudo python3 etc from the command line to run it. I’m seeing the key codes on screen so I’m thinking that’s because of the “print (control)” line, and I can remove it to stop that. After a couple of key presses, that don’t seem to be doing anything, I get a “sys not defined” error which I think is the " if sys.version_info[0] >= 3:" line?

import socket
import re

VLC_HOST = "127.0.0.1"
VLC_PORT = 9294

class VLC():

    def __init__(self, host="127.0.0.1", port=9294):
        self.host = host
        self.port = port
        self.current_stream = None
        self.current_state = None
        self.connecting = False
        self.socket = None

    def send(self, command):
        if self.connecting:
            return False

        print("Sending command: {}".format(command))
        command_string = command + "\n"

        if sys.version_info[0] >= 3:
            command_string = command_string.encode("utf-8")

        try:
            self.socket.send(command_string)

        except socket.error:
            print("Failed to send command to VLC") 
            if self.connect():
                self.send(command)

    def recv(self, length):
        value = self.socket.recv(8192)

        if sys.version_info[0] >= 3:
            value = value.decode("utf-8")

        return value

    def communicate(self, command, response_length=8192):
        self.send(command)
        return self.recv(response_length)

    def get_current_stream(self):
        self.send("status")
        status = self.recv(8192)

        result = re.search("input:\ (.*)\ ", status)
        state = re.search("state\ (.*)\ ", status)

        if state is not None:
            self.current_state = state.group(1)

        if result is not None:
            self.current_stream = result.group(1)
        else:
            self.current_stream = None

        return self.current_stream

    def connect(self):
        if self.connecting:
            return

        self.connecting = True

        if self.socket is not None:
            self.socket.close()

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        for attempt in range(10):
            try:
                print("Attempting to connect to VLC on {host}:{port}".format(host=self.host, port=self.port))
                self.socket.connect((self.host, self.port))
                print("Connection successful!")
                self.connecting = False
                return True

            except socket.error:
                time.sleep(1)

        print("Connection failed!")
        self.connecting = False
        return False

vlc = VLC(host=VLC_HOST, port=VLC_PORT)

if vlc.connect():

    import usb.core
    import usb.util
    import time

    USB_VENDOR  = 0x20a0
    USB_PRODUCT = 0x0001

    USB_IF      = 0 # Interface
    USB_TIMEOUT = 5 # Timeout in MS

    BTN_FASTFWD = 181
    BTN_REWIND = 182
    BTN_PLAYPAUSE = 205
    BTN_VOLUP = 223
    BTN_VOLDN = 234
    BTN_ONOFF = 1

    dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
    endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
  dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

while True:
    control = None
    try:
        control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
        print(control)
    except:
        pass

    if control != None:
        if BTN_FASTFWD in control:
           vlc.communicate("next") #  Next track

        if BTN_REWIND in control:
            vlc.communicate("prev") # Prev track

        if BTN_PLAYPAUSE in control:
            vlc.communicate("pause") # Pause/Play

        if BTN_VOLUP in control:
            vlc.communicate("volup") # Turn the volume up one step

        if BTN_VOLDN in control:
            vlc.communicate("voldown") # Turn the volume down one step

        if BTN_ONOFF in control:
            vlc.communicate("power") # Turn Off

    time.sleep(0.02)

Not too bad! This is looking incredibly close to being functional from what I can tell at a glance :D

Note that if vlc.connect() fails then the code from if dev.is_kernel_driver_active onwards will still be run and the program will fail when a button is pressed.

It looks like you need to import sys at the top of your code too, I missed that when I was skimming through looking for the imports that the VLC class needed.

The vlc.communicate("command") lines are sending commands directly to VLC media player, so they’re limited to what commands it actually supports.

I say “limited”, but there are quite a few more commands available in VLC than pHAT BEAT uses:

pi@raspberry:~ $ vlc -I rc
VLC media player 2.2.5 Weatherwax (revision 2.2.5-0-g9275f0fefa)
[0204da48] pulse audio output error: PulseAudio server connection failure: Connection refused
[02045630] core interface error: no suitable interface module
[01fae8f8] core libvlc error: interface "globalhotkeys,none" initialization failed
[01fce6c0] dbus interface error: Failed to connect to the D-Bus session daemon: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
[01fce6c0] core interface error: no suitable interface module
[01fae8f8] core libvlc error: interface "dbus,none" initialization failed
[01fce608] [cli] lua interface: Listening on host "*console".
VLC media player 2.2.5 Weatherwax
Command Line Interface initialized. Type `help' for help.
longhelp
> +----[ CLI commands ]
| add XYZ  . . . . . . . . . . . . . . . . . . . . add XYZ to playlist
| enqueue XYZ  . . . . . . . . . . . . . . . . . queue XYZ to playlist
| playlist . . . . . . . . . . . . .  show items currently in playlist
| search [string]  . .  search for items in playlist (or reset search)
| delete [X] . . . . . . . . . . . . . . . . delete item X in playlist
| move [X][Y]  . . . . . . . . . . . . move item X in playlist after Y
| sort key . . . . . . . . . . . . . . . . . . . . . sort the playlist
| sd [sd]  . . . . . . . . . . . . . show services discovery or toggle
| play . . . . . . . . . . . . . . . . . . . . . . . . . . play stream
| stop . . . . . . . . . . . . . . . . . . . . . . . . . . stop stream
| next . . . . . . . . . . . . . . . . . . . . . .  next playlist item
| prev . . . . . . . . . . . . . . . . . . . .  previous playlist item
| goto, gotoitem . . . . . . . . . . . . . . . . .  goto item at index
| repeat [on|off]  . . . . . . . . . . . . . .  toggle playlist repeat
| loop [on|off]  . . . . . . . . . . . . . . . .  toggle playlist loop
| random [on|off]  . . . . . . . . . . . . . .  toggle playlist random
| clear  . . . . . . . . . . . . . . . . . . . . .  clear the playlist
| status . . . . . . . . . . . . . . . . . . . current playlist status
| title [X]  . . . . . . . . . . . . . . set/get title in current item
| title_n  . . . . . . . . . . . . . . . .  next title in current item
| title_p  . . . . . . . . . . . . . .  previous title in current item
| chapter [X]  . . . . . . . . . . . . set/get chapter in current item
| chapter_n  . . . . . . . . . . . . . .  next chapter in current item
| chapter_p  . . . . . . . . . . . .  previous chapter in current item
|
| seek X . . . . . . . . . . . seek in seconds, for instance `seek 12'
| pause  . . . . . . . . . . . . . . . . . . . . . . . .  toggle pause
| fastforward  . . . . . . . . . . . . . . . . . . set to maximum rate
| rewind . . . . . . . . . . . . . . . . . . . . . set to minimum rate
| faster . . . . . . . . . . . . . . . . . .  faster playing of stream
| slower . . . . . . . . . . . . . . . . . .  slower playing of stream
| normal . . . . . . . . . . . . . . . . . .  normal playing of stream
| rate [playback rate] . . . . . . . . . .  set playback rate to value
| frame  . . . . . . . . . . . . . . . . . . . . . play frame by frame
| fullscreen, f, F [on|off]  . . . . . . . . . . . . toggle fullscreen
| info . . . . . . . . . . . . .  information about the current stream
| stats  . . . . . . . . . . . . . . . .  show statistical information
| get_time . . . . . . . . .  seconds elapsed since stream's beginning
| is_playing . . . . . . . . . . . .  1 if a stream plays, 0 otherwise
| get_title  . . . . . . . . . . . . . the title of the current stream
| get_length . . . . . . . . . . . .  the length of the current stream
|
| volume [X] . . . . . . . . . . . . . . . . . .  set/get audio volume
| volup [X]  . . . . . . . . . . . . . . .  raise audio volume X steps
| voldown [X]  . . . . . . . . . . . . . .  lower audio volume X steps
| achan [X]  . . . . . . . . . . . .  set/get stereo audio output mode
| atrack [X] . . . . . . . . . . . . . . . . . . . set/get audio track
| vtrack [X] . . . . . . . . . . . . . . . . . . . set/get video track
| vratio [X] . . . . . . . . . . . . . . .  set/get video aspect ratio
| vcrop, crop [X]  . . . . . . . . . . . . . . . .  set/get video crop
| vzoom, zoom [X]  . . . . . . . . . . . . . . . .  set/get video zoom
| vdeinterlace [X] . . . . . . . . . . . . . set/get video deinterlace
| vdeinterlace_mode [X]  . . . . . . .  set/get video deinterlace mode
| snapshot . . . . . . . . . . . . . . . . . . . . take video snapshot
| strack [X] . . . . . . . . . . . . . . . . .  set/get subtitle track
| hotkey, key [hotkey name]  . . . . . . . . . . simulate hotkey press
|
| vlm  . . . . . . . . . . . . . . . . . . . . . . . . .  load the VLM
| set [var [value]]  . . . . . . . . . . . . . . . . . set/get env var
| save_env . . . . . . . . . . . .  save env vars (for future clients)
| alias [cmd]  . . . . . . . . . . . . . . . . set/get command aliases
| description  . . . . . . . . . . . . . . . . .  describe this module
| license  . . . . . . . . . . . . . . . . print VLC's license message
| help, ? [pattern]  . . . . . . . . . . . . . . . . .  a help message
| longhelp [pattern] . . . . . . . . . . . . . . a longer help message
| lock . . . . . . . . . . . . . . . . . . . .  lock the telnet prompt
| logout . . . . . . . . . . . . . .  exit (if in a socket connection)
| quit . . . . . . . .  quit VLC (or logout if in a socket connection)
| shutdown . . . . . . . . . . . . . . . . . . . . . . .  shutdown VLC
+----[ end of help ]

I’ll have another go latter on today and post back. I have some errands to run at the moment. Thanks for all the help.

You’re absolutely welcome! Hope to see this up and running 'cos I think it would be a great project to share!

Got it working woot. I can even shut down when I press the power button on the remote. Here is my code. What is the best way to launch this on boot up?

import os
import socket
import re
import sys

VLC_HOST = "127.0.0.1"
VLC_PORT = 9294

class VLC():

    def __init__(self, host="127.0.0.1", port=9294):
        self.host = host
        self.port = port
        self.current_stream = None
        self.current_state = None
        self.connecting = False
        self.socket = None

    def send(self, command):
        if self.connecting:
            return False

        print("Sending command: {}".format(command))
        command_string = command + "\n"

        if sys.version_info[0] >= 3:
            command_string = command_string.encode("utf-8")

        try:
            self.socket.send(command_string)

        except socket.error:
            print("Failed to send command to VLC") 
            if self.connect():
                self.send(command)

    def recv(self, length):
        value = self.socket.recv(8192)

        if sys.version_info[0] >= 3:
            value = value.decode("utf-8")

        return value

    def communicate(self, command, response_length=8192):
        self.send(command)
        return self.recv(response_length)

    def get_current_stream(self):
        self.send("status")
        status = self.recv(8192)

        result = re.search("input:\ (.*)\ ", status)
        state = re.search("state\ (.*)\ ", status)

        if state is not None:
            self.current_state = state.group(1)

        if result is not None:
            self.current_stream = result.group(1)
        else:
            self.current_stream = None

        return self.current_stream

    def connect(self):
        if self.connecting:
            return

        self.connecting = True

        if self.socket is not None:
            self.socket.close()

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        for attempt in range(10):
            try:
                print("Attempting to connect to VLC on {host}:{port}".format(host=self.host, port=self.port))
                self.socket.connect((self.host, self.port))
                print("Connection successful!")
                self.connecting = False
                return True

            except socket.error:
                time.sleep(1)

        print("Connection failed!")
        self.connecting = False
        return False

vlc = VLC(host=VLC_HOST, port=VLC_PORT)

if vlc.connect():

    import usb.core
    import usb.util
    import time

    USB_VENDOR  = 0x20a0
    USB_PRODUCT = 0x0001

    USB_IF      = 0 # Interface
    USB_TIMEOUT = 5 # Timeout in MS

    BTN_FASTFWD = 181
    BTN_REWIND = 182
    BTN_PLAYPAUSE = 205
    BTN_VOLUP = 233
    BTN_VOLDN = 234
    BTN_ONOFF = 1

    dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
    endpoint = dev[0][(0,0)][0]

if dev.is_kernel_driver_active(USB_IF) is True:
  dev.detach_kernel_driver(USB_IF)

usb.util.claim_interface(dev, USB_IF)

while True:
    control = None
    try:
        control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
        #print(control)
    except:
        pass

    if control != None:
        if BTN_FASTFWD in control:
           vlc.communicate("next") #  Next track

        if BTN_REWIND in control:
            vlc.communicate("prev") # Prev track

        if BTN_PLAYPAUSE in control:
            vlc.communicate("pause") # Pause/Play

        if BTN_VOLUP in control:
            vlc.communicate("volup") # Turn the volume up one step

        if BTN_VOLDN in control:
            vlc.communicate("voldown") # Turn the volume down one step

        if BTN_ONOFF in control:
            os.system("sudo shutdown now -P")

    time.sleep(0.02)

    


See here for loading on boot: https://learn.pimoroni.com/tutorial/sandyj/running-scripts-at-boot

There are a whole bunch of different ways, but I think this one should work for you.

OK, crontab -e is how I usually do it, but in this case it didn’t work? My remote didn’t work.
I tried
@reboot sudo python3 /home/pi/PRRC.py &
and just
@reboot python3 /home/pi/PRRC.py &
sudo python3 /home/pi/PRRC.py is what I run from command line and that works. If I don’t do sudo I get all kinds of error messages.
I’ll have another try in an hour or so after my morning walk with my dog Ginger. Maybe I had a typo and didn’t see it?