001package jmri.jmrit.beantable.routetable; 002 003import jmri.*; 004import jmri.implementation.DefaultConditionalAction; 005import jmri.util.FileUtil; 006 007import java.util.ArrayList; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.Nonnull; 011 012/** 013 * Enable creation of a Logix from a Route. 014 * 015 * Split from {@link jmri.jmrit.beantable.RouteTableAction} 016 * 017 * @author Dave Duchamp Copyright (C) 2004 018 * @author Bob Jacobsen Copyright (C) 2007 019 * @author Simon Reader Copyright (C) 2008 020 * @author Pete Cressman Copyright (C) 2009 021 * @author Egbert Broerse Copyright (C) 2016 022 * @author Paul Bender Copyright (C) 2020 023 */ 024public class RouteExportToLogix { 025 026 private final String logixSysName; 027 private final String conditionalSysPrefix; 028 private final String systemName; 029 private final RouteManager routeManager; 030 private final LogixManager logixManager; 031 private final ConditionalManager conditionalManager; 032 033 RouteExportToLogix(String systemName){ 034 this(systemName,InstanceManager.getDefault(RouteManager.class), 035 InstanceManager.getDefault(LogixManager.class), 036 InstanceManager.getDefault(ConditionalManager.class)); 037 } 038 039 RouteExportToLogix(String systemName, RouteManager routeManager, 040 LogixManager logixManager,ConditionalManager conditionalManager){ 041 this.systemName = systemName; 042 this.routeManager = routeManager; 043 this.logixManager = logixManager; 044 this.conditionalManager = conditionalManager; 045 046 String logixPrefix = logixManager.getSystemNamePrefix(); 047 logixSysName = logixPrefix + ":RTX:"; 048 conditionalSysPrefix = logixSysName + "C"; 049 } 050 051 public void export() { 052 String logixSystemName = logixSysName + systemName; 053 Route route = routeManager.getBySystemName(systemName); 054 if(route == null ){ 055 log.error("Route {} does not exist",systemName); 056 return; 057 } 058 String uName = route.getUserName(); 059 Logix logix = logixManager.getBySystemName(logixSystemName); 060 if (logix == null) { 061 logix = logixManager.createNewLogix(logixSystemName, uName); 062 if (logix == null) { 063 log.error("Failed to create Logix {}, {}", logixSystemName, uName); 064 return; 065 } 066 } 067 logix.deActivateLogix(); 068 069 /////////////////// Construct output actions for change to true ////////////////////// 070 ArrayList<ConditionalAction> actionList = getConditionalActions(route); 071 072 String file = route.getOutputSoundName(); 073 if (file!=null && file.length() > 0) { 074 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE, 075 Conditional.Action.PLAY_SOUND, "", -1, FileUtil.getPortableFilename(file))); 076 } 077 file = route.getOutputScriptName(); 078 if (file!=null && file.length() > 0) { 079 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE, 080 Conditional.Action.RUN_SCRIPT, "", -1, FileUtil.getPortableFilename(file))); 081 } 082 083 ///// Construct 'AND' clause from 'VETO' controls //////// 084 ArrayList<ConditionalVariable> vetoList = getVetoVariables(route); 085 086 removeOldConditionalNames(route,logix); 087 088 ///////////////// Make Trigger Conditionals ////////////////////// 089 int numConds = 1; // passed through all these, with new value returned each time 090 numConds = makeSensorConditional(route.getRouteSensor(0),route.getRouteSensorMode(0), 091 numConds, false, actionList, vetoList, logix, logixSystemName, uName); 092 numConds = makeSensorConditional(route.getRouteSensor(1), route.getRouteSensorMode(1), 093 numConds, false, actionList, vetoList, logix, logixSystemName, uName); 094 numConds = makeSensorConditional(route.getRouteSensor(2), route.getRouteSensorMode(2), 095 numConds, false, actionList, vetoList, logix, logixSystemName, uName); 096 numConds = makeTurnoutConditional(route.getCtlTurnout(), route.getControlTurnoutState(), 097 numConds, false, actionList, vetoList, logix, logixSystemName, uName); 098 099 ////// Construct actions for false from the 'any change' controls //////////// 100 numConds = makeSensorConditional(route.getRouteSensor(0), route.getRouteSensorMode(0), 101 numConds, true, actionList, vetoList, logix, logixSystemName, uName); 102 numConds = makeSensorConditional(route.getRouteSensor(1), route.getRouteSensorMode(1), 103 numConds, true, actionList, vetoList, logix, logixSystemName, uName); 104 numConds = makeSensorConditional(route.getRouteSensor(2), route.getRouteSensorMode(2), 105 numConds, true, actionList, vetoList, logix, logixSystemName, uName); 106 numConds = makeTurnoutConditional(route.getCtlTurnout(), route.getControlTurnoutState(), 107 numConds, true, actionList, vetoList, logix, logixSystemName, uName); 108 log.debug("Final number of conditionals: {}", numConds); 109 addRouteAlignmentSensorToLogix(logixSystemName, route, uName, logix); 110 111 addRouteLockToLogix(logixSystemName, route, uName, logix); 112 113 logix.activateLogix(); 114 routeManager.deleteRoute(route); 115 } 116 117 private void addRouteAlignmentSensorToLogix(String logixSystemName, Route route, String uName, Logix logix) { 118 String cUserName; 119 ArrayList<ConditionalAction> actionList; 120 ///////////////// Set up Alignment Sensor, if there is one ////////////////////////// 121 Sensor sens = route.getTurnoutsAlgdSensor(); 122 if (sens != null) { 123 String sensorDisplayName = sens.getDisplayName(); 124 String cSystemName = logixSystemName + "1A"; // NOI18N 125 cUserName = sens.getDisplayName() + "A " + uName; // NOI18N 126 127 ArrayList<ConditionalVariable> variableList = new ArrayList<>(); 128 for(int i=0;i<route.getNumOutputTurnouts();i++){ 129 Turnout ot = route.getOutputTurnout(i); 130 if ( ot != null ) { 131 String name = ot.getDisplayName(); 132 133 // exclude toggled outputs 134 switch (route.getOutputTurnoutState(i)) { 135 case Turnout.CLOSED: 136 variableList.add(new ConditionalVariable(false, Conditional.Operator.AND, 137 Conditional.Type.TURNOUT_CLOSED, name, true)); 138 break; 139 case Turnout.THROWN: 140 variableList.add(new ConditionalVariable(false, Conditional.Operator.AND, 141 Conditional.Type.TURNOUT_THROWN, name, true)); 142 break; 143 default: 144 log.warn("Turnout {} was {}, neither CLOSED nor THROWN; not handled", 145 name, route.getOutputTurnoutState(i)); // NOI18N 146 } 147 } 148 } 149 actionList = new ArrayList<>(); 150 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE, 151 Conditional.Action.SET_SENSOR, sensorDisplayName, Sensor.ACTIVE, "")); 152 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_FALSE, 153 Conditional.Action.SET_SENSOR, sensorDisplayName, Sensor.INACTIVE, "")); 154 155 Conditional c = conditionalManager.createNewConditional(cSystemName, cUserName); 156 c.setStateVariables(variableList); 157 c.setLogicType(Conditional.AntecedentOperator.ALL_AND, ""); 158 c.setAction(actionList); 159 logix.addConditional(cSystemName, 0); 160 c.calculate(true, null); 161 } 162 } 163 164 private void removeOldConditionalNames(Route route,Logix logix) { 165 // remove old Conditionals for actions (ver 2.5.2 only -remove a bad idea) 166 char[] ch = route.getSystemName().toCharArray(); 167 int hash = 0; 168 for (char value : ch) { 169 hash += value; 170 } 171 String cSystemName = conditionalSysPrefix + "T" + hash; 172 removeConditionals(cSystemName, logix); 173 cSystemName = conditionalSysPrefix + "F" + hash; 174 removeConditionals(cSystemName, logix); 175 cSystemName = conditionalSysPrefix + "A" + hash; 176 removeConditionals(cSystemName, logix); 177 cSystemName = conditionalSysPrefix + "L" + hash; 178 removeConditionals(cSystemName, logix); 179 180 int n = 0; 181 do { 182 n++; 183 cSystemName = logix.getSystemName() + n + "A"; 184 } while (removeConditionals(cSystemName, logix)); 185 n = 0; 186 do { 187 n++; 188 cSystemName = logix.getSystemName() + n + "T"; 189 } while (removeConditionals(cSystemName, logix)); 190 cSystemName = logix.getSystemName() + "L"; 191 removeConditionals(cSystemName, logix); 192 } 193 194 private void addRouteLockToLogix(String logixSystemName, Route route, String uName, Logix logix) { 195 String cSystemName; 196 String cUserName; 197 ArrayList<ConditionalAction> actionList; 198 ///////////////// Set lock turnout information if there is any ////////////////////////// 199 Turnout lockControlTurnout = route.getLockCtlTurnout(); 200 if ( lockControlTurnout != null ) { 201 202 203 // verify name (logix doesn't use "provideXXX") 204 cSystemName = logixSystemName + "1L"; // NOI18N 205 cUserName = lockControlTurnout.getSystemName() + "L " + uName; // NOI18N 206 ArrayList<ConditionalVariable> variableList = new ArrayList<>(); 207 int mode = route.getLockControlTurnoutState(); 208 Conditional.Type conditionalType = Conditional.Type.TURNOUT_CLOSED; 209 if (mode == Route.ONTHROWN) { 210 conditionalType = Conditional.Type.TURNOUT_THROWN; 211 } 212 variableList.add(new ConditionalVariable(false, Conditional.Operator.NONE, conditionalType, 213 lockControlTurnout.getSystemName(), true)); 214 215 actionList = new ArrayList<>(); 216 int option = Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE; 217 int type = Turnout.LOCKED; 218 if (mode == Route.ONCHANGE) { 219 option = Conditional.ACTION_OPTION_ON_CHANGE; 220 type = Route.TOGGLE; 221 } 222 for(int i=0;i<route.getNumOutputTurnouts();i++){ 223 Turnout ot = route.getOutputTurnout(i); 224 if ( ot != null ) { 225 actionList.add(new DefaultConditionalAction(option, Conditional.Action.LOCK_TURNOUT, 226 ot.getDisplayName(), type, "")); 227 } 228 } 229 if (mode != Route.ONCHANGE) { 230 // add non-toggle actions on 231 option = Conditional.ACTION_OPTION_ON_CHANGE_TO_FALSE; 232 type = Turnout.UNLOCKED; 233 for(int i=0;i<route.getNumOutputTurnouts();i++){ 234 Turnout ot = route.getOutputTurnout(i); 235 if ( ot != null ) { 236 actionList.add(new DefaultConditionalAction(option, Conditional.Action.LOCK_TURNOUT, 237 ot.getDisplayName(), type, "")); 238 } 239 } 240 } 241 242 // add new Conditionals for action on 'locks' 243 Conditional c = conditionalManager.createNewConditional(cSystemName, cUserName); 244 c.setStateVariables(variableList); 245 c.setLogicType(Conditional.AntecedentOperator.ALL_AND, ""); 246 c.setAction(actionList); 247 logix.addConditional(cSystemName, 0); 248 c.calculate(true, null); 249 } 250 } 251 252 private ArrayList<ConditionalVariable> getVetoVariables(Route route) { 253 ArrayList<ConditionalVariable> vetoList = new ArrayList<>(); 254 255 ConditionalVariable cVar = makeCtrlSensorVar(route.getRouteSensor(0),route.getRouteSensorMode(0), true, false); 256 if (cVar != null) { 257 vetoList.add(cVar); 258 } 259 cVar = makeCtrlSensorVar(route.getRouteSensor(1),route.getRouteSensorMode(1), true, false); 260 if (cVar != null) { 261 vetoList.add(cVar); 262 } 263 cVar = makeCtrlSensorVar(route.getRouteSensor(2),route.getRouteSensorMode(2), true, false); 264 if (cVar != null) { 265 vetoList.add(cVar); 266 } 267 cVar = makeCtrlTurnoutVar(route.getCtlTurnout(), route.getControlTurnoutState(), true, false); 268 if (cVar != null) { 269 vetoList.add(cVar); 270 } 271 return vetoList; 272 } 273 274 private ArrayList<ConditionalAction> getConditionalActions(Route route) { 275 ArrayList<ConditionalAction> actionList = new ArrayList<>(); 276 277 for(int i=0;i<route.getNumOutputSensors();i++){ 278 Sensor sens = route.getOutputSensor(i); 279 if ( sens != null ) { 280 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE, 281 Conditional.Action.SET_SENSOR, sens.getDisplayName(), 282 route.getOutputSensorState(i), "")); 283 } 284 } 285 for(int i=0;i<route.getNumOutputTurnouts();i++){ 286 Turnout to = route.getOutputTurnout(i); 287 if ( to != null ) { 288 actionList.add(new DefaultConditionalAction(Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE, 289 Conditional.Action.SET_TURNOUT, to.getDisplayName(), 290 route.getOutputTurnoutState(i), "")); 291 } 292 } 293 log.debug("sensor actions {} turnout actions {} resulting Action List size {}", 294 route.getNumOutputSensors(),route.getNumOutputTurnouts(),actionList.size()); 295 return actionList; 296 } 297 298 private boolean removeConditionals(String cSystemName, Logix logix) { 299 Conditional c = conditionalManager.getBySystemName(cSystemName); 300 if (c != null) { 301 logix.deleteConditional(cSystemName); 302 conditionalManager.deleteConditional(c); 303 return true; 304 } 305 return false; 306 } 307 308 /** 309 * Create a new sensor conditional. 310 * 311 * @param selectedSensor will be used to determine which sensor to make a conditional for 312 * @param sensorMode will be used to determine the mode for the conditional 313 * @param numConds number of existing route conditionals 314 * @param onChange ??? 315 * @param actionList actions to take in conditional 316 * @param vetoList conditionals that can veto an action 317 * @param logix Logix to add the conditional to 318 * @param prefix system prefix for conditional 319 * @param uName user name for conditional 320 * @return number of conditionals after the creation 321 * @throws IllegalArgumentException if "user input no good" 322 */ 323 private int makeSensorConditional(Sensor selectedSensor, int sensorMode, int numConds, 324 boolean onChange, ArrayList<ConditionalAction> actionList, ArrayList<ConditionalVariable> vetoList, 325 Logix logix, String prefix, String uName) { 326 327 int loop = numConds; 328 ConditionalVariable cVar = makeCtrlSensorVar(selectedSensor, sensorMode, false, onChange); 329 if (cVar != null) { 330 ArrayList<ConditionalVariable> varList = new ArrayList<>(); 331 varList.add(cVar); 332 for (ConditionalVariable conditionalVariable : vetoList) { 333 varList.add(cloneVariable(conditionalVariable)); 334 } 335 String cSystemName = prefix + loop + "T"; 336 String cUserName = selectedSensor.getDisplayName() + loop + "C " + uName; 337 Conditional c; 338 try { 339 c = conditionalManager.createNewConditional(cSystemName, cUserName); 340 } catch (Exception ex) { 341 // throw without creating any 342 throw new IllegalArgumentException("user input no good"); 343 } 344 c.setStateVariables(varList); 345 int option = onChange ? Conditional.ACTION_OPTION_ON_CHANGE : Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE; 346 c.setAction(cloneActionList(actionList, option)); 347 c.setLogicType(Conditional.AntecedentOperator.ALL_AND, ""); 348 logix.addConditional(cSystemName, 0); 349 c.calculate(true, null); 350 loop++; 351 } 352 return loop; 353 } 354 355 /** 356 * Create a new turnout conditional. 357 * 358 * @param turnout will be used to determine which turnout to make a conditional for 359 * @param state will be used to determine the mode for the conditional 360 * @param numConds number of existing route conditionals 361 * @param onChange ??? 362 * @param actionList actions to take in conditional 363 * @param vetoList conditionals that can veto an action 364 * @param logix Logix to add the conditional to 365 * @param prefix system prefix for conditional 366 * @param uName user name for conditional 367 * @return number of conditionals after the creation 368 * @throws IllegalArgumentException if "user input no good" 369 */ 370 private int makeTurnoutConditional(Turnout turnout, int state, int numConds, boolean onChange, 371 ArrayList<ConditionalAction> actionList, ArrayList<ConditionalVariable> vetoList, Logix logix, 372 String prefix, String uName) { 373 374 int loop = numConds; 375 ConditionalVariable cVar = makeCtrlTurnoutVar(turnout,state, false, onChange); 376 if (cVar != null) { 377 ArrayList<ConditionalVariable> varList = new ArrayList<>(); 378 varList.add(cVar); 379 for (ConditionalVariable conditionalVariable : vetoList) { 380 varList.add(cloneVariable(conditionalVariable)); 381 } 382 String cSystemName = prefix + loop + "T"; 383 String cUserName = turnout.getDisplayName() + loop + "C " + uName; 384 Conditional c; 385 try { 386 c = conditionalManager.createNewConditional(cSystemName, cUserName); 387 } catch (Exception ex) { 388 // throw without creating any 389 throw new IllegalArgumentException("user input no good"); 390 } 391 c.setStateVariables(varList); 392 int option = onChange ? Conditional.ACTION_OPTION_ON_CHANGE : Conditional.ACTION_OPTION_ON_CHANGE_TO_TRUE; 393 c.setAction(cloneActionList(actionList, option)); 394 c.setLogicType(Conditional.AntecedentOperator.ALL_AND, ""); 395 logix.addConditional(cSystemName, 0); 396 c.calculate(true, null); 397 loop++; 398 } 399 return loop; 400 } 401 402 @CheckForNull 403 private ConditionalVariable makeCtrlTurnoutVar(@CheckForNull Turnout turnout, 404 int mode, boolean makeVeto, boolean onChange) { 405 406 if (turnout == null) { 407 return null; 408 } 409 String devName = turnout.getDisplayName(); 410 Conditional.Operator oper = Conditional.Operator.AND; 411 Conditional.Type type; 412 boolean negated = false; 413 boolean trigger = true; 414 switch (mode) { 415 case Route.ONCLOSED: // route fires if turnout goes closed 416 if (makeVeto || onChange) { 417 return null; 418 } 419 type = Conditional.Type.TURNOUT_CLOSED; 420 break; 421 case Route.ONTHROWN: // route fires if turnout goes thrown 422 if (makeVeto || onChange) { 423 return null; 424 } 425 type = Conditional.Type.TURNOUT_THROWN; 426 break; 427 case Route.ONCHANGE: // route fires if turnout goes active or inactive 428 if (makeVeto || !onChange) { 429 return null; 430 } 431 type = Conditional.Type.TURNOUT_CLOSED; 432 break; 433 case Route.VETOCLOSED: // turnout must be closed for route to fire 434 if (!makeVeto || onChange) { 435 return null; 436 } 437 type = Conditional.Type.TURNOUT_CLOSED; 438 trigger = false; 439 negated = true; 440 break; 441 case Route.VETOTHROWN: // turnout must be thrown for route to fire 442 if (!makeVeto || onChange) { 443 return null; 444 } 445 type = Conditional.Type.TURNOUT_THROWN; 446 trigger = false; 447 negated = true; 448 break; 449 default: 450 log.error("Control Turnout {} has bad mode= {}", devName, mode); 451 return null; 452 } 453 return new ConditionalVariable(negated, oper, type, devName, trigger); 454 } 455 456 457 private ConditionalVariable cloneVariable(@Nonnull ConditionalVariable v) { 458 return new ConditionalVariable(v.isNegated(), v.getOpern(), v.getType(), v.getName(), v.doTriggerActions()); 459 } 460 461 private ArrayList<ConditionalAction> cloneActionList(@Nonnull ArrayList<ConditionalAction> actionList, int option) { 462 ArrayList<ConditionalAction> list = new ArrayList<>(); 463 for (ConditionalAction action : actionList) { 464 ConditionalAction clone = new DefaultConditionalAction(); 465 clone.setType(action.getType()); 466 clone.setOption(option); 467 clone.setDeviceName(action.getDeviceName()); 468 clone.setActionData(action.getActionData()); 469 clone.setActionString(action.getActionString()); 470 list.add(clone); 471 } 472 return list; 473 } 474 475 @CheckForNull 476 private ConditionalVariable makeCtrlSensorVar(@CheckForNull Sensor selectedSensor, 477 int mode, boolean makeVeto, boolean onChange) { 478 479 if (selectedSensor == null) { 480 return null; 481 } 482 String devName = selectedSensor.getDisplayName(); 483 Conditional.Operator oper = Conditional.Operator.AND; 484 boolean trigger = true; 485 boolean negated = false; 486 Conditional.Type type; 487 switch (mode) { 488 case Route.ONACTIVE: // route fires if sensor goes active 489 if (makeVeto || onChange) { 490 return null; 491 } 492 type = Conditional.Type.SENSOR_ACTIVE; 493 break; 494 case Route.ONINACTIVE: // route fires if sensor goes inactive 495 if (makeVeto || onChange) { 496 return null; 497 } 498 type = Conditional.Type.SENSOR_INACTIVE; 499 break; 500 case Route.ONCHANGE: // route fires if sensor goes active or inactive 501 if (makeVeto || !onChange) { 502 return null; 503 } 504 type = Conditional.Type.SENSOR_ACTIVE; 505 break; 506 case Route.VETOACTIVE: // sensor must be active for route to fire 507 if (!makeVeto || onChange) { 508 return null; 509 } 510 type = Conditional.Type.SENSOR_ACTIVE; 511 negated = true; 512 trigger = false; 513 break; 514 case Route.VETOINACTIVE: 515 if (!makeVeto || onChange) { 516 return null; 517 } 518 type = Conditional.Type.SENSOR_INACTIVE; 519 negated = true; 520 trigger = false; 521 break; 522 default: 523 log.error("Control Sensor {} has bad mode= {}", devName, mode); 524 return null; 525 } 526 return new ConditionalVariable(negated, oper, type, devName, trigger); 527 } 528 529 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RouteExportToLogix.class); 530}