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     * Release a session freeing up the slot for reuse.
071     *
072     * @param handle the handle for the session to be released
073     */
074    protected void releaseSession(int handle) {
075        // Send KLOC
076        CanMessage msg = new CanMessage(2, tc.getCanid());
077        msg.setOpCode(CbusConstants.CBUS_KLOC);
078        msg.setElement(1, handle);
079        log.debug("Release session handle {}", handle);
080        tc.sendCanMessage(msg, null);
081    }
082
083    /**
084     * Send keep alive (DKEEP) packet for a throttle.
085     * @param handle    The handle of the session to which it applies
086     */
087    protected void sendKeepAlive(int handle) {
088        CanMessage msg = new CanMessage(2, tc.getCanid());
089        msg.setOpCode(CbusConstants.CBUS_DKEEP);
090        msg.setElement(1, handle);
091        log.debug("keep alive handle: {}", handle);
092        tc.sendCanMessage(msg, null);
093    }
094
095    /**
096     * Set loco speed and direction.
097     *
098     * @param handle    The handle of the session to which it applies
099     * @param speed_dir Bit 7 is direction (1 = forward) 6:0 are speed
100     */
101    protected void setSpeedDir(int handle, int speed_dir) {
102        CanMessage msg = new CanMessage(3, tc.getCanid());
103        msg.setOpCode(CbusConstants.CBUS_DSPD);
104        msg.setElement(1, handle);
105        msg.setElement(2, speed_dir);
106        log.debug("setSpeedDir session handle {} speedDir {}", handle, speed_dir);
107        tc.sendCanMessage(msg, null);
108    }
109
110    /**
111     * Send a CBUS message to set functions.
112     *
113     * @param group     The function group
114     * @param handle    The handle of the session for the loco being controlled
115     * @param functions Function bits
116     */
117    protected void setFunctions(int group, int handle, int functions) {
118        log.debug("Set function group {} Fns {} for session handle {}", group, functions, handle);
119        CanMessage msg = new CanMessage(4, tc.getCanid());
120        msg.setOpCode(CbusConstants.CBUS_DFUN);
121        msg.setElement(1, handle);
122        msg.setElement(2, group);
123        msg.setElement(3, functions);
124        tc.sendCanMessage(msg, null);
125    }
126
127    /**
128     * Send a CBUS message to change the session speed step mode.
129     *
130     * @param handle    The handle of the session to which it applies
131     * @param mode the speed step mode
132     */
133    protected void setSpeedSteps(int handle, int mode) {
134        log.debug("Set speed step mode {} for session handle {}", mode, handle);
135        CanMessage msg = new CanMessage(3, tc.getCanid());
136        msg.setOpCode(CbusConstants.CBUS_STMOD);
137        msg.setElement(1, handle);
138        msg.setElement(2, mode);
139        tc.sendCanMessage(msg, null);
140    }
141
142    /**
143     * Get the master command station from the CBUS Node Table
144     * <p>
145     * Full CBUS spec is defined as to comply with CBUS Developers Guide Version 6b
146     * <p>
147     * eg. CANCMD FW v3 supports the main loco OPCs but not full spec, will return null.
148     * eg. CANCMD FW v4 supports the full steal / share spec, will return the CbusNode.
149     *
150     * @return the Master Command Station, else null if not found
151     */
152    @CheckForNull
153    protected CbusNode getMasterCommandStation(){
154        // if NodeTable already has the node stored, use it
155        CbusNodeTableDataModel nodeModel =  adapterMemo.get(CbusNodeTableDataModel.class);
156        if (nodeModel!=null && nodeModel.getCsByNum(0)!=null) {
157            return nodeModel.getCsByNum(0);
158        }
159        return null;
160    }
161    
162    // we can't rely on the command station flags at present so instead we check the
163    // node variables of master command station 0
164    
165    /**
166     * Get if Steal is available on the Command Station
167     * <p>
168     * Steal availability can change, so CbusThrottleManager checks this value
169     * when it struggles on initial attempt to obtain a throttle
170     * @return false if not available, defaults to true
171     */
172    protected boolean isStealAvailable() {
173        CbusNode mCs = getMasterCommandStation();
174        if ( mCs != null ) {
175            log.debug("found master command station");
176            if ( mCs.getNodeNvManager().getNV(2) > -1 ){
177                log.debug("nv 2 has a value");
178                return (( mCs.getNodeNvManager().getNV(2) >> 1 ) & 1) != 0; // NV2 bit 1 set
179            }
180        }
181        return true;
182    }
183    
184    /**
185     * Get if Share is available on the Command Station.
186     * <p>
187     * Share availability can change, so CbusThrottleManager checks this value
188     * when it struggles on initial attempt to obtain a throttle.
189     * @return false if not available, defaults to true
190    */
191    protected boolean isShareAvailable() {
192        CbusNode mCs = getMasterCommandStation();
193        if ( mCs != null ) {
194            if ( mCs.getNodeNvManager().getNV(2) > -1 ){ // NV2 is set
195                return (( mCs.getNodeNvManager().getNV(2) >> 2 ) & 1) != 0; // NV2 bit 2 set
196            }
197        } 
198        return true;
199    }
200
201    /**
202     * {@inheritDoc}
203     */
204    @Override
205    public String getUserName() {
206        return adapterMemo.getUserName();
207    }
208
209    /**
210     * {@inheritDoc}
211     */
212    @Override
213    public String getSystemPrefix() {
214        return adapterMemo.getSystemPrefix();
215    }
216
217    private final static Logger log = LoggerFactory.getLogger(CbusCommandStation.class);
218
219}