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.store(getPowerManager(), jmri.PowerManager.class);
154
155        InstanceManager.setStringIOManager(
156                getStringIOManager());
157
158        InstanceManager.setThrottleManager(
159                getThrottleManager());
160
161        InstanceManager.setReporterManager(
162                getReporterManager());
163
164        InstanceManager.setLightManager(
165                getLightManager()
166        );
167
168        InstanceManager.setMeterManager(
169                getMeterManager()
170        );
171
172        InstanceManager.store(getCommandStation(), jmri.CommandStation.class);
173        
174        if (getProgrammerManager().isAddressedModePossible()) {
175            InstanceManager.store(getProgrammerManager(), jmri.AddressedProgrammerManager.class);
176        }
177        if (getProgrammerManager().isGlobalProgrammerAvailable()) {
178            jmri.InstanceManager.store(getProgrammerManager(), GlobalProgrammerManager.class);
179        }
180
181        // start alias acquisition
182        new StartUpHandler().start();
183
184        OlcbInterface iface = getInterface();
185        loaderClient = new LoaderClient(iface.getOutputConnection(),
186                iface.getMemoryConfigurationService(),
187                iface.getDatagramService());
188        iface.registerMessageListener(loaderClient);
189
190        iface.registerMessageListener(new SimpleNodeIdentInfoHandler());
191        iface.registerMessageListener(new PipRequestHandler());
192
193        initializeFastClock();
194
195        aliasMap = new AliasMap();
196        tc.addCanListener(new CanListener() {
197            @Override
198            public void message(CanMessage m) {
199                if (!m.isExtended() || m.isRtr()) {
200                    return;
201                }
202                aliasMap.processFrame(convertFromCan(m));
203            }
204
205            @Override
206            public void reply(CanReply m) {
207                if (!m.isExtended() || m.isRtr()) {
208                    return;
209                }
210                aliasMap.processFrame(convertFromCan(m));
211            }
212        });
213        messageBuilder = new MessageBuilder(aliasMap);
214    }
215
216    CanInterface olcbCanInterface;
217    TrafficController tc;
218    NodeID nodeID;
219    LoaderClient loaderClient;
220    OlcbClockControl clockControl;
221    OlcbEventNameStore olcbEventNameStore = new OlcbEventNameStore();
222    
223    OlcbInterface getInterface() {
224        return olcbCanInterface.getInterface();
225    }
226
227    // internal to OpenLCB library, should not be exposed
228    AliasMap aliasMap;
229    // internal to OpenLCB library, should not be exposed
230    MessageBuilder messageBuilder;
231
232    /**
233     * Check if a type of manager is provided by this manager.
234     *
235     * @param type the class of manager to check
236     * @return true if the type of manager is provided; false otherwise
237     */
238    @Override
239    public boolean provides(Class<?> type) {
240        if (adapterMemo.getDisabled()) {
241            return false;
242        }
243        if (type.equals(jmri.ThrottleManager.class)) {
244            return true;
245        }
246        if (type.equals(jmri.SensorManager.class)) {
247            return true;
248        }
249        if (type.equals(jmri.TurnoutManager.class)) {
250            return true;
251        }
252        if (type.equals(jmri.PowerManager.class)) {
253            return true;
254        }
255        if (type.equals(jmri.ReporterManager.class)) {
256            return true;
257        }
258        if (type.equals(jmri.LightManager.class)) {
259            return true;
260        }
261        if (type.equals(jmri.MeterManager.class)) {
262            return true;
263        }
264        if (type.equals(jmri.StringIOManager.class)) {
265            return true;
266        }
267        if (type.equals(jmri.GlobalProgrammerManager.class)) {
268            return true;
269        }
270        if (type.equals(jmri.AddressedProgrammerManager.class)) {
271            return true;
272        }
273        if (type.equals(jmri.CommandStation.class)) {
274            return true;
275        }
276        if (type.equals(AliasMap.class)) {
277            return true;
278        }
279        if (type.equals(MessageBuilder.class)) {
280            return true;
281        }
282        if (type.equals(MimicNodeStore.class)) {
283            return true;
284        }
285        if (type.equals(Connection.class)) {
286            return true;
287        }
288        if (type.equals(MemoryConfigurationService.class)) {
289            return true;
290        }
291        if (type.equals(DatagramService.class)) {
292            return true;
293        }
294        if (type.equals(NodeID.class)) {
295            return true;
296        }
297        if (type.equals(OlcbInterface.class)) {
298            return true;
299        }
300        if (type.equals(CanInterface.class)) {
301            return true;
302        }
303        if (type.equals(ClockControl.class)) {
304            return clockControl != null;
305        }
306        if (type.equals(OlcbEventNameStore.class)) {
307            return true;
308        }
309        return false; // nothing, by default
310    }
311
312    @SuppressWarnings("unchecked")
313    @Override
314    public <T> T get(Class<?> T) {
315        if (adapterMemo.getDisabled()) {
316            return null;
317        }
318        if (T.equals(jmri.ThrottleManager.class)) {
319            return (T) getThrottleManager();
320        }
321        if (T.equals(jmri.SensorManager.class)) {
322            return (T) getSensorManager();
323        }
324        if (T.equals(jmri.TurnoutManager.class)) {
325            return (T) getTurnoutManager();
326        }
327        if (T.equals(jmri.PowerManager.class)) {
328            return (T) getPowerManager();
329        }
330        if (T.equals(jmri.LightManager.class)) {
331            return (T) getLightManager();
332        }
333        if (T.equals(jmri.MeterManager.class)) {
334            return (T) getMeterManager();
335        }
336        if (T.equals(jmri.StringIOManager.class)) {
337            return (T) getStringIOManager();
338        }
339        if (T.equals(jmri.ReporterManager.class)) {
340            return (T) getReporterManager();
341        }
342        if (T.equals(jmri.GlobalProgrammerManager.class)) {
343            return (T) getProgrammerManager();
344        }
345        if (T.equals(jmri.AddressedProgrammerManager.class)) {
346            return (T) getProgrammerManager();
347        }
348        if (T.equals(jmri.CommandStation.class)) {
349            return (T) getCommandStation();
350        }
351        if (T.equals(AliasMap.class)) {
352            return (T) aliasMap;
353        }
354        if (T.equals(MessageBuilder.class)) {
355            return (T) messageBuilder;
356        }
357        if (T.equals(MimicNodeStore.class)) {
358            return (T) getInterface().getNodeStore();
359        }
360        if (T.equals(Connection.class)) {
361            return (T) getInterface().getOutputConnection();
362        }
363        if (T.equals(MemoryConfigurationService.class)) {
364            return (T) getInterface().getMemoryConfigurationService();
365        }
366        if (T.equals(DatagramService.class)) {
367            return (T) getInterface().getDatagramService();
368        }
369        if (T.equals(LoaderClient.class)) {
370            return (T) loaderClient;
371        }
372        if (T.equals(NodeID.class)) {
373            return (T) nodeID;
374        }
375        if (T.equals(OlcbInterface.class)) {
376            return (T) getInterface();
377        }
378        if (T.equals(CanInterface.class)) {
379            return (T) olcbCanInterface;
380        }
381        if (T.equals(ClockControl.class)) {
382            return (T) clockControl;
383        }
384        if (T.equals(OlcbEventNameStore.class)) {
385            return (T) olcbEventNameStore;
386        }
387        return null; // nothing, by default
388    }
389
390    protected OlcbProgrammerManager programmerManager;
391
392    public OlcbProgrammerManager getProgrammerManager() {
393        if (adapterMemo.getDisabled()) {
394            return null;
395        }
396        if (programmerManager == null) {
397            programmerManager = new OlcbProgrammerManager(adapterMemo);
398        }
399        return programmerManager;
400    }
401
402    protected OlcbThrottleManager throttleManager;
403
404    public OlcbThrottleManager getThrottleManager() {
405        if (adapterMemo.getDisabled()) {
406            return null;
407        }
408        if (throttleManager == null) {
409            throttleManager = new OlcbThrottleManager(adapterMemo);
410        }
411        return throttleManager;
412    }
413
414    protected OlcbTurnoutManager turnoutManager;
415
416    public OlcbTurnoutManager getTurnoutManager() {
417        if (adapterMemo.getDisabled()) {
418            return null;
419        }
420        if (turnoutManager == null) {
421            turnoutManager = new OlcbTurnoutManager(adapterMemo);
422        }
423        return turnoutManager;
424    }
425
426    protected OlcbPowerManager powerManager;
427
428    public OlcbPowerManager getPowerManager() {
429        if (adapterMemo.getDisabled()) {
430            return null;
431        }
432        if (powerManager == null) {
433            powerManager = new OlcbPowerManager(adapterMemo);
434        }
435        return powerManager;
436    }
437
438    protected OlcbSensorManager sensorManager;
439
440    public OlcbSensorManager getSensorManager() {
441        if (adapterMemo.getDisabled()) {
442            return null;
443        }
444        if (sensorManager == null) {
445            sensorManager = new OlcbSensorManager(adapterMemo);
446        }
447        return sensorManager;
448    }
449
450    protected OlcbLightManager lightManager;
451
452    public OlcbLightManager getLightManager() {
453        if (adapterMemo.getDisabled()) {
454            return null;
455        }
456        if (lightManager == null) {
457            lightManager = new OlcbLightManager(adapterMemo);
458        }
459        return lightManager;
460    }
461
462    protected OlcbMeterManager meterManager;
463
464    public OlcbMeterManager getMeterManager() {
465        if (adapterMemo.getDisabled()) {
466            return null;
467        }
468        if (meterManager == null) {
469            meterManager = new OlcbMeterManager(adapterMemo);
470        }
471        return meterManager;
472    }
473
474    protected OlcbStringIOManager stringIOManager;
475
476    public OlcbStringIOManager getStringIOManager() {
477        if (adapterMemo.getDisabled()) {
478            return null;
479        }
480        if (stringIOManager == null) {
481            stringIOManager = new OlcbStringIOManager(adapterMemo);
482        }
483        return stringIOManager;
484    }
485
486    protected OlcbReporterManager reporterManager;
487
488    public OlcbReporterManager getReporterManager() {
489        if (adapterMemo.getDisabled()) {
490            return null;
491        }
492        if (reporterManager == null) {
493            reporterManager = new OlcbReporterManager(adapterMemo);
494        }
495        return reporterManager;
496    }
497
498    protected OlcbCommandStation commandStation;
499
500    public OlcbCommandStation getCommandStation() {
501        if (adapterMemo.getDisabled()) {
502            return null;
503        }
504        if (commandStation == null) {
505            commandStation = new OlcbCommandStation(adapterMemo);
506        }
507        return commandStation;
508    }
509
510    @Override
511    public void dispose() {
512        if (turnoutManager != null) {
513            InstanceManager.deregister(turnoutManager, jmri.jmrix.openlcb.OlcbTurnoutManager.class);
514        }
515        if (sensorManager != null) {
516            InstanceManager.deregister(sensorManager, jmri.jmrix.openlcb.OlcbSensorManager.class);
517        }
518        if (lightManager != null) {
519            InstanceManager.deregister(lightManager, jmri.jmrix.openlcb.OlcbLightManager.class);
520        }
521        if (cf != null) {
522            InstanceManager.deregister(cf, jmri.jmrix.swing.ComponentFactory.class);
523        }
524        InstanceManager.deregister(this, OlcbConfigurationManager.class);
525
526        if (clockControl != null) {
527            clockControl.dispose();
528            InstanceManager.deregister(clockControl, ClockControl.class);
529        }
530    }
531
532    class SimpleNodeIdentInfoHandler extends MessageDecoder {
533        /**
534         * Helper function to add a string value to the sequence of bytes to send for SNIP
535         * response content.
536         *
537         * @param addString  string to render into byte stream
538         * @param contents   represents the byte stream that will be sent.
539         * @param maxlength  maximum number of characters to include, not counting terminating null
540         */
541        private void  addStringPart(String addString, List<Byte> contents, int maxlength) {
542            if (addString != null && !addString.isEmpty()) {
543                String value = addString.substring(0,Math.min(maxlength, addString.length()));
544                byte[] bb = value.getBytes(StandardCharsets.UTF_8);
545                for (byte b : bb) {
546                    contents.add(b);
547                }
548            }
549            // terminating null byte.
550            contents.add((byte)0);
551        }
552
553        SimpleNodeIdentInfoHandler() {
554            List<Byte> l = new ArrayList<>(256);
555
556            l.add((byte)4); // version byte
557            addStringPart("JMRI", l, 40);  // mfg field; 40 char limit in Standard, not counting final null
558            addStringPart(jmri.Application.getApplicationName(), l, 40);  // model
559            String name = ProfileManager.getDefault().getActiveProfileName();
560            if (name != null) {
561                addStringPart(name, l, 20); // hardware version
562            } else {
563                addStringPart("", l, 20); // hardware version
564            }
565            addStringPart(jmri.Version.name(), l, 20); // software version
566
567            l.add((byte)2); // version byte
568            addStringPart(adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_USERNAME), l, 62);
569            addStringPart(adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_DESCRIPTION), l, 63);
570
571            content = new byte[l.size()];
572            for (int i = 0; i < l.size(); ++i) {
573                content[i] = l.get(i);
574            }
575        }
576        private final byte[] content;
577
578        @Override
579        public void handleSimpleNodeIdentInfoRequest(SimpleNodeIdentInfoRequestMessage msg,
580                Connection sender) {
581            if (msg.getDestNodeID().equals(nodeID)) {
582                // Sending a SNIP reply to the bus crashes the library up to 0.7.7.
583                if (msg.getSourceNodeID().equals(nodeID) || Version.libVersionAtLeast(0, 7, 8)) {
584                    getInterface().getOutputConnection().put(new SimpleNodeIdentInfoReplyMessage(nodeID, msg.getSourceNodeID(), content), this);
585                }
586            }
587        }
588    }
589
590    class PipRequestHandler extends MessageDecoder {
591
592        @Override
593        public void handleProtocolIdentificationRequest(ProtocolIdentificationRequestMessage msg, Connection sender) {
594            long flags = 0x00041000000000L;  // PC, SNIP protocols
595            // only reply if for us
596            if (msg.getDestNodeID() == nodeID) {
597                getInterface().getOutputConnection().put(new ProtocolIdentificationReplyMessage(nodeID, msg.getSourceNodeID(), flags), this);
598            }
599        }
600
601    }
602
603    @Override
604    protected ResourceBundle getActionModelResourceBundle() {
605        return ResourceBundle.getBundle("jmri.jmrix.openlcb.OlcbActionListBundle");
606    }
607
608    /**
609     * Create a node ID in the JMRI range from one byte of IP address, and 2
610     * bytes of PID. That changes each time, which isn't perhaps what's wanted.
611     */
612    protected void getOurNodeID() {
613        try {
614            String userOption = adapterMemo.getProtocolOption(OPT_PROTOCOL_IDENT, OPT_IDENT_NODEID);
615            if (userOption != null && !userOption.isEmpty()) {
616                try {
617                    nodeID = new NodeID(userOption);
618                    log.trace("getOurNodeID sets known option Node ID: {}", nodeID);
619                    return;
620                } catch (IllegalArgumentException e) {
621                    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);
622                }
623            }
624            List<NodeID> previous = InstanceManager.getList(NodeID.class);
625            if (!previous.isEmpty()) {
626                nodeID = previous.get(0);
627                log.trace("getOurNodeID sets known instance Node ID: {}", nodeID);
628                return;
629            }
630    
631            long pid = getProcessId(1);
632            log.trace("Process ID: {}", pid);
633    
634            // get first network interface internet address
635            // almost certainly the wrong approach, isn't likely to
636            // find real IP address for coms, but it gets some entropy.
637            InetAddress address = null;
638            try {
639                NetworkInterface n = NetworkInterface.getNetworkInterfaces().nextElement();
640                if (n != null) {
641                    address = n.getInetAddresses().nextElement();
642                }
643                log.debug("InetAddress: {}", address);
644            } catch (SocketException | java.util.NoSuchElementException e) {
645                // SocketException is part of the getNetworkInterfaces specification.
646                // java.util.NoSuchElementException seen on some Windows machines
647                // for unknown reasons.  We provide a short error message in that case.
648                log.warn("Can't get IP address to make NodeID. You should set a NodeID in the Connection preferences.");
649            }
650            
651            int b2 = 0;
652            if (address != null) {
653                b2 = address.getAddress()[0];
654            } else {
655                b2 = (byte)(RANDOM.nextInt(255) & 0xFF); // & 0xFF not strictly necessary, but makes SpotBugs happy
656                log.trace("Used random value {} for address byte", b2);
657            }
658            
659            // store new NodeID
660            nodeID = new NodeID(new byte[]{2, 1, 18, (byte) (b2 & 0xFF), (byte) ((pid >> 8) & 0xFF), (byte) (pid & 0xFF)});
661            log.debug("getOurNodeID sets new Node ID: {}", nodeID);
662            
663        } catch (Exception e) {
664            // We catch Exception here, instead of within the NetworkInterface lookup, because
665            // we want to know which kind of exceptions we're seeing.  If/when this gets reported,
666            // generalize the catch statement above.
667            log.error("Unexpected Exception while processing Node ID definition. Please report this to the JMRI developers", e);
668            byte b2 = (byte)(RANDOM.nextInt(255) & 0xFF); // & 0xFF not strictly necessary, but makes SpotBugs happy
669            byte b1 = (byte)(RANDOM.nextInt(255) & 0xFF);
670            byte b0 = (byte)(RANDOM.nextInt(255) & 0xFF);
671            nodeID = new NodeID(new byte[]{2, 1, 18, b2, b1, b0});            
672            log.debug("Setting random Node ID: {}", nodeID);
673        }
674    }
675
676    private static final Random RANDOM = new Random();
677    
678    protected long getProcessId(final long fallback) {
679        // Note: may fail in some JVM implementations
680        // therefore fallback has to be provided
681
682        // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
683        final String jvmName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
684        final int index = jvmName.indexOf('@');
685
686        if (index < 1) {
687            // part before '@' empty (index = 0) / '@' not found (index = -1)
688            return fallback;
689        }
690
691        try {
692            return Long.parseLong(jvmName.substring(0, index));
693        } catch (NumberFormatException e) {
694            // ignore
695        }
696        return fallback;
697    }
698
699    public static CanInterface createOlcbCanInterface(NodeID nodeID, TrafficController tc) {
700        final CanInterface olcbIf = new CanInterface(nodeID, frame -> tc.sendCanMessage(convertToCan(frame), null));
701        tc.addCanListener(new CanListener() {
702            @Override
703            public void message(CanMessage m) {
704                // ignored -- loopback is handled by the olcbInterface.
705            }
706
707            @Override
708            public void reply(CanReply m) {
709                if (!m.isExtended() || m.isRtr()) {
710                    return;
711                }
712                olcbIf.frameInput().send(convertFromCan(m));
713            }
714        });
715        olcbIf.getInterface().setLoopbackThread((Runnable r)->ThreadingUtil.runOnLayout(r::run));
716        return olcbIf;
717    }
718
719    static jmri.jmrix.can.CanMessage convertToCan(org.openlcb.can.CanFrame f) {
720        jmri.jmrix.can.CanMessage fout = new jmri.jmrix.can.CanMessage(f.getData(), f.getHeader());
721        fout.setExtended(true);
722        return fout;
723    }
724
725    static OpenLcbCanFrame convertFromCan(jmri.jmrix.can.CanFrame message) {
726        OpenLcbCanFrame fin = new OpenLcbCanFrame(0);
727        fin.setHeader(message.getHeader());
728        if (message.getNumDataElements() == 0) {
729            return fin;
730        }
731        byte[] data = new byte[message.getNumDataElements()];
732        for (int i = 0; i < data.length; ++i) {
733            data[i] = (byte) (message.getElement(i) & 0xff);
734        }
735        fin.setData(data);
736        return fin;
737    }
738
739    /**
740     * State machine to handle startup
741     */
742    class StartUpHandler {
743
744        javax.swing.Timer timer;
745
746        static final int START_DELAY = 2500;
747
748        void start() {
749            log.debug("StartUpHandler starts up");
750            // wait geological time for adapter startup
751            timer = new javax.swing.Timer(START_DELAY, new javax.swing.AbstractAction() {
752
753                @Override
754                public void actionPerformed(java.awt.event.ActionEvent e) {
755                    Thread t = jmri.util.ThreadingUtil.newThread(
756                                    () -> {
757                                        // N.B. during JUnit testing, the following call tends to hang
758                                        // on semaphore acquisition in org.openlcb.can.CanInterface.initialize()
759                                        // near line 109 in openlcb lib 0.7.22, which leaves
760                                        // the thread hanging around forever.
761                                        olcbCanInterface.initialize();
762                                    },
763                                "olcbCanInterface.initialize");
764                    t.start();
765                }
766            });
767            timer.setRepeats(false);
768            timer.start();
769        }
770    }
771
772    private final static Logger log = LoggerFactory.getLogger(OlcbConfigurationManager.class);
773}