001package jmri.jmrix.openlcb.swing.monitor;
002
003import jmri.InstanceManager;
004import jmri.UserPreferencesManager;
005import jmri.jmrix.can.CanListener;
006import jmri.jmrix.can.CanMessage;
007import jmri.jmrix.can.CanReply;
008import jmri.jmrix.can.CanSystemConnectionMemo;
009import jmri.jmrix.can.swing.CanPanelInterface;
010
011import org.openlcb.EventID;
012import org.openlcb.EventMessage;
013import org.openlcb.Message;
014import org.openlcb.OlcbInterface;
015import org.openlcb.can.AliasMap;
016import org.openlcb.can.MessageBuilder;
017import org.openlcb.can.OpenLcbCanFrame;
018import org.openlcb.implementations.EventTable;
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022import javax.swing.BoxLayout;
023import javax.swing.JCheckBox;
024import javax.swing.JPanel;
025
026/**
027 * Frame displaying (and logging) OpenLCB (CAN) frames
028 *
029 * @author Bob Jacobsen Copyright (C) 2009, 2010
030 */
031public class MonitorPane extends jmri.jmrix.AbstractMonPane implements CanListener, CanPanelInterface {
032
033    public MonitorPane() {
034        super();
035        pm = InstanceManager.getDefault(UserPreferencesManager.class);
036    }
037
038    CanSystemConnectionMemo memo;
039    AliasMap aliasMap;
040    MessageBuilder messageBuilder;
041    OlcbInterface olcbInterface;
042    final JCheckBox nodeNameCheckBox = new JCheckBox();
043    final JCheckBox eventCheckBox = new JCheckBox();
044    final JCheckBox eventAllCheckBox = new JCheckBox();
045    final String nodeNameCheck = this.getClass().getName() + ".NodeName";
046    final String eventCheck = this.getClass().getName() + ".Event";
047    final String eventAllCheck = this.getClass().getName() + ".EventAll";
048    private final UserPreferencesManager pm;
049
050    @Override
051    public void initContext(Object context) {
052        if (context instanceof CanSystemConnectionMemo) {
053            initComponents((CanSystemConnectionMemo) context);
054        }
055    }
056
057    @Override
058    public void initComponents(CanSystemConnectionMemo memo) {
059        this.memo = memo;
060
061        memo.getTrafficController().addCanListener(this);
062
063        aliasMap = memo.get(org.openlcb.can.AliasMap.class);
064        messageBuilder = new MessageBuilder(aliasMap);
065        olcbInterface = memo.get(OlcbInterface.class);
066
067        setFixedWidthFont();
068    }
069
070    @Override
071    public String getTitle() {
072        return Bundle.getMessage("MonitorTitle");
073    }
074
075    @Override
076    protected void init() {
077    }
078
079    @Override
080    public void dispose() {
081        try {
082            memo.getTrafficController().removeCanListener(this);
083        } catch(NullPointerException npe){
084            log.debug("Null Pointer Exception while attempting to remove Can Listener",npe);
085        }
086
087        pm.setSimplePreferenceState(nodeNameCheck, nodeNameCheckBox.isSelected());
088        pm.setSimplePreferenceState(eventCheck, eventCheckBox.isSelected());
089        pm.setSimplePreferenceState(eventAllCheck, eventAllCheckBox.isSelected());
090
091        super.dispose();
092    }
093
094    @Override
095    protected void addCustomControlPanes(JPanel parent) {
096        JPanel p = new JPanel();
097        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
098
099        nodeNameCheckBox.setText(Bundle.getMessage("CheckBoxShowNodeName"));
100        nodeNameCheckBox.setVisible(true);
101        nodeNameCheckBox.setSelected(pm.getSimplePreferenceState(nodeNameCheck));
102        p.add(nodeNameCheckBox);
103
104        eventCheckBox.setText(Bundle.getMessage("CheckBoxShowEvent"));
105        eventCheckBox.setVisible(true);
106        eventCheckBox.setSelected(pm.getSimplePreferenceState(eventCheck));
107        p.add(eventCheckBox);
108
109        eventAllCheckBox.setText(Bundle.getMessage("CheckBoxShowEventAll"));
110        eventAllCheckBox.setVisible(true);
111        eventAllCheckBox.setSelected(pm.getSimplePreferenceState(eventAllCheck));
112        p.add(eventAllCheckBox);
113
114        parent.add(p);
115        super.addCustomControlPanes(parent);
116    }
117
118    String formatFrame(boolean extended, int header, int len, int[] content) {
119        StringBuilder formatted = new StringBuilder();
120        formatted.append(extended ? "[" : "(");
121        formatted.append(Integer.toHexString(header));
122        formatted.append((extended ? "]" : ")"));
123        for (int i = 0; i < len; i++) {
124            formatted.append(" ");
125            formatted.append(jmri.util.StringUtil.twoHexFromInt(content[i]));
126        }
127        for (int i = len; i < 8; i++) {
128            formatted.append("   ");
129        }
130        return new String(formatted);
131    }
132
133    // see jmri.jmrix.openlcb.OlcbConfigurationManager
134    java.util.List<Message> frameToMessages(int header, int len, int[] content) {
135        OpenLcbCanFrame frame = new OpenLcbCanFrame(header & 0xFFF);
136        frame.setHeader(header);
137        if (len != 0) {
138            byte[] data = new byte[len];
139            for (int i = 0; i < data.length; i++) {
140                data[i] = (byte) content[i];
141            }
142            frame.setData(data);
143        }
144
145        aliasMap.processFrame(frame);
146        return messageBuilder.processFrame(frame);
147    }
148
149    void format(String prefix, boolean extended, int header, int len, int[] content) {
150        String raw = formatFrame(extended, header, len, content);
151        String formatted;
152        if (extended && (header & 0x08000000) != 0) {
153            // is a message type
154            java.util.List<Message> list = frameToMessages(header, len, content);
155            if (list == null || list.isEmpty()) {
156                // didn't format, check for partial datagram
157                if ((header & 0x0F000000) == 0x0B000000) {
158                    formatted = prefix + ": (Start of Datagram)";
159                } else if ((header & 0x0F000000) == 0x0C000000) {
160                    formatted = prefix + ": (Middle of Datagram)";
161                } else if (((header & 0x0FFFF000) == 0x09A08000) && (content.length > 0)) {
162                    // SNIP multi frame reply
163                    switch (content[0] & 0xF0) {
164                        case 0x10:
165                            formatted = prefix + ": SNIP Reply 1st frame";
166                            break;
167                        case 0x20:
168                            formatted = prefix + ": SNIP Reply last frame";
169                            break;
170                        case 0x30:
171                            formatted = prefix + ": SNIP Reply middle frame";
172                            break;
173                        default:
174                            formatted = prefix + ": SNIP Reply unknown";
175                            break;
176                    }
177                } else if (((header & 0x0FFFF000) == 0x095EB000) && (content.length > 0)) {
178                    // Traction Control Command multi frame reply
179                    switch (content[0] & 0xF0) {
180                        case 0x10:
181                            formatted = prefix + ": Traction Control Command 1st frame";
182                            break;
183                        case 0x20:
184                            formatted = prefix + ": Traction Control Command last frame";
185                            break;
186                        case 0x30:
187                            formatted = prefix + ": Traction Control Command middle frame";
188                            break;
189                        default:
190                            formatted = prefix + ": Traction Control Command unknown";
191                            break;
192                    }
193                } else if (((header & 0x0FFFF000) == 0x091E9000) && (content.length > 0)) {
194                    // Traction Control Reply multi frame reply
195                    switch (content[0] & 0xF0) {
196                        case 0x10:
197                            formatted = prefix + ": Traction Control Reply 1st frame";
198                            break;
199                        case 0x20:
200                            formatted = prefix + ": Traction Control Reply last frame";
201                            break;
202                        case 0x30:
203                            formatted = prefix + ": Traction Control Reply middle frame";
204                            break;
205                        default:
206                            formatted = prefix + ": Traction Control Reply unknown";
207                            break;
208                    }
209                } else {
210                    formatted = prefix + ": Unknown message " + raw;
211                }
212            } else {
213                Message msg = list.get(0);
214                StringBuilder sb = new StringBuilder();
215                sb.append(prefix);
216                sb.append(": ");
217                sb.append(list.get(0).toString());
218                if (nodeNameCheckBox.isSelected() && olcbInterface != null) {
219                    String name = olcbInterface.getNodeStore().findNode(list.get(0).getSourceNodeID()).getSimpleNodeIdent().getUserName();
220                    if (name != null && !name.equals("")) {
221                        sb.append("\n  Src: ");
222                        sb.append(name);
223                    }
224                }
225                if ((eventCheckBox.isSelected() || eventAllCheckBox.isSelected()) && olcbInterface != null && msg instanceof EventMessage) {
226                    EventID ev = ((EventMessage) msg).getEventID();
227                    EventTable.EventTableEntry[] descr =
228                            olcbInterface.getEventTable().getEventInfo(ev).getAllEntries();
229                    if (descr.length > 0) {
230                        sb.append("\n  Event: ");
231                        sb.append(descr[0].getDescription());
232                    }
233                    if (eventAllCheckBox.isSelected()) {
234                        for (int i = 1; i < descr.length; i++) {
235                            sb.append("\n  Event: ");
236                            sb.append(descr[i].getDescription());
237                        }
238                    }
239                }
240                formatted = sb.toString();
241            }
242        } else {
243            // control type
244            String alias = "0x" + Integer.toHexString(header & 0xFFF).toUpperCase();
245            if ((header & 0x07000000) == 0x00000000) {
246                int[] data = new int[len];
247                System.arraycopy(content, 0, data, 0, len);
248                switch (header & 0x00FFF000) {
249                    case 0x00700000:
250                        formatted = prefix + ": Alias " + alias + " RID frame";
251                        break;
252                    case 0x00701000:
253                        formatted = prefix + ": Alias " + alias + " AMD frame for node " + org.openlcb.Utilities.toHexDotsString(data);
254                        break;
255                    case 0x00702000:
256                        formatted = prefix + ": Alias " + alias + " AME frame for node " + org.openlcb.Utilities.toHexDotsString(data);
257                        break;
258                    case 0x00703000:
259                        formatted = prefix + ": Alias " + alias + " AMR frame for node " + org.openlcb.Utilities.toHexDotsString(data);
260                        break;
261                    default:
262                        formatted = prefix + ": Unknown CAN control frame: " + raw;
263                        break;
264                }
265            } else {
266                formatted = prefix + ": Alias " + alias + " CID " + ((header & 0x7000000) / 0x1000000) + " frame";
267            }
268        }
269        nextLine(formatted + "\n", raw);
270    }
271
272    @Override
273    public synchronized void message(CanMessage l) {  // receive a message and log it
274        log.debug("Message: {}", l);
275        format("S", l.isExtended(), l.getHeader(), l.getNumDataElements(), l.getData());
276    }
277
278    @Override
279    public synchronized void reply(CanReply l) {  // receive a reply and log it
280        log.debug("Reply: {}", l);
281        format("R", l.isExtended(), l.getHeader(), l.getNumDataElements(), l.getData());
282    }
283
284    private final static Logger log = LoggerFactory.getLogger(MonitorPane.class);
285
286}