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}