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