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}