001package jmri.jmrix.openlcb.swing.downloader; 002 003import java.awt.event.WindowEvent; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.IOException; 007 008import javax.swing.BoxLayout; 009import javax.swing.JCheckBox; 010import javax.swing.JFileChooser; 011import javax.swing.JLabel; 012import javax.swing.JPanel; 013 014import jmri.jmrit.MemoryContents; 015import jmri.jmrix.can.CanSystemConnectionMemo; 016import jmri.jmrix.openlcb.swing.NodeSpecificFrame; 017import jmri.util.swing.JmriPanel; 018import jmri.util.swing.WrapLayout; 019 020import org.openlcb.Connection; 021import org.openlcb.LoaderClient; 022import org.openlcb.LoaderClient.LoaderStatusReporter; 023import org.openlcb.MimicNodeStore; 024import org.openlcb.NodeID; 025import org.openlcb.OlcbInterface; 026import org.openlcb.implementations.DatagramService; 027import org.openlcb.implementations.MemoryConfigurationService; 028import org.openlcb.swing.NodeSelector; 029import org.openlcb.swing.MemorySpaceSelector; 030 031/** 032 * Pane for downloading firmware files files to OpenLCB devices which support 033 * firmware updates according to the Firmware Upgrade Protocol. 034 * 035 * @author Bob Jacobsen Copyright (C) 2005, 2015 (from the LocoNet version by B. 036 * Milhaupt Copyright (C) 2013, 2014) David R Harris (C) 2016 Balazs Racz (C) 037 * 2016 038 */ 039public class LoaderPane extends jmri.jmrix.AbstractLoaderPane 040 implements jmri.jmrix.can.swing.CanPanelInterface { 041 042 protected CanSystemConnectionMemo memo; 043 Connection connection; 044 MemoryConfigurationService mcs; 045 DatagramService dcs; 046 MimicNodeStore store; 047 NodeSelector nodeSelector; 048 JPanel selectorPane; 049 MemorySpaceSelector spaceField; 050 JCheckBox lockNode; 051 LoaderClient loaderClient; 052 NodeID nid; 053 OlcbInterface iface; 054 055 public String getTitle(String menuTitle) { 056 return Bundle.getMessage("TitleLoader"); 057 } 058 059 @Override 060 public void initComponents(CanSystemConnectionMemo memo) { 061 this.memo = memo; 062 this.connection = memo.get(Connection.class); 063 this.mcs = memo.get(MemoryConfigurationService.class); 064 this.dcs = memo.get(DatagramService.class); 065 this.store = memo.get(MimicNodeStore.class); 066 this.nodeSelector = new NodeSelector(store, Integer.MAX_VALUE); // display all ID terms available 067 this.loaderClient = memo.get(LoaderClient.class); 068 this.nid = memo.get(NodeID.class); 069 this.iface = memo.get(OlcbInterface.class); 070 071 // We can add to GUI here 072 loadButton.setText("Load"); 073 loadButton.setToolTipText("Start Load Process"); 074 JPanel p; 075 076 p = new JPanel(); 077 p.setLayout(new WrapLayout()); 078 p.add(new JLabel("Target Node ID: ")); 079 p.add(nodeSelector); 080 selectorPane.add(p); 081 082 p = new JPanel(); 083 p.setLayout(new WrapLayout()); 084 p.add(new JLabel("Address Space: ")); 085 086 spaceField = new MemorySpaceSelector(0xEF); 087 p.add(spaceField); 088 selectorPane.add(p); 089 spaceField.setToolTipText("The number of the address space, e.g. 239 or 0xEF"); 090 091 p = new JPanel(); 092 p.setLayout(new WrapLayout()); 093 lockNode = new JCheckBox("Lock Node"); 094 p.add(lockNode); 095 selectorPane.add(p); 096 097 // Verify not an option 098 verifyButton.setVisible(false); 099 } 100 101 @Override 102 protected void addChooserFilters(JFileChooser chooser) { 103 } 104 105 @Override 106 public void doRead(JFileChooser chooser) { 107 // has a file been selected? Might not been if Chooser was cancelled 108 if (chooser == null || chooser.getSelectedFile() == null) return; 109 110 String fn = chooser.getSelectedFile().getPath(); 111 readFile(fn); 112 bar.setValue(0); 113 loadButton.setEnabled(true); 114 } 115 116 public LoaderPane() { 117 } 118 119 @Override 120 public String getHelpTarget() { 121 return "package.jmri.jmrix.openlcb.swing.downloader.LoaderFrame"; 122 } 123 124 @Override 125 public String getTitle() { 126 if (memo != null) { 127 return (memo.getUserName() + " Firmware Downloader"); 128 } 129 return getTitle(Bundle.getMessage("TitleLoader")); 130 } 131 132 @Override 133 protected void addOptionsPanel() { 134 selectorPane = new JPanel(); 135 selectorPane.setLayout(new BoxLayout(selectorPane, BoxLayout.Y_AXIS)); 136 137 add(selectorPane); 138 } 139 140 @Override 141 protected void handleOptionsInFileContent(MemoryContents inputContent) { 142 } 143 144 @Override 145 protected void doLoad() { 146 super.doLoad(); 147 148 // if window referencing this node is open, close it 149 var frames = jmri.util.JmriJFrame.getFrames(); 150 for (var frame : frames) { 151 if (frame instanceof NodeSpecificFrame) { 152 if ( ((NodeSpecificFrame)frame).getNodeID() == destNodeID() ) { 153 // This window references the node and should be closed 154 155 // Notify the user to handle any prompts before continuing. 156 jmri.util.swing.JmriJOptionPane.showMessageDialog(this, 157 Bundle.getMessage("OpenWindowMessage") 158 ); 159 160 // Depending on the state of the window, and how the user handles 161 // a prompt to discard changes or cancel, this might be 162 // presented multiple times until the user finally 163 // allows the window to close. See the message in the Bundle.properties 164 // file for how we handle this. 165 166 // Close this window - force onto the queue before a possible next modal dialog 167 jmri.util.ThreadingUtil.runOnGUI(() -> { 168 frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); 169 }); 170 171 } 172 } 173 } 174 175 // de-cache CDI information so next window opening will reload 176 iface.dropConfigForNode(destNodeID()); 177 178 // start firmware load operation 179 setOperationAborted(false); 180 abortButton.setEnabled(false); 181 abortButton.setToolTipText(Bundle.getMessage("TipAbortDisabled")); 182 int ispace = spaceField.getMemorySpace(); 183 long addr = 0; 184 loaderClient.doLoad(nid, destNodeID(), ispace, addr, fdata, new LoaderStatusReporter() { 185 @Override 186 public void onProgress(float percent) { 187 updateGUI(Math.round(percent)); 188 } 189 190 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 191 justification = "message String also used in status JLabel") 192 @Override 193 public void onDone(int errorCode, String errorString) { 194 if (errorCode == 0) { 195 updateGUI(100); //draw bar to 100% 196 if (errorString.isEmpty()) { 197 status.setText(Bundle.getMessage("StatusDownloadOk")); 198 } else { 199 status.setText(Bundle.getMessage("StatusDownloadOkWithMessage", errorString)); 200 } 201 setOperationAborted(false); 202 } else { 203 String msg = Bundle.getMessage("StatusDownloadFailed", Integer.toHexString(errorCode), errorString); 204 status.setText(msg); 205 setOperationAborted(true); 206 log.info(msg); 207 } 208 enableDownloadVerifyButtons(); 209 } 210 }); 211 } 212 213 void updateGUI(final int value) { 214 javax.swing.SwingUtilities.invokeLater(() -> { 215 log.debug("updateGUI with {}",value); 216 // update progress bar 217 bar.setValue(value); 218 }); 219 } 220 221 /** 222 * Get NodeID from the GUI 223 * 224 * @return selected node id 225 */ 226 NodeID destNodeID() { 227 return nodeSelector.getSelectedNodeID(); 228 } 229 230 /** 231 * Set NodeID in the GUI 232 */ 233 void setDestNodeID(NodeID nodeID) { 234 nodeSelector.setSelectedNodeID(nodeID); 235 } 236 237 @Override 238 protected void setDefaultFieldValues() { 239 // currently, doesn't do anything, as just loading raw hex files. 240 log.debug("setDefaultFieldValues leaves fields unchanged"); 241 } 242 243 byte[] fdata; 244 245 public void readFile(String filename) { 246 File file = new File(filename); 247 try (FileInputStream fis = new FileInputStream(file)) { 248 249 log.info("Total file size to read (in bytes) : {}",fis.available()); 250 fdata = new byte[fis.available()]; 251 int i = 0; 252 int content; 253 while ((content = fis.read()) != -1) { 254 fdata[i++] = (byte) content; 255 } 256 257 } catch (IOException e) { 258 log.error("Unable to read {}", filename, e); 259 } 260 } 261 262 /** 263 * Checks the values in the GUI text boxes to determine if any are invalid. 264 * Intended for use immediately after reading a firmware file for the 265 * purpose of validating any key/value pairs found in the file. Also 266 * intended for use immediately before a "verify" or "download" operation to 267 * check that the user has not changed any of the GUI text values to ones 268 * that are unsupported. 269 * <p> 270 * Note that this method cannot guarantee that the values are suitable for 271 * the hardware being updated and/or for the particular firmware information 272 * which was read from the firmware file. 273 * 274 * @return false if one or more GUI text box contains an invalid value 275 */ 276 @Override 277 protected boolean parametersAreValid() { 278 return true; 279 } 280 281 /** 282 * Nested class to create one of these using old-style defaults 283 */ 284 public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 285 286 public Default() { 287 super("LCC Firmware Download", 288 new jmri.util.swing.sdi.JmriJFrameInterface(), 289 LoaderAction.class.getName(), 290 jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 291 } 292 293 /** 294 * Constructor that explicits sets the node to be upgraded 295 */ 296 public Default(NodeID nodeID) { 297 super("LCC Firmware Download", 298 new jmri.util.swing.sdi.JmriJFrameInterface(), 299 LoaderPane.class.getName(), 300 jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class)); 301 this.nodeID = nodeID; 302 } 303 304 NodeID nodeID; 305 306 @Override 307 public JmriPanel makePanel() { 308 var panel = (LoaderPane) super.makePanel(); 309 panel.setDestNodeID(nodeID); 310 return panel; 311 } 312 313 } 314 315 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoaderPane.class); 316}