When it is Hammer Time, it is important everybody knows it. This is akin to an emergency. 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.
$ sudo ./hammertime.py ack True err False ack True err False ack True err False ack True err False hammer button pressed here 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 yields 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.
It still needs to be tested.
Some useful extensions to this toy might include configurable actions for state changes of the button, rather than hardcoded Hammer Time.
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
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!).