Support: The JMRI LocoNet® implementation
This page describes various high-level structures of the JMRI LocoNet® implementation. Please also read the Javadocs for the jmrix.loconet package.
LocoNet-specific values
The LnConstants class provides static, final constants to represent various fields and values in LocoNet messages. At some point, some of this should be built into to the specific classes (i.e. LocoNetMessage) so the coding and decoding algorithms don't have to appear in so many places.
Sending and receiving LocoNet messages
The LocoNetInterface class provides the basic connection to a LocoNet for user classes. Messages are sent by passing them to a LocoNetInterface implementation, and you can register with a LocoNetInterface to be notified of all LocoNet traffic.
The LocoNetMessage class represents the basic message. Currently (since July 2001), this class doesn't really help other code construct and decode LocoNet packets, but rather just contains them. This should be improved.
The steps to send a message to the LocoNet are:
- Create a LocoNetMessage object, and fill it with the message you want to send. It's not necessary to fill in the error-check byte; that will be done as part of sending.
- Locate an object providing a
LocoNetInterface interface. In many cases, the
LnTrafficController is providing this, and the object
can be located with
LocoNetInterface l = LnTrafficController.instance();
Since JMRI 4.11.6 The LnTrafficController is connected to a given LocoNetSystemConnectionMemo, and the object can be located withLocoNetInterface l = memo.getLnTrafficController();
- Send the message:
l.sendLocoNetMessage(msg);
Classes that want to receive inbound LocoNet packets should implement the LocoNetListener interface, and register their desire to listen via an object with a LocoNetInterface interface. It's important to note that listener objects can't assume that they receive incoming LocoNet messages in any specific thread. In particular, they should not assume that they receive these messages in a GUI thread, so they'll have to forward any changes to the user interface.
Implementing the LocoNet connection
Implementing communication with a real LocoNet is handled by classes that implement the LocoNetListener interface. There are currently three (see below): LnTrafficController and its subclasses LnPacketizer and LnTrafficRouter.
LnTrafficController
The LnTrafficController abstract class provides some common implementation for it's subclasses, and adds a mechanism to find a usable LocoNetInterface implementation.
The routine addLocoNetListener and removeLocoNetListener methods are implemented here, along with a notify method to forward LocoNetMessages to the listeners.
Until JMRI version 4.11.5 the static instance() method
was used by a large number of
jmrix.loconet classes to find a LocoNetInterface for
transmitting and receiving messages. It worked through a
"self" static member, which was initialized when a
LnTrafficController subclass object was created. All objects
wanting to send or receive over the LocoNet will thence use
the last-created LnTrafficController implementation.
See the section on "Startup" for
more information on this.
LnPacketizer
The LnPacketizer class extends the LnTrafficController implementation to send and receive packets over a LocoBuffer serial link to a LocoNet. It works with an implementation of the LnPortController - Abstract class, which works at the level of character streams. These communicate through Java streams which carry the LocoNet messages as character sequences. LnPortController implementations are available for the LocoBuffer, MS100 and for reading from a hex log file.
It uses separate threads for transmission and reception of characters from the streams. The receive operation is done in a thread so it can easily stall when no messages are available. The transmit operation is done in a thread for a similar reason; sometimes a LocoBuffer will shut off input (output from the program), which causes the stream write operations to stall. By doing those in a separate thread, we can detect or at least bypass this without the entire program coming to a stop.
LnTrafficRouter
The
LnTrafficRouter class provides a scatter-gather operation
for the LocoNetListener interface. Note that this
implementation doesn't transform the LocoNetMessages into
serial traffic.
Note the LnTrafficRouter object. It provides a
LocoNetInterface for all the LocoNet-using messages in the
remote node, so that only one copy of each message has to
travel across the remote link.
Note that the "some remote comm class" could also be implemented as a subclass of LnTrafficRouter, instead of communicating with one.
Startup
There are "action" classes that connect to an input source.
The main program puts these in a menu, on a button, etc,
so that the user can select the connection desired.
The current (March 2002) set is:
- LocoBufferAction - creates a LocoBufferAdapter object and configures for operation with a LocoBuffer
- MS100Action - creates a MS100Adapter object and configures for operation with an MS100
- LnHexFileAction - creates an LnHexFilePort object and configures for operation as "LocoNet Simulator" (previously, this was known as "LocoNet Hex File", as it can load contents from a file of hexadecimal strings)
- Typically a LnPacketizer, which becomes the "instance" for objects that use memo.getLnTrafficController() to locate an interface.
- A ProgrammerInstance
- A PowerManagerInstance
- A TurnoutManagerInstance
The configure() methods in the various adapter classes do this. That's not a very general mechanism. Although a LnPacketizer is the right thing to connect to each of the serial port adapters, the rest of the configuration might vary.
Port adapters
LnPortController is an abstract base class to carry common implementations for the Adapter classes that connect to serial ports with specific protocols.MS100
Note that the current MS100 implementation is not as robust as we really need it to be. In particular, back-off and retransmit is not being checked. Other interface devices, such as the Digitrax PR3, the LocoBuffer, LocoBuffer-II, and LocoBuffer-USB all implement an internal microcontroller which handles back-off and retransmit operations properly and are therefore preferred over the MS100.
The MS100Action class (package jmrix.loconet.ms100) starts up a LocoNet connection via a MS100. When triggered, it creates a visible MS100Frame object.
In turn, the MS100Frame creates an MS100Adapter object, then shows the available comm ports, allowing the user to pick one. The MS100Adapter object implements the LnPortController interface, so it can eventually connect an LnTrafficController to a serial port and MS100.
When the "Open MS100 port" button is pressed, the MS100Frame object
- passes the selected communication port to the MS100Adapter. The MS100 adapter then creates a new LocoNetSystemConnectionMemo() object, connects to that port and creates input and output streams
-
Since JMRI 4.11.6
then makes sure that a LnTrafficController object
exists by calling the memo.getLnTrafficController() method
(providing multi-connection support and replacing the use of LnTrafficController.instance() used before) - connects that LnTrafficController instance to the MS100Adapter (subclass of LnPortController)
- starts LnTrafficController in a new thread so that it can handle inbound messages asynchronously.
LocoBuffer
Very similar to the MS100 case, with the same sequence of operations. The port setup is somewhat different. Classes are in the jmrix.loconet.locobuffer package.
HexFile
The HexFile classes (package jmrix.loconet.hexfile) are meant to simulate a LocoNet connection from a data file. They provide the "LocoNet Simulator" connection type. A hexadecimal format data file feeds in messages as if they came from an outside connection.
Initialization is provided by the HexFileAction class. When triggered, it creates a visible HexFileFrame object. This provides a button the user can use to select an input file.
When a file is selected, the HexFileFrame object
- creates a HexFileAdapter object connected to that file
- The HexFileAdapter adapter then creates a new LocoNetSystemConnectionMemo() object, connects to that port and creates input and output streams
-
Since JMRI 4.11.6
then makes sure that a LnTrafficController object
exists by calling the memo.getLnTrafficController() method
(providing multi-connection support and replacing the use of LnTrafficController.instance() used before) - connects that LnTrafficController instance to the HexFileAdapter (subclass of LnPortController)
- starts LnTrafficController in a new thread so that it can handle inbound messages asynchronously.
Unlike the MS100Frame and LocoBufferFrame objects, the HexFileFrame object stays visible so that it can control the flow of messages from the file.
Slot and Programmer management
"Slots" are basic to the operation of a LocoNet command station. They are represented by the LocoNetSlot class. Like LocoNetMessage, this class does not (yet) provide a lot of support for creating and decoding slot status. The SlotManager class listens to LocoNet traffic to keep an up-to-date idea of the command stations slot contents. It ma someday be necessary for the SlotManager to actively communicate with the command station to update that information, but for not the SlotManager only listens to slot change commands that originate on the LocoNet or are transmitted from the program.
The SlotListener interface should be implemented by any class that wants to be notified when a slot changes.
Because Digitrax command stations handle programming via a special reserved slot, the jmri.Programmer interface is also implemented by the loconet.SlotManager class. This greatly complicates the class, but is acceptable for now.
LocoNet® is a registered trademark of Digitrax, Inc.