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}