/**
* 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.onewire.internal.connection;
import java.io.IOException;
import java.util.Dictionary;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.onewire.internal.deviceproperties.AbstractOneWireDevicePropertyBindingConfig;
import org.osgi.service.cm.ConfigurationException;
import org.owfs.jowfsclient.Enums.OwBusReturn;
import org.owfs.jowfsclient.Enums.OwPersistence;
import org.owfs.jowfsclient.Enums.OwTemperatureScale;
import org.owfs.jowfsclient.OwfsConnection;
import org.owfs.jowfsclient.OwfsConnectionConfig;
import org.owfs.jowfsclient.OwfsConnectionFactory;
import org.owfs.jowfsclient.OwfsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class establishes the connection to the 1-Wire-bus.
*
* @author Dennis Riegelbauer
* @author Chris Carman (added server connection retry logic)
* @since 1.7.0
*
*/
public class OneWireConnection {
private static final Logger logger = LoggerFactory.getLogger(OneWireConnection.class);
/**
* Connection to the owserver server
*/
private static OwfsConnection cvOwConnection = null;
/**
* ip of the owserver (must be set in obenHab.cfg)
*/
private static String cvIp = null;
/**
* port of the owserver (can be set in obenHab.cfg)
*/
private static int cvPort = 4304;
/**
* Default TempScale is Celsius (can be set in obenHab.cfg)
*/
private static OwTemperatureScale cvTempScale = OwTemperatureScale.CELSIUS;
/**
* the retry count in case no valid value was returned upon read (optional, defaults to 3)
*/
private static int cvRetry = 3;
/**
* The number of retries that will be attempted after a failed connection attempt.
* Optional, defaults to 3. 0 means no retries will be attempted.
*/
private static int cvServerRetries = 3;
/**
* The time to wait between connection attempts. Optional, defaults to 60 seconds.
* May not be less than 5 seconds.
*/
private static int cvServerRetryInterval = 60;
/**
* signals that the connection is established
*/
private static boolean cvIsEstablished = false;
/**
* Returns an OwfsConnection
*
* @return the OwfsConnection network link
*/
public static synchronized OwfsConnection getConnection() {
if (cvOwConnection == null) {
if (!connect()) {
return null;
}
}
return cvOwConnection;
}
/**
* Tries to connect either by IP or serial bus, depending on supplied config data.
*
* @return true if connection was established, false otherwise
*/
public static synchronized boolean connect() {
OwfsConnectionFactory owfsConnectorFactory = new OwfsConnectionFactory(cvIp, cvPort);
OwfsConnectionConfig owConnectionConfig = new OwfsConnectionConfig(cvIp, cvPort);
owConnectionConfig.setTemperatureScale(cvTempScale);
owConnectionConfig.setPersistence(OwPersistence.ON);
owConnectionConfig.setBusReturn(OwBusReturn.ON);
owfsConnectorFactory.setConnectionConfig(owConnectionConfig);
cvOwConnection = owfsConnectorFactory.createNewConnection();
boolean connected = false;
int attempts = 0, retriesRemaining = cvServerRetries;
List<String> result = null;
try {
result = cvOwConnection.listDirectory("/");
if (result != null) {
connected = true;
} else {
cvIsEstablished = false;
}
} catch (OwfsException oe) {
logger.warn("Unexpected owfs exception: {}", oe.getMessage(), oe);
} catch (IOException e) {
logger.warn("Unexpected connection failure.", e);
}
while (!connected && retriesRemaining > 0) {
logger.warn("Connection failed. Will retry in {} seconds.", cvServerRetryInterval);
synchronized (cvOwConnection) {
try {
cvOwConnection.wait(cvServerRetryInterval * 1000L);
} catch (InterruptedException e) {
logger.debug("Wait was interrupted.");
}
}
attempts++;
retriesRemaining--;
logger.info("Retrying failed connection... Attempt {} of {}.", attempts, cvServerRetries);
try {
result = cvOwConnection.listDirectory("/");
if (result != null) {
connected = true;
}
} catch (OwfsException oe) {
logger.warn("Unexpected owfs exception: {}", oe.getMessage(), oe);
} catch (IOException e) {
logger.warn("Unexpected connection failure.", e);
}
}
if (!connected) {
logger.error("Couldn't connect to owserver [IP '{}' Port '{}']", cvIp, cvPort);
cvIsEstablished = false;
return false;
}
logger.info("Connected to owserver [IP '{}' Port '{}']", cvIp, cvPort);
cvIsEstablished = true;
return true;
}
/**
* Reconnects to owserver
*
* @return
*/
public static synchronized boolean reconnect() {
logger.info("Trying to reconnect to owserver...");
try {
cvOwConnection.disconnect();
} catch (Exception lvException) {
logger.error("Error while disconnecting from owserver: ", lvException);
}
cvOwConnection = null;
cvIsEstablished = false;
return connect();
}
public static synchronized void updated(Dictionary<String, ?> pvConfig) throws ConfigurationException {
if (pvConfig == null) {
logger.debug(
"OneWireBinding configuration is not present. Please check your configuration file or if not needed remove the OneWireBinding addon.");
return;
}
logger.debug("OneWire configuration present. Setting up owserver connection.");
cvIp = Objects.toString(pvConfig.get("ip"), null);
if (StringUtils.isBlank(cvIp)) {
logger.error("owserver IP address was configured as an empty string.");
throw new ConfigurationException("onewire:ip", "owserver IP address was configured as an empty string.");
}
String lvPortConfig = Objects.toString(pvConfig.get("port"), null);
if (StringUtils.isNotBlank(lvPortConfig)) {
cvPort = Integer.parseInt(lvPortConfig);
}
if (cvPort < 1) {
logger.error("owserver port was configured with an invalid value: {}", cvPort);
throw new ConfigurationException("onewire:port",
"owserver port was configured with an invalid value: " + cvPort);
}
logger.debug("owserver ip:port = {}:{}", cvIp, cvPort);
String lvTempScaleString = Objects.toString(pvConfig.get("tempscale"), null);
if (StringUtils.isNotBlank(lvTempScaleString)) {
try {
cvTempScale = OwTemperatureScale.valueOf(lvTempScaleString);
} catch (IllegalArgumentException iae) {
String lvFehlertext = "Unknown temperature scale '" + lvTempScaleString
+ "'. Valid values are CELSIUS, FAHRENHEIT, KELVIN or RANKINE.";
logger.error(lvFehlertext, iae);
throw new ConfigurationException("onewire:tempscale", lvFehlertext);
}
}
String lvRetryString = Objects.toString(pvConfig.get("retry"), null);
if (StringUtils.isNotBlank(lvRetryString)) {
cvRetry = Integer.parseInt(lvRetryString);
}
logger.debug("onewire:retry = {}", cvRetry);
String lvServerRetries = Objects.toString(pvConfig.get("server_retries"), null);
if (StringUtils.isNotBlank(lvServerRetries)) {
cvServerRetries = Integer.parseInt(lvServerRetries);
}
logger.debug("onewire:server_retries = {}", cvServerRetries);
String lvRetryIntervalString = Objects.toString(pvConfig.get("server_retryInterval"), null);
if (StringUtils.isNotBlank(lvRetryIntervalString)) {
cvServerRetryInterval = Integer.parseInt(lvRetryIntervalString);
if (cvServerRetryInterval < 5 && cvServerRetryInterval > 0) {
logger.info("server_retryInterval was set to {}. Using the minimum allowed value of 5 instead.",
cvServerRetryInterval);
cvServerRetryInterval = 5;
}
}
logger.debug("onewire:server_retryInterval = {} seconds", cvServerRetryInterval);
if (cvOwConnection == null) {
logger.debug("Not connected to owserver yet. Trying to connect...");
if (!connect()) {
logger.warn("Connection to owserver failed!");
} else {
logger.debug("Success: connected to owserver.");
}
}
}
/**
* @return boolean, is the connection to oserver established
*/
public static boolean isConnectionEstablished() {
return cvIsEstablished;
}
/**
* Checks if an device exists in 1-Wire network
*
* @param pvDevicePropertyPath
* @return
* @throws IOException
* @throws OwfsException
*/
private static synchronized boolean checkIfDeviceExists(String pvDevicePropertyPath)
throws IOException, OwfsException {
String[] pvDevicePropertyPathParts = pvDevicePropertyPath.trim().split("/");
String lvDevicePath = pvDevicePropertyPathParts[0];
logger.debug("check if device exists '{}': ", new Object[] { lvDevicePath });
return OneWireConnection.getConnection().exists(lvDevicePath);
}
/**
* Read a Value for a device property from 1-Wire network
*
* @param pvDevicePropertyPath
* @return device property value as String
*/
public static synchronized String readFromOneWire(AbstractOneWireDevicePropertyBindingConfig pvBindingConfig) {
String lvDevicePropertyPath = pvBindingConfig.getDevicePropertyPath();
int lvAttempt = 1;
while (lvAttempt <= cvRetry) {
try {
logger.debug("trying to read from '{}', read attempt={}",
new Object[] { lvDevicePropertyPath, lvAttempt });
if (checkIfDeviceExists(lvDevicePropertyPath)) {
String lvReadValue = OneWireConnection.getConnection().read(lvDevicePropertyPath);
logger.debug("Read value '{}' from {}, read attempt={}",
new Object[] { lvReadValue, lvDevicePropertyPath, lvAttempt });
// Test
if (pvBindingConfig.isIgnore85CPowerOnResetValues()) {
double lvReadDouble = Double.parseDouble(lvReadValue);
if (lvReadDouble == 85.0) {
logger.debug("reading from path '{}' attempt {}. Ignoring 85C value", lvDevicePropertyPath,
lvAttempt);
} else {
return lvReadValue;
}
} else {
return lvReadValue;
}
} else {
logger.info("there is no device for path {}, read attempt={}",
new Object[] { lvDevicePropertyPath, lvAttempt });
}
} catch (OwfsException oe) {
String lvLogText = "reading from path " + lvDevicePropertyPath + " attempt " + lvAttempt
+ " throws exception";
if (pvBindingConfig.isIgnoreReadErrors()) {
logger.debug(lvLogText, oe);
} else {
logger.error(lvLogText, oe);
reconnect();
}
} catch (IOException ioe) {
logger.error("couldn't establish network connection while read attempt {} '{}' ip:port={}:{}",
lvAttempt, lvDevicePropertyPath, cvIp, cvPort, ioe);
reconnect();
} catch (NumberFormatException lvNumberFormatException) {
logger.error(
"Ignoring 85C PowerOnReset values can only be used with temperature sensors! Read a value, which is not a number");
} finally {
lvAttempt++;
}
}
return null;
}
/**
* Writes String to 1-Wire device property
*
* @param pvDevicePropertyPath
* @param pvValue
*/
public static synchronized void writeToOneWire(String pvDevicePropertyPath, String pvValue) {
int lvAttempt = 1;
while (lvAttempt <= cvRetry) {
try {
logger.debug("Trying to write '{}' to '{}', write attempt={}", pvValue, pvDevicePropertyPath,
lvAttempt);
if (checkIfDeviceExists(pvDevicePropertyPath)) {
OneWireConnection.getConnection().write(pvDevicePropertyPath, pvValue);
return; // Success, exit
} else {
logger.info("There is no device for path {}, write attempt={}", pvDevicePropertyPath, lvAttempt);
}
} catch (OwfsException oe) {
logger.error("Writing {} to path {} attempt {} threw an exception", pvValue, pvDevicePropertyPath,
lvAttempt, oe);
reconnect();
} catch (IOException ioe) {
logger.error("Couldn't establish network connection while write attempt {} to '{}' ip:port={}:{}",
lvAttempt, pvDevicePropertyPath, cvIp, cvPort, ioe);
reconnect();
} finally {
lvAttempt++;
}
}
}
}