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}