Files
JIMRI/jython/AutoActiveTrains_Simulator.py
2026-06-17 14:00:51 +02:00

93 lines
4.8 KiB
Python

# Simulator for Dispatcher's AutoActiveTrains
# while auto train(s) are "moving", repeatedly activate "next" allocated block, and deactivate "last" occupied block
# waits for sensor changes plus a bit, to allow signals, etc. to respond.
# Runs as a background thread, ends itself when no trains are found in Dispatcher Active Trains list.
# NOTE: to enable logging, see https://www.jmri.org/help/en/html/apps/Debug.shtml
# Add the Logger Category name "jmri.jmrit.jython.exec" at DEBUG Level.
import jmri
import datetime
from org.slf4j import Logger;
from org.slf4j import LoggerFactory;
log = LoggerFactory.getLogger("jmri.jmrit.jython.exec.AutoActiveTrains_Simulator");
minLoopMS = 2000 # minimum time in ms allowed for one loop
extraDelayMS = 250 # extra time in ms for processing
# Optional control sensor. If it exists, the main loop can be paused and resumed by changing
# sensor state. When there are no active trains, the sensor will be set Inactive and the
# thread will wait for the sensor to become Active again.
controlSensorName = ''
# create a new class to run as thread
class AutoActiveTrains_Simulator(jmri.jmrit.automat.AbstractAutomaton) :
def init(self):
self.controlSensor = sensors.getSensor(controlSensorName)
def handle(self):
if self.controlSensor is not None: self.waitSensorActive(self.controlSensor)
DF = jmri.InstanceManager.getDefault(jmri.jmrit.dispatcher.DispatcherFrame)
trainsList=DF.getActiveTrainsList() #loop thru all trains
if (trainsList.size() == 0): # kill the thread if no trains found TODO: add something outside to restart
if self.controlSensor is not None:
self.controlSensor.setKnownState(INACTIVE)
return True
else:
log.info("AutoActiveTrains_Simulator thread ended")
return False # no trains, end
start_time = datetime.datetime.now()
# loop through all trains
for i in range(trainsList.size()):
at = trainsList.get(i) #: :type at: ActiveTrain
if (at.getAutoRun()): #ignore if not auto
aat=at.getAutoActiveTrain()
targetSpeed = aat.getTargetSpeed()
bl = at.getBlockList()
lastBlock = None #most-rear occupied block of train
nextBlock = None #first unoccupied block allocated to train
occupiedBlocks = 0
for j in range(bl.size()): #look for first NOT-allocated block (may be NONE)
b = bl.get(j)
if (b.getState()==jmri.Block.OCCUPIED):
if (lastBlock==None):
lastBlock = b
occupiedBlocks += 1
elif (occupiedBlocks > 0): # ignore any initial unoccupied blocks
nextBlock = b
break
log.debug(at.getTrainName() + ": occupiedBlocks: " + str(occupiedBlocks)
+ " next:" + ("None" if (nextBlock==None) else str(nextBlock.getDisplayName()))
+ " last:" + ("None" if (lastBlock==None) else str(lastBlock.getDisplayName()))
+ " speed:" + str(targetSpeed))
if ((nextBlock != None) and (targetSpeed > 0)): # occupy next block if moving
s = nextBlock.getSensor()
sn = s.getSystemName()
s = sensors.getSensor(sn)
if s.getKnownState() != ACTIVE:
log.debug(at.getTrainName() + ": set {} ON for block {}", sn,
nextBlock.getDisplayName())
s.setKnownState(ACTIVE)
self.waitSensorActive(s)
self.waitMsec(extraDelayMS) # extra time for handling change
if occupiedBlocks > 1: # unoccupy trailing block TODO: change this to check train length
s = lastBlock.getSensor()
sn = s.getSystemName()
s = sensors.getSensor(sn)
if s.getKnownState() != INACTIVE:
log.debug(at.getTrainName() + ": set {} OFF for block {}", sn,
lastBlock.getDisplayName())
s.setKnownState(INACTIVE)
self.waitSensorInactive(s)
self.waitMsec(extraDelayMS) # extra time for handling change
#pause for at least min specified time
elapsedMS = int((datetime.datetime.now() - start_time).total_seconds() * 1000)
if elapsedMS < minLoopMS:
self.waitMsec(minLoopMS - elapsedMS)
return True # keep looping
aats = AutoActiveTrains_Simulator("AutoActiveTrains_Simulator") # setup the thread class
aats.start() # run until it ends itself