Added NexaDimmer and Cmd classes to simplify sending tellstick commands

This commit is contained in:
Ziver Koc 2016-08-29 17:03:58 +02:00
parent 5351e56be7
commit c0e2fccce5
11 changed files with 334 additions and 48 deletions

View file

@ -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<TellstickDecodedEntry> decode(byte[] data);
public TellstickCmd encode(HalEventConfig deviceConfig, HalEventData deviceData){ return null; }
public abstract List<TellstickDecodedEntry> decode(byte[] data);
public static class TellstickDecodedEntry {

View file

@ -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());

View file

@ -0,0 +1,10 @@
package se.hal.plugin.tellstick.cmd;
/**
* Created by Ziver on 2016-08-29.
*/
public interface TellstickCmd {
String getTransmissionString();
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -107,7 +107,6 @@ public class NexaSelfLearning implements HalEventConfig,TellstickDevice,Tellstic
public Class<? extends HalEventController> getEventControllerClass() {
return TellstickSerialComm.class;
}
@Override
public Class<? extends HalEventData> getEventDataClass() {
return SwitchEventData.class;

View file

@ -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<? extends HalEventController> getEventControllerClass() {
return TellstickSerialComm.class;
}
@Override
public Class<? extends HalEventData> getEventDataClass() {
return DimmerEventData.class;
}
@Override
public String getProtocolName() { return NexaSelfLearningProtocol.PROTOCOL; }
@Override
public String getModelName() { return NexaSelfLearningProtocol.MODEL; }
}

View file

@ -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 <a href="https://github.com/telldus/telldus/blob/master/telldus-core/service/ProtocolNexa.cpp">ProtocolNexa.cpp Tellstick Reference</a>
*/
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);

View file

@ -18,6 +18,7 @@ import java.util.logging.Logger;
/**
* Created by Ziver on 2015-11-19.
* @see <a href="https://github.com/telldus/telldus/blob/master/telldus-core/service/ProtocolOregon.cpp">ProtocolOregon.cpp Tellstick Reference</a>
*/
public class Oregon0x1A2DProtocol extends TellstickProtocol {
private static final Logger logger = LogUtil.getLogger();

View file

@ -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;

View file

@ -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());