001package jmri.jmrix.rfid; 002 003import jmri.jmrix.AbstractMRReply; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007/** 008 * Basic interface to be implemented for each tag protocol. 009 * 010 * @author Matthew Harris Copyright (C) 2014 011 * @since 3.9.2 012 */ 013abstract public class RfidProtocol { 014 015 protected boolean isConcentrator; 016 protected char concentratorFirst; 017 protected char concentratorLast; 018 protected int portPosition; 019 020 /** 021 * Constructor for an RFID Protocol. Used when a single reader is connected 022 * directly to a port, not via a concentrator. 023 */ 024 public RfidProtocol() { 025 this('\u0000', '\u0000', 0); 026 } 027 028 /** 029 * Constructor for an RFID Protocol. Supports the use of concentrators where 030 * a character range is used to determine the specific reader port. 031 * 032 * @param concentratorFirst character representing first concentrator port 033 * @param concentratorLast character representing last concentrator port 034 * @param portPosition position of port character in reply string; 035 * 1 for first character 036 */ 037 public RfidProtocol(char concentratorFirst, char concentratorLast, int portPosition) { 038 isConcentrator = concentratorFirst != '\u0000' && concentratorLast != '\u0000' && portPosition != 0; 039 this.concentratorFirst = concentratorFirst; 040 this.concentratorLast = concentratorLast; 041 this.portPosition = portPosition - 1; // needs to be zero-based; 042 } 043 044 /** 045 * Retrieves RFID Tag information from message 046 * 047 * @param msg Message to decode 048 * @return String representation of tag 049 */ 050 public String getTag(AbstractMRReply msg) { 051 log.error("getTag should not be called"); 052 return String.valueOf(msg.getElement(0)); 053 } 054 055 /** 056 * Determines if this protocol provides checksum values Default is false. 057 * Protocols that do provide them should override this method. 058 * 059 * @return true if provided 060 */ 061 public boolean providesChecksum() { 062 return false; 063 } 064 065 /** 066 * When available, returns the checksum portion of an RFID reply 067 * 068 * @param msg RFID reply to process 069 * @return checksum value 070 */ 071 abstract public String getCheckSum(AbstractMRReply msg); 072 073 /** 074 * Determines if this RFID reply is valid 075 * 076 * @param msg RFID reply to process 077 * @return true if valid 078 */ 079 abstract public boolean isValid(AbstractMRReply msg); 080 081 /** 082 * Determines if at the end of this RFID reply 083 * 084 * @param msg RFID reply to process 085 * @return true if at end 086 */ 087 abstract public boolean endOfMessage(AbstractMRReply msg); 088 089 /** 090 * Returns the initialisation string to be sent to an adapter implementing 091 * the protocol. For those protocols that do not require one, return a blank 092 * string 093 * 094 * @return initialisation string 095 */ 096 abstract public String initString(); 097 098 public char getReaderPort(AbstractMRReply msg) { 099 if (isConcentrator) { 100 Character p = (char) msg.getElement(portPosition); 101 if (p.toString().matches("[" + this.concentratorFirst + "-" + this.concentratorLast + "]")) { 102 return p; 103 } 104 } 105 return 0x00; 106 } 107 108 /** 109 * Provides a textual representation of this message for the monitor 110 * 111 * @param msg RFID reply to process 112 * @return textual representation 113 */ 114 public String toMonitorString(AbstractMRReply msg) { 115 StringBuilder sb = new StringBuilder(); 116 117 // don't know, just show 118 sb.append("Unknown reply of length "); 119 sb.append(msg.getNumDataElements()); 120 sb.append(": "); 121 sb.append(msg.toString()).append("\n"); 122 sb.append("\n"); 123 return sb.toString(); 124 } 125 126 /** 127 * Precomputed translation table for hex characters 0..f 128 */ 129 private static final byte[] hexCodes = new byte['f' + 1]; 130 131 /** 132 * Static method to initialise translation table 133 */ 134 static { 135 // Only 0..9, A..F & a..f are valid hex characters 136 // all others are invalid 137 138 // Set everything to invalid initially 139 for (int i = 0; i <= 'f'; i++) { 140 hexCodes[i] = -1; 141 } 142 143 // Now set values for 0..9 144 for (int i = '0'; i <= '9'; i++) { 145 hexCodes[i] = (byte) (i - '0'); 146 } 147 148 // Now set values for A..F 149 for (int i = 'A'; i <= 'F'; i++) { 150 hexCodes[i] = (byte) (i - 'A' + 10); 151 } 152 153 // Finally, set values for a..f 154 for (int i = 'a'; i <= 'f'; i++) { 155 hexCodes[i] = (byte) (i - 'a' + 10); 156 } 157 } 158 159 /** 160 * Convert a single hex character to its corresponding hex value using 161 * pre-calculated translation table. 162 * 163 * @param c character to convert (0..9, a..f or A..F) 164 * @return corresponding integer value (0..15) 165 * @throws IllegalArgumentException when c is not a hex character 166 */ 167 private static int charToNibble(char c) { 168 if (c > 'f') { 169 throw new IllegalArgumentException("Invalid hex character: " + c); 170 } 171 int nibble = hexCodes[c]; 172 if (nibble < 0) { 173 throw new IllegalArgumentException("Invalid hex character: " + c); 174 } 175 return nibble; 176 } 177 178 /** 179 * Converts a hex string to an unsigned byte array. Both upper and lower 180 * case hex codes are permitted. 181 * 182 * @param s String representation of a hex number. Must be a whole number of 183 * bytes (i.e. an even number of characters) and be formed only of 184 * digits 0..9, a..f or A..F 185 * @return corresponding unsigned byte array 186 * @throws IllegalArgumentException when s is not a valid hex string 187 */ 188 protected static byte[] convertHexString(String s) { 189 190 // Check the length of the string to convert 191 // is a whole number of bytes 192 int stringLength = s.length(); 193 if ((stringLength & 0x1) != 0) { 194 throw new IllegalArgumentException("convertHexString requires an even number of hex characters"); 195 } 196 197 // Create byte array to store the converted string 198 byte[] bytes = new byte[stringLength / 2]; 199 200 // Loop through the string converting individual bytes 201 for (int i = 0, j = 0; i < stringLength; i += 2, j++) { 202 // Convert the high and low nibbles 203 int high = charToNibble(s.charAt(i)); 204 int low = charToNibble(s.charAt(i + 1)); 205 206 // Combine both nibbles into a byte 207 bytes[j] = (byte) ((high << 4) | low); 208 } 209 return bytes; 210 } 211 212 private static final Logger log = LoggerFactory.getLogger(RfidProtocol.class); 213 214}