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}