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