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}