001package jmri.jmrix.loconet.hexfile; 002 003import javax.swing.*; 004 005import jmri.*; 006import jmri.jmrix.debugthrottle.DebugThrottleManager; 007import jmri.jmrix.loconet.LnCommandStationType; 008import jmri.jmrix.loconet.LnPacketizer; 009import jmri.jmrix.loconet.LocoNetListener; 010import jmri.jmrix.loconet.LocoNetMessage; 011import jmri.managers.DefaultProgrammerManager; 012import jmri.util.JmriJFrame; 013 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016 017/** 018 * Frame to inject LocoNet messages from a hex file and (optionally) mock a response to specific Discover 019 * messages. This is a sample frame that drives a test App. It controls reading from a .hex file, feeding 020 * the information to a LocoMonFrame (monitor) and connecting to a LocoGenFrame (for 021 * manually sending commands). Pane includes a checkbox to turn on simulated replies, see {@link LnHexFilePort}. 022 * Note that running a simulated LocoNet connection, {@link HexFileFrame#configure()} will substitute the 023 * {@link jmri.progdebugger.ProgDebugger} for the {@link jmri.jmrix.loconet.LnOpsModeProgrammer} 024 * overriding the readCV and writeCV methods. 025 * 026 * @author Bob Jacobsen Copyright 2001, 2002 027 * @author Egbert Broerse 2017, 2021 028 */ 029public class HexFileFrame extends JmriJFrame implements LocoNetListener { 030 031 // member declarations 032 javax.swing.JButton openHexFileButton = new javax.swing.JButton(); 033 javax.swing.JButton filePauseButton = new javax.swing.JButton(); 034 javax.swing.JButton jButton1 = new javax.swing.JButton(); 035 javax.swing.JTextField delayField = new javax.swing.JTextField(5); 036 javax.swing.JLabel jLabel1 = new javax.swing.JLabel(); 037 JCheckBox simReplyBox = new JCheckBox(Bundle.getMessage("SimReplyBox")); 038 039 private int maxSlots = 10; //maximum addresses that can be acquired at once, this default will be overridden by config 040 private int slotsInUse = 0; 041 042 // to find and remember the log file 043 final javax.swing.JFileChooser inputFileChooser; 044 045 /** 046 * Because this creates a FileChooser, this should be invoked on the 047 * GUI frame. 048 */ 049 @InvokeOnGuiThread 050 public HexFileFrame() { 051 super(); 052 inputFileChooser = jmri.jmrit.XmlFile.userFileChooser("Hex files", "hex"); // NOI18N 053 } 054 055 /** 056 * {@inheritDoc} 057 */ 058 @InvokeOnGuiThread 059 @Override 060 public void initComponents() { 061 if (port == null) { 062 log.error("initComponents called before adapter has been set"); 063 } 064 // the following code sets the frame's initial state 065 066 openHexFileButton.setText(Bundle.getMessage("OpenFile")); 067 openHexFileButton.setVisible(true); 068 openHexFileButton.setToolTipText(Bundle.getMessage("OpenFileTooltip")); 069 070 filePauseButton.setText(Bundle.getMessage("ButtonPause")); 071 filePauseButton.setVisible(true); 072 filePauseButton.setToolTipText(Bundle.getMessage("ButtonPauseTooltip")); 073 074 jButton1.setText(Bundle.getMessage("ButtonContinue")); 075 jButton1.setVisible(true); 076 jButton1.setToolTipText(Bundle.getMessage("ButtonContinueTooltip")); 077 078 delayField.setText("200"); 079 delayField.setVisible(true); 080 delayField.setToolTipText(Bundle.getMessage("DelayTooltip")); 081 delayField.addPropertyChangeListener(this::delayFieldActionPerformed); 082 083 jLabel1.setText(Bundle.getMessage("FieldDelay")); 084 jLabel1.setVisible(true); 085 086 simReplyBox.setToolTipText(Bundle.getMessage("SimReplyTip")); 087 setTitle(Bundle.getMessage("TitleLocoNetSimulator", getAdapter().getUserName())); 088 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 089 090 JPanel pane1 = new JPanel(); 091 pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS)); 092 pane1.add(openHexFileButton); 093 pane1.add(new JPanel()); // dummy 094 getContentPane().add(pane1); 095 096 JPanel pane2 = new JPanel(); 097 pane2.setLayout(new BoxLayout(pane2, BoxLayout.X_AXIS)); 098 pane2.add(jLabel1); 099 pane2.add(delayField); 100 getContentPane().add(pane2); 101 102 JPanel pane3 = new JPanel(); 103 pane3.setLayout(new BoxLayout(pane3, BoxLayout.X_AXIS)); 104 pane3.add(filePauseButton); 105 pane3.add(jButton1); 106 getContentPane().add(pane3); 107 108 JPanel pane4 = new JPanel(); 109 pane4.add(simReplyBox); 110 getContentPane().add(pane4); 111 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 112 simReplyBox.setSelected(prefMgr.getSimplePreferenceState("simReply")); 113 port.simReply(simReplyBox.isSelected()); // update state in adapter 114 }); 115 116 openHexFileButton.addActionListener(this::openHexFileButtonActionPerformed); 117 filePauseButton.addActionListener(this::filePauseButtonActionPerformed); 118 jButton1.addActionListener(this::jButton1ActionPerformed); 119 simReplyBox.addActionListener(this::simReplyActionPerformed); 120 121 pack(); 122 } 123 124 boolean connected = false; 125 126 @Override 127 @InvokeOnGuiThread 128 public void dispose() { 129 // leaves the LocoNet Packetizer (e.g. the simulated connection) running 130 // so that the application can keep pretending to run with the window closed. 131 super.dispose(); 132 } 133 134 LnPacketizer packets = null; 135 136 @InvokeOnGuiThread 137 public void openHexFileButtonActionPerformed(java.awt.event.ActionEvent e) { 138 // select the file 139 // start at current file, show dialog 140 inputFileChooser.rescanCurrentDirectory(); 141 int retVal = inputFileChooser.showOpenDialog(this); 142 143 // handle selection or cancel 144 if (retVal != JFileChooser.APPROVE_OPTION) { 145 return; // give up if no file selected 146 } 147 // call load to process the file 148 Integer delayValue = null; 149 port.load(inputFileChooser.getSelectedFile()); 150 try { 151 delayValue = Integer.parseInt(delayField.getText()); 152 153 } catch (NumberFormatException exception) { 154 log.error("invalid number in delay field - {}", delayField.getText()); 155 } 156 if (delayValue != null && delayValue > 1 ) 157 port.setDelay(delayValue); 158 // wake copy 159 sourceThread.interrupt(); // really should be using notifyAll instead.... 160 161 // reach here while file runs. Need to return so GUI still acts, 162 // but that normally lets the button go back to default. 163 } 164 165 @InvokeOnGuiThread 166 public void configure() { 167 if (port == null) { 168 log.error("configure called before adapter has been set"); 169 return; 170 } 171 // connect to a packetizing LnTrafficController 172 packets = new LnPacketizer(port.getSystemConnectionMemo()); 173 packets.connectPort(port); 174 connected = true; 175 176 // create memo 177 port.getSystemConnectionMemo().setLnTrafficController(packets); 178 179 // do the common manager config 180 port.getSystemConnectionMemo().configureCommandStation(LnCommandStationType.COMMAND_STATION_DCS100, // full featured by default 181 false, false, false, false, false); 182 port.getSystemConnectionMemo().configureManagers(); 183 jmri.SensorManager sm = port.getSystemConnectionMemo().getSensorManager(); 184 if (sm != null) { 185 if ( sm instanceof LnSensorManager) { 186 ((LnSensorManager) sm).setDefaultSensorState(port.getOptionState("SensorDefaultState")); // NOI18N 187 } else { 188 log.info("SensorManager referenced by port is not an LnSensorManager. Have not set the default sensor state."); 189 } 190 } 191 //get the maxSlots value from the connection options 192 try { 193 maxSlots = Integer.parseInt(port.getOptionState("MaxSlots")); 194 } catch (NumberFormatException e) { 195 //ignore missing or invalid option and leave at the default value 196 } 197 198 // Install a debug programmer, replacing the existing LocoNet one 199 // Note that this needs to be repeated for the DefaultManagers, if one is set to HexFile (Ln Sim) 200 // see jmri.jmrix.loconet.hexfile.HexFileSystemConnectionMemo 201 log.debug("HexFileFrame called"); 202 DefaultProgrammerManager ep = port.getSystemConnectionMemo().getProgrammerManager(); 203 port.getSystemConnectionMemo().setProgrammerManager( 204 new jmri.progdebugger.DebugProgrammerManager(port.getSystemConnectionMemo())); 205 if (port.getSystemConnectionMemo().getProgrammerManager().isAddressedModePossible()) { 206 log.debug("replacing AddressedProgrammer in Hex"); 207 jmri.InstanceManager.store(port.getSystemConnectionMemo().getProgrammerManager(), jmri.AddressedProgrammerManager.class); 208 } 209 if (port.getSystemConnectionMemo().getProgrammerManager().isGlobalProgrammerAvailable()) { 210 log.debug("replacing GlobalProgrammer in Hex"); 211 jmri.InstanceManager.store(port.getSystemConnectionMemo().getProgrammerManager(), GlobalProgrammerManager.class); 212 } 213 jmri.InstanceManager.deregister(ep, jmri.AddressedProgrammerManager.class); 214 jmri.InstanceManager.deregister(ep, jmri.GlobalProgrammerManager.class); 215 216 // Install a debug throttle manager and override 217 DebugThrottleManager tm = new DebugThrottleManager(port.getSystemConnectionMemo() ) { 218 /** 219 * Only address 128 and above can be a long address 220 */ 221 @Override 222 public boolean canBeLongAddress(int address) { 223 return (address >= 128); 224 } 225 226 @Override 227 public void requestThrottleSetup(LocoAddress a, boolean control) { 228 if (!(a instanceof DccLocoAddress)) { 229 log.error("{} is not a DccLocoAddress",a); 230 failedThrottleRequest(a, "LocoAddress " + a + " is not a DccLocoAddress"); 231 return; 232 } 233 DccLocoAddress address = (DccLocoAddress) a; 234 235 //check for slot limit exceeded 236 if (slotsInUse >= maxSlots) { 237 log.warn("SLOT MAX of {} reached. Throttle {} not added. Current slotsInUse={}", maxSlots, a, slotsInUse); 238 failedThrottleRequest(address, "SLOT MAX of " + maxSlots + " reached"); 239 return; 240 } 241 242 slotsInUse++; 243 log.debug("Throttle {} requested. slotsInUse={}, maxSlots={}", a, slotsInUse, maxSlots); 244 super.requestThrottleSetup(a, control); 245 } 246 247 @Override 248 public boolean disposeThrottle(DccThrottle t, jmri.ThrottleListener l) { 249 if (slotsInUse > 0) slotsInUse--; 250 log.debug("Throttle {} disposed. slotsInUse={}, maxSlots={}", t, slotsInUse, maxSlots); 251 return super.disposeThrottle(t, l); 252 } 253 }; 254 255 port.getSystemConnectionMemo().setThrottleManager(tm); 256 jmri.InstanceManager.setThrottleManager( 257 port.getSystemConnectionMemo().getThrottleManager()); 258 259 // start listening for messages 260 port.getSystemConnectionMemo().getLnTrafficController().addLocoNetListener(~0, this); 261 262 // start operation of packetizer 263 packets.startThreads(); 264 sourceThread = jmri.util.ThreadingUtil.newThread(port, "LocoNet HexFileFrame"); 265 sourceThread.start(); 266 } 267 268 public void filePauseButtonActionPerformed(java.awt.event.ActionEvent e) { 269 port.suspendReading(true); 270 } 271 272 public void jButton1ActionPerformed(java.awt.event.ActionEvent e) { // resume button 273 port.suspendReading(false); 274 } 275 276 public void delayFieldActionPerformed(java.beans.PropertyChangeEvent e) { 277 // if the hex file has been started, change its delay 278 if (port != null) { 279 port.setDelay(Integer.parseInt(delayField.getText())); 280 } 281 } 282 283 @Override 284 public synchronized void message(LocoNetMessage m) { 285 //log.debug("HexFileFrame heard message {}", m.toMonitorString()); 286 if (port.simReply()) { 287 LocoNetMessage reply = LnHexFilePort.generateReply(m); 288 if (reply != null) { 289 packets.sendLocoNetMessage(reply); 290 //log.debug("message reply forwarded to port"); 291 } 292 } 293 } 294 295 Thread sourceThread; // tests need access 296 297 public void setAdapter(LnHexFilePort adapter) { 298 port = adapter; 299 } 300 301 public LnHexFilePort getAdapter() { 302 return port; 303 } 304 private LnHexFilePort port = null; 305 306 public void simReplyActionPerformed(java.awt.event.ActionEvent e) { // resume button 307 port.simReply(simReplyBox.isSelected()); 308 InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefMgr) -> { 309 prefMgr.setSimplePreferenceState("simReply", simReplyBox.isSelected()); 310 }); 311 } 312 313 private final static Logger log = LoggerFactory.getLogger(HexFileFrame.class); 314 315}