/**
* 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.samsungac.internal;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.samsungac.SamsungAcBindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Binding listening OpenHAB bus and send commands to Samsung Air Conditioner
* devices when command is received.
*
* @author Stein Tore Tøsse
* @author John Cocula added optional port parameter
* @since 1.6.0
*/
public class SamsungAcBinding extends AbstractActiveBinding<SamsungAcBindingProvider> implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(SamsungAcBinding.class);
/**
* the refresh interval which is used to check for lost connections
* (optional, defaults to 60000ms)
*/
private long refreshInterval = 60000;
private Map<String, AirConditioner> nameHostMapper = null;
protected Map<String, Map<String, String>> deviceConfigCache = new HashMap<String, Map<String, String>>();
public SamsungAcBinding() {
}
@Override
public void activate() {
logger.debug("Started Samsung AC Binding");
}
@Override
public void deactivate() {
logger.debug("deactive");
// close any open connections
if (nameHostMapper != null) {
for (AirConditioner connector : nameHostMapper.values()) {
connector.disconnect();
}
}
}
/**
* @{inheritDoc
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
if (itemName != null && command != null) {
logger.debug("InternalReceiveCommand [{} : {}]", itemName, command);
String hostName = getAirConditionerInstance(itemName);
AirConditioner host = nameHostMapper.get(hostName);
if (host == null) {
logger.debug("Host with hostname: '{}' not found...", hostName);
return;
}
CommandEnum property = getProperty(itemName);
String cmd = getCmdStringFromEnumValue(command, property);
if (cmd != null) {
sendCommand(host, property, cmd, hostName);
} else {
logger.warn("Not sending for itemName: '{}' because property not implemented: '{}'", itemName,
property);
}
}
}
private String getCmdStringFromEnumValue(Command command, CommandEnum property) {
String cmd = null;
switch (property) {
case AC_FUN_POWER:
case AC_ADD_SPI:
case AC_ADD_AUTOCLEAN:
cmd = "ON".equals(command.toString()) ? "On" : "Off";
break;
case AC_FUN_WINDLEVEL:
cmd = WindLevelEnum.getFromValue(command).toString();
break;
case AC_FUN_OPMODE:
cmd = OperationModeEnum.getFromValue(command).toString();
break;
case AC_FUN_COMODE:
cmd = ConvenientModeEnum.getFromValue(command).toString();
break;
case AC_FUN_DIRECTION:
cmd = DirectionEnum.getFromValue(command).toString();
break;
case AC_FUN_TEMPSET:
cmd = Integer.toString(new Double(command.toString()).intValue());
break;
case AC_FUN_ERROR:
default:
cmd = command.toString();
break;
}
return cmd;
}
private void sendCommand(AirConditioner aircon, CommandEnum property, String value, String hostName) {
int i = 1;
boolean commandSent = false;
while (i < 5 && !commandSent) {
try {
logger.debug("[{}/5] Sending command: {} to property:{} with ip:{}", i, value, property,
aircon.getIpAddress());
Map<CommandEnum, String> status = aircon.sendCommand(property, value);
if (status != null) {
commandSent = true;
logger.debug("Command[{}] sent on try number {}", value, i);
updateAllItemsFromStatusMap(status, hostName);
}
} catch (Exception e) {
logger.warn("Could not send value: '{}' to property:'{}', try {}/5", value, property, i);
} finally {
i++;
}
}
}
private String getAirConditionerInstance(String itemName) {
for (BindingProvider provider : providers) {
if (provider instanceof SamsungAcBindingProvider) {
SamsungAcBindingProvider acProvider = (SamsungAcBindingProvider) provider;
if (acProvider.getItemNames().contains(itemName)) {
return acProvider.getAirConditionerInstance(itemName);
}
}
}
return null;
}
private CommandEnum getProperty(String itemName) {
for (BindingProvider provider : providers) {
if (provider instanceof SamsungAcBindingProvider) {
SamsungAcBindingProvider acProvider = (SamsungAcBindingProvider) provider;
if (acProvider.getItemNames().contains(itemName)) {
return acProvider.getProperty(itemName);
}
}
}
return null;
}
private String getItemName(String acName, CommandEnum property) {
for (BindingProvider provider : providers) {
if (provider instanceof SamsungAcBindingProvider) {
SamsungAcBindingProvider acProvider = (SamsungAcBindingProvider) provider;
return acProvider.getItemName(acName, property);
}
}
return null;
}
protected void addBindingProvider(SamsungAcBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(SamsungAcBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
Enumeration<String> keys = config.keys();
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
logger.info("Refresh interval set to {} ms", refreshIntervalString);
} else {
logger.info("No refresh interval configured, using default: {} ms", refreshInterval);
}
Map<String, AirConditioner> hosts = new HashMap<String, AirConditioner>();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
logger.debug("Configuration key is: {}", key);
if ("service.pid".equals(key)) {
continue;
}
String[] parts = key.split("\\.");
String hostname = parts[0];
AirConditioner host = hosts.get(hostname);
if (host == null) {
host = new AirConditioner();
}
String value = ((String) config.get(key)).trim();
if ("host".equals(parts[1])) {
host.setIpAddress(value);
}
if ("port".equals(parts[1])) {
try {
host.setPort(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
throw new ConfigurationException("port", "Invalid port number specified '" + value + "'", nfe);
}
}
if ("mac".equals(parts[1])) {
host.setMacAddress(value);
}
if ("token".equals(parts[1])) {
host.setToken(value);
}
if ("certificate".equals(parts[1])) {
host.setCertificateFileName(value);
}
if ("password".equals(parts[1])) {
host.setCertificatePassword(value);
}
hosts.put(hostname, host);
}
nameHostMapper = hosts;
if (nameHostMapper == null || nameHostMapper.size() == 0) {
setProperlyConfigured(false);
Map<String, Map<String, String>> discovered = SsdpDiscovery.discover();
if (discovered != null && discovered.size() > 0) {
for (Map<String, String> ac : discovered.values()) {
if (ac.get("IP") != null && ac.get("MAC_ADDR") != null) {
logger.warn(
"We found air conditioner. Please put the following in your configuration file: "
+ "\r\n samsungac:<ACNAME>.host={}\r\n samsungac:<ACNAME>.mac={}",
ac.get("IP"), ac.get("MAC_ADDR"));
}
}
} else {
logger.warn("No Samsung Air Conditioner has been configured, and we could not find one either");
}
} else {
setProperlyConfigured(true);
}
}
@Override
protected void execute() {
if (!bindingsExist()) {
logger.debug("There is no existing Samsung AC binding configuration => refresh cycle aborted!");
return;
}
if (nameHostMapper == null) {
logger.debug("Name host mapper not yet set. Aborted refresh");
return;
}
for (Map.Entry<String, AirConditioner> entry : nameHostMapper.entrySet()) {
AirConditioner host = entry.getValue();
String acName = entry.getKey();
if (host.isConnected()) {
getAndUpdateStatusForAirConditioner(acName, host);
} else {
reconnectToAirConditioner(entry.getKey(), host);
}
}
}
private void reconnectToAirConditioner(String key, AirConditioner host) {
logger.debug("Broken connection found for '{}', attempting to reconnect...", key);
try {
host.login();
logger.debug("Connection to {} has succeeded", host.toString());
} catch (Exception e) {
if (e == null || e.toString() == null || e.getCause() == null) {
logger.debug("Returned null-exception...");
} else {
logger.debug("Caught exception: {} : {}", e.toString(), e.getCause().toString());
}
logger.debug("Reconnect failed for '{}', will retry in {}s", key, refreshInterval / 1000);
}
}
private void getAndUpdateStatusForAirConditioner(String acName, AirConditioner host) {
Map<CommandEnum, String> status = new HashMap<CommandEnum, String>();
try {
logger.debug("Getting status for ac: '{}'", acName);
status = host.getStatus();
} catch (Exception e) {
logger.debug("Could not get status.. returning.., got exception: {}", e.toString());
return;
}
updateAllItemsFromStatusMap(status, acName);
}
private void updateAllItemsFromStatusMap(Map<CommandEnum, String> status, String acName) {
for (CommandEnum cmd : status.keySet()) {
logger.debug("Trying to find item for: {} and cmd: {}", acName, cmd.toString());
String item = getItemName(acName, cmd);
String value = status.get(cmd);
if (item != null && value != null) {
updateItemWithValue(cmd, item, value);
}
}
}
private void updateItemWithValue(CommandEnum cmd, String item, String value) {
try {
switch (cmd) {
case AC_FUN_TEMPNOW:
case AC_FUN_TEMPSET:
postUpdate(item, DecimalType.valueOf(value));
break;
case AC_FUN_POWER:
case AC_ADD_SPI:
case AC_ADD_AUTOCLEAN:
postUpdate(item, value.toUpperCase().equals("ON") ? OnOffType.ON : OnOffType.OFF);
break;
case AC_FUN_COMODE:
postUpdate(item, DecimalType.valueOf(Integer.toString(ConvenientModeEnum.valueOf(value).value)));
break;
case AC_FUN_OPMODE:
postUpdate(item, DecimalType.valueOf(Integer.toString(OperationModeEnum.valueOf(value).value)));
break;
case AC_FUN_WINDLEVEL:
postUpdate(item, DecimalType.valueOf(Integer.toString(WindLevelEnum.valueOf(value).value)));
break;
case AC_FUN_DIRECTION:
postUpdate(item, DecimalType.valueOf(Integer.toString(DirectionEnum.valueOf(value).value)));
break;
case AC_FUN_ERROR:
default:
postUpdate(item, StringType.valueOf(value));
break;
}
} catch (IllegalArgumentException iae) {
logger.warn(
"Update of item [{}] failed, probably because the received value for the command is not implemented [{}.{}]",
item, cmd, value);
}
}
private void postUpdate(String item, State state) {
if (item != null && state != null) {
logger.debug("{} gets updated to: {}", item, state);
eventPublisher.postUpdate(item, state);
} else {
logger.debug("Could not update item: '{}' with state: '{}'", item, state.toString());
}
}
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
@Override
protected String getName() {
return "Samsung Air Conditioner service";
}
}