001package jmri.jmrix.can.cbus.node; 002 003import java.util.ArrayList; 004import javax.annotation.CheckForNull; 005import javax.annotation.Nonnull; 006import jmri.jmrix.can.CanSystemConnectionMemo; 007import jmri.util.ThreadingUtil; 008 009/** 010 * Class to represent a Processing of CAN Frames for a CbusNode. 011 * 012 * @author Steve Young Copyright (C) 2019,2020 013 */ 014public class CbusNodeEventManager { 015 private final CbusBasicNodeWithManagers _node; 016 private final CanSystemConnectionMemo _memo; 017 018 protected int nextEvInArray; 019 private ArrayList<CbusNodeEvent> _nodeEvents; 020 private boolean _eventIndexValid; 021 private ArrayList<CbusNodeEvent> eventsToTeachArray; 022 private int nextEvVar; 023 protected boolean TEACH_OUTSTANDING_EVS; 024 025 /** 026 * Create a new CbusNodeNVManager 027 * 028 * @param memo System connection 029 * @param node The Node 030 */ 031 public CbusNodeEventManager ( CanSystemConnectionMemo memo, CbusBasicNodeWithManagers node ){ 032 _node = node; 033 _memo = memo; 034 _nodeEvents = null; 035 _eventIndexValid = false; 036 TEACH_OUTSTANDING_EVS = false; 037 } 038 039 /** 040 * Returns total number of node events, 041 * including those with outstanding event variables. 042 * 043 * @return number of events, -1 if events not set 044 */ 045 public int getTotalNodeEvents(){ 046 if (_nodeEvents == null) { 047 return -1; 048 } 049 return _nodeEvents.size(); 050 } 051 052 /** 053 * Returns number of fully loaded events, ie no outstanding event variables. 054 * 055 * @return number of loaded events, -1 if events not set 056 */ 057 public int getLoadedNodeEvents(){ 058 if (_nodeEvents == null) { 059 return -1; 060 } 061 int count = 0; 062 for (int i = 0; i < _nodeEvents.size(); i++) { 063 if ( ( _nodeEvents.get(i).getNn() != -1 ) && ( _nodeEvents.get(i).getEn() != -1 )) { 064 count ++; 065 } 066 } 067 return count; 068 } 069 070 /** 071 * Returns outstanding events from initial event fetch. 072 * 073 * @return number of outstanding index events 074 */ 075 public int getOutstandingIndexNodeEvents(){ 076 return getTotalNodeEvents() - getLoadedNodeEvents(); 077 } 078 079 /** 080 * Add an event to the node, will not overwrite an existing event. 081 * Resets Event Index as Invalid for All Node Events 082 * 083 * @param newEvent the new event to be added 084 */ 085 public void addNewEvent( @Nonnull CbusNodeEvent newEvent ) { 086 if (_nodeEvents == null) { 087 _nodeEvents = new ArrayList<>(); 088 } 089 _nodeEvents.add(newEvent); 090 091 setEvIndexValid(false); // Also produces Property Change Event 092 } 093 094 /** 095 * Remove an event from the CbusNode, does not update hardware. 096 * 097 * @param nn the event Node Number 098 * @param en the event Event Number 099 */ 100 public void removeEvent(int nn, int en){ 101 _nodeEvents.remove(getNodeEvent(nn, en)); 102 setEvIndexValid(false); // Also produces Property Change Event 103 } 104 105 /** 106 * Get a Node event from its Event and Node number combination 107 * 108 * @param en the Event event number 109 * @param nn the Event node number 110 * @return the node event else null if no Event / Node number combination. 111 */ 112 @CheckForNull 113 public CbusNodeEvent getNodeEvent(int nn, int en) { 114 if (_nodeEvents==null){ 115 return null; 116 } 117 for (int i = 0; i < _nodeEvents.size(); i++) { 118 if ( ( _nodeEvents.get(i).getNn() == nn ) && ( _nodeEvents.get(i).getEn() == en )) { 119 return _nodeEvents.get(i); 120 } 121 } 122 return null; 123 } 124 125 /** 126 * Provide a Node event from its Event and Node number combination 127 * <p> 128 * If an event for this number pair does not already exist on the node 129 * one will be created, else the existing will be returned. 130 * <p> 131 * Adds any new CbusNodeEvent to the node event array, 132 * which will also be created if it doesn't exist. 133 * 134 * @param en the Event event number 135 * @param nn the Event node number 136 * @return the node event 137 */ 138 @Nonnull 139 public CbusNodeEvent provideNodeEvent(int nn, int en) { 140 if (_nodeEvents == null) { 141 _nodeEvents = new ArrayList<>(); 142 } 143 CbusNodeEvent newev = getNodeEvent(nn,en); 144 if ( newev ==null){ 145 newev = new CbusNodeEvent(_memo,nn, en, _node.getNodeNumber(), 146 -1, _node.getNodeParamManager().getParameter(5) ); 147 addNewEvent(newev); 148 } 149 setEvIndexValid(false); // Also produces Property Change Event 150 return newev; 151 } 152 153 /** 154 * Update node with new Node Event. 155 * 156 * @param nn Node Number 157 * @param en Event Number 158 * @param evvarindex Event Variable Index 159 * @param evvarval Event Variable Value 160 */ 161 protected void updateNodeFromLearn(int nn, int en, int evvarindex, int evvarval ){ 162 CbusNodeEvent nodeEv = provideNodeEvent( nn , en ); 163 nodeEv.setEvVar( evvarindex , evvarval ); 164 _node.notifyPropertyChangeListener("ALLEVUPDATE",null,null); 165 166 } 167 168 /** 169 * Get a Node event from its Index Field 170 * <p> 171 * This is NOT the node array index. 172 * 173 * @param index the Node event index, as set by a node from a NERD request 174 * @return the node event, else null if the index is not located 175 */ 176 @CheckForNull 177 public CbusNodeEvent getNodeEventByIndex(int index) { 178 179 if ( _nodeEvents == null ){ 180 return null; 181 } 182 for (int i = 0; i < _nodeEvents.size(); i++) { 183 if ( _nodeEvents.get(i).getIndex() == index ) { 184 return _nodeEvents.get(i); 185 } 186 } 187 return null; 188 } 189 190 /** 191 * Get the Node event Array index from its Index Field 192 * 193 * @param index the Node event index, as set by a node from a NERD request 194 * @return the array index, else -1 if event index number not found in array 195 */ 196 protected int getEventRowFromIndex(int index ){ 197 for (int i = 0; i < _nodeEvents.size(); i++) { 198 if ( _nodeEvents.get(i).getIndex() == index ) { 199 return i; 200 } 201 } 202 return -1; 203 } 204 205 /** 206 * Get the Node event by ArrayList Index. 207 * 208 * @param index the index of the CbusNodeEvent within the ArrayList 209 * @return the Node Event 210 */ 211 @CheckForNull 212 public CbusNodeEvent getNodeEventByArrayID(int index) { 213 return _nodeEvents.get(index); 214 } 215 216 /** 217 * Get the Node event ArrayList 218 * 219 * @return the list of Node Events 220 */ 221 @CheckForNull 222 public ArrayList<CbusNodeEvent> getEventArray(){ 223 return _nodeEvents; 224 } 225 226 /** 227 * Get the Number of Outstanding Event Variables 228 * <p> 229 * Sometimes, the Event Variables have to be initialised with an unknown 230 * status, this returns a count of unknown Event Variables for the whole Node. 231 * 232 * @return -1 if main node events array null, else number Outstanding Ev Vars 233 */ 234 public int getOutstandingEvVars(){ 235 int count = 0; 236 ArrayList<CbusNodeEvent> evs = getEventArray(); 237 if ( evs == null ){ 238 return -1; 239 } 240 for (int i = 0; i < evs.size(); i++) { 241 count += evs.get(i).getOutstandingVars(); 242 } 243 return count; 244 } 245 246 /** 247 * The last message from the node CMDERR5 indicates that all remaining event variables 248 * for a particular event are not required. 249 * This sets the remaining ev vars to 0 so are not requested 250 */ 251 protected void remainingEvVarsNotNeeded(){ 252 ArrayList<CbusNodeEvent> evs = getEventArray(); 253 if (evs!=null) { 254 for (int i = 0; i < evs.size(); i++) { 255 if ( evs.get(i).getNextOutstanding() > 0 ) { 256 evs.get(i).allOutstandingEvVarsNotNeeded(); 257 258 // cancel Timer 259 _node.getNodeTimerManager().clearNextEvVarTimeout(); 260 // update GUI 261 _node.notifyPropertyChangeListener("SINGLEEVUPDATE",null,i); 262 return; 263 } 264 } 265 } 266 } 267 268 /** 269 * Send a request for the next unknown Event Variable to the Physical Node 270 * <p> 271 * If events are unknown, sends the NERD request and starts that timer, 272 * else requests the next Ev Var with unknown status ( represented as int value -1 ) 273 * Will NOT send if any Node is in Learn Mode or if there are any outstanding requests from the Node. 274 */ 275 protected void sendNextEvVarToFetch() { 276 ArrayList<CbusNodeEvent> _evs = getEventArray(); 277 // do not request if node is learn mode 278 if (( _node.getTableModel().getAnyNodeInLearnMode() > -1 ) 279 || ( _evs == null ) 280 || ( _node.getNodeTimerManager().hasActiveTimers()) ){ 281 return; 282 } 283 284 // if events on module, get their event, node and node index 285 // *** This could produce up to 255 responses per node *** 286 if ( ( getTotalNodeEvents() > 0 ) && getOutstandingIndexNodeEvents()>0 ) { 287 _node.send.nERD( _node.getNodeNumber() ); 288 // starts timeout 289 _node.getNodeTimerManager().setAllEvTimeout(); 290 return; 291 } 292 293 for (int i = 0; i < _evs.size(); i++) { 294 if ( _evs.get(i).getOutstandingVars() > 0 ) { 295 int index = _evs.get(i).getIndex(); 296 int nextevvar = _evs.get(i).getNextOutstanding(); 297 298 // index from NERD / ENRSP indexing may start at 0 299 if ( index > -1 ) { 300 301 // start timer 302 _node.getNodeTimerManager().setNextEvVarTimeout( nextevvar,_evs.get(i).toString() ); 303 _node.send.rEVAL( _node.getNodeNumber(), index, nextevvar ); 304 return; 305 } 306 else { // if index < 0 event index is invalid so attempt refetch. 307 // reset events 308 log.info("Invalid index, resetting events for node {}", _node ); 309 _nodeEvents = null; 310 return; 311 } 312 } 313 } 314 } 315 316 /** 317 * Used in CBUS_NEVAL response from Node. 318 * Set the value of an event variable by event Index and event Variable Index 319 * @param eventIndex Event Index 320 * @param eventVarIndex Event Variable Index 321 * @param newVal New Value 322 */ 323 protected void setEvVarByIndex(int eventIndex, int eventVarIndex, int newVal) { 324 CbusNodeEvent nodeEvByIndex = getNodeEventByIndex(eventIndex); 325 if ( nodeEvByIndex != null ) { 326 nodeEvByIndex.setEvVar(eventVarIndex,newVal); 327 _node.notifyPropertyChangeListener("SINGLEEVUPDATE",null,getEventRowFromIndex(eventIndex)); 328 } 329 } 330 331 /** 332 * Used to process a CBUS_ENRSP response from node 333 * 334 * If existing index known, use that slot in the event array, 335 * else if event array has empty slot for that index, use that slot. 336 * @param nn Node Number 337 * @param en Event Number 338 * @param index Index Number 339 */ 340 protected void setNextEmptyNodeEvent(int nn, int en, int index){ 341 ArrayList<CbusNodeEvent> _evs = getEventArray(); 342 if ( _evs == null ){ 343 log.error("Indexed events are not expected as total number of events unknown"); 344 return; 345 } else { 346 for (int i = 0; i < _evs.size(); i++) { 347 if ( _evs.get(i).getIndex() == index ) { 348 _evs.get(i).setNn(nn); 349 _evs.get(i).setEn(en); 350 _node.notifyPropertyChangeListener("SINGLEEVUPDATE",null,i); 351 return; 352 } 353 } 354 } 355 356 for (int i = 0; i < _nodeEvents.size(); i++) { 357 if ( ( _nodeEvents.get(i).getNn() == -1 ) && ( _nodeEvents.get(i).getEn() == -1 ) ) { 358 _nodeEvents.get(i).setNn(nn); 359 _nodeEvents.get(i).setEn(en); 360 _nodeEvents.get(i).setIndex(index); 361 362 _node.notifyPropertyChangeListener("SINGLEEVUPDATE",null,i); 363 return; 364 } 365 } 366 log.error("Issue setting node event, index {} not valid",index); 367 _nodeEvents = null; 368 } 369 370 /** 371 * Get if the Node event index is valid. 372 * @return true if event index is valid, else false if invalid or no events on node. 373 */ 374 protected boolean isEventIndexValid(){ 375 return _eventIndexValid; 376 } 377 378 /** 379 * Set the Node event index flag as valid or invalid. 380 * <p> 381 * Resets all Node Event Indexes to -1 if invalid. 382 * @param newval true if Event Index Valid, else false 383 */ 384 protected void setEvIndexValid( boolean newval ) { 385 _eventIndexValid = newval; 386 if (!newval){ // event index no longer valid so clear values in individual events 387 for (int i = 0; i < _nodeEvents.size(); i++) { 388 _nodeEvents.get(i).setIndex(-1); 389 } 390 } 391 _node.notifyPropertyChangeListener("ALLEVUPDATE",null,null); 392 } 393 394 /** 395 * Send and teach updated Events to this node 396 * 397 * @param evArray array of CbusNodeEvents to be taught 398 */ 399 public void sendNewEvSToNode( @Nonnull ArrayList<CbusNodeEvent> evArray ){ 400 eventsToTeachArray = evArray; 401 402 if (eventsToTeachArray==null){ 403 _node.getNodeTimerManager().sendEvErrorCount=1; 404 teachEventsComplete(); 405 return; 406 } 407 408 if (eventsToTeachArray.isEmpty()){ 409 teachEventsComplete(); 410 return; 411 } 412 413 // check other nodes in learn mode 414 if ( _node.getTableModel().getAnyNodeInLearnMode() > -1 ) { 415 String err = "Cancelling teach event. Node " + _node.getTableModel().getAnyNodeInLearnMode() + " is already in Learn Mode"; 416 log.warn("Unable to teach: {}",err); 417 _node.notifyPropertyChangeListener("ADDEVCOMPLETE", null, err); 418 return; 419 } 420 421 TEACH_OUTSTANDING_EVS = true; 422 nextEvInArray = 0; 423 nextEvVar = 1; // start at 1 as 0 is used for total ev vars 424 _node.getNodeTimerManager().sendEvErrorCount = 0; 425 426 _node.send.nodeEnterLearnEvMode( _node.getNodeNumber() ); // no response expected but we add a mini delay for other traffic 427 428 log.debug("sendNewEvSToNode {}",evArray); 429 430 ThreadingUtil.runOnLayoutDelayed( () -> { 431 teachNewEvLoop(); 432 }, 50 ); 433 434 } 435 436 /** 437 * Send a message to delete an event stored on this node 438 * 439 * @param nn event node number 440 * @param en event event number 441 */ 442 public void deleteEvOnNode( int nn, int en){ 443 444 // check other nodes in learn mode 445 if ( _node.getTableModel().getAnyNodeInLearnMode() > -1 ) { 446 String err = "Cancelling delete event. Node " + _node.getTableModel().getAnyNodeInLearnMode() + " is already in Learn Mode"; 447 log.warn("Unable to teach: {}",err); 448 _node.notifyPropertyChangeListener("DELETEEVCOMPLETE", null, err); 449 return; 450 } 451 452 _node.send.nodeEnterLearnEvMode( _node.getNodeNumber() ); 453 // no response expected but we add a mini delay for other traffic 454 ThreadingUtil.runOnLayoutDelayed( () -> { 455 _node.send.nodeUnlearnEvent( nn, en ); 456 setEvIndexValid(false); 457 }, 50 ); 458 log.info("Deleted Event {} on Node {}", 459 new jmri.jmrix.can.cbus.CbusEvent(nn,en),_node.getNodeNumber()); 460 ThreadingUtil.runOnGUIDelayed( () -> { 461 _node.send.nodeExitLearnEvMode( _node.getNodeNumber() ); 462 // notify ui 463 _node.notifyPropertyChangeListener("DELETEEVCOMPLETE", null, ""); 464 }, 100 ); 465 } 466 467 private void teachEventsComplete(){ 468 469 TEACH_OUTSTANDING_EVS = false; 470 _node.send.nodeExitLearnEvMode( _node.getNodeNumber() ); 471 String err; 472 if ( _node.getNodeTimerManager().sendEvErrorCount==0 ) { 473 log.info("Completed Event Write with No errors, node {}.", _node ); 474 err = ""; 475 } 476 else { 477 err = "Event Write Failed with "+ _node.getNodeTimerManager().sendEvErrorCount +" errors."; 478 log.error("{} Node {}.", err, _node); 479 480 } 481 // notify ui's 482 ThreadingUtil.runOnGUIDelayed( () -> { 483 _node.notifyPropertyChangeListener("ADDEVCOMPLETE", null, err); 484 _node.notifyPropertyChangeListener("ADDALLEVCOMPLETE", null, _node.getNodeTimerManager().sendEvErrorCount); 485 _node.getNodeTimerManager().sendEvErrorCount=0; 486 },50 ); 487 } 488 489 /** 490 * Loop for event teaching, triggered from various places 491 */ 492 protected void teachNewEvLoop(){ 493 494 if ( nextEvVar > _node.getNodeParamManager().getParameter(5) ) { 495 nextEvVar = 1; 496 nextEvInArray++; 497 } 498 499 if ( nextEvInArray >= eventsToTeachArray.size() ) { 500 log.debug("all done"); 501 teachEventsComplete(); 502 return; 503 } 504 505 CbusNodeEvent wholeEvent = eventsToTeachArray.get(nextEvInArray); 506 log.debug("event variable array length: {}", wholeEvent.getEvVarArray().length); 507 log.debug("Number of event vars from node parameters: {}",_node.getNodeParamManager().getParameter(CbusNodeConstants.EV_PER_EN_IDX) ); 508 if (wholeEvent.getEvVarArray().length < nextEvVar ){ 509 log.warn("Incorrect number of event variables ({}) for {}", 510 wholeEvent.getEvVarArray().length ,wholeEvent ); 511 _node.getNodeTimerManager().sendEvErrorCount++; 512 nextEvVar = 1; 513 nextEvInArray++; 514 teachNewEvLoop(); 515 return; 516 } 517 518 log.debug("teach event var {} val {} ",nextEvVar,wholeEvent.getEvVar(nextEvVar)); 519 CbusNodeEvent existingEvent = getNodeEvent( wholeEvent.getNn(), wholeEvent.getEn() ); 520 521 log.debug("teach event {} with existing event {}",wholeEvent,existingEvent); 522 523 // increment and restart loop if no change 524 if ((existingEvent!=null) && (existingEvent.getEvVar(nextEvVar)==wholeEvent.getEvVar(nextEvVar))){ 525 nextEvVar++; 526 teachNewEvLoop(); 527 return; 528 } 529 530 // start timeout , send message, increment for next run, only sent when WRACK received 531 _node.getNodeTimerManager().setsendEditEvTimeout(); 532 _node.send.nodeTeachEventLearnMode(wholeEvent.getNn(),wholeEvent.getEn(),nextEvVar,wholeEvent.getEvVar(nextEvVar)); 533 nextEvVar++; 534 } 535 536 /** 537 * Resets Node Events with null array. 538 * For when a CbusNode is reset to unknown events. 539 */ 540 public void resetNodeEvents() { 541 _nodeEvents = null; 542 _node.notifyPropertyChangeListener("ALLEVUPDATE",null,null); 543 } 544 545 /** 546 * Resets Node Events with zero length array. 547 * For when a CbusNode is reset to 0 events 548 * 549 */ 550 public void resetNodeEventsToZero() { 551 _nodeEvents = null; 552 _nodeEvents = new ArrayList<>(); 553 _node.notifyPropertyChangeListener("ALLEVUPDATE",null,null); 554 } 555 556 /** 557 * the next event index for a CbusDummyNode NODE to allocate, 558 * NOT a software tool. 559 * @return next available event index 560 */ 561 public int getNextFreeIndex(){ 562 int newIndex = 1; 563 for (int i = 0; i < getTotalNodeEvents(); i++) { 564 CbusNodeEvent a = getNodeEventByArrayID(i); 565 if ( (a!=null) && newIndex <= a.getIndex() ) { 566 newIndex = a.getIndex()+1; 567 } 568 } 569 log.debug("dummy node sets index {}",newIndex); 570 return newIndex; 571 } 572 573 /** 574 * @return descriptive string 575 */ 576 @Override 577 public String toString() { 578 return "Node Events"; 579 } 580 581 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeEventManager.class); 582 583}