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