001package jmri.jmrix.openlcb.swing.send;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.awt.BorderLayout;
006import java.awt.Dimension;
007
008import javax.swing.Box;
009import javax.swing.BoxLayout;
010import javax.swing.JButton;
011import javax.swing.JCheckBox;
012import javax.swing.JComboBox;
013import javax.swing.JComponent;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JSeparator;
017import javax.swing.JTextField;
018import javax.swing.JToggleButton;
019
020import jmri.jmrix.can.CanListener;
021import jmri.jmrix.can.CanMessage;
022import jmri.jmrix.can.CanReply;
023import jmri.jmrix.can.CanSystemConnectionMemo;
024import jmri.jmrix.can.TrafficController;
025import jmri.jmrix.can.cbus.CbusAddress;
026import jmri.jmrix.openlcb.swing.ClientActions;
027import jmri.jmrix.openlcb.swing.NamedEventIdTextField;
028import jmri.util.StringUtil;
029import jmri.util.javaworld.GridLayout2;
030import jmri.util.swing.WrapLayout;
031
032import org.openlcb.*;
033import org.openlcb.can.AliasMap;
034import org.openlcb.implementations.MemoryConfigurationService;
035import org.openlcb.swing.NodeSelector;
036import org.openlcb.swing.MemorySpaceSelector;
037
038/**
039 * User interface for sending OpenLCB CAN frames to exercise the system
040 * <p>
041 * When sending a sequence of operations:
042 * <ul>
043 * <li>Send the next message and start a timer
044 * <li>When the timer trips, repeat if buttons still down.
045 * </ul>
046 *
047 * @author Bob Jacobsen Copyright (C) 2008, 2012
048 *
049 */
050public class OpenLcbCanSendPane extends jmri.jmrix.can.swing.CanPanel implements CanListener {
051
052    // member declarations
053    final JLabel jLabel1 = new JLabel();
054    final JButton sendButton = new JButton();
055    final JTextField packetTextField = new JTextField(60);
056
057    // internal members to hold sequence widgets
058    static final int MAXSEQUENCE = 4;
059    final JTextField[] mPacketField = new JTextField[MAXSEQUENCE];
060    final JCheckBox[] mUseField = new JCheckBox[MAXSEQUENCE];
061    final JTextField[] mDelayField = new JTextField[MAXSEQUENCE];
062    final JToggleButton mRunButton = new JToggleButton(Bundle.getMessage("ButtonGo"));
063
064    final JTextField srcAliasField = new JTextField(4);
065    NodeSelector nodeSelector;
066    NamedEventIdTextField sendEventField;
067    final JTextField datagramContentsField = new JTextField("20 61 00 00 00 00 08");  // NOI18N
068    final JTextField configNumberField = new JTextField("40");                        // NOI18N
069    final JTextField configAddressField = new JTextField("000000");                   // NOI18N
070    final JTextField readDataField = new JTextField(60);
071    final JTextField writeDataField = new JTextField(60);
072    final MemorySpaceSelector addrSpace = new MemorySpaceSelector(0xFF);
073    final JComboBox<String> validitySelector = new JComboBox<String>(new String[]{Bundle.getMessage("ValiditySelectorUnknown"), 
074        Bundle.getMessage("ValiditySelectorValid"), Bundle.getMessage("ValiditySelectorInvalid")});
075    
076    JButton cdiButton;
077    
078    Connection connection;
079    AliasMap aliasMap;
080    NodeID srcNodeID;
081    MemoryConfigurationService mcs;
082    MimicNodeStore store;
083    OlcbInterface iface;
084    ClientActions actions;
085
086    public OpenLcbCanSendPane() {
087        // most of the action is in initComponents
088    }
089
090    @Override
091    public void initComponents(CanSystemConnectionMemo memo) {
092        super.initComponents(memo);
093        iface = memo.get(OlcbInterface.class);
094        actions = new ClientActions(iface, memo);
095        tc = memo.getTrafficController();
096        tc.addCanListener(this);
097        connection = memo.get(org.openlcb.Connection.class);
098        srcNodeID = memo.get(org.openlcb.NodeID.class);
099        aliasMap = memo.get(org.openlcb.can.AliasMap.class);
100
101        sendEventField = new NamedEventIdTextField(memo);
102        
103        // register request for notification
104        Connection.ConnectionListener cl = new Connection.ConnectionListener() {
105            @Override
106            public void connectionActive(Connection c) {
107                log.debug("connection active");
108                // load the alias field
109                srcAliasField.setText(Integer.toHexString(aliasMap.getAlias(srcNodeID)));
110            }
111        };
112        connection.registerStartNotification(cl);
113
114        mcs = memo.get(MemoryConfigurationService.class);
115        store = memo.get(MimicNodeStore.class);
116        nodeSelector = new NodeSelector(store);
117        nodeSelector.addActionListener (new ActionListener () {
118            @Override
119            public void actionPerformed(ActionEvent e) {
120                setCdiButton();
121                jmri.util.ThreadingUtil.runOnGUIDelayed( ()->{ 
122                    setCdiButton(); 
123                }, 500);
124            }
125        });
126
127        // start window layout
128        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
129
130        // handle single-packet part
131        add(getSendSinglePacketJPanel());
132
133        add(new JSeparator());
134
135        // Configure the sequence
136        add(new JLabel(Bundle.getMessage("ConfigureSendSequence")));
137        JPanel pane2 = new JPanel();
138        pane2.setLayout(new GridLayout2(MAXSEQUENCE + 2, 4));
139        pane2.add(new JLabel(""));
140        pane2.add(new JLabel(Bundle.getMessage("ConfigureSend")));
141        pane2.add(new JLabel(Bundle.getMessage("ConfigureSendPacket")));
142        pane2.add(new JLabel(Bundle.getMessage("ConfigureWait")));
143        for (int i = 0; i < MAXSEQUENCE; i++) {
144            pane2.add(new JLabel(Integer.toString(i + 1)));
145            mUseField[i] = new JCheckBox();
146            mPacketField[i] = new JTextField(20);
147            mDelayField[i] = new JTextField(10);
148            pane2.add(mUseField[i]);
149            pane2.add(mPacketField[i]);
150            pane2.add(mDelayField[i]);
151        }
152        add(pane2);
153        add(mRunButton); // below rows
154
155        mRunButton.addActionListener(this::runButtonActionPerformed);
156
157        // special packet forms
158        add(new JSeparator());
159        
160        pane2 = new JPanel();
161        pane2.setLayout(new WrapLayout());
162        add(pane2);
163        pane2.add(new JLabel(Bundle.getMessage("SpecialSendControlFrame")));
164        pane2.add(srcAliasField);
165        JButton b;
166        b = new JButton(Bundle.getMessage("SpecialSendCIM"));
167        b.addActionListener(this::sendCimPerformed);
168        pane2.add(b);
169
170        // send OpenLCB messages
171        add(new JSeparator());
172
173        pane2 = new JPanel();
174        pane2.setLayout(new WrapLayout());
175        add(pane2);
176        pane2.add(new JLabel(Bundle.getMessage("OpenLCBMessagesGlobal")));
177        b = new JButton(Bundle.getMessage("OpenLCBMessagesVerify"));
178        b.addActionListener(this::sendVerifyNodeGlobal);
179        pane2.add(b);
180        b = new JButton(Bundle.getMessage("OpenLCBMessagesNodeID"));
181        b.addActionListener(this::sendVerifyNodeGlobalID);
182        pane2.add(b);
183
184        // event messages 
185        add(new JSeparator());
186        
187        var insert = new JPanel();
188        insert.setLayout(new WrapLayout());
189        insert.add(sendEventField);
190        insert.add(validitySelector);
191        
192        
193        add(addLineLabel(Bundle.getMessage("EventMessagesEventID"), insert));
194        pane2 = new JPanel();
195        pane2.setLayout(new WrapLayout());
196        add(pane2);
197        b = new JButton(Bundle.getMessage("EventMessagesGlobalIdentify"));
198        b.addActionListener(this::sendGlobalIdentifyEvents);
199        pane2.add(b);
200        b = new JButton(Bundle.getMessage("EventMessagesEventProduced"));
201        b.addActionListener(this::sendEventPerformed);
202        pane2.add(b);
203        pane2 = new JPanel();
204        pane2.setLayout(new WrapLayout());
205        add(pane2);
206        b = new JButton(Bundle.getMessage("EventMessagesIdentifyConsumers"));
207        b.addActionListener(this::sendReqConsumers);
208        pane2.add(b);
209        b = new JButton(Bundle.getMessage("EventMessagesConsumerIdentified"));
210        b.addActionListener(this::sendConsumerID);
211        pane2.add(b);
212        b = new JButton(Bundle.getMessage("EventMessagesIdentifyProducers"));
213        b.addActionListener(this::sendReqProducers);
214        pane2.add(b);
215        b = new JButton(Bundle.getMessage("EventMessagesProducerIdentified"));
216        b.addActionListener(this::sendProducerID);
217        pane2.add(b);
218
219        // addressed messages
220        add(new JSeparator());
221        add(addLineLabel(Bundle.getMessage("AddressedMessagesMessageTo"), nodeSelector));
222        pane2 = new JPanel();
223        pane2.setLayout(new WrapLayout());
224        add(pane2);
225        b = new JButton(Bundle.getMessage("AddressedMessagesIdentifyEvents"));
226        b.addActionListener(this::sendRequestEvents);
227        pane2.add(b);
228        b = new JButton(Bundle.getMessage("AddressedMessagesPIPRequest"));
229        b.addActionListener(this::sendRequestPip);
230        pane2.add(b);
231        b = new JButton(Bundle.getMessage("AddressedMessagesSNIPRequest"));
232        b.addActionListener(this::sendRequestSnip);
233        pane2.add(b);
234
235        add(new JSeparator());
236
237        pane2 = new JPanel();
238        pane2.setLayout(new WrapLayout());
239        add(pane2);
240        b = new JButton(Bundle.getMessage("AddressedMessagesDatagram"));
241        b.addActionListener(this::sendDatagramPerformed);
242        pane2.add(b);
243        pane2.add(new JLabel(Bundle.getMessage("AddressedMessagesContents")));
244        datagramContentsField.setColumns(45);
245        pane2.add(datagramContentsField);
246        b = new JButton(Bundle.getMessage("AddressedMessagesPositiveReply"));
247        b.addActionListener(this::sendDatagramReply);
248        pane2.add(b);
249
250        // send OpenLCB Configuration message
251        add(new JSeparator());
252
253        pane2 = new JPanel();
254        pane2.setLayout(new WrapLayout());
255        add(pane2);
256        
257        pane2.add(new JLabel(Bundle.getMessage("ConfigMessagesMemoryRequest")));
258        pane2.add(configAddressField);
259        pane2.add(new JLabel(Bundle.getMessage("ConfigMessagesAddressSpace")));
260        pane2.add(addrSpace);
261        pane2 = new JPanel();
262        pane2.setLayout(new WrapLayout());
263        add(pane2);
264        pane2.add(new JLabel(Bundle.getMessage("ConfigMessagesByteCount")));
265        pane2.add(configNumberField);
266        b = new JButton(Bundle.getMessage("ConfigMessagesRead"));
267        b.addActionListener(this::readPerformed);
268        pane2.add(b);
269        pane2.add(new JLabel(Bundle.getMessage("ConfigMessagesData")));
270        pane2.add(readDataField);
271
272        pane2 = new JPanel();
273        pane2.setLayout(new WrapLayout());
274        add(pane2);
275        b = new JButton(Bundle.getMessage("ConfigMessagesWrite"));
276        b.addActionListener(this::writePerformed);
277        pane2.add(b);
278        pane2.add(new JLabel(Bundle.getMessage("ConfigMessagesData")));
279        writeDataField.setText("00 00");   // NOI18N
280        pane2.add(writeDataField);
281
282        pane2 = new JPanel();
283        pane2.setLayout(new WrapLayout());
284        add(pane2);
285
286        var restartButton = new JButton(Bundle.getMessage("ConfigMessagesRestart"));
287        pane2.add(restartButton);
288        restartButton.addActionListener(this::restartNode);
289        
290        cdiButton = new JButton(Bundle.getMessage("ConfigMessagesOpenCDI"));
291        pane2.add(cdiButton);
292        cdiButton.addActionListener(e -> openCdiPane());
293        cdiButton.setToolTipText(Bundle.getMessage("ConfigMessagesOpenCDItt"));
294        setCdiButton(); // get initial state
295
296        var clearCacheButton = new JButton(Bundle.getMessage("ConfigMessagesClearCDI"));
297        pane2.add(clearCacheButton);
298        clearCacheButton.addActionListener(this::clearCache);
299        clearCacheButton.setToolTipText(Bundle.getMessage("ConfigMessagesClearCDItt"));
300
301        // listen for mimic store changes to set CDI button
302        store.addPropertyChangeListener(e -> {
303            setCdiButton();
304        });
305        jmri.util.ThreadingUtil.runOnGUIDelayed( ()->{ 
306            setCdiButton(); 
307        }, 500);
308    }
309
310    /**
311     * Set whether Open CDI button is enabled based on whether
312     * the selected node has CDI in its PIP
313     */
314    protected void setCdiButton() {
315        var nodeID = nodeSelector.getSelectedNodeID();
316        if (nodeID == null) { 
317            cdiButton.setEnabled(false);
318            log.debug("null nodeID disables cdiButton");
319            return;
320        }
321        var pip = store.getProtocolIdentification(nodeID);
322        if (pip == null || pip.getProtocols() == null) { 
323            cdiButton.setEnabled(false);
324            log.debug("null pip info disables cdiButton");
325            return;
326        }
327        boolean setValue = 
328            pip.getProtocols()
329                .contains(org.openlcb.ProtocolIdentification.Protocol.ConfigurationDescription);
330        cdiButton.setEnabled(setValue);
331        log.debug("cdiButton set {} from PIP info", setValue);
332    }
333    
334    private JPanel getSendSinglePacketJPanel() {
335        JPanel outer = new JPanel();
336        outer.setLayout(new BoxLayout(outer, BoxLayout.X_AXIS));
337        
338        JPanel pane1 = new JPanel();
339        pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS));
340
341        jLabel1.setText(Bundle.getMessage("SinglePacketLabel"));
342        jLabel1.setVisible(true);
343
344        sendButton.setText(Bundle.getMessage("SinglePacketSend"));
345        sendButton.setVisible(true);
346        sendButton.setToolTipText(Bundle.getMessage("SinglePacketSendTt"));
347
348        packetTextField.setToolTipText(Bundle.getMessage("SinglePacketText"));
349        packetTextField.setMaximumSize(packetTextField.getPreferredSize());
350
351        pane1.add(jLabel1);
352        pane1.add(packetTextField);
353        pane1.add(sendButton);
354        pane1.add(Box.createVerticalGlue());
355
356        sendButton.addActionListener(this::sendButtonActionPerformed);
357        
358        outer.add(Box.createHorizontalGlue());
359        outer.add(pane1);
360        outer.add(Box.createHorizontalGlue());
361        return outer;
362    }
363
364    @Override
365    public String getHelpTarget() {
366        return "package.jmri.jmrix.openlcb.swing.send.OpenLcbCanSendFrame";  // NOI18N
367    }
368
369    @Override
370    public String getTitle() {
371        if (memo != null) {
372            return (memo.getUserName() + " " + Bundle.getMessage("Title"));
373        }
374        return Bundle.getMessage("Title");
375    }
376
377    JComponent addLineLabel(String text) {
378        return addLineLabel(text, null);
379    }
380
381    JComponent addLineLabel(String text, JComponent c) {
382        JLabel lab = new JLabel(text);
383        JPanel p = new JPanel();
384        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
385        if (c != null) {
386            p.add(lab, BorderLayout.EAST);
387            if (c instanceof JTextField) {
388                int height = lab.getMinimumSize().height+4;
389                int width = c.getMinimumSize().width;
390                Dimension d = new Dimension(width, height);
391                c.setMaximumSize(d);
392            }
393            p.add(c);
394        } else {
395            p.add(lab, BorderLayout.EAST);
396        }
397        p.add(Box.createHorizontalGlue());
398        return p;
399    }
400
401    public void sendButtonActionPerformed(java.awt.event.ActionEvent e) {
402        String input = packetTextField.getText();
403        // TODO check input + feedback on error. Too easy to cause NPE
404        CanMessage m = createPacket(input);
405        log.debug("sendButtonActionPerformed: {}",m);
406        tc.sendCanMessage(m, this);
407    }
408
409    public void sendCimPerformed(java.awt.event.ActionEvent e) {
410        String data = "[10700" + srcAliasField.getText() + "]";  // NOI18N
411        log.debug("sendCimPerformed: |{}|",data);
412        CanMessage m = createPacket(data);
413        log.debug("sendCimPerformed");
414        tc.sendCanMessage(m, this);
415    }
416
417    NodeID destNodeID() {
418        return nodeSelector.getSelectedNodeID();
419    }
420
421    EventID eventID() {
422        return sendEventField.getEventID();
423    }
424
425    public void sendVerifyNodeGlobal(java.awt.event.ActionEvent e) {
426        Message m = new VerifyNodeIDNumberGlobalMessage(srcNodeID);
427        connection.put(m, null);
428    }
429
430    public void sendVerifyNodeGlobalID(java.awt.event.ActionEvent e) {
431        Message m = new VerifyNodeIDNumberGlobalMessage(srcNodeID, destNodeID());
432        connection.put(m, null);
433    }
434
435    public void sendRequestEvents(java.awt.event.ActionEvent e) {
436        Message m = new IdentifyEventsAddressedMessage(srcNodeID, destNodeID());
437        connection.put(m, null);
438    }
439
440    public void sendRequestPip(java.awt.event.ActionEvent e) {
441        Message m = new ProtocolIdentificationRequestMessage(srcNodeID, destNodeID());
442        connection.put(m, null);
443    }
444
445    public void sendRequestSnip(java.awt.event.ActionEvent e) {
446        Message m = new SimpleNodeIdentInfoRequestMessage(srcNodeID, destNodeID());
447        connection.put(m, null);
448    }
449
450    public void sendGlobalIdentifyEvents(java.awt.event.ActionEvent e) {
451        Message m = new IdentifyEventsGlobalMessage(srcNodeID);
452        connection.put(m, null);
453    }
454
455    public void sendEventPerformed(java.awt.event.ActionEvent e) {
456        Message m = new ProducerConsumerEventReportMessage(srcNodeID, eventID());
457        connection.put(m, null);
458    }
459
460    public void sendReqConsumers(java.awt.event.ActionEvent e) {
461        Message m = new IdentifyConsumersMessage(srcNodeID, eventID());
462        connection.put(m, null);
463    }
464
465    EventState validity() {
466        switch (validitySelector.getSelectedIndex()) {
467            case 1 : return EventState.Valid;
468            case 2 : return EventState.Invalid;
469            case 0 : 
470            default: return EventState.Unknown;
471        }
472    }
473    
474    public void sendConsumerID(java.awt.event.ActionEvent e) {
475        Message m = new ConsumerIdentifiedMessage(srcNodeID, eventID(), validity());
476        connection.put(m, null);
477    }
478
479    public void sendReqProducers(java.awt.event.ActionEvent e) {
480        Message m = new IdentifyProducersMessage(srcNodeID, eventID());
481        connection.put(m, null);
482    }
483
484    public void sendProducerID(java.awt.event.ActionEvent e) {
485        Message m = new ProducerIdentifiedMessage(srcNodeID, eventID(), validity());
486        connection.put(m, null);
487    }
488
489    public void sendDatagramPerformed(java.awt.event.ActionEvent e) {
490        Message m = new DatagramMessage(srcNodeID, destNodeID(),
491                jmri.util.StringUtil.bytesFromHexString(datagramContentsField.getText()));
492        connection.put(m, null);
493    }
494
495    public void sendDatagramReply(java.awt.event.ActionEvent e) {
496        Message m = new DatagramAcknowledgedMessage(srcNodeID, destNodeID());
497        connection.put(m, null);
498    }
499
500    public void restartNode(java.awt.event.ActionEvent e) {
501        Message m = new DatagramMessage(srcNodeID, destNodeID(),
502                new byte[] {0x20, (byte) 0xA9});
503        connection.put(m, null);        
504    }
505    
506    public void clearCache(java.awt.event.ActionEvent e) {
507        jmri.jmrix.openlcb.swing.DropCdiCache.drop(destNodeID(), memo.get(OlcbInterface.class));
508    }
509    
510    public void readPerformed(java.awt.event.ActionEvent e) {
511        int space = addrSpace.getMemorySpace();
512        long addr = Integer.parseInt(configAddressField.getText(), 16);
513        int length = Integer.parseInt(configNumberField.getText());
514        mcs.requestRead(destNodeID(), space, addr,
515                length, new MemoryConfigurationService.McsReadHandler() {
516                    @Override
517                    public void handleReadData(NodeID dest, int space, long address, byte[] data) {
518                        log.debug("Read data received {} bytes",data.length);
519                        readDataField.setText(jmri.util.StringUtil.hexStringFromBytes(data));
520                    }
521
522                    @Override
523                    public void handleFailure(int errorCode) {
524                        log.warn("OpenLCB read failed: 0x{}", Integer.toHexString
525                                (errorCode));
526                    }
527                });
528    }
529
530    public void writePerformed(java.awt.event.ActionEvent e) {
531        int space = addrSpace.getMemorySpace();
532        long addr = Integer.parseInt(configAddressField.getText(), 16);
533        byte[] content = jmri.util.StringUtil.bytesFromHexString(writeDataField.getText());
534        mcs.requestWrite(destNodeID(), space, addr, content, new MemoryConfigurationService.McsWriteHandler() {
535            @Override
536            public void handleSuccess() {
537                // no action required on success
538            }
539
540            @Override
541            public void handleFailure(int errorCode) {
542                log.warn("OpenLCB write failed:  0x{}", Integer.toHexString
543                        (errorCode));
544            }
545        });
546    }
547
548    public void openCdiPane() {
549        actions.openCdiWindow(destNodeID(), destNodeID().toString());
550    }
551
552    // control sequence operation
553    int mNextSequenceElement = 0;
554    javax.swing.Timer timer = null;
555
556    /**
557     * Internal routine to handle timer starts and restarts
558     * @param delay milliseconds to delay
559     */
560    protected void restartTimer(int delay) {
561        if (timer == null) {
562            timer = new javax.swing.Timer(delay, e -> sendNextItem());
563        }
564        timer.stop();
565        timer.setInitialDelay(delay);
566        timer.setRepeats(false);
567        timer.start();
568    }
569
570    /**
571     * Internal routine to handle a timeout and send next item
572     */
573    protected synchronized void timeout() {
574        sendNextItem();
575    }
576
577    /**
578     * Run button pressed down, start the sequence operation
579     * @param e event from GUI
580     *
581     */
582    public void runButtonActionPerformed(java.awt.event.ActionEvent e) {
583        if (!mRunButton.isSelected()) {
584            return;
585        }
586        // make sure at least one is checked
587        boolean ok = false;
588        for (int i = 0; i < MAXSEQUENCE; i++) {
589            if (mUseField[i].isSelected()) {
590                ok = true;
591            }
592        }
593        if (!ok) {
594            mRunButton.setSelected(false);
595            return;
596        }
597        // start the operation
598        mNextSequenceElement = 0;
599        sendNextItem();
600    }
601
602    /**
603     * Echo has been heard, start delay for next packet
604     */
605    void startSequenceDelay() {
606        // at the start, mNextSequenceElement contains index we're
607        // working on
608        int delay = Integer.parseInt(mDelayField[mNextSequenceElement].getText());
609        // increment to next line at completion
610        mNextSequenceElement++;
611        // start timer
612        restartTimer(delay);
613    }
614
615    /**
616     * Send next item; may be used for the first item or when a delay has
617     * elapsed.
618     */
619    void sendNextItem() {
620        // check if still running
621        if (!mRunButton.isSelected()) {
622            return;
623        }
624        // have we run off the end?
625        if (mNextSequenceElement >= MAXSEQUENCE) {
626            // past the end, go back
627            mNextSequenceElement = 0;
628        }
629        // is this one enabled?
630        if (mUseField[mNextSequenceElement].isSelected()) {
631            // make the packet
632            CanMessage m = createPacket(mPacketField[mNextSequenceElement].getText());
633            // send it
634            tc.sendCanMessage(m, this);
635            startSequenceDelay();
636        } else {
637            // ask for the next one
638            mNextSequenceElement++;
639            sendNextItem();
640        }
641    }
642
643    /**
644     * Create a well-formed message from a String String is expected to be space
645     * seperated hex bytes or CbusAddress, e.g.: 12 34 56 +n4e1
646     * @param s string of spaced hex byte codes
647     * @return The packet, with contents filled-in
648     */
649    CanMessage createPacket(String s) {
650        CanMessage m;
651        // Try to convert using CbusAddress class to reuse a little code
652        CbusAddress a = new CbusAddress(s);
653        if (a.check()) {
654            m = a.makeMessage(tc.getCanid());
655        } else {
656            m = new CanMessage(tc.getCanid());
657            // check for header
658            if (s.charAt(0) == '[') {           // NOI18N
659                // extended header
660                m.setExtended(true);
661                int i = s.indexOf(']');       // NOI18N
662                String h = s.substring(1, i);
663                m.setHeader(Integer.parseInt(h, 16));
664                s = s.substring(i + 1);
665            } else if (s.charAt(0) == '(') {  // NOI18N
666                // standard header
667                int i = s.indexOf(')');       // NOI18N
668                String h = s.substring(1, i);
669                m.setHeader(Integer.parseInt(h, 16));
670                s = s.substring(i + 1);
671            }
672            // Try to get hex bytes
673            byte[] b = StringUtil.bytesFromHexString(s);
674            m.setNumDataElements(b.length);
675            // Use &0xff to ensure signed bytes are stored as unsigned ints
676            for (int i = 0; i < b.length; i++) {
677                m.setElement(i, b[i] & 0xff);
678            }
679        }
680        return m;
681    }
682
683    /**
684     * Don't pay attention to messages
685     */
686    @Override
687    public void message(CanMessage m) {
688        // ignore outgoing messages
689    }
690
691    /**
692     * Don't pay attention to replies
693     */
694    @Override
695    public void reply(CanReply m) {
696        // ignore incoming replies
697    }
698
699    /**
700     * When the window closes, stop any sequences running
701     */
702    @Override
703    public void dispose() {
704        mRunButton.setSelected(false);
705        super.dispose();
706    }
707
708    // private data
709    private TrafficController tc = null; // was CanInterface
710    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OpenLcbCanSendPane.class);
711
712}