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