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}