001package jmri.jmrix.rps; 002 003import java.util.ArrayList; 004import javax.vecmath.Point3d; 005import javax.vecmath.Vector3d; 006import javax.vecmath.Vector3f; 007import jmri.DccLocoAddress; 008import jmri.LocoAddress; 009import jmri.PhysicalLocationReporter; 010import jmri.implementation.AbstractReporter; 011import jmri.util.PhysicalLocation; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * RPS implementation of the Reporter interface. 017 * 018 * @author Bob Jacobsen Copyright (C) 2008 019 * @since 2.3.1 020 */ 021public class RpsReporter extends AbstractReporter implements MeasurementListener { 022 023 public RpsReporter(String systemName, String prefix) { 024 super(systemName); 025 // create Region from all but prefix 026 region = new Region(systemName.substring(prefix.length() + 1)); // multichar prefix from memo 027 Model.instance().addRegion(region); 028 } 029 030 public RpsReporter(String systemName, String userName, String prefix) { 031 super(systemName, userName); 032 // create Region from all but prefix 033 region = new Region(systemName.substring(prefix.length() + 1)); // multichar prefix from memo 034 Model.instance().addRegion(region); 035 } 036 037 @Override 038 public void notify(Measurement r) { 039 Point3d p = new Point3d(r.getX(), r.getY(), r.getZ()); 040 Integer id = Integer.valueOf(r.getReading().getId()); 041 042 // ignore if code not OK 043 if (!r.isOkPoint()) { 044 return; 045 } 046 047 // ignore if not in Z fiducial volume 048 if (r.getZ() > 20 || r.getZ() < -20) { 049 return; 050 } 051 052 log.debug("starting {}", getSystemName()); 053 if (region.isInside(p)) { 054 notifyInRegion(id); 055 } else { 056 notifyOutOfRegion(id); 057 } 058 } 059 060 void notifyInRegion(Integer id) { 061 // make sure region contains this Reading.getId(); 062 if (!contents.contains(id)) { 063 contents.add(id); 064 notifyArriving(id); 065 } 066 } 067 068 void notifyOutOfRegion(Integer id) { 069 // make sure region does not contain this Reading.getId(); 070 if (contents.contains(id)) { 071 contents.remove(id); 072 notifyLeaving(id); 073 } 074 } 075 076 transient Region region; 077 ArrayList<Integer> contents = new ArrayList<Integer>(); 078 079 /** 080 * Notify parameter listeners that a device has left the region covered by 081 * this reporter. 082 * @param id Number of region being left 083 */ 084 void notifyLeaving(Integer id) { 085 firePropertyChange("Leaving", null, id); 086 setReport(""); 087 } 088 089 /** 090 * Notify parameter listeners that a device has entered the region covered 091 * by this reporter. 092 * @param id Number of region being entered 093 */ 094 void notifyArriving(Integer id) { 095 firePropertyChange("Arriving", null, id); 096 setReport("" + id); 097 } 098 099 /** 100 * Numerical state is the number of transmitters in the region. 101 */ 102 @Override 103 public int getState() { 104 return contents.size(); 105 } 106 107 @Override 108 public void setState(int i) { 109 } 110 111 @Override 112 public void dispose() { 113 Model.instance().removeRegion(region); 114 super.dispose(); 115 } 116 117 // Methods to support PhysicalLocationReporter interface 118 119 /** 120 * Parses out a (possibly old) RpsReporter-generated report string to 121 * extract the address from the front. 122 * Assumes the RpsReporter format is "NNNN". 123 * @param rep loco string. 124 * @return loco address, may be null. 125 */ 126 public LocoAddress getLocoAddress(String rep) { 127 // The report is a string, that is the ID of the locomotive (I think) 128 log.debug("Parsed ID: {}", rep); 129 // I have no idea what kind of loco address an RPS reporter uses, 130 // so we'll default to DCC for now. 131 if (rep.length() > 0) { 132 try { 133 int id = Integer.parseInt(rep); 134 int addr = Engine.instance().getTransmitter(id).getAddress(); 135 return (new DccLocoAddress(addr, LocoAddress.Protocol.DCC)); 136 } catch (NumberFormatException e) { 137 return (null); 138 } 139 } else { 140 return (null); 141 } 142 } 143 144 /** 145 * Get the direction (ENTER/EXIT) of the report. 146 * <p> 147 * Because of the way Ecos Reporters work (or appear to), all reports are ENTER type. 148 * @param rep reporter ID in string form. 149 * @return direction is always a location entrance 150 */ 151 public PhysicalLocationReporter.Direction getDirection(String rep) { 152 // The RPS reporter only sends a report on entry. 153 return (PhysicalLocationReporter.Direction.ENTER); 154 } 155 156 /** 157 * Get the PhysicalLocation of the Reporter. 158 * <p> 159 * Reports its own location, for now. 160 * Not sure if that's the right thing or not. 161 * Would be nice if it reported the exact measured location of the 162 * transmitter, but right now that doesn't appear to be being stored 163 * anywhere retrievable. NOT DONE YET 164 * @return PhysicalLocation.getBeanPhysicalLocation 165 */ 166 public PhysicalLocation getPhysicalLocation() { 167 return (PhysicalLocation.getBeanPhysicalLocation(this)); 168 } 169 170 /** 171 * Get the PhysicalLocation of the Transmitter for a given ID. 172 * <p> 173 * Given an ID (in String form), looks up the Transmitter and gets its 174 * current PhysicalLocation (translated from the RPS Measurement). 175 * 176 * @param s transmitter ID. 177 * @return physical location. 178 */ 179 public PhysicalLocation getPhysicalLocation(String s) { 180 if (s.length() > 0) { 181 try { 182 int id = Integer.parseInt(s); 183 Vector3d v = Engine.instance().getTransmitter(id).getLastMeasurement().getVector(); 184 return (new PhysicalLocation(new Vector3f(v))); 185 } catch (NumberFormatException e) { 186 return (null); 187 } catch (NullPointerException e) { 188 return (null); 189 } 190 } else { 191 return (null); 192 } 193 } 194 195 private final static Logger log = LoggerFactory.getLogger(RpsReporter.class); 196 197}