001package jmri.jmrix.lenz; 002 003import java.util.HashMap; 004import java.util.concurrent.LinkedBlockingQueue; 005import jmri.jmrix.AbstractMRListener; 006import jmri.jmrix.AbstractMRMessage; 007import jmri.jmrix.AbstractMRReply; 008import jmri.jmrix.AbstractMRTrafficController; 009import net.jcip.annotations.GuardedBy; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Abstract base class for implementations of XNetInterface. 015 * <p> 016 * This provides just the basic interface. 017 * @see jmri.jmrix.AbstractMRTrafficController 018 * 019 * @author Bob Jacobsen Copyright (C) 2002 020 * @author Paul Bender Copyright (C) 2004-2010 021 */ 022public abstract class XNetTrafficController extends AbstractMRTrafficController implements XNetInterface { 023 024 @GuardedBy("this") 025 // PENDING: the field should be probably made private w/ accessor to force proper synchronization for reading. 026 protected final HashMap<XNetListener, Integer> mListenerMasks; 027 028 /** 029 * Create a new XNetTrafficController. 030 * Must provide a LenzCommandStation reference at creation time. 031 * 032 * @param pCommandStation reference to associated command station object, 033 * preserved for later. 034 */ 035 XNetTrafficController(LenzCommandStation pCommandStation) { 036 mCommandStation = pCommandStation; 037 setAllowUnexpectedReply(true); 038 mListenerMasks = new HashMap<>(); 039 highPriorityQueue = new LinkedBlockingQueue<>(); 040 highPriorityListeners = new LinkedBlockingQueue<>(); 041 } 042 043 static XNetTrafficController self = null; 044 045 // Abstract methods for the XNetInterface 046 047 /** 048 * Make connection to existing PortController object. 049 */ 050 @Override 051 public void connectPort(jmri.jmrix.AbstractPortController p) { 052 super.connectPort(p); 053 if (p instanceof XNetPortController) { 054 this.addXNetListener(XNetInterface.COMMINFO, new XNetTimeSlotListener((XNetPortController) p)); 055 } 056 } 057 058 /** 059 * Forward a preformatted XNetMessage to a specific listener interface. 060 * 061 * @param m Message to send 062 */ 063 @Override 064 public void forwardMessage(AbstractMRListener reply, AbstractMRMessage m) { 065 if (!(reply instanceof XNetListener) || !(m instanceof XNetMessage)) { 066 throw new IllegalArgumentException(""); 067 } 068 ((XNetListener) reply).message((XNetMessage) m); 069 } 070 071 /** 072 * Forward a preformatted XNetMessage to the registered XNetListeners. 073 * <p> 074 * NOTE: this drops the packet if the checksum is bad. 075 * 076 * @param client is the client getting the message 077 * @param m Message to send 078 */ 079 @Override 080 public void forwardReply(AbstractMRListener client, AbstractMRReply m) { 081 if (!(client instanceof XNetListener) || !(m instanceof XNetReply)) { 082 throw new IllegalArgumentException(""); 083 } 084 // check parity 085 if (!((XNetReply) m).checkParity()) { 086 log.warn("Ignore packet with bad checksum: {}", (m)); 087 } else { 088 int mask; 089 synchronized (this) { 090 mask = mListenerMasks.getOrDefault(client, XNetInterface.ALL); 091 } 092 if (mask == XNetInterface.ALL) { 093 // Note: also executing this branch, if the client is not registered at all. 094 ((XNetListener) client).message((XNetReply) m); 095 } else if ((mask & XNetInterface.COMMINFO) 096 == XNetInterface.COMMINFO 097 && (m.getElement(0) 098 == XNetConstants.LI_MESSAGE_RESPONSE_HEADER)) { 099 ((XNetListener) client).message((XNetReply) m); 100 } else if ((mask & XNetInterface.CS_INFO) 101 == XNetInterface.CS_INFO 102 && (m.getElement(0) 103 == XNetConstants.CS_INFO 104 || m.getElement(0) 105 == XNetConstants.CS_SERVICE_MODE_RESPONSE 106 || m.getElement(0) 107 == XNetConstants.CS_REQUEST_RESPONSE 108 || m.getElement(0) 109 == XNetConstants.BC_EMERGENCY_STOP)) { 110 ((XNetListener) client).message((XNetReply) m); 111 } else if ((mask & XNetInterface.FEEDBACK) 112 == XNetInterface.FEEDBACK 113 && (((XNetReply) m).isFeedbackMessage() 114 || ((XNetReply) m).isFeedbackBroadcastMessage())) { 115 ((XNetListener) client).message((XNetReply) m); 116 } else if ((mask & XNetInterface.THROTTLE) 117 == XNetInterface.THROTTLE 118 && ((XNetReply) m).isThrottleMessage()) { 119 ((XNetListener) client).message((XNetReply) m); 120 } else if ((mask & XNetInterface.CONSIST) 121 == XNetInterface.CONSIST 122 && ((XNetReply) m).isConsistMessage()) { 123 ((XNetListener) client).message((XNetReply) m); 124 } else if ((mask & XNetInterface.INTERFACE) 125 == XNetInterface.INTERFACE 126 && (m.getElement(0) 127 == XNetConstants.LI_VERSION_RESPONSE 128 || m.getElement(0) 129 == XNetConstants.LI101_REQUEST)) { 130 ((XNetListener) client).message((XNetReply) m); 131 } 132 } 133 } 134 135 // We use the pollMessage routines for high priority messages. 136 // This means responses to time critical messages (turnout off messages). 137 // PENDING: these fields should be probably made private w/ accessor to force proper synchronization for reading. 138 final LinkedBlockingQueue<XNetMessage> highPriorityQueue; 139 final LinkedBlockingQueue<XNetListener> highPriorityListeners; 140 141 public synchronized void sendHighPriorityXNetMessage(XNetMessage m, XNetListener reply) { 142 // using offer as the queue is unbounded and should never block on write. 143 // Note: the message should be inserted LAST, as the message is tested/acquired first 144 // by the reader; serves a a guard for next item processing. 145 highPriorityListeners.add(reply); 146 highPriorityQueue.add(m); 147 } 148 149 @Override 150 protected AbstractMRMessage pollMessage() { 151 try { 152 if (highPriorityQueue.peek() == null) { 153 return null; 154 } else { 155 return highPriorityQueue.take(); 156 } 157 } catch (java.lang.InterruptedException ie) { 158 log.error("Interrupted while removing High Priority Message from Queue"); 159 } 160 return null; 161 } 162 163 @Override 164 protected AbstractMRListener pollReplyHandler() { 165 try { 166 if (highPriorityListeners.peek() == null) { 167 return null; 168 } else { 169 return highPriorityListeners.take(); 170 } 171 } catch (java.lang.InterruptedException ie) { 172 log.error("Interrupted while removing High Priority Message Listener from Queue"); 173 } 174 return null; 175 } 176 177 @Override 178 public synchronized void addXNetListener(int mask, XNetListener l) { 179 addListener(l); 180 // This is adds all the mask information. A better way to do 181 // this would be to allow updating individual bits 182 mListenerMasks.put(l, mask); 183 } 184 185 @Override 186 public synchronized void removeXNetListener(int mask, XNetListener l) { 187 removeListener(l); 188 // This is removes all the mask information. A better way to do 189 // this would be to allow updating of individual bits 190 mListenerMasks.remove(l); 191 } 192 193 /** 194 * This method has to be available, even though it doesn't do anything on 195 * Lenz. 196 */ 197 @Override 198 protected AbstractMRMessage enterProgMode() { 199 return null; 200 } 201 202 /** 203 * Return the value of getExitProgModeMsg(). 204 */ 205 @Override 206 protected AbstractMRMessage enterNormalMode() { 207 return XNetMessage.getExitProgModeMsg(); 208 } 209 210 /** 211 * Check to see if the programmer associated with this interface is idle or 212 * not. 213 */ 214 @Override 215 protected boolean programmerIdle() { 216 if (mMemo == null) { 217 return true; 218 } 219 jmri.jmrix.lenz.XNetProgrammerManager pm = mMemo.getProgrammerManager(); 220 if (pm == null) { 221 return true; 222 } 223 XNetProgrammer p = (XNetProgrammer) pm.getGlobalProgrammer(); 224 if (p == null) { 225 return true; 226 } 227 return !(p.programmerBusy()); 228 } 229 230 @Override 231 protected boolean endOfMessage(AbstractMRReply msg) { 232 int len = (msg.getElement(0) & 0x0f) + 2; // opCode+Nbytes+ECC 233 log.debug("Message Length {} Current Size {}", len, msg.getNumDataElements()); 234 return msg.getNumDataElements() >= len; 235 } 236 237 @Override 238 protected AbstractMRReply newReply() { 239 return new XNetReply(); 240 } 241 242 /** 243 * Get characters from the input source, and file a message. 244 * <p> 245 * Returns only when the message is complete. 246 * <p> 247 * Only used in the Receive thread. 248 * 249 * @param msg message to fill 250 * @param istream character source. 251 * @throws java.io.IOException when presented by the input source. 252 */ 253 @Override 254 protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException { 255 int i; 256 for (i = 0; i < msg.maxSize(); i++) { 257 byte char1 = readByteProtected(istream); 258 msg.setElement(i, char1 & 0xFF); 259 if (endOfMessage(msg)) { 260 break; 261 } 262 } 263 if (mCurrentState == IDLESTATE) { 264 msg.setUnsolicited(); 265 } 266 } 267 268 @Override 269 protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) { 270 super.handleTimeout(msg, l); 271 if (l != null) { 272 ((XNetListener) l).notifyTimeout((XNetMessage) msg); 273 } 274 } 275 276 @Override 277 protected void notifyMessage(AbstractMRMessage m, AbstractMRListener notMe) { 278 super.notifyMessage(m, notMe); 279 if(notMe!=null) { 280 forwardMessage(notMe, m); 281 } 282 } 283 284 /** 285 * Reference to the command station in communication here. 286 */ 287 final LenzCommandStation mCommandStation; 288 289 /** 290 * Get access to communicating command station object. 291 * 292 * @return associated Command Station object 293 */ 294 public LenzCommandStation getCommandStation() { 295 return mCommandStation; 296 } 297 298 /** 299 * Reference to the system connection memo. 300 */ 301 XNetSystemConnectionMemo mMemo = null; 302 303 /** 304 * Get access to the system connection memo associated with this traffic 305 * controller. 306 * 307 * @return associated systemConnectionMemo object 308 */ 309 public XNetSystemConnectionMemo getSystemConnectionMemo() { 310 return (mMemo); 311 } 312 313 /** 314 * Set the system connection memo associated with this traffic controller. 315 * 316 * @param m associated systemConnectionMemo object 317 */ 318 public void setSystemConnectionMemo(XNetSystemConnectionMemo m) { 319 mMemo = m; 320 } 321 322 private XNetFeedbackMessageCache _FeedbackCache = null; 323 324 /** 325 * Return an XNetFeedbackMessageCache object associated with this traffic 326 * controller. 327 * @return the feedback message cache. One is provided if null. 328 */ 329 public XNetFeedbackMessageCache getFeedbackMessageCache() { 330 if (_FeedbackCache == null) { 331 _FeedbackCache = new XNetFeedbackMessageCache(this); 332 } 333 return _FeedbackCache; 334 } 335 336 /** 337 * @return whether or not this connection currently has a timeslot from the Command station. 338 */ 339 boolean hasTimeSlot(){ 340 return ((XNetPortController)controller).hasTimeSlot(); 341 } 342 343 private static final Logger log = LoggerFactory.getLogger(XNetTrafficController.class); 344 345}