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