/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.smarthomatic.internal;
import java.util.StringTokenizer;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* these class represents the basestation and the communication between openhab
* and the smarthomatic basestation
* {@link https://www.smarthomatic.org/devices/base_station.html}
*
* @author mcjobo
* @author arohde
* @since 1.9.0
*/
public class BaseStation implements SerialEventWorker {
private static final Logger logger = LoggerFactory.getLogger(BaseStation.class);
private SerialDevice serialDevice;
private String[] versionInfo;
private SerialEventWorker bindingEventWorker;
/**
* public constructor for BaseStation
*
* @param port
* of the serial line uses eg com4 or /dev/ttyUSB0
* @param baud
* rate used eg 19200
* @param sew
* used event worker
*/
public BaseStation(String port, int baud, SerialEventWorker sew) {
bindingEventWorker = sew;
serialDevice = new SerialDevice(port, baud);
serialDevice.setEventWorker(this);
try {
serialDevice.initialize();
} catch (InitializationException e) {
logger.error("Port cannot be opened. Port: {}", port);
logger.error("initialization exception", e);
}
}
/**
* closes the serial port
*
*/
public void closeSerialPort() {
serialDevice.close();
}
private String genHexString(int deviceID, int length) {
String s = Integer.toHexString(deviceID);
int len = s.length();
for (int i = len; i < length; i++) {
s = "0" + s;
}
return s;
}
private String getToggleTime(int toggleTime, boolean on) {
// shift left the on bit, so that the on-bit stands on position 17 from
// right
long temp = (on ? 1 : 0) << 16;
// patch toggletime into the last 16 bit and be sure to prevent to
// override on-bit
temp |= (toggleTime & 0x0001FFFF);
// shift temp to left by 15 bits (the on bit should stand at msb
// position)
temp <<= 15;
// write hex representation of temp to s
String s = Long.toHexString(temp);
// fill String with leading zeros until it's minimum 2 chars long
for (int i = s.length(); i < 2; i++) {
s = "0" + s;
}
return s;
}
public String prepareCommand(int deviceID, int messageGroupId, int messageId, int toggleTime, Command command) {
String cmd = "";
String messageData = "";
// GPIO Digital Port Message
if (messageGroupId == 1 && messageId == 1 && command instanceof DecimalType) {
int setting = ((DecimalType) command).intValue();
// message data length = 8 bits, no need to shift
messageData = genHexString(setting, 2);
} else if (messageGroupId == 1 && messageId == 5 && command instanceof DecimalType) {
// GPIO Digital Pin Message
int setting = ((DecimalType) command).intValue();
messageData = genHexString(setting, 1);
} else if (messageGroupId == 1 && messageId == 6 && command instanceof DecimalType) {
// GPIO Digital Pin Timeout Message
int setting = ((DecimalType) command).intValue();
setting = setting << 4; // message data length = 20 bits
messageData = genHexString(setting, 5);
} else if (messageGroupId == 60 && messageId == 1 && command instanceof DecimalType) {
// Dimmer Brightness Message
int brightness = ((DecimalType) command).intValue();
brightness = brightness << 1;
messageData = Integer.toHexString(brightness);
} else if (messageGroupId == 60 && messageId == 2 && command instanceof DecimalType) {
// Dimmer Animation Message
int setting = ((DecimalType) command).intValue();
messageData = genHexString(setting, 8);
} else if (messageGroupId == 60 && messageId == 10) {
// RGB Dimmer Color Message
if (command instanceof HSBType) {
ShcColor translateColor = translateColor((HSBType) command);
messageData = translateColor.toString();
} else if (command instanceof DecimalType) {
int color = ((DecimalType) command).intValue();
color = color << 2; // 6 bits
messageData = genHexString(color, 2);
}
} else if (messageGroupId == 60 && messageId == 11 && command instanceof DecimalType) {
// Dimmer Color Animation Message
int setting = ((DecimalType) command).intValue();
/*
* TODO: Right now only two time-color pairs are supported in this
* implemenation. It is unclear to me how to handle 115 bits that
* are required for the complete message consisting of 10 time-color
* pairs which gives an integer the size of 2^115
*/
setting = setting << 5;
messageData = Integer.toHexString(setting);
}
if (!"".equals(messageData)) {
cmd = "s0002" + genHexString(deviceID, 4) + genHexString(messageGroupId, 2) + genHexString(messageId, 2)
+ messageData;
}
return cmd;
}
/**
* sends a command to the smarthomatic basestation with the given parameters
*
* @param deviceID
* @param messageGroupId
* @param messageId
* @param toggleTime
* @param command
*/
public void sendCommand(int deviceID, int messageGroupId, int messageId, int toggleTime, Command command) {
String cmd = prepareCommand(deviceID, messageGroupId, messageId, toggleTime, command);
if (cmd != "") {
logger.debug("send to serial port: {}", cmd);
serialDevice.writeString(cmd + "\r");
}
}
private ShcColor translateColor(HSBType color) {
int red = color.getRed().intValue() * 16 / 100;
int green = color.getGreen().intValue() * 16 / 100;
int blue = color.getBlue().intValue() * 16 / 100;
ShcColor result = new ShcColor((byte) red, (byte) green, (byte) blue);
return result;
}
/**
* {@inheritDoc}
*
* handles new events send from the smarthomatic basestation to openhab
*/
@Override
public void eventOccured(String message) {
// normally there comes only one line to the baseStation
// except when station is restarted
// Filter out the lines that contain garbage data
if (message.contains("(CRC wrong after decryption)")) {
logger.debug("BaseStation eventOccured: CRC wrong after decryption");
return;
}
if (message.contains("Base Station")) {
StringTokenizer strTok = new StringTokenizer(message, "\n");
String data = null;
versionInfo = new String[strTok.countTokens()];
logger.debug("BaseStation eventOccured - initial message ( {} )", strTok.countTokens());
int i = 0;
while (strTok.hasMoreTokens()) {
versionInfo[i] = strTok.nextToken();
logger.debug("<BaseStation>[ {} ]: {}", i, versionInfo[i]);
i++;
}
} else {
String logResult = message.replaceAll("\n", "\\\\n").replaceAll("\r", "\\\\r").substring(0, 40);
logger.debug("BaseStation eventOccured - giving to Binding {}", logResult);
if (bindingEventWorker != null) {
bindingEventWorker.eventOccured(message);
}
}
}
/**
* prints the uses device
*
*/
@Override
public String toString() {
return "BaseStation listening on port " + serialDevice.getPort();
}
/**
* private Class that holds the the color parts (means the red, green and
* blue part) of a color
*
* @author jbolay
* @since 1.9.0
*/
private class ShcColor {
byte red;
byte green;
byte blue;
public ShcColor(byte red, byte green, byte blue) {
this.red = red;
this.green = green;
this.blue = blue;
logger.debug("red: {}, green: {}, blue: {}", red, green, blue);
}
@Override
public String toString() {
// calculating the translation from rgb values to SHC color table
int colorNumber = (((red - 1) / 4) * 16) + (((green - 1) / 4) * 4) + ((blue - 1) / 4);
colorNumber = colorNumber << 2;
String hexString = Integer.toHexString(colorNumber);
while (hexString.length() < 2) {
hexString = "0" + hexString;
}
return hexString;
}
}
}