001package jmri.jmrix.powerline;
002
003/**
004 * Represent a sequence of one or more X10 commands (addresses and functions).
005 * <p>
006 * These are X10 specific, but not device/interface specific.
007 * <p>
008 * A sequence should consist of addressing (1 or more), and then one or more
009 * commands. It can address multiple devices, but not more than one house-code.
010 * <p>
011 * House codes and devices within this class are sequential numbers (1-16 for
012 * house code, 1-16 for device code). These must be translated to line coding by
013 * other code that converts the sequence to adapter-specific messages. The
014 * {@link #encode} and {@link #decode} functions are provided to make that
015 * easier by converting to and from the standard line-code sequences, but you
016 * should check the coding of your new specific adapter before using them.
017 *
018 * @author Bob Jacobsen Copyright (C) 2008
019 */
020public class X10Sequence {
021
022    public static final int FUNCTION_ALL_UNITS_OFF = 0;
023    public static final int FUNCTION_ALL_LIGHTS_ON = 1;
024    public static final int FUNCTION_ON = 2;
025    public static final int FUNCTION_OFF = 3;
026    public static final int FUNCTION_DIM = 4;
027    public static final int FUNCTION_BRIGHT = 5;
028    public static final int FUNCTION_ALL_LIGHTS_OFF = 6;
029    public static final int FUNCTION_EXTENDED_CODE = 7;
030    public static final int FUNCTION_HAIL_REQUEST = 8;
031    public static final int FUNCTION_HAIL_ACKNOWLEDGE = 9;
032    public static final int FUNCTION_PRESET_DIM_1 = 10;
033    public static final int FUNCTION_PRESET_DIM_2 = 11;
034    public static final int FUNCTION_EXTENDED_DATA_TRANSFER = 12;
035    public static final int FUNCTION_STATUS_ON = 13;
036    public static final int FUNCTION_STATUS_OFF = 14;
037    public static final int FUNCTION_STATUS_REQUEST = 15;
038
039    public static final int EXTCMD_DIM = 0x31;
040
041    // First implementation of this class uses a fixed length
042    // array to hold the sequence; there's a practical limit to how
043    // many X10 commands anybody would want to send at once!
044    private static final int MAXINDEX = 32;
045    int index = 0;
046    Command[] cmds = new Command[MAXINDEX];  // doesn't scale, but that's for another day
047
048    /**
049     * Append a new "do function" operation to the sequence
050     * @param house    house code
051     * @param function function
052     * @param dimcount dimming step count
053     */
054    public void addFunction(int house, int function, int dimcount) {
055        if (index >= MAXINDEX) {
056            throw new IllegalArgumentException("Sequence too long");
057        }
058        cmds[index] = new Function(house, function, dimcount);
059        index++;
060    }
061
062    /**
063     * Append a new "set address" operation to the sequence
064     * @param house  house code A-P
065     * @param device device 1-16
066     */
067    public void addAddress(int house, int device) {
068        if (index >= MAXINDEX) {
069            throw new IllegalArgumentException("Sequence too long");
070        }
071        cmds[index] = new Address(house, device);
072        index++;
073    }
074
075    /**
076     * Append a new "do function" operation to the sequence
077     * @param house  A-P
078     * @param device 1-16
079     * @param cmd    command code
080     * @param data   additional data
081     */
082    public void addExtData(int house, int device, int cmd, int data) {
083        if (index >= MAXINDEX) {
084            throw new IllegalArgumentException("Sequence too long");
085        }
086        cmds[index] = new ExtData(house, device, cmd, data);
087        index++;
088    }
089
090    /**
091     * Next getCommand will be the first in the sequence
092     */
093    public void reset() {
094        index = 0;
095    }
096
097    /**
098     * Retrieve the next command in the sequence
099     * @return next available command
100     */
101    public Command getCommand() {
102        return cmds[index++];
103    }
104
105    /**
106     * Represent a single X10 command, which is either a "set address" or "do
107     * function" operation
108     */
109    public interface Command {
110
111        public boolean isAddress();
112
113        public boolean isFunction();
114
115        public int getHouseCode();
116    }
117
118    /**
119     * Represent a single "set address" X10 command
120     */
121    public static class Address implements Command {
122
123        public Address(int house, int device) {
124            this.house = house;
125            this.device = device;
126        }
127        int house;
128        int device;
129
130        @Override
131        public int getHouseCode() {
132            return house;
133        }
134
135        public int getAddress() {
136            return device;
137        }
138
139        @Override
140        public boolean isAddress() {
141            return true;
142        }
143
144        @Override
145        public boolean isFunction() {
146            return false;
147        }
148    }
149
150    /**
151     * Represent a single "do function" X10 command
152     */
153    public static class Function implements Command {
154
155        public Function(int house, int function, int dimcount) {
156            this.house = house;
157            this.function = function;
158            this.dimcount = dimcount;
159        }
160        int house;
161        int function;
162        int dimcount;
163
164        @Override
165        public int getHouseCode() {
166            return house;
167        }
168
169        public int getFunction() {
170            return function;
171        }
172
173        public int getDimCount() {
174            return dimcount;
175        }
176
177        @Override
178        public boolean isAddress() {
179            return false;
180        }
181
182        @Override
183        public boolean isFunction() {
184            return true;
185        }
186    }
187
188    /**
189     * Represent a single "Extended Data" X10 command
190     */
191    public static class ExtData implements Command {
192
193        public ExtData(int house, int device, int cmd, int data) {
194            this.house = house;
195            this.device = device;
196            this.cmd = cmd;
197            this.data = data;
198        }
199        int house;
200        int device;
201        int cmd;
202        int data;
203
204        public int getExtData() {
205            return data;
206        }
207
208        public int getExtCmd() {
209            return cmd;
210        }
211
212        @Override
213        public int getHouseCode() {
214            return house;
215        }
216
217        public int getAddress() {
218            return device;
219        }
220
221        @Override
222        public boolean isAddress() {
223            return false;
224        }
225
226        @Override
227        public boolean isFunction() {
228            return false;
229        }
230    }
231
232    /**
233     * Array of human readable names for X10 commands, indexed by the command
234     * numbers that are constants in this class.
235     */
236    static String[] functionNames = new String[]{
237        "All Off", "All Lights On", "On", "Off",
238        "Dim", "Bright", "All Lights Off", "Extended Code",
239        "Hail Request", "Hail Ack", "Preset Dim 1", "Preset Dim 2",
240        "Ext Data Trnsfr", "Status On", "Status Off", "Status Req"
241    };
242
243    /**
244     * Return a human-readable name for a function code
245     * @param i value of function code
246     * @return  string translation
247     */
248    public static String functionName(int i) {
249        return functionNames[i];
250    }
251
252    /**
253     * For the house (A-P) and device (1-16) codes, get the line-coded value.
254     * Argument is from 1 to 16 only.
255     * @param i house or device code value
256     * @return  line code value
257     */
258    public static int encode(int i) {
259        if (i < 1 || i > 16) {
260            throw new IllegalArgumentException("Encode outside 1-16: " + i);
261        }
262        return encoder[i];
263    }
264    static final int[] encoder = new int[]{-1,
265        0x6, 0xE, 0x2, 0xA, 0x1, 0x9, 0x5, 0xD, // 1-8
266        0x7, 0xF, 0x3, 0xB, 0x0, 0x8, 0x4, 0xC};
267
268    /**
269     * Get house (A-P as 1-16) or device (1-16) from line-coded value.
270     * @param i line code value
271     * @return  house or device code value
272     */
273    public static int decode(int i) {
274        if (i < 0 || i > 15) {
275            throw new IllegalArgumentException("Decode outside 1-16: " + i);
276        }
277        return decoder[i];
278    }
279    static final int[] decoder = new int[]{13,
280        5, 3, 11, 15, 7, 1, 9, 14, // 1-8
281        6, 4, 12, 16, 8, 2, 10}; // 9-15
282
283    /**
284     * Pretty-print an address code
285     * @param b address code value
286     * @return  human string form
287     */
288    public static String formatAddressByte(int b) {
289        return "House " + X10Sequence.houseValueToText(X10Sequence.decode((b >> 4) & 0x0F))
290                + " address device " + X10Sequence.decode(b & 0x0f);
291    }
292
293    /**
294     * Pretty-print a function code
295     * @param b command code value
296     * @return  human string form
297     */
298    public static String formatCommandByte(int b) {
299        return "House " + X10Sequence.houseValueToText(X10Sequence.decode((b >> 4) & 0x0F))
300                + " function: " + X10Sequence.functionName(b & 0x0f);
301    }
302
303    /**
304     * Translate House Value (1 to 16) to text
305     * @param hV device code value
306     * @return  human string form
307     */
308    public static String houseValueToText(int hV) {
309        if (hV >= 1 && hV <= 16) {
310            return houseValueDecoder[hV];
311        } else {
312            return "??";
313        }
314    }
315    static String[] houseValueDecoder = new String[]{"??",
316        "A", "B", "C", "D", "E", "F", "G", "H",
317        "I", "J", "K", "L", "M", "N", "O", "P"};
318
319    /**
320     * Translate House Code to text
321     * @param hC house code
322     * @return   A-P
323     */
324    public static String houseCodeToText(int hC) {
325        String hCode = "";
326        switch (hC) {
327            case 0x06:
328                hCode = "A";
329                break;
330            case 0x0E:
331                hCode = "B";
332                break;
333            case 0x02:
334                hCode = "C";
335                break;
336            case 0x0A:
337                hCode = "D";
338                break;
339            case 0x01:
340                hCode = "E";
341                break;
342            case 0x09:
343                hCode = "F";
344                break;
345            case 0x05:
346                hCode = "G";
347                break;
348            case 0x0D:
349                hCode = "H";
350                break;
351            case 0x07:
352                hCode = "I";
353                break;
354            case 0x0F:
355                hCode = "J";
356                break;
357            case 0x03:
358                hCode = "K";
359                break;
360            case 0x0B:
361                hCode = "L";
362                break;
363            case 0x00:
364                hCode = "M";
365                break;
366            case 0x08:
367                hCode = "N";
368                break;
369            case 0x04:
370                hCode = "O";
371                break;
372            case 0x0C:
373                hCode = "P";
374                break;
375            default:
376                hCode = "Unk hC:" + hC;
377                break;
378        }
379        return hCode;
380    }
381}