001package jmri.jmrix.lenz;
002
003import javax.annotation.CheckForNull;
004import jmri.Turnout;
005
006/**
007 * Represents a single response from the XpressNet.
008 *
009 * @author svatopluk.dedic@gmail.com Copyright (C) 2020
010 *
011 */
012public class FeedbackItem {
013    private final int number;
014    private final int data;
015    private final XNetReply reply;
016
017    protected FeedbackItem(XNetReply reply, int number, int data) {
018        this.number = number;
019        this.data = data;
020        this.reply = reply;
021    }
022
023    /**
024     * Determines if the feedback was solicited.
025     * @return {@code true}, if feedback was solicited.
026     */
027    public boolean isUnsolicited() {
028        return reply.isUnsolicited();
029    }
030
031    /**
032     * Returns the (base) address of the item.
033     * For turnouts, return the reported address. For encoders,
034     * return the address of the first contained sensor
035     * @return the address.
036     */
037    public int getAddress() {
038        return number;
039    }
040
041    /**
042     * Determines if the feedback is for the given Turnout address
043     * @param address address to check
044     * @return {@code true}, if the item applies to the address.
045     */
046    public boolean matchesAddress(int address) {
047        if (isAccessory()) {
048            return number == address;
049        } else {
050            return ((address - 1) & ~0x03) + 1 == number;
051        }
052    }
053
054    /**
055     * Determines if the turnout motion has completed. Requires decoder/switch
056     * feedback to be processed by the command station; always {@code false} if not connected.
057     * @return {@code true} if the motion is complete.
058     */
059    public boolean isMotionComplete() {
060        return (data & 0x80) == 0;
061    }
062
063    /**
064     * Returns the feedback type.
065     * <ul>
066     * <li> 0: Turnout without feedback
067     * <li> 1: Turnout with feedback
068     * <li> 2: Feedback encoder
069     * <li> 3: reserved, invalid
070     * </ul>
071     * @return feedback type.
072     */
073    public int getType() {
074        return (data & 0b0110_0000) >> 5;
075    }
076
077    /**
078     * Translates raw value in {@link #getAccessoryStatus} into Turnout's CLOSED/THROWN
079     * values
080     * @return {@link Turnout#CLOSED}, {@link Turnout#THROWN} or -1 for inconsistent.s
081     */
082    public int getTurnoutStatus() {
083        int t = getType();
084        if (t > 1) {
085            return -1;
086        }
087        switch (getAccessoryStatus()) {
088            case 0x01: return Turnout.CLOSED;
089            case 0x02: return Turnout.THROWN;
090            default: // fall through
091        }
092        return -1;
093    }
094
095    /**
096     * Returns true, if the feedback is from feedback encoder.
097     * @return {@code true} for encoder feedback.
098     */
099    public boolean isEncoder() {
100        return getType() == 2;
101    }
102
103    /**
104     * Returns true, if the feedback is from turnout (accessory).
105     * @return {@code true} for turnout feedback.
106     */
107    public boolean isAccessory() {
108        return getType() < 2;
109    }
110
111    /**
112     * Gives status value as specified in XPressNet.
113     * <ul>
114     * <li> 0x00: turnout was not operated
115     * <li> 0x01: last command was "0", turnout left, CLOSED.
116     * <li> 0x02: last command was "1", turnout right, THROWN.
117     * <li> 0x03: reserved, invalid
118     * </ul>
119     * The method returns 0x03, if the feedback is not for accessory.
120     * @return accessory state.
121     */
122    public int getAccessoryStatus() {
123        if (!isAccessory()) {
124            return 0x03;    // invalid
125        }
126        return (number & 0x01) != 0 ? (data & 0b0011) : (data & 0b1100) >> 2;
127    }
128
129    /**
130     * Returns encoder feedback for the given sensor. The function return {@code null}
131     * if the sensor number is not within this FeedbackItem range, or the item does
132     * not represent an encoder feedback.
133     * @param sensorNumber sensor number, starting with 1.
134     * @return The sensor's reported bit value (true/false) or {@code null}, if
135     * no encoder feedback for the sensor is found.
136     */
137    @CheckForNull
138    public Boolean getEncoderStatus(int sensorNumber) {
139        if (!matchesAddress(number) || isAccessory()) {
140            return null;
141        } else {
142            return (data & (1 << ((sensorNumber -1) % 4))) > 0;
143        }
144    }
145
146    /**
147     * Returns a FeedbackItem instance for the other accessory address reported in the
148     * item. Returns {@code null} for non-accessory feedbacks.
149     * @return instance for the paired accessory, or {@code null}.
150     */
151    public FeedbackItem pairedAccessoryItem() {
152        if (!isAccessory()) {
153            return null;
154        }
155        int a = (number & 0x01) != 0 ? number + 1 : number - 1;
156        return new FeedbackItem(reply, a, data);
157    }
158}