001package jmri.jmrix.powerline.simulator;
002
003import java.io.DataInputStream;
004import jmri.jmrix.AbstractMRListener;
005import jmri.jmrix.AbstractMRMessage;
006import jmri.jmrix.AbstractMRReply;
007import jmri.jmrix.powerline.InsteonSequence;
008import jmri.jmrix.powerline.SerialListener;
009import jmri.jmrix.powerline.SerialMessage;
010import jmri.jmrix.powerline.SerialSystemConnectionMemo;
011import jmri.jmrix.powerline.SerialTrafficController;
012import jmri.jmrix.powerline.X10Sequence;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Converts Stream-based I/O to/from messages. The "SerialInterface" side
018 * sends/receives message objects.
019 * <p>
020 * The connection to a SerialPortController is via a pair of *Streams, which
021 * then carry sequences of characters for transmission. Note that this
022 * processing is handled in an independent thread.
023 * <p>
024 * This maintains a list of nodes, but doesn't currently do anything with it.
025 *
026 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2005, 2006, 2008, 2009
027 * @author Ken Cameron Copyright (C) 2010 Converted to multiple connection
028 * @author kcameron Copyright (C) 2011
029 */
030public class SpecificTrafficController extends SerialTrafficController {
031
032    public SpecificTrafficController(SerialSystemConnectionMemo memo) {
033        super();
034        this.memo = memo;
035        logDebug = log.isDebugEnabled();
036
037        // not polled at all, so allow unexpected messages, and
038        // use poll delay just to spread out startup
039        setAllowUnexpectedReply(true);
040        mWaitBeforePoll = 1000;  // can take a long time to send
041    }
042
043    /**
044     * Send a sequence of X10 messages.
045     * <p>
046     * Makes them into the local messages and then queues in order.
047     */
048    @Override
049    synchronized public void sendX10Sequence(X10Sequence s, SerialListener l) {
050        s.reset();
051        X10Sequence.Command c;
052        while ((c = s.getCommand()) != null) {
053            SpecificMessage m;
054            if (c.isAddress()) {
055                m = SpecificMessage.getX10Address(c.getHouseCode(), ((X10Sequence.Address) c).getAddress());
056            } else if (c.isFunction()) {
057                X10Sequence.Function f = (X10Sequence.Function) c;
058                if (f.getDimCount() > 0) {
059                    m = SpecificMessage.getX10FunctionDim(f.getHouseCode(), f.getFunction(), f.getDimCount());
060                } else {
061                    m = SpecificMessage.getX10Function(f.getHouseCode(), f.getFunction());
062                }
063            } else {
064                // isn't address or function
065                X10Sequence.ExtData e = (X10Sequence.ExtData) c;
066                m = SpecificMessage.getExtCmd(c.getHouseCode(), e.getAddress(), e.getExtCmd(), e.getExtData());
067            }
068            sendSerialMessage(m, l);
069            // Someone help me improve this
070            // Without this wait, the commands are too close together and will return
071            // an 0x15 which means they failed.
072            // But there must be a better way to delay the sending of the next command.
073            try {
074                wait(250);
075            } catch (InterruptedException ex) {
076                log.error("Interrupted Exception", ex);
077            }
078        }
079    }
080
081    /**
082     * Send a sequence of Insteon messages.
083     * <p>
084     * Makes them into the local messages and then queues in order.
085     */
086    @Override
087    synchronized public void sendInsteonSequence(InsteonSequence s, SerialListener l) {
088        s.reset();
089        InsteonSequence.Command c;
090        while ((c = s.getCommand()) != null) {
091            SpecificMessage m;
092            if (c.isAddress()) {
093                // We should not get here
094                // Clean this up later
095                m = SpecificMessage.getInsteonAddress(-1, -1, -1);
096            } else {
097                InsteonSequence.Function f = (InsteonSequence.Function) c;
098                m = SpecificMessage.getInsteonFunction(f.getAddressHigh(), f.getAddressMiddle(), f.getAddressLow(), f.getFunction(), f.getFlag(), f.getCommand1(), f.getCommand2());
099            }
100            sendSerialMessage(m, l);
101            // Someone help me improve this
102            // Without this wait, the commands are too close together and will return
103            // an 0x15 which means they failed.
104            // But there must be a better way to delay the sending of the next command.
105 /*
106             try {
107             wait(250);
108             } catch (InterruptedException ex) {
109             log.error("", ex);
110             }
111             */
112        }
113    }
114
115    /**
116     * Get a message of a specific length for filling in.
117     */
118    @Override
119    public SerialMessage getSerialMessage(int length) {
120        return new SpecificMessage(length);
121    }
122
123    @Override
124    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
125        if (logDebug) {
126            log.debug("forward {}", m);
127        }
128        super.forwardToPort(m, reply);
129    }
130
131    @Override
132    protected AbstractMRReply newReply() {
133        SpecificReply reply = new SpecificReply(memo.getTrafficController());
134        return reply;
135    }
136
137    @Override
138    protected boolean endOfMessage(AbstractMRReply msg) {
139        if (msg.getNumDataElements() >= 2) {
140            if (msg.getElement(0) != Constants.HEAD_STX) {
141                return false;
142            }
143            int cmd = msg.getElement(1);
144            switch (msg.getNumDataElements()) {
145                case 2:
146                    if (cmd == Constants.POLL_REQ_BUTTON_RESET) {
147                        return true;
148                    }
149                    break;
150                case 3:
151                    if (cmd == Constants.POLL_REQ_BUTTON) {
152                        return true;
153                    }
154                    break;
155                case 4:
156                    if (cmd == Constants.POLL_REQ_X10) {
157                        return true;
158                    }
159                    break;
160                case 5: // reply from send X10 command
161                    if (cmd == Constants.FUNCTION_REQ_X10) {
162                        return true;
163                    }
164                    break;
165                case 11:
166                    if (cmd == Constants.POLL_REQ_STD) {
167                        return true;
168                    }
169                    break;
170                case 12: // reply from send standard Insteon command
171                    if ((cmd == Constants.FUNCTION_REQ_STD) && ((msg.getElement(5) & Constants.FLAG_BIT_STDEXT) == Constants.FLAG_STD)) {
172                        return true;
173                    }
174                    break;
175                case 25:
176                    if (cmd == Constants.POLL_REQ_EXT) {
177                        return true;
178                    }
179                    break;
180                case 26: // reply from send extended Insteon command
181                    if ((cmd == Constants.FUNCTION_REQ_STD) && ((msg.getElement(5) & Constants.FLAG_BIT_STDEXT) == Constants.FLAG_EXT)) {
182                        return true;
183                    }
184                    break;
185                default:
186                    break;
187            }
188        }
189        if (logDebug) {
190            log.debug("end of message: {}", msg);
191        }
192        return false;
193    }
194
195    /**
196     * Read a stream and pick packets out of it. Knows the size of the packets
197     * from the contents.
198     */
199    @Override
200    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
201        byte char1 = readByteProtected(istream);
202        if (logDebug) {
203            log.debug("loadChars: {}", char1);
204        }
205        if ((char1 & 0xFF) == Constants.HEAD_STX) {  // 0x02 means start of command.
206            msg.setElement(0, char1);
207            byte char2 = readByteProtected(istream);
208            if ((char2 & 0xFF) == Constants.FUNCTION_REQ_STD) {  // 0x62 means normal send command reply.
209                msg.setElement(1, char2);
210                byte addr1 = readByteProtected(istream);
211                msg.setElement(2, addr1);
212                byte addr2 = readByteProtected(istream);
213                msg.setElement(3, addr2);
214                byte addr3 = readByteProtected(istream);
215                msg.setElement(4, addr3);
216                byte flag1 = readByteProtected(istream);
217                msg.setElement(5, flag1);
218                int bufsize = 2 + 1;
219                if ((flag1 & Constants.FLAG_BIT_STDEXT) != 0x00) {
220                    bufsize = 14 + 1;
221                }
222                for (int i = 6; i < (5 + bufsize); i++) {
223                    byte byt = readByteProtected(istream);
224                    msg.setElement(i, byt);
225                }
226            } else if ((char2 & 0xFF) == Constants.FUNCTION_REQ_X10) {  // 0x63 means normal send X10 command reply.
227                msg.setElement(1, char2);
228                byte addrx1 = readByteProtected(istream);
229                msg.setElement(2, addrx1);
230                byte cmd1 = readByteProtected(istream);
231                msg.setElement(3, cmd1);
232                byte ack1 = readByteProtected(istream);
233                msg.setElement(4, ack1);
234            } else if ((char2 & 0xFF) == Constants.POLL_REQ_STD) {  // 0x50 means normal command received.
235                msg.setElement(1, char2);
236                for (int i = 2; i < (2 + 9); i++) {
237                    byte byt = readByteProtected(istream);
238                    msg.setElement(2, byt);
239                }
240            } else if ((char2 & 0xFF) == Constants.POLL_REQ_EXT) {  // 0x51 means extended command received.
241                msg.setElement(1, char2);
242                for (int i = 2; i < (2 + 23); i++) {
243                    byte byt = readByteProtected(istream);
244                    msg.setElement(2, byt);
245                }
246            } else if ((char2 & 0xFF) == Constants.POLL_REQ_X10) {  // 0x52 means standard X10 received command.
247                msg.setElement(1, char2);
248                byte rawX10data = readByteProtected(istream);
249                msg.setElement(2, rawX10data);
250                byte x10Flag = readByteProtected(istream);
251                msg.setElement(3, x10Flag);
252                if ((x10Flag&0xFF) == Constants.FLAG_X10_RECV_CMD) {
253                    if (logDebug) {
254                        log.debug("loadChars: X10 Command Poll Received {} {}", X10Sequence.houseValueToText((rawX10data & 0xF0) >> 4), X10Sequence.functionName((rawX10data & 0x0F)));
255                    }
256                } else {
257                    if (logDebug) {
258                        log.debug("loadChars: X10 Unit Poll Received {} {}", X10Sequence.houseValueToText((rawX10data & 0xF0) >> 4), X10Sequence.formatCommandByte(rawX10data));
259                    }
260                }
261            } else if ((char2 & 0xFF) == Constants.POLL_REQ_BUTTON) {  // 0x54 means interface button received command.
262                msg.setElement(1, char2);
263                byte dat = readByteProtected(istream);
264                msg.setElement(2, dat);
265            } else if ((char2 & 0xFF) == Constants.POLL_REQ_BUTTON_RESET) {  // 0x55 means interface button received command.
266                msg.setElement(1, char2);
267            } else {
268                msg.setElement(1, char2);
269                if (logDebug) {
270                    log.debug("loadChars: Unknown cmd byte {}", char2);
271                }
272            }
273        }
274    }
275
276    private final static Logger log = LoggerFactory.getLogger(SpecificTrafficController.class);
277
278}