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}