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 String[] modes = {""}; 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<String> modeList = new ArrayList<>(); 131 for (ProgrammingMode mode : _programmerManager.getDefaultModes()) { 132 log.debug("Available programming mode: {}", mode); 133 modeList.add(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(ProgrammingMode.OPSBYTEMODE.getStandardName()); 140 } 141 142 String[] modes = modeList.toArray(String[]::new); 143 _selectProgrammingMode.setValues(modes); 144 } 145 146 public final SystemConnectionMemo getMemo() { 147 return _memo; 148 } 149 150 /** {@inheritDoc} */ 151 @Override 152 public Category getCategory() { 153 return 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); 227 ProgrammingMode progMode = new ProgrammingMode(progModeStr); 228 229 int address = _selectAddress.evaluateValue(conditionalNG); 230 LongOrShortAddress longOrShort = _selectLongOrShortAddress.evaluateEnum(conditionalNG); 231 int cv = _selectCV.evaluateValue(conditionalNG); 232 int value = _selectValue.evaluateValue(conditionalNG); 233 234 doProgrammingOnMain(conditionalNG, newSymbolTable, progMode, address, longOrShort, cv, value, _wait); 235 } 236 237 @Override 238 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 239 switch (index) { 240 case 0: 241 return _executeSocket; 242 243 default: 244 throw new IllegalArgumentException( 245 String.format("index has invalid value: %d", index)); 246 } 247 } 248 249 @Override 250 public int getChildCount() { 251 return 1; 252 } 253 254 @Override 255 public void connected(FemaleSocket socket) { 256 if (socket == _executeSocket) { 257 _executeSocketSystemName = socket.getConnectedSocket().getSystemName(); 258 } else { 259 throw new IllegalArgumentException("unkown socket"); 260 } 261 } 262 263 @Override 264 public void disconnected(FemaleSocket socket) { 265 if (socket == _executeSocket) { 266 _executeSocketSystemName = null; 267 } else { 268 throw new IllegalArgumentException("unkown socket"); 269 } 270 } 271 272 @Override 273 public String getShortDescription(Locale locale) { 274 return Bundle.getMessage(locale, "ProgramOnMain_Short"); 275 } 276 277 @Override 278 public String getLongDescription(Locale locale) { 279 if (_memo != null) { 280 return Bundle.getMessage(locale, "ProgramOnMain_LongConnection", 281 _selectLongOrShortAddress.getDescription(locale), 282 _selectAddress.getDescription(locale, false), 283 _selectCV.getDescription(locale, false), 284 _selectValue.getDescription(locale, false), 285 _selectProgrammingMode.getDescription(locale), 286 _memo.getUserName()); 287 } else { 288 return Bundle.getMessage(locale, "ProgramOnMain_Long", 289 _selectLongOrShortAddress.getDescription(locale), 290 _selectAddress.getDescription(locale, false), 291 _selectCV.getDescription(locale, false), 292 _selectValue.getDescription(locale, false), 293 _selectProgrammingMode.getDescription(locale)); 294 } 295 } 296 297 public FemaleDigitalActionSocket getExecuteSocket() { 298 return _executeSocket; 299 } 300 301 public String getExecuteSocketSystemName() { 302 return _executeSocketSystemName; 303 } 304 305 public void setExecuteSocketSystemName(String systemName) { 306 _executeSocketSystemName = systemName; 307 } 308 309 /** {@inheritDoc} */ 310 @Override 311 public void setup() { 312 try { 313 if (!_executeSocket.isConnected() 314 || !_executeSocket.getConnectedSocket().getSystemName() 315 .equals(_executeSocketSystemName)) { 316 317 String socketSystemName = _executeSocketSystemName; 318 319 _executeSocket.disconnect(); 320 321 if (socketSystemName != null) { 322 MaleSocket maleSocket = 323 InstanceManager.getDefault(DigitalActionManager.class) 324 .getBySystemName(socketSystemName); 325 if (maleSocket != null) { 326 _executeSocket.connect(maleSocket); 327 maleSocket.setup(); 328 } else { 329 log.error("cannot load digital action {}", socketSystemName); 330 } 331 } 332 } else { 333 _executeSocket.getConnectedSocket().setup(); 334 } 335 } catch (SocketAlreadyConnectedException ex) { 336 // This shouldn't happen and is a runtime error if it does. 337 throw new RuntimeException("socket is already connected"); 338 } 339 } 340 341 /** {@inheritDoc} */ 342 @Override 343 public void propertyChange(PropertyChangeEvent evt) { 344 getConditionalNG().execute(); 345 } 346 347 348 private class InternalFemaleSocket extends jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket { 349 350 private ConditionalNG conditionalNG; 351 private SymbolTable newSymbolTable; 352 private int status; 353 354 public InternalFemaleSocket() { 355 super(null, new FemaleSocketListener(){ 356 @Override 357 public void connected(FemaleSocket socket) { 358 // Do nothing 359 } 360 361 @Override 362 public void disconnected(FemaleSocket socket) { 363 // Do nothing 364 } 365 }, "A"); 366 } 367 368 @Override 369 public void execute() throws JmriException { 370 if (_executeSocket != null) { 371 MaleSocket maleSocket = (MaleSocket)ProgramOnMain.this.getParent(); 372 try { 373 SymbolTable oldSymbolTable = conditionalNG.getSymbolTable(); 374 conditionalNG.setSymbolTable(newSymbolTable); 375 if (!_localVariableForStatus.isEmpty()) { 376 newSymbolTable.setValue(_localVariableForStatus, status); 377 } 378 _executeSocket.execute(); 379 conditionalNG.setSymbolTable(oldSymbolTable); 380 } catch (JmriException e) { 381 if (e.getErrors() != null) { 382 maleSocket.handleError(ProgramOnMain.this, rbx.getString("ExceptionExecuteMulti"), e.getErrors(), e, log); 383 } else { 384 maleSocket.handleError(ProgramOnMain.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 385 } 386 } catch (RuntimeException e) { 387 maleSocket.handleError(ProgramOnMain.this, Bundle.formatMessage(rbx.getString("ExceptionExecuteAction"), e.getLocalizedMessage()), e, log); 388 } 389 } 390 } 391 392 } 393 394 395 public enum LongOrShortAddress { 396 Short(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Short")), 397 Long(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Long")), 398 Auto(Bundle.getMessage("ProgramOnMain_LongOrShortAddress_Auto")); 399 400 private final String _text; 401 402 private LongOrShortAddress(String text) { 403 this._text = text; 404 } 405 406 @Override 407 public String toString() { 408 return _text; 409 } 410 411 } 412 413 414 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProgramOnMain.class); 415 416}