Skip to main content

Just checked code to support subscribing to system events, and have refactored the command echoing program echo.py to use it. The mechanism uses a simple subscribe/publish mechanism to allow "listeners" to subscribe to "events" they are interested in. Events can be broad, like any changes to any engine, or specific; changes to a specific engine (or switch or accessory or train). There is also a "broadcast" channel you can subscribe to which receives all TMCC events the system reads from the LCS Ser2. This is how echo.py now receives its updates.

I threw together a quick and dirty command syntax to query device state and added it into pytrain.py. I've only wired in switches and accessories, but will work on engine and train state next. I have to figure out how to serve this information up to client pytrains. Once I do that, I'll be able to change speeds more gradually by querying an engine's current speed and ramping up/down to the new requested speed. I also want to wire listeners in to the GPIO support so indicated light states can be kept in sync.

I've asked the Lionel folks how to communicate directly with the Base 3 over wifi. This will be a far better mechanism to get the current state of switches, accessories, and power districts, but as far as I know, engine and train states are most likely perceived by interpreting the TMCC command stream (over wifi or serial).

  -- Dave

My $15 Raspberry Pi Zero W (2017 vintage) arrived yesterday. The short answer is it works with my software and can be used to control trains! The longer answer is it is v e r y  v e r y s l o.  w.

It takes very little power, with is a plus, but it is pretty slow to boot up. I'll post some pictures of it running my Lionel Strasburg switcher once my LCS Ser2 arrives (I've switched locales to Boston for a few months; I miss my layout in NH already.

  -- Dave

Attachments

Images (1)
  • mceclip0
@BlueFeather posted:

Dave, that's a $15, seven year old board.  :-)  You can spend a little more and speed things up fairly easily if that's the bottleneck.

LOL, no,  just wanted to see if it works. IMHO, it boots too slow to be useable in a control panel. You want the panels to be up and operational as soon as the Base 3 is booted.

Because of its small size, the Pi Zero would have been a good choice to build right into a panel. I have a Pi Zero 2 on order at the whopping price of $24, so there may still be hope!

but frankly, that's something a little bigger than the size of a stick of chewing gum can be a fully functioning computer with a GUI, TCP/IP, etc., is still pretty amazing!

  -- Dave

Well, it looks like you're on the trail of something faster.

As someone that's worked with software all my life, I am still fascinated by the array of hardware we now have available and its capability. Here's some timing, using the elapsed time it takes to run my test suite:

Mac M2 Ultra (2023):          1.36 sec
Raspberry Pi 5 (2024): 1.44 sec
Raspberry Pi 4B (2018): 3.11 sec
Raspberry Pi Zero W 2 (2023): 4.31 sec
Raspberry Pi Zero W (2017): 24.27 sec


It's making me wonder if my big, honking M2 Ultra was worth the price

  -- Dave

Last edited by cdswindell

Fairly major update. The code that parses the byte stream from the LCS Ser2 is paying off in spades. I've could echo received commands for a couple of weeks now. This means that no matter how the TMCC command was generated, via a Cab2, the Cab 3, another third-party vendor, or my Python code, I can capture it via the output stream from the LCS Ser2 and echo it to a console.

One of the issues with building control panels is keeping system state in sync. For example, if I have a light on a control panel that reflects if a power district is on or off, I want that light to reflect the correct state no matter how that state is changed. So, if a power district is on, and the corresponding led is lit, it should turn off if:

  • I manually turn off the district via a cab2
  • I manually turn off the district via a cab3
  • It is turned off via a 3rd party app
  • A TMCC Halt command is issued

For this to work correctly, you need:

  • A way to maintain state on the PyTrain server
  • A way to keep that state in sync on all PyTrain clients (the ones powering the panels)
  • An understanding of the dependencies that exist between Lionel commands

This last item is particularly interesting. It's obvious that a power district led should turn off if it sees a "turn power district off" command (Accessory Aux2, in my case), but what about that Halt command? And when you do an engine reset, (the (R) button on the Cab2/Cab3) it sets the engine speed to zero, resets direction to forward, turns the bell off, sets RPM to zero, sets engine labor to 12, and performs an immediate startup, and I may still be missing some; phew!).

With this morning's checkin, the following is available:

  • A state management system has been implemented that observes the command stream from the LCS SER2 and determines device states from it,
  • Commands received on the server (the one with the LCS Ser2 connection) are now propagated to all clients; they in turn, using the same code, keep their own local state caches in sync,
  • I've implemented a command listener system that listens for specific commands (scoped to device type, specific device, device command, and device command data) that calls callback methods in response, and
  • I've started developing a dependency mechanism that understands the relationship between commands (e.g., Halt command should disable power districts)

Devices are engines, trains, switches, and accessories. There is still a lot more work to be done, but with these components, the system is now aware of events happening elsewhere on the layout and from other control mechanisms, and can maintain device state accordingly.

My next step is to wire these dependency-aware event listeners into the code to control LEDS so their state always reflects reality.

I am hoping that one day soon I will learn how to query LCS devices via wifi, but for now, you can do a lot with an LCS Ser2.

  -- Dave

@cdswindell posted:

One of the issues with building control panels is keeping system state in sync. For example, if I have a light on a control panel that reflects if a power district is on or off, I want that light to reflect the correct state no matter how that state is changed. So, if a power district is on, and the corresponding led is lit, it should turn off if:

  • I manually turn off the district via a cab2
  • I manually turn off the district via a cab3
  • It is turned off via a 3rd party app
  • A TMCC Halt command is issued

No offense Dave, but the simple way to do this is to monitor the power at the actual power district and control your panel light with that.  No mistakes, and you can't get a false indication unless a bulb burns out.

However, a TMCC halt command, IMO, probably shouldn't turn the light off.  It doesn't remove power from the track, it just stops all current TMCC/Legacy activity.  However, you can immediately spin the throttle and be on the move again, so in that scenario, I feel the power district light should remain on.

No offense Dave, but the simple way to do this is to monitor the power at the actual power district and control your panel light with that.  No mistakes, and you can't get a false indication unless a bulb burns out.

However, a TMCC halt command, IMO, probably shouldn't turn the light off.  It doesn't remove power from the track, it just stops all current TMCC/Legacy activity.  However, you can immediately spin the throttle and be on the move again, so in that scenario, I feel the power district light should remain on.

None taken, but with my BP2s configured to accept accessory commands, when the TMCC1 Halt command is given, those districts all turn off. Power isn't restored until you reenable that power district (at least on my layout).

I used power district as an example. You encounter the same situation when you reset an individual engine (Numeric 0). The direction is set back to "FWD", speed is set to 0, the bell is turned off, etc.

  -- Dave

State management is now in place and working well, both on the PyTrain server as well as clients. Engine speed, direction, and startup/shutdown status is tracked, and maintained correctly when changed via a Cab 3 as well as impacting commands (engine reset and halt commands).

My next plan is to use the engine speed state to smoothly ramp up or down speed changes.

Stay tuned.

  — Dave

I'm getting pretty close to putting together my first control panel that uses the PyTrain code. I've added several methods to the GpioHandler class that model Lionel LCS modules and accessories, which makes it easier to build out physical devices and associate pushbuttons and switches to Raspberry Pi pins.

For example, here is the code to support a Command/Control Smoke Fluid Loader:

GpioHandler.smoke_fluid_loader(12,
boom_pin_1=12,
boom_pin_2=16,
dispense_pin=21,
lights_on_pin=26,
lights_off_pin=19)


This is all the code a user would need to write to operate the Smoke Fluid loader with Accessory ID #12. Boom rotation is done via a rotary encoder that is connected to Pi pins 12 and 16. Smoke fluid droplets are dispensed via a pushbutton connected to pin 21. And you can turn the unit's lights on and off via a toggle switch connected to pins 26 and 19.

Of course, you could use which ever pins you want...

I've defined the following helper methods:

GpioHandler.route

GpioHandler.switch

GpioHandler.power_district # LCS BP2 in accessory mode

GpioHandler.accessory # LCS ASC2

GpioHandler.culvert_loader # Command & Control

GpioHandler.smoke_fluid_loader # Command & Control

GpioHandler.gantry_crane # Command & Control



These are all items I have on my layout and that I want to build panels for. Other devices (including the pre-command/control variants of those listed above) are easy enough to piece together.

All of these devices are state-aware, that is, if an operation is started via a control panel, it can be completed via a Cab 2, Cab 3, or other 3rd-party software. Indicator lights on the panels will reflect actual state, regardless of how it got there.

My first panel will control 1 switch, 2 power districts, and a gantry crane. I will send pictures as I make progress. What I am most happy about is that each panel will only need one wire; the power wire for the Raspberry Pi itself. All control operations and state management will be done via the PyTrain software, WiFi, and a single Pi connected to an LCS SER2.

  -- Dave

p.s. The $25 Raspberry Pi Zero W 2 looks like a real winner for this application. It is about the size of a piece of chewing gum and can be powered by a small USB power adapter, like they used to send out with your iPhones. I will build the Pi directly into the panel itself, so there will be short wire runs from the physical switches and status LEDs to the Pi's GPIO pins.

I am learning how to interact with the Base 3 over WiFi. I still don't know enough to say if I can dispense with the LCS Ser2 altogether. One interesting observation is that commands sent and received appear to be the ASCII representations of the byte sequences and not the byte sequences themselves. For example, the "keep alive" sequence one needs to send to the Base to keep the TCP/IP connection open is D1 29 27 DF (thanks to a new friend who sent me that info in a DM). I assumed I would be sending 0xd12927df, but instead, I have to send "D12927DF"!! This was a bit of a surprise and makes me a little worried the mechanism I'm using to connect is a debugging backdoor that may be slammed shut one day...

I've also modified my client code so that it requests a complete update from the server when started. This will allow all clients (deployed in separate control panels) to be up to date when booted up, should the server receive state before clients boot.

Back to it...

  -- Dave

@cdswindell posted:

I am learning how to interact with the Base 3 over WiFi. I still don't know enough to say if I can dispense with the LCS Ser2 altogether. One interesting observation is that commands sent and received appear to be the ASCII representations of the byte sequences and not the byte sequences themselves. For example, the "keep alive" sequence one needs to send to the Base to keep the TCP/IP connection open is D1 29 27 DF (thanks to a new friend who sent me that info in a DM). I assumed I would be sending 0xd12927df, but instead, I have to send "D12927DF"!! This was a bit of a surprise and makes me a little worried the mechanism I'm using to connect is a debugging backdoor that may be slammed shut one day...

I've also modified my client code so that it requests a complete update from the server when started. This will allow all clients (deployed in separate control panels) to be up to date when booted up, should the server receive state before clients boot.

Back to it...

  -- Dave

Originally when Lionel released the Legacy code, they made a point of saying you had to use the SER2 to access the full Legacy command set.  I always figured they had done something different in the internal communications that would make it more difficult to use the "raw" data.  Of course, the app has to communicate with the BASE3 over the network, but there might be some sort of "key" that gets changed periodically with releases that will break the communications.

@cdswindell posted:

State management is now in place and working well, both on the PyTrain server as well as clients. Engine speed, direction, and startup/shutdown status is tracked, and maintained correctly when changed via a Cab 3 as well as impacting commands (engine reset and halt commands).

My next plan is to use the engine speed state to smoothly ramp up or down speed changes.

Stay tuned.

  — Dave

That's pretty cool!  Are you keeping some sort of state table for everything?  If so, I'm guessing it is dynamic in nature (record creation and destruction) so as to minimize memory usage.

George

Last edited by G3750
@G3750 posted:

That's pretty cool!  Are you keeping some sort of state table for everything?  If so, I'm guessing it is dynamic in nature (record creation and destruction) so as to minimize memory usage.

George

Yes, I'm keeping a state table for the devices discovered by sniffing the TMCC command flow fro the LCS Ser2. The info is fairly minimal, to reduce the memory footprint. For example, for switches, just the thru/diverged info. For Accessories, a triplet for Aux on/off, Aux 1 on/off, and Aux2 on/off. For engines and trains, for now, I'm only listening for speed, direction, and if the engine has been "started" or "shutdown". I could maintain more info, such as momentum, brake level, smoke, etc, but I'm not sure I'll need that for what I'm doing (but it could be added).

I never really delete any state records, as on a typical layout, you are not removing switches or accessories. I could time out engine/train state records, but they really don't take too much memory.  On my Pi Zero W 2, which I think only has about 512MB of ram, the program only uses about 20mb.

  -- Dave

@cdswindell posted:

Yes, I'm keeping a state table for the devices discovered by sniffing the TMCC command flow fro the LCS Ser2. The info is fairly minimal, to reduce the memory footprint. For example, for switches, just the thru/diverged info. For Accessories, a triplet for Aux on/off, Aux 1 on/off, and Aux2 on/off. For engines and trains, for now, I'm only listening for speed, direction, and if the engine has been "started" or "shutdown". I could maintain more info, such as momentum, brake level, smoke, etc, but I'm not sure I'll need that for what I'm doing (but it could be added).

I never really delete any state records, as on a typical layout, you are not removing switches or accessories. I could time out engine/train state records, but they really don't take too much memory.  On my Pi Zero W 2, which I think only has about 512MB of ram, the program only uses about 20mb.

  -- Dave

Makes sense.  If you limit the amount of information kept per engine and only keep state for detected, i.e. started locomotives, you'll be fine.  Statically accommodating comprehensive records for hundreds of locomotives might eventually be a problem, but you are going about this logically and efficiently.  And you're doing this with a Raspberry Pi, not an Arduino.

George

Add Reply

Post
×
×
×
×
Link copied to your clipboard.
×
×