001package jmri.jmrix.can.cbus; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import javax.annotation.Nonnull; 007 008import jmri.*; 009import jmri.jmrix.can.CanListener; 010import jmri.jmrix.can.CanMessage; 011import jmri.jmrix.can.CanMutableFrame; 012import jmri.jmrix.can.CanReply; 013import jmri.jmrix.can.CanSystemConnectionMemo; 014import jmri.managers.AbstractReporterManager; 015 016/** 017 * Implement ReporterManager for CAN CBUS systems. 018 * <p> 019 * System names are "MRnnnnn", where M is the user-configurable system getSystemPrefix(), 020 * nnnnn is the reporter number without padding. 021 * <p> 022 * CBUS Reporters are NOT automatically created. 023 * 024 * @author Mark Riddoch Copyright (C) 2015 025 * @author Steve Young Copyright (C) 2019 026 */ 027public class CbusReporterManager extends AbstractReporterManager implements CanListener { 028 029 public CbusReporterManager(CanSystemConnectionMemo memo) { 030 super(memo); 031 032 // At construction, register for messages to auto create CbusReporters 033 addTc(memo.getTrafficController()); 034 } 035 036 /** 037 * {@inheritDoc} 038 */ 039 @Override 040 @Nonnull 041 public CanSystemConnectionMemo getMemo() { 042 return (CanSystemConnectionMemo) memo; 043 } 044 045 /** 046 * {@inheritDoc} 047 */ 048 @Override 049 @Nonnull 050 protected Reporter createNewReporter(@Nonnull String systemName, String userName) throws IllegalArgumentException { 051 log.debug("ReporterManager create new CbusReporter: {}", systemName); 052 String addr = systemName.substring(getSystemNamePrefix().length()); 053 Reporter t = new CbusReporter(addr, getMemo()); 054 t.setUserName(userName); 055 t.addPropertyChangeListener(this); 056 return t; 057 } 058 059 /** 060 * {@inheritDoc} 061 * Checks for reporter number between 0 and 65535 062 */ 063 @Override 064 public NameValidity validSystemNameFormat(@Nonnull String systemName) { 065 // name must be in the MSnnnnn format (M is user configurable); no + or ; or - for Reporter address 066 log.debug("Checking system name: {}", systemName); 067 try { 068 // try to parse the string; success returns true 069 int testnum = Integer.parseInt(systemName.substring(getSystemPrefix().length() + 1, systemName.length())); 070 if ( testnum < 0 ) { 071 log.debug("Number field cannot be negative in system name: {}", systemName); 072 return NameValidity.INVALID; 073 } 074 if ( testnum > 65535 ) { 075 log.debug("Number field cannot be greater than 65535 in system name: {}", systemName); 076 return NameValidity.INVALID; 077 } 078 } 079 catch (NumberFormatException e) { 080 log.debug("Illegal character in number field of system name: {}", systemName); 081 return NameValidity.INVALID; 082 } 083 catch (StringIndexOutOfBoundsException e) { 084 log.debug("Wrong length ( missing MR? ) for system name: {}", systemName); 085 return NameValidity.INVALID; 086 } 087 log.debug("Valid system name: {}", systemName); 088 return NameValidity.VALID; 089 } 090 091 /** 092 * {@inheritDoc} 093 */ 094 @Override 095 public boolean allowMultipleAdditions(@Nonnull String systemName) { 096 return true; 097 } 098 099 /** 100 * {@inheritDoc} 101 */ 102 @Override 103 public String getEntryToolTip() { 104 return Bundle.getMessage("AddReporterEntryToolTip"); 105 } 106 107 /** 108 * Validates to only numeric system names. 109 * {@inheritDoc} 110 */ 111 @Override 112 @Nonnull 113 public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException { 114 return validateSystemNameFormatOnlyNumeric(name,locale); 115 } 116 117 protected final static String CBUS_REPORTER_DESCRIPTOR_KEY = "CBUS Reporter Type"; // NOI18N 118 119 protected final static String CBUS_REPORTER_TYPE_CLASSIC = "Classic RFID"; // NOI18N 120 121 protected final static String CBUS_REPORTER_TYPE_DDES_DESCRIBING = "CANRC522 / CANRCOM"; // NOI18N 122 123 final static String[] CBUS_REPORTER_TYPES = { 124 CBUS_REPORTER_TYPE_CLASSIC,CBUS_REPORTER_TYPE_DDES_DESCRIBING}; 125 126 final static String[] CBUS_REPORTER_TYPE_TIPS = { 127 "DDES / ACDAT 5 byte unique tag.","DDES self-describing ( Writeable CANRC522 / Railcom )"}; // NOI18N 128 129 protected final static String CBUS_DEFAULT_REPORTER_TYPE = CBUS_REPORTER_TYPES[0]; 130 131 protected final static String CBUS_MAINTAIN_SENSOR_DESCRIPTOR_KEY = "Maintain CBUS Sensor"; // NOI18N 132 133 @Override 134 @Nonnull 135 public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() { 136 List<NamedBeanPropertyDescriptor<?>> l = new ArrayList<>(); 137 l.add(new SelectionPropertyDescriptor( 138 CBUS_REPORTER_DESCRIPTOR_KEY, CBUS_REPORTER_TYPES, CBUS_REPORTER_TYPE_TIPS, CBUS_DEFAULT_REPORTER_TYPE) { 139 @Override 140 public String getColumnHeaderText() { 141 return CBUS_REPORTER_DESCRIPTOR_KEY; 142 } 143 @Override 144 public boolean isEditable(NamedBean bean) { 145 return (bean instanceof CbusReporter); 146 } 147 }); 148 l.add(new BooleanPropertyDescriptor( 149 CBUS_MAINTAIN_SENSOR_DESCRIPTOR_KEY, false) { 150 @Override 151 public String getColumnHeaderText() { 152 return CBUS_MAINTAIN_SENSOR_DESCRIPTOR_KEY; 153 } 154 @Override 155 public boolean isEditable(NamedBean bean) { 156 return (bean instanceof CbusReporter); 157 } 158 }); 159 return l; 160 } 161 162 private int _timeout=2000; // same default as TimeoutReporter 163 164 /** 165 * Set the Reporter timeout. 166 * @param timeout time in milliseconds that CbusReporters stay at IdTag.SEEN after hearing an ID Tag. 167 */ 168 public void setTimeout(int timeout){ 169 _timeout = timeout; 170 } 171 172 /** 173 * Get the Reporter Timeout. 174 * @return milliseconds for CbusReporters to return to IdTag.UNSEEN 175 */ 176 public int getTimeout(){ 177 return _timeout; 178 } 179 180 /** 181 * Parse incoming reply to see if it is interesting to a Reporter, 182 * and if it is make sure that reporter exists. 183 */ 184 private CbusReporter processFrame(CanMutableFrame m) { 185 // see if this is a RailCom message from CBus 186 if ( m.extendedOrRtr() ) { 187 return null; 188 } 189 if ( m.getElement(0) != CbusConstants.CBUS_DDES && m.getElement(0) != CbusConstants.CBUS_ACDAT) { 190 return null; 191 } 192 // here an DDES (RFID or RailCom) or ACATmessage, extract device number & check for existing CBusReporter 193 var device = (m.getElement(1) << 8) + m.getElement(2); 194 var systemName = ""+device; 195 196 var reporter = provideReporter(systemName); // create if it doesn't exist 197 198 // is this the right algorithm? 199 int least_significant_nibble = m.getElement(3) & 0x0F; 200 if ( least_significant_nibble == 0x01 && m.getElement(0) == CbusConstants.CBUS_DDES) { 201 // is a RailCom Reporter, set that as mode 202 reporter.setProperty(CBUS_REPORTER_DESCRIPTOR_KEY,CBUS_REPORTER_TYPE_DDES_DESCRIBING); 203 } 204 205 return (CbusReporter)reporter; 206 } 207 208 @Override 209 public void reply(CanReply m) { 210 var reporter = processFrame(m); 211 if (reporter!= null) { 212 reporter.reply(m); 213 } 214 } 215 216 @Override 217 public void message(CanMessage m) { 218 var reporter = processFrame(m); 219 if (reporter!= null) { 220 reporter.message(m); 221 } 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public void dispose() { 229 if (getMemo().getTrafficController() != null) { // not all tests provide a TrafficController 230 getMemo().getTrafficController().removeCanListener(this); 231 } 232 super.dispose(); 233 } 234 235 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusReporterManager.class); 236 237}