001package jmri.jmrix.can.cbus.node;
002
003import java.util.TimerTask;
004import jmri.util.TimerUtil;
005
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Class to handle Timers for a CbusNode.
011 *
012 * @author Steve Young Copyright (C) 2019,2020
013 */
014public class CbusNodeTimerManager {
015    private final CbusBasicNodeWithManagers _node;
016    
017    protected int fetchNvTimeoutCount;
018    private TimerTask nextNvTimerTask;
019    protected int fetchEvVarTimeoutCount;
020    private TimerTask nextEvTimerTask;
021    protected int numEvTimeoutCount;
022    private TimerTask numEvTimerTask;
023    protected int allEvTimeoutCount;
024    protected TimerTask allEvTimerTask;
025    protected int paramRequestTimeoutCount;
026    private TimerTask allParamTask;
027    private TimerTask sendEditNvTask;
028    private TimerTask sendEditEvTask;
029    protected TimerTask sendEnumTask;    
030    protected int sendEvErrorCount;
031    protected int _sendNVErrorCount;
032    
033    public static int SINGLE_MESSAGE_TIMEOUT_TIME = 1500;
034    
035    /**
036     * Create a new CbusNodeTimers
037     *
038     * @param node The Node
039     */
040    public CbusNodeTimerManager ( CbusBasicNodeWithManagers node ){
041        _node = node;
042        resetTimeOutCounts();
043    }
044    
045    /**
046     * See if any timers are running, ie waiting for a response from a physical Node.
047     *
048     * @return true if timers are running else false
049     */
050    protected boolean hasActiveTimers(){
051        
052        return allParamTask != null
053            || allEvTimerTask != null
054            || nextEvTimerTask != null
055            || nextNvTimerTask != null
056            || sendEnumTask != null
057            || sendEditEvTask != null
058            || sendEditNvTask != null
059            || numEvTimerTask != null;
060    }
061    
062    // stop any timers running
063    protected void cancelTimers(){
064        clearSendEnumTimeout();
065        clearsendEditEvTimeout();
066        clearsendEditNvTimeout();
067        clearAllParamTimeout();
068        clearAllEvTimeout();
069        clearNextEvVarTimeout();
070        clearNextNvVarTimeout();
071        clearNumEvTimeout();
072    }
073    
074    protected final void resetTimeOutCounts(){
075        fetchNvTimeoutCount = 0;
076        fetchEvVarTimeoutCount = 0;
077        numEvTimeoutCount = 0;
078        allEvTimeoutCount = 0;
079        paramRequestTimeoutCount = 0;
080        sendEvErrorCount = 0;
081    }
082    
083    /**
084     * Stop timer for a single NV fetch request.
085     */
086    protected void clearNextNvVarTimeout(){
087        if (nextNvTimerTask != null ) {
088            nextNvTimerTask.cancel();
089            nextNvTimerTask = null;
090            fetchNvTimeoutCount = 0;
091        }
092    }
093    
094    /**
095     * Start timer for a single Node Variable request.
096     * 
097     * If 10 failed requests aborts loop and sets number of NV's to unknown
098     */
099    protected void setNextNvVarTimeout() {
100        nextNvTimerTask = new TimerTask() {
101            @Override
102            public void run() {
103                nextNvTimerTask = null;
104                fetchNvTimeoutCount++;
105                if ( fetchNvTimeoutCount == 1 ) {
106                    log.info("NV Fetch from node {} timed out",_node.getNodeNumber() ); // 
107                }
108                else if ( fetchNvTimeoutCount == 10 ) {
109                    log.error("Aborting NV Fetch from node {}",_node.getNodeNumber() ); //
110                    _node.getNodeNvManager().reset();
111                    _node.getNodeParamManager().setParameter(5,-1); // reset number of NV's to unknown and force refresh
112                }
113                
114                _node.getTableModel().triggerUrgentFetch();
115                
116            }
117        };
118        TimerUtil.schedule(nextNvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME);
119    }
120    
121    /**
122     * Stop timer for a single event variable request.
123     */
124    protected void clearNextEvVarTimeout(){
125        if (nextEvTimerTask != null ) {
126            nextEvTimerTask.cancel();
127            nextEvTimerTask = null;
128            fetchEvVarTimeoutCount = 0;
129        }
130    }
131    
132    /**
133     * Start timer for a single event variable request.
134     * 
135     * If 10 failed requests aborts loop and sets events to 0
136     * @param eventVarIndex Event Variable Index
137     * @param eventString User Friendly Event Text
138     */
139    protected void setNextEvVarTimeout(int eventVarIndex, String eventString) {
140        nextEvTimerTask = new TimerTask() {
141            @Override
142            public void run() {
143                nextEvTimerTask = null;
144                fetchEvVarTimeoutCount++;
145                if ( fetchEvVarTimeoutCount == 1 ) {
146                    log.info("Event Var fetch Timeout from Node {} event {}index {}",
147                        _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex);
148                }
149                if ( fetchEvVarTimeoutCount == 10 ) {
150                    log.error("Aborting Event Variable fetch from Node {} Event {}Index {}",
151                        _node.getNodeStats().getNodeNumberName(),eventString,eventVarIndex);
152                    _node.getNodeEventManager().resetNodeEvents();
153                    fetchEvVarTimeoutCount = 0;
154                }
155                
156                _node.getTableModel().triggerUrgentFetch();
157            }
158        };
159        TimerUtil.schedule(nextEvTimerTask, SINGLE_MESSAGE_TIMEOUT_TIME);
160    }
161    
162    /**
163     * Stop timer for event total RQEVN request.
164     */
165    protected void clearNumEvTimeout(){
166        if (numEvTimerTask != null ) {
167            numEvTimerTask.cancel();
168            numEvTimerTask = null;
169        }
170        numEvTimeoutCount = 0;
171    }
172    
173    /**
174     * Start timer for event total RQEVN request.
175     * 
176     * If 10 failed requests aborts loop and sets event number to 0
177     */
178    protected void setNumEvTimeout() {
179        numEvTimerTask = new TimerTask() {
180            @Override
181            public void run() {
182                numEvTimerTask = null;
183                if ( _node.getNodeEventManager().getTotalNodeEvents() < 0 ) {
184                    
185                    numEvTimeoutCount++;
186                    // the process will be re-attempted by the background fetch routine,
187                    // we don't start it here to give a little bit more time for network / node to recover.
188                    if ( numEvTimeoutCount == 1 ) {
189                        log.info("No reponse to RQEVN ( Get Total Events ) from node {}", _node );
190                    }
191                    if ( numEvTimeoutCount == 10 ) {
192                        log.info("Aborting requests for Total Events from node {}", _node );
193                        _node.getNodeEventManager().resetNodeEvents();
194                        numEvTimeoutCount = 0;
195                    }
196                }
197            }
198        };
199        TimerUtil.schedule(numEvTimerTask, ( 5000 ) );
200    }
201    
202    /**
203     * Stop timer for an ALL event by index fetch request.
204     */
205    protected void clearAllEvTimeout(){
206        if (allEvTimerTask != null ) {
207            allEvTimerTask.cancel();
208            allEvTimerTask = null;
209        }
210    }
211    
212    /**
213     * Starts timer for an ALL event by index fetch request.
214     * <p>
215     * This has a higher chance of failing as 
216     * we could be expecting up to 255 CAN Frames in response.
217     *
218     * If fails, re-sends the NERD to the physical node
219     * Aborts on 10 failed requests
220     */
221    protected void setAllEvTimeout() {
222        allEvTimerTask = new TimerTask() {
223            @Override
224            public void run() {
225                clearAllEvTimeout();
226                if ( _node.getNodeEventManager().getOutstandingIndexNodeEvents() > 0 ) {
227                    allEvTimeoutCount++;
228                    
229                    if ( allEvTimeoutCount < 10 ) {
230                        log.warn("Re-attempting whole event / node / index fetch from node {}", _node );
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    /**
349     * Stops timer for CAN ID Self Enumeration Timeout
350     */
351    protected void clearSendEnumTimeout(){
352        if (sendEnumTask != null ) {
353            sendEnumTask.cancel();
354            sendEnumTask = null;
355        }
356    }
357    
358    /**
359     * Starts timer for CAN ID Self Enumeration Timeout
360     * If no response adds warning to console log
361     */
362    protected void setsendEnumTimeout() {
363        sendEnumTask = new TimerTask() {
364            @Override
365            public void run() {
366                log.warn("Late response from node while request CAN ID Self Enumeration");
367                sendEnumTask = null;
368                // popup dialogue?
369            }
370        };
371        TimerUtil.schedule(sendEnumTask, ( SINGLE_MESSAGE_TIMEOUT_TIME ) );
372    }
373    
374    private static final Logger log = LoggerFactory.getLogger(CbusNodeTimerManager.class);
375    
376}