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