Model Train-related Notes Blog -- these are personal notes and musings on the subject of model train control, automation, electronics, or whatever I find interesting. I also have more posts in a blog dedicated to the maintenance of the Randall Museum Model Railroad.

2024-12-16 - Conductor 2: New MQTT Support

Category Rtac

There’s been a few changes in Conductor 2 in the recent weeks.

The first major change has been to add MQTT support. The custom script language now exposes an “mqtt” object. Once configured, one can publish MQTT topics/messages. This is used to control the Ambiance LED string display. Here’s an example of usage in the latest script:

mqtt.configure("~/bin/JMRI/rtac_mqtt_conf.json")

 

mqtt.publish("ambiance/script/init", "Brightness 0.5 ; Fill #000000 1 ; SlowFill 0.1 #FF1000 40 #FF4000 10")

mqtt.publish("ambiance/script/event", "Slide -0.05 100")

 

var _ambiance_counter = 0

fun Ambiance_Trigger() {

    mqtt.publish("ambiance/event/trigger", _ambiance_counter.toString())

    _ambiance_counter++

}

 

fun Ambiance_Off() {

    mqtt.publish("ambiance/script/init", "Brightness 0.5 ; SlowFill 0.25 #000000 1")

}

The first line configures the MQTT publisher with the broker’s IP and port. This one currently only supports basic auth user/password as that’s all I needed for now -- I simply run the Mosquitto MQTT broker on localhost yet I still setup some kind of basic auth. It could be extended later to support SSL authentication as needed, of course. It’s open source -- quality patches are welcome 🙂

Once configured, usage is a simple “publish(topic, message)” call. Fairly obvious. Creating function wrappers is just a cleaner way to inject these later during route execution without polluting the train execution with auxiliary details, for example:

val Passenger_Route = ML_Route.sequence {

    name = "Passenger"

    throttle = PA

 

    onActivate {

        PA.light(On)

        PA.bell(Off)

        PA.sound(On)

    }

 

    val B503b_start = node(B503b) {

        onEnter {

            PA.bell(On)

            PA_beacon(On)

            after (AM_Data.Delay_Horn) then {

                PA.horn()

                PA.forward(AM_Data.Leaving_Speed)

                Ambiance_Trigger()

            }

        }

    }

...

The only surprise I had was using the HiveMQ-MQTT-Client Java library. I originally used the latest 1.3.4 version with the MQTT 5 support. It worked very well at home with my simulator and my local JMRI server. Then I deployed it at Randall and two issues appeared:

  • hivemq-mqtt-client 1.3.4 brings in a Kotlin 1.9 runtime dependency that is somehow conflicting when loaded dynamically via Jython in JMRI 5.0 -- it conflicts because my own engine has a Kotlin 1.6 dependency and I guess I can’t have both runtimes loaded in the JVM at the same time? Just a guess. I do my tests at home against the latest JMRI 5.5, so maybe I need to update my production environment to match. That was solved by checking the release dates for HiveMQ -- I simply went back to hivemq-mqtt-client 1.3.1, which was published before Kotling 1.9, thus I know it can only have a Kotlin 1.6 dependency at most. Eventually I want to solve that and update to Kotlin 1.9.
  • HiveMQ has both MQTT 3 and 5 support as a client. I started with MQTT 5. It worked fine at home with Mosquitto 2.0.18. When I deployed at the museum, the calls were rejected by the local Mosquitto broker as “wrong version”. The museum version is Debian’s Mosquitto 1.5.7, which turns out to only have up to protocol 3.1.1 support. I simply updated the Conductor 2 usage of HiveMQ to switch to the MQTT 3 protocol.

Overall, I’m satisfied with the implementation. It’s simple. Usage on the script side is fairly straightforward and clean.


 Generated on 2025-01-11 by Rig4j 0.1-Exp-f2c0035