001package jmri.jmrix.powerline.insteon2412s;
002
003import jmri.jmrix.powerline.SerialMessage;
004import jmri.jmrix.powerline.X10Sequence;
005import jmri.util.StringUtil;
006
007/**
008 * Contains the data payload of a serial packet.
009 * <p>
010 * The transmission protocol can come in one of several forms:
011 * <ul>
012 * <li>If the interlocked parameter is false (default), the packet is just sent.
013 * If the response length is not zero, a reply of that length is expected.
014 * <li>If the interlocked parameter is true, the transmission will require a CRC
015 * interlock, which will be automatically added. (Design note: this is done to
016 * make sure that the messages remain atomic)
017 * </ul>
018 *
019 * @author Bob Jacobsen Copyright (C) 2001,2003, 2006, 2007, 2008, 2009
020 * @author Ken Cameron Copyright (C) 2010
021 */
022public class SpecificMessage extends SerialMessage {
023    // is this logically an abstract class?
024
025    public SpecificMessage(int l) {
026        super(l);
027        setResponseLength(0);  // only polls require a response
028        setBinary(true);
029        setTimeout(5000);
030    }
031
032    /**
033     * This ctor interprets the String as the exact sequence to send,
034     * byte-for-byte.
035     *
036     * @param m message
037     * @param l response length in bytes
038     */
039    public SpecificMessage(String m, int l) {
040        super(m, l);
041    }
042
043    boolean interlocked = false;
044
045    @Override
046    public void setInterlocked(boolean v) {
047        interlocked = v;
048    }
049
050    @Override
051    public boolean getInterlocked() {
052        return interlocked;
053    }
054
055    @Override
056    public String toMonitorString() {
057        // check for valid length
058        int len = getNumDataElements();
059        StringBuilder text = new StringBuilder();
060        if ((getElement(0) & 0xFF) != Constants.HEAD_STX) {
061            text.append("INVALID HEADER: ").append(String.format("0x%1X", getElement(0) & 0xFF));
062            text.append(" len: ").append(len);
063        } else {
064            switch (getElement(1) & 0xFF) {
065                case Constants.FUNCTION_REQ_STD:
066                    text.append("Send Cmd ");
067                    if (len == 8 || len == 22) {
068                        if ((getElement(5) & Constants.FLAG_BIT_STDEXT) == Constants.FLAG_STD) {
069                            text.append(" Std");
070                        } else if (len == 22) {
071                            text.append(" Ext");
072                        }
073                        switch (getElement(5) & Constants.FLAG_MASK_MSGTYPE) {
074                            case Constants.FLAG_TYPE_P2P:
075                                text.append(" Direct");
076                                break;
077                            case Constants.FLAG_TYPE_ACK:
078                                text.append(" ACK");
079                                break;
080                            case Constants.FLAG_TYPE_NAK:
081                                text.append(" NAK");
082                                break;
083                            case Constants.FLAG_TYPE_GBCAST:
084                                text.append(" Group Broadcast");
085                                break;
086                            case Constants.FLAG_TYPE_GBCLEANUP:
087                                text.append(" Group Broadcast Cleanup");
088                                break;
089                            case Constants.FLAG_TYPE_GBCLEANACK:
090                                text.append(" Group Broadcast Cleanup ACK");
091                                break;
092                            case Constants.FLAG_TYPE_GBCLEANNAK:
093                                text.append(" Group Broadcast Cleanup NAK");
094                                break;
095                            default:
096                                log.warn("Unhandled flag type: {}", getElement(5) & Constants.FLAG_MASK_MSGTYPE);
097                                break;
098                        }
099                        text.append(" message,");
100                        text.append(String.format(" %d hops left",
101                            (getElement(5) & Constants.FLAG_MASK_HOPSLEFT >> Constants.FLAG_SHIFT_HOPSLEFT)));
102                        text.append(String.format(" , %d max hops", (getElement(5) & Constants.FLAG_MASK_MAXHOPS)));
103                        text.append(" addr ").append(String.format("%1$X.%2$X.%3$X",
104                            (getElement(2) & 0xFF), (getElement(3) & 0xFF), (getElement(4) & 0xFF)));
105                        switch (getElement(6) & 0xFF) {
106                            case Constants.CMD_LIGHT_ON_RAMP:
107                                text.append(" ON RAMP ");
108                                text.append((getElement(7) & 0xFF) / 256.0);
109                                break;
110                            case Constants.CMD_LIGHT_ON_FAST:
111                                text.append(" ON FAST ");
112                                text.append((getElement(7) & 0xFF) / 256.0);
113                                break;
114                            case Constants.CMD_LIGHT_OFF_FAST:
115                                text.append(" OFF FAST ");
116                                text.append((getElement(7) & 0xFF) / 256.0);
117                                break;
118                            case Constants.CMD_LIGHT_OFF_RAMP:
119                                text.append(" OFF ");
120                                text.append((getElement(7) & 0xFF) / 256.0);
121                                break;
122                            case Constants.CMD_LIGHT_CHG:
123                                text.append(" CHG ");
124                                text.append((getElement(7) & 0xFF) / 256.0);
125                                break;
126                            default:
127                                text.append(" Unknown cmd: ").append(StringUtil.twoHexFromInt(getElement(6) & 0xFF));
128                                break;
129                        }
130                    } else {
131                        text.append(" !! Length wrong: ").append(len);
132                    }
133                    break;
134                // i wrote this then figured the POLL are replies
135//             case Constants.POLL_REQ_BUTTON :
136//              text.append("Poll Button ");
137//              int button = ((getElement(2) & Constants.BUTTON_BITS_ID) >> 4) + 1;
138//              text.append(button);
139//              int op = getElement(2) & Constants.BUTTON_BITS_OP;
140//              if (op == Constants.BUTTON_HELD) {
141//               text.append(" HELD");
142//              } else if (op == Constants.BUTTON_REL) {
143//               text.append(" RELEASED");
144//              } else if (op == Constants.BUTTON_TAP) {
145//               text.append(" TAP");
146//              }
147//              break;
148//             case Constants.POLL_REQ_BUTTON_RESET :
149//              text.append("Reset by Button at Power Cycle");
150//              break;
151                case Constants.FUNCTION_REQ_X10:
152                    text.append("Send Cmd X10 ");
153                    if ((getElement(3) & Constants.FLAG_BIT_X10_CMDUNIT) == Constants.FLAG_X10_RECV_CMD) {
154                        text.append(X10Sequence.formatCommandByte(getElement(2) & 0xFF));
155                    } else {
156                        text.append(X10Sequence.formatAddressByte(getElement(2) & 0xFF));
157                    }
158                    break;
159//             case Constants.POLL_REQ_X10 :
160//              text.append("Poll Cmd X10 ");
161//                    if ((getElement(3)& Constants.FLAG_BIT_X10_CMDUNIT) == Constants.FLAG_X10_RECV_CMD) {
162//                     text.append(X10Sequence.formatCommandByte(getElement(2) & 0xFF));
163//                    } else {
164//                     text.append(X10Sequence.formatAddressByte(getElement(2)& 0xFF));
165//                    }
166//              break;
167                default: {
168                    text.append(" Unknown command: ").append(StringUtil.twoHexFromInt(getElement(1) & 0xFF));
169                    text.append(" len: ").append(len);
170                }
171            }
172        }
173        return text + "\n";
174    }
175
176    /**
177     * This ctor interprets the byte array as a sequence of characters to send.
178     *
179     * @param a Array of bytes to send
180     * @param l length of expected reply
181     */
182    public SpecificMessage(byte[] a, int l) {
183        super(a, l);
184    }
185
186    int responseLength = -1;  // -1 is an invalid value, indicating it hasn't been set
187
188    @Override
189    public void setResponseLength(int l) {
190        responseLength = l;
191    }
192
193    @Override
194    public int getResponseLength() {
195        return responseLength;
196    }
197
198    // static methods to recognize a message
199//    public boolean isPoll() { return getElement(1)==48;}
200//    public boolean isXmt()  { return getElement(1)==17;}
201//    public int getAddr() { return getElement(0); }
202    // static methods to return a formatted message
203    public static SerialMessage getPoll(int addr) {
204        // Powerline implementation does not currently poll
205        return null;
206    }
207
208    /**
209     * create an Insteon message with the X10 address
210     * @param housecode  X10 housecode
211     * @param devicecode X10 devicecode
212     *
213     * @return message   formated message
214     */
215    public static SpecificMessage getX10Address(int housecode, int devicecode) {
216        SpecificMessage m = new SpecificMessage(4);
217        m.setInterlocked(false);
218        m.setElement(0, Constants.HEAD_STX);
219        m.setElement(1, Constants.FUNCTION_REQ_X10);
220        m.setElement(2, (X10Sequence.encode(housecode) << 4) + X10Sequence.encode(devicecode));
221        m.setElement(3, 0x00);  //  0x00 Means address
222        return m;
223    }
224
225    /**
226     * create an Insteon message with the X10 address and dim steps
227     *
228     * @param housecode  X10 housecode
229     * @param devicecode X10 devicecode
230     * @param dimcode    value for dimming
231     *
232     * @return message   formated message
233     */
234    public static SpecificMessage getX10AddressDim(int housecode, int devicecode, int dimcode) {
235        SpecificMessage m = new SpecificMessage(4);
236        m.setInterlocked(false);
237        m.setElement(0, Constants.HEAD_STX);
238        m.setElement(1, Constants.FUNCTION_REQ_X10);
239        if (dimcode > 0) {
240            m.setElement(2, 0x04 | ((dimcode & 0x1f) << 3));
241        } else {
242            m.setElement(2, 0x04);
243        }
244        m.setElement(3, (X10Sequence.encode(housecode) << 4) + X10Sequence.encode(devicecode));
245        m.setElement(3, 0x80);  //  0x00 Means address
246        return m;
247    }
248
249    public static SpecificMessage getX10FunctionDim(int housecode, int function, int dimcode) {
250        SpecificMessage m = new SpecificMessage(2);
251        m.setInterlocked(true);
252        if (dimcode > 0) {
253            m.setElement(0, 0x06 | ((dimcode & 0x1f) << 3));
254        } else {
255            m.setElement(0, 0x06);
256        }
257        m.setElement(1, (X10Sequence.encode(housecode) << 4) + function);
258        return m;
259    }
260
261    public static SpecificMessage getX10Function(int housecode, int function) {
262        SpecificMessage m = new SpecificMessage(4);
263//        m.setInterlocked(true);
264        m.setInterlocked(false);
265        m.setElement(0, Constants.HEAD_STX);
266        m.setElement(1, Constants.FUNCTION_REQ_X10);
267        m.setElement(2, (X10Sequence.encode(housecode) << 4) + function);
268        m.setElement(3, 0x80);  //  0x80 means function
269        return m;
270    }
271
272    public static SpecificMessage getInsteonAddress(int idhighbyte, int idmiddlebyte, int idlowbyte) {
273        SpecificMessage m = new SpecificMessage(8);
274//        m.setInterlocked(true);
275        m.setInterlocked(false);
276        m.setElement(0, Constants.HEAD_STX);
277        m.setElement(1, Constants.FUNCTION_REQ_STD);
278        m.setElement(2, idhighbyte);
279        m.setElement(3, idmiddlebyte);
280        m.setElement(4, idlowbyte);
281        m.setElement(5, 0x0F);
282        m.setElement(6, 0x11);
283        m.setElement(7, 0xFF);
284        return m;
285    }
286
287    public static SpecificMessage getInsteonFunction(int idhighbyte, int idmiddlebyte, int idlowbyte,
288            int function, int flag, int cmd1, int cmd2) {
289        SpecificMessage m = new SpecificMessage(8);
290//        m.setInterlocked(true);
291        m.setInterlocked(false);
292        m.setElement(0, Constants.HEAD_STX);
293        m.setElement(1, Constants.FUNCTION_REQ_STD);
294        m.setElement(2, idhighbyte);
295        m.setElement(3, idmiddlebyte);
296        m.setElement(4, idlowbyte);
297        m.setElement(5, flag);
298        m.setElement(6, cmd1);
299        m.setElement(7, cmd2);
300        return m;
301    }
302
303    // initialize logging
304    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SpecificMessage.class);
305
306}