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}