001package jmri.jmrix.bidib; 002 003import java.util.BitSet; 004import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 005import jmri.SpeedStepMode; 006import jmri.DccLocoAddress; 007import jmri.LocoAddress; 008import jmri.jmrix.AbstractThrottle; 009//import jmri.Throttle; 010 011import org.bidib.jbidibc.messages.enums.DirectionEnum; 012import org.bidib.jbidibc.core.DefaultMessageListener; 013import org.bidib.jbidibc.messages.DriveState; 014import org.bidib.jbidibc.core.MessageListener; 015import org.bidib.jbidibc.messages.Node; 016import org.bidib.jbidibc.messages.enums.CsQueryTypeEnum; 017import org.bidib.jbidibc.messages.enums.DriveAcknowledge; 018import org.bidib.jbidibc.messages.enums.SpeedStepsEnum; 019import org.bidib.jbidibc.messages.message.CommandStationQueryMessage; 020import org.bidib.jbidibc.messages.message.CommandStationDriveMessage; 021import org.bidib.jbidibc.messages.utils.NodeUtils; 022 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026 027/** 028 * An implementation of DccThrottle with code specific to an BiDiB connection. 029 * <P> 030 * 031 * @author Bob Jacobsen Copyright (C) 2001 032 * @author Eckart Meyer Copyright (C) 2019-2023 033 */ 034public class BiDiBThrottle extends AbstractThrottle { 035 036 /* Unfortunately one of the recent changes removes the possibility to set the 037 * current status of the functions as received from BiDiB, because 038 * AbstractThrottle now uses a private array FUNCTION_BOOLEAN_ARRAY[]. 039 * Using set provided setFx functions would send out the new status again. 040 * 041 * So we have no choice and have to duplicate this array here and also 042 * some of the functions :-( 043 */ 044 045 046 private final BitSet activeFunctions;// = new BitSet(29); //0..28 047 private final BitSet functions;// = new BitSet(29); 048 private float oldSpeed = 0.0f; 049 050 private BiDiBTrafficController tc = null; 051 MessageListener messageListener = null; 052 protected Node node = null; 053 054 // sendDeregister is a little hack to enable the user to set the loco to sleep 055 // i.e. remove it from the DCC memory of the command station. The loco 056 // won't be updated then until another MSG_CS_DRIVE message for that 057 // loco will arrive. 058 private boolean sendDeregister = false; 059 060 /** 061 * Constructor. 062 * @param memo system connection memo to use 063 * @param locoAddress DCC loco locoAddress 064 */ 065// @SuppressWarnings("OverridableMethodCallInConstructor") 066 public BiDiBThrottle(BiDiBSystemConnectionMemo memo, DccLocoAddress locoAddress) { 067 super(memo); 068 this.tc = memo.getBiDiBTrafficController(); 069 node = tc.getFirstCommandStationNode(); 070 log.trace("++ctor"); 071// setSpeedStepMode(SpeedStepMode128); 072 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 073 074 // cache settings. It would be better to read the actual state or at least cache this somethere 075 this.speedSetting = 0; 076/* 077 this.f0 = false; 078 this.f1 = false; 079 this.f2 = false; 080 this.f3 = false; 081 this.f4 = false; 082 this.f5 = false; 083 this.f6 = false; 084 this.f7 = false; 085 this.f8 = false; 086 this.f9 = false; 087 this.f10 = false; 088 this.f11 = false; 089 this.f12 = false; 090 this.f13 = false; 091 this.f14 = false; 092 this.f15 = false; 093 this.f16 = false; 094 this.f17 = false; 095 this.f18 = false; 096 this.f19 = false; 097 this.f20 = false; 098 this.f21 = false; 099 this.f22 = false; 100 this.f23 = false; 101 this.f24 = false; 102 this.f25 = false; 103 this.f26 = false; 104 this.f27 = false; 105 this.f28 = false; 106*/ 107 this.locoAddress = locoAddress; 108 this.isForward = true; 109 110 // jbidibc wants the functions as a BitSet ... 111 activeFunctions = new BitSet(29); //0..28 112 functions = new BitSet(29); 113 for (int bitIndex = 0; bitIndex < activeFunctions.size(); bitIndex++) { 114 //log.trace("init function {}", bitIndex); 115 activeFunctions.set(bitIndex, true); //all functions enabled for now... no way to ask the loco as far as I can see 116 functions.set(bitIndex, false); //all off 117 } 118 119 createThrottleListener(); 120 121 //requestStateDelayed(); 122 requestState(); 123 } 124 125 DccLocoAddress locoAddress; 126 127 128 /** 129 * Request the state of a loco from BiDiB 130 */ 131 public void requestState() { 132 log.debug("request csState for addr {}", locoAddress); 133 tc.sendBiDiBMessage( 134 new CommandStationQueryMessage(CsQueryTypeEnum.LOCO_LIST, this.locoAddress.getNumber()), node); //send to command station node 135 } 136 137 /** 138 * {@inheritDoc} 139 */ 140 @Override 141 public LocoAddress getLocoAddress() { 142 return locoAddress; 143 } 144 145 /** 146 * Send the message to set the state of functions F0, F1, F2, F3, F4. 147 */ 148 @Override 149 protected void sendFunctionGroup1() { 150 log.trace("sendFunctionGroup1"); 151 sendDriveCommand(false); 152 } 153 154 /** 155 * Send the message to set the state of functions F5, F6, F7, F8. 156 */ 157 @Override 158 protected void sendFunctionGroup2() { 159 log.trace("sendFunctionGroup2"); 160 sendDriveCommand(false); 161 } 162 163 /** 164 * Send the message to set the state of functions F9, F10, F11, F12. 165 */ 166 @Override 167 protected void sendFunctionGroup3() { 168 log.trace("sendFunctionGroup3"); 169 sendDriveCommand(false); 170 } 171 172 /** 173 * Send the message to set the state of functions F13, F14, F15, F16, F17, 174 * F18, F19, F20 175 */ 176 @Override 177 protected void sendFunctionGroup4() { 178 log.trace("sendFunctionGroup4"); 179 sendDriveCommand(false); 180 } 181 182 /** 183 * Send the message to set the state of functions F21, F22, F23, F24, F25, 184 * F26, F27, F28 185 */ 186 @Override 187 protected void sendFunctionGroup5() { 188 log.trace("sendFunctionGroup5"); 189 sendDriveCommand(false); 190 } 191 192 /** 193 * Set the speed {@literal &} direction. 194 * <P> 195 * 196 * @param speed Number from 0 to 1; less than zero is emergency stop 197 */ 198 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 199 @Override 200 public void setSpeedSetting(float speed) { 201 synchronized(this) { 202 oldSpeed = this.speedSetting; 203 this.speedSetting = speed; //sendDriveCommand needs it - TODO: should be redesigned 204 205 if (sendDriveCommand(true)) { 206 if (log.isDebugEnabled()) { 207 log.debug("setSpeedSetting= {}",speed); 208 } 209 this.speedSetting = oldSpeed; //super.setSpeedSetting needs the old speed here and then sets the new one. As sayed, this should be redesigned 210 super.setSpeedSetting(speed); 211 } 212 else { 213 this.speedSetting = oldSpeed; 214 //notifyPropertyChangeListener("SpeedSetting", null, oldSpeed); 215 } 216 } 217 } 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override 223 public void setIsForward(boolean forward) { 224 boolean old = isForward; 225 isForward = forward; //see above 226 227 if (sendDriveCommand(false)) { 228 if (log.isDebugEnabled()) { 229 log.debug("setIsForward= {}", forward); 230 } 231 if (old != forward) { 232 isForward = old; 233 super.setIsForward(forward); 234 } 235 } 236 else { 237 isForward = old; 238 //notifyPropertyChangeListener("IsForward", null, old); 239 } 240 } 241 242 /** 243 * Internal send method for this class. 244 * Allocates speed and function data and constructs a BiDiB message 245 * 246 * @param isSpeedSet false if not yet 247 * @return true if successful 248 */ 249 protected boolean sendDriveCommand(boolean isSpeedSet) { 250 int addr; 251 SpeedStepsEnum mode; 252 Integer speed; 253 254 synchronized(this) { 255 if (!isSpeedSet && this.speedSetting < 0) { 256 this.speedSetting = 0; //remove estop condition when changing something other than speed 257 } 258 // BiDiB has only one message to set speed, direction and all functions 259 addr = locoAddress.getNumber(); 260 switch(this.speedStepMode) { 261 case NMRA_DCC_14: 262 mode = SpeedStepsEnum.DCC14; break; 263 case NMRA_DCC_28: 264 mode = SpeedStepsEnum.DCC28; break; 265 default: 266 mode = SpeedStepsEnum.DCC128; break; 267 } 268 speed = intSpeed(speedSetting); 269 } 270 DirectionEnum dir = isForward ? DirectionEnum.FORWARD : DirectionEnum.BACKWARD; 271/* old - before v5.1.2 272 functions.set(0, getF0()); 273 functions.set(1, getF1()); 274 functions.set(2, getF2()); 275 functions.set(3, getF3()); 276 functions.set(4, getF4()); 277 functions.set(5, getF5()); 278 functions.set(6, getF6()); 279 functions.set(7, getF7()); 280 functions.set(8, getF8()); 281 functions.set(9, getF9()); 282 functions.set(10, getF10()); 283 functions.set(11, getF11()); 284 functions.set(12, getF12()); 285 functions.set(13, getF13()); 286 functions.set(14, getF14()); 287 functions.set(15, getF15()); 288 functions.set(16, getF16()); 289 functions.set(17, getF17()); 290 functions.set(18, getF18()); 291 functions.set(19, getF19()); 292 functions.set(20, getF20()); 293 functions.set(21, getF21()); 294 functions.set(22, getF22()); 295 functions.set(23, getF23()); 296 functions.set(24, getF24()); 297 functions.set(25, getF25()); 298 functions.set(26, getF26()); 299 functions.set(27, getF27()); 300 functions.set(28, getF28()); 301*/ 302 for (int i = 0; i <= 28; i++) { 303 functions.set(i, getFunction(i)); 304 } 305 306 BitSet curActiveFunctions = (BitSet)activeFunctions.clone(); 307 308 if (sendDeregister) { 309 sendDeregister = false; 310 //functions.clear(); 311 curActiveFunctions.clear(); 312 speed = null; 313 log.info("deregister loco reuqested ({})", addr); 314 } 315 316 317 log.debug("sendBiDiBMessage: addr: {}, mode: {}, direction: {}, speed: {}, active functions: {}, enabled functions: {}", 318 addr, mode, dir, speed, curActiveFunctions.toByteArray(), functions.toByteArray()); 319 320//direct message variant, fully async 321 tc.sendBiDiBMessage( 322 new CommandStationDriveMessage(addr, mode, speed, dir, curActiveFunctions, functions), 323 node); //send to command station node 324 325 return true; 326 } 327 328/// just to see what happens... seems that those methods won't be called by JMRI 329// @Override 330// public void dispatch(ThrottleListener l) { 331// log.debug("BiDiBThrottle.dispatch: {}", l); 332// super.dispatch(l); 333// } 334// 335// @Override 336// public void release(ThrottleListener l) { 337// log.debug("BiDiBThrottle.release: {}", l); 338// super.release(l); 339// } 340/////////////////////////// 341 342 protected void receiveFunctions(byte[] functions) { 343 344 updateFunction(0, (functions[0] & 0x10) != 0); 345 updateFunction(1, (functions[0] & 0x01) != 0); 346 updateFunction(2, (functions[0] & 0x02) != 0); 347 updateFunction(3, (functions[0] & 0x04) != 0); 348 updateFunction(4, (functions[0] & 0x08) != 0); 349 350 updateFunction(5, (functions[1] & 0x01) != 0); 351 updateFunction(6, (functions[1] & 0x02) != 0); 352 updateFunction(7, (functions[1] & 0x04) != 0); 353 updateFunction(8, (functions[1] & 0x08) != 0); 354 updateFunction(9, (functions[1] & 0x10) != 0); 355 updateFunction(10, (functions[1] & 0x20) != 0); 356 updateFunction(11, (functions[1] & 0x40) != 0); 357 updateFunction(12, (functions[1] & 0x80) != 0); 358 359 updateFunction(13, (functions[2] & 0x01) != 0); 360 updateFunction(14, (functions[2] & 0x02) != 0); 361 updateFunction(15, (functions[2] & 0x04) != 0); 362 updateFunction(16, (functions[2] & 0x08) != 0); 363 updateFunction(17, (functions[2] & 0x10) != 0); 364 updateFunction(18, (functions[2] & 0x20) != 0); 365 updateFunction(19, (functions[2] & 0x40) != 0); 366 updateFunction(20, (functions[2] & 0x80) != 0); 367 368 updateFunction(21, (functions[3] & 0x01) != 0); 369 updateFunction(22, (functions[3] & 0x02) != 0); 370 updateFunction(23, (functions[3] & 0x04) != 0); 371 updateFunction(24, (functions[3] & 0x08) != 0); 372 updateFunction(25, (functions[3] & 0x10) != 0); 373 updateFunction(26, (functions[3] & 0x20) != 0); 374 updateFunction(27, (functions[3] & 0x40) != 0); 375 updateFunction(28, (functions[3] & 0x80) != 0); 376 377/* 378 not possible any more since 4.19.5 - updateFunction is now used, see above 379 this.f0 = receiveFunction(Throttle.F0, this.f0, functions[0] & 0x10); 380 this.f1 = receiveFunction(Throttle.F1, this.f1, functions[0] & 0x01); 381 this.f2 = receiveFunction(Throttle.F2, this.f2, functions[0] & 0x02); 382 this.f3 = receiveFunction(Throttle.F3, this.f3, functions[0] & 0x04); 383 this.f4 = receiveFunction(Throttle.F4, this.f4, functions[0] & 0x08); 384 385 this.f5 = receiveFunction(Throttle.F5, this.f5, functions[1] & 0x01); 386 this.f6 = receiveFunction(Throttle.F6, this.f6, functions[1] & 0x02); 387 this.f7 = receiveFunction(Throttle.F7, this.f7, functions[1] & 0x04); 388 this.f8 = receiveFunction(Throttle.F8, this.f8, functions[1] & 0x08); 389 this.f9 = receiveFunction(Throttle.F9, this.f9, functions[1] & 0x10); 390 this.f10 = receiveFunction(Throttle.F10, this.f10, functions[1] & 0x20); 391 this.f11 = receiveFunction(Throttle.F11, this.f11, functions[1] & 0x40); 392 this.f12 = receiveFunction(Throttle.F12, this.f12, functions[1] & 0x80); 393 394 this.f13 = receiveFunction(Throttle.F13, this.f13, functions[2] & 0x01); 395 this.f14 = receiveFunction(Throttle.F14, this.f14, functions[2] & 0x02); 396 this.f15 = receiveFunction(Throttle.F15, this.f15, functions[2] & 0x04); 397 this.f16 = receiveFunction(Throttle.F16, this.f16, functions[2] & 0x08); 398 this.f17 = receiveFunction(Throttle.F17, this.f17, functions[2] & 0x10); 399 this.f18 = receiveFunction(Throttle.F18, this.f18, functions[2] & 0x20); 400 this.f19 = receiveFunction(Throttle.F19, this.f19, functions[2] & 0x40); 401 this.f20 = receiveFunction(Throttle.F20, this.f20, functions[2] & 0x80); 402 403 this.f21 = receiveFunction(Throttle.F21, this.f21, functions[3] & 0x01); 404 this.f22 = receiveFunction(Throttle.F22, this.f22, functions[3] & 0x02); 405 this.f23 = receiveFunction(Throttle.F23, this.f23, functions[3] & 0x04); 406 this.f24 = receiveFunction(Throttle.F24, this.f24, functions[3] & 0x08); 407 this.f25 = receiveFunction(Throttle.F25, this.f25, functions[3] & 0x10); 408 this.f26 = receiveFunction(Throttle.F26, this.f26, functions[3] & 0x20); 409 this.f27 = receiveFunction(Throttle.F27, this.f27, functions[3] & 0x40); 410 this.f28 = receiveFunction(Throttle.F28, this.f28, functions[3] & 0x80); 411*/ 412 } 413 /* 414 protected boolean receiveFunction(String property, boolean curStat, int newStat) { 415 boolean old = curStat; 416 curStat = (newStat != 0); 417 log.trace(" set fn: property: {}, old: {}, new: {}", property, old, curStat); 418 if (old != curStat) { 419 notifyPropertyChangeListener(property, old, curStat); 420 } 421 return (newStat != 0); 422 } 423 */ 424 425 protected void receiveSpeedSetting(int speed) { 426 synchronized(this) { 427 oldSpeed = this.speedSetting; 428 float newSpeed = floatSpeed(speed, 127); 429 log.trace(" set speed: old: {}, new: {} {}", oldSpeed, newSpeed, speed); 430 super.setSpeedSetting(newSpeed); 431 } 432 } 433 434 protected void receiveIsForward(boolean forward) { 435 boolean old = isForward; 436 log.trace(" set isForward: old: {}, new: {}", old, forward); 437 if (old != forward) { 438 //isForward = forward; 439 //notifyPropertyChangeListener("IsForward", old, forward);//TODO: use firePropertyChange or super.setIsForward 440 super.setIsForward(forward); 441 } 442 } 443 444 /** 445 * Convert speed step value to floating value. 446 * This is the oppsite of AbstractThrottle.intSpeed(speed, steps) 447 * 448 * @param speed as integer from 1...steps 449 * @param steps number if speed steps 450 * @return speed as floating number from 0.0 to 1.0 451 */ 452 public float floatSpeed(int speed, int steps) { 453 // test that speed is 1 for emergency stop 454 if (speed == 1) { 455 return -1.0f; // emergency stop 456 } 457 else if (speed == 0) { 458 return 0.0f; 459 } 460 float value = (float)(speed - 1) / (float)(steps - 1); 461 log.trace("speed: {}, steps: {}, float value: {}", speed, steps, value); 462 if (value > 1.0) { 463 return 1.0f; 464 } 465 else if (value < 0.0) { 466 return 0.0f; 467 } 468 return value; 469 } 470 471 protected void driveReceive(byte[] address, DriveState driveState) { 472 if (NodeUtils.isAddressEqual(node.getAddr(), address) && locoAddress.getNumber() == driveState.getAddress()) { 473 log.info("THROTTLE csDrive was signalled, node addr: {}, loco addr: {}, state: {}", 474 address, driveState.getAddress(), driveState); 475 // set speed 476 receiveSpeedSetting(driveState.getSpeed()); 477 receiveIsForward(driveState.getDirection() == DirectionEnum.FORWARD); 478 receiveFunctions(driveState.getFunctions()); 479 } 480 } 481 482 private void createThrottleListener() { 483 messageListener = new DefaultMessageListener() { 484 @Override 485 public void csDriveAcknowledge(byte[] address, int messageNum, int dccAddress, DriveAcknowledge state, Integer acknowledgedMessageNumber) { //new 486// public void csDriveAcknowledge(byte[] address, int dccAddress, DriveAcknowledge state) { //12.5 487 //log.trace("csDriveAcknowledge: node addr: {}, Lok addr: {}, Ack: {}", address, dccAddress, state, acknowledgedMessageNumber); 488 //log.trace("csDriveAcknowledge: Ack: {}, Lok addr: {}, node: {}", state, dccAddress, node); 489 if (NodeUtils.isAddressEqual(node.getAddr(), address) && locoAddress.getNumber() == dccAddress) { 490 log.debug("THROTTLE: drive ackn was signalled, acknowledge: {}, dccAddress: {}, node: {}", state, dccAddress, node); 491 if (state == DriveAcknowledge.NOT_ACKNOWLEDGED) { 492 log.warn("setDrive was not acknowledged on node: {}, Lok addr: {}", address, dccAddress); 493 } 494 } 495 } 496 @Override 497// public void csDriveState(byte[] address, DriveState driveState) { 498 public void csDriveState(byte[] address, int messageNum, int opCode, DriveState driveState) { 499 log.trace("csDriveState: node addr: {}, opCode: {}, DriveState: {}", address, opCode, driveState); 500 //log.trace(" node addr: {}, locoAddress: {}", node.getAddr(), locoAddress.getNumber()); 501 if (NodeUtils.isAddressEqual(node.getAddr(), address) && locoAddress.getNumber() == driveState.getAddress()) { 502 //log.debug("THROTTLE: Drive State was signalled, DriveState: {}, node: {}", driveState, node); 503 driveReceive(address, driveState); 504 } 505 } 506 @Override 507 public void csDriveManual(byte[] address, int messageNum, DriveState driveState) { 508 //log.trace("csDriveManual: node addr: {}, DriveState: {}", address, driveState); 509 if (NodeUtils.isAddressEqual(node.getAddr(), address) && locoAddress.getNumber() == driveState.getAddress()) { 510 log.debug("THROTTLE: Drive Manual was signalled, DriveState: {}, node: {}", driveState, node); 511 driveReceive(address, driveState); 512 } 513 } 514 }; 515 tc.addMessageListener(messageListener); 516 } 517 518 /** 519 * {@inheritDoc} 520 */ 521 @Override 522 protected void throttleDispose() { 523 log.trace("dispose throttle addr {}", locoAddress); 524 synchronized(this) { 525 if (this.speedSetting < 0) { 526 sendDeregister = true; 527 this.speedSetting = 0; 528 sendDriveCommand(false); //will send a DCC deregister message 529 } 530 } 531 //tc.removeMessageListener(messageListener); //TEMP 532 active = false; 533 finishRecord(); 534 } 535 536 // initialize logging 537 private final static Logger log = LoggerFactory.getLogger(BiDiBThrottle.class); 538 539}