001package jmri.jmrix.powerline.cp290;
002
003import jmri.jmrix.AbstractMRListener;
004import jmri.jmrix.AbstractMRMessage;
005import jmri.jmrix.AbstractMRReply;
006import jmri.jmrix.powerline.SerialListener;
007import jmri.jmrix.powerline.SerialMessage;
008import jmri.jmrix.powerline.SerialSystemConnectionMemo;
009import jmri.jmrix.powerline.SerialTrafficController;
010import jmri.jmrix.powerline.X10Sequence;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Converts Stream-based I/O to/from messages. The "SerialInterface" side
016 * sends/receives message objects.
017 * <p>
018 * The connection to a SerialPortController is via a pair of *Streams, which
019 * then carry sequences of characters for transmission. Note that this
020 * processing is handled in an independent thread.
021 * <p>
022 * This maintains a list of nodes, but doesn't currently do anything with it.
023 *
024 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2005, 2006, 2008 Converted to
025 * multiple connection
026 * @author kcameron Copyright (C) 2011
027 */
028public class SpecificTrafficController extends SerialTrafficController {
029
030    private boolean cmdOutstanding;
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    /**
045     * Send a sequence of X10 messages
046     * <p>
047     * Makes them into the local messages and then queues in order
048     */
049    @Override
050    synchronized public void sendX10Sequence(X10Sequence s, SerialListener l) {
051        s.reset();
052        X10Sequence.Command c;
053        // index through address commands
054        int devicemask = 0;
055        // there should be at least one address
056        c = s.getCommand();
057        if (c == null) {
058            return;  // nothing!
059        }
060        int housecode = c.getHouseCode();
061        devicemask = setDeviceBit(devicemask, ((X10Sequence.Address) c).getAddress());
062
063        // loop through other addresses, if any
064        while (((c = s.getCommand()) != null) && (c.isAddress())) {
065            if (housecode != ((X10Sequence.Address) c).getHouseCode()) {
066                log.error("multiple housecodes found: {}, {}", housecode, c.getHouseCode());
067                return;
068            }
069            devicemask = setDeviceBit(devicemask, ((X10Sequence.Address) c).getAddress());
070        }
071        // at this point, we've picked up all the addresses, start
072        // to process functions; there should be at least one
073        if (c == null) {
074            log.warn("no command");
075            return;
076        }
077        formatAndSend(housecode, devicemask, (X10Sequence.Function) c, l);
078
079        // loop through other functions, if any
080        while (((c = s.getCommand()) != null) && (c.isFunction())) {
081            if (housecode != ((X10Sequence.Function) c).getHouseCode()) {
082                log.error("multiple housecodes found: {}, {}", housecode, c.getHouseCode());
083                return;
084            }
085            formatAndSend(housecode, devicemask, (X10Sequence.Function) c, l);
086        }
087    }
088
089    /**
090     * Turn a 1-16 device number into a mask bit
091     * @param devicemask mask value
092     * @param device     X10 device code
093     * @return           bit mask for device code
094     */
095    int setDeviceBit(int devicemask, int device) {
096        return devicemask | (0x10000 >> device);
097    }
098
099    /**
100     * Format a message and send it
101     * @param housecode  X10 housecode value
102     * @param devicemask X10 devicemask
103     * @param c          X10 cmd code
104     * @param l          listener
105     */
106    void formatAndSend(int housecode, int devicemask,
107            X10Sequence.Function c, SerialListener l) {
108        SpecificMessage m = new SpecificMessage(22);  // will be 22 bytes
109        for (int i = 0; i < 16; i++) {
110            m.setElement(i, 0xFF);
111        }
112        int level = c.getDimCount();
113        if (level > 16) {
114            log.warn("can't handle dim counts > 15?");
115            level = 16;
116        }
117        if (logDebug) {
118            log.debug("dim level: {}", level);
119        }
120        level = 16 - level;
121        int function = c.getFunction();
122
123        // need to encode the housecode into line code
124        int lineHouseCode = X10Sequence.encode(housecode);
125
126        m.setElement(16, 1);
127        m.setElement(17, level * 16 + function);
128        m.setElement(18, lineHouseCode * 16 + 0);
129        m.setElement(19, devicemask & 0xFF);
130        m.setElement(20, (devicemask >> 8) & 0xFF);
131        m.setElement(21, 0xFF & (m.getElement(17) + m.getElement(18) + m.getElement(19) + m.getElement(20))); // checksum
132
133        sendSerialMessage(m, l);
134        cmdOutstanding = true;
135    }
136
137    /**
138     * This system provides 16 dim steps
139     */
140    @Override
141    public int getNumberOfIntensitySteps() {
142        return 16;
143    }
144
145    /**
146     * Get a message of a specific length for filling in.
147     */
148    @Override
149    public SerialMessage getSerialMessage(int length) {
150        return new SpecificMessage(length);
151    }
152
153    @Override
154    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
155        if (logDebug) {
156            log.debug("forward {}", m);
157        }
158        super.forwardToPort(m, reply);
159    }
160
161    @Override
162    protected AbstractMRReply newReply() {
163        SpecificReply reply = new SpecificReply(memo.getTrafficController());
164        return reply;
165    }
166
167    /**
168     * Decide if a reply have been completely received.
169     *
170     * @return true if the reply is complete
171     */
172    @Override
173    protected boolean endOfMessage(AbstractMRReply msg) {
174        // count number of FF bytes
175        // if 16 FF, byte 17 is 0x01, expect total of 22 bytes, direct msg
176        // if 16 FF, byte 17 is 0x02, expect total of 21 bytes, clock msg
177        // if 16 FF, byte 17 is 0x03, expect total of 28 bytes, timer msg
178        // if 16 FF, byte 17 is 0x03, expect total of 22 bytes, graphic msg
179        // if 16 FF, byte 17 is 0x04, expect total of 18 bytes, time/housecode msg
180        // if 6 FF, byte 7 is 0x00, means AC line was off
181        // if 6 FF, byte 7 is 0x01, means Ack of cmd, if command just sent
182        // if 6 FF, byte 7 is 0x01 or 0x00, but 12 bytes total is record of command on AC line
183        int syncCount = 0;
184        for (int i = 0; i < msg.getNumDataElements(); i++) {
185            if ((msg.getElement(i) & 0xFF) == 0xFF) {
186                syncCount++;
187            } else {
188                break;
189            }
190        }
191        if (cmdOutstanding) {
192            if (syncCount == 6) {
193                if (msg.getNumDataElements() == 7) {
194                    cmdOutstanding = false;
195                    return true;
196                }
197            }
198        } else {
199            if (syncCount == 6) {
200                if (msg.getNumDataElements() == 12) {
201                    return true;
202                }
203            }
204        }
205        return false;
206    }
207
208    private final static Logger log = LoggerFactory.getLogger(SpecificTrafficController.class);
209}