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); 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); 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 log.debug("Decoding from hardware"); 145 // no throwaway GridConnectReply is allocated for every incoming frame 146 GridConnectReply gc; 147 if (m instanceof GridConnectReply) { 148 gc = (GridConnectReply) m; 149 } else { 150 log.error("{} cannot cast to a GridConnectReply",m); 151 gc = new GridConnectReply(); 152 } 153 return gc.createReply(); 154 } 155 156 /** 157 * Encode a CanMessage for the hardware. 158 * {@inheritDoc} 159 */ 160 @Override 161 public AbstractMRMessage encodeForHardware(CanMessage m) { 162 //log.debug("Encoding for hardware"); 163 return new GridConnectMessage(m); 164 } 165 166 /** 167 * New reply from hardware. 168 * {@inheritDoc} 169 */ 170 @Override 171 protected AbstractMRReply newReply() { 172 log.debug("New GridConnectReply created"); 173 return new GridConnectReply(); 174 } 175 176 /* 177 * Normal CAN-RS replies will end with ";" 178 * Bootloader will end with ETX with no preceding DLE 179 */ 180 @Override 181 protected boolean endOfMessage(AbstractMRReply r) { 182 if (endNormalReply(r)) { 183 return true; 184 } 185// if (endBootReply(r)) return true; 186 return false; 187 } 188 189 /** 190 * Detect if the reply buffer ends with ";". 191 * @param r Reply 192 * @return true if contains end, else false. 193 */ 194 boolean endNormalReply(AbstractMRReply r) { 195 int num = r.getNumDataElements() - 1; 196 //log.debug("endNormalReply checking "+(num+1)+" of "+(r.getNumDataElements())); 197 if (r.getElement(num) == ';') { 198 log.debug("End of normal message detected"); 199 return true; 200 } 201 return false; 202 } 203 204 /** 205 * Get characters from the input source, and file a message. 206 * <p> 207 * Returns only when the message is complete. 208 * <p> 209 * This is over-ridden from AbstractMRTrafficController so we can add 210 * suppression of the characters before ':'. We can't use 211 * waitForStartOfReply() because that strips the 1st character also. 212 * <p> 213 * Handles timeouts on read by ignoring zero-length reads. 214 * 215 * @param msg message to fill 216 * @param istream character source. 217 * @throws java.io.IOException when presented by the input source. 218 */ 219 @Override 220 protected void loadChars(AbstractMRReply msg, DataInputStream istream) 221 throws java.io.IOException { 222 int i; 223 for (i = 0; i < msg.maxSize(); i++) { 224 byte char1 = readByteProtected(istream); 225 if (i == 0) { 226 // skip until you find ':' 227 while (char1 != ':') { 228 char1 = readByteProtected(istream); 229 } 230 } else if (char1 == ':') { 231 // Start of a new frame before the end of the current one, 232 // the end of the current frame ( ';' ) has been lost, eg. a serial overrun. 233 // Discard the partial frame and restart with this ':' so that the 234 // two frames are not merged into one corrupt frame. 235 log.warn("Incomplete frame discarded: {}", msg); 236 msg.flush(); 237 i = 0; 238 } 239 //if (log.isDebugEnabled()) log.debug("char: "+(char1&0xFF)+" i: "+i); 240 // if there was a timeout, flush any char received and start over 241 if (flushReceiveChars) { 242 log.warn("timeout flushes receive buffer: {}", msg.toString()); 243 msg.flush(); 244 i = 0; // restart 245 flushReceiveChars = false; 246 } 247 if (canReceive()) { 248 msg.setElement(i, char1); 249 if (endOfMessage(msg)) { 250 break; 251 } 252 } else { 253 i--; // flush char 254 log.error("unsolicited character received: {}", Integer.toHexString(char1)); 255 } 256 } 257 } 258 259 private int gcState; 260 261 private static final Logger log = LoggerFactory.getLogger(GcTrafficController.class); 262}