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