001package jmri.jmrix.openlcb;
002
003import java.net.InetAddress;
004import java.net.NetworkInterface;
005import java.net.SocketException;
006import java.nio.charset.StandardCharsets;
007import java.util.ArrayList;
008import java.util.List;
009import java.util.Random;
010import java.util.ResourceBundle;
011
012import jmri.ClockControl;
013import jmri.GlobalProgrammerManager;
014import jmri.InstanceManager;
015import jmri.jmrix.can.CanListener;
016import jmri.jmrix.can.CanMessage;
017import jmri.jmrix.can.CanReply;
018import jmri.jmrix.can.CanSystemConnectionMemo;
019import jmri.jmrix.can.TrafficController;
020import jmri.profile.ProfileManager;
021import jmri.util.ThreadingUtil;
022
023import org.openlcb.*;
024import org.openlcb.can.AliasMap;
025import org.openlcb.can.CanInterface;
026import org.openlcb.can.MessageBuilder;
027import org.openlcb.can.OpenLcbCanFrame;
028import org.openlcb.implementations.DatagramService;
029import org.openlcb.implementations.MemoryConfigurationService;
030import org.openlcb.protocols.TimeProtocol;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * Does configuration for OpenLCB communications implementations.
036 *
037 * @author Bob Jacobsen Copyright (C) 2010
038 */
039public class OlcbConfigurationManager extends jmri.jmrix.can.ConfigurationManager {
040
041    // Constants for the protocol options keys. These option keys are used to save configuration
042    // in the profile.xml and set on a per-connection basis in the connection preferences.
043
044    // Protocol key for node identification
045    public static final String OPT_PROTOCOL_IDENT = "Ident";
046
047    // Option key for Node ID
048    public static final String OPT_IDENT_NODEID = "NodeId";
049    // Option key for User Name, used for the Simple Node Ident Protocol
050    public static final String OPT_IDENT_USERNAME = "UserName";
051    // Option key for User Description, used for the Simple Node Ident Protocol
052    public static final String OPT_IDENT_DESCRIPTION = "UserDescription";
053
054    // Protocol key for fast clock
055    public static final String OPT_PROTOCOL_FASTCLOCK = "FastClock";
056
057    // Option key for fast clock mode
058    public static final String OPT_FASTCLOCK_ENABLE = "EnableMode";
059    // Option value for setting fast clock to disabled.
060    public static final String OPT_FASTCLOCK_ENABLE_OFF = "disabled";
061    // Option value for setting fast clock to clock generator/producer/master.
062    public static final String OPT_FASTCLOCK_ENABLE_GENERATOR = "generator";
063    // Option value for setting fast clock to clock consumer/slave.
064    public static final String OPT_FASTCLOCK_ENABLE_CONSUMER = "consumer";
065
066    // Option key for setting the clock identifier.
067    public static final String OPT_FASTCLOCK_ID = "ClockId";
068    // Option value for using the well-known clock id "default clock"
069    public static final String OPT_FASTCLOCK_ID_DEFAULT = "default";
070    // Option value for using the well-known clock id "default real-time clock"
071    public static final String OPT_FASTCLOCK_ID_DEFAULT_RT = "realtime";
072    // Option value for using the well-known clock id "alternate clock 1"
073    public static final String OPT_FASTCLOCK_ID_ALT_1 = "alt1";
074    // Option value for using the well-known clock id "alternate clock 2"
075    public static final String OPT_FASTCLOCK_ID_ALT_2 = "alt2";
076    // Option value for using a custom clock ID
077    public static final String OPT_FASTCLOCK_ID_CUSTOM = "custom";
078
079    // Option key for setting the clock identifier to a custom value. Must set ClockId==custom in
080    // order to be in effect. The custom clock id is in node ID format.
081    public static final String OPT_FASTCLOCK_CUSTOM_ID = "ClockCustomId";
082
083    public OlcbConfigurationManager(CanSystemConnectionMemo memo) {
084        super(memo);
085
086        InstanceManager.store(cf = new jmri.jmrix.openlcb.swing.OpenLcbComponentFactory(adapterMemo),
087                jmri.jmrix.swing.ComponentFactory.class);
088        InstanceManager.store(this, OlcbConfigurationManager.class);
089    }
090
091    final jmri.jmrix.swing.ComponentFactory cf;
092
093    private void initializeFastClock() {
094        boolean isMaster;
095        String enableOption = adapterMemo.getProtocolOption(OPT_PROTOCOL_FASTCLOCK, OPT_FASTCLOCK_ENABLE);
096        if (OPT_FASTCLOCK_ENABLE_GENERATOR.equals(enableOption)) {
097            isMaster = true;
098        } else if (OPT_FASTCLOCK_ENABLE_CONSUMER.equals(enableOption)) {
099            isMaster = false;
100        } else {
101            // no clock needed.
102            return;
103        }
104
105        NodeID clockId = null;
106        String clockIdSetting = adapterMemo.getProtocolOption(OPT_PROTOCOL_FASTCLOCK, OPT_FASTCLOCK_ID);
107        if (OPT_FASTCLOCK_ID_DEFAULT.equals(clockIdSetting)) {
108            clockId = TimeProtocol.DEFAULT_CLOCK;
109        } else if (OPT_FASTCLOCK_ID_DEFAULT_RT.equals(clockIdSetting)) {
110            clockId = TimeProtocol.DEFAULT_RT_CLOCK;
111        } else if (OPT_FASTCLOCK_ID_ALT_1.equals(clockIdSetting)) {
112            clockId = TimeProtocol.ALT_CLOCK_1;
113        } else if (OPT_FASTCLOCK_ID_ALT_2.equals(clockIdSetting)) {
114            clockId = TimeProtocol.ALT_CLOCK_2;
115        } else if (OPT_FASTCLOCK_ID_CUSTOM.equals(clockIdSetting)) {
116            String customId = adapterMemo.getProtocolOption(OPT_PROTOCOL_FASTCLOCK, OPT_FASTCLOCK_CUSTOM_ID);
117            if (customId == null || customId.isEmpty()) {
118                log.error("OpenLCB clock initialize: User selected custom clock, but did not provide a Custom Clock ID. Using default clock.");
119            } else {
120                try {
121                    clockId = new NodeID(customId);
122                } catch (IllegalArgumentException e) {
123                    log.error("OpenLCB clock initialize: Custom Clock ID '{}' is in illegal format. Use dotted hex notation like 05.01.01.01.DD.EE", customId);
124                }
125            }
126        }
127        if (clockId == null) {
128            clockId = TimeProtocol.DEFAULT_CLOCK;
129        }
130        log.debug("Creating olcb clock with id {} is_master {}", clockId, isMaster);
131        clockControl = new OlcbClockControl(getInterface(), clockId, isMaster);
132        InstanceManager.setDefault(ClockControl.class, clockControl);
133    }
134
135    @Override
136    public void configureManagers() {
137
138        // create our NodeID
139        getOurNodeID();
140
141        // do the connections
142        tc = adapterMemo.getTrafficController();
143
144        olcbCanInterface = createOlcbCanInterface(nodeID, tc);
145
146        // create JMRI objects
147        InstanceManager.setSensorManager(
148                getSensorManager());
149
150        InstanceManager.setTurnoutManager(
151                getTurnoutManager());
152
153        InstanceManager.setStringIOManager(
154                getStringIOManager());
155
156        InstanceManager.setThrottleManager(
157                getThrottleManager());
158
159        InstanceManager.setReporterManager(
160                getReporterManager());
161
162        InstanceManager.setLightManager(
163                getLightManager()
164        );
165
166        InstanceManager.setMeterManager(
167                getMeterManager()
168        );
169
170        InstanceManager.store(getCommandStation(), jmri.CommandStation.class);
171        
172        if (getProgrammerManager().isAddressedModePossible()) {
173            InstanceManager.store(getProgrammerManager(), jmri.AddressedProgrammerManager.class);
174        }
175        if (getProgrammerManager().isGlobalProgrammerAvailable()) {
176            jmri.InstanceManager.store(getProgrammerManager(), GlobalProgrammerManager.class);
177        }
178
179        // start alias acquisition
180        new StartUpHandler().start();
181
182        OlcbInterface iface = getInterface();
183        loaderClient = new LoaderClient(iface.getOutputConnection(),
184                iface.getMemoryConfigurationService(),
185                iface.getDatagramService());
186        iface.registerMessageListener(loaderClient);
187
188        iface.registerMessageListener(new SimpleNodeIdentInfoHandler());
189        iface.registerMessageListener(new PipRequestHandler());
190
191        initializeFastClock();
192
193        aliasMap = new AliasMap();
194        tc.addCanListener(new CanListener() {
195            @Override
196            public void message(CanMessage m) {
197                if (!m.isExtended() || m.isRtr()) {
198                    return;
199                }
200                aliasMap.processFrame(convertFromCan(m));
201            }
202
203            @Override
204            public void reply(CanReply m) {
205                if (!m.isExtended() || m.isRtr()) {
206                    return;
207                }
208                aliasMap.processFrame(convertFromCan(m));
209            }
210        });
211        messageBuilder = new MessageBuilder(aliasMap);
212    }
213
214    CanInterface olcbCanInterface;
215    TrafficController tc;
216    NodeID nodeID;
217    LoaderClient loaderClient;
218    OlcbClockControl clockControl;
219    OlcbEventNameStore olcbEventNameStore = new OlcbEventNameStore();
220    
221    OlcbInterface getInterface() {
222        return olcbCanInterface.getInterface();
223    }
224
225    // internal to OpenLCB library, should not be exposed
226    AliasMap aliasMap;
227    // internal to OpenLCB library, should not be exposed
228    MessageBuilder messageBuilder;
229
230    /**
231     * Check if a type of manager is provided by this manager.
232     *
233     * @param type the class of manager to check
234     * @return true if the type of manager is provided; false otherwise
235     */
236    @Override
237    public boolean provides(Class<?> type) {
238        if (adapterMemo.getDisabled()) {
239            return false;
240        }
241        if (type.equals(jmri.ThrottleManager.class)) {
242            return true;
243        }
244        if (type.equals(jmri.SensorManager.class)) {
245            return true;
246        }
247        if (type.equals(jmri.TurnoutManager.class)) {
248            return true;
249        }
250        if (type.equals(jmri.ReporterManager.class)) {
251            return true;
252        }
253        if (type.equals(jmri.LightManager.class)) {
254            return true;
255        }
256        if (type.equals(jmri.MeterManager.class)) {
257            return true;
258        }
259        if (type.equals(jmri.StringIOManager.class)) {
260            return true;
261        }
262        if (type.equals(jmri.GlobalProgrammerManager.class)) {
263            return true;
264        }
265        if (type.equals(jmri.AddressedProgrammerManager.class)) {
266            return true;
267        }
268        if (type.equals(jmri.CommandStation.class)) {
269            return true;
270        }
271        if (type.equals(AliasMap.class)) {
272            return true;
273        }
274        if (type.equals(MessageBuilder.class)) {
275            return true;
276        }
277        if (type.equals(MimicNodeStore.class)) {
278            return true;
279        }
280        if (type.equals(Connection.class)) {
281            return true;
282        }
283        if (type.equals(MemoryConfigurationService.class)) {
284            return true;
285        }
286        if (type.equals(DatagramService.class)) {
287            return true;
288        }
289        if (type.equals(NodeID.class)) {
290            return true;
291        }
292        if (type.equals(OlcbInterface.class)) {
293            return true;
294        }
295        if (type.equals(CanInterface.class)) {
296            return true;
297        }
298        if (type.equals(ClockControl.class)) {
299            return clockControl != null;
300        }
301        if (type.equals(OlcbEventNameStore.class)) {
302            return true;
303        }
304        return false; // nothing, by default
305    }
306
307    @SuppressWarnings("unchecked")
308    @Override
309    public <T> T get(Class<?> T) {
310        if (adapterMemo.getDisabled()) {
311            return null;
312        }
313        if (T.equals(jmri.ThrottleManager.class)) {
314            return (T) getThrottleManager();
315        }
316        if (T.equals(jmri.SensorManager.class)) {
317            return (T) getSensorManager();
318        }
319        if (T.equals(jmri.TurnoutManager.class)) {
320            return (T) getTurnoutManager();
321        }
322        if (T.equals(jmri.LightManager.class)) {
323            return (T) getLightManager();
324        }
325        if (T.equals(jmri.MeterManager.class)) {
326            return (T) getMeterManager();
327        }
328        if (T.equals(jmri.StringIOManager.class)) {
329            return (T) getStringIOManager();
330        }
331        if (T.equals(jmri.ReporterManager.class)) {
332            return (T) getReporterManager();
333        }
334        if (T.equals(jmri.GlobalProgrammerManager.class)) {
335            return (T) getProgrammerManager();
336        }
337        if (T.equals(jmri.AddressedProgrammerManager.class)) {
338            return (T) getProgrammerManager();
339        }
340        if (T.equals(jmri.CommandStation.class)) {
341            return (T) getCommandStation();
342        }
343        if (T.equals(AliasMap.class)) {
344            return (T) aliasMap;
345        }
346        if (T.equals(MessageBuilder.class)) {
347            return (T) messageBuilder;
348        }
349        if (T.equals(MimicNodeStore.class)) {
350            return (T) getInterface().getNodeStore();
351        }
352        if (T.equals(Connection.class)) {
353            return (T) getInterface().getOutputConnection();
354        }
355        if (T.equals(MemoryConfigurationService.class)) {
356            return (T) getInterface().getMemoryConfigurationService();
357        }
358        if (T.equals(DatagramService.class)) {
359            return (T) getInterface().getDatagramService();
360        }
361        if (T.equals(LoaderClient.class)) {
362            return (T) loaderClient;
363        }
364        if (T.equals(NodeID.class)) {
365            return (T) nodeID;
366        }
367        if (T.equals(OlcbInterface.class)) {
368            return (T) getInterface();
369        }
370        if (T.equals(CanInterface.class)) {
371            return (T) olcbCanInterface;
372        }
373        if (T.equals(ClockControl.class)) {
374            return (T) clockControl;
375        }
376        if (T.equals(OlcbEventNameStore.class)) {
377            return (T) olcbEventNameStore;
378        }
379        return null; // nothing, by default
380    }
381
382    protected OlcbProgrammerManager programmerManager;
383
384    public OlcbProgrammerManager getProgrammerManager() {
385        if (adapterMemo.getDisabled()) {
386            return null;
387        }
388        if (programmerManager == null) {
389            programmerManager = new OlcbProgrammerManager(adapterMemo);
390        }
391        return programmerManager;
392    }
393
394    protected OlcbThrottleManager throttleManager;
395
396    public OlcbThrottleManager getThrottleManager() {
397        if (adapterMemo.getDisabled()) {
398            return null;
399        }
400        if (throttleManager == null) {
401            throttleManager = new OlcbThrottleManager(adapterMemo);
402        }
403        return throttleManager;
404    }
405
406    protected OlcbTurnoutManager turnoutManager;
407
408    public OlcbTurnoutManager getTurnoutManager() {
409        if (adapterMemo.getDisabled()) {
410            return null;
411        }
412        if (turnoutManager == null) {
413            turnoutManager = new OlcbTurnoutManager(adapterMemo);
414        }
415        return turnoutManager;
416    }
417
418    protected OlcbSensorManager sensorManager;
419
420    public OlcbSensorManager getSensorManager() {
421        if (adapterMemo.getDisabled()) {
422            return null;
423        }
424        if (sensorManager == null) {
425            sensorManager = new OlcbSensorManager(adapterMemo);
426        }
427        return sensorManager;
428    }
429
430    protected OlcbLightManager lightManager;
431
432    public OlcbLightManager getLightManager() {
433        if (adapterMemo.getDisabled()) {
434            return null;
435        }
436        if (lightManager == null) {
437            lightManager = new OlcbLightManager(adapterMemo);
438        }
439        return lightManager;
440    }
441
442    protected OlcbMeterManager meterManager;
443
444    public OlcbMeterManager getMeterManager() {
445        if (adapterMemo.getDisabled()) {
446            return null;
447        }
448        if (meterManager == null) {
449            meterManager = new OlcbMeterManager(adapterMemo);
450        }
451        return meterManager;
452    }
453
454    protected OlcbStringIOManager stringIOManager;
455
456    public OlcbStringIOManager getStringIOManager() {
457        if (adapterMemo.getDisabled()) {
458            return null;
459        }
460        if (stringIOManager == null) {
461            stringIOManager = new OlcbStringIOManager(adapterMemo);
462        }
463        return stringIOManager;
464    }
465
466    protected OlcbReporterManager reporterManager;
467
468    public OlcbReporterManager getReporterManager() {
469        if (adapterMemo.getDisabled()) {
470            return null;
471        }
472        if (reporterManager == null) {
473            reporterManager = new OlcbReporterManager(adapterMemo);
474        }
475        return reporterManager;
476    }
477
478    protected OlcbCommandStation commandStation;
479
480    public OlcbCommandStation getCommandStation() {
481        if (adapterMemo.getDisabled()) {
482            return null;
483        }
484        if (commandStation == null) {
485            commandStation = new OlcbCommandStation(adapterMemo);
486        }
487        return commandStation;
488    }
489
490    @Override
491    public void dispose() {
492        if (turnoutManager != null) {
493            InstanceManager.deregister(turnoutManager, jmri.jmrix.openlcb.OlcbTurnoutManager.class);
494        }
495        if (sensorManager != null) {
496            InstanceManager.deregister(sensorManager, jmri.jmrix.openlcb.OlcbSensorManager.class);
497        }
498        if (lightManager != null) {
499            InstanceManager.deregister(lightManager, jmri.jmrix.openlcb.OlcbLightManager.class);
500        }
501        if (cf != null) {
502            InstanceManager.deregister(cf, jmri.jmrix.swing.ComponentFactory.class);
503        }
504        InstanceManager.deregister(this, OlcbConfigurationManager.class);
505
506        if (clockControl != null) {
507            clockControl.dispose();
508            InstanceManager.deregister(clockControl, ClockControl.class);
509        }
510    }
511
512    class SimpleNodeIdentInfoHandler extends MessageDecoder {
513        /**
514         * Helper function to add a string value to the sequence of bytes to send for SNIP
515         * response content.
516         *
517         * @param addString  string to render into byte stream
518         * @param contents   represents the byte stream that will be sent.
519         * @param maxlength  maximum number of characters to include, not counting terminating null
520         */
521        private void  addStringPart(String addString, List<Byte> contents, int maxlength) {
522            if (addString != null && !addString.isEmpty()) {
523                String value = addString.substring(0,Math.min(maxlength, addString.length()));
524                byte[] bb = value.getBytes(StandardCharsets.UTF_8);
525                for (byte b : bb) {
526                    contents.add(b);
527                }
528            }
529            // terminating null byte.
530            contents.add((byte)0);
531        }
532
533        SimpleNodeIdentInfoHandler() {
534            List<Byte> l = new ArrayList<>(256);
535
536            l.add((byte)4); // version byte
537            addStringPart("JMRI", l, 40);  // mfg field; 40 char limit in Standard, not counting final null
538            addStringPart(jmri.Application.getApplicationName(), l, 40);  // model
539            String name = ProfileManager.getDefault().getActiveProfileName();
540            if (name != null) {
541                addStringPart(name, l, 20); // hardware version
542            } else {
543                addStringPart("", l, 20); // hardware version
544            }
545            addStringPart(jmri.Version.name(), l, 20); // software version
546
547            l.add((byte)2); // version byte
548            addStringPart(adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_USERNAME), l, 62);
549            addStringPart(adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_DESCRIPTION), l, 63);
550
551            content = new byte[l.size()];
552            for (int i = 0; i < l.size(); ++i) {
553                content[i] = l.get(i);
554            }
555        }
556        private final byte[] content;
557
558        @Override
559        public void handleSimpleNodeIdentInfoRequest(SimpleNodeIdentInfoRequestMessage msg,
560                Connection sender) {
561            if (msg.getDestNodeID().equals(nodeID)) {
562                // Sending a SNIP reply to the bus crashes the library up to 0.7.7.
563                if (msg.getSourceNodeID().equals(nodeID) || Version.libVersionAtLeast(0, 7, 8)) {
564                    getInterface().getOutputConnection().put(new SimpleNodeIdentInfoReplyMessage(nodeID, msg.getSourceNodeID(), content), this);
565                }
566            }
567        }
568    }
569
570    class PipRequestHandler extends MessageDecoder {
571
572        @Override
573        public void handleProtocolIdentificationRequest(ProtocolIdentificationRequestMessage msg, Connection sender) {
574            long flags = 0x00041000000000L;  // PC, SNIP protocols
575            // only reply if for us
576            if (msg.getDestNodeID() == nodeID) {
577                getInterface().getOutputConnection().put(new ProtocolIdentificationReplyMessage(nodeID, msg.getSourceNodeID(), flags), this);
578            }
579        }
580
581    }
582
583    @Override
584    protected ResourceBundle getActionModelResourceBundle() {
585        return ResourceBundle.getBundle("jmri.jmrix.openlcb.OlcbActionListBundle");
586    }
587
588    /**
589     * Create a node ID in the JMRI range from one byte of IP address, and 2
590     * bytes of PID. That changes each time, which isn't perhaps what's wanted.
591     */
592    protected void getOurNodeID() {
593        try {
594            String userOption = adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_NODEID);
595            if (userOption != null && !userOption.isEmpty()) {
596                try {
597                    nodeID = new NodeID(userOption);
598                    log.trace("getOurNodeID sets known option Node ID: {}", nodeID);
599                    return;
600                } catch (IllegalArgumentException e) {
601                    log.error("User configured a node ID protocol option which is in invalid format ({}). Expected dotted hex notation like 02.01.12.FF.EE.DD", userOption);
602                }
603            }
604            List<NodeID> previous = InstanceManager.getList(NodeID.class);
605            if (!previous.isEmpty()) {
606                nodeID = previous.get(0);
607                log.trace("getOurNodeID sets known instance Node ID: {}", nodeID);
608                return;
609            }
610    
611            long pid = getProcessId(1);
612            log.trace("Process ID: {}", pid);
613    
614            // get first network interface internet address
615            // almost certainly the wrong approach, isn't likely to
616            // find real IP address for coms, but it gets some entropy.
617            InetAddress address = null;
618            try {
619                NetworkInterface n = NetworkInterface.getNetworkInterfaces().nextElement();
620                if (n != null) {
621                    address = n.getInetAddresses().nextElement();
622                }
623                log.debug("InetAddress: {}", address);
624            } catch (SocketException | java.util.NoSuchElementException e) {
625                // SocketException is part of the getNetworkInterfaces specification.
626                // java.util.NoSuchElementException seen on some Windows machines
627                // for unknown reasons.  We provide a short error message in that case.
628                log.warn("Can't get IP address to make NodeID. You should set a NodeID in the Connection preferences.");
629            }
630            
631            int b2 = 0;
632            if (address != null) {
633                b2 = address.getAddress()[0];
634            } else {
635                b2 = (byte)(RANDOM.nextInt(255) & 0xFF); // & 0xFF not strictly necessary, but makes SpotBugs happy
636                log.trace("Used random value {} for address byte", b2);
637            }
638            
639            // store new NodeID
640            nodeID = new NodeID(new byte[]{2, 1, 18, (byte) (b2 & 0xFF), (byte) ((pid >> 8) & 0xFF), (byte) (pid & 0xFF)});
641            log.debug("getOurNodeID sets new Node ID: {}", nodeID);
642            
643        } catch (Exception e) {
644            // We catch Exception here, instead of within the NetworkInterface lookup, because
645            // we want to know which kind of exceptions we're seeing.  If/when this gets reported,
646            // generalize the catch statement above.
647            log.error("Unexpected Exception while processing Node ID definition. Please report this to the JMRI developers", e);
648            byte b2 = (byte)(RANDOM.nextInt(255) & 0xFF); // & 0xFF not strictly necessary, but makes SpotBugs happy
649            byte b1 = (byte)(RANDOM.nextInt(255) & 0xFF);
650            byte b0 = (byte)(RANDOM.nextInt(255) & 0xFF);
651            nodeID = new NodeID(new byte[]{2, 1, 18, b2, b1, b0});            
652            log.debug("Setting random Node ID: {}", nodeID);
653        }
654    }
655
656    private static final Random RANDOM = new Random();
657    
658    protected long getProcessId(final long fallback) {
659        // Note: may fail in some JVM implementations
660        // therefore fallback has to be provided
661
662        // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
663        final String jvmName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
664        final int index = jvmName.indexOf('@');
665
666        if (index < 1) {
667            // part before '@' empty (index = 0) / '@' not found (index = -1)
668            return fallback;
669        }
670
671        try {
672            return Long.parseLong(jvmName.substring(0, index));
673        } catch (NumberFormatException e) {
674            // ignore
675        }
676        return fallback;
677    }
678
679    public static CanInterface createOlcbCanInterface(NodeID nodeID, TrafficController tc) {
680        final CanInterface olcbIf = new CanInterface(nodeID, frame -> tc.sendCanMessage(convertToCan(frame), null));
681        tc.addCanListener(new CanListener() {
682            @Override
683            public void message(CanMessage m) {
684                // ignored -- loopback is handled by the olcbInterface.
685            }
686
687            @Override
688            public void reply(CanReply m) {
689                if (!m.isExtended() || m.isRtr()) {
690                    return;
691                }
692                olcbIf.frameInput().send(convertFromCan(m));
693            }
694        });
695        olcbIf.getInterface().setLoopbackThread((Runnable r)->ThreadingUtil.runOnLayout(r::run));
696        return olcbIf;
697    }
698
699    static jmri.jmrix.can.CanMessage convertToCan(org.openlcb.can.CanFrame f) {
700        jmri.jmrix.can.CanMessage fout = new jmri.jmrix.can.CanMessage(f.getData(), f.getHeader());
701        fout.setExtended(true);
702        return fout;
703    }
704
705    static OpenLcbCanFrame convertFromCan(jmri.jmrix.can.CanFrame message) {
706        OpenLcbCanFrame fin = new OpenLcbCanFrame(0);
707        fin.setHeader(message.getHeader());
708        if (message.getNumDataElements() == 0) {
709            return fin;
710        }
711        byte[] data = new byte[message.getNumDataElements()];
712        for (int i = 0; i < data.length; ++i) {
713            data[i] = (byte) (message.getElement(i) & 0xff);
714        }
715        fin.setData(data);
716        return fin;
717    }
718
719    /**
720     * State machine to handle startup
721     */
722    class StartUpHandler {
723
724        javax.swing.Timer timer;
725
726        static final int START_DELAY = 2500;
727
728        void start() {
729            log.debug("StartUpHandler starts up");
730            // wait geological time for adapter startup
731            timer = new javax.swing.Timer(START_DELAY, new javax.swing.AbstractAction() {
732
733                @Override
734                public void actionPerformed(java.awt.event.ActionEvent e) {
735                    Thread t = jmri.util.ThreadingUtil.newThread(
736                                    () -> {
737                                        // N.B. during JUnit testing, the following call tends to hang
738                                        // on semaphore acquisition in org.openlcb.can.CanInterface.initialize()
739                                        // near line 109 in openlcb lib 0.7.22, which leaves
740                                        // the thread hanging around forever.
741                                        olcbCanInterface.initialize();
742                                    },
743                                "olcbCanInterface.initialize");
744                    t.start();
745                }
746            });
747            timer.setRepeats(false);
748            timer.start();
749        }
750    }
751
752    private final static Logger log = LoggerFactory.getLogger(OlcbConfigurationManager.class);
753}