When it is Hammer Time, it is important everybody knows it. Urgently. So I hooked a latching emergency stop switch to my media centre so MC Hammer's Can't Touch This can be played in a hurry, at all times.
The idea is to hook the peripheral's Ack and Error lines to the stop button's “normally closed” and “normally open” contacts, respectively. Hitting the button would close (resp. open) the normally open (resp. closed) line, and create a signal on the parallel port that can be programatically captured.
The Python pyParallel module provides all we need. The button can be quickly tested with the following code.
#!/usr/bin/env python import parallel import time p = parallel.Parallel("/dev/parport0") # Set the strobe signal (pin 1) to 0 (+ve) p.setDataStrobe(1) while 1: print "ack", p.getInAcknowledge() print "err", p.getInError() time.sleep(1)
It simply shows the state of the lines every second.
$ <in>sudo ./hammertime.py</in> ack True err False ack True err False ack True err False ack True err False <in>hammer button pressed here</in> ack False err True ack False err True ack False err True
Hammer button events are sent to XBMC via its Event Server. They have also have examples Python code to use it.
This is simply done by extending the hammertime.py
script from above. The parallel port management logic is changed into a Python generator that yield
s the new state of the button (either the Ack or Error line) whenever it changes.
#!/usr/bin/env python """ A parallel port button driver to start MC Hammer' Can't Touch This in XBMC Copyright (c) 2012, Olivier Mehani <shtrom@ssji.net> All rights reserved. See [0] for implementation details. [0] https://www.narf.ssji.net/~shtrom/wiki/projets/hammerbutton Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Olivier Mehani nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import parallel import time import socket try: import xbmcclient except ImportError: # If the client library is not here, download it import urllib webFile = urllib.urlopen("http://xbmc.svn.sourceforge.net/viewvc/xbmc/trunk/tools/EventClients/lib/python/xbmcclient.py") # ?revision=32014 localFile = open("xbmcclient.py", 'w') localFile.write(webFile.read()) webFile.close() localFile.close() import xbmcclient __scriptname__ = "Hammer Button" __author__ = "Olivier Mehani <shtrom-xbmc@ssji.net>" __url__ = "https://www.narf.ssji.net/~shtrom/wiki/projets/hammerbutton" class HammerButton: poll_period = .5 # seconds def __init__(self, port="/dev/parport0"): self.port = parallel.Parallel(port) # Set the strobe signal (pin 1) to 0 (+ve) self.port.setDataStrobe(1) def ack_generator(self): return self.generator(self.port.getInAcknowledge) def err_generator(self): return self.generator(self.port.getInError) def generator(self, fun=None): if fun is None: fun = self.port.getInAcknowledge old_state = fun() while 1: time.sleep(self.poll_period) state = fun() if old_state is not state: yield state old_state=state if __name__ == "__main__": hb = HammerButton().err_generator() xbmc = xbmcclient.XBMCClient("Hammer Button", "resources/images/mchammer.png") xbmc.connect() # Connects to 127.0.0.1:9777 count = 1 try: while 1: stat = hb.next() if stat: xbmc.send_action("XBMC.PlayMedia(\"/data/music/shtrom/MC Hammer - 1990 - Please Hammer Don't Hurt 'Em/02 - MC Hammer - U Can't Touch This.flac\")") xbmc.send_notification("STOP!", "Hammer time. (#%d)" % count, "resources/images/hammertime.png") count = count + 1 else: xbmc.send_action("XBMC.PlayerControl(PartyMode(music))") xbmc.send_notification("STOP!", "Collaborate and listen.", "resources/images/e=mc-hammer.png") except KeyboardInterrupt: pass xbmc.close()
It needs a couple of pictures collected from the interwebs, placed in
resources/images
. Peruse the code for the references to the png files in
there. This file organisation is part of an attempt to make an XBMC Addon out of
this code. The full
code is available in a Git repo.
Some useful extensions to this toy including configurable actions for state changes of the button, rather than hardcoded Hammer Time, are in the (now proper) XBMC addon.
xbmc.executebuiltin("XBMC.RunScript(special://home/addons/script.hammerbutton/default.py)")
For a quick job, we just want to toggle playing Can't Touch This and the normal playlist based on the status of a pin on the serial port (for the target computer has a serial port; no time to hack a USB thingo). Let's use the DSR pin; we'll use the DTR and RTS signals to provide the levels for the DSR pin (on and off, respectively). On the hardware side, the switch behaves as a DCE by connecting them to the normally closed and normally open switches, respectively, on one side and the DSR signal on the other. For proper operation, the DCD line is also connected directly to the DTR one.
' HammerButton for Windows/iTunes [0] ' Copyright (c) 2013 Olivier Mehani <shtrom-hammertime@ssji.net>, BSD Licensed ' Hammer time at the tip of your fingers, for when you need it. ' ' This Script uses the NETCommOCX [1] component for serial port access, ' and the iTunes COM SDK [2] to control playback. ' [0] https://www.narf.ssji.net/~shtrom/wiki/projets/hammerbutton ' [1] https://home.comcast.net/~hardandsoftware/NETCommOCX.htm ' [2] https://developer.apple.com/sdk/itunescomsdk.html ' ' The Windows 7 NETCommOCX object is a 32-bit component ' ' This script needs to run with [c|w]script.exe found in ' c:\Windows\SysWOW64\ (which contains 32-bit binaries, ' unlike c:\Windows\System32, where the 64-bit ones are...). ' ' c:\Windows\SysWOW64\cscript.exe HammerButton.vbs ' Option Explicit ' Force to use CScript [3] ' [3] http://www.robvanderwoude.com/vbstech_engine_force.php Dim strArgs, strCmd, strEngine, i, objDebug, wshShell Set wshShell = CreateObject( "WScript.Shell" ) strEngine = UCase( Right( WScript.FullName, 12 ) ) If strEngine <> "\CSCRIPT.EXE" Then ' Recreate the list of command line arguments strArgs = "" If WScript.Arguments.Count > 0 Then For i = 0 To WScript.Arguments.Count strArgs = strArgs & " " & WScript.Arguments(i) Next End If ' Create the complete command line to rerun this script in CSCRIPT strCmd = "CSCRIPT.EXE //NoLogo """ & WScript.ScriptFullName & """" & strArgs ' Rerun the script in CSCRIPT Set objDebug = wshShell.Exec( strCmd ) ' Wait until the script exits Do While objDebug.Status = 0 WScript.Sleep 100 Loop ' Exit with CSCRIPT's return code WScript.Quit objDebug.ExitCode End If ' MSComm constants, see [0] ' [0] http://msdn.microsoft.com/en-us/library/aa259405%28v=vs.60%29.aspx 'Com error events: Const comEventBreak = 1001 Const comEventFrame = 1004 Const comEventOverrun = 1006 Const comEventRxOver = 1008 Const comEventRxParity = 1009 Const comEventTxFull = 1010 Const comEventDCB = 1011 'Com events: Const comEvSend = 1 Const comEvReceive = 2 Const comEvCTS = 3 Const comEvDSR = 4 Const comEvCD = 5 Const comEvRing = 6 Const comEvEOF = 7 Dim serport Dim lastDSR Dim curDSR Dim lastCTS Dim curCTS Dim playcount Dim iTunes Dim Playlist Dim Songs Dim s Dim Song Wscript.Echo "HammerButton for Windows/iTunes" Wscript.Echo "Copyright (c) 2013 Olivier Mehani <shtrom-hammertime@ssji.net>, BSD Licensed" Wscript.Echo "https://www.narf.ssji.net/~shtrom/wiki/projets/hammerbutton" Wscript.Echo "Hammer time at the tip of your fingers, for when you need it." 'Set serport = CreateObject("MSCommLib.MSComm.1") Set serport = WScript.CreateObject("NETCommOCX.NETComm", "SerPortEvent_") 'The second argument is the prefix for the event functions serport.CommPort = 1 'serport.RThreshold = 2 serport.RTSEnable = FALSE 'Kept low when the port is open serport.DTRenable = TRUE serport.PortOpen = TRUE lastDSR = TRUE 'Assume the button is armed lastDSR = FALSE 'Assume the button is off Set iTunes = Wscript.CreateObject("iTunes.Application") 'Set Playlist = iTunes.LibrarySource.Playlists Set Playlist = iTunes.LibraryPlaylist Set Songs = Playlist.search("Can't touch this", 5) If IsEmpty(Songs) or (TypeName(Songs) = "Nothing") Then WScript.Echo "No Hammer... We *CAN* touch this!" WScript.Quit ElseIf (TypeName(Songs) = "String") Then Set Songs = Song Else 'We should just get one here, but it s unclear how to index collections... 'For Each s in Songs ' Set Song = s 'Next 'Wscript.echo Typename(Songs) Set Song = Songs.Item(1) End If playcount = 0 While (1) Wscript.Sleep(10000) Wend Sub SerPortEvent_OnComm ' OnComm event handler Select Case serport.CommEvent ' Errors Case comEventBreak ' A Break was received. Wscript.Echo "comEventBreak" Case comEventFrame ' Framing Error Wscript.Echo "comEventFrame" Case comEventOverrun ' Data Lost. Wscript.Echo "comEventOverrun" Case comEventRxOver ' Receive buffer overflow. Wscript.Echo "comEventRxOver" Case comEventRxParity ' Parity Error. Wscript.Echo "comEventRxParity" Case comEventTxFull ' Transmit buffer full. Wscript.Echo "comEventTxFull" Case comEventDCB ' Unexpected error retrieving DCB] Wscript.Echo "comEventDCB" ' Events Case comEvCD ' Change in the CD line. Wscript.Echo "comEvCD" Case comEvCTS ' Change in the CTS line. curCTS = serport.CTSHolding if curCTS <> lastCTS Then If curCTS <> True Then Wscript.Echo "!CTS" Else Wscript.Echo "CTS" End If End If lastCTS = curCTS Case comEvDSR ' Change in the DSR line. curDSR = serport.DSRHolding if curDSR <> lastDSR Then If curDSR <> True Then playcount = playcount + 1 Wscript.Echo "STOP! Hammer Time! (" & playcount & ")" Song.Play Else Wscript.Echo "Stop. Collaborate and listen." PlayList.Shuffle = True Playlist.PlayFirstTrack End If End If lastDSR = curDSR Case comEvRing ' Change in the Ring Indicator. Wscript.Echo "comEvRing" Case comEvReceive Wscript.Echo "comEvReceive" 'rxCnt = rxCnt + objTest.InBufferCount 'rxBuf = rxBuf & objTest.Input 'If rxCnt >= MyThreshold Then ' For i = 2 To MyThreshold Step 2 ' objLogFile.Write (Mid (rxBuf, i - 1, 2)) ' objLogFile.Write (",") ' Next ' flag = TRUE ' rxCnt = 0 'End If Case comEvSend ' There are SThreshold number of ' characters in the transmit ' buffer. Wscript.Echo "comEvSend" Case comEvEof ' An EOF charater was found in ' the input stream Wscript.Echo "comEvEof" Case Else End Select End Sub
There is a Git repo, and an untested installer for that; the Xiph QuickTime Components (XiphQT) will be needed to use the included sample.
Following on the previous quick hack, it turns out using the serial port is sufficient, and the wiring, leveraging the symmetry of the switch's state, does not require any resistors. Also, we need a way to safely launch the party; we can afford a pull-up resistor here, as well as a debouncing capacitor. This new switch is wired to the CTS pin (8). It is also adequately protected to avoid starting the party by mistake.
The HammerButton XBMC Add-On has been updated accordingly. It still lacks any configuration interface, but works remotely if the XBMC event interface is open to the network where the HammerButton-powered machine is (It's surprising when unexpected!).