001package jmri.jmrix.can.cbus.node;
002
003import jmri.jmrix.can.CanMessage;
004import jmri.jmrix.can.CanReply;
005import jmri.jmrix.can.CanSystemConnectionMemo;
006import jmri.jmrix.can.cbus.CbusConstants;
007import jmri.jmrix.can.cbus.CbusMessage;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Class to represent a Processing of CAN Frames for a CbusNode.
014 *
015 * @author Steve Young Copyright (C) 2019,2020
016 */
017public class CbusNodeCanListener implements jmri.jmrix.can.CanListener {
018    public final CanSystemConnectionMemo memo;
019    private final CbusBasicNodeWithManagers _node;
020    private int[] tempSetupParams;
021    
022    /**
023     * Create a new CbusNodeCanListener
024     *
025     * @param connmemo The CAN Connection to listen to.
026     * @param node The Node
027     */
028    public CbusNodeCanListener ( CanSystemConnectionMemo connmemo, CbusBasicNodeWithManagers node ){
029        memo = connmemo;
030        _node = node;
031        addTc(memo);
032    }
033    
034    /**
035     * Processes certain outgoing CAN Frames.
036     * <p>
037     * We don't know if it's this JMRI instance or something external teaching the node
038     * so we monitor them the same
039     *
040     * {@inheritDoc} 
041     */
042    @Override
043    public void message(CanMessage m) {
044        switch ( CbusMessage.getOpcode(m) ) {
045            case CbusConstants.CBUS_NVSET:
046            case CbusConstants.CBUS_NNREL:
047            case CbusConstants.CBUS_NNLRN:
048            case CbusConstants.CBUS_NNULN:
049            case CbusConstants.CBUS_EVLRN:
050            case CbusConstants.CBUS_EVULN:
051            case CbusConstants.CBUS_ENUM:
052            case CbusConstants.CBUS_CANID:
053            case CbusConstants.CBUS_NNCLR:
054                CanReply r = new CanReply(m);
055                reply(r);
056                break;
057            default:
058                break;
059        }
060    }
061    
062    /**
063     * Processes all incoming and certain outgoing CAN Frames
064     *
065     * {@inheritDoc} 
066     */
067    @Override
068    public void reply(CanReply m) {
069        if ( m.extendedOrRtr() ) {
070            return;
071        }
072        int opc = CbusMessage.getOpcode(m);
073        int nn = ( m.getElement(1) * 256 ) + m.getElement(2);
074        
075        // if the OPC is coming from a Node, update the CAN ID field
076        // if the OPC is coming from software, do NOT update the CAN ID field
077        
078        // if node in learn mode 
079        if ( _node.getNodeInLearnMode() ) {
080           processInLearnMode(m);
081        }
082        
083        if (nn != _node.getNodeNumber() ) {
084            return;
085        }
086        
087        _node.setCanId(CbusMessage.getId(m));
088
089        switch (CbusMessage.getOpcode(m)) {
090            case CbusConstants.CBUS_CMDERR:
091                // response from node with an error message
092                
093                // if in middle of a learn process we do not re-kick the node here,
094                // as it may be another software sending the learn.
095                // If it is JMRI doing the learn, the timer for the learn will
096                // sort out any abort / resume logic.
097                
098                if (m.getElement(3)==5){
099                    // node reporting that last requested and further event variables for single event
100                    // are not required by the node, no need to request them
101                    _node.getNodeEventManager().remainingEvVarsNotNeeded();
102                }
103                else {
104                    if ((m.getElement(3) > 0 ) && (m.getElement(3) < 13 )) {
105                        log.error("Node {} reporting {}",_node,Bundle.getMessage("CMDERR"+m.getElement(3)) );
106                    } else {
107                        log.error("Node {} Reporting Error Code {} (decimal)",_node,m.getElement(3) );
108                    }
109                }   break;
110            case CbusConstants.CBUS_NNACK:
111                // response from node acknowledging something
112                if ( _node.getNodeTimerManager().sendEnumTask != null ) {
113                    _node.getNodeTimerManager().clearSendEnumTimeout();
114                }   break;
115            case CbusConstants.CBUS_PARAN:
116                // response from node
117                
118                processParam(m);
119                break;
120            case CbusConstants.CBUS_NUMEV:
121                // response from node
122                
123                int newEventsOnNode = m.getElement(3);
124                _node.getNodeTimerManager().clearNumEvTimeout();
125                _node.getNodeEventManager().resetNodeEventsToZero();
126                if ( _node.getNodeParamManager().getParameter(5)<0 ){
127                    return;
128                }   for (int i = 0; i < newEventsOnNode; i++) {
129                    CbusNodeEvent newev = new CbusNodeEvent(memo,-1, -1, _node.getNodeNumber(), -1,
130                            _node.getNodeParamManager().getParameter(5) );
131                    // (int nn, int en, int thisnode, int index, int maxEvVar);
132                    _node.getNodeEventManager().addNewEvent(newev);
133                }
134                _node.notifyPropertyChangeListener("ALLEVUPDATE",null,null);
135                break;
136            case CbusConstants.CBUS_ENRSP:
137                // response from node with a stored event, node + index
138                
139                int evnode = ( m.getElement(3) * 256 ) + m.getElement(4);
140                int evev = ( m.getElement(5) * 256 ) + m.getElement(6);
141                // get next node event which is empty
142                _node.getNodeEventManager().setNextEmptyNodeEvent(evnode,evev,m.getElement(7));
143                if ( ( _node.getNodeTimerManager().allEvTimerTask !=null ) && ( _node.getNodeEventManager().getOutstandingIndexNodeEvents() == 0 ) ) {
144                    // all events returned ok, this is the only
145                    // point ANYWHERE that the event index is set valid
146                    _node.getNodeTimerManager().clearAllEvTimeout();
147                    _node.getNodeEventManager().setEvIndexValid(true);
148                }   break;
149            case CbusConstants.CBUS_NEVAL: // response from node with event variable
150                _node.getNodeTimerManager().clearNextEvVarTimeout();
151                _node.getNodeEventManager().setEvVarByIndex(m.getElement(3),m.getElement(4),m.getElement(5));
152                break;
153            case CbusConstants.CBUS_NVANS: // response from node with node variable
154                // stop timer
155                _node.getNodeTimerManager().clearNextNvVarTimeout();
156                _node.getNodeNvManager().setNV(m.getElement(3),m.getElement(4));
157                break;
158            case CbusConstants.CBUS_NVSET:
159                // sent from software
160                _node.getNodeNvManager().setNV(m.getElement(3),m.getElement(4));
161                break;
162            case CbusConstants.CBUS_NNLRN:
163                // sent from software
164                // [AC] Ignore setting servo modules into learn mode during NV GUI edit
165                if (((CbusNode)_node).getnvWriteInLearnOnly() == false) {
166                    _node.setNodeInLearnMode(true);
167                }
168                break;
169            case CbusConstants.CBUS_NNULN:
170                // sent from software
171                _node.setNodeInLearnMode(false);
172                break;
173            case CbusConstants.CBUS_ENUM:
174                // sent from software
175                _node.setCanId(-1);
176                // now waiting for a NNACK confirmation or error message 7
177                // start a timer waiting for the response
178                _node.getNodeTimerManager().setsendEnumTimeout();
179                break;
180            case CbusConstants.CBUS_CANID:
181                // sent from software
182                _node.setCanId(-1);
183                // no response expected from node ( ? )
184                break;
185            default:
186                break;
187        }
188        
189        if ( _node.getNodeNvManager().teachOutstandingNvs() ) {
190        
191            if ( opc == CbusConstants.CBUS_WRACK ) { // response from node
192                _node.getNodeTimerManager().clearsendEditNvTimeout();
193                _node.getNodeNvManager().sendNextNvToNode();
194            }
195            else if ( opc == CbusConstants.CBUS_CMDERR ) { // response from node
196                _node.getNodeTimerManager()._sendNVErrorCount++;
197                log.warn("Node reports NV Write Error");
198                _node.getNodeTimerManager().clearsendEditNvTimeout();
199                _node.getNodeNvManager().sendNextNvToNode();
200            }
201        }
202        
203        if ( _node.getTableModel() != null ) {
204            _node.getTableModel().triggerUrgentFetch(); // 
205        }
206        
207    }
208    
209    private void processParam(CanReply m){
210        _node.getNodeTimerManager().clearAllParamTimeout();
211        if (m.getElement(3)==0) { // param 0 is number of params
212            int [] myarray = new int[(m.getElement(4)+1)]; // +1 to account for index 0 being the parameter count
213            java.util.Arrays.fill(myarray, -1);
214            // node may already be aware of some params via the initial PNN or STAT
215
216            myarray[1] = _node.getPnnManufacturer();
217            myarray[2] = _node._fwMin;
218            myarray[3] = _node.getPnnModule();
219            myarray[7] = _node._fwMaj;
220
221            if ( tempSetupParams !=null ) {
222
223                log.debug("tempSetupParams {}",tempSetupParams);
224
225                myarray[1] = tempSetupParams[0];
226                myarray[2] = tempSetupParams[1];
227                myarray[3] = tempSetupParams[2];
228                myarray[4] = tempSetupParams[3];
229                myarray[5] = tempSetupParams[4];
230                myarray[6] = tempSetupParams[5];
231                myarray[7] = tempSetupParams[6];
232
233                // reset NV array
234                if ( myarray[6] > -1 ){
235
236                    int [] myParray = new int[(myarray[6]+1)]; // +1 to account for index 0 being the NV count
237                    java.util.Arrays.fill(myParray, -1);
238                    myParray[0] = myarray[6];
239                    _node.getNodeNvManager().setNVs(myParray);
240                }
241            }
242
243            myarray[0] = m.getElement(4);
244
245            // log.info("parameter 0 is {}",myarray[0]);
246            _node.getNodeParamManager().setParameters(myarray);
247
248            // setting them via setParameter to avoid nulls if number of parameters is v low
249            // most modules report up to 20, but some may not.
250            _node.getNodeParamManager().setParameter( 20, _node._fwBuild );
251
252        } else {
253            _node.getNodeParamManager().setParameter( m.getElement(3), m.getElement(4) );
254            if ( m.getElement(3) == 6 ) { // param 3 is number of NVs
255                int [] myarray = new int[(m.getElement(4)+1)]; // +1 to account for index 0 being the NV count
256                java.util.Arrays.fill(myarray, -1);
257                myarray[0] = m.getElement(4);
258                _node.getNodeNvManager().setNVs(myarray);
259            }
260        }
261    }
262    
263    private void processInLearnMode(CanReply m){
264    
265        int opc = CbusMessage.getOpcode(m);
266        int nn = ( m.getElement(1) * 256 ) + m.getElement(2);
267        
268        switch (opc) {
269            case CbusConstants.CBUS_NNCLR:
270                // instruction to delete all node events
271                if ( nn == _node.getNodeNumber() ) {
272                    _node.getNodeEventManager().resetNodeEventsToZero();
273                }
274                break;
275            case CbusConstants.CBUS_EVLRN:
276                // update node database with event
277                _node.getNodeEventManager().updateNodeFromLearn(
278                        nn,
279                        ( m.getElement(3) * 256 ) + m.getElement(4),
280                        m.getElement(5),
281                        m.getElement(6) );
282                break;
283            case CbusConstants.CBUS_EVULN:
284                _node.getNodeEventManager().removeEvent( ( m.getElement(1) * 256 ) + m.getElement(2), ( m.getElement(3) * 256 ) + m.getElement(4) );
285                break;
286            case CbusConstants.CBUS_EVLRNI:
287                processEvlrni(m);
288                break;
289            default:
290                break;
291        }
292        
293        if ( _node.getNodeEventManager().TEACH_OUTSTANDING_EVS ) {
294            if ( opc == CbusConstants.CBUS_WRACK ) {
295                // cancel timer
296                _node.getNodeTimerManager().clearsendEditEvTimeout();
297                // start next in loop
298                _node.getNodeEventManager().teachNewEvLoop();
299            }
300            if ( opc == CbusConstants.CBUS_CMDERR ) {
301                // cancel timer
302                _node.getNodeTimerManager().clearsendEditEvTimeout();
303                _node.getNodeTimerManager().sendEvErrorCount++;
304                // start next in loop
305                _node.getNodeEventManager().teachNewEvLoop();
306            }
307        }
308    }
309    
310    private void processEvlrni(CanReply m){
311        // check if current index is valid
312        if ( !_node.getNodeEventManager().isEventIndexValid() ){
313            log.warn("EVRLNI OPC heard while Event Index Invalid for Node {}",_node );
314        }
315        else {
316            // find existing event , m.getElement(5) is event index number being edited
317            CbusNodeEvent toEdit = _node.getNodeEventManager().getNodeEventByIndex( m.getElement(5) );
318            if (toEdit == null) {
319                log.warn("No event with index {} found on node {}",m.getElement(5),toString() );
320            } else {
321                // event found with correct index number
322                toEdit.setNn( ( m.getElement(1) * 256 ) + m.getElement(2) );
323                toEdit.setEn( ( m.getElement(3) * 256 ) + m.getElement(4) );
324                toEdit.setEvVar( ( m.getElement(6) * 256 ), m.getElement(7) );
325            }
326        }
327    }
328    
329    /**
330     * Temporarily store Node Parameters obtained from a Node requesting a Node Number
331     * <p>
332     * Parameter array is not created until total number of parameters is known.
333     * This saves asking the Node for them.
334     *
335     * @param setupParams an int array in order of final 7 bytes of the CBUS_PARAMS node response
336     */
337    public void setParamsFromSetup(int[] setupParams) {
338        log.debug("setup parameters received {}",setupParams);
339        tempSetupParams = setupParams;
340    }
341    
342    /**
343     * Disconnects from network
344     */
345    public void dispose(){
346        removeTc(memo);
347    }
348    
349    private static final Logger log = LoggerFactory.getLogger(CbusNodeCanListener.class);
350    
351}