001package jmri.implementation; 002 003import java.util.Objects; 004import java.util.regex.Matcher; 005import java.util.regex.Pattern; 006 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.util.PhysicalLocation; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Extend AbstractReporter for IdTag reporters 017 * <p> 018 * This file is based on {@link jmri.jmrix.rfid.RfidReporter} 019 * 020 * @author Matthew Harris Copyright (c) 2011 021 * @author Paul Bender Copyright (c) 2016, 2019 022 * @since 4.15.3 023 */ 024public class AbstractIdTagReporter extends AbstractReporter 025 implements IdTagListener, PhysicalLocationReporter { 026 027 public AbstractIdTagReporter(String systemName) { 028 super(systemName); 029 } 030 031 public AbstractIdTagReporter(String systemName, String userName) { 032 super(systemName, userName); 033 } 034 035 /** 036 * Indicates whether the underlying hardware sends messages determining 037 * when an Id is no longer in this reporter (e.g. RailCom block occupancy exit events). 038 * <p> 039 * When {@code false} (e.g. RFID point detectors), seeing a tag elsewhere implies 040 * it has departed this reporter. 041 * When {@code true}, departure is strictly governed by hardware exit/clear messages. 042 * 043 * @return true if hardware sends explicit exit/departure messages; false otherwise. 044 */ 045 public boolean hasExitReports() { 046 return false; 047 } 048 049 private String lastReportString = null; 050 051 /** 052 * {@inheritDoc} 053 */ 054 @Override 055 public void notify(IdTag id) { 056 log.debug("Notify: {}", mSystemName); 057 String oldReportString = lastReportString; 058 String newReportString = null; 059 if (id != null) { 060 newReportString = (id instanceof Reportable) ? ((Reportable) id).toReportString() : id.toString(); 061 } 062 lastReportString = newReportString; 063 // These are both nullable. 064 if (!Objects.equals(oldReportString, newReportString)) { 065 firePropertyChange(PROPERTY_REPORT_METADATA, oldReportString, newReportString); 066 } 067 if (id != null) { 068 log.debug("Tag {} notified in {}", id, this); 069 // do not update last reporter and last seen if this is an "exit" report 070 // Only happens on LocoNet transponding reports 071 var entryexit = id.getProperty("entryexit"); 072 if (entryexit == null || !entryexit.equals("exits")) { 073 Reporter r = id.getWhereLastSeen(); 074 id.setWhereLastSeen(this); 075 if (r != null && !r.equals(this)) { 076 log.trace("{} notifySeenElsewhere on {}", id, r); 077 r.notifySeenElsewhere(id, this); 078 } 079 log.trace("{} last seen here: {}", id, this.mSystemName); 080 } else { 081 log.trace("{} skipping setWhereLastSeen on {} exits report", this, id); 082 } 083 } 084 setReport(id); 085 setState(id != null ? IdTag.SEEN : IdTag.UNSEEN); 086 } 087 088 /** 089 * {@inheritDoc} 090 */ 091 @Override 092 public void setReport(Object r) { 093 super.setReport(r); 094 if (r == null) { 095 lastReportString = null; 096 } else if (r instanceof Reportable) { 097 lastReportString = ((Reportable) r).toReportString(); 098 } else { 099 lastReportString = r.toString(); 100 } 101 } 102 103 /** 104 * {@inheritDoc} 105 */ 106 @Override 107 public void notifySeenElsewhere(IdTag tag, Reporter newReporter) { 108 if (!hasExitReports() && !this.equals(newReporter) && getCurrentReport() == tag) { 109 notify(null); 110 } 111 } 112 113 private int state = UNKNOWN; 114 115 /** {@inheritDoc} */ 116 @Override 117 public void setState(int s) { 118 state = s; 119 } 120 121 /** {@inheritDoc} */ 122 @Override 123 public int getState() { 124 return state; 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 @Nonnull 130 public String describeState(int state) { 131 switch (state) { 132 case IdTag.SEEN: 133 return Bundle.getMessage("IdTagReporterStateSeen"); 134 case IdTag.UNSEEN: 135 return Bundle.getMessage("IdTagReporterStateUnSeen"); 136 default: 137 return super.describeState(state); 138 } 139 } 140 141 // Methods to support PhysicalLocationReporter interface 142 143 /** 144 * Get the locomotive address we're reporting about from the current report. 145 * {@inheritDoc} 146 * @param rep ignored, IdTag Reporters don't send String type reports. 147 */ 148 @Override 149 public LocoAddress getLocoAddress(String rep) { 150 // For now, we assume the current report. 151 // IdTag.getTagID() is a system-name-ized version of the loco address. I think. 152 // Matcher.group(1) : loco address (I think) 153 Object report = this.getCurrentReport(); 154 if (!(report instanceof IdTag)) { 155 // nothing being reported now 156 return null; 157 } 158 IdTag cr = (IdTag) report; 159 ReporterManager rm = InstanceManager.getDefault(jmri.ReporterManager.class); 160 Pattern p = Pattern.compile("" + rm.getSystemPrefix() + rm.typeLetter() + "(\\d+)"); 161 Matcher m = p.matcher(cr.getTagID()); 162 if (m.find()) { 163 log.debug("Parsed address: {}", m.group(1)); 164 // I have no idea what kind of loco address an Ecos reporter uses, 165 // so we'll default to DCC for now. 166 return (new DccLocoAddress(Integer.parseInt(m.group(1)), LocoAddress.Protocol.DCC)); 167 } else { 168 return (null); 169 } 170 } 171 172 /** 173 * Gets the direction (ENTER/EXIT) of the report. 174 * <p> 175 * Because of the way 176 * IdTag Reporters work, all reports are ENTER type. 177 * {@inheritDoc} 178 */ 179 @Override 180 public PhysicalLocationReporter.Direction getDirection(String rep) { 181 // TEMPORARY: Assume we're always Entering, if asked. 182 return (PhysicalLocationReporter.Direction.ENTER); 183 } 184 185 /** 186 * Get the PhysicalLocation of the Reporter 187 * 188 * Reports its own location, for now. 189 * Not sure if that's the right thing or 190 * not. NOT DONE YET 191 * 192 * {@inheritDoc} 193 */ 194 @Override 195 public PhysicalLocation getPhysicalLocation() { 196 return (this.getPhysicalLocation(null)); 197 } 198 199 /** 200 * Get the PhysicalLocation of the Reporter. 201 * 202 * {@inheritDoc} 203 * @param s unused. 204 */ 205 @Override 206 public PhysicalLocation getPhysicalLocation(String s) { 207 return (PhysicalLocation.getBeanPhysicalLocation(this)); 208 } 209 210 private static final Logger log = LoggerFactory.getLogger(AbstractIdTagReporter.class); 211 212}