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 need the throttle register to be sent? 204 * @return true if yes or false if no 205 */ 206 public boolean isThrottleRegisterRequired() { 207 boolean ret = true; 208 try { 209 ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") < 0); 210 } catch (IllegalArgumentException ignore) { 211 } 212 return ret; 213 } 214 215 /** 216 * Can this command station handle the newer (V4) function message format? 217 * @return true if yes or false if no 218 */ 219 public boolean isFunctionV4Supported() { 220 boolean ret = false; 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) program message formats? 230 * @return true if yes or false if no 231 */ 232 public boolean isProgramV4Supported() { 233 boolean ret = false; 234 try { 235 ret = (jmri.Version.compareCanonicalVersions(version, "4.0.1") >= 0); 236 } catch (IllegalArgumentException ignore) { 237 } 238 return ret; 239 } 240 241 // A few utility functions 242 /** 243 * Get the Lower byte of a locomotive address from the decimal locomotive 244 * address. 245 * @param address loco address. 246 * @return loco address byte lo. 247 */ 248 public static int getDCCAddressLow(int address) { 249 /* For addresses below 128, we just return the address, otherwise, 250 we need to return the upper byte of the address after we add the 251 offset 0xC000. The first address used for addresses over 127 is 0xC080*/ 252 if (address < 128) { 253 return (address); 254 } else { 255 int temp = address + 0xC000; 256 temp = temp & 0x00FF; 257 return temp; 258 } 259 } 260 261 /** 262 * Get the Upper byte of a locomotive address from the decimal locomotive 263 * address. 264 * @param address loco address. 265 * @return high byte of address. 266 */ 267 public static int getDCCAddressHigh(int address) { 268 /* this isn't actually the high byte, For addresses below 128, we 269 just return 0, otherwise, we need to return the upper byte of the 270 address after we add the offset 0xC000 The first address used for 271 addresses over 127 is 0xC080*/ 272 if (address < 128) { 273 return (0x00); 274 } else { 275 int temp = address + 0xC000; 276 temp = temp & 0xFF00; 277 temp = temp / 256; 278 return temp; 279 } 280 } 281 282 /* To implement the CommandStation Interface, we have to define the 283 sendPacket function */ 284 /** 285 * Send a specific packet to the rails. 286 * 287 * @param packet Byte array representing the packet, including the 288 * error-correction byte. Must not be null. 289 * @param repeats Number of times to repeat the transmission. 290 */ 291 @Override 292 public boolean sendPacket(@Nonnull byte [] packet, int repeats) { 293 294 if (_tc == null) { 295 log.error("Send Packet Called without setting traffic controller"); 296 return false; 297 } 298 299 int reg = 0; // register 0, so this doesn't repeat 300 // DCC++ BaseStation code appends its own error-correction byte. 301 // So we have to omit the JMRI-generated one. 302 DCCppMessage msg = DCCppMessage.makeWriteDCCPacketMainMsg(reg, packet.length - 1, packet); 303 assert msg != null; 304 log.debug("sendPacket:'{}'", msg); 305 306 for (int i = 0; i < repeats; i++) { 307 _tc.sendDCCppMessage(msg, null); 308 } 309 return true; 310 } 311 312 /* 313 * For the command station interface, we need to set the traffic 314 * controller. 315 */ 316 public void setTrafficController(DCCppTrafficController tc) { 317 _tc = tc; 318 } 319 320 private DCCppTrafficController _tc = null; 321 322 public void setSystemConnectionMemo(DCCppSystemConnectionMemo memo) { 323 adaptermemo = memo; 324 } 325 326 public DCCppSystemConnectionMemo getSystemConnectionMemo() { 327 return adaptermemo; 328 } 329 330 private DCCppSystemConnectionMemo adaptermemo; 331 332 private void creatermgr() { 333 if (rmgr == null) { 334 rmgr = new DCCppRegisterManager(maxNumSlots); 335 } 336 } 337 338 @Override 339 public String getUserName() { 340 if (adaptermemo == null) { 341 return "DCC++"; 342 } 343 return adaptermemo.getUserName(); 344 } 345 346 @Override 347 @Nonnull 348 public String getSystemPrefix() { 349 if (adaptermemo == null) { 350 return "D"; 351 } 352 return adaptermemo.getSystemPrefix(); 353 } 354 355 public int requestNewRegister(int addr) { 356 creatermgr(); 357 return (rmgr.requestRegister(addr)); 358 } 359 360 public void releaseRegister(int addr) { 361 creatermgr(); 362 rmgr.releaseRegister(addr); 363 } 364 365 // Return DCCppConstants.NO_REGISTER_FREE if address is not in list 366 public int getRegisterNum(int addr) { 367 creatermgr(); 368 return (rmgr.getRegisterNum(addr)); 369 } 370 371 // Return DCCppConstants.REGISTER_UNALLOCATED if register is unused. 372 public int getRegisterAddress(int num) { 373 creatermgr(); 374 return (rmgr.getRegisterAddress(num)); 375 } 376 377 /* 378 * We need to register for logging 379 */ 380 private final static Logger log = LoggerFactory.getLogger(DCCppCommandStation.class); 381 382}