diff --git a/src/se/hal/plugin/tellstick/TellstickProtocol.java b/src/se/hal/plugin/tellstick/TellstickProtocol.java index deb3c904..fe7a2884 100755 --- a/src/se/hal/plugin/tellstick/TellstickProtocol.java +++ b/src/se/hal/plugin/tellstick/TellstickProtocol.java @@ -24,6 +24,7 @@ package se.hal.plugin.tellstick; import se.hal.intf.HalDeviceData; import se.hal.intf.HalEventConfig; import se.hal.intf.HalEventData; +import se.hal.plugin.tellstick.cmd.TellstickCmd; import java.util.List; @@ -49,9 +50,9 @@ public abstract class TellstickProtocol { return model; } - public String encode(HalEventConfig deviceConfig, HalEventData deviceData){ return null; } - public abstract List decode(byte[] data); + public TellstickCmd encode(HalEventConfig deviceConfig, HalEventData deviceData){ return null; } + public abstract List decode(byte[] data); public static class TellstickDecodedEntry { diff --git a/src/se/hal/plugin/tellstick/TellstickSerialComm.java b/src/se/hal/plugin/tellstick/TellstickSerialComm.java index db267b85..9f8c62cb 100755 --- a/src/se/hal/plugin/tellstick/TellstickSerialComm.java +++ b/src/se/hal/plugin/tellstick/TellstickSerialComm.java @@ -26,6 +26,7 @@ import com.fazecast.jSerialComm.SerialPort; import se.hal.HalContext; import se.hal.intf.*; import se.hal.plugin.tellstick.TellstickProtocol.TellstickDecodedEntry; +import se.hal.plugin.tellstick.cmd.TellstickCmd; import zutil.log.LogUtil; import zutil.struct.TimedHashSet; @@ -34,7 +35,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.concurrent.Executors; import java.util.logging.Level; @@ -42,8 +42,8 @@ import java.util.logging.Logger; /** - * This version of the TwoWaySerialComm example makes use of the - * SerialPortEventListener to avoid polling. + * Tellstick serial port controller, this class handles all tellstick + * communication and reporting to Hal */ public class TellstickSerialComm implements Runnable, HalSensorController, HalEventController, HalAutoScannableController { @@ -194,7 +194,9 @@ public class TellstickSerialComm implements Runnable, TellstickProtocol prot = TellstickParser.getProtocolInstance( tellstickDevice.getProtocolName(), tellstickDevice.getModelName()); - write(prot.encode(deviceConfig, deviceData)); + TellstickCmd cmd = prot.encode(deviceConfig, deviceData); + if (cmd != null) + write(cmd.getTransmissionString()); parser.waitSendConformation(); deviceData.setTimestamp(System.currentTimeMillis()); diff --git a/src/se/hal/plugin/tellstick/cmd/TellstickCmd.java b/src/se/hal/plugin/tellstick/cmd/TellstickCmd.java new file mode 100755 index 00000000..8d3b7c53 --- /dev/null +++ b/src/se/hal/plugin/tellstick/cmd/TellstickCmd.java @@ -0,0 +1,10 @@ +package se.hal.plugin.tellstick.cmd; + + +/** + * Created by Ziver on 2016-08-29. + */ +public interface TellstickCmd { + + String getTransmissionString(); +} diff --git a/src/se/hal/plugin/tellstick/cmd/TellstickCmdExtendedSend.java b/src/se/hal/plugin/tellstick/cmd/TellstickCmdExtendedSend.java new file mode 100755 index 00000000..4e64453f --- /dev/null +++ b/src/se/hal/plugin/tellstick/cmd/TellstickCmdExtendedSend.java @@ -0,0 +1,92 @@ +package se.hal.plugin.tellstick.cmd; + +import java.nio.charset.StandardCharsets; + +/** + * Created by Ziver on 2016-08-29. + */ +public class TellstickCmdExtendedSend implements TellstickCmd{ + private static int OFFSET_TIMINGS = 1; + private static int OFFSET_PULSE_LENGTH = 5; + private static int OFFSET_PULSES = 6; + + private byte[] cmd = new byte[79]; + private int length = 0; + + + /** + * @param timing set first timing in us + * @return an instance of itself + */ + public TellstickCmdExtendedSend setPulls0Timing(int timing){ + setPullsTiming(0, timing); return this; + } + /** + * @param timing set second timing in us + * @return an instance of itself + */ + public TellstickCmdExtendedSend setPulls1Timing(int timing){ + setPullsTiming(1, timing); return this; + } + /** + * @param timing set third timing in us + * @return an instance of itself + */ + public TellstickCmdExtendedSend setPulls2Timing(int timing){ + setPullsTiming(2, timing); return this; + } + /** + * @param timing set fourth timing in us + * @return an instance of itself + */ + public TellstickCmdExtendedSend setPulls3Timing(int timing){ + setPullsTiming(3, timing); return this; + } + /** + * @param i an index from 0 to 3 + * @param timing set first pulls length in us between 0-2550 + * @return an instance of itself + */ + private void setPullsTiming(int i, int timing){ // TODO: should probably have high and low timing to be called pulls + if (0 > timing || timing > 2550) + throw new IllegalArgumentException("Invalid pulls "+timing+" must be between 0-2550" ); + cmd[OFFSET_TIMINGS + i] = (byte)(timing/10); + } + + + public TellstickCmdExtendedSend addPulls0(){ + addPulls(0); return this; + } + public TellstickCmdExtendedSend addPulls1(){ + addPulls(1); return this; + } + public TellstickCmdExtendedSend addPulls2(){ + addPulls(2); return this; + } + public TellstickCmdExtendedSend addPulls3(){ + addPulls(3); return this; + } + private void addPulls(int i) { + if (OFFSET_PULSES+(length/4) > cmd.length) + throw new IndexOutOfBoundsException("Maximum length "+cmd.length+" reached"); + switch (length % 4){ + case 0: + cmd[OFFSET_PULSES+ length/4] |= 0b1100_0000 & (i << 6); break; + case 1: + cmd[OFFSET_PULSES+ length/4] |= 0b0011_0000 & (i << 4); break; + case 2: + cmd[OFFSET_PULSES+ length/4] |= 0b0000_1100 & (i << 2); break; + case 3: + cmd[OFFSET_PULSES+ length/4] |= 0b0000_0011 & (i); break; + } + length++; + } + + + public String getTransmissionString(){ + cmd[0] = 'T'; + cmd[OFFSET_PULSE_LENGTH] = (byte)length; + cmd[OFFSET_PULSES+(int)Math.ceil(length/4.0)] = '+'; + return new String(cmd, 0, OFFSET_PULSES+(int)Math.ceil(length/4.0)+1, StandardCharsets.ISO_8859_1); + } +} diff --git a/src/se/hal/plugin/tellstick/cmd/TellstickCmdSend.java b/src/se/hal/plugin/tellstick/cmd/TellstickCmdSend.java new file mode 100755 index 00000000..ae3bb182 --- /dev/null +++ b/src/se/hal/plugin/tellstick/cmd/TellstickCmdSend.java @@ -0,0 +1,35 @@ +package se.hal.plugin.tellstick.cmd; + +import java.nio.charset.StandardCharsets; + +/** + * Created by Ziver on 2016-08-29. + */ +public class TellstickCmdSend implements TellstickCmd{ + private static int OFFSET_PULSES = 1; + + private byte[] cmd = new byte[79]; + private int length = 0; + + + + /** + * @param timing adds a pulls timing in us between 0-2550 + * @return an instance of itself + */ + private void addPulls(int timing) { // TODO: should probably have high and low timing to be called pulls + if (OFFSET_PULSES+length > cmd.length) + throw new IndexOutOfBoundsException("Maximum length "+cmd.length+" reached"); + if (0 > timing || timing > 2550) + throw new IllegalArgumentException("Invalid pulls "+timing+" must be between 0-2550" ); + cmd[OFFSET_PULSES+length] = (byte)(timing/10); + length++; + } + + + public String getTransmissionString(){ + cmd[0] = 'S'; + cmd[OFFSET_PULSES+length] = '+'; + return new String(cmd, 0, OFFSET_PULSES+length+1, StandardCharsets.ISO_8859_1); + } +} diff --git a/src/se/hal/plugin/tellstick/device/NexaSelfLearning.java b/src/se/hal/plugin/tellstick/device/NexaSelfLearning.java index dde999e1..2af9b383 100755 --- a/src/se/hal/plugin/tellstick/device/NexaSelfLearning.java +++ b/src/se/hal/plugin/tellstick/device/NexaSelfLearning.java @@ -107,7 +107,6 @@ public class NexaSelfLearning implements HalEventConfig,TellstickDevice,Tellstic public Class getEventControllerClass() { return TellstickSerialComm.class; } - @Override public Class getEventDataClass() { return SwitchEventData.class; diff --git a/src/se/hal/plugin/tellstick/device/NexaSelfLearningDimmer.java b/src/se/hal/plugin/tellstick/device/NexaSelfLearningDimmer.java new file mode 100755 index 00000000..c333ffb0 --- /dev/null +++ b/src/se/hal/plugin/tellstick/device/NexaSelfLearningDimmer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 Ziver + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package se.hal.plugin.tellstick.device; + +import se.hal.intf.HalEventConfig; +import se.hal.intf.HalEventController; +import se.hal.intf.HalEventData; +import se.hal.plugin.tellstick.TellstickDevice; +import se.hal.plugin.tellstick.TellstickDeviceGroup; +import se.hal.plugin.tellstick.TellstickSerialComm; +import se.hal.plugin.tellstick.protocol.NexaSelfLearningProtocol; +import se.hal.struct.devicedata.DimmerEventData; +import se.hal.struct.devicedata.SwitchEventData; +import zutil.ui.Configurator; + +/** + * Created by Ziver on 2015-02-18. + */ +public class NexaSelfLearningDimmer implements HalEventConfig,TellstickDevice { + + @Configurator.Configurable("House code") + private int house = 0; + + @Configurator.Configurable("Unit code") + private int unit = 0; + + + public NexaSelfLearningDimmer() { } + public NexaSelfLearningDimmer(int house, int unit) { + this.house = house; + this.unit = unit; + } + + + + public int getHouse() { + return house; + } + public void setHouse(int house) { + this.house = house; + } + public int getUnit() { + return unit; + } + public void setUnit(int unit) { + this.unit = unit; + } + + + + public String toString(){ + return "house:"+house+ + ", unit:"+unit; + } + + @Override + public boolean equals(Object obj){ + if(obj instanceof NexaSelfLearningDimmer) + return ((NexaSelfLearningDimmer) obj).house == house && + ((NexaSelfLearningDimmer)obj).unit == unit; + return false; + } + + + @Override + public Class getEventControllerClass() { + return TellstickSerialComm.class; + } + @Override + public Class getEventDataClass() { + return DimmerEventData.class; + } + + @Override + public String getProtocolName() { return NexaSelfLearningProtocol.PROTOCOL; } + @Override + public String getModelName() { return NexaSelfLearningProtocol.MODEL; } +} diff --git a/src/se/hal/plugin/tellstick/protocol/NexaSelfLearningProtocol.java b/src/se/hal/plugin/tellstick/protocol/NexaSelfLearningProtocol.java index a481e15e..8a44a9ad 100755 --- a/src/se/hal/plugin/tellstick/protocol/NexaSelfLearningProtocol.java +++ b/src/se/hal/plugin/tellstick/protocol/NexaSelfLearningProtocol.java @@ -24,8 +24,12 @@ package se.hal.plugin.tellstick.protocol; import se.hal.intf.HalEventConfig; import se.hal.intf.HalEventData; +import se.hal.plugin.tellstick.cmd.TellstickCmd; +import se.hal.plugin.tellstick.cmd.TellstickCmdExtendedSend; import se.hal.plugin.tellstick.TellstickProtocol; import se.hal.plugin.tellstick.device.NexaSelfLearning; +import se.hal.plugin.tellstick.device.NexaSelfLearningDimmer; +import se.hal.struct.devicedata.DimmerEventData; import se.hal.struct.devicedata.SwitchEventData; import zutil.ByteUtil; import zutil.log.LogUtil; @@ -41,6 +45,7 @@ import java.util.logging.Logger; /** * Created by Ziver on 2015-02-18. + * @see ProtocolNexa.cpp Tellstick Reference */ public class NexaSelfLearningProtocol extends TellstickProtocol { private static final Logger logger = LogUtil.getLogger(); @@ -49,18 +54,23 @@ public class NexaSelfLearningProtocol extends TellstickProtocol { private static class NexaSLTransmissionStruct implements BinaryStruct{ - @BinaryField(index=1, length=26) + @BinaryField(index=10, length=26) int house = 0; - @BinaryField(index=2, length=1) + @BinaryField(index=20, length=1) boolean group = false; - @BinaryField(index=3, length=1) + @BinaryField(index = 30, length = 1) boolean enable = false; - @BinaryField(index=4, length=4) + @BinaryField(index=40, length=4) int unit = 0; } + private static class NexaSLTransmissionDimmerStruct extends NexaSLTransmissionStruct { + @BinaryField(index = 50, length = 4) + int dimLevel = 0; + } + public NexaSelfLearningProtocol() { @@ -68,45 +78,52 @@ public class NexaSelfLearningProtocol extends TellstickProtocol { } @Override - public String encode(HalEventConfig deviceConfig, HalEventData deviceData){ - if ( ! (deviceConfig instanceof NexaSelfLearning)){ + public TellstickCmd encode(HalEventConfig deviceConfig, HalEventData deviceData){ + if ( ! (deviceConfig instanceof NexaSelfLearning || deviceConfig instanceof NexaSelfLearningDimmer)){ logger.severe("Device config is not instance of NexaSelfLearning: "+deviceConfig.getClass()); return null; } - if ( ! (deviceData instanceof SwitchEventData)){ - logger.severe("Device data is not instance of SwitchEventData: "+deviceData.getClass()); + if ( ! (deviceData instanceof SwitchEventData || deviceData instanceof DimmerEventData)){ + logger.severe("Device data is not an instance of SwitchEventData or DimmerEventData: "+deviceData.getClass()); return null; } - NexaSLTransmissionStruct struct = new NexaSLTransmissionStruct(); - struct.house = ((NexaSelfLearning) deviceConfig).getHouse(); - struct.group = ((NexaSelfLearning) deviceConfig).getGroup(); - struct.enable = ((SwitchEventData) deviceData).isOn(); - struct.unit = ((NexaSelfLearning) deviceConfig).getUnit(); + // Create transmission struct + NexaSLTransmissionStruct struct; + if (deviceData instanceof DimmerEventData) { + struct = new NexaSLTransmissionDimmerStruct(); + struct.house = ((NexaSelfLearningDimmer) deviceConfig).getHouse(); + struct.unit = ((NexaSelfLearningDimmer) deviceConfig).getUnit(); + ((NexaSLTransmissionDimmerStruct)struct).dimLevel = (int)(deviceData.getData()*16); + } + else { + struct = new NexaSLTransmissionStruct(); + struct.house = ((NexaSelfLearning) deviceConfig).getHouse(); + struct.group = ((NexaSelfLearning) deviceConfig).getGroup(); + struct.unit = ((NexaSelfLearning) deviceConfig).getUnit(); + struct.enable = ((SwitchEventData) deviceData).isOn(); + } + + + // Generate transmission string try { - // T[t0][t1][t2][t3][length][d1]..[dn]+ - StringBuilder enc = new StringBuilder(90); // Tellstick supports max 74 bytes - enc.append(new char[]{'T', 127, 255, 24, 0}); - enc.append((char)0); // length + TellstickCmdExtendedSend cmd = new TellstickCmdExtendedSend(); + cmd.setPulls0Timing(1270); + cmd.setPulls1Timing(2550); + cmd.setPulls2Timing(240); - enc.append((char)0b0000_1001); // preamble - int length = 4; + cmd.addPulls2().addPulls1(); // preamble byte[] data = BinaryStructOutputStream.serialize(struct); for (byte b : data){ for (int i=7; i>=0; --i){ - if (ByteUtil.getBits(b, i, 1) == 0) - enc.append((char) 0b1010_1000); // 0b1010_1000 + if (ByteUtil.getBits(b, i, 1) == 0) // 0 + cmd.addPulls2().addPulls2().addPulls2().addPulls0(); // 0b1010_1000 else // 1 - enc.append((char) 0b1000_1010); // 0b1000_1010 - length += 4; + cmd.addPulls2().addPulls0().addPulls2().addPulls2(); // 0b1000_1010 } } - enc.append((char)0b0000_0000); // postemble - length += 2; - enc.setCharAt(5, (char)length); // Set calculated length - - enc.append("+"); - return enc.toString(); + //cmd.addPulls2().addPulls2(); // postemble? + return cmd; } catch (IOException e) { logger.log(Level.SEVERE, null, e); diff --git a/src/se/hal/plugin/tellstick/protocol/Oregon0x1A2DProtocol.java b/src/se/hal/plugin/tellstick/protocol/Oregon0x1A2DProtocol.java index f571c71b..f77556c8 100755 --- a/src/se/hal/plugin/tellstick/protocol/Oregon0x1A2DProtocol.java +++ b/src/se/hal/plugin/tellstick/protocol/Oregon0x1A2DProtocol.java @@ -18,6 +18,7 @@ import java.util.logging.Logger; /** * Created by Ziver on 2015-11-19. + * @see ProtocolOregon.cpp Tellstick Reference */ public class Oregon0x1A2DProtocol extends TellstickProtocol { private static final Logger logger = LogUtil.getLogger(); diff --git a/src/se/hal/struct/devicedata/DimmerEventData.java b/src/se/hal/struct/devicedata/DimmerEventData.java index f658f712..a61739c4 100755 --- a/src/se/hal/struct/devicedata/DimmerEventData.java +++ b/src/se/hal/struct/devicedata/DimmerEventData.java @@ -38,6 +38,9 @@ public class DimmerEventData extends HalEventData { } + /** + * @return the dim level from 0.0 to 1.0 + */ @Override public double getData() { return dimmValue; diff --git a/test/se/hal/plugin/tellstick/protocol/NexaSelfLearningTest.java b/test/se/hal/plugin/tellstick/protocol/NexaSelfLearningTest.java index 54362024..e6924155 100755 --- a/test/se/hal/plugin/tellstick/protocol/NexaSelfLearningTest.java +++ b/test/se/hal/plugin/tellstick/protocol/NexaSelfLearningTest.java @@ -25,6 +25,8 @@ package se.hal.plugin.tellstick.protocol; import se.hal.plugin.tellstick.TellstickProtocol; import se.hal.plugin.tellstick.TellstickProtocol.TellstickDecodedEntry; import se.hal.plugin.tellstick.device.NexaSelfLearning; +import se.hal.plugin.tellstick.device.NexaSelfLearningDimmer; +import se.hal.struct.devicedata.DimmerEventData; import se.hal.struct.devicedata.SwitchEventData; import zutil.converter.Converter; @@ -36,28 +38,54 @@ import static org.junit.Assert.*; public class NexaSelfLearningTest { @org.junit.Test - public void testEncode() throws Exception { + public void testSwitchEncode() throws Exception { NexaSelfLearning nexaDevice = new NexaSelfLearning(); nexaDevice.setHouse(11_772_006); - nexaDevice.setUnit(0); - SwitchEventData nexaData = new SwitchEventData(); - nexaData.turnOn(); + nexaDevice.setUnit(3); + SwitchEventData nexaData = new SwitchEventData(true); byte[] expected = Converter.toBytes(new char[]{ 84, // T 127, 255, 24, 0, // timings - 134, // length + 130, // length - 0x09, // preamble - 168, 168, 138, 168, 138, 138, 168, 168, 138, 138, - 138, 168, 138, 168, 168, 168, 168, 168, 168, 138, - 138, 168, 168, 138, 138, 168, 168, 138, 168, 168, - 168, 168, - 0x00, // postemble + 154, 138, 136, 170, 136, 168, 170, 138, 136, 168, + 168, 170, 136, 170, 138, 138, 138, 138, 138, 136, + 168, 170, 138, 136, 168, 170, 138, 136, 170, 138, + 136, 168, 160, 43}); // + NexaSelfLearningProtocol nexaProt = new NexaSelfLearningProtocol(); - byte[] actual = nexaProt.encode(nexaDevice, nexaData).getBytes(StandardCharsets.ISO_8859_1); + byte[] actual = nexaProt.encode(nexaDevice, nexaData).getTransmissionString() + .getBytes(StandardCharsets.ISO_8859_1); + + System.out.println("Expected: "+Converter.toHexString(expected).toUpperCase()); + System.out.println("Actual : "+Converter.toHexString(actual).toUpperCase()); + assertArrayEquals(expected, actual); + } + + @org.junit.Test + public void testDimmerEncode() throws Exception { + NexaSelfLearningDimmer nexaDevice = new NexaSelfLearningDimmer(); + nexaDevice.setHouse(11_772_006); + nexaDevice.setUnit(3); + DimmerEventData nexaData = new DimmerEventData(0.5); + + byte[] expected = Converter.toBytes(new char[]{ + 84, // T + 127, 255, 24, 0, // timings + 162, // length, 32 extra timings + + 154, 138, 136, 170, 136, 168, 170, 138, 136, 168, + 168, 170, 136, 170, 138, 138, 138, 138, 138, 136, + 168, 170, 138, 136, 168, 170, 138, + 138, 138, 138, 136, 168, + 168, 170, 138, 138, 138, 138, 138, 138, 128, // Dimer value + + 43}); // + + NexaSelfLearningProtocol nexaProt = new NexaSelfLearningProtocol(); + byte[] actual = nexaProt.encode(nexaDevice, nexaData).getTransmissionString() + .getBytes(StandardCharsets.ISO_8859_1); System.out.println("Expected: "+Converter.toHexString(expected).toUpperCase()); System.out.println("Actual : "+Converter.toHexString(actual).toUpperCase());