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