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