001package jmri.jmrix.dccpp; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import javax.annotation.Nonnull; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Defines the standard/common routines used in multiple classes related to the 013 * DCC++ Command Station, on a DCC++ network. 014 * 015 * @author Bob Jacobsen Copyright (C) 2001 016 * @author Portions by Paul Bender Copyright (C) 2003 017 * @author Mark Underwood Copyright (C) 2015 018 * @author Harald Barth Copyright (C) 2019 019 * 020 * Based on LenzCommandStation by Bob Jacobsen and Paul Bender 021 */ 022public class DCCppCommandStation implements jmri.CommandStation { 023 024 /* The First group of routines is for obtaining the Software and 025 hardware version of the Command station */ 026 /** 027 * We need to add a few data members for saving the version information we 028 * get from the layout. 029 * 030 */ 031 @Nonnull private String stationType = "Unknown"; 032 @Nonnull private String build = "Unknown"; 033 @Nonnull private String version = "0.0.0"; 034 private DCCppRegisterManager rmgr = null; 035 private int maxNumSlots = DCCppConstants.MAX_MAIN_REGISTERS; //default to register size 036 private List<String> trackModes = new ArrayList<String>(); //Modes for tracks A-H (as returned from EX-CS) 037 038 public DCCppCommandStation() { 039 super(); 040 } 041 042 public DCCppCommandStation(DCCppSystemConnectionMemo memo) { 043 super(); 044 adaptermemo = memo; 045 } 046 047 public void setStationType(String s) { 048 if (!stationType.equals(s)) { 049 log.info("Station Type set to '{}'", s); 050 stationType = s; 051 } 052 } 053 054 /** 055 * Get the Station Type of the connected Command Station 056 * it is populated by response from the CS, initially "Unknown" 057 * @return StationType 058 */ 059 @Nonnull 060 public String getStationType() { 061 return stationType; 062 } 063 064 public void setBuild(String s) { 065 if (!build.equals(s)) { 066 log.info("Build set to '{}'", s); 067 build = s; 068 } 069 } 070 071 /** 072 * Get the Build of the connected Command Station 073 * it is populated by response from the CS, initially "Unknown" 074 * @return Build 075 */ 076 @Nonnull 077 public String getBuild() { 078 return build; 079 } 080 081 public void setVersion(String s) { 082 if (!version.equals(s)) { 083 if (jmri.Version.isCanonicalVersion(s)) { 084 log.info("Version set to '{}'", s); 085 version = s; 086 } else { 087 log.warn("'{}' is not a canonical version, version not changed", s); 088 } 089 } 090 } 091 092 /** 093 * Get the canonical version of the connected Command Station 094 * it is populated by response from the CS, so initially '0.0.0' 095 * @return Version 096 */ 097 @Nonnull 098 public String getVersion() { 099 return version; 100 } 101 102 /** 103 * Parse the DCC++ CS status response to pull out the base station version 104 * and software version. 105 * @param l status response to query. 106 */ 107 protected void setCommandStationInfo(DCCppReply l) { 108 // V1.0 Syntax 109 //String syntax = "iDCC\\+\\+\\s+BASE\\s+STATION\\s+v([a-zA-Z0-9_.]+):\\s+BUILD\\s+((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))"; 110 // V1.1 Syntax 111 //String syntax = "iDCC\\+\\+BASE STATION FOR ARDUINO \\b(\\w+)\\b \\/ (ARDUINO|POLOLU\\sMC33926) MOTOR SHIELD: BUILD ((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))"; 112 // V1.0/V1.1 Simplified 113 //String syntax = "iDCC\\+\\+(.*): BUILD (.*)"; 114 // V1.2.1 Syntax 115 // String syntax = "iDCC++ BASE STATION FOR ARDUINO \\b(\\w+)\\b \\/ (ARDUINO|POLOLU\\sMC33926) MOTOR SHIELD: ((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))"; 116 // Changes from v1.1: space between "DCC++" and "BASE", and "BUILD" is removed. 117 // V1.0/V1.1/V1.2 Simplified 118 // String syntax = "iDCC\\+\\+\\s?(.*):\\s?(?:BUILD)? (.*)"; 119 120 setStationType(l.getStationType()); 121 setBuild(l.getBuildString()); 122 setVersion(l.getVersion()); 123 } 124 125 protected void setCommandStationMaxNumSlots(DCCppReply l) { 126 int newNumSlots = l.getValueInt(1); 127 setCommandStationMaxNumSlots(newNumSlots); 128 } 129 protected void setCommandStationMaxNumSlots(int newNumSlots) { 130 if (newNumSlots < maxNumSlots) { 131 log.warn("Command Station maxNumSlots cannot be reduced from {} to {}", maxNumSlots, newNumSlots); 132 return; 133 } 134 if (newNumSlots != maxNumSlots) { 135 log.info("changing maxNumSlots from {} to {}", maxNumSlots, newNumSlots); 136 maxNumSlots = newNumSlots; 137 } 138 } 139 protected int getCommandStationMaxNumSlots() { 140 return maxNumSlots; 141 } 142 143 /** 144 * Provide the version string returned during the initial check. 145 * @return version string. 146 */ 147 public String getVersionString() { 148 return(stationType + ": BUILD " + build); 149 } 150 151 /** 152 * Remember whether or not in service mode. 153 */ 154 boolean mInServiceMode = false; 155 156 /** 157 * DCC++ command station does provide Ops Mode. 158 * @return always true. 159 */ 160 public boolean isOpsModePossible() { 161 return true; 162 } 163 164 /** 165 * Does this command station require JMRI to send periodic function refresh packets? 166 * @return true if required, false if not 167 */ 168 public boolean isFunctionRefreshRequired() { 169 boolean ret = true; 170 try { 171 //command stations starting with 3 handle their own function refresh 172 ret = (jmri.Version.compareCanonicalVersions(version, "3.0.0") < 0); 173 } catch (IllegalArgumentException ignore) { 174 } 175 return ret; 176 } 177 178 /** 179 * Does this command station support the latest Current commands <JG>, <JI>, etc.? 180 * @return true if supported, false if not 181 */ 182 public boolean isCurrentListSupported() { 183 boolean ret = false; 184 try { 185 ret = (jmri.Version.compareCanonicalVersions(version, "4.2.20") >= 0); 186 } catch (IllegalArgumentException ignore) { 187 } 188 return ret; 189 } 190 191 /** 192 * Can this command station handle the Read with a starting value ('V'erify) 193 * @return true if yes or false if no 194 */ 195 public boolean isReadStartValSupported() { 196 boolean ret = false; 197 try { 198 //command stations starting with 3 can handle reads with startVals 199 ret = (jmri.Version.compareCanonicalVersions(version, "3.0.0") >= 0); 200 } catch (IllegalArgumentException ignore) { 201 } 202 return ret; 203 } 204 205 /** 206 * Can this command station handle the Servo and Vpin Turnout creation message formats? 207 * @return true if yes or false if no 208 */ 209 public boolean isServoTurnoutCreationSupported() { 210 boolean ret = false; 211 try { 212 // SERVO and VPIN turnout commands added at 3.2.0 213 ret = (jmri.Version.compareCanonicalVersions(version, "3.2.0") >= 0); 214 } catch (IllegalArgumentException ignore) { 215 } 216 return ret; 217 } 218 219 /** 220 * Does this command station require the new "J" commands for turnout definitions? 221 * @return true if yes or false if no 222 */ 223 public boolean isTurnoutIDsMessageRequired() { 224 boolean ret = false; 225 try { 226 ret = (jmri.Version.compareCanonicalVersions(version, "5.0.0") >= 0); 227 } catch (IllegalArgumentException ignore) { 228 } 229 return ret; 230 } 231 232 /** 233 * Does this command station need the throttle register to be sent? 234 * @return true if yes or false if no 235 */ 236 public boolean isThrottleRegisterRequired() { 237 boolean ret = true; 238 try { 239 ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") < 0); 240 } catch (IllegalArgumentException ignore) { 241 } 242 return ret; 243 } 244 245 /** 246 * Can this command station handle the newer (V4) function message format? 247 * @return true if yes or false if no 248 */ 249 public boolean isFunctionV4Supported() { 250 boolean ret = false; 251 try { 252 ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") >= 0); 253 } catch (IllegalArgumentException ignore) { 254 } 255 return ret; 256 } 257 258 /** 259 * Can this command station handle the newer (V4) program message formats? 260 * @return true if yes or false if no 261 */ 262 public boolean isProgramV4Supported() { 263 boolean ret = false; 264 try { 265 ret = (jmri.Version.compareCanonicalVersions(version, "4.0.1") >= 0); 266 } catch (IllegalArgumentException ignore) { 267 } 268 return ret; 269 } 270 271 // A few utility functions 272 /** 273 * Get the Lower byte of a locomotive address from the decimal locomotive 274 * address. 275 * @param address loco address. 276 * @return loco address byte lo. 277 */ 278 public static int getDCCAddressLow(int address) { 279 /* For addresses below 128, we just return the address, otherwise, 280 we need to return the upper byte of the address after we add the 281 offset 0xC000. The first address used for addresses over 127 is 0xC080*/ 282 if (address < 128) { 283 return (address); 284 } else { 285 int temp = address + 0xC000; 286 temp = temp & 0x00FF; 287 return temp; 288 } 289 } 290 291 /** 292 * Get the Upper byte of a locomotive address from the decimal locomotive 293 * address. 294 * @param address loco address. 295 * @return high byte of address. 296 */ 297 public static int getDCCAddressHigh(int address) { 298 /* this isn't actually the high byte, For addresses below 128, we 299 just return 0, otherwise, we need to return the upper byte of the 300 address after we add the offset 0xC000 The first address used for 301 addresses over 127 is 0xC080*/ 302 if (address < 128) { 303 return (0x00); 304 } else { 305 int temp = address + 0xC000; 306 temp = temp & 0xFF00; 307 temp = temp / 256; 308 return temp; 309 } 310 } 311 312 /* To implement the CommandStation Interface, we have to define the 313 sendPacket function */ 314 /** 315 * Send a specific packet to the rails. 316 * 317 * @param packet Byte array representing the packet, including the 318 * error-correction byte. Must not be null. 319 * @param repeats Number of times to repeat the transmission. 320 */ 321 @Override 322 public boolean sendPacket(@Nonnull byte [] packet, int repeats) { 323 324 if (_tc == null) { 325 log.error("Send Packet Called without setting traffic controller"); 326 return false; 327 } 328 329 int reg = 0; // register 0, so this doesn't repeat 330 // DCC++ BaseStation code appends its own error-correction byte. 331 // So we have to omit the JMRI-generated one. 332 DCCppMessage msg = DCCppMessage.makeWriteDCCPacketMainMsg(reg, packet.length - 1, packet); 333 assert msg != null; 334 log.debug("sendPacket:'{}'", msg); 335 336 for (int i = 0; i < repeats; i++) { 337 _tc.sendDCCppMessage(msg, null); 338 } 339 return true; 340 } 341 342 /* 343 * For the command station interface, we need to set the traffic 344 * controller. 345 */ 346 public void setTrafficController(DCCppTrafficController tc) { 347 _tc = tc; 348 } 349 350 private DCCppTrafficController _tc = null; 351 352 public void setSystemConnectionMemo(DCCppSystemConnectionMemo memo) { 353 adaptermemo = memo; 354 } 355 356 public DCCppSystemConnectionMemo getSystemConnectionMemo() { 357 return adaptermemo; 358 } 359 360 private DCCppSystemConnectionMemo adaptermemo; 361 362 private void creatermgr() { 363 if (rmgr == null) { 364 rmgr = new DCCppRegisterManager(maxNumSlots); 365 } 366 } 367 368 @Override 369 public String getUserName() { 370 if (adaptermemo == null) { 371 return "DCC++"; 372 } 373 return adaptermemo.getUserName(); 374 } 375 376 @Override 377 @Nonnull 378 public String getSystemPrefix() { 379 if (adaptermemo == null) { 380 return "D"; 381 } 382 return adaptermemo.getSystemPrefix(); 383 } 384 385 public int requestNewRegister(int addr) { 386 creatermgr(); 387 return (rmgr.requestRegister(addr)); 388 } 389 390 public void releaseRegister(int addr) { 391 creatermgr(); 392 rmgr.releaseRegister(addr); 393 } 394 395 // Return DCCppConstants.NO_REGISTER_FREE if address is not in list 396 public int getRegisterNum(int addr) { 397 creatermgr(); 398 return (rmgr.getRegisterNum(addr)); 399 } 400 401 // Return DCCppConstants.REGISTER_UNALLOCATED if register is unused. 402 public int getRegisterAddress(int num) { 403 creatermgr(); 404 return (rmgr.getRegisterAddress(num)); 405 } 406 407 // entries will be received in order, but the whole list may be sent again 408 public void setTrackMode(int i, String mode) { 409 if (this.trackModes.size() > i) { 410 this.trackModes.set(i, mode); //update it 411 } else { 412 this.trackModes.add(mode); // add it 413 } 414 } 415 public List<String> getTrackModes() { 416 return trackModes; 417 } 418 public String getTrackMode(int i) { 419 if (this.trackModes.size() > i) { 420 return trackModes.get(i); 421 } else { 422 return ""; //don't crash downstream 423 } 424 } 425 426 /* 427 * We need to register for logging 428 */ 429 private final static Logger log = LoggerFactory.getLogger(DCCppCommandStation.class); 430 431}