This page is about how JMRI connects to external systems, e.g. DCC systems.
There's a lot of variation within JMRI on this, so you'll have to go through any specific implementation. Specifically, older systems weren't always arranged this way, so existing code may not be a good example.
See also the Multiple Connection Update page.
The code for a
general type, like "LocoNet connections" or "NCE connections", should be gathered in a
specific package right under jmri.jmrix
e.g.
jmri.jmrix.loconet
and jmri.jmrix.nce
.
In the preferences dialog and JmrixConfigPane
main configuration code, this
level is called the "manufacturer selection". It provides a level of grouping, which we may
someday want to use for e.g. providing separate updates for specific hardware, while still
separating the system-specific code from the system-independent parts of JMRI.
Within that, the code should be separated further by putting specific hardware options into their own subpackages, for example
jmri.jmrix.loconet.locobuffer
vs
jmri.jmrix.loconet.locobufferusb
vs. jmri.jmrix.loconet.pr3
vs.
jmri.jmrix.loconet.locormi
jmri.jmrix.nce.serialdriver
vs. jmri.jmrix.nce.usbdriver
vs.
jmri.jmrix.nce.simulator
vs. jmri.jmrix.nce.networkdriver
Additional subpackages can be used grouping various functions as needed. For example, Swing-based tools should go in their own swing subpackage or at a further level within the swing subpackage.
The key to normal operation (after start up and before shut down) is a SystemConnectionMemo
object that provides all necessary access to the system connection's objects. For example,
the LocoNetSystemConnectionMemo
provides access to a number of LocoNet-specific objects and LocoNet-specific implementations
of common objects. Although some of those (e.g. a SensorManager) might be separately
available from the InstanceManager, accessing them from a SystemConnectionMemo allows you to
find the consistent set associated with one specific connection of a multiple-connection
setup, even when there are multiple connections of a specific type. There are also a few
tools that work with the SystemConnectionManager
objects themselves after
obtaining them from the InstanceManager
.
We don't directly persist the SystemConnectionMemo. This is partly for historical reasons, but it also reflects the level of abstraction: A SystemConnectionMemo is at the level of a "LocoNet connection" or a "NCE connection", and there's a lot of specific information below it to configure one of many possible such connections.
Instead, configuration of the connection is from the bottom up: From the most specific code up to the general. The "Adapter" object connects directly to the system, e.g. managing a serial link, and then builds up the objects that work with that link, including all the various type managers. This makes sense because the type of the connection is really specified via the type of that link and what's on the other end of it.
This section describes the LocoNet implementation of the new (post-multiple) configuration system. This is similar for LocoBuffer, LocoBuffer-USB, PR3, etc connections, but we use the specific LocoBuffer-USB case for concreteness. This sequence picks up after the basic startup of the application itself, see the App Structure page for that.
There are several objects involved in startup:
ConnectionConfigXml
object, created by the ConfigureXML system as part of reading the preferences. It
drives the process.
ConnectionConfig
object, registered so that a later store of the
preferences will write out the right stuffAdapter
object of a very specific type, which handles both the
connection to the system hardware, and (through its configure()
method) the
creation of the rest of the system.The profile XML file contains a connection element that drives the configuration:
<connection xmlns="" class="jmri.jmrix.loconet.locobufferusb.configurexml.ConnectionConfigXml" disabled="no" manufacturer="Digitrax" port="/dev/tty.usbserial-LWPMMU13" speed="57,600 baud" systemPrefix="L" userName="LocoNet"> <options> <option> <name>CommandStation</name> <value>DCS50 (Zephyr)</value> </option> <option> <name>TurnoutHandle</name> <value>Normal</value> </option> </options> </connection>
Initialization proceeds through multiple steps (click on the diagram to expand it):
jmri.jmrix.loconet.locobufferusb.configurexml.ConnectionConfigXml
is
constructed by the configurexml mechanism when the specific class is named by the file
during the initial preference load at application
startup.
load(..)
on the
ConnectionConfigXml object. This is implemented in
jmri.jmrix.configurexml.AbstractSerialConnectionConfigXml which does:
getInstance()
which initializes an adapter
member
implementing SerialPortAdapter
.
In this case, getInstance()
is implemented in
jmri.jmrix.loconet.locobufferusb.configurexml.ConnectionConfigXml
and
assigns a
jmri.jmrix.loconet.locobufferusb.LocoBufferUsbAdapter
to the "adapter"
member of "ConnectionConfigXml" That's used later on to configure the port, etc.loadCommon(shared, perNode, adapter)
from the base class, which
brings in common information:
loadOptions(perNode.getChild("options"),
perNode.getChild("options"), adapter)
to do any additional handling of info
coded in an <options>
element. Although overridden in some
cases, the default for this is to invoke adapter.setOptionState(name,
value)
In this LocoNet case, that stores the command station name, see the
element above.register()
, which is implemented in
jmri.jmrix.loconet.locobufferusb.configurexml.ConnectionConfigXml
by
invoking this.register(new ConnectionConfig(adapter))
, which in turn is
implemented in jmri.jmrix.configurexml.AbstractConnectionConfigXml
as
protected void register(ConnectionConfig c) { c.register(); }The
ConnectionConfig c
here is of type
jmri.jmrix.loconet.locobufferusb.ConnectionConfig
which extends
jmri.jmrix.AbstractSerialConnectionConfig
which extends
jmri.jmrix.AbstractConnectionConfig
.AbstractConnectionConfig
, finally, register()
does:
this.setInstance(); InstanceManager.getDefault(jmri.ConfigureManager.class).registerPref(this); ConnectionConfigManager ccm = InstanceManager.getNullableDefault(ConnectionConfigManager.class); if (ccm != null) { ccm.add(this); }
this.setInstance()
call is implemented in
jmri.jmrix.loconet.locobufferusb.ConnectionConfig
to set the "adapter"
member there to a new LocoBufferUsbAdapter
object. Note that this
"adapter" is from the ConnectionConfig (specifically AbstractConnectionConfig)
object, not the ConnectionConfigXml object referred to above. In the sequence we're
showing here, the LocoBufferUsbAdapter
object had already been created
by getInstance in ConnectionConfigXml
, and passed to the
ConnectionConfig
object when it's created inside the
register()
sequence.
At this point, we have a
jmri.jmrix.loconet.locobufferusb.ConnectionConfig
object registered for
persistence, so it can be written out later.
adapter.openPort(portName, "JMRI")
.
This uses code specific to the adapter
member that was initialized in
getInstance()
, i.e. in this case LocoBuffer-USB code.adapter
object,
initialize the operation of the system by calling adapter.configure()
method. That Adapter configure()
method does (through the general LocoBufferAdapter
superclass) (this is given as a sample, ignore the details):
setCommandStationType(getOptionState(option2Name)); setTurnoutHandling(getOptionState(option3Name)); // connect to a packetizing traffic controller LnPacketizer packets = new LnPacketizer(); packets.connectPort(this); // create memo and load this.getSystemConnectionMemo().setLnTrafficController(packets); this.getSystemConnectionMemo().configureCommandStation(commandStationType, mTurnoutNoRetry, mTurnoutExtraSpace); this.getSystemConnectionMemo().configureManagers(); // start operation packets.startThreads();
The getSystemConnectionMemo
is in the common
LocoBufferAdapter
superclass. (There's some code in the inheritance
chain that does some casting that should someday be cleaned up)
At this point, the system is basically up and ready for operation.
jmri.jmrix.loconet.LocoNetSystemConnectionMemo
object is created and registered with the InstanceManager.jmri.jmrix.ActiveSystemsMenu
and/or jmri.jmrix.SystemsMenu
will create the main menu bar menus for the individual systems:
loconet.swing.LnComponentFactory
)LocoNetMenu
) and
post that to the GUI.jmrix.PortAdapter
interface, JMRI has two different forms for those: jmrix.SerialPortAdapter
(Serial/USB connections) and jmrix.NetworkPortAdapter
(network connections).
Abstract base classes implement those as jmrix.AbstractSerialPortController
(Serial/USB connections) and jmrix.AbstractNetworkPortController
(network connections) (most, but not all, systems use one of those) with a common base of
jmrix.AbstractPortController
.
These in turn are inherited into the system-specific classes, e.g loconet.LnPortController
and loconet.LnNetworkPortController
respectively (see UML diagrams on those linked Javadoc pages).
Because Java doesn't allow multiple inheritance, the system-specific descendants of the two abstract base classes can't actually share a single common system-specific base class. This results in some code duplication in e.g. serial/USB connections vs the network connection classes in the system-specific classes.
Abstract*PortAdapter <- Sys*PortController <- Sys*PortAdapter
jmrix.AbstractStreamPortController
fit into the PortAdapter
class hierarchy? (there is no *StreamPortController as defined in its header; it
extends AbstractPortController)For a more complex example, consider C/MRI, which has more content in its
<connection>
element:
<connection userName="C/MRI" systemPrefix="C" manufacturer="C/MRI" disabled="no" port="(none selected)" speed="9,600 baud" class="jmri.jmrix.cmri.serial.sim.configurexml.ConnectionConfigXml"> <options /> <node name="0"> <parameter name="nodetype">2</parameter> <parameter name="bitspercard">32</parameter> <parameter name="transmissiondelay">0</parameter> <parameter name="num2lsearchlights">0</parameter> <parameter name="pulsewidth">500</parameter> <parameter name="locsearchlightbits">000000000000000000000000000000000000000000000000</parameter> <parameter name="cardtypelocation">1122221112000000000000000000000000000000000000000000000000000000</parameter> </node> <node name="1"> <parameter name="nodetype">1</parameter> <parameter name="bitspercard">24</parameter> <parameter name="transmissiondelay">0</parameter> <parameter name="num2lsearchlights">0</parameter> <parameter name="pulsewidth">500</parameter> <parameter name="locsearchlightbits">000000000000000000000000000000000000000000000000</parameter> <parameter name="cardtypelocation">2210000000000000000000000000000000000000000000000000000000000000</parameter> </node> <node name="2"> <parameter name="nodetype">2</parameter> <parameter name="bitspercard">32</parameter> <parameter name="transmissiondelay">0</parameter> <parameter name="num2lsearchlights">0</parameter> <parameter name="pulsewidth">500</parameter> <parameter name="locsearchlightbits">000000000000000000000000000000000000000000000000</parameter> <parameter name="cardtypelocation">2212120000000000000000000000000000000000000000000000000000000000</parameter> </node> <node name="4"> <parameter name="nodetype">1</parameter> <parameter name="bitspercard">24</parameter> <parameter name="transmissiondelay">0</parameter> <parameter name="num2lsearchlights">0</parameter> <parameter name="pulsewidth">500</parameter> <parameter name="locsearchlightbits">000000000000000000000000000000000000000000000000</parameter> <parameter name="cardtypelocation">2210000000000000000000000000000000000000000000000000000000000000</parameter> </node> </connection>
How this gets read in.
Implications for internal structure.
Proper internal structure
See jmrix.JmrixConfigPane Javadoc for links to configuration elements. (Is there another place that the configuration process and preferences support is described? If so, it should be linked from here.)
Any particular system connection is included in the preferences by being listed in the
java/src/META-INF/services/jmri.jmrix.ConnectionTypeList
list. This file is
normally generated from the @ServiceProvider(service = ConnectionTypeList)
class-level annotations.
Any particular system connection is included in the preferences by
being listed in the target/classes/META-INF/services/jmri.jmrix.ConnectionTypeList
list. This file is normally
generated from the @ServiceProvider(service = ConnectionTypeList)
class-level annotations.
# Providers of System Connections type lists in Preferences # Order is Insignificant jmri.jmrix.internal.InternalConnectionTypeList jmri.jmrix.lenz.LenzConnectionTypeList ... jmri.jmrix.loconet.LnConnectionTypeList ...This provides the contents for the 1st-level selection in the top JComboBox, e.g. in this case "Digitrax". This (generally) corresponds to selecting a system package within the JMRI package that might contain multiple variants of a specific connection. Within
JmrixConfigPane
this is called the "manufacturer" selection.
The contents of the
jmri.jmrix.loconet.LnConnectionTypeList
, an instance of jmri.jmrix
.ConnectionTypeList
then provides the contents for the second-level JComboBox of
specific connection types, each corresponding (generally) to a specific
ConnectionConfig
implementation that can configure a specific connection type.
Within JmrixConfigPane
this is called the "mode" selection.
Filling the details
JPanel is done within the ConnectionConfig
object via a call to loadDetails()
. In many cases, including this LocoNet
example, that's referred up to a base class:
AbstractSerialConnectionConfig
handles connections through serial links
that need specification of serial port name, baud rate, etc.
AbstractNetworkConnectionConfig
handles connections through network
(TCP) connections that need specification of network address, port, etc.
AbstractStreamConnectionConfig
handles configuration of connections
based on streams.
AbstractSimulatorConnectionConfig
handles configuration of simulated
connections.
JmrixConfigPane
first clears the existing
contents of the details
JPanel with removeAll()
, then calls the
JmrixConfigPane.selection()
method to refill it.
jmri.swing.ConnectionLabel
class is a Swing JLabel that listens to a single connection and displays its status. We use
those on the main splash screen, but they can also be used in other places.