Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Pinball USB Controller for Windows via RPi + Arcade Buttons

Sun Feb 09, 2014 5:32 pm

Hello everyone,

I like playing Pinball Arcade and Pinball FX2, that's why I decided - as a pinball fan - to build my own Pinball USB Controller.

I want to plug the RPi in via USB and let Windows 7 know it's a keyboard. All it had to do is sending Left-Shift, Right-Shift, Left-Ctrl, Right-Ctrl and A-S-D-F-codes to Windows.

What I did so far:

Raspbian is installed on my RPi, I wrote a python script that includes the uinput library. With uinput I use the emit_click function to "generate" pushed keys.

So far, so good, rc.local has the line that starts the script after the device booted. According to "top" it's running in background.

My RPi is powered by my Windows-PC through the micro-USB-Port. The Arcade-Buttons I want to use are connected to a breadboard. This one is connected to my RPi via T-Cobbler.

Things I need to know:

-Is it possible to let Windows "think" it's a keyboard? Like that bubble that pops up each time an USB device is connected on bottom right of Windows?

-Is it possible to send these "keyboard-codes" provided by uinput through the USB cable that powers by RPi and let Windows receive them?

-if YES, what packages/libraries do I need finish my plan?

dan3008
Posts: 1172
Joined: Wed Aug 15, 2012 1:05 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sun Feb 09, 2014 8:17 pm

The Pi model B, CANT be a usb slave device, only works in master mode. Thats a hardware limitation. In theory, the A can be used as a device, but i dont think anyone has got it working yet (not that I've seen anyway) what you are really looking for is a usb enabled micro controller such as the minimus, adafruit trinket or similar

Hope that helps
dan3008 wrote:Pays your money, takes your choice

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sun Feb 09, 2014 10:32 pm

A possible alternative is to use the Pi as a Bluetooth HID (keyboard) instead of a USB HID. You'd need a Bluetooth dongle and a procedure similar to this Linux User tutorial: Emulate a Bluetooth keyboard with the Raspberry Pi.

4thdwarflord
Posts: 142
Joined: Mon Dec 03, 2012 4:26 pm
Location: Deep in the mines of Moria

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Mon Feb 10, 2014 1:25 pm

There are various somethingduinos that can do this far easier, for example the arduino leonardo.

PiGraham
Posts: 3578
Joined: Fri Jun 07, 2013 12:37 pm
Location: Waterlooville

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Mon Feb 10, 2014 1:48 pm

Consider hacking a standard keyboard. Connect arcade buttons to the right lines on the keyboard controller and they will act as shift keys, or whatever keys you need.

See Google.

User avatar
ReBoot
Posts: 147
Joined: Mon Sep 17, 2012 2:23 pm
Location: Germany
Contact: Website

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Tue Feb 11, 2014 2:36 pm

4thdwarflord wrote:There are various somethingduinos that can do this far easier, for example the arduino leonardo.
Yup, this is the way to go. Alternatively a Teensy, but something Arduino-based is the way to go here. The Pi is just a detour, it surely is possible to achieve what you want to achieve using it, but why take the Long road when you can take a short one?

Emulating a Keyboard with an Arduino is dumb easy. I can send you some code, if you want.

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Feb 15, 2014 11:13 am

Thanks for your replies, guys.

I could get me an Arduino or an I-PAC2 or just a simple keyboard cuircuit board, but the only way for me to learn how the Pi works is to stick at it. :lol:

So yeah, for now I have 6 Buttons (3 more coming soon), plugged onto the breadboard which is connected to the Pi.

It is the first time I am dealing with bluetooth, been about time. USB-Bluetooth dongles are plugged in my Windows 7 PC and my RPi. Thanks to L2CAP, my current python script can contact the Win7-dongle, pretending it's a keyboard.

So far, so good, I have to find out what to use as an argument in the send() method of the Socket obejct ( socket.send('idunnowhat') )...some keycodes...hex-stuff maybe? 0x01337?

The only keycodes I will need to send now are LEFT SHIFT, RIGHT SHIFT, RIGHT CTRL, A, W and D.

Only thing I would hate now is that Win7 acts like "lol, whut?" once it receives the bluetooth signals and DO NOTHING! But we I will see. :mrgreen:

By the way: Optionally, I could let the Pi pretend it's a joypad, but afaik the Pinball Arcade videogame supports the Xbox360 controller only, with its LB, RB, LT, RT shoulder buttons. I will have to research more. Some other time maybe.

User avatar
ReBoot
Posts: 147
Joined: Mon Sep 17, 2012 2:23 pm
Location: Germany
Contact: Website

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Feb 15, 2014 12:34 pm

Learning to work the Pi by a Task which an Arduino would be a way better solution for is not exactly a good way to learn the Pi.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Feb 15, 2014 4:46 pm

That tutorial I linked to earlier has a download link for a zip containing keymap.py. Does that help you out?

Code: Select all

# Convert value returned from Linux event device ("evdev") to a HID code. This
# is reverse of what's actually hardcoded in the kernel.
#
# Lubomir Rintel <[email protected]>
# License: GPL
#
# Ported to a Python module by Liam Fraser.
#

keytable = {
    "KEY_RESERVED" : 0,
    "KEY_ESC" : 41,
    "KEY_1" : 30,
    "KEY_2" : 31,
    "KEY_3" : 32,
    "KEY_4" : 33,
    "KEY_5" : 34,
    "KEY_6" : 35,
    "KEY_7" : 36,
    "KEY_8" : 37,
    "KEY_9" : 38,
    "KEY_0" : 39,
    "KEY_MINUS" : 45,
    "KEY_EQUAL" : 46,
    "KEY_BACKSPACE" : 42,
    "KEY_TAB" : 43,
    "KEY_Q" : 20,
    "KEY_W" : 26,
    "KEY_E" : 8,
    "KEY_R" : 21,
    "KEY_T" : 23,
    "KEY_Y" : 28,
    "KEY_U" : 24,
    "KEY_I" : 12,
    "KEY_O" : 18,
    "KEY_P" : 19,
    "KEY_LEFTBRACE" : 47,
    "KEY_RIGHTBRACE" : 48,
    "KEY_ENTER" : 40,
    "KEY_LEFTCTRL" : 224,
    "KEY_A" : 4,
    "KEY_S" : 22,
    "KEY_D" : 7,
    "KEY_F" : 9,
    "KEY_G" : 10,
    "KEY_H" : 11,
    "KEY_J" : 13,
    "KEY_K" : 14,
    "KEY_L" : 15,
    "KEY_SEMICOLON" : 51,
    "KEY_APOSTROPHE" : 52,
    "KEY_GRAVE" : 53,
    "KEY_LEFTSHIFT" : 225,
    "KEY_BACKSLASH" : 50,
    "KEY_Z" : 29,
    "KEY_X" : 27,
    "KEY_C" : 6,
    "KEY_V" : 25,
    "KEY_B" : 5,
    "KEY_N" : 17,
    "KEY_M" : 16,
    "KEY_COMMA" : 54,
    "KEY_DOT" : 55,
    "KEY_SLASH" : 56,
    "KEY_RIGHTSHIFT" : 229,
    "KEY_KPASTERISK" : 85,
    "KEY_LEFTALT" : 226,
    "KEY_SPACE" : 44,
    "KEY_CAPSLOCK" : 57,
    "KEY_F1" : 58,
    "KEY_F2" : 59,
    "KEY_F3" : 60,
    "KEY_F4" : 61,
    "KEY_F5" : 62,
    "KEY_F6" : 63,
    "KEY_F7" : 64,
    "KEY_F8" : 65,
    "KEY_F9" : 66,
    "KEY_F10" : 67,
    "KEY_NUMLOCK" : 83,
    "KEY_SCROLLLOCK" : 71,
    "KEY_KP7" : 95,
    "KEY_KP8" : 96,
    "KEY_KP9" : 97,
    "KEY_KPMINUS" : 86,
    "KEY_KP4" : 92,
    "KEY_KP5" : 93,
    "KEY_KP6" : 94,
    "KEY_KPPLUS" : 87,
    "KEY_KP1" : 89,
    "KEY_KP2" : 90,
    "KEY_KP3" : 91,
    "KEY_KP0" : 98,
    "KEY_KPDOT" : 99,
    "KEY_ZENKAKUHANKAKU" : 148,
    "KEY_102ND" : 100,
    "KEY_F11" : 68,
    "KEY_F12" : 69,
    "KEY_RO" : 135,
    "KEY_KATAKANA" : 146,
    "KEY_HIRAGANA" : 147,
    "KEY_HENKAN" : 138,
    "KEY_KATAKANAHIRAGANA" : 136,
    "KEY_MUHENKAN" : 139,
    "KEY_KPJPCOMMA" : 140,
    "KEY_KPENTER" : 88,
    "KEY_RIGHTCTRL" : 228,
    "KEY_KPSLASH" : 84,
    "KEY_SYSRQ" : 70,
    "KEY_RIGHTALT" : 230,
    "KEY_HOME" : 74,
    "KEY_UP" : 82,
    "KEY_PAGEUP" : 75,
    "KEY_LEFT" : 80,
    "KEY_RIGHT" : 79,
    "KEY_END" : 77,
    "KEY_DOWN" : 81,
    "KEY_PAGEDOWN" : 78,
    "KEY_INSERT" : 73,
    "KEY_DELETE" : 76,
    "KEY_MUTE" : 239,
    "KEY_VOLUMEDOWN" : 238,
    "KEY_VOLUMEUP" : 237,
    "KEY_POWER" : 102,
    "KEY_KPEQUAL" : 103,
    "KEY_PAUSE" : 72,
    "KEY_KPCOMMA" : 133,
    "KEY_HANGEUL" : 144,
    "KEY_HANJA" : 145,
    "KEY_YEN" : 137,
    "KEY_LEFTMETA" : 227,
    "KEY_RIGHTMETA" : 231,
    "KEY_COMPOSE" : 101,
    "KEY_STOP" : 243,
    "KEY_AGAIN" : 121,
    "KEY_PROPS" : 118,
    "KEY_UNDO" : 122,
    "KEY_FRONT" : 119,
    "KEY_COPY" : 124,
    "KEY_OPEN" : 116,
    "KEY_PASTE" : 125,
    "KEY_FIND" : 244,
    "KEY_CUT" : 123,
    "KEY_HELP" : 117,
    "KEY_CALC" : 251,
    "KEY_SLEEP" : 248,
    "KEY_WWW" : 240,
    "KEY_COFFEE" : 249,
    "KEY_BACK" : 241,
    "KEY_FORWARD" : 242,
    "KEY_EJECTCD" : 236,
    "KEY_NEXTSONG" : 235,
    "KEY_PLAYPAUSE" : 232,
    "KEY_PREVIOUSSONG" : 234,
    "KEY_STOPCD" : 233,
    "KEY_REFRESH" : 250,
    "KEY_EDIT" : 247,
    "KEY_SCROLLUP" : 245,
    "KEY_SCROLLDOWN" : 246,
    "KEY_F13" : 104,
    "KEY_F14" : 105,
    "KEY_F15" : 106,
    "KEY_F16" : 107,
    "KEY_F17" : 108,
    "KEY_F18" : 109,
    "KEY_F19" : 110,
    "KEY_F20" : 111,
    "KEY_F21" : 112,
    "KEY_F22" : 113,
    "KEY_F23" : 114,
    "KEY_F24" : 115
}

# Map modifier keys to array element in the bit array
modkeys = {
   "KEY_RIGHTMETA" : 0,
   "KEY_RIGHTALT" : 1,
   "KEY_RIGHTSHIFT" : 2,
   "KEY_RIGHTCTRL" : 3,
   "KEY_LEFTMETA" : 4,
   "KEY_LEFTALT": 5,
   "KEY_LEFTSHIFT": 6,
   "KEY_LEFTCTRL": 7
}

def convert(evdev_keycode):
    return keytable[evdev_keycode]

def modkey(evdev_keycode):
    if evdev_keycode in modkeys:
        return modkeys[evdev_keycode]
    else:
        return -1 # Return an invalid array element
I'd be especially interested to know what the Win7 box will do when sent an HID code 249 (=KEY_COFFEE) :)
ReBoot wrote:Learning to work the Pi by a Task which an Arduino would be a way better solution for is not exactly a good way to learn the Pi.
I'm sorry, I have to respectfully and completely disagree. Making the Pi do things that haven't been done a thousand times before is a GREAT way to learn about the pi.

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Mon Feb 17, 2014 9:58 pm

Douglas6 wrote: I'd be especially interested to know what the Win7 box will do when sent an HID code 249 (=KEY_COFFEE) :)
Hmmm, seems I am still one step away from my goal. I have added the HID code ( KEY_T, "23", for now) into my code, and yes, the KEY_COFFEE is there too ( see "LEFT ARM" in source code). But Win7 doesn't react to the pushed buttons, although it's recognized and accepted as a keyboard. RPi is printing each time I push them though.

Code: Select all

import bluetooth
import os
import RPIO


os.system("hciconfig hci0 class 0x002540")
os.system("hciconfig hci0 name Pinball\ Controller")
os.system("hciconfig hci0 piscan")
bd_addr = "00:0C:55:10:75:B3" # MAC of PC-BLuetooth-Dongle
port = 1
sock=bluetooth.BluetoothSocket( bluetooth.L2CAP )
sock.connect((bd_addr, port))

def gpio_callback(gpio_id, val):
        if gpio_id == 23:
                print "LEFT NUDGE"
                sock.send( "23" )
        elif gpio_id == 25:
                print "RIGHT NUDGE"
                sock.send( "23" )
        elif gpio_id == 24:
                print "FRONT NUDGE"
                sock.send( "23" )
        elif gpio_id == 17:
                print "RIGHT MAGNA SAVE"
                sock.send( "23" )
        elif gpio_id == 8:
                print "RIGHT ARM"
                sock.send( "23" )
        elif gpio_id == 11:
                print "LEFT ARM"
                sock.send( "249" ) # Hmmmmmmmmm....
        else:
                print "NONE OF ABOVE!"
        print("gpio %s: %s" % (gpio_id, val))

RPIO.add_interrupt_callback( 8, gpio_callback, edge='rising')
RPIO.add_interrupt_callback( 11, gpio_callback, edge='rising')
RPIO.add_interrupt_callback( 17, gpio_callback, edge='rising')
RPIO.add_interrupt_callback( 23, gpio_callback, edge='rising')
RPIO.add_interrupt_callback( 24, gpio_callback, edge='rising')
RPIO.add_interrupt_callback( 25, gpio_callback, edge='rising')

RPIO.wait_for_interrupts()
sock.close()

User avatar
Jessie
Posts: 1754
Joined: Fri Nov 04, 2011 7:40 pm
Location: C/S CO USA

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Tue Feb 18, 2014 1:52 am

You could have done this with a minimusAVR and KADE firmware in about 10 seconds. But if you just wanted to exercise your brain then... mission accomplished.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Tue Feb 18, 2014 3:02 am

Jessie wrote:You could have done this with a minimusAVR and KADE firmware in about 10 seconds. But if you just wanted to exercise your brain then... mission accomplished.
Checks the sign on the door - "Raspberry Pi", OK, I thought I may have been in the wrong place. Another 'buy more hardware' solution may have been this, but back to the post.
I'm thinking that might be too simple. Rfcomm is for serial, mostly, I think HID requires a bit more. This is on my list, so, let me know how it's going, and I'll do the same.

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Wed Feb 19, 2014 10:46 am

Allright guys, seems I am one step further.

Had to stop last night though because it was 2:30 AM already. :roll:

My Win7 PC started to make that "ding" sound once I pressed one of the arcade buttons too often. It came out that I confused hex values with number values, so the wrong stuff had been sent. :lol:

At least something seems to arrive at the destination! I will keep trying.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Wed Feb 19, 2014 2:38 pm

You're ahead of me. I can't get Win7 to pair without getting the yellow '!' and a 'device was not properly installed' error.

Are you sending the full 9 byte input record?

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Wed Feb 19, 2014 4:00 pm

Douglas6 wrote:You're ahead of me. I can't get Win7 to pair without getting the yellow '!' and a 'device was not properly installed' error.

Are you sending the full 9 byte input record?
Yes, I do. Highly motivated I confused numbers with hex, therefore wrong stuff had been sent, he he. I will try again tonight.

I still have to check why the connection gets interrupted after a minute.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Thu Feb 20, 2014 7:15 am

Some small success here. I've got it working connected to Ubuntu (running in a VMBox on the laptop). Still no joy with Win7.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Fri Feb 21, 2014 5:58 am

Turns out Win7 doesn't like my Pi's CSR 4.0 BTLE adapter. I replaced it with a generic 2.0 dongle and things are working fine now. I cleaned up the code a bit, let me know if you want me to post it.

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Fri Feb 21, 2014 9:00 pm

Douglas6 wrote:Turns out Win7 doesn't like my Pi's CSR 4.0 BTLE adapter. I replaced it with a generic 2.0 dongle and things are working fine now. I cleaned up the code a bit, let me know if you want me to post it.
Does the code let Win7 recognize it as an HID? I really need a solution. It's shown as a keyboard, but doesn't accept any signal. And I am sure the hex code is correct.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Fri Feb 21, 2014 10:47 pm

Yup, I had the Pi typing "Hello World" all over WordPad last night. The procedure is not so automated as I would like (setting the adapter class, adding the service record, setting discoverable and pairing must currently be done from the command-line, but you could add some python sys.os() calls or something better) The code itself is rather simple, once I figured out the correct L2CAP PSMs (17 and 19, NOT 17 and 1, as the linked tutorial says).

I'll post it as a WIP here tonight, probably late.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Feb 22, 2014 6:56 pm

Ok, here's the latest state of my Bluetooth HID code. This is very much a work in progress, but I'm posting the code in the hopes that others may find it useful and hopefully improve on it.
Some set-up is required (I'm assuming the required software has been installed). For clarity, I've disabled all Bluez 'plug-ins'. Edit /etc/bluetooth/main.conf and add this line

Code: Select all

DisablePlugins = network,input,audio,pnat,sap,serial
restart the bluetooth daemon with

Code: Select all

sudo/etc/init.d.bluetooth restart
Set the bluetooth adapter as as HID peripheral keyboard device with

Code: Select all

sudo hciconfig hci0 class 0x002540
Set your adapter as discoverable and start a pairing agent

Code: Select all

sudo hciconfig hci0 piscan
sudo bluez-simple-agent
Now you'll need to start piboard.py (see following) in a separate terminal window. It will add the keyboard Service Record so your host will recognize the Pi as a keyboard. On your Win7 or Linux host, search for the Pi and pair with it. Respond 'yes' in the Bluez-simple-agent window. PiBoard will wait 5 seconds and then start spamming the host computer.

piboard.py:

Code: Select all

#!/usr/bin/env python

# This code is based on and inspired by PiTooth, by Liam Fraser
# http://www.linuxuser.co.uk/tutorials/emulate-a-bluetooth-keyboard-with-the-raspberry-pi

import os
import sys
import time
import logging

import dbus
from bluetooth import *

class Hid:
    # Define the Bluetooth HID PSMs
    PSM_CTRL = 0x11
    PSM_INTR = 0x13

    # define the keyboard service record
    SERVICE_RECORD = """<?xml version="1.0" encoding="UTF-8" ?><record><attribute id="0x0001"><sequence><uuid value="0x1124" /></sequence></attribute>
<attribute id="0x0004"><sequence><sequence><uuid value="0x0100" /><uint16 value="0x0011" /></sequence><sequence><uuid value="0x0011" /></sequence></sequence></attribute>
<attribute id="0x0005"><sequence><uuid value="0x1002" /></sequence></attribute>
<attribute id="0x0006"><sequence><uint16 value="0x656e" /><uint16 value="0x006a" /><uint16 value="0x0100" /></sequence></attribute>
<attribute id="0x0009"><sequence><sequence><uuid value="0x1124" /><uint16 value="0x0100" /></sequence></sequence></attribute>
<attribute id="0x000d"><sequence><sequence><sequence><uuid value="0x0100" /><uint16 value="0x0013" /></sequence><sequence><uuid value="0x0011" /></sequence></sequence></sequence></attribute>
<attribute id="0x0100"><text value="Raspberry Pi Virtual Keyboard" /></attribute><attribute id="0x0101"><text value="USB > BT Keyboard" /></attribute>
<attribute id="0x0102"><text value="PiBoard" /></attribute><attribute id="0x0200"><uint16 value="0x0100" /></attribute>
<attribute id="0x0201"><uint16 value="0x0111" /></attribute><attribute id="0x0202"><uint8 value="0x40" /></attribute>
<attribute id="0x0203"><uint8 value="0x00" /></attribute><attribute id="0x0204"><boolean value="true" /></attribute>
<attribute id="0x0205"><boolean value="true" /></attribute><attribute id="0x0206"><sequence><sequence><uint8 value="0x22" />
<text encoding="hex" value="05010906a101850175019508050719e029e715002501810295017508810395057501050819012905910295017503910395067508150026ff000507190029ff8100c0050c0901a1018503150025017501950b0a23020a21020ab10109b809b609cd09b509e209ea09e9093081029501750d8103c0" />
</sequence></sequence></attribute><attribute id="0x0207"><sequence><sequence><uint16 value="0x0409" /><uint16 value="0x0100" /></sequence></sequence></attribute>
<attribute id="0x020b"><uint16 value="0x0100" /></attribute><attribute id="0x020c"><uint16 value="0x0c80" /></attribute>
<attribute id="0x020d"><boolean value="false" /></attribute><attribute id="0x020e"><boolean value="true" /></attribute>
<attribute id="0x020f"><uint16 value="0x0640" /></attribute><attribute id="0x0210"><uint16 value="0x0320" /></attribute></record>"""

    # define some HID key codes
    KEY_H = 11
    KEY_E = 8
    KEY_L = 15
    KEY_O = 18
    KEY_W = 26
    KEY_R = 21
    KEY_D = 7
    KEY_1 = 30
    KEY_SPACE = 44

    LOG_LEVEL = logging.DEBUG
    LOG_FILE = "/dev/stdout"
    LOG_FORMAT = "%(asctime)s %(levelname)s %(message)s"

    # Define some key modifier bitmasks
    MODIFIER_NONE = 0x00
    SHIFT_LEFT = 0x02

    def __init__(self):
        self.sock_control = None
        self.conn_control = None
        self.conn_interrupt = None
        self.sock_intetrrupt = None
        self.input_report = bytearray([
                0xA1, # Input report
                0x01, # Usage: keyboard
                0x00, # Reserved
                0x00, # Modifier bits
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # Keys
        ])
        logging.basicConfig(filename=Hid.LOG_FILE, format=Hid.LOG_FORMAT, level=Hid.LOG_LEVEL)

    def connect(self):
        # Create the control and interrupt sockets, and bind and listen
        self.sock_control = BluetoothSocket(L2CAP)
        self.sock_control.bind(("", Hid.PSM_CTRL))
        self.sock_control.listen(1)
        self.sock_interrupt = BluetoothSocket(L2CAP)
        self.sock_interrupt.bind(("", Hid.PSM_INTR))
        self.sock_interrupt.listen(1)
        logging.info("Waiting for a connection...")

        # Accept connections from the host, first the control socket and then the interrupt
        self.conn_control, conn_info = self.sock_control.accept()
        self.conn_interrupt, conn_info = self.sock_interrupt.accept()
        logging.info("PiBoard is connected to %s" % (conn_info[0]))

    def close(self):
        self.sock_interrupt.close()
        self.sock_control.close()

    def addService(self):
        bus = dbus.SystemBus()
        manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
        adapter_path = manager.DefaultAdapter()
        service = dbus.Interface(bus.get_object("org.bluez", adapter_path), "org.bluez.Service")

        logging.debug("Adding keyboard service record")
        service.AddRecord(Hid.SERVICE_RECORD)

    # Send a key press and release
    def sendKey(self, key, modifiers=None):
        # press the key
        self.pressKey(key, modifiers=modifiers)
        # Give the host some time to process; keyboards aren't expected to be fast
        time.sleep(0.01)
        # release the key
        self.releaseKey()

    def pressKey(self, key, modifiers=None):
        if (modifiers != None):
            self.input_report[2] = modifiers
        self.input_report[4] = key
        self.conn_interrupt.send(str(self.input_report))

    def releaseKey(self):
        self.input_report[4] = 0x00
        self.conn_interrupt.send(str(self.input_report))

if __name__ == "__main__":

    hid = Hid()
    hid.addService()
    hid.connect()
    time.sleep(5)
    try:
        while True:
            hid.sendKey(chr(Hid.KEY_H), modifiers=Hid.SHIFT_LEFT)
            hid.sendKey(chr(Hid.KEY_E), modifiers=Hid.MODIFIER_NONE)
            hid.sendKey(chr(Hid.KEY_L))
            hid.sendKey(chr(Hid.KEY_L))
            hid.sendKey(chr(Hid.KEY_O))
            hid.sendKey(chr(Hid.KEY_SPACE))
            hid.sendKey(chr(Hid.KEY_W), modifiers=Hid.SHIFT_LEFT)
            hid.sendKey(chr(Hid.KEY_O), modifiers=Hid.MODIFIER_NONE)
            hid.sendKey(chr(Hid.KEY_R))
            hid.sendKey(chr(Hid.KEY_L))
            hid.sendKey(chr(Hid.KEY_D))
            hid.sendKey(chr(Hid.KEY_1), modifiers=Hid.SHIFT_LEFT)
            hid.sendKey(chr(Hid.KEY_SPACE), modifiers=Hid.MODIFIER_NONE)
            time.sleep(5)
    except KeyboardInterrupt as ex:
        hid.close()
    except BluetoothError as ex:
        print("Connection was lost")

    print("Thank you for using PiBoard")

Dougy
Posts: 7
Joined: Sun Feb 09, 2014 5:15 pm

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sun Feb 23, 2014 12:44 pm

Allright, now I got it!

Thanks a lot, Douglas6. I looked at your code and realised what was missing in my code, and therefore I added the dbus functionality with the service record.

Somehow I could not use it as a constant, there was an error, I dunno why. But it was in a file all the time, so I just let it read and use the content. Your script was working fine though. The constant just was not working for me, some "input/output error" exception by bluez.

But now, my PC gets attacked by a lot of "a" letters once I press the left nudge button a.k.a. simulated a-key. Same goes with right nudge / "d".

First, press the left arm button / "SHIFT LEFT" once and take my finger off, and then the left nudge button. Here is the output:

Code: Select all

LEFT ARM
gpio 11: 1
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 225, 0, 0, 0, 0, 0]
LEFT ARM
gpio 11: 1
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 225, 225, 0, 0, 0, 0]
LEFT ARM
gpio 11: 0
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0, 0, 0, 0, 0]
NUDGE LEFT
gpio 23: 0
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0, 0, 0, 0, 0]
NUDGE LEFT
gpio 23: 1
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 4, 0, 0, 0, 0, 0]
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Funny that left arm fires 3 times! and nudge left activates itself only when I release my finger.

Here's the current code.

Code: Select all


import bluetooth
import os
import RPIO
import time
import evdev
from evdev import *
import keymap
import dbus
from bluetooth import *

os.system("hciconfig hci0 up")
os.system("hciconfig hci0 class 0x2508")
os.system("hciconfig hci0 name Pinball\ Wizard\ 2014")
os.system("hciconfig hci0 piscan")

bd_addr = "00:0C:55:10:75:B3" # MAC PC-BLuetooth-Dongle

# Define the Bluetooth HID PSMs
PSM_CTRL = 0x11
PSM_INTR = 0x13

sock=bluetooth.BluetoothSocket( bluetooth.L2CAP )
sock.connect((bd_addr, PSM_INTR))

#interrupt=bluetooth.BluetoothSocket( bluetooth.L2CAP )
#interrupt.connect((bd_addr, PSM_INTR))

status = [ 0xA1, 0x01, [0, 0, 0, 0, 0, 0, 0, 0], 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]

# Read the service record from file
try:
        fh = open(sys.path[0] +"/sdp_record.xml", "r")
except:
        sys.exit("Could not open the sdp record. Exiting...")
service_record = fh.read()
fh.close()

bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")

adapter_path = manager.DefaultAdapter()
service = dbus.Interface(bus.get_object("org.bluez", adapter_path), "org.bluez.Service")
service.AddRecord(service_record)


def gpio_callback(gpio_id, val):
        if gpio_id == 23:
                print "NUDGE LEFT"
                eventnr = 30
        elif gpio_id == 25:
                print "NUDGE RIGHT"
                eventnr = 32
        elif gpio_id == 24:
                print "NUDGE FRONT"
                eventnr = 17
        elif gpio_id == 17:
                print "RIGHT OPTIONAL BUTTON"
                eventnr = 29
        elif gpio_id == 8:
                print "RIGHT ARM"
                eventnr = 54
        elif gpio_id == 11:
                print "LEFT ARM"
                eventnr = 42
        else:
                print "NONE OF ABOVE!"
                eventnr = 30

        print("gpio %s: %s" % (gpio_id, val))

        global ecodes

        evdev_code = ecodes.KEY[eventnr]
        modkey_element = keymap.modkey(evdev_code)
        #if modkey_element > 0:
            # Need to set one of the modifier bits
        #    if status[2][modkey_element] == 0:
        #        status[2][modkey_element] = 1
        #    else:
        #        status[2][modkey_element] = 0

        # I HAVE DECIDED TO SEND SHIFT AND CTRL AS NORMAL KEY CODES INSTEAD OF MODIFIERS!!!!

        hex_key = keymap.convert(ecodes.KEY[eventnr])

        # Loop through elements 4 to 9 of the input report structure
        for i in range (4, 10):
                if status[i] == hex_key and val == 0:
                    # Code is 0 so we need to depress
                    status[i] = 0x00
                elif status[i] == 0x00 and val == 1:
                    # If the current space is empty and the key is being pressed
                    status[i] = hex_key
                    break

        output = ""

        for x in status:
            if type(x) is list:
                a = ""
                for y in x:
                    a += str(y)
                output += chr(int(a, 2))
            else:
                output += chr(x)
        print status

        sock.send( output )

RPIO.add_interrupt_callback( 8, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=0 )
RPIO.add_interrupt_callback( 11, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=0 )
RPIO.add_interrupt_callback( 17, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=0 )
RPIO.add_interrupt_callback( 23, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=10 ) 
RPIO.add_interrupt_callback( 24, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=10 ) 
RPIO.add_interrupt_callback( 25, gpio_callback, pull_up_down=RPIO.PUD_DOWN, debounce_timeout_ms=10 ) 

RPIO.wait_for_interrupts()
sock.close()

I have been experimenting with PUD_DOWN/PUD_UP/PUD_OFF and the timeouts all the time, that's why there's 0 and/or 10.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sun Feb 23, 2014 3:59 pm

From your description I suspect a combination of two things: switch bounce and key repeat. Make sure you have some debounce timeout, start large and tune it down for performance. And assuming your buttons are wired to GND, the pull-ups should be on.

Secondly key repeat. Each key stroke requires 2 input reports be sent, one, with a key code, to press the key, and another with all zero key codes to release the key. If the key is not released, Win7 will start repeating the key, just like a real keyboard.

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sun Feb 23, 2014 8:13 pm

Ok, I have a minute to look more closely at your code and results. It looks like you ARE sending both input records for press and release, but they're backwards. Take a look at your two 'NUDGE LEFT' events:

Code: Select all

NUDGE LEFT
gpio 23: 0
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0, 0, 0, 0, 0]
NUDGE LEFT
gpio 23: 1
[161, 1, [0, 0, 0, 0, 0, 0, 0, 0], 0, 4, 0, 0, 0, 0, 0]
The first one is clearing (releasing) all keys, and the second one sets (presses) key code 4. That's backwards, and leaves the key pressed, so key repeat kicks in. And I think the reason may be here:

Code: Select all

                if status[i] == hex_key and val == 0:
                    # Code is 0 so we need to depress
                    status[i] = 0x00
                elif status[i] == 0x00 and val == 1:
                    # If the current space is empty and the key is being pressed
                    status[i] = hex_key
                    break
I'm again assuming your buttons are wired to ground, which makes them 'active-low', which means val = 0 = the button is pressed, and val = 1 = not pressed, and so this bit is backwards.

The first three 'LEFT ARM' events look goofy, I'm guessing this is a result of switch bounce, since it looks like you have a debounce_timeout_ms=0 on that pin, or possibly the lack of a pull-up. It depends again on how the buttons are wired.

Printing out the input reports is an excellent debugging tool, keep a close eye on those while you press and release your buttons. I think you're really close.

Abhijith Kini
Posts: 5
Joined: Sat Mar 25, 2017 8:05 am

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Mar 25, 2017 8:17 am

Thanks a lot @ Douglas6. I tried your code without any problem i can achieve pairing with by virtual Pi Keyboard in My "Android " phone(Of course , it print " Hello world").But in case of iOS it not working .Unsuccessful paring. According to my knowledge , iOS will achieve pairing with BLE device but HID will act as Classic one .So hope need to implement HID over GATT .( I am using Dual mode Bluetooth Dongle ). Did anyone tried out HID over GATT to enable same functionality in iOS? Advance Thanks .

User avatar
Douglas6
Posts: 4742
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Pinball USB Controller for Windows via RPi + Arcade Butt

Sat Mar 25, 2017 12:56 pm

I am not familiar with iOS, but surely it does classic HID? Maybe not, but I would have thought so.

I have no experience with HID over GATT. There are reports here That HoG keyboards don't work with standard Raspbian, but perhaps do if upgraded to BlueZ 5.43, perhaps not. As far as creating a HoG keyboard, I'm afraid I have no idea. It would be very different from classic HID. Perhaps there is a sample in the BlueZ source tree.

Return to “Gaming”