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    public SpecificMessage(int l) {
028        super(l);
029        setResponseLength(0);  // only polls require a response
030        setBinary(true);
031        setTimeout(5000);
032    }
033
034    /**
035     * This ctor interprets the String as the exact sequence to send,
036     * byte-for-byte.
037     *
038     * @param m message
039     * @param l response length in bytes
040     */
041    public SpecificMessage(String m, int l) {
042        super(m, l);
043    }
044
045    boolean interlocked = false;
046
047    @Override
048    public void setInterlocked(boolean v) {
049        interlocked = v;
050    }
051
052    @Override
053    public boolean getInterlocked() {
054        return interlocked;
055    }
056
057    @Override
058    public String toMonitorString() {
059        // check for valid length
060        int len = getNumDataElements();
061        StringBuilder text = new StringBuilder();
062        if ((getElement(0) & 0xFF) != Constants.HEAD_STX) {
063            text.append("INVALID HEADER: " + String.format("0x%1X", getElement(0) & 0xFF));
064            text.append(" len: " + len);
065        } else {
066            switch (getElement(1) & 0xFF) {
067                case Constants.FUNCTION_REQ_STD:
068                    text.append("Send Cmd ");
069                    if (len == 8 || len == 22) {
070                        if ((getElement(5) & Constants.FLAG_BIT_STDEXT) == Constants.FLAG_STD) {
071                            text.append(" Std");
072                        } else if (len == 22) {
073                            text.append(" Ext");
074                        }
075                        switch (getElement(5) & Constants.FLAG_MASK_MSGTYPE) {
076                            case Constants.FLAG_TYPE_P2P:
077                                text.append(" Direct");
078                                break;
079                            case Constants.FLAG_TYPE_ACK:
080                                text.append(" ACK");
081                                break;
082                            case Constants.FLAG_TYPE_NAK:
083                                text.append(" NAK");
084                                break;
085                            case Constants.FLAG_TYPE_GBCAST:
086                                text.append(" Group Broadcast");
087                                break;
088                            case Constants.FLAG_TYPE_GBCLEANUP:
089                                text.append(" Group Broadcast Cleanup");
090                                break;
091                            case Constants.FLAG_TYPE_GBCLEANACK:
092                                text.append(" Group Broadcast Cleanup ACK");
093                                break;
094                            case Constants.FLAG_TYPE_GBCLEANNAK:
095                                text.append(" Group Broadcast Cleanup NAK");
096                                break;
097                            default:
098                                log.warn("Unhandled flag type: {}", getElement(5) & Constants.FLAG_MASK_MSGTYPE);
099                                break;
100                        }
101                        text.append(" message,");
102                        text.append(String.format(" %d hops left", (getElement(5) & Constants.FLAG_MASK_HOPSLEFT >> Constants.FLAG_SHIFT_HOPSLEFT)));
103                        text.append(String.format(" , %d max hops", (getElement(5) & Constants.FLAG_MASK_MAXHOPS)));
104                        text.append(" addr " + String.format("%1$X.%2$X.%3$X", (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: " + StringUtil.twoHexFromInt(getElement(6) & 0xFF));
128                                break;
129                        }
130                    } else {
131                        text.append(" !! Length wrong: " + 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: " + StringUtil.twoHexFromInt(getElement(1) & 0xFF));
169                    text.append(" len: " + 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    static public 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    static public 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    static public 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    static public 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    static public 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    static public 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    static public SpecificMessage getInsteonFunction(int idhighbyte, int idmiddlebyte, int idlowbyte, int function, int flag, int cmd1, int cmd2) {
288        SpecificMessage m = new SpecificMessage(8);
289//        m.setInterlocked(true);
290        m.setInterlocked(false);
291        m.setElement(0, Constants.HEAD_STX);
292        m.setElement(1, Constants.FUNCTION_REQ_STD);
293        m.setElement(2, idhighbyte);
294        m.setElement(3, idmiddlebyte);
295        m.setElement(4, idlowbyte);
296        m.setElement(5, flag);
297        m.setElement(6, cmd1);
298        m.setElement(7, cmd2);
299        return m;
300    }
301
302    // initialize logging
303    private final static Logger log = LoggerFactory.getLogger(SpecificMessage.class);
304
305}