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