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