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                    _node.getNodeParamManager().setParameter(5,-1); // reset number of NV's to unknown and force refresh
109                }
110                
111                _node.getTableModel().triggerUrgentFetch();
112                
113            }
114        };
115        TimerUtil.schedule(nextNvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME);
116    }
117
118    /**
119     * Stop timer for a single event variable request.
120     */
121    protected void clearNextEvVarTimeout(){
122        if (nextEvTimerTask != null ) {
123            nextEvTimerTask.cancel();
124            nextEvTimerTask = null;
125            fetchEvVarTimeoutCount = 0;
126        }
127    }
128
129    /**
130     * Start timer for a single event variable request.
131     * 
132     * If 10 failed requests aborts loop and sets events to 0
133     * @param eventVarIndex Event Variable Index
134     * @param eventString User Friendly Event Text
135     */
136    protected void setNextEvVarTimeout(int eventVarIndex, String eventString) {
137        nextEvTimerTask = new TimerTask() {
138            @Override
139            public void run() {
140                nextEvTimerTask = null;
141                fetchEvVarTimeoutCount++;
142                if ( fetchEvVarTimeoutCount == 1 ) {
143                    log.info("Event Var fetch Timeout from Node {} event {}index {}",
144                        _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex);
145                }
146                if ( fetchEvVarTimeoutCount == 10 ) {
147                    log.error("Aborting Event Variable fetch from Node {} Event {}Index {}",
148                        _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex);
149                    _node.getNodeEventManager().resetNodeEvents();
150                    fetchEvVarTimeoutCount = 0;
151                }
152                
153                _node.getTableModel().triggerUrgentFetch();
154            }
155        };
156        TimerUtil.schedule(nextEvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME);
157    }
158
159    /**
160     * Stop timer for event total RQEVN request.
161     */
162    protected void clearNumEvTimeout(){
163        if (numEvTimerTask != null ) {
164            numEvTimerTask.cancel();
165            numEvTimerTask = null;
166        }
167        numEvTimeoutCount = 0;
168    }
169
170    /**
171     * Start timer for event total RQEVN request.
172     * 
173     * If 10 failed requests aborts loop and sets event number to 0
174     */
175    protected void setNumEvTimeout() {
176        numEvTimerTask = new TimerTask() {
177            @Override
178            public void run() {
179                numEvTimerTask = null;
180                if ( _node.getNodeEventManager().getTotalNodeEvents() < 0 ) {
181                    
182                    numEvTimeoutCount++;
183                    // the process will be re-attempted by the background fetch routine,
184                    // we don't start it here to give a little bit more time for network / node to recover.
185                    if ( numEvTimeoutCount == 1 ) {
186                        log.info("No reponse to RQEVN ( Get Total Events ) from node {}", _node );
187                    }
188                    if ( numEvTimeoutCount == 10 ) {
189                        log.info("Aborting requests for Total Events from node {}", _node );
190                        _node.getNodeEventManager().resetNodeEvents();
191                        numEvTimeoutCount = 0;
192                    }
193                }
194            }
195        };
196        TimerUtil.schedule(numEvTimerTask, ( 5000 ) );
197    }
198
199    /**
200     * Stop timer for an ALL event by index fetch request.
201     */
202    protected void clearAllEvTimeout(){
203        if (allEvTimerTask != null ) {
204            allEvTimerTask.cancel();
205            allEvTimerTask = null;
206        }
207    }
208
209    /**
210     * Starts timer for an ALL event by index fetch request.
211     * <p>
212     * This has a higher chance of failing as 
213     * we could be expecting up to 255 CAN Frames in response.
214     *
215     * If fails, re-sends the NERD to the physical node
216     * Aborts on 10 failed requests
217     */
218    protected void setAllEvTimeout() {
219        allEvTimerTask = new TimerTask() {
220            @Override
221            public void run() {
222                clearAllEvTimeout();
223                if ( _node.getNodeEventManager().getOutstandingIndexNodeEvents() > 0 ) {
224                    allEvTimeoutCount++;
225                    
226                    if ( allEvTimeoutCount < 10 ) {
227                        log.warn("Re-attempting event index fetch from node {}", _node );
228                        log.warn("NUMEV reports {} events, {} outstanding via ENRSP.",
229                            _node.getNodeEventManager().getTotalNodeEvents(),
230                            _node.getNodeEventManager().getOutstandingIndexNodeEvents());
231                        setAllEvTimeout();
232                        _node.send.nERD( _node.getNodeNumber() );
233                    }
234                    else {
235                        log.warn("Aborting whole event / node / index fetch from node {}", _node );
236                        _node.getNodeEventManager().resetNodeEvents();
237                    }
238                }
239            }
240        };
241        TimerUtil.schedule(allEvTimerTask, ( 5000 ) );
242    }
243
244    /**
245     * Stop timer for a single parameter fetch
246     */
247    protected void clearAllParamTimeout(){
248        if (allParamTask != null ) {
249            allParamTask.cancel();
250            allParamTask = null;
251        }
252    }
253
254    /**
255     * Start timer for a Parameter request
256     * If 10 timeouts are counted, aborts loop, sets 8 parameters to 0
257     * and node events array to 0
258     * @param index Parameter Index
259     */
260    protected void setAllParamTimeout( int index) {
261        clearAllParamTimeout(); // resets if timer already running
262        allParamTask = new TimerTask() {
263            @Override
264            public void run() {
265                allParamTask = null;
266                if ( paramRequestTimeoutCount == 0 ) {
267                    log.warn("No response to parameter {} request from node {}", index ,_node );
268                }
269                paramRequestTimeoutCount++;
270                if ( paramRequestTimeoutCount == 10 ) {
271                    log.warn("Aborting requests to parameter {} for node {}",index,_node );
272                    if (_node instanceof CbusNode) {
273                        ((CbusNode) _node).nodeOnNetwork(false);
274                    }
275                }
276            }
277        };
278        TimerUtil.schedule(allParamTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) );
279    }
280
281    /**
282     * Stop timer for Teaching NV Node Variables
283     */
284    protected void clearsendEditNvTimeout(){
285        if (sendEditNvTask != null ) {
286            sendEditNvTask.cancel();
287            sendEditNvTask = null;
288        }
289    }
290
291    /**
292     * Start timer for Teaching NV Node Variables
293     * If no response received, increases error count and resumes loop to teach next NV
294     * which handles the error
295     */
296    protected void setsendEditNvTimeout() {
297        if (!(_node instanceof CbusNode )){
298            return;
299        }
300
301        sendEditNvTask = new TimerTask() {
302            @Override
303            public void run() {
304                sendEditNvTask = null;
305                //  log.info(" getsendsWRACKonNVSET {} ",getsendsWRACKonNVSET()  ); 
306                if ( ((CbusNode)_node).getsendsWRACKonNVSET() ) {
307                    log.warn("teach nv timeout");
308                    _sendNVErrorCount++;
309                }
310                _node.getNodeNvManager().sendNextNvToNode();
311            }
312        };
313        TimerUtil.schedule(sendEditNvTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) );
314    }
315
316    /**
317     * Stops timer for Teaching Events
318     */
319    protected void clearsendEditEvTimeout(){
320        if (sendEditEvTask != null ) {
321            sendEditEvTask.cancel();
322            sendEditEvTask = null;
323        }
324    }
325
326    /**
327     * Start timer for Teaching Events
328     * On timeout, ie Node does not Respond with a success message,
329     * stops Learn Loop and takes node out of Learn Mode.
330     */
331    protected void setsendEditEvTimeout() {
332        sendEditEvTask = new TimerTask() {
333            @Override
334            public void run() {
335                log.info("Late / no response from node while teaching event");
336                sendEditEvTask = null;
337                sendEvErrorCount++;
338                
339                // stop loop and take node out of learn mode
340                _node.getNodeEventManager().nextEvInArray=999;
341                _node.getNodeEventManager().teachNewEvLoop();
342            }
343        };
344        TimerUtil.schedule(sendEditEvTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) );
345    }
346
347    /**
348     * Stops timer for CAN ID Self Enumeration Timeout
349     */
350    protected void clearSendEnumTimeout(){
351        if (sendEnumTask != null ) {
352            sendEnumTask.cancel();
353            sendEnumTask = null;
354        }
355    }
356
357    /**
358     * Starts timer for CAN ID Self Enumeration Timeout
359     * If no response adds warning to console log
360     */
361    protected void setsendEnumTimeout() {
362        sendEnumTask = new TimerTask() {
363            @Override
364            public void run() {
365                log.warn("Late response from node while request CAN ID Self Enumeration");
366                sendEnumTask = null;
367                // popup dialogue?
368            }
369        };
370        TimerUtil.schedule(sendEnumTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) );
371    }
372
373    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeTimerManager.class);
374
375}