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}