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