/**
* 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.energenie.internal;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.energenie.EnergenieBindingProvider;
import org.openhab.binding.energenie.internal.EnergenieBindingConfig.ChannelTypeDef;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.io.net.http.HttpUtil;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Hans-Jörg Merk - initial contribution
* @author Christoph Kuhr - PWM-LAN support
* @since 1.6.0
*/
public class EnergenieBinding extends AbstractActiveBinding<EnergenieBindingProvider>implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(EnergenieBinding.class);
/**
* the refresh interval which is used to poll values from the Energenie
* server (optional, defaults to 60000ms)
*/
private long refreshInterval = 60000;
/** the timeout to use for connecting to a given host (defaults to 5000 milliseconds) */
private int timeout = 6000;
/** RegEx to validate a config <code>'^(.*?)\\.(host|port)$'</code> */
private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.(host|password)$");
protected Map<String, DeviceConfig> deviceConfigs = new HashMap<String, DeviceConfig>();
protected Map<String, String> pmsIpConfig = new HashMap<String, String>();
protected Map<String, String> pmsPasswordConfig = new HashMap<String, String>();
public EnergenieBinding() {
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
/**
* @{inheritDoc}
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* @{inheritDoc}
*/
@Override
protected String getName() {
return "Energenie Refresh Service";
}
/**
* @{inheritDoc}
*/
@Override
protected void execute() {
logger.debug("execute() method is called!");
for (EnergenieBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
EnergenieBindingConfig deviceConfig = getConfigForItemName(itemName);
if (deviceConfig == null) {
return;
}
try {
String pmsId = deviceConfig.getDeviceId();
String pmsIp = pmsIpConfig.get(pmsId);
String pmsPw = pmsPasswordConfig.get(pmsId);
String url = "http://" + pmsIp + "/login.html";
String urlParameters = "pw=" + pmsPw;
InputStream urlContent = new ByteArrayInputStream(urlParameters.getBytes(Charset.forName("UTF-8")));
String loginResponseString = null;
try {
loginResponseString = HttpUtil.executeUrl("POST", url, urlContent, "TEXT/PLAIN", timeout);
String itemType = deviceConfig.getItemType();
if (itemType != null) {
logger.trace("item is of Type {}", itemType);
if (itemType.contains("Type=SwitchItem")) {
logger.trace("about to process SwitchItem");
int pmsSocketId = Integer.parseInt(deviceConfig.getItemConfig());
processSwitchItem(loginResponseString, pmsSocketId, itemName);
} else if (itemType.contains("Type=NumberItem")) {
logger.trace("about to process NumberItem");
String pwmChannelType = deviceConfig.getItemConfig();
processNumberItem(deviceConfig, loginResponseString, pwmChannelType, itemName);
}
}
} catch (Exception e) {
logger.error("Failed to connect to ip {}", pmsIp);
}
sendLogOut(pmsId);
} catch (Exception e) {
logger.error("failed to read state", e);
}
}
}
}
/**
* @{inheritDoc}
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("internalReceiveCommand() is called!");
EnergenieBindingConfig deviceConfig = getConfigForItemName(itemName);
if (deviceConfig == null) {
return;
}
try {
String pmsId = deviceConfig.getDeviceId();
int pmsSocketId = Integer.parseInt(deviceConfig.getItemConfig());
sendLogin(pmsId);
if (OnOffType.ON.equals(command)) {
sendOn(pmsId, pmsSocketId);
} else if (OnOffType.OFF.equals(command)) {
sendOff(pmsId, pmsSocketId);
}
sendLogOut(pmsId);
} catch (Exception e) {
logger.error("energenie: failed to send {} command", command, e);
}
}
private void sendLogin(String pmsId) {
String pmsIp = pmsIpConfig.get(pmsId);
String pmsPw = pmsPasswordConfig.get(pmsId);
String url = "http://" + pmsIp + "/login.html";
String urlParameters = "pw=" + pmsPw;
InputStream urlContent = new ByteArrayInputStream(urlParameters.getBytes(Charset.forName("UTF-8")));
try {
HttpUtil.executeUrl("POST", url, urlContent, "TEXT/PLAIN", timeout);
logger.trace("sendlogin to {} with password {}", pmsIp, pmsPw);
logger.trace("sending 'POST' request to URL : {}", url);
} catch (Exception e) {
logger.error("energenie: failed to login to ip {}", pmsIp);
}
}
private void sendOn(String pmsId, int pmsSocketId) {
String pmsIp = pmsIpConfig.get(pmsId);
String url = "http://" + pmsIp;
String urlParameters = "cte" + pmsSocketId + "=1";
InputStream urlContent = new ByteArrayInputStream(urlParameters.getBytes(Charset.forName("UTF-8")));
try {
HttpUtil.executeUrl("POST", url, urlContent, "TEXT/PLAIN", timeout);
logger.trace("sending 'POST' request to URL : {}", url);
logger.trace("send command ON to socket {} at host {}", pmsSocketId, pmsIp);
} catch (Exception e) {
logger.error("failed to send command ON to socket {} at ip {}", pmsIp, pmsSocketId);
}
}
private void sendOff(String pmsId, int pmsSocketId) {
String pmsIp = pmsIpConfig.get(pmsId);
String url = "http://" + pmsIp;
String urlParameters = "cte" + pmsSocketId + "=0";
InputStream urlContent = new ByteArrayInputStream(urlParameters.getBytes(Charset.forName("UTF-8")));
try {
HttpUtil.executeUrl("POST", url, urlContent, "TEXT/PLAIN", timeout);
logger.trace("sending 'POST' request to URL : {}", url);
logger.trace("send command OFF to socket {} at host {}", pmsSocketId, pmsIp);
} catch (Exception e) {
logger.error("failed to send command OFF to socket {} at ip {}", pmsIp, pmsSocketId);
}
}
private void sendLogOut(String pmsId) {
String pmsIp = pmsIpConfig.get(pmsId);
String url = "http://" + pmsIp + "/login.html";
try {
HttpUtil.executeUrl("POST", url, timeout);
logger.trace("logout from ip {}", pmsIp);
} catch (Exception e) {
logger.error("failed to logout from ip {}", pmsIp);
}
}
/**
* @{inheritDoc}
*/
private void processSwitchItem(String loginResponseString, int pmsSocketId, String itemName) {
String stateResponseSearch = "var sockstates =";
int findState = loginResponseString.lastIndexOf(stateResponseSearch);
if (findState != 0) {
logger.trace("searchstring sockstates found at position {}", findState);
String slicedResponse = loginResponseString.substring(findState + 18, findState + 25);
logger.trace("transformed state response = {}", slicedResponse);
try {
String[] parts = slicedResponse.split(",");
String itemState = parts[pmsSocketId - 1];
logger.trace("Response for item {} = {}", itemName, itemState);
if (itemState.equals("0")) {
State state = OnOffType.valueOf("OFF");
logger.trace("transformed state for item {} = {}", itemName, state);
eventPublisher.postUpdate(itemName, state);
}
if (itemState.equals("1")) {
State state = OnOffType.valueOf("ON");
logger.trace("transformed state for item {} = {}", itemName, state);
eventPublisher.postUpdate(itemName, state);
}
} catch (Exception te) {
logger.error("Response transformation throws exception ", te);
}
} else {
logger.trace("searchstring sockstates not found");
}
}
/**
* @{inheritDoc}
*/
private void processNumberItem(EnergenieBindingConfig deviceConfig, String loginResponseString,
String pwmChannelType, String itemName) {
String stateResponseSearch = "";
int start = 0, stop = 0, divisor = 1;
double value = 0.0;
ChannelTypeDef channelType = ChannelTypeDef.valueOf(pwmChannelType);
switch (channelType) {
case VOLTAGE:
stateResponseSearch = "var V = ";
start = 9;
stop = 20;
divisor = 10;
break;
case CURRENT:
stateResponseSearch = "var I = ";
start = 9;
stop = 20;
divisor = 100;
break;
case POWER:
stateResponseSearch = "var P=";
start = 6;
stop = 20;
divisor = 466;
break;
case ENERGY:
stateResponseSearch = "var E=";
start = 6;
stop = 20;
divisor = 25600;
break;
case NONE:
break;
default:
break;
}
int findState = loginResponseString.lastIndexOf(stateResponseSearch);
if (findState > 0) {
logger.trace("searchstring {} found at position {}", stateResponseSearch, findState);
String slicedResponseTmp = loginResponseString.substring(findState + start, findState + stop);
logger.trace("transformed state response = {}", slicedResponseTmp);
String[] slicedResponse = slicedResponseTmp.split(";");
logger.trace("transformed state response = {} - {}", slicedResponse[0], slicedResponse[1]);
if (Double.parseDouble(slicedResponse[0]) / 1 == Double.parseDouble(slicedResponse[0])) {
value = Double.parseDouble(slicedResponse[0]) / divisor;
} else {
value = -1.0;
}
logger.trace("transformed state for item {} = {}", itemName, value);
State newState = new DecimalType(value);
eventPublisher.postUpdate(itemName, newState);
} else {
logger.trace("searchstring %s not found", stateResponseSearch);
}
}
/**
* @{inheritDoc}
*/
@Override
protected void internalReceiveUpdate(String itemName, State newState) {
logger.debug("internalReceiveCommand() is called!");
}
/**
* Lookup of the configuration of the named item.
*
* @param itemName
* The name of the item.
* @return The configuration, null otherwise.
*/
private EnergenieBindingConfig getConfigForItemName(String itemName) {
for (EnergenieBindingProvider provider : this.providers) {
if (provider.getItemConfig(itemName) != null) {
return provider.getItemConfig(itemName);
}
}
return null;
}
protected void addBindingProvider(EnergenieBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(EnergenieBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
Enumeration<String> keys = config.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
// the config-key enumeration contains additional keys that we
// don't want to process here ...
if ("service.pid".equals(key)) {
continue;
}
Matcher matcher = EXTRACT_CONFIG_PATTERN.matcher(key);
if (!matcher.matches()) {
logger.debug("given config key '" + key
+ "' does not follow the expected pattern '<id>.<host|password>'");
continue;
}
matcher.reset();
matcher.find();
String deviceId = matcher.group(1);
DeviceConfig deviceConfig = deviceConfigs.get(deviceId);
if (deviceConfig == null) {
deviceConfig = new DeviceConfig(deviceId);
deviceConfigs.put(deviceId, deviceConfig);
}
String configKey = matcher.group(2);
String value = (String) config.get(key);
if ("host".equals(configKey)) {
deviceConfig.host = value;
pmsIpConfig.put(deviceId, value);
} else if ("password".equals(configKey)) {
deviceConfig.password = value;
pmsPasswordConfig.put(deviceId, value);
} else {
throw new ConfigurationException(configKey, "the given configKey '" + configKey + "' is unknown");
}
}
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
}
setProperlyConfigured(true);
}
}
/**
* Internal data structure which carries the connection details of one
* device (there could be several)
*/
static class DeviceConfig {
String host;
String password;
String deviceId;
public DeviceConfig(String deviceId) {
this.deviceId = deviceId;
}
public String getHost() {
return host;
}
public String getPassword() {
return password;
}
@Override
public String toString() {
return "Device [id=" + deviceId + "]";
}
}
}