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_ADVANCED_INFO_RESPONSE 110 || m.getElement(0) 111 == XNetConstants.CS_SERVICE_MODE_RESPONSE 112 || m.getElement(0) 113 == XNetConstants.CS_REQUEST_RESPONSE 114 || m.getElement(0) 115 == XNetConstants.BC_EMERGENCY_STOP)) { 116 ((XNetListener) client).message((XNetReply) m); 117 } else if ((mask & XNetInterface.FEEDBACK) 118 == XNetInterface.FEEDBACK 119 && (((XNetReply) m).isFeedbackMessage() 120 || ((XNetReply) m).isFeedbackBroadcastMessage())) { 121 ((XNetListener) client).message((XNetReply) m); 122 } else if ((mask & XNetInterface.THROTTLE) 123 == XNetInterface.THROTTLE 124 && ((XNetReply) m).isThrottleMessage()) { 125 ((XNetListener) client).message((XNetReply) m); 126 } else if ((mask & XNetInterface.CONSIST) 127 == XNetInterface.CONSIST 128 && ((XNetReply) m).isConsistMessage()) { 129 ((XNetListener) client).message((XNetReply) m); 130 } else if ((mask & XNetInterface.INTERFACE) 131 == XNetInterface.INTERFACE 132 && (m.getElement(0) 133 == XNetConstants.LI_VERSION_RESPONSE 134 || m.getElement(0) 135 == XNetConstants.LI101_REQUEST)) { 136 ((XNetListener) client).message((XNetReply) m); 137 } 138 } 139 } 140 141 // We use the pollMessage routines for high priority messages. 142 // This means responses to time critical messages (turnout off messages). 143 // PENDING: these fields should be probably made private w/ accessor to force proper synchronization for reading. 144 final LinkedBlockingQueue<XNetMessage> highPriorityQueue; 145 final LinkedBlockingQueue<XNetListener> highPriorityListeners; 146 147 public synchronized void sendHighPriorityXNetMessage(XNetMessage m, XNetListener reply) { 148 // using offer as the queue is unbounded and should never block on write. 149 // Note: the message should be inserted LAST, as the message is tested/acquired first 150 // by the reader; serves a a guard for next item processing. 151 highPriorityListeners.add(reply); 152 highPriorityQueue.add(m); 153 } 154 155 @Override 156 protected AbstractMRMessage pollMessage() { 157 try { 158 if (highPriorityQueue.peek() == null) { 159 return null; 160 } else { 161 return highPriorityQueue.take(); 162 } 163 } catch (java.lang.InterruptedException ie) { 164 log.error("Interrupted while removing High Priority Message from Queue"); 165 } 166 return null; 167 } 168 169 @Override 170 protected AbstractMRListener pollReplyHandler() { 171 try { 172 if (highPriorityListeners.peek() == null) { 173 return null; 174 } else { 175 return highPriorityListeners.take(); 176 } 177 } catch (java.lang.InterruptedException ie) { 178 log.error("Interrupted while removing High Priority Message Listener from Queue"); 179 } 180 return null; 181 } 182 183 @Override 184 public synchronized void addXNetListener(int mask, XNetListener l) { 185 addListener(l); 186 // This is adds all the mask information. A better way to do 187 // this would be to allow updating individual bits 188 mListenerMasks.put(l, mask); 189 } 190 191 @Override 192 public synchronized void removeXNetListener(int mask, XNetListener l) { 193 removeListener(l); 194 // This is removes all the mask information. A better way to do 195 // this would be to allow updating of individual bits 196 mListenerMasks.remove(l); 197 } 198 199 /** 200 * This method has to be available, even though it doesn't do anything on 201 * Lenz. 202 */ 203 @Override 204 protected AbstractMRMessage enterProgMode() { 205 return null; 206 } 207 208 /** 209 * Return the value of getExitProgModeMsg(). 210 */ 211 @Override 212 protected AbstractMRMessage enterNormalMode() { 213 return XNetMessage.getExitProgModeMsg(); 214 } 215 216 /** 217 * Check to see if the programmer associated with this interface is idle or 218 * not. 219 */ 220 @Override 221 protected boolean programmerIdle() { 222 if (mMemo == null) { 223 return true; 224 } 225 jmri.jmrix.lenz.XNetProgrammerManager pm = mMemo.getProgrammerManager(); 226 if (pm == null) { 227 return true; 228 } 229 XNetConfigurator p = (XNetConfigurator) pm.getGlobalProgrammer().getConfigurator(); 230 if (p == null) { 231 return true; 232 } 233 return !(p.programmerBusy()); 234 } 235 236 @Override 237 protected boolean endOfMessage(AbstractMRReply msg) { 238 int len = (msg.getElement(0) & 0x0f) + 2; // opCode+Nbytes+ECC 239 log.debug("Message Length {} Current Size {}", len, msg.getNumDataElements()); 240 return msg.getNumDataElements() >= len; 241 } 242 243 @Override 244 protected AbstractMRReply newReply() { 245 return new XNetReply(); 246 } 247 248 /** 249 * Get characters from the input source, and file a message. 250 * <p> 251 * Returns only when the message is complete. 252 * <p> 253 * Only used in the Receive thread. 254 * 255 * @param msg message to fill 256 * @param istream character source. 257 * @throws java.io.IOException when presented by the input source. 258 */ 259 @Override 260 protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException { 261 int i; 262 for (i = 0; i < msg.maxSize(); i++) { 263 byte char1 = readByteProtected(istream); 264 msg.setElement(i, char1 & 0xFF); 265 if (endOfMessage(msg)) { 266 break; 267 } 268 } 269 if (mCurrentState == IDLESTATE) { 270 msg.setUnsolicited(); 271 } 272 } 273 274 @Override 275 protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) { 276 super.handleTimeout(msg, l); 277 if (l != null) { 278 ((XNetListener) l).notifyTimeout((XNetMessage) msg); 279 } 280 } 281 282 @Override 283 protected void notifyMessage(AbstractMRMessage m, AbstractMRListener notMe) { 284 super.notifyMessage(m, notMe); 285 if(notMe!=null) { 286 forwardMessage(notMe, m); 287 } 288 } 289 290 /** 291 * Reference to the command station in communication here. 292 */ 293 final LenzCommandStation mCommandStation; 294 295 /** 296 * Get access to communicating command station object. 297 * 298 * @return associated Command Station object 299 */ 300 public LenzCommandStation getCommandStation() { 301 return mCommandStation; 302 } 303 304 /** 305 * Reference to the system connection memo. 306 */ 307 XNetSystemConnectionMemo mMemo = null; 308 309 /** 310 * Get access to the system connection memo associated with this traffic 311 * controller. 312 * 313 * @return associated systemConnectionMemo object 314 */ 315 public XNetSystemConnectionMemo getSystemConnectionMemo() { 316 return (mMemo); 317 } 318 319 /** 320 * Set the system connection memo associated with this traffic controller. 321 * 322 * @param m associated systemConnectionMemo object 323 */ 324 public void setSystemConnectionMemo(XNetSystemConnectionMemo m) { 325 mMemo = m; 326 } 327 328 private XNetFeedbackMessageCache _FeedbackCache = null; 329 330 /** 331 * Return an XNetFeedbackMessageCache object associated with this traffic 332 * controller. 333 * @return the feedback message cache. One is provided if null. 334 */ 335 public XNetFeedbackMessageCache getFeedbackMessageCache() { 336 if (_FeedbackCache == null) { 337 _FeedbackCache = new XNetFeedbackMessageCache(this); 338 } 339 return _FeedbackCache; 340 } 341 342 /** 343 * @return whether or not this connection currently has a timeslot from the Command station. 344 */ 345 boolean hasTimeSlot(){ 346 return ((XNetPortController)controller).hasTimeSlot(); 347 } 348 349 private static final Logger log = LoggerFactory.getLogger(XNetTrafficController.class); 350 351}