/**
* 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.anel.internal;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.openhab.binding.anel.internal.AnelBinding.IInternalAnelBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The MessageListener runs as a separate thread.
*
* Thread listening message from Anel Net-PwrCtrl devices and send updates to
* openHAB bus.
*
* @since 1.6.0
* @author paphko
*/
class AnelConnectorThread extends Thread {
private static final Logger logger = LoggerFactory.getLogger(AnelConnectorThread.class);
private static final String UDP_STATE_REQUEST = "wer da?";
/** Interruption indicator for listening thread. */
private boolean interrupted = false;
/** Cached states of device name, temperature, switches, and I/O. */
private final AnelState state;
/** The actual connector to to the device. */
private final AnelUDPConnector connector;
/** Hook to binding to request binding items. */
private final IInternalAnelBinding binding;
/** Login of the device. */
private final String user;
/** Password of the device. */
private final String password;
/** If caching is enabled, keep cache for this amount of minutes. */
private long cachePeriod;
/** Remember last time the cache was purged. */
private long lastCachePurge = 0;
/** The device name specified in config and used in items. */
private String device;
/**
* Initialize a new thread for listening on UDP packages of an Anel device.
*
* @param device
* The device name for this thread.
* @param host
* The IP address / host name of an Anel device.
* @param udpReceivePort
* The UDP receiver port.
* @param udpSendPort
* The UDP sender port.
* @param user
* Login for the Anel device.
* @param password
* The password for the Anel device.
* @param binding
* A facade to the binding for sending updates to the openHAB
* event bus.
* @param cachePeriod
* Cache values for the given amount of minutes.
*/
AnelConnectorThread(String device, String host, int udpReceivePort, int udpSendPort, String user, String password,
IInternalAnelBinding binding, long cachePeriod) {
this.device = device;
this.binding = binding;
this.password = password;
this.user = user;
this.cachePeriod = cachePeriod;
state = new AnelState(host);
connector = new AnelUDPConnector(host, udpReceivePort, udpSendPort);
}
/**
* Switch relay on or off.
*
* @param switchNr
* The relay number to switch.
* @param newState
* The new state.
*/
void sendSwitch(int switchNr, boolean newState) {
final Boolean switchState;
final Boolean switchLocked;
synchronized (state) {
switchState = state.switchState[switchNr - 1];
switchLocked = state.switchLocked[switchNr - 1];
}
// check via Boolean object because current state may be null
if (!Boolean.valueOf(newState).equals(switchState)) {
logger.debug("switch " + switchNr + " has already the requested state " + (newState ? "ON" : "OFF")
+ ", but sending update anyway.");
}
// check that this switch is not locked!
if (switchLocked != null) {
if (!switchLocked) {
// Format to switch on: Sw_on<nr><user><pwd>
// Format to switch off: Sw_off<nr><user><pwd>
// Example: Sw_on3adminanel
final String cmd = "Sw_" + (newState ? "on" : "off") + switchNr + user + password;
logger.debug("Sending to " + connector.host + ":" + connector.receivePort + " -> " + cmd);
try {
connector.sendDatagram(cmd.getBytes());
} catch (Exception e) {
logger.error("Error occured when sending UDP data to Anel device: " + cmd, e);
}
} else {
logger.debug("switch " + switchNr + " is locked, nothing sent.");
}
} else {
logger.debug("switch " + switchNr + " lock state not yet initialized, nothing sent.");
}
}
/**
* Switch IO on or off, assuming that it is set to input. Otherwise nothing
* happens.
*
* @param ioNr
* The IO number to switch.
* @param newState
* The new state.
*/
protected void sendIO(int ioNr, boolean newState) {
final Boolean isInput;
final Boolean ioState;
synchronized (state) {
isInput = state.ioIsInput[ioNr - 1];
ioState = state.ioState[ioNr - 1];
}
// check via Boolean object because current state may be null
if (Boolean.valueOf(newState).equals(ioState)) {
logger.debug("IO " + ioNr + " has already the requested state " + (newState ? "ON" : "OFF")
+ ", but sending update anyway.");
}
// check whether IO is of direction output
if (isInput == null || !isInput) {
logger.warn("Attempted to change IO" + ioNr + " to " + (newState ? "ON" : "OFF") + " but its direction is "
+ (isInput == null ? "unknown" : "input"));
return; // better not send anything if direction is not
// 'out'
}
// Format to switch on: IO_on<nr><user><pwd>
// Format to switch off: IO_off<nr><user><pwd>
// Example: IO_on3adminanel
final String cmd = "IO_" + (newState ? "on" : "off") + ioNr + user + password;
logger.debug("Sending to " + state.host + ": " + cmd);
try {
connector.sendDatagram(cmd.getBytes());
} catch (Exception e) {
if (e.getCause() instanceof UnknownHostException) {
logger.error("Could not check status of Anel device '" + state.host + "'");
} else {
logger.error("Error occured when sending UDP data to Anel device: " + cmd, e);
}
}
}
/**
* Stop this connector thread.
*/
public void setInterrupted() {
this.interrupted = true;
connector.disconnect(); // interrupt any blocking UDP listener!
}
/**
* Explicitly update state.
*/
void requestRefresh() {
if (interrupted || isInterrupted()) {
return; // do not refresh if thread is interrupted!
}
// we could add a check that refreshes are not too frequent...
// final long now = System.currentTimeMillis();
// if (state.lastUpdate + refreshInterval < now) {
logger.debug("Sending to " + state.host + ": " + UDP_STATE_REQUEST);
try {
connector.sendDatagram(UDP_STATE_REQUEST.getBytes());
} catch (Exception e) {
logger.error("Error occured when sending UDP data to Anel device: '" + UDP_STATE_REQUEST + "'", e);
}
// }
}
@Override
public void run() {
logger.debug("Anel NET-PwrCtrl message listener started for host '" + connector.host + ":"
+ connector.receivePort + "'");
try {
connector.connect();
} catch (Exception e) {
logger.error("Error occured when connecting to NET-PwrCtrl device", e);
logger.warn("Closing NET-PwrCtrl message listener");
interrupted = true; // exit
}
// as long as no interrupt is requested, continue running
while (!interrupted) {
try {
// Wait for a packet (blocking)
logger.trace("Listening on " + state.host + "...");
final byte[] data = connector.receiveDatagram();
if (data == null) {
logger.info("Nothing received, this happens during shutdown or some unknown system error.");
continue;
}
logger.trace("Received data (len={}): {}", data.length,
DatatypeConverter.printString(new String(data)));
// parse data and create commands for all state changes
final Map<AnelCommandType, org.openhab.core.types.State> newValues;
long now = System.currentTimeMillis();
synchronized (state) {
// clear cache after <cachePeriod> minutes
if (lastCachePurge + (cachePeriod * 60000) < now) {
logger.debug("Clearing cache because it was older than " + cachePeriod + " minutes.");
state.clear();
lastCachePurge = now;
}
newValues = AnelDataParser.parseData(data, state);
}
// updates are only needed if commands have been parsed
if (newValues != null && !newValues.isEmpty()) {
logger.debug("newValues ({}, len={}): {}", this.connector.host, newValues.size(), newValues);
// get all item names and post updates to event bus
for (AnelCommandType cmd : newValues.keySet()) {
final org.openhab.core.types.State state = newValues.get(cmd);
final Collection<String> itemNames = binding.getItemNamesForCommandType(device, cmd);
for (String itemName : itemNames) {
binding.postUpdateToEventBus(itemName, state);
}
}
}
} catch (InterruptedException e) {
// blocking call seems to be interrupted...
// interrupted is probably false, so we can properly cleanup
} catch (SocketTimeoutException e) {
// nothing received after timeout. continue with loop
} catch (Exception e) {
logger.error("Error occured when received data from Anel device: " + state.host, e);
}
}
try {
connector.disconnect();
} catch (Exception e) {
logger.error("Error occured when disconnecting from Anel device: " + state.host, e);
}
}
@Override
public String toString() {
return "Anel connection to '" + state.host + "', send UDP port " + connector.sendPort + ", receive UDP port "
+ connector.receivePort + ", user='" + user + "', password='" + password + "', cache period="
+ cachePeriod + "min.";
}
}