001package jmri.jmrix.can.adapters.gridconnect; 002 003import java.io.DataInputStream; 004import jmri.jmrix.AbstractMRListener; 005import jmri.jmrix.AbstractMRMessage; 006import jmri.jmrix.AbstractMRReply; 007import jmri.jmrix.can.CanListener; 008import jmri.jmrix.can.CanMessage; 009import jmri.jmrix.can.CanReply; 010import jmri.jmrix.can.TrafficController; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Traffic controller for the GridConnect protocol. 016 * <p> 017 * GridConnect uses messages transmitted as an ASCII string of up to 24 018 * characters of the form: :ShhhhNd0d1d2d3d4d5d6d7; 019 * <p> 020 * The S indicates a standard 021 * CAN frame hhhh is the two byte header (11 useful bits) N or R indicates a 022 * normal or remote frame d0 - d7 are the (up to) 8 data bytes 023 * 024 * @author Andrew Crosland Copyright (C) 2008 025 */ 026public class GcTrafficController extends TrafficController { 027 028 /** 029 * Create new GridConnect Traffic Controller. 030 */ 031 public GcTrafficController() { 032 super(); 033 this.setSynchronizeRx(false); 034 } 035 036 /** 037 * Forward a CanMessage to all registered CanInterface listeners. 038 * {@inheritDoc} 039 */ 040 @Override 041 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 042 ((CanListener) client).message((CanMessage) m); 043 } 044 045 /** 046 * Forward a CanReply to all registered CanInterface listeners. 047 * {@inheritDoc} 048 */ 049 @Override 050 protected void forwardReply(AbstractMRListener client, AbstractMRReply r) { 051 if (r != null) { 052 ((CanListener) client).reply((CanReply) r); 053 } else { 054 // null shouldn't happen, but we've seen it; try to track if down 055 log.error("Found unexpected null message", new Exception("traceback") ); 056 } 057 } 058 059 // Current state 060 public static final int NORMAL = 0; 061 public static final int BOOTMODE = 1; 062 063 /** 064 * Get the GridConnect State. 065 * @return NORMAL or BOOTMODE 066 */ 067 public int getgcState() { 068 return gcState; 069 } 070 071 /** 072 * Set the GridConnect State. 073 * @param state NORMAL or BOOTMODE 074 */ 075 public void setgcState(int state) { 076 gcState = state; 077 log.debug("Setting gcState {}", state); 078 } 079 080 /** 081 * Get if GcTC is in Boot Mode. 082 * @return true if in Boot Mode, else False. 083 */ 084 public boolean isBootMode() { 085 return gcState == BOOTMODE; 086 } 087 088 /** 089 * Forward a preformatted message to the actual interface. 090 * {@inheritDoc} 091 */ 092 @Override 093 public void sendCanMessage(CanMessage m, CanListener reply) { 094 log.debug("GcTrafficController sendCanMessage() {}", m.toString()); 095 sendMessage(m, reply); 096 } 097 098 /** 099 * Forward a preformatted reply to the actual interface. 100 * {@inheritDoc} 101 */ 102 @Override 103 public void sendCanReply(CanReply r, CanListener reply) { 104 log.debug("TrafficController sendCanReply() {}", r.toString()); 105 notifyReply(r, reply); 106 } 107 108 /** 109 * Does nothing. 110 * {@inheritDoc} 111 */ 112 @Override 113 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 114 } 115 116 /** 117 * Determine how much many bytes the entire message will take, 118 * including space for header and trailer. 119 * 120 * @param m The message to be sent 121 * @return Number of bytes 122 */ 123 @Override 124 protected int lengthOfByteStream(AbstractMRMessage m) { 125 return m.getNumDataElements(); 126 } 127 128 /** 129 * Get new message for hardware protocol. 130 * @return New GridConnect Message. 131 */ 132 @Override 133 protected AbstractMRMessage newMessage() { 134 log.debug("New GridConnectMessage created"); 135 return new GridConnectMessage(); 136 } 137 138 /** 139 * Make a CanReply from a GridConnect reply. 140 * {@inheritDoc} 141 */ 142 @Override 143 public CanReply decodeFromHardware(AbstractMRReply m) { 144 GridConnectReply gc = new GridConnectReply(); 145 log.debug("Decoding from hardware"); 146 try { 147 gc = (GridConnectReply) m; 148 } catch(java.lang.ClassCastException cce){ 149 log.error("{} cannot cast to a GridConnectReply",m); 150 } 151 CanReply ret = gc.createReply(); 152 return ret; 153 } 154 155 /** 156 * Encode a CanMessage for the hardware. 157 * {@inheritDoc} 158 */ 159 @Override 160 public AbstractMRMessage encodeForHardware(CanMessage m) { 161 //log.debug("Encoding for hardware"); 162 return new GridConnectMessage(m); 163 } 164 165 /** 166 * New reply from hardware. 167 * {@inheritDoc} 168 */ 169 @Override 170 protected AbstractMRReply newReply() { 171 log.debug("New GridConnectReply created"); 172 return new GridConnectReply(); 173 } 174 175 /* 176 * Normal CAN-RS replies will end with ";" 177 * Bootloader will end with ETX with no preceding DLE 178 */ 179 @Override 180 protected boolean endOfMessage(AbstractMRReply r) { 181 if (endNormalReply(r)) { 182 return true; 183 } 184// if (endBootReply(r)) return true; 185 return false; 186 } 187 188 /** 189 * Detect if the reply buffer ends with ";". 190 * @param r Reply 191 * @return true if contains end, else false. 192 */ 193 boolean endNormalReply(AbstractMRReply r) { 194 int num = r.getNumDataElements() - 1; 195 //log.debug("endNormalReply checking "+(num+1)+" of "+(r.getNumDataElements())); 196 if (r.getElement(num) == ';') { 197 log.debug("End of normal message detected"); 198 return true; 199 } 200 return false; 201 } 202 203 /** 204 * Get characters from the input source, and file a message. 205 * <p> 206 * Returns only when the message is complete. 207 * <p> 208 * This is over-ridden from AbstractMRTrafficController so we can add 209 * suppression of the characters before ':'. We can't use 210 * waitForStartOfReply() because that strips the 1st character also. 211 * <p> 212 * Handles timeouts on read by ignoring zero-length reads. 213 * 214 * @param msg message to fill 215 * @param istream character source. 216 * @throws java.io.IOException when presented by the input source. 217 */ 218 @Override 219 protected void loadChars(AbstractMRReply msg, DataInputStream istream) 220 throws java.io.IOException { 221 int i; 222 for (i = 0; i < msg.maxSize(); i++) { 223 byte char1 = readByteProtected(istream); 224 if (i == 0) { 225 // skip until you find ':' 226 while (char1 != ':') { 227 char1 = readByteProtected(istream); 228 } 229 } 230 //if (log.isDebugEnabled()) log.debug("char: "+(char1&0xFF)+" i: "+i); 231 // if there was a timeout, flush any char received and start over 232 if (flushReceiveChars) { 233 log.warn("timeout flushes receive buffer: {}", msg.toString()); 234 msg.flush(); 235 i = 0; // restart 236 flushReceiveChars = false; 237 } 238 if (canReceive()) { 239 msg.setElement(i, char1); 240 if (endOfMessage(msg)) { 241 break; 242 } 243 } else { 244 i--; // flush char 245 log.error("unsolicited character received: {}", Integer.toHexString(char1)); 246 } 247 } 248 } 249 250 private int gcState; 251 252 private final static Logger log = LoggerFactory.getLogger(GcTrafficController.class); 253}