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}