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