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