001/** 002 * @author Gregory J. Bedlek Copyright (C) 2018, 2019, 2020 003 */ 004 005package jmri.jmrit.ctc; 006 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.LinkedList; 012 013import jmri.*; 014import jmri.jmrit.ctc.ctcserialdata.CTCSerialData; 015import jmri.jmrit.ctc.ctcserialdata.CodeButtonHandlerData; 016import jmri.jmrit.ctc.ctcserialdata.OtherData; 017import jmri.util.swing.JmriJOptionPane; 018 019public class CTCMain { 020 021 private final CTCSerialData _mCTCSerialData; 022 private final ArrayList<CodeButtonHandler> _mCodeButtonHandlersArrayList = new ArrayList<>(); // "Const" after initialization completes. 023 private NBHSensor _mCTCDebugSystemReloadInternalSensor = null; 024 private final PropertyChangeListener _mCTCDebugSystemReloadInternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { handleCTCDebugSystemReload(e); }; 025 private NBHSensor _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor = null; 026 private final PropertyChangeListener _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener = (PropertyChangeEvent e) -> { handleLogging(e); }; 027 private final HashMap<Integer, SignalDirectionIndicatorsInterface> _mSIDIHashMap = new HashMap<>(); // "Const" after initialization completes. 028 private final HashMap<Integer, SwitchDirectionIndicators> _mSWDIHashMap = new HashMap<>(); // "Const" after initialization completes. 029 private final HashMap<Integer, CodeButtonHandler> _mCBHashMap = new HashMap<>(); // "Const" after initialization completes. 030 private final LockedRoutesManager _mLockedRoutesManager = new LockedRoutesManager(); 031 private javax.swing.Timer _mLockTurnoutsTimer = null; 032 private final CTCExceptionBuffer _mCTCExceptionBuffer = new CTCExceptionBuffer(); 033 034// So that external python script can set locks on all of the lockable turnouts: 035 public void externalLockTurnout() { 036 for (CodeButtonHandler codeButtonHandler : _mCodeButtonHandlersArrayList) { 037 codeButtonHandler.externalLockTurnout(); 038 } 039 } 040 041 public CTCMain() { 042 CtcManager mgr = InstanceManager.getDefault(CtcManager.class); 043 _mCTCSerialData = mgr.getCTCSerialData(); 044 InstanceManager.store(_mCTCExceptionBuffer, CTCExceptionBuffer.class); 045 } 046 047 private void handleCTCDebugSystemReload(PropertyChangeEvent e) { 048 if (e.getPropertyName().equals("KnownState") && (int)e.getNewValue() == Sensor.ACTIVE) { // NOI18N 049 log.info("{} : Reload", Bundle.getMessage("CTCMainShuttingDown")); // NOI18N 050 shutdown(); 051 startup(); 052 } 053 } 054 055 public boolean _mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled = false; 056 private void handleLogging(PropertyChangeEvent e) { 057 if (e.getPropertyName().equals("KnownState")) { // NOI18N 058 _mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled = (int)e.getNewValue() == Sensor.ACTIVE; 059 if (_mCTCDebug_TrafficLockingRuleTriggeredDisplayLoggingEnabled) _mLockedRoutesManager.dumpAllRoutes(); 060 } 061 } 062 063// This will insure all objects are disconnected from propertyChangeListeners: 064 private void shutdown() { 065 for (CodeButtonHandler codeButtonHandler : _mCodeButtonHandlersArrayList) { 066 codeButtonHandler.removeAllListeners(); 067 } 068 _mLockedRoutesManager.removeAllListeners(); 069 _mCTCDebugSystemReloadInternalSensor.removePropertyChangeListener(_mCTCDebugSystemReloadInternalSensorPropertyChangeListener); 070 _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.removePropertyChangeListener(_mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener); 071 } 072 073 void startup() { 074 _mLockedRoutesManager.clearAllLockedRoutes(); 075 SignalDirectionIndicators.resetSignalsUsed(); 076 077// One of's: 078 OtherData otherData = _mCTCSerialData.getOtherData(); 079 Fleeting fleeting = new Fleeting( otherData._mFleetingToggleInternalSensor, 080 otherData._mDefaultFleetingEnabled); 081 082 ArrayList <CodeButtonHandlerData> codeButtonHandlerDataList = _mCTCSerialData.getCodeButtonHandlerDataArrayList(); 083 LinkedList <TrafficLocking> trafficLockingFileReadComplete = new LinkedList<>(); 084 085// For each code button defined: 086 codeButtonHandlerDataList.forEach((codeButtonHandlerData) -> { 087 088 boolean slavedSwitch = codeButtonHandlerData._mOSSectionSwitchSlavedToUniqueID != -1; 089 String userIdentifier = codeButtonHandlerData.myShortStringNoComma() + ": "; 090 boolean turnoutLockingOnlyEnabled 091 = !codeButtonHandlerData._mSIDI_Enabled 092 && !codeButtonHandlerData._mSIDL_Enabled 093 && !codeButtonHandlerData._mSWDI_Enabled 094 && !codeButtonHandlerData._mSWDL_Enabled 095 && !codeButtonHandlerData._mCO_Enabled 096 && !codeButtonHandlerData._mTRL_Enabled 097 && codeButtonHandlerData._mTUL_Enabled 098 && !codeButtonHandlerData._mIL_Enabled; 099 100// Slave Switch: null 101 SignalDirectionIndicatorsInterface signalDirectionIndicators = (codeButtonHandlerData._mSIDI_Enabled && !slavedSwitch) ? 102 new SignalDirectionIndicators( userIdentifier, 103 codeButtonHandlerData._mSIDI_LeftInternalSensor, 104 codeButtonHandlerData._mSIDI_NormalInternalSensor, 105 codeButtonHandlerData._mSIDI_RightInternalSensor, 106 codeButtonHandlerData._mSIDI_CodingTimeInMilliseconds, 107 codeButtonHandlerData._mSIDI_TimeLockingTimeInMilliseconds, 108 codeButtonHandlerData._mSIDI_TrafficDirection, 109 codeButtonHandlerData._mSIDI_LeftRightTrafficSignals, 110 codeButtonHandlerData._mSIDI_RightLeftTrafficSignals, 111 fleeting) 112 : new SignalDirectionIndicatorsNull(); 113 _mSIDIHashMap.put(codeButtonHandlerData._mUniqueID, signalDirectionIndicators); 114 115// Slave Switch: null 116 SignalDirectionLever signalDirectionLever = (codeButtonHandlerData._mSIDL_Enabled && !slavedSwitch) ? 117 new SignalDirectionLever( userIdentifier, 118 codeButtonHandlerData._mSIDL_LeftInternalSensor, 119 codeButtonHandlerData._mSIDL_NormalInternalSensor, 120 codeButtonHandlerData._mSIDL_RightInternalSensor) 121 : null; 122 123// Slave Switch: Valid 124 SwitchDirectionIndicators switchDirectionIndicators = codeButtonHandlerData._mSWDI_Enabled ? 125 new SwitchDirectionIndicators( userIdentifier, 126 codeButtonHandlerData._mSWDI_NormalInternalSensor, 127 codeButtonHandlerData._mSWDI_ReversedInternalSensor, 128 codeButtonHandlerData._mSWDI_ExternalTurnout, 129 codeButtonHandlerData._mSWDI_CodingTimeInMilliseconds, 130 codeButtonHandlerData._mSWDI_FeedbackDifferent) 131 : null; 132 if (switchDirectionIndicators != null) _mSWDIHashMap.put(codeButtonHandlerData._mUniqueID, switchDirectionIndicators); 133 134// Slave Switch: Valid 135 SwitchDirectionLever switchDirectionLever = codeButtonHandlerData._mSWDL_Enabled ? 136 new SwitchDirectionLever( userIdentifier, 137 codeButtonHandlerData._mSWDL_InternalSensor) 138 : null; 139 140// Slave Switch: null 141 CallOn callOn = (codeButtonHandlerData._mCO_Enabled && !slavedSwitch) ? 142 new CallOn( _mLockedRoutesManager, 143 userIdentifier, 144 codeButtonHandlerData._mCO_CallOnToggleInternalSensor, 145 codeButtonHandlerData._mCO_GroupingsList, 146 otherData._mSignalSystemType) 147 : null; 148 149// Slave Switch: Valid 150 TurnoutLock turnoutLock = codeButtonHandlerData._mTUL_Enabled ? 151 new TurnoutLock(userIdentifier, 152 codeButtonHandlerData._mTUL_DispatcherInternalSensorLockToggle, 153 codeButtonHandlerData._mTUL_ExternalTurnout, 154 codeButtonHandlerData._mTUL_ExternalTurnoutFeedbackDifferent, 155 codeButtonHandlerData._mTUL_DispatcherInternalSensorUnlockedIndicator, 156 codeButtonHandlerData._mTUL_NoDispatcherControlOfSwitch, 157 codeButtonHandlerData._mTUL_ndcos_WhenLockedSwitchStateIsClosed ? Turnout.CLOSED : Turnout.THROWN, 158 codeButtonHandlerData._mTUL_LockImplementation, 159 otherData._mTUL_EnabledAtStartup, 160 codeButtonHandlerData._mTUL_AdditionalExternalTurnout1, 161 codeButtonHandlerData._mTUL_AdditionalExternalTurnout1FeedbackDifferent, 162 codeButtonHandlerData._mTUL_AdditionalExternalTurnout2, 163 codeButtonHandlerData._mTUL_AdditionalExternalTurnout2FeedbackDifferent, 164 codeButtonHandlerData._mTUL_AdditionalExternalTurnout3, 165 codeButtonHandlerData._mTUL_AdditionalExternalTurnout3FeedbackDifferent) 166 : null; 167 168// Slave Switch: duplicate other referenced entry, otherwise handle IL normally: 169 IndicationLockingSignals indicationLockingSignals = null; // Default if not enabled 170 if (slavedSwitch) { 171 CodeButtonHandlerData slavedSwitchCodeButtonHandlerData = _mCTCSerialData.getCodeButtonHandlerDataViaUniqueID(codeButtonHandlerData._mOSSectionSwitchSlavedToUniqueID); 172 if (slavedSwitchCodeButtonHandlerData != null) { // Safety check 173 indicationLockingSignals = new IndicationLockingSignals(userIdentifier, 174 slavedSwitchCodeButtonHandlerData._mIL_Signals, 175 codeButtonHandlerData._mSWDI_ExternalTurnout, 176 otherData._mSignalSystemType); 177 } 178 } else if (codeButtonHandlerData._mIL_Enabled) { 179 indicationLockingSignals = new IndicationLockingSignals(userIdentifier, 180 codeButtonHandlerData._mIL_Signals, 181 codeButtonHandlerData._mSWDI_ExternalTurnout, 182 otherData._mSignalSystemType); 183 } 184 185// Slave Switch: null 186 TrafficLocking trafficLocking = (codeButtonHandlerData._mTRL_Enabled && !slavedSwitch) ? 187 new TrafficLocking( userIdentifier, 188 codeButtonHandlerData._mTRL_LeftTrafficLockingRules, 189 codeButtonHandlerData._mTRL_RightTrafficLockingRules, 190 _mLockedRoutesManager) 191 : null; 192 if (trafficLocking != null) trafficLockingFileReadComplete.add(trafficLocking); 193 194 CodeButtonHandler codeButtonHandler = new CodeButtonHandler(turnoutLockingOnlyEnabled, 195 _mLockedRoutesManager, 196 userIdentifier, 197 codeButtonHandlerData._mUniqueID, 198 codeButtonHandlerData._mCodeButtonInternalSensor, 199 codeButtonHandlerData._mCodeButtonDelayTime, 200 codeButtonHandlerData._mOSSectionOccupiedExternalSensor, 201 codeButtonHandlerData._mOSSectionOccupiedExternalSensor2, 202 signalDirectionIndicators, 203 signalDirectionLever, 204 switchDirectionIndicators, 205 switchDirectionLever, 206 fleeting, 207 callOn, 208 trafficLocking, 209 turnoutLock, 210 indicationLockingSignals); 211 _mCodeButtonHandlersArrayList.add(codeButtonHandler); 212 _mCBHashMap.put(codeButtonHandlerData._mUniqueID, codeButtonHandler); 213 }); 214 215 _mCTCDebugSystemReloadInternalSensor = otherData._mCTCDebugSystemReloadInternalSensor; 216 _mCTCDebugSystemReloadInternalSensor.setKnownState(Sensor.INACTIVE); 217 _mCTCDebugSystemReloadInternalSensor.addPropertyChangeListener(_mCTCDebugSystemReloadInternalSensorPropertyChangeListener); 218 _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor = otherData._mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor; 219 _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.setKnownState(Sensor.INACTIVE); 220 _mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensor.addPropertyChangeListener(_mCTCDebug_TrafficLockingRuleTriggeredDisplayInternalSensorPropertyChangeListener); 221 222 for (TrafficLocking trafficLocking : trafficLockingFileReadComplete) { // Call these routines to give them a chance to initialize: 223 trafficLocking.fileReadComplete(_mCBHashMap, _mSWDIHashMap); 224 } 225 226/* As a final item, if the developer wants us to lock all of the lockable 227 turnouts after a time period, create a GUI timer to do that, so that 228 when we call the objects, they are called on the GUI thread for safety. 229 In the called routines, sensors will be updated, but I don't know how 230 thread safe they are, or whether they will directly update GUI objects, 231 since GUI objects "visually back" the sensors. 232*/ 233 if (otherData._mTUL_SecondsToLockTurnouts > 0) { // Enabled: 234 _mLockTurnoutsTimer = new javax.swing.Timer(otherData._mTUL_SecondsToLockTurnouts * 1000, lockTurnoutsTimerTicked); 235 _mLockTurnoutsTimer.setRepeats(false); 236 _mLockTurnoutsTimer.start(); 237 } 238 239// Finally, display errors to the user: 240 if (!_mCTCExceptionBuffer.isEmpty()) { 241 CTCExceptionBuffer.ExceptionBufferRecordSeverity exceptionBufferRecordSeverity2 = _mCTCExceptionBuffer.getHighestExceptionBufferRecordSeverity(); 242 int messageType; 243 switch (exceptionBufferRecordSeverity2) { 244 case ERROR: 245 messageType = JmriJOptionPane.ERROR_MESSAGE; 246 break; 247 case WARN: 248 messageType = JmriJOptionPane.WARNING_MESSAGE; 249 break; 250 default: // And INFO 251 messageType = JmriJOptionPane.INFORMATION_MESSAGE; 252 break; 253 } 254 JmriJOptionPane.showMessageDialog(null, _mCTCExceptionBuffer.getAllMessages(), Bundle.getMessage("CTCMainRuntimeStartupIssues"), messageType); // NOI18N 255 } 256 log.info("CTC {} {}", OtherData.CTC_VERSION, Bundle.getMessage("CTCMainStarted")); // NOI18N 257 } 258 259// One shot routine: 260 private final java.awt.event.ActionListener lockTurnoutsTimerTicked = new java.awt.event.ActionListener() { 261 @Override 262 public void actionPerformed(java.awt.event.ActionEvent evt) { 263// Shut down this timer so this doesn't happen again: 264 _mLockTurnoutsTimer.stop(); 265 _mLockTurnoutsTimer.removeActionListener(lockTurnoutsTimerTicked); 266 _mLockTurnoutsTimer = null; 267 externalLockTurnout(); 268 } 269 }; 270 271 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CTCMain.class); 272 273}