JMRI Web Access
Basic Use
JMRI supports working with your layout from a web browser.
Services
Details of the JMRI web services.
Applications
By the community of JMRI.org:

JMRI Help:

Contents Index
Glossary FAQ

Donate to JMRI.org

JMRI Remote Panels

JMRI displays panels via a web browser at the /panel relative URL.

Web Server Panel screenshot

Remote Panel URLs are:

/panel
Without any additional parameters, /panel lists the open JMRI panels, as shown in the screen shot above (panels may be hidden in PanelPro, but they must be loaded to show up).
Clicking or tapping on one of the listed panel names will open a functioning panel.
/panel?name=<type>/<name>
Displays an open, operable panel. <type> is one of: Panel, ControlPanel, Switchboard or Layout.
/panel/<type>/<name>?format=xml
Retrieves a portable XML representation of the panel. <type> is one of: Panel, ControlPanel, Switchboard or Layout.
/panel/<type>/<name>?protect=yes
Displays a read-only representation of the specified panel.

Panel display requires a modern browser with HTML5 support including WebSocket. The JMRI web server's About page includes a test for this.

Technical Example: Behind the scenes of Web Server Panels

Web Server Data Flow Diagram

This chapter shows an overview of how JMRI Web Server operates between the JMRI application and the Web Browser to demonstrate what code is involved, using Panels as an example.

In part A we show at how a panel initially shows up in the browser of a (remote) user logged on to JMRI Web Server.

Part B focuses on the opposing flow of information. You will discover how user action in the browser is picked up all the way back to JMRI and even to the layout.

Some definitions

A. From JMRI to the browser

We assume JMRI and Web Server are running and the user has a browser pointing to http://localhost:12080 as set in JMRI Web Server preferences.
Configuration and current state of panel items is handled by the jmri/jmrit/display/xPanelEditor.java code. This means that creating a panel and items like Turnouts connected to the layout, and listening for changes has been completed first in the JMRI application. A graphical control panel needs to be loaded in JMRI for it to show up in the JMRI web browser drop down (it is OK if a panel is hidden in JMRI UI).

Web Server Sequence Diagram A (draft)
  1. To open a panel the User uses the mouse to click the "Panel" combo on the menu bar.
  2. The web browser responds by asking Web Server to produce the panel by sending a GET request together with a /panel/"name" URL.
  3. This starts the /web/js/panel.js JavaScript code.
  4. The main method in panel.js is $(document).ready(function() { (around line 2000).
    It ends with a call to the requestPanelXML(panelName); method:

    141 var requestPanelXML = function(panelName) {
    142    $.ajax({
    143    type: "GET",
    144    url: "/panel/" + panelName + "?format=xml", [...]
    145    success: function(data, textStatus, jqXHR) {
    146    processPanelXML(data, textStatus, jqXHR);
    147    setTitle($gPanel["name"]); } [...]

    Here, line 142 sends an AJAX (Asynchronous JavaScript And XML) GET XMLHttpRequestcode> for the panel to be retrieved by its name to the JMRI PanelServlet.
  5. The connection between web server and JMRI categories is handled by servlets in JMRI/java/src/jmri/web/servlet/, pieces of Java code interacting with the web server. The extended HttpServlet jmri/web/servlet/panel/AbstractPanelServlet.java links the web server requests to the information kept in Java. The doGet() HttpServer message calls in panel.js to answer the request.
  6. In the case of Switchboards the /jmri/web/servlet/panel/SwitchboardServlet.java contains methods such as:
    • String getPanelType()
    • String getXmlPanel(String name)
    When you want to develop new features on web panels, that second method needs to be expanded to include them when pulling new information from SwitchboardEditor and individual BeanSwitches placed on that panel:

    40 protected String getXmlPanel(String name) {
    41    SwitchboardEditor editor = (SwitchboardEditor) getEditor(name);
    42     Element panel = new Element("panel");
    43     panel.setAttribute("shape", editor.getSwitchShape());
    44     Element color = new Element("backgroundColor");
    45     color.setAttribute("red", Integer.toString(editor.getBackgroundColor().getRed()));
    46     [...] 47     panel.addContent(color);

    Line 45 allows us to later from panel.js call e.g.,
    979 $("#panel-area").css({"background-color": "rgb(" + $widget.red + "," + $widget.green + "," + $widget.blue + ")"});
  7. From the Java code running JMRI, specific information is written out to an xml "snapshot" for both the panel (JFrame) and the individual objects placed on that panel, so called 'Positionables':
    • The complete panel is "stored" by the store(object) method in jmri/jmrit/display/switchboardEditor/configurexml/SwitchboardEditorXml.java:

      41 public Element store(Object o) {
      42  SwitchboardEditor p = (SwitchboardEditor) o;
      43  Element panel = new Element("switchboardeditor");
      44  JFrame frame = p.getTargetFrame();
      45  panel.setAttribute("name", "" + frame.getTitle());
      46  panel.setAttribute("class", "jmri.jmrit.display.switchboardEditor.configurexml.SwitchboardEditorXml");
      [...]

      followed by a for-each loop:

      for (BeanSwitch sub : _switches) {
        try {
          Element e = ConfigXmlManager.elementFromObject(sub);
          e.setAttribute("label", sub.getNameString());

    • Additional properties of all BeanSwitches on the Switchboard panel are exported in a for-each loop in jmri/jmrit/display/switchboardEditor/configurexml/BeanSwitchXml.java:

      public Element store(Object o) {
        BeanSwitch bs = (BeanSwitch) o;
        Element element = new Element("beanswitch");
        element.setAttribute("label", bs.getNameString());
        [...]
      }

    The result is an xml out.outputString(doc) in reply to the web server request.
  8. To process the xml response returned for the requestPanelXML command, the JavaScript code in /web/js/panel.js contains the function
    processPanelXML($returnedData, $success, $xhr) (around line 160)
    that renders from the xml data in $xml an object-based web panel html, ready to forward to the browser and display client-side using CSS and HTML5.
    Among a lot of other properties, the background color is set from the panel attribute:

    $("#panel-area").css({backgroundColor: $gPanel.backgroundcolor});

    where $() invokes jQuery as a selector (a function that returns a set of elements found in the DOM of the web page).
    After the attributes of the main panel such as color, size and position are copied from the xml, all individual elements in the panel xml are processed in a for-each loop, building a persistent array of $widget arrays:

    236 $panel.contents().each(
    237   function() {
    238     var $widget = new Array();

    In such an array the attributes of each (positionable) item are copied in 1-on-1 from the xml, filling the basic widget web object:
    244 $(this.attributes).each(function() {
    245   $widget[this.name] = this.value;
    246 });
  9. Next, every $widget array if enriched by extra fields, either using attributes shared on the panel, like
    666 var $cr = $gPanel.turnoutcirclesize * SIZE;
    with attributes of one of the elements: 1039 $widget['text8'] = $(this).find('inconsistentText').attr('text');
    or with copies of other elements already present in the $widget array, for example:

    case "beanswitch" :
      $widget['name'] = $widget.label;

    which creates a 'name' item in the array and copies in the label item.
  10. Next, the HTML is written:

    $("#panel-area").append("<div id=" + $widget.id + "r class='" +
    $widget.classes + "' " + $hoverText + "></div>");

  11. The complete HTML "page" is sent out to user's screen as a structured but basically static visual, composed of graphical "objects" that will be updated via the server as needed at a later point in time when the status of one such item changes inside JMRI.
  12. Next, panel.js starts up a process to send and listens for changes to each panel element (called "nodes" in xml speak).
    The nodes in our example panel are called BeanSwitches. SensorIcons and LayoutTracks are similar nodes. Each element is assigned a $widget variable in /web/js/panel.js, to interact with the browser document object model (DOM) HTML5+ entities via jdom (Java-based document object model for XML), jQuery and JavaScript Object Notation (JSON).
    To be able to keep a link to every graphical element displayed on the browser we mainly use <div/>s that the server will feed to the user's browser and using jQuery can be contacted by their unique ID.
  13. From that moment, panel.js follows interaction in java.src.jmrit.display.xPanelEditor and the separate PositionableItems like LayoutTurnout, CPE SensorIcon and Switchboard Beanswitch.
  14. Now that the $(document).ready(function() main method is running, it will listen for changes, and depending on the type (object) will start updateWidgets(name, state, data), which in turn calls setWidgetState(widgetId, state, data) to update the shape displayed on screen, e.g., when a sensor on the layout changes state.

What to Code:

  1. Describe how to get your specific stuff out of JMRI and into the web server:
    • Shared: create the store(panel) method
    • Per item: add code in a for-each element loop in the same method
  2. Describe how to fill the $widgets for your items in the processPanelXML() function.

Where to Code:

  1. In /jmri/jmrit/display/xEditor/configureXml/xEditorXml.java + similar files for any special classes your panel type relies on.
  2. In /web/js/panel.js

B. From the browser to JMRI

Web Server Sequence Diagram A (draft)

Note: Web click events are only enabled if a panel is configured (in JMRI xPanelEditor) to allow control.

  1. To perform regular click-handling, a mouseup() function is hooked up to widgets in /web/js/panel.js (around line 1120):
    • a state toggle function for all non-momentary clickable widgets:
      $('.clickable:not(.momentary)').bind(UPEVENT, $handleClick);
    • Momentary widgets go active on mousedown, and inactive on mouseup
  2. Sending commands to the JMRI items under control, and listening for changes to those items in JMRI, is handled by the JSON WebSocket Server.
    • User interaction in the browser is picked up via UserClicked(object) and GET/POST HTTP messages.
    • When a mouseClickEvent is heard, the $handleClick(e) function in panel.js calls sendElementChange($widget.jsonType, $widget.systemName, $newState), resulting in jmri.setObject(type, name, state) forwarding to the JMRI system.
  3. The $handleClick(e) function in panel.js (around line 1160) updates the widgets involved:
    • Text based widgets are told how to change the <div> item that's already displayed on screen: fetch them by their ID, update their contents e.g., show an ON label instead of OFF.
    • Icon-based widget are redrawn to reflect changes to state or occupancy by drawing on the graphical "canvas"
    • It is also possible to swap styles on a div using CSS, for example to change the style of a widget from "css2" to a new style called "css4" (where 4 represents the int value for Inactive and Thrown):

      var $reDrawIcon = function($widget) {
        $('div#' + $id).css($widget['css' + $newState]);

    The actual CSS styling code for the element is taken from web/css/panel.css file by he user's browser.
  4. Via sendElementChange (around line 1750) a call is made to jmri.setObject(type, name, state);, where jmri represents the JMRI WebSocket on this server
  5. setObject() in /web/js/jquery.jmri.js jQuery JavaScript Library instructs JMRI to set for example a Light to "On" via a Post message:

    jmri.setLight = function (name, state) {
      jmri.socket.send("light", { name: name, state: state }, 'post');
    }
  6. If properly connected, on the layout a light will start to light.

What to Code: Specify the response to user action by adding an if(...) block to the $setWidgetState(id, newState, data) function (near line 1570).

Where to Code: $handleClick() function in jmri/web/js/panel.js

Conclusion

We hope the above example will help new developers understand which parts are required to create and maintain web display for a panel type, and how each part is connected to display stuff from JMRI in the web browser, and back.
As always, start small and check often!