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}