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}