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("Load");
071        loadButton.setToolTipText("Start Load Process");
072        JPanel p;
073
074        p = new JPanel();
075        p.setLayout(new WrapLayout());
076        p.add(new JLabel("Target Node ID: "));
077        p.add(nodeSelector);
078        selectorPane.add(p);
079
080        p = new JPanel();
081        p.setLayout(new WrapLayout());
082        p.add(new JLabel("Address Space: "));
083
084        spaceField = new MemorySpaceSelector(0xEF);
085        p.add(spaceField);
086        selectorPane.add(p);
087        spaceField.setToolTipText("The number of the address space, e.g. 239 or 0xEF");
088
089        p = new JPanel();
090        p.setLayout(new WrapLayout());
091        lockNode = new JCheckBox("Lock Node");
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() + " Firmware Downloader");
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                }
179                enableDownloadVerifyButtons();
180            }
181        });
182    }
183
184    void updateGUI(final int value) {
185        javax.swing.SwingUtilities.invokeLater(() -> {
186            log.debug("updateGUI with {}",value);
187            // update progress bar
188            bar.setValue(value);
189        });
190    }
191
192    /**
193     * Get NodeID from the GUI
194     *
195     * @return selected node id
196     */
197    NodeID destNodeID() {
198        return nodeSelector.getSelectedNodeID();
199    }
200
201    /**
202     * Set NodeID in the GUI
203     */
204    void setDestNodeID(NodeID nodeID) {
205        nodeSelector.setSelectedNodeID(nodeID);
206    }
207
208    @Override
209    protected void setDefaultFieldValues() {
210        // currently, doesn't do anything, as just loading raw hex files.
211        log.debug("setDefaultFieldValues leaves fields unchanged");
212    }
213
214    byte[] fdata;
215
216    public void readFile(String filename) {
217        File file = new File(filename);
218        try (FileInputStream fis = new FileInputStream(file)) {
219
220            log.info("Total file size to read (in bytes) : {}",fis.available());
221            fdata = new byte[fis.available()];
222            int i = 0;
223            int content;
224            while ((content = fis.read()) != -1) {
225                fdata[i++] = (byte) content;
226            }
227
228        } catch (IOException e) {
229            log.error("Unable to read {}", filename, e);
230        }
231    }
232
233    /**
234     * Checks the values in the GUI text boxes to determine if any are invalid.
235     * Intended for use immediately after reading a firmware file for the
236     * purpose of validating any key/value pairs found in the file. Also
237     * intended for use immediately before a "verify" or "download" operation to
238     * check that the user has not changed any of the GUI text values to ones
239     * that are unsupported.
240     * <p>
241     * Note that this method cannot guarantee that the values are suitable for
242     * the hardware being updated and/or for the particular firmware information
243     * which was read from the firmware file.
244     *
245     * @return false if one or more GUI text box contains an invalid value
246     */
247    @Override
248    protected boolean parametersAreValid() {
249        return true;
250    }
251
252    /**
253     * Nested class to create one of these using old-style defaults
254     */
255    public static class Default extends jmri.jmrix.can.swing.CanNamedPaneAction {
256
257        public Default() {
258            super("LCC Firmware Download",
259                    new jmri.util.swing.sdi.JmriJFrameInterface(),
260                    LoaderAction.class.getName(),
261                    jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class));
262        }
263
264        /**
265         * Constructor that explicits sets the node to be upgraded
266         */
267        public Default(NodeID nodeID) {
268            super("LCC Firmware Download",
269                    new jmri.util.swing.sdi.JmriJFrameInterface(),
270                    LoaderPane.class.getName(),
271                    jmri.InstanceManager.getDefault(jmri.jmrix.can.CanSystemConnectionMemo.class));
272            this.nodeID = nodeID;
273        }
274
275        NodeID nodeID;
276        
277        @Override
278        public JmriPanel makePanel() {
279            var panel = (LoaderPane) super.makePanel();
280            panel.setDestNodeID(nodeID);
281            return panel;
282        }
283        
284    }
285
286    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LoaderPane.class);
287}