314 lines
13 KiB
Python
314 lines
13 KiB
Python
# Author: Lionel Jeanson copyright 2009
|
|
# Part of the JMRI distribution
|
|
#
|
|
# Use a Nintendo Wiimote device as a throttle
|
|
# You need to have Bluecove and WiiRemoteJ jars in your Java classpath, JMRI lib folder is a good place for that (copy both jars there)
|
|
# See: http://bluecove.org/ and https://github.com/micromu/WiiRemoteJ
|
|
#
|
|
# once Jynstrument started (aka WiimoteThrottle.jyn **FOLDER** (no the py file) dropped into a throttle window toolbar
|
|
# press 1+2 on the Wiimote you want to use, it should connect
|
|
# connection will be validated by Wiimote vibrating and one of the LED turned on
|
|
#
|
|
# See JMRI output or log in case of issue.
|
|
#
|
|
# Customize at will.
|
|
# Unfortunately, this is only a classic remote, nothing with movements
|
|
#
|
|
# Default control:
|
|
# left / right : browse through throttles in instrumented window
|
|
# home : lights
|
|
# +/- : direction
|
|
# A : brake
|
|
# B : accelerate
|
|
# 1 : jump speed to "Cruise speed"
|
|
# 1 twice : jump speed to "Max speed"
|
|
# 2 : jump speed to "Slow Speed"
|
|
# 2 twice : jump speed to "Stop Speed"
|
|
# 1+2 : EStop
|
|
|
|
speedEStopSpeed = -1
|
|
speedStopSpeed = 0
|
|
speedSlowSpeed = 0.3
|
|
speedCruiseSpeed = 0.8
|
|
speedMaxSpeed = 1
|
|
|
|
valueSpeedTimerRepeat = 25 # repeat time in ms for speed set task
|
|
valueSpeedIncrement = 0.01
|
|
|
|
delay4double = 500 # delay for double tap on button (ms)
|
|
|
|
depErr="""Required dependency must be installed!
|
|
|
|
You need to install WiiRemoteJ library :
|
|
https://github.com/micromu/WiiRemoteJ/raw/master/WiiRemoteJ.jar
|
|
|
|
Copy the jar file your JMRI lib folder.
|
|
"""
|
|
|
|
import java
|
|
import java.awt
|
|
import java.awt.event
|
|
import java.beans
|
|
import java.util
|
|
import jmri
|
|
import jmri.jmrit.jython.Jynstrument as Jynstrument
|
|
import java.beans.PropertyChangeListener as PropertyChangeListener
|
|
import jmri.jmrit.throttle.interfaces.AddressListener as AddressListener
|
|
import javax.swing.Timer as Timer
|
|
import java.awt.event.ActionListener as ActionListener
|
|
import java.util.Calendar as Calendar
|
|
import java.lang.Runnable as Runnable
|
|
import thread
|
|
import javax.swing.SwingUtilities as SwingUtilities
|
|
import javax.swing.JButton as JButton
|
|
import javax.swing.ImageIcon as ImageIcon
|
|
import javax.swing.JOptionPane as JOptionPane
|
|
import javax.swing.JTextArea as JTextArea
|
|
import javax.swing.JFrame as JFrame
|
|
try:
|
|
import wiiremotej.event.WiiRemoteListener as WiiRemoteListener
|
|
import wiiremotej.event.WiiDeviceDiscoveryListener as WiiDeviceDiscoveryListener
|
|
import wiiremotej.WiiRemoteJ as WiiRemoteJ
|
|
import wiiremotej.event.WRButtonEvent as WRButtonEvent
|
|
except:
|
|
JOptionPane.showMessageDialog(JFrame(), JTextArea(depErr), "Missing dependency", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
|
class WiimoteThrottle(Jynstrument, PropertyChangeListener, AddressListener, WiiDeviceDiscoveryListener, WiiRemoteListener, Runnable):
|
|
#Wiimote discoverer events
|
|
def findFinished(self, nb):
|
|
print "Search finished, found ",nb ," wiimotes"
|
|
|
|
def wiiDeviceDiscovered(self, evt):
|
|
print "Found a Wiimote, number: ", evt.getNumber()
|
|
self.wiiDevice = evt.getWiiDevice()
|
|
ledLights = [False, False, False, False]
|
|
ledLights[evt.getNumber()%4] = True
|
|
self.wiiDevice.setLEDLights(ledLights)
|
|
self.wiiDevice.addWiiRemoteListener(self)
|
|
|
|
#Wiimote events
|
|
def buttonInputReceived(self, evt):
|
|
# print("Wiimote Button event: ", evt)
|
|
self.sync.acquire()
|
|
self.evt = evt
|
|
self.sync.release()
|
|
SwingUtilities.invokeLater(self) # Delegate processing to Swing thread (when we are here, we're in the WiiRemoteJ driver thread)
|
|
|
|
def run(self):
|
|
self.sync.acquire()
|
|
evt = self.evt
|
|
self.sync.release()
|
|
if (self.speedTimer != None):
|
|
self.speedTimer.stop() # In any case
|
|
# ThrottleFrames
|
|
if ( evt.wasReleased(WRButtonEvent.RIGHT) ): # NEXT
|
|
self.getContext().nextThrottleFrame()
|
|
if ( evt.wasReleased(WRButtonEvent.LEFT) ): # PREVIOUS
|
|
self.getContext().previousThrottleFrame()
|
|
if ( evt.wasReleased(WRButtonEvent.UP) ): # NEXT RUNNING
|
|
self.getContext().nextRunningThrottleFrame()
|
|
if ( evt.wasReleased(WRButtonEvent.DOWN) ): # PREVIOUS RUNNING
|
|
self.getContext().previousRunningThrottleFrame()
|
|
# No throttle assigned to current frame, browse through roster
|
|
if (self.throttle == None):
|
|
if ( evt.wasReleased(WRButtonEvent.HOME) ): # Assign selected roster entry
|
|
self.addressPanel.selectRosterEntry()
|
|
return
|
|
if ( evt.wasReleased(WRButtonEvent.PLUS) ): # Next roster entry
|
|
selectedIndex = self.addressPanel.getRosterSelectedIndex()
|
|
self.addressPanel.setIcon(False)
|
|
self.addressPanel.setVisible(True)
|
|
self.addressPanel.setRosterSelectedIndex(selectedIndex + 1)
|
|
return
|
|
if ( evt.wasReleased(WRButtonEvent.MINUS) ): # Previous roster entry
|
|
selectedIndex = self.addressPanel.getRosterSelectedIndex()
|
|
self.addressPanel.setIcon(False)
|
|
self.addressPanel.setVisible(True)
|
|
self.addressPanel.setRosterSelectedIndex(selectedIndex - 1)
|
|
return
|
|
# Throttle assigned to current frame, control it
|
|
if (self.throttle != None):
|
|
if ( evt.wasReleased(WRButtonEvent.HOME) ): # LIGHTS
|
|
self.throttle.setFunction(0, not self.throttle.getFunction(0) )
|
|
return
|
|
if ( evt.wasReleased(WRButtonEvent.PLUS) ): # FORWARD
|
|
self.throttle.setIsForward(True)
|
|
return
|
|
if ( evt.wasReleased(WRButtonEvent.MINUS) ): # BACKWARD
|
|
self.throttle.setIsForward(False)
|
|
return
|
|
# Speed control
|
|
if ( evt.isPressed(WRButtonEvent.B) ): # SPEED - increment
|
|
self.speedAction.setSpeedIncrement( valueSpeedIncrement )
|
|
self.speedTimer.start()
|
|
return
|
|
if ( evt.isPressed(WRButtonEvent.A) ): # SPEED - decrement
|
|
self.speedAction.setSpeedIncrement( -valueSpeedIncrement )
|
|
self.speedTimer.start()
|
|
return
|
|
# EStop
|
|
if ( evt.isPressed( WRButtonEvent.ONE | WRButtonEvent.TWO ) ): # estop = button1 + button2
|
|
self.throttle.setSpeedSetting( speedEStopSpeed )
|
|
self.lastTimeEStop = Calendar.getInstance().getTimeInMillis() # To cancel next inputs
|
|
self.wiiDevice.vibrateFor(750)
|
|
return
|
|
# Speed presets
|
|
if (Calendar.getInstance().getTimeInMillis() - self.lastTimeEStop > delay4double): # Delay for nothing after EStop
|
|
if (( evt.wasReleased(WRButtonEvent.TWO) ) and #STOP = button2 x2 or (button2 and CurrentSpeed = slow speed)
|
|
( (Calendar.getInstance().getTimeInMillis() - self.lastTimeButton2 < delay4double) or ( self.throttle.getSpeedSetting() == speedSlowSpeed ))):
|
|
self.throttle.setSpeedSetting( speedStopSpeed )
|
|
return
|
|
if ( evt.wasReleased(WRButtonEvent.TWO) ): # SLOW SPEED = button2
|
|
self.throttle.setSpeedSetting( speedSlowSpeed )
|
|
self.lastTimeButton2 = Calendar.getInstance().getTimeInMillis()
|
|
return
|
|
if (( evt.wasReleased(WRButtonEvent.ONE) ) and # MAX SPEED = button1x2 or (button1 and CurrentSpeed = cruise speed)
|
|
( (Calendar.getInstance().getTimeInMillis() - self.lastTimeButton1 < delay4double) or ( self.throttle.getSpeedSetting() == speedCruiseSpeed ))):
|
|
self.throttle.setSpeedSetting( speedMaxSpeed )
|
|
return
|
|
if ( evt.wasReleased( WRButtonEvent.ONE) ): # CRUISE SPEED = button1
|
|
self.throttle.setSpeedSetting( speedCruiseSpeed )
|
|
self.lastTimeButton1 = Calendar.getInstance().getTimeInMillis()
|
|
return
|
|
|
|
def disconnected(self):
|
|
self.wiiDevice = None
|
|
print("Lost wiimote")
|
|
|
|
def accelerationInputReceived(self, evt):
|
|
pass
|
|
def combinedInputReceived(self, evt):
|
|
pass
|
|
def extensionConnected(self, extension):
|
|
pass
|
|
def extensionDisconnected(self, extension):
|
|
pass
|
|
def extensionInputReceived(self, evt):
|
|
pass
|
|
def extensionPartiallyInserted(self):
|
|
pass
|
|
def extensionUnknown(self):
|
|
pass
|
|
def IRInputReceived(self, evt):
|
|
pass
|
|
def statusReported(self, evt):
|
|
print("Wiimote status reported: ", evt)
|
|
|
|
#Property listener part
|
|
def propertyChange(self, event):
|
|
self.speedTimer.stop()
|
|
if (event.propertyName.startswith("ThrottleFrame")) : # Current throttle frame changed
|
|
if event.oldValue != None :
|
|
event.oldValue.getAddressPanel().removeAddressListener(self)
|
|
if event.newValue != None :
|
|
self.addressPanel = event.newValue.getAddressPanel()
|
|
self.throttle = self.addressPanel.getThrottle()
|
|
self.speedAction.setThrottle( self.throttle )
|
|
self.addressPanel.addAddressListener(self)
|
|
|
|
#Jynstrument main and mandatory methods
|
|
def getExpectedContextClassName(self):
|
|
return "jmri.jmrit.throttle.ThrottleWindow"
|
|
|
|
def init(self):
|
|
self.getContext().addPropertyChangeListener(self) #ThrottleFrame change
|
|
self.addressPanel=self.getContext().getCurentThrottleController().getAddressPanel();
|
|
self.addressPanel.addAddressListener(self) # change of throttle in Current frame
|
|
self.throttle = self.getContext().getCurentThrottleController().getAddressPanel().getThrottle() # the throttle
|
|
self.speedAction = SpeedAction() #Speed increase thread
|
|
self.speedAction.setThrottle( self.throttle )
|
|
self.speedTimer = Timer(valueSpeedTimerRepeat, self.speedAction ) # Very important to use swing Timer object (see Swing and multithreading doc)
|
|
self.speedTimer.setRepeats(True)
|
|
self.label = JButton(ImageIcon(self.getFolder() + "/WiimoteThrottle.png","WiiMote")) #label
|
|
self.label.addMouseListener(self.getMouseListeners()[0]) # In order to get the popupmenu on the button too
|
|
self.add(self.label)
|
|
self.lastTimeButton1 = Calendar.getInstance().getTimeInMillis()
|
|
self.lastTimeButton2 = Calendar.getInstance().getTimeInMillis()
|
|
self.lastTimeEStop = Calendar.getInstance().getTimeInMillis()
|
|
self.wiiDevice = None
|
|
self.sync = thread.allocate_lock() # A lock protecting bellow self.evt
|
|
self.evt = None
|
|
java.lang.System.setProperty("bluecove.jsr82.psm_minimum_off", "true"); # Required for Bluecove + WiiRemoteJ
|
|
WiiRemoteJ.findRemotes(self, 1) # Search for 1 Wiimote, and call back
|
|
|
|
def quit(self):
|
|
self.speedTimer.stop()
|
|
WiiRemoteJ.stopFind()
|
|
if ((self.wiiDevice != None) and (self.wiiDevice.isConnected())):
|
|
self.wiiDevice.removeWiiRemoteListener(self)
|
|
self.wiiDevice.disconnect()
|
|
self.wiiDevice = None
|
|
self.speedAction = None
|
|
self.speedTimer = None
|
|
self.throttle = None
|
|
if (self.getContext() != None):
|
|
self.getContext().removePropertyChangeListener(self)
|
|
if (self.addressPanel != None):
|
|
self.addressPanel.removeAddressListener(self)
|
|
self.addressPanel = None
|
|
|
|
#AddressListener part: to listen for address changes in address panel (release, acquired)
|
|
def notifyAddressChosen(self, address):
|
|
pass
|
|
|
|
def notifyAddressThrottleFound(self, throttle):
|
|
self.speedTimer.stop()
|
|
self.throttle = throttle
|
|
self.speedAction.setThrottle( self.throttle )
|
|
|
|
def notifyAddressReleased(self, address):
|
|
self.speedTimer.stop()
|
|
self.throttle = None
|
|
self.speedAction.setThrottle( self.throttle )
|
|
|
|
def notifyConsistAddressChosen(self, address):
|
|
self.notifyAddressChosen(address)
|
|
|
|
def notifyConsistAddressThrottleFound(self, throttle):
|
|
self.notifyAddressThrottleFound(throttle)
|
|
|
|
def notifyConsistAddressReleased(self, address):
|
|
self.notifyAddressReleased(address)
|
|
|
|
# Speed timer class, to increase speed regularly once button pushed, thread stopped on button release
|
|
class SpeedAction(ActionListener):
|
|
def __init__(self):
|
|
self.sync = thread.allocate_lock() # Protects properties getter and setter
|
|
self.speedIncrement = 0
|
|
self.throttle = None
|
|
|
|
def setSpeedIncrement(self, si):
|
|
self.sync.acquire()
|
|
self.speedIncrement = si
|
|
self.sync.release()
|
|
|
|
def getSpeedIncrement(self):
|
|
self.sync.acquire()
|
|
si = self.speedIncrement
|
|
self.sync.release()
|
|
return si
|
|
|
|
def setThrottle(self, throt):
|
|
self.sync.acquire()
|
|
self.throttle = throt
|
|
self.sync.release()
|
|
|
|
def getThrottle(self):
|
|
self.sync.acquire()
|
|
throt = self.throttle
|
|
self.sync.release()
|
|
return throt
|
|
|
|
def actionPerformed(self, e):
|
|
throttle = self.getThrottle()
|
|
spi = self.getSpeedIncrement()
|
|
if (throttle != None) :
|
|
ns = throttle.getSpeedSetting() + spi
|
|
if (ns < 0 ) :
|
|
ns = 0
|
|
if (ns > 1 ) :
|
|
ns = 1
|
|
throttle.setSpeedSetting( ns )
|
|
|