001package jmri.jmrix.loconet; 002 003import java.util.List; 004 005import javax.annotation.concurrent.GuardedBy; 006 007import jmri.Programmer; 008import jmri.ProgrammingMode; 009import jmri.beans.PropertyChangeSupport; 010import jmri.jmrit.roster.Roster; 011import jmri.jmrit.roster.RosterEntry; 012 013import jmri.jmrix.ProgrammingTool; 014import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents; 015import jmri.jmrix.loconet.uhlenbrock.LncvDevice; 016import jmri.jmrix.loconet.uhlenbrock.LncvDevices; 017import jmri.managers.DefaultProgrammerManager; 018import jmri.util.swing.JmriJOptionPane; 019 020/** 021 * LocoNet LNCV Devices Manager 022 * 023 * A centralized resource to help identify LocoNet "LNCV Format" 024 * devices and "manage" them. 025 * 026 * Supports the following features: 027 * - LNCV "discovery" process supported via PROG_START_ALL call 028 * - LNCV Device "destination address" change supported by writing a new value to LNCV 0 (close session next) 029 * - LNCV Device "reconfigure/reset" not supported/documented 030 * - identification of devices with conflicting "destination address"es (warning before program start) 031 * - identification of a matching JMRI "decoder definition" for each discovered 032 * device, if an appropriate definition exists (only 1 value is matched, checks for LNCV protocol support) 033 * - identification of matching JMRI "roster entry" which matches each 034 * discovered device, if an appropriate roster entry exists 035 * - ability to open a symbolic programmer for a given discovered device, if 036 * an appropriate roster entry exists 037 * 038 * @author B. Milhaupt Copyright (c) 2020 039 * @author Egbert Broerse (c) 2021 040 */ 041 042public class LncvDevicesManager extends PropertyChangeSupport 043 implements LocoNetListener { 044 private final LocoNetSystemConnectionMemo memo; 045 @GuardedBy("this") 046 private final LncvDevices lncvDevices; 047 048 public LncvDevicesManager(LocoNetSystemConnectionMemo memo) { 049 this.memo = memo; 050 if (memo.getLnTrafficController() != null) { 051 memo.getLnTrafficController().addLocoNetListener(~0, this); 052 } else { 053 log.error("No LocoNet connection available, this tool cannot function"); // NOI18N 054 } 055 synchronized (this) { 056 lncvDevices = new LncvDevices(); 057 } 058 } 059 060 public synchronized LncvDevices getDeviceList() { 061 return lncvDevices; 062 } 063 064 public synchronized int getDeviceCount() { 065 return lncvDevices.size(); 066 } 067 068 public void clearDevicesList() { 069 synchronized (this) { 070 lncvDevices.removeAllDevices(); 071 } 072 jmri.util.ThreadingUtil.runOnLayoutEventually( ()-> firePropertyChange("DeviceListChanged", true, false)); 073 } 074 075 /** 076 * Extract module information from LNCV READREPLY/READREPLY2 message, 077 * if not already in the lncvDevices list, try to find a matching decoder definition (by article number) 078 * and add it. Skip if already in the list. 079 * 080 * @param m The received LocoNet message. Note that this same object may 081 * be presented to multiple users. It should not be modified 082 * here. 083 */ 084 @Override 085 public void message(LocoNetMessage m) { 086 if (LncvMessageContents.isSupportedLncvMessage(m)) { 087 if ((LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY) || 088 //Updated 2022 to also accept undocumented Digikeijs DR5088 reply format LNCV_READ_REPLY2 089 (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY2)) { 090 // it's an LNCV ReadReply message, decode contents: 091 LncvMessageContents contents = new LncvMessageContents(m); 092 int art = contents.getLncvArticleNum(); 093 int addr = -1; 094 int cv = contents.getCvNum(); 095 int val = contents.getCvValue(); 096 log.debug("LncvDevicesManager got read reply: art:{}, address:{} cv:{} val:{}", art, addr, cv, val); 097 if (cv == 0) { // trust last used address 098 addr = val; // if cvNum = 0, this is the LNCV module address 099 log.debug("LNCV read reply: device address {} of LNCV returns {}", addr, val); 100 101 synchronized (this) { 102 if (lncvDevices.addDevice(new LncvDevice(art, addr, cv, val, "", "", -1))) { 103 log.debug("new LncvDevice added to table"); 104 // Annotate the discovered device LNCV data based on address 105 for (int i = 0; i < lncvDevices.size(); ++i) { 106 LncvDevice dev = lncvDevices.getDevice(i); 107 if ((dev.getProductID() == art) && (dev.getDestAddr() == addr)) { 108 // need to find a corresponding roster entry? 109 if (dev.getRosterName() != null && dev.getRosterName().length() == 0) { 110 // Yes. Try to find a roster entry which matches the device characteristics 111 log.debug("Looking for prodID {}/adr {} in Roster", dev.getProductID(), dev.getDestAddr()); 112 List<RosterEntry> l = Roster.getDefault().matchingList(Integer.toString(dev.getDestAddr()), Integer.toString(dev.getProductID())); 113 log.debug("LncvDeviceManager found {} matches in Roster", l.size()); 114 if (l.size() == 0) { 115 log.debug("No corresponding roster entry found"); 116 } else if (l.size() == 1) { 117 log.debug("Matching roster entry found"); 118 dev.setRosterEntry(l.get(0)); // link this device to the entry 119 } else { 120 JmriJOptionPane.showMessageDialog(null, 121 Bundle.getMessage("WarnMultipleLncvModsFound", art, addr, l.size()), 122 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 123 log.info("Found multiple matching roster entries. " + "Cannot associate any one to this device."); 124 } 125 } 126 // notify listeners of pertinent change to device list 127 firePropertyChange("DeviceListChanged", true, false); 128 } 129 } 130 } else { 131 log.debug("LNCV device was already in list"); 132 } 133 } 134 } else { 135 log.debug("LNCV device check skipped as value not CV0/module address"); 136 } 137 } else { 138 log.debug("LNCV message not a READ REPLY [{}]", m); 139 } 140 } else { 141 log.debug("LNCV message not recognized"); 142 } 143 } 144 145 public synchronized LncvDevice getDevice(int art, int addr) { 146 for (int i = 0; i < lncvDevices.size(); ++ i) { 147 LncvDevice dev = lncvDevices.getDevice(i); 148 if ((dev.getProductID() == art) && (dev.getDestAddr() == addr)) { 149 return dev; 150 } 151 } 152 return null; 153 } 154 155 public ProgrammingResult prepareForSymbolicProgrammer(LncvDevice dev, ProgrammingTool t) { 156 synchronized(this) { 157 if (lncvDevices.isDeviceExistant(dev) < 0) { 158 return ProgrammingResult.FAIL_NO_SUCH_DEVICE; 159 } 160 int destAddr = dev.getDestAddr(); 161 if (destAddr == 0) { 162 return ProgrammingResult.FAIL_DESTINATION_ADDRESS_IS_ZERO; 163 } 164 int deviceCount = 0; 165 for (LncvDevice d : lncvDevices.getDevices()) { 166 if (destAddr == d.getDestAddr()) { 167 deviceCount++; 168 } 169 } 170 log.debug("prepareForSymbolicProgrammer found {} matches", deviceCount); 171 if (deviceCount > 1) { 172 return ProgrammingResult.FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS; 173 } 174 } 175 176 if ((dev.getRosterName() == null) || (dev.getRosterName().length() == 0)) { 177 return ProgrammingResult.FAIL_NO_MATCHING_ROSTER_ENTRY; 178 } 179 180 DefaultProgrammerManager pm = memo.getProgrammerManager(); 181 if (pm == null) { 182 return ProgrammingResult.FAIL_NO_APPROPRIATE_PROGRAMMER; 183 } 184 Programmer p = pm.getAddressedProgrammer(false, dev.getDestAddr()); 185 if (p == null) { 186 return ProgrammingResult.FAIL_NO_ADDRESSED_PROGRAMMER; 187 } 188 189 //if (p.getClass() != ProgDebugger.class) { 190 // ProgDebugger is used for LocoNet HexFile Sim, uncommenting above line allows testing of LNCV Tool 191 if (!p.getSupportedModes().contains(LnProgrammerManager.LOCONETLNCVMODE)) { 192 return ProgrammingResult.FAIL_NO_LNCV_PROGRAMMER; 193 } 194 p.setMode(LnProgrammerManager.LOCONETLNCVMODE); 195 ProgrammingMode prgMode = p.getMode(); 196 if (!prgMode.equals(LnProgrammerManager.LOCONETLNCVMODE)) { 197 return ProgrammingResult.FAIL_NO_LNCV_PROGRAMMER; 198 } 199 //} 200 RosterEntry re = Roster.getDefault().entryFromTitle(dev.getRosterName()); 201 String name = re.getId(); 202 203 t.openPaneOpsProgFrame(re, name, "programmers/Comprehensive.xml", p); // NOI18N 204 return ProgrammingResult.SUCCESS_PROGRAMMER_OPENED; 205 } 206 207 public enum ProgrammingResult { 208 SUCCESS_PROGRAMMER_OPENED, 209 FAIL_NO_SUCH_DEVICE, 210 FAIL_NO_APPROPRIATE_PROGRAMMER, 211 FAIL_NO_MATCHING_ROSTER_ENTRY, 212 FAIL_DESTINATION_ADDRESS_IS_ZERO, 213 FAIL_MULTIPLE_DEVICES_SAME_DESTINATION_ADDRESS, 214 FAIL_NO_ADDRESSED_PROGRAMMER, 215 FAIL_NO_LNCV_PROGRAMMER 216 } 217 218 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LncvDevicesManager.class); 219 220}