001package jmri.jmrix.can.cbus;
002
003import javax.annotation.CheckForNull;
004import jmri.CommandStation;
005import jmri.jmrix.can.CanMessage;
006import jmri.jmrix.can.CanSystemConnectionMemo;
007import jmri.jmrix.can.TrafficController;
008import jmri.jmrix.can.cbus.node.CbusNode;
009import jmri.jmrix.can.cbus.node.CbusNodeTableDataModel;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * CommandStation for CBUS communications.
016 *
017 * Unlike some other systems, we will hold minimal command station state 
018 * in the software model. The actual command station state
019 * should always be referred to.
020 *
021 * @author Andrew Crosland Copyright (C) 2009
022 * @author Steve Young Copyright (C) 2019
023 */
024public class CbusCommandStation implements CommandStation {
025
026    public CbusCommandStation(CanSystemConnectionMemo memo) {
027        tc = memo.getTrafficController();
028        adapterMemo = memo;
029        
030    }
031    
032    private final TrafficController tc;
033    private final CanSystemConnectionMemo adapterMemo;
034
035    /**
036     * Send a specific packet to the rails.
037     *
038     * @param packet  Byte array representing the packet, including the
039     *                error-correction byte. Must not be null.
040     * @param repeats Number of times to repeat the transmission, but is ignored
041     *                in the current implementation
042     */
043    @Override
044    public boolean sendPacket(byte[] packet, int repeats) {
045
046        if (repeats < 1) {
047            repeats = 1;
048            log.warn("Ops Mode Accessory Packet 'Send count' of < 1 is illegal and is forced to 1.");
049        }
050        if (repeats > 8) {
051            repeats = 8;
052            log.warn("Ops Mode Accessory Packet 'Send count' reduced to 8.");
053        }        
054
055        CanMessage m = new CanMessage(2 + packet.length, tc.getCanid());     // Account for opcode and repeat
056
057        m.setElement(0, CbusConstants.CBUS_RDCC3 + (((packet.length - 3) & 0x3) << 5));
058        m.setElement(1, repeats);   // repeat
059
060        // add each byte of the input message
061        for (int j = 0; j < packet.length; j++) {
062            m.setElement(j + 2, packet[j] & 0xFF);
063        }
064
065        tc.sendCanMessage(m, null);
066        return true;
067    }
068
069    /**
070     * Check that a session handle is valid before it is sent to the layout.
071     * <p>
072     * A CbusThrottle has a handle of -1 when disposed, or when its session has
073     * been stolen and is awaiting recovery. A negative handle must never be
074     * put on the CAN bus.
075     *
076     * @param handle the session handle to check
077     * @param opName the operation, for logging
078     * @return true if the handle is valid, else false
079     */
080    private boolean isValidHandle(int handle, String opName) {
081        if (handle < 0) {
082            log.warn("Invalid session handle {} for {}, not sent to layout", handle, opName);
083            return false;
084        }
085        return true;
086    }
087
088    /**
089     * Release a session freeing up the slot for reuse.
090     *
091     * @param handle the handle for the session to be released
092     */
093    protected void releaseSession(int handle) {
094        if (!isValidHandle(handle, "KLOC")) {
095            return;
096        }
097        // Send KLOC
098        CanMessage msg = new CanMessage(2, tc.getCanid());
099        msg.setOpCode(CbusConstants.CBUS_KLOC);
100        msg.setElement(1, handle);
101        log.debug("Release session handle {}", handle);
102        tc.sendCanMessage(msg, null);
103    }
104
105    /**
106     * Send keep alive (DKEEP) packet for a throttle.
107     * @param handle    The handle of the session to which it applies
108     */
109    protected void sendKeepAlive(int handle) {
110        if (!isValidHandle(handle, "DKEEP")) {
111            return;
112        }
113        CanMessage msg = new CanMessage(2, tc.getCanid());
114        msg.setOpCode(CbusConstants.CBUS_DKEEP);
115        msg.setElement(1, handle);
116        log.debug("keep alive handle: {}", handle);
117        tc.sendCanMessage(msg, null);
118    }
119
120    /**
121     * Set loco speed and direction.
122     *
123     * @param handle    The handle of the session to which it applies
124     * @param speed_dir Bit 7 is direction (1 = forward) 6:0 are speed
125     */
126    protected void setSpeedDir(int handle, int speed_dir) {
127        if (!isValidHandle(handle, "DSPD")) {
128            return;
129        }
130        CanMessage msg = new CanMessage(3, tc.getCanid());
131        msg.setOpCode(CbusConstants.CBUS_DSPD);
132        msg.setElement(1, handle);
133        msg.setElement(2, speed_dir);
134        log.debug("setSpeedDir session handle {} speedDir {}", handle, speed_dir);
135        tc.sendCanMessage(msg, null);
136    }
137
138    /**
139     * Send a CBUS message to set functions.
140     *
141     * @param group     The function group
142     * @param handle    The handle of the session for the loco being controlled
143     * @param functions Function bits
144     */
145    protected void setFunctions(int group, int handle, int functions) {
146        if (!isValidHandle(handle, "DFUN")) {
147            return;
148        }
149        log.debug("Set function group {} Fns {} for session handle {}", group, functions, handle);
150        CanMessage msg = new CanMessage(4, tc.getCanid());
151        msg.setOpCode(CbusConstants.CBUS_DFUN);
152        msg.setElement(1, handle);
153        msg.setElement(2, group);
154        msg.setElement(3, functions);
155        tc.sendCanMessage(msg, null);
156    }
157
158    /**
159     * Send a CBUS message to change the session speed step mode.
160     *
161     * @param handle    The handle of the session to which it applies
162     * @param mode the speed step mode
163     */
164    protected void setSpeedSteps(int handle, int mode) {
165        if (!isValidHandle(handle, "STMOD")) {
166            return;
167        }
168        log.debug("Set speed step mode {} for session handle {}", mode, handle);
169        CanMessage msg = new CanMessage(3, tc.getCanid());
170        msg.setOpCode(CbusConstants.CBUS_STMOD);
171        msg.setElement(1, handle);
172        msg.setElement(2, mode);
173        tc.sendCanMessage(msg, null);
174    }
175
176    /**
177     * Get the master command station from the CBUS Node Table
178     * <p>
179     * Full CBUS spec is defined as to comply with CBUS Developers Guide Version 6b
180     * <p>
181     * eg. CANCMD FW v3 supports the main loco OPCs but not full spec, will return null.
182     * eg. CANCMD FW v4 supports the full steal / share spec, will return the CbusNode.
183     *
184     * @return the Master Command Station, else null if not found
185     */
186    @CheckForNull
187    protected CbusNode getMasterCommandStation(){
188        // if NodeTable already has the node stored, use it
189        CbusNodeTableDataModel nodeModel =  adapterMemo.get(CbusNodeTableDataModel.class);
190        if (nodeModel!=null && nodeModel.getCsByNum(0)!=null) {
191            return nodeModel.getCsByNum(0);
192        }
193        return null;
194    }
195    
196    // we can't rely on the command station flags at present so instead we check the
197    // node variables of master command station 0
198    
199    /**
200     * Get if Steal is available on the Command Station
201     * <p>
202     * Steal availability can change, so CbusThrottleManager checks this value
203     * when it struggles on initial attempt to obtain a throttle
204     * @return false if not available, defaults to true
205     */
206    protected boolean isStealAvailable() {
207        CbusNode mCs = getMasterCommandStation();
208        if ( mCs != null ) {
209            log.debug("found master command station");
210            if ( mCs.getNodeNvManager().getNV(2) > -1 ){
211                log.debug("nv 2 has a value");
212                return (( mCs.getNodeNvManager().getNV(2) >> 1 ) & 1) != 0; // NV2 bit 1 set
213            }
214        }
215        return true;
216    }
217    
218    /**
219     * Get if Share is available on the Command Station.
220     * <p>
221     * Share availability can change, so CbusThrottleManager checks this value
222     * when it struggles on initial attempt to obtain a throttle.
223     * @return false if not available, defaults to true
224    */
225    protected boolean isShareAvailable() {
226        CbusNode mCs = getMasterCommandStation();
227        if ( mCs != null ) {
228            if ( mCs.getNodeNvManager().getNV(2) > -1 ){ // NV2 is set
229                return (( mCs.getNodeNvManager().getNV(2) >> 2 ) & 1) != 0; // NV2 bit 2 set
230            }
231        } 
232        return true;
233    }
234
235    /**
236     * {@inheritDoc}
237     */
238    @Override
239    public String getUserName() {
240        return adapterMemo.getUserName();
241    }
242
243    /**
244     * {@inheritDoc}
245     */
246    @Override
247    public String getSystemPrefix() {
248        return adapterMemo.getSystemPrefix();
249    }
250
251    private static final Logger log = LoggerFactory.getLogger(CbusCommandStation.class);
252
253}