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}