001package jmri.jmrix.can.cbus.node; 002 003import java.util.TimerTask; 004import jmri.util.TimerUtil; 005 006/** 007 * Class to handle Timers for a CbusNode. 008 * 009 * @author Steve Young Copyright (C) 2019,2020 010 */ 011public class CbusNodeTimerManager { 012 private final CbusBasicNodeWithManagers _node; 013 014 protected int fetchNvTimeoutCount; 015 private TimerTask nextNvTimerTask; 016 protected int fetchEvVarTimeoutCount; 017 private TimerTask nextEvTimerTask; 018 protected int numEvTimeoutCount; 019 private TimerTask numEvTimerTask; 020 protected int allEvTimeoutCount; 021 protected TimerTask allEvTimerTask; 022 protected int paramRequestTimeoutCount; 023 private TimerTask allParamTask; 024 private TimerTask sendEditNvTask; 025 private TimerTask sendEditEvTask; 026 protected TimerTask sendEnumTask; 027 protected int sendEvErrorCount; 028 protected int _sendNVErrorCount; 029 030 public static int SINGLE_MESSAGE_TIMEOUT_TIME = 1500; 031 032 /** 033 * Create a new CbusNodeTimers 034 * 035 * @param node The Node 036 */ 037 public CbusNodeTimerManager ( CbusBasicNodeWithManagers node ){ 038 _node = node; 039 resetTimeOutCounts(); 040 } 041 042 /** 043 * See if any timers are running, ie waiting for a response from a physical Node. 044 * 045 * @return true if timers are running else false 046 */ 047 protected boolean hasActiveTimers(){ 048 049 return allParamTask != null 050 || allEvTimerTask != null 051 || nextEvTimerTask != null 052 || nextNvTimerTask != null 053 || sendEnumTask != null 054 || sendEditEvTask != null 055 || sendEditNvTask != null 056 || numEvTimerTask != null; 057 } 058 059 // stop any timers running 060 protected void cancelTimers(){ 061 clearSendEnumTimeout(); 062 clearsendEditEvTimeout(); 063 clearsendEditNvTimeout(); 064 clearAllParamTimeout(); 065 clearAllEvTimeout(); 066 clearNextEvVarTimeout(); 067 clearNextNvVarTimeout(); 068 clearNumEvTimeout(); 069 } 070 071 protected final void resetTimeOutCounts(){ 072 fetchNvTimeoutCount = 0; 073 fetchEvVarTimeoutCount = 0; 074 numEvTimeoutCount = 0; 075 allEvTimeoutCount = 0; 076 paramRequestTimeoutCount = 0; 077 sendEvErrorCount = 0; 078 } 079 080 /** 081 * Stop timer for a single NV fetch request. 082 */ 083 protected void clearNextNvVarTimeout(){ 084 if (nextNvTimerTask != null ) { 085 nextNvTimerTask.cancel(); 086 nextNvTimerTask = null; 087 fetchNvTimeoutCount = 0; 088 } 089 } 090 091 /** 092 * Start timer for a single Node Variable request. 093 * 094 * If 10 failed requests aborts loop and sets number of NV's to unknown 095 */ 096 protected void setNextNvVarTimeout() { 097 nextNvTimerTask = new TimerTask() { 098 @Override 099 public void run() { 100 nextNvTimerTask = null; 101 fetchNvTimeoutCount++; 102 if ( fetchNvTimeoutCount == 1 ) { 103 log.info("NV Fetch from node {} timed out",_node.getNodeNumber() ); // 104 } 105 else if ( fetchNvTimeoutCount == 10 ) { 106 log.error("Aborting NV Fetch from node {}",_node.getNodeNumber() ); // 107 _node.getNodeNvManager().reset(); 108 // reset number of NV's to unknown and force refresh 109 _node.getNodeParamManager().setParameter(CbusNodeConstants.NUM_NV_IDX,-1); 110 fetchNvTimeoutCount = 0; 111 } 112 113 _node.getTableModel().triggerUrgentFetch(); 114 115 } 116 }; 117 TimerUtil.schedule(nextNvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME); 118 } 119 120 /** 121 * Stop timer for a single event variable request. 122 */ 123 protected void clearNextEvVarTimeout(){ 124 if (nextEvTimerTask != null ) { 125 nextEvTimerTask.cancel(); 126 nextEvTimerTask = null; 127 fetchEvVarTimeoutCount = 0; 128 } 129 } 130 131 /** 132 * Start timer for a single event variable request. 133 * 134 * If 10 failed requests aborts loop and sets events to unknown. 135 * The timeout count is left at 10 so the fetch is not automatically 136 * re-attempted by the background fetch, see {@link #eventFetchAbandoned()} 137 * @param eventVarIndex Event Variable Index 138 * @param eventString User Friendly Event Text 139 */ 140 protected void setNextEvVarTimeout(int eventVarIndex, String eventString) { 141 nextEvTimerTask = new TimerTask() { 142 @Override 143 public void run() { 144 nextEvTimerTask = null; 145 fetchEvVarTimeoutCount++; 146 if ( fetchEvVarTimeoutCount == 1 ) { 147 log.info("Event Var fetch Timeout from Node {} event {}index {}", 148 _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex); 149 } 150 if ( fetchEvVarTimeoutCount >= MAX_TIMEOUTS ) { 151 log.error("Aborting Event Variable fetch from Node {} Event {}Index {}", 152 _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex); 153 _node.getNodeEventManager().resetNodeEvents(); 154 } 155 156 _node.getTableModel().triggerUrgentFetch(); 157 } 158 }; 159 TimerUtil.schedule(nextEvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME); 160 } 161 162 /** 163 * Check if a fetch of the node events has been abandoned following repeated timeouts, 164 * ie. from an RQEVN ( total events ), NERD ( event index ) or REVAL ( event variable ) request. 165 * <p> 166 * Counts are reset when a response is received, or by {@link #resetTimeOutCounts()}. 167 * 168 * @return true if an event fetch has been abandoned, else false. 169 */ 170 protected boolean eventFetchAbandoned() { 171 return numEvTimeoutCount >= MAX_TIMEOUTS 172 || fetchEvVarTimeoutCount >= MAX_TIMEOUTS 173 || allEvTimeoutCount >= MAX_TIMEOUTS; 174 } 175 176 private static final int MAX_TIMEOUTS = 10; 177 178 /** 179 * Stop timer for event total RQEVN request. 180 */ 181 protected void clearNumEvTimeout(){ 182 if (numEvTimerTask != null ) { 183 numEvTimerTask.cancel(); 184 numEvTimerTask = null; 185 } 186 numEvTimeoutCount = 0; 187 } 188 189 /** 190 * Start timer for event total RQEVN request. 191 * 192 * If 10 failed requests aborts loop and leaves event number unknown. 193 * The timeout count is left at 10 so the request is not automatically 194 * re-attempted by the background fetch, see {@link #eventFetchAbandoned()} 195 */ 196 protected void setNumEvTimeout() { 197 numEvTimerTask = new TimerTask() { 198 @Override 199 public void run() { 200 numEvTimerTask = null; 201 if ( _node.getNodeEventManager().getTotalNodeEvents() < 0 ) { 202 203 numEvTimeoutCount++; 204 // the process will be re-attempted by the background fetch routine, 205 // we don't start it here to give a little bit more time for network / node to recover. 206 if ( numEvTimeoutCount == 1 ) { 207 log.info("No reponse to RQEVN ( Get Total Events ) from node {}", _node ); 208 } 209 if ( numEvTimeoutCount >= MAX_TIMEOUTS ) { 210 log.info("Aborting requests for Total Events from node {}", _node ); 211 _node.getNodeEventManager().resetNodeEvents(); 212 } 213 } 214 } 215 }; 216 TimerUtil.schedule(numEvTimerTask, ( 5000 ) ); 217 } 218 219 /** 220 * Stop timer for an ALL event by index fetch request. 221 */ 222 protected void clearAllEvTimeout(){ 223 if (allEvTimerTask != null ) { 224 allEvTimerTask.cancel(); 225 allEvTimerTask = null; 226 } 227 } 228 229 /** 230 * Starts timer for an ALL event by index fetch request. 231 * <p> 232 * This has a higher chance of failing as 233 * we could be expecting up to 255 CAN Frames in response. 234 * 235 * If fails, re-sends the NERD to the physical node 236 * Aborts on 10 failed requests 237 */ 238 protected void setAllEvTimeout() { 239 allEvTimerTask = new TimerTask() { 240 @Override 241 public void run() { 242 clearAllEvTimeout(); 243 if ( _node.getNodeEventManager().getOutstandingIndexNodeEvents() > 0 ) { 244 allEvTimeoutCount++; 245 246 if ( allEvTimeoutCount < MAX_TIMEOUTS ) { 247 log.warn("Re-attempting event index fetch from node {}", _node ); 248 log.warn("NUMEV reports {} events, {} outstanding via ENRSP.", 249 _node.getNodeEventManager().getTotalNodeEvents(), 250 _node.getNodeEventManager().getOutstandingIndexNodeEvents()); 251 setAllEvTimeout(); 252 _node.send.nERD( _node.getNodeNumber() ); 253 } 254 else { 255 log.warn("Aborting whole event / node / index fetch from node {}", _node ); 256 _node.getNodeEventManager().resetNodeEvents(); 257 } 258 } 259 } 260 }; 261 TimerUtil.schedule(allEvTimerTask, ( 5000 ) ); 262 } 263 264 /** 265 * Stop timer for a single parameter fetch 266 */ 267 protected void clearAllParamTimeout(){ 268 if (allParamTask != null ) { 269 allParamTask.cancel(); 270 allParamTask = null; 271 } 272 } 273 274 /** 275 * Start timer for a Parameter request 276 * If 10 timeouts are counted, aborts loop, sets 8 parameters to 0 277 * and node events array to 0 278 * @param index Parameter Index 279 */ 280 protected void setAllParamTimeout( int index) { 281 clearAllParamTimeout(); // resets if timer already running 282 allParamTask = new TimerTask() { 283 @Override 284 public void run() { 285 allParamTask = null; 286 if ( paramRequestTimeoutCount == 0 ) { 287 log.warn("No response to parameter {} request from node {}", index ,_node ); 288 } 289 paramRequestTimeoutCount++; 290 if ( paramRequestTimeoutCount == 10 ) { 291 log.warn("Aborting requests to parameter {} for node {}",index,_node ); 292 if (_node instanceof CbusNode) { 293 ((CbusNode) _node).nodeOnNetwork(false); 294 } 295 } 296 } 297 }; 298 TimerUtil.schedule(allParamTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) ); 299 } 300 301 /** 302 * Stop timer for Teaching NV Node Variables 303 */ 304 protected void clearsendEditNvTimeout(){ 305 if (sendEditNvTask != null ) { 306 sendEditNvTask.cancel(); 307 sendEditNvTask = null; 308 } 309 } 310 311 /** 312 * Start timer for Teaching NV Node Variables 313 * If no response received, increases error count and resumes loop to teach next NV 314 * which handles the error 315 */ 316 protected void setsendEditNvTimeout() { 317 if (!(_node instanceof CbusNode )){ 318 return; 319 } 320 321 sendEditNvTask = new TimerTask() { 322 @Override 323 public void run() { 324 sendEditNvTask = null; 325 // log.info(" getsendsWRACKonNVSET {} ",getsendsWRACKonNVSET() ); 326 if ( ((CbusNode)_node).getsendsWRACKonNVSET() ) { 327 log.warn("teach nv timeout"); 328 _sendNVErrorCount++; 329 } 330 _node.getNodeNvManager().sendNextNvToNode(); 331 } 332 }; 333 TimerUtil.schedule(sendEditNvTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) ); 334 } 335 336 /** 337 * Stops timer for Teaching Events 338 */ 339 protected void clearsendEditEvTimeout(){ 340 if (sendEditEvTask != null ) { 341 sendEditEvTask.cancel(); 342 sendEditEvTask = null; 343 } 344 } 345 346 /** 347 * Start timer for Teaching Events 348 * On timeout, ie Node does not Respond with a success message, 349 * stops Learn Loop and takes node out of Learn Mode. 350 */ 351 protected void setsendEditEvTimeout() { 352 sendEditEvTask = new TimerTask() { 353 @Override 354 public void run() { 355 log.info("Late / no response from node while teaching event"); 356 sendEditEvTask = null; 357 sendEvErrorCount++; 358 359 // stop loop and take node out of learn mode 360 _node.getNodeEventManager().nextEvInArray=999; 361 _node.getNodeEventManager().teachNewEvLoop(); 362 } 363 }; 364 TimerUtil.schedule(sendEditEvTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) ); 365 } 366 367 /** 368 * Stops timer for CAN ID Self Enumeration Timeout 369 */ 370 protected void clearSendEnumTimeout(){ 371 if (sendEnumTask != null ) { 372 sendEnumTask.cancel(); 373 sendEnumTask = null; 374 } 375 } 376 377 /** 378 * Starts timer for CAN ID Self Enumeration Timeout 379 * If no response adds warning to console log 380 */ 381 protected void setsendEnumTimeout() { 382 sendEnumTask = new TimerTask() { 383 @Override 384 public void run() { 385 log.warn("Late response from node while request CAN ID Self Enumeration"); 386 sendEnumTask = null; 387 // popup dialogue? 388 } 389 }; 390 TimerUtil.schedule(sendEnumTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) ); 391 } 392 393 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeTimerManager.class); 394 395}