Outils d'utilisateurs

Outils du Site


projets:hammerbutton

Hammer Button

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.

Parallel Port Wiring

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.

Wiring of the stop button to the parallel ports' DB25 pins

Parallel Port Logic

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

XBMC Interface

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.

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.

Mounting

Starting at XBMC startup

~/.xbmc/userdata/autoexec.py
xbmc.executebuiltin("XBMC.RunScript(special://home/addons/script.hammerbutton/default.py)")

Special Hack Job: Porting the Hammer Button to Windows 7/iTunes

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.

New and Improved Hammer Button

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.

Wiring of the stop button to the serial ports' DB9 pins

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!).

projets/hammerbutton.txt · Dernière modification: 2014/01/12 08:10 par oliviermehani