001package jmri.jmrit.logixng.actions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006import java.util.concurrent.atomic.AtomicInteger; 007 008import jmri.*; 009import jmri.jmrit.logixng.*; 010import jmri.jmrit.logixng.implementation.DefaultSymbolTable; 011import jmri.jmrit.logixng.util.LogixNG_SelectComboBox; 012import jmri.jmrit.logixng.util.LogixNG_SelectEnum; 013import jmri.jmrit.logixng.util.LogixNG_SelectInteger; 014 015/** 016 * Program a CV on main. 017 * 018 * @author Daniel Bergqvist Copyright 2024 019 */ 020public class ProgramOnMain extends AbstractDigitalAction 021 implements FemaleSocketListener, PropertyChangeListener { 022 023 private static final ResourceBundle rbx = 024 ResourceBundle.getBundle("jmri.jmrit.logixng.implementation.ImplementationBundle"); 025 026 private String _executeSocketSystemName; 027 private final FemaleDigitalActionSocket _executeSocket; 028 private SystemConnectionMemo _memo; 029 private AddressedProgrammerManager _programmerManager; 030 private ThrottleManager _throttleManager; 031 private final LogixNG_SelectComboBox _selectProgrammingMode; 032 private final LogixNG_SelectEnum<LongOrShortAddress> _selectLongOrShortAddress = 033 new LogixNG_SelectEnum<>(this, LongOrShortAddress.values(), LongOrShortAddress.Auto, this); 034 private final LogixNG_SelectInteger _selectAddress = new LogixNG_SelectInteger(this, this); 035 private final LogixNG_SelectInteger _selectCV = new LogixNG_SelectInteger(this, this); 036 private final LogixNG_SelectInteger _selectValue = new LogixNG_SelectInteger(this, this); 037 private String _localVariableForStatus = ""; 038 private boolean _wait = true; 039 private final InternalFemaleSocket _internalSocket = new InternalFemaleSocket(); 040 041 public ProgramOnMain(String sys, String user) { 042 super(sys, user); 043 044 // The array is updated with correct values when setMemo() is called 045 POMItem[] modes = {new POMItem("")}; 046 _selectProgrammingMode = new LogixNG_SelectComboBox(this, modes, modes[0], this); 047 048 // Set the _programmerManager and _throttleManager variables 049 setMemo(null); 050 051 _executeSocket = InstanceManager.getDefault(DigitalActionManager.class) 052 .createFemaleSocket(this, this, Bundle.getMessage("ProgramOnMain_Socket")); 053 } 054 055 @Override 056 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 057 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 058 String sysName = systemNames.get(getSystemName()); 059 String userName = userNames.get(getSystemName()); 060 if (sysName == null) sysName = manager.getAutoSystemName(); 061 ProgramOnMain copy = new ProgramOnMain(sysName, userName); 062 copy.setComment(getComment()); 063 copy.setMemo(_memo); 064 _selectProgrammingMode.copy(copy._selectProgrammingMode); 065 _selectLongOrShortAddress.copy(copy._selectLongOrShortAddress); 066 _selectAddress.copy(copy._selectAddress); 067 _selectCV.copy(copy._selectCV); 068 _selectValue.copy(copy._selectValue); 069 copy._wait = _wait; 070 copy.setLocalVariableForStatus(_localVariableForStatus); 071 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 072 } 073 074 public final LogixNG_SelectComboBox getSelectProgrammingMode() { 075 return _selectProgrammingMode; 076 } 077 078 public final LogixNG_SelectInteger getSelectAddress() { 079 return _selectAddress; 080 } 081 082 public LogixNG_SelectEnum<LongOrShortAddress> getSelectLongOrShortAddress() { 083 return _selectLongOrShortAddress; 084 } 085 086 public final LogixNG_SelectInteger getSelectCV() { 087 return _selectCV; 088 } 089 090 public final LogixNG_SelectInteger getSelectValue() { 091 return _selectValue; 092 } 093 094 public void setLocalVariableForStatus(String localVariable) { 095 _localVariableForStatus = localVariable; 096 } 097 098 public String getLocalVariableForStatus() { 099 return _localVariableForStatus; 100 } 101 102 public void setWait(boolean wait) { 103 _wait = wait; 104 } 105 106 public boolean getWait() { 107 return _wait; 108 } 109 110 public final void setMemo(SystemConnectionMemo memo) { 111 assertListenersAreNotRegistered(log, "setMemo"); 112 113 _memo = memo; 114 if (_memo != null) { 115 _programmerManager = _memo.get(AddressedProgrammerManager.class); 116 _throttleManager = _memo.get(ThrottleManager.class); 117 if (_throttleManager == null) { 118 throw new IllegalArgumentException("Memo "+memo.getUserName()+" doesn't have a ThrottleManager"); 119 } 120 121 // LocoNet memo doesn't have a programmer during tests 122 if (_programmerManager == null) { 123 _programmerManager = InstanceManager.getDefault(AddressedProgrammerManager.class); 124 } 125 } else { 126 _programmerManager = InstanceManager.getDefault(AddressedProgrammerManager.class); 127 _throttleManager = InstanceManager.getDefault(ThrottleManager.class); 128 } 129 130 List<POMItem> modeList = new ArrayList<>(); 131 for (ProgrammingMode mode : _programmerManager.getDefaultModes()) { 132 log.debug("Available programming mode: {}", mode); 133 modeList.add(new POMItem(mode.getStandardName())); 134 } 135 136 // Add OPSBYTEMODE in case we don't have any mode, 137 // for example if we are running a simulator. 138 if (modeList.isEmpty()) { 139 modeList.add(new POMItem(ProgrammingMode.OPSBYTEMODE.getStandardName())); 140 } 141 142 POMItem[] modes = modeList.toArray(POMItem[]::new); 143 _selectProgrammingMode.setValues(modes); 144 } 145 146 public final SystemConnectionMemo getMemo() { 147 return _memo; 148 } 149 150 /** {@inheritDoc} */ 151 @Override 152 public LogixNG_Category getCategory() { 153 return LogixNG_Category.ITEM; 154 } 155 156 private void doProgrammingOnMain(ConditionalNG conditionalNG, 157 DefaultSymbolTable newSymbolTable, ProgrammingMode progMode, 158 int address, LongOrShortAddress longOrShort, int cv, int value, boolean wait) 159 throws JmriException { 160 try { 161 boolean longAddress; 162 163 switch (longOrShort) { 164 case Short: 165 longAddress = false; 166 break; 167 168 case Long: 169 longAddress = true; 170 break; 171 172 case Auto: 173 longAddress = !_throttleManager.canBeShortAddress(address); 174 break; 175 176 default: 177 throw new IllegalArgumentException("longOrShort has unknown value"); 178 } 179 180 AddressedProgrammer programmer = _programmerManager.getAddressedProgrammer( 181 new DccLocoAddress(address, longAddress)); 182 183 if (programmer != null) { 184 programmer.setMode(progMode); 185 if (!progMode.equals(programmer.getMode())) { 186 throw new IllegalArgumentException("The addressed programmer doesn't support mode " + progMode.getStandardName()); 187 } 188 AtomicInteger result = new AtomicInteger(-1); 189 programmer.writeCV("" + cv, value, (int value1, int status) -> { 190 result.set(status); 191 192 log.debug("Result of programming cv {} to value {} for address {}: {}", cv, value, address, status); 193 194 synchronized(ProgramOnMain.this) { 195 _internalSocket.conditionalNG = conditionalNG; 196 _internalSocket.newSymbolTable = newSymbolTable; 197 _internalSocket.status = status; 198 conditionalNG.execute(_internalSocket); 199 } 200 }); 201 202 if (wait) { 203 try { 204 while (result.get() == -1) { 205 Thread.sleep(10); 206 } 207 } catch (InterruptedException e) { 208 log.warn("Waiting for programmer to complete was aborted"); 209 } 210 } 211 212 } else { 213 throw new IllegalArgumentException("An addressed programmer isn't available for address " + address); 214 } 215 } catch (ProgrammerException e) { 216 throw new JmriException(e); 217 } 218 } 219 220 /** {@inheritDoc} */ 221 @Override 222 public void execute() throws JmriException { 223 ConditionalNG conditionalNG = this.getConditionalNG(); 224 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG.getSymbolTable()); 225 226 String progModeStr = _selectProgrammingMode.evaluateValue(conditionalNG).getKey(); 227 228 ProgrammingMode progMode = null; 229 for (ProgrammingMode mode : _programmerManager.getDefaultModes()) { 230 if (mode.getStandardName().equals(progModeStr)) { 231 progMode = mode; 232 } 233 } 234 235 if (progMode == null) { 236 throw new IllegalArgumentException("Programming mode "+progModeStr+" is not found"); 237 } 238 239 int address = _selectAddress.evaluateValue(conditionalNG); 240 LongOrShortAddress longOrShort = _selectLongOrShortAddress.evaluateEnum(conditionalNG); 241 int cv = _selectCV.evaluateValue(conditionalNG); 242 int value = _selectValue.evaluateValue(conditionalNG); 243 244 doProgrammingOnMain(conditionalNG, newSymbolTable, progMode, address, longOrShort, cv, value, _wait); 245 } 246 247 @Override 248 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 249 switch (index) { 250 case 0: 251 return _executeSocket; 252 253 default: 254 throw new IllegalArgumentException( 255 String.format("index has invalid value: %d", index)); 256 } 257 } 258 259 @Override 260 public int getChildCount() { 261 return 1; 262 } 263 264 @Override 265 public void connected(FemaleSocket socket) { 266 if (socket == _executeSocket) { 267 _executeSocketSystemName = socket.getConnectedSocket().getSystemName(); 268 } else { 269 throw new IllegalArgumentException("unkown socket"); 270 } 271 } 272 273 @Override 274 public void disconnected(FemaleSocket socket) { 275 if (socket == _executeSocket) { 276 _executeSocketSystemName = null; 277 } else { 278 throw new IllegalArgumentException("unkown socket"); 279 } 280 } 281 282 @Override 283 public String getShortDescription(Locale locale) { 284 return Bundle.getMessage(locale, "ProgramOnMain_Short"); 285 } 286 287 @Override 288 public String getLongDescription(Locale locale) { 289 if (_memo != null) { 290 return Bundle.getMessage(locale, "ProgramOnMain_LongConnection", 291 _selectLongOrShortAddress.getDescription(locale), 292 _selectAddress.getDescription(locale, false), 293 _selectCV.getDescription(locale, false), 294 _selectValue.getDescription(locale, false), 295 _selectProgrammingMode.getDescription(locale), 296 _memo.getUserName()); 297 } else { 298 return Bundle.getMessage(locale, "ProgramOnMain_Long", 299 _selectLongOrShortAddress.getDescription(locale), 300 _selectAddress.getDescription(locale, false), 301 _selectCV.getDescription(locale, false), 302 _selectValue.getDescription(locale, false), 303 _selectProgrammingMode.getDescription(locale)); 304 } 305 } 306 307 public FemaleDigitalActionSocket getExecuteSocket() { 308 return _executeSocket; 309 } 310 311 public String getExecuteSocketSystemName() { 312 return _executeSocketSystemName; 313 } 314 315 public void setExecuteSocketSystemName(String systemName) { 316 _executeSocketSystemName = systemName; 317 } 318 319 /** {@inheritDoc} */ 320 @Override 321 public void setup() { 322 try { 323 if (!_executeSocket.isConnected() 324 || !_executeSocket.getConnectedSocket().getSystemName() 325 .equals(_executeSocketSystemName)) { 326 327 String socketSystemName = _executeSocketSystemName; 328 329 _executeSocket.disconnect(); 330 331 if (socketSystemName != null) { 332 MaleSocket maleSocket = 333 InstanceManager.getDefault(DigitalActionManager.class) 334 .getBySystemName(socketSystemName); 335 if (maleSocket != null) { 336 _executeSocket.connect(maleSocket); 337 maleSocket.setup(); 338 } else { 339 log.error("cannot load digital action {}", socketSystemName); 340 } 341 } 342 } else { 343 _executeSocket.getConnectedSocket().setup(); 344 } 345 } catch (SocketAlreadyConnectedException ex) { 346 // This shouldn't happen and is a runtime error if it does. 347 throw new RuntimeException("socket is already connected"); 348 } 349 } 350 351 /** {@inheritDoc} */ 352 @Override 353 public void propertyChange(PropertyChangeEvent evt) { 354 getConditionalNG().execute(); 355 } 356 357 358 private class InternalFemaleSocket extends jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket { 359 360 private ConditionalNG conditionalNG; 361 private SymbolTable newSymbolTable; 362 private int status; 363 364 public InternalFemaleSocket() { 365 super(null, new FemaleSocketListener(){ 366 @Override 367 public void connected(FemaleSocket socket) { 368 // Do nothing 369 } 370 371 @Override 372 public void disconnected(FemaleSocket socket) { 373 // Do nothing 374 } 375 }, "A"); 376 } 377 378 @Override 379 public void execute() throws JmriException { 380 if (_executeSocket != null) { 381 MaleSocket maleSocket = (MaleSocket)ProgramOnMain.this.getParent(); 382 try { 383 SymbolTable oldSymbolTable = conditionalNG.getSymbolTable(); 384 conditionalNG.setSymbolTable(newSymbolTable); 385 if (!_localVariableForStatus.isEmpty()) { 386 newSymbolTable.setValue(_localVariableForStatus, status); 387 } 388 _executeSocket.execute(); 389 conditionalNG.setSymbolTable(oldSymbolTable); 390 } catch (JmriException e) { 391 if (e.getErrors() != null) { 392 maleSocket.handleError(ProgramOnMain.this, rbx.getString("ExceptionExecuteMulti"), e.getErrors(), e, log); 393 } else { 394 maleSocket.handleError(ProgramOnMain.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 395 } 396 } catch (RuntimeException e) { 397 maleSocket.handleError(ProgramOnMain.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 398 } 399 } 400 } 401 402 } 403 404 405 public enum LongOrShortAddress { 406 Short(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Short")), 407 Long(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Long")), 408 Auto(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Auto")); 409 410 private final String _text; 411 412 private LongOrShortAddress(String text) { 413 this._text = text; 414 } 415 416 @Override 417 public String toString() { 418 return _text; 419 } 420 421 } 422 423 424 private static class POMItem implements LogixNG_SelectComboBox.Item { 425 426 private final String _value; 427 428 public POMItem(String value) { 429 this._value = value; 430 } 431 432 @Override 433 public String getKey() { 434 return _value; 435 } 436 437 @Override 438 public String toString() { 439 return _value; 440 } 441 442 } 443 444 445 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProgramOnMain.class); 446 447}