# Sample script showing how to persist the state of turnouts between sessions. # # State is saved to a .csv file at shutdown and re-instated from said .csv file # when script is launched. # # This shows how .csv files can be both written and read-back complete with # a header row using the Apache Commons CSV library # # This also shows how entries can be added to the log as opposed to using # 'print' commands # # Author: Matthew Harris, copyright 2011 # Author: Randall Wood, copyright 2020 # Part of the JMRI distribution # import jmri import java import java.io import java.util import org.apache.commons.csv from org.slf4j import LoggerFactory # Define turnout state file # Default is 'TurnoutState.csv' stored in the preferences directory turnoutFile = jmri.util.FileUtil.getUserFilesPath() + "TurnoutState.csv" # Define task to persist turnout state at shutdown class PersistTurnoutStateTask(jmri.implementation.AbstractShutDownTask): # Get reference to the logger # # This reference is unique to instances of this class, hence the use of # 'self.log' whenever it needs to be used # # The logger has been instantiated within the pseudo package: # 'jmri.jmrit.jython.exec' # This allows for easy identification and configuration of logging. # # 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. log = LoggerFactory.getLogger("jmri.jmrit.jython.exec.TurnoutStatePersistence.PersistTurnoutStateTask") # Define task to run at ShutDown def run(self): # Write an info entry to the log self.log.info("Write turnout state to file: '%s'" % turnoutFile) # Open file csvFormat = org.apache.commons.csv.CSVFormat.Builder.create(org.apache.commons.csv.CSVFormat.DEFAULT).setCommentMarker('#').build() csvFile = org.apache.commons.csv.CSVPrinter(java.io.FileWriter(turnoutFile), csvFormat) # Initialise counter turnoutCount = 0 # Write header csvFile.print("System Name") csvFile.print("User Name") csvFile.print("Comment") csvFile.print("Is Inverted") csvFile.print("Saved State") csvFile.println() # Loop through all known turnouts for to in turnouts.getNamedBeanSet(): # Write a debug entry to the log if (self.log.isDebugEnabled()): self.log.debug("Storing turnout: {}", to.getSystemName()) # Retrieve details to persist csvFile.print(to.getSystemName()) csvFile.print(to.getUserName()) csvFile.print(to.getComment()) csvFile.print(self.booleanName(to.getInverted())) csvFile.print(self.stateName(to.getState())) # Notify end of record csvFile.println() # Increment counter turnoutCount +=1 # Write an info entry to the log self.log.info("Stored state of %d turnouts" % turnoutCount) # Append a comment to the end of the file csvFile.printComment("Written by JMRI version %s on %s" % (jmri.Version.name(), (java.util.Date()).toString())) # Flush the write buffer and close the file csvFile.flush() csvFile.close() # All done return # Function to convert state values to names def stateName(self, state): if (state == CLOSED): return "CLOSED" if (state == THROWN): return "THROWN" if (state == INCONSISTENT): return "INCONSISTENT" # Anything else is UNKNOWN return "UNKNOWN" # Function to convert boolean values to names def booleanName(self, value): if (value == True): return "Yes" # Anything else is No return "No" # Define task to load turnout state at script start # # This is implemented as a separate class so that it can run on a # different thread in the background rather than holding up the main # thread while executing class LoadTurnoutState(jmri.jmrit.automat.AbstractAutomaton): # Get reference to the logger log = LoggerFactory.getLogger("jmri.jmrit.jython.exec.TurnoutStatePersistence.LoadTurnoutState") # Perform any initialisation def init(self): return # Define task to run def handle(self): # Retrieve the state file as a File object inFile = java.io.File(turnoutFile) # Check if state file exists if inFile.exists(): # It does, so load it csvFormat = org.apache.commons.csv.CSVFormat.Builder.create(org.apache.commons.csv.CSVFormat.DEFAULT).setHeader().setCommentMarker('#').build() csvFile = org.apache.commons.csv.CSVParser.parse(inFile, java.nio.charset.StandardCharsets.UTF_8, csvFormat) # Write an info entry to the log self.log.info("Loading turnout state file: %s" % turnoutFile) # Initialise counter turnoutCount = 0 # Loop through each record for record in csvFile.getRecords(): # Read the record details systemName = record.get("System Name") userName = record.get("User Name") comment = record.get("Comment") inverted = self.booleanName(record.get("Is Inverted")) savedState = self.stateValue(record.get("Saved State")) # Get reference to the turnout turnout = turnouts.provideTurnout(systemName) # Write a debug entry to the log if (self.log.isDebugEnabled()): self.log.debug("Setting state of turnout: %s" % systemName) # Set other parameters if specified if (userName != ""): turnout.setUserName(userName) if (comment != ""): turnout.setComment(comment) if (turnout.canInvert()): turnout.setInverted(inverted) # Finally, set the state turnout.setState(savedState) # Increment counter turnoutCount +=1 # Close the file csvFile.close() # Write an info entry to the log self.log.info("Loaded state of %d turnouts" % turnoutCount) else: # It doesn't, so log this fact and carry on self.log.warn("Turnout state file '%s' does not exist" % turnoutFile) # All done return False # Only need to run once # Function to convert state names to values def stateValue(self, state): if (state == "CLOSED"): return CLOSED if (state == "THROWN"): return THROWN if (state == "INCONSISTENT"): return INCONSISTENT # Anything else is UNKNOWN return UNKNOWN # Function to convert boolean names to values def booleanName(self, value): if (value == "Yes"): return True # Anything else is False return False # Register the turnout persistence shutdown task shutdown.register(PersistTurnoutStateTask("PersistTurnoutState")) # Launch the load task LoadTurnoutState().start()