322 lines
14 KiB
Python
322 lines
14 KiB
Python
# Connect a RailDriver Modern Desktop (USB device) to a throttle.
|
|
#
|
|
# ATTENTION: Special version for HidRawEnvironmentPlugin of JInput
|
|
# ATTENTION: Currently works in Windows only
|
|
#
|
|
# See <http://jmri.org/help/en/html/hardware/raildriver/index.shtml>
|
|
#
|
|
# Author: Joan Carranc, 2010
|
|
# - Based on the original RailDriver.py, Bob Jacobsen, copyright 2008
|
|
# - Throttle window management and roster selection based on xboxThrottle.py, Andrew Berridge, copyright 2010
|
|
# Part of the JMRI distribution
|
|
#
|
|
# This is still experimental code and is under development. Currently, it supports:
|
|
# 1. Each controller will open its own throttle window, identified by hashCode
|
|
# 2. All blue buttons assigned to functions (0-27)
|
|
# 3. Leftmost lever as reverser - front moves forwards, back reverses
|
|
# 4. Second from left lever as throttle - back increases speed
|
|
# 5. Selection of locos using the "zoom" rocker switch. Up selects a loco, Down dispatches
|
|
# current loco.
|
|
# 6. Selection of loco addresses / throttle panels using the "pov" four-way switch.
|
|
# Using up and down on the pov switch will move between locos in the roster
|
|
# (as long as the last loco has been "dispatched")
|
|
# Using left and right on the pov switch will select between throttle panels... Click the
|
|
# plus (+) button on the throttle window to add another panel.
|
|
# 7. Emergency stop! Push the corresponding button(s). This will e-stop the current throttle.
|
|
# 8. Gear buttons: High/Low range can be selected via these buttons, mapped to "shuntFn" function
|
|
# 9. Cab buttons: alerter, sander, pantograph and bell buttons can be mapped to a function
|
|
#10. Horn momentary lever: mapped to "hornFn" function
|
|
#
|
|
# Future development ideas:
|
|
# 1. Think of functionalities for the other levers
|
|
# 2. Other uses for the display ??
|
|
# 3. Calibration ??
|
|
#
|
|
##
|
|
# IMPORTANT Warnings:
|
|
# 1. Make sure you calibrate and set a "Dead zone" for each of the analogue levers in the
|
|
# Calibration window! If you don't there will be too many events triggered
|
|
# and everything will slow right down....
|
|
|
|
import jmri
|
|
import java
|
|
import java.beans
|
|
|
|
try:
|
|
#
|
|
# Set the name of the controller you're using
|
|
#
|
|
desiredControllerName = "RailDriver Modern Desktop"
|
|
|
|
#
|
|
# Some function numbers, specific to decoder type / CV mapping
|
|
#
|
|
bellFn = 1
|
|
hornFn = 2
|
|
shuntFn = 3 # Lenz Silver and Gold
|
|
alertFn = 6
|
|
sandFn = 7
|
|
pantoFn = 8
|
|
|
|
#
|
|
# Component 'mapping'
|
|
#
|
|
# Two rows of Blue Buttons: Functions 0-27
|
|
componentMaxFunction = 27 # Max function to use for blue buttons
|
|
# Special Blue Buttons: Rocker switch and pov buttons
|
|
componentSelectAddr = "28" # Acquire the current address shown on the list
|
|
componentDispatchAddr = "29" # Release the previous address
|
|
componentNextAddr = "32" # Scroll down to the next address on the list
|
|
componentPrevAddr = "30" # Scroll up to the previous address on the list
|
|
componentNextFrame = "31" # Move the next Throttle Frame
|
|
componentPrevFrame = "33" # Move to the previous Throttle Frame
|
|
# Cab buttons
|
|
componentHiRange = "34" # Gear momentary button (up) to use for High range
|
|
componentLoRange = "35" # Gear momentary button (down) to use for Low range / Shunting
|
|
componentEStop = "36" # E-Stop momentary button (up)
|
|
componentEStopBis = "37" # E-Stop momentary button (down)
|
|
componentAlert = "38" # Alert button
|
|
componentSand = "39" # Sand button
|
|
componentPanto = "40" # Panto button
|
|
componentBell = "41" # Bell button
|
|
componentHorn = "42" # Horn momentary lever (up)
|
|
componentHornBis = "43" # Horn momentary lever (down)
|
|
# Axes
|
|
componentReverser = "Axis 0"
|
|
componentThrottle = "Axis 1"
|
|
leverMin = 0.05 # value of component for zero speed
|
|
leverMax = 1.00 # value of component for max speed
|
|
#
|
|
tempMillis = 500 # milliseconds to show (temporary) the press of a button
|
|
|
|
# From here down is the code for the throttle
|
|
# Generally, you shouldn't touch it unless you're debugging a problem
|
|
|
|
# uncomment the following two lines to run from a keyboard for debug ??
|
|
#desiredControllerName = "Keyboard"
|
|
#componentThrottle = "Axis 1"
|
|
|
|
# connect to USB device
|
|
model = jmri.jmrix.jinput.TreeModel.instance()
|
|
|
|
#Keep track of the number of throttles created
|
|
numThrottles = 0
|
|
# and the number of controllers used
|
|
numControllers = 0
|
|
|
|
def isNaN(num):
|
|
return num != num
|
|
|
|
# add listener for USB events
|
|
class TreeListener(java.beans.PropertyChangeListener):
|
|
|
|
#hash code is unique instance of controller
|
|
controllerHashCode = 0
|
|
throttleWindow = None
|
|
controlPanel = None
|
|
functionPanel = None
|
|
addressPanel = None
|
|
activeThrottleFrame = None
|
|
|
|
def __init__(self, controller):
|
|
global numThrottles
|
|
global numControllers
|
|
self.controller = controller
|
|
self.controllerHashCode = controller.hashCode()
|
|
# open a throttle window and get components
|
|
self.throttleWindow = jmri.InstanceManager.getDefault(jmri.jmrit.throttle.ThrottleFrameManager).createThrottleWindow()
|
|
self.activeThrottleFrame = self.throttleWindow.addThrottleFrame()
|
|
# move throttle on screen so multiple throttles don't overlay each other
|
|
self.throttleWindow.setLocation(400 * numThrottles, 50 * numThrottles)
|
|
numThrottles += 1
|
|
numControllers += 1
|
|
self.activeThrottleFrame.toFront()
|
|
self.controlPanel = self.activeThrottleFrame.getControlPanel()
|
|
self.functionPanel = self.activeThrottleFrame.getFunctionPanel()
|
|
self.addressPanel = self.activeThrottleFrame.getAddressPanel()
|
|
self.controller.displayStrImm("hi")
|
|
#self.controller.displayNumNext(self.addressPanel.getCurrentAddress().getNumber())
|
|
self.throttleWindow.addPropertyChangeListener(self)
|
|
self.activeThrottleFrame.addPropertyChangeListener(self)
|
|
|
|
def propertyChange(self, event):
|
|
if (event.propertyName == "ancestor"):
|
|
#print "ancestor property change - closing throttle window"
|
|
# Remove all property change listeners and
|
|
# dereference all throttle components
|
|
self.activeThrottleFrame.removePropertyChangeListener(self)
|
|
self.throttleWindow.removePropertyChangeListener(self)
|
|
self.activeThrottleFrame = None
|
|
self.controlPanel = None
|
|
self.functionPanel = None
|
|
self.addressPanel = None
|
|
self.throttleWindow = None
|
|
self.controller.displayStrImm("bye")
|
|
# Now remove this propertyChangeListener from the model
|
|
global model
|
|
model.removePropertyChangeListener(self)
|
|
|
|
if (event.propertyName.startswith("ThrottleFrame")) : # Current throttle frame changed
|
|
#print "Throttle Frame changed"
|
|
self.addressPanel = event.newValue.getAddressPanel()
|
|
self.controlPanel = event.newValue.getControlPanel()
|
|
self.functionPanel = event.newValue.getFunctionPanel()
|
|
if (self.addressPanel.getCurrentAddress() != None) :
|
|
self.controller.displayNumNext(self.addressPanel.getCurrentAddress().getNumber())
|
|
|
|
if (event.propertyName == "Value") :
|
|
# event.oldValue is the UsbNode
|
|
#
|
|
# uncomment the following line to see controller names
|
|
#print "|"+event.oldValue.getController().toString()+"|"
|
|
#print event.oldValue.getController().getName()
|
|
#print event.oldValue.getController().hashCode()
|
|
#
|
|
# Select just the device (controller) we want
|
|
cont = event.oldValue.getController()
|
|
if (cont.toString() == desiredControllerName
|
|
and cont.hashCode() == self.controllerHashCode) :
|
|
# event.newValue is the value, e.g. 1.0
|
|
# Check for desired component and act
|
|
component = event.oldValue.getComponent().getIdentifier().toString()
|
|
value = event.newValue
|
|
#
|
|
# uncomment the following to see the entries
|
|
print component, value
|
|
|
|
# Function buttons
|
|
try:
|
|
fNum = int(component) # direct mapping of buttons 0 -> maxFunction
|
|
except ValueError:
|
|
fNum = 99 # axis
|
|
# cab button mapping
|
|
if (component == componentAlert) :
|
|
fNum = alertFn
|
|
if (component == componentSand) :
|
|
fNum = sandFn
|
|
if (component == componentPanto) :
|
|
fNum = pantoFn
|
|
if (component == componentBell) :
|
|
fNum = bellFn
|
|
# toggle / fixed setting depending on throttle button definition
|
|
if fNum <= componentMaxFunction: # component out of range (not a blue button or cab button with special mapping)
|
|
button = self.functionPanel.getFunctionButtons()[fNum]
|
|
if (button != None) :
|
|
if button.getIsLockable() :
|
|
if value > 0.5 :
|
|
button.setSelected(not button.getState())
|
|
else :
|
|
button.setSelected(value > 0.5)
|
|
if (value > 0.5 and button.getState()) : # only display if actually setting the function
|
|
cont.displayStrTemp("F" + str(fNum)) #, tempMillis)
|
|
return
|
|
|
|
# Address and Throttle Frame selection
|
|
#print "addr: " + self.addressPanel.getCurrentAddress().toString()
|
|
#self.addressPanel.showRosterSelectorPopup()
|
|
selectedIndex = self.addressPanel.getRosterSelectedIndex()
|
|
|
|
if (component == componentNextAddr and value > 0.5):
|
|
self.addressPanel.setRosterSelectedIndex(selectedIndex + 1)
|
|
return
|
|
|
|
if (component == componentPrevAddr and value > 0.5):
|
|
self.addressPanel.setRosterSelectedIndex(selectedIndex - 1)
|
|
return
|
|
|
|
if (component == componentSelectAddr and value > 0.5):
|
|
cont.displayStrTemp("sel") #, tempMillis)
|
|
self.addressPanel.selectRosterEntry()
|
|
cont.displayNumNext(self.addressPanel.getCurrentAddress().getNumber())
|
|
return
|
|
|
|
if (component == componentDispatchAddr and value > 0.5):
|
|
cont.displayStrTemp("dis") #, tempMillis)
|
|
self.addressPanel.dispatchAddress()
|
|
cont.displayNumNext(0)
|
|
return
|
|
|
|
if (component == componentNextFrame and value > 0.5):
|
|
self.throttleWindow.nextThrottleFrame()
|
|
return
|
|
|
|
if (component == componentPrevFrame and value > 0.5):
|
|
self.throttleWindow.previousThrottleFrame()
|
|
return
|
|
|
|
# Special buttons
|
|
# "Emergency stop" button
|
|
if (component == componentEStop or component == componentEStopBis) :
|
|
if (value > 0.5) :
|
|
self.controlPanel.stop()
|
|
print "Emergency Stop!"
|
|
return
|
|
|
|
# "High Range" button, fixed setting
|
|
if (component == componentHiRange) :
|
|
if (value > 0.5) :
|
|
button = self.functionPanel.getFunctionButtons()[shuntFn]
|
|
button.setSelected(False)
|
|
return
|
|
|
|
# "Low Range" button, fixed setting
|
|
if (component == componentLoRange) :
|
|
if (value > 0.5) :
|
|
button = self.functionPanel.getFunctionButtons()[shuntFn]
|
|
button.setSelected(True)
|
|
return
|
|
|
|
# "Horn" (digital) lever, momentary
|
|
if (component == componentHorn or component == componentHornBis) :
|
|
if (value > 0.5) :
|
|
self.functionPanel.getFunctionButtons()[hornFn].setSelected(True)
|
|
else :
|
|
self.functionPanel.getFunctionButtons()[hornFn].setSelected(False)
|
|
return
|
|
|
|
# Reverser lever
|
|
if (component == componentReverser) :
|
|
# negative is lever front, positive is lever back, (-0.3 to 0.3) is dead zone
|
|
if (value < 0.3) :
|
|
cont.displayStrTemp("fwd") #, tempMillis)
|
|
self.controlPanel.setForwardDirection(True)
|
|
if (value > 0.3) :
|
|
cont.displayStrTemp("rev") #, tempMillis)
|
|
self.controlPanel.setForwardDirection(False)
|
|
#print "Direction changed"
|
|
return
|
|
|
|
# Throttle lever
|
|
if (component == componentThrottle) :
|
|
# negative is lever front, positive is lever back
|
|
# limit range to only positive side of lever
|
|
if (value < leverMin) : value = leverMin
|
|
if (value > leverMax) : value = leverMax
|
|
# convert fraction of input to speed step
|
|
fraction = (value-leverMin)/(leverMax-leverMin)
|
|
slider = self.controlPanel.getSpeedSlider()
|
|
setting = int(round(fraction*(slider.getMaximum()-slider.getMinimum()), 0))
|
|
slider.setValue(setting)
|
|
cont.displayNumTemp(self.controlPanel.getDisplaySlider())
|
|
#print "Throttle:", value, setting
|
|
# How do I get the speed "value" ???
|
|
print "Slider Speed:", self.controlPanel.getDisplaySlider()
|
|
return
|
|
|
|
#Iterate over the controllers, creating a new listener for each
|
|
#controller of the type we are interested in
|
|
for c in model.controllers():
|
|
name = c.getName()
|
|
hashCode = c.hashCode()
|
|
if (name == desiredControllerName and c.getType().toString() == "Gamepad"):
|
|
print "Found " + name + " " + str(hashCode)
|
|
model.addPropertyChangeListener(TreeListener(c))
|
|
except:
|
|
import javax.swing.JOptionPane as JOptionPane
|
|
import javax.swing.JFrame as JFrame
|
|
JOptionPane.showMessageDialog(JFrame(),
|
|
"""This code is no longer maintained.
|
|
|
|
Please use Jynstrument jython/Jynstrument/ThrottleWindowToolBar/USBThrottle.jyn instead
|
|
Or jython/USBThrottleAsJynstrument.py script""", "Outdated code", JOptionPane.WARNING_MESSAGE)
|