/**
* 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.plugwise.internal;
import static org.openhab.binding.plugwise.internal.PlugwiseBinding.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.plugwise.PlugwiseCommandType;
import org.openhab.binding.plugwise.protocol.AcknowledgeMessage;
import org.openhab.binding.plugwise.protocol.AnnounceAwakeRequestMessage;
import org.openhab.binding.plugwise.protocol.BroadcastGroupSwitchResponseMessage;
import org.openhab.binding.plugwise.protocol.CalibrationResponseMessage;
import org.openhab.binding.plugwise.protocol.ClockGetResponseMessage;
import org.openhab.binding.plugwise.protocol.InformationResponseMessage;
import org.openhab.binding.plugwise.protocol.InitialiseRequestMessage;
import org.openhab.binding.plugwise.protocol.InitialiseResponseMessage;
import org.openhab.binding.plugwise.protocol.Message;
import org.openhab.binding.plugwise.protocol.MessageType;
import org.openhab.binding.plugwise.protocol.ModuleJoinedNetworkRequestMessage;
import org.openhab.binding.plugwise.protocol.NodeAvailableMessage;
import org.openhab.binding.plugwise.protocol.NodeAvailableResponseMessage;
import org.openhab.binding.plugwise.protocol.PowerBufferResponseMessage;
import org.openhab.binding.plugwise.protocol.PowerInformationResponseMessage;
import org.openhab.binding.plugwise.protocol.RealTimeClockGetResponseMessage;
import org.openhab.binding.plugwise.protocol.RoleCallResponseMessage;
import org.openhab.binding.plugwise.protocol.SenseReportRequestMessage;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
/**
* This class represents a Plugwise Stick that is connected to a serial port on the host.
* This class borrows heavily from the Serial binding for the serial port communication
*
* @author Karel Goderis
* @since 1.1.0
*/
public class Stick extends PlugwiseDevice implements SerialPortEventListener {
private static final Logger logger = LoggerFactory.getLogger(Stick.class);
/** Plugwise protocol header code (hex) */
private static final String PROTOCOL_HEADER = "\u0005\u0005\u0003\u0003";
/** Carriage return */
private static final char CR = '\r';
/** Line feed */
private static final char LF = '\n';
/** Plugwise protocol trailer code (hex) */
private static final String PROTOCOL_TRAILER = new String(new char[] { CR, LF });
/** Matches Plugwise responses into the following groups: protocolHeader command sequence payload CRC */
private static final Pattern RESPONSE_PATTERN = Pattern.compile("(.{4})(\\w{4})(\\w{4})(\\w*?)(\\w{4})");
// Serial communication fields
private String port;
private CommPortIdentifier portId;
private SerialPort serialPort;
private WritableByteChannel outputChannel;
private ByteBuffer readBuffer = ByteBuffer.allocate(maxBufferSize);
private int previousByte = -1;
// Queue fields
private static int maxBufferSize = 1024;
private final ReentrantLock sentQueueLock = new ReentrantLock();
private BlockingQueue<Message> sendQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true);
private BlockingQueue<Message> prioritySendQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true);
private BlockingQueue<AcknowledgeMessage> acknowledgedQueue = new ArrayBlockingQueue<AcknowledgeMessage>(
maxBufferSize, true);
private BlockingQueue<Message> sentQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true);
private BlockingQueue<Message> receivedQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true);
// Background threads
private final Thread sendThread;
private final Thread processMessageThread;
// Stick fields
private boolean initialised = false;
private final PlugwiseDeviceCache plugwiseDeviceCache = new PlugwiseDeviceCache();
private PlugwiseBinding binding;
/** default interval for sending messages on the ZigBee network */
private int interval = 50;
/** default maximum number of attempts to send a message */
private int maxRetries = 1;
public Stick(String port, PlugwiseBinding binding) {
super("", PlugwiseDevice.DeviceType.Stick, "stick");
this.port = port;
this.binding = binding;
plugwiseDeviceCache.add(this);
sendThread = new SendThread(this);
processMessageThread = new ProcessMessageThread(this);
try {
initialize();
} catch (PlugwiseInitializationException e) {
logger.error("Failed to initialize Plugwise stick: {}", e.getLocalizedMessage());
initialised = false;
}
}
protected void addDevice(PlugwiseDevice device) {
plugwiseDeviceCache.add(device);
}
protected PlugwiseDevice getDevice(String id) {
return plugwiseDeviceCache.get(id);
}
protected PlugwiseDevice getDeviceByMAC(String mac) {
return plugwiseDeviceCache.getByMAC(mac);
}
protected PlugwiseDevice getDeviceByName(String name) {
return plugwiseDeviceCache.getByName(name);
}
protected <T> List<T> getDevicesByClass(Class<T> deviceClass) {
return plugwiseDeviceCache.getByClass(deviceClass);
}
public String getPort() {
return port;
}
public void setInterval(int interval) {
this.interval = interval;
}
public void setRetries(int retries) {
this.maxRetries = retries;
}
public boolean isInitialised() {
return initialised;
}
/**
* Initialize this device and open the serial port
*
* @throws PlugwiseInitializationException if port can not be opened
*/
@SuppressWarnings("rawtypes")
private void initialize() throws PlugwiseInitializationException {
// Flush the deviceCache
plugwiseDeviceCache.clear();
// parse ports and if the default port is found, initialized the reader
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
if (id.getName().equals(port)) {
logger.debug("Serial port '{}' has been found.", port);
portId = id;
}
}
}
if (portId != null) {
// initialize serial port
try {
serialPort = portId.open("openHAB", 2000);
} catch (PortInUseException e) {
throw new PlugwiseInitializationException(e);
}
try {
serialPort.addEventListener(this);
} catch (TooManyListenersException e) {
throw new PlugwiseInitializationException(e);
}
// activate the DATA_AVAILABLE notifier
serialPort.notifyOnDataAvailable(true);
try {
// set port parameters
serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
throw new PlugwiseInitializationException(e);
}
try {
// get the output stream
outputChannel = Channels.newChannel(serialPort.getOutputStream());
} catch (IOException e) {
throw new PlugwiseInitializationException(e);
}
} else {
StringBuilder sb = new StringBuilder();
portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
sb.append(id.getName() + "\n");
}
}
throw new PlugwiseInitializationException(
"Serial port '" + port + "' could not be found. Available ports are:\n" + sb);
}
initialised = true;
// initialise the Stick
sendMessage(new InitialiseRequestMessage());
}
public void startBackgroundThreads() {
sendThread.start();
processMessageThread.start();
}
/**
* Close this serial device associated with the Stick
*/
public void close() {
stopBackgroundThread(sendThread);
stopBackgroundThread(processMessageThread);
serialPort.removeEventListener();
try {
IOUtils.closeQuietly(serialPort.getInputStream());
IOUtils.closeQuietly(serialPort.getOutputStream());
serialPort.close();
} catch (IOException e) {
logger.error("An exception occurred while closing the serial port {} ({})", serialPort, e.getMessage());
}
initialised = false;
}
private static void stopBackgroundThread(Thread thread) {
thread.interrupt();
try {
thread.join();
} catch (InterruptedException e) {
Thread.interrupted();
}
}
@Override
public void serialEvent(SerialPortEvent event) {
switch (event.getEventType()) {
case SerialPortEvent.BI:
case SerialPortEvent.OE:
case SerialPortEvent.FE:
case SerialPortEvent.PE:
case SerialPortEvent.CD:
case SerialPortEvent.CTS:
case SerialPortEvent.DSR:
case SerialPortEvent.RI:
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
break;
case SerialPortEvent.DATA_AVAILABLE:
// we get here if data has been received
try {
// read data from serial device
while (serialPort.getInputStream().available() > 0) {
int currentByte = serialPort.getInputStream().read();
// Plugwise sends ASCII data, but for some unknown reason we sometimes get data with unsigned
// byte value >127 which in itself is very strange. We filter these out for the time being
if (currentByte < 128) {
readBuffer.put((byte) currentByte);
if (previousByte == CR && currentByte == LF) {
readBuffer.flip();
parseAndQueue(readBuffer);
readBuffer.clear();
previousByte = -1;
} else {
previousByte = currentByte;
}
}
}
} catch (IOException e) {
logger.debug("Error receiving data on serial port {}: {}", port, e.getMessage());
}
break;
}
}
public void sendMessage(Message message) {
if (message != null && isInitialised()) {
try {
logger.debug("Adding to sendQueue: {}", message);
sendQueue.put(message);
} catch (InterruptedException e) {
logger.error("Interrupted while adding to sendQueue: {}", message);
}
}
}
public void sendPriorityMessage(Message message) {
if (message != null && isInitialised()) {
try {
logger.debug("Adding to prioritySendQueue: {}", message);
prioritySendQueue.put(message);
} catch (InterruptedException e) {
logger.error("Interrupted while adding to prioritySendQueue: {}", message);
}
}
}
@Override
public boolean postUpdate(String MAC, PlugwiseCommandType type, Object value) {
if (MAC != null && type != null && value != null) {
binding.postUpdate(MAC, type, value);
return true;
} else {
return false;
}
}
/**
* Parse a buffer into a Message and put it in the appropriate queue for further processing
*
* @param readBuffer - the string to parse
*/
private void parseAndQueue(ByteBuffer readBuffer) {
if (readBuffer != null) {
String response = new String(readBuffer.array(), 0, readBuffer.limit());
response = StringUtils.chomp(response);
Matcher matcher = RESPONSE_PATTERN.matcher(response);
if (matcher.matches()) {
String protocolHeader = matcher.group(1);
String command = matcher.group(2);
String sequence = matcher.group(3);
String payload = matcher.group(4);
String CRC = matcher.group(5);
if (protocolHeader.equals(PROTOCOL_HEADER)) {
String calculatedCRC = getCRCFromString(command + sequence + payload);
if (calculatedCRC.equals(CRC)) {
int messageTypeNumber = Integer.parseInt(command, 16);
MessageType messageType = MessageType.forValue(messageTypeNumber);
int sequenceNumber = Integer.parseInt(sequence, 16);
if (messageType == null) {
logger.debug("Received unrecognized messageTypeNumber:{} command:{} sequence:{} payload:{}",
messageTypeNumber, command, sequenceNumber, payload);
return;
}
logger.debug("Received message: command:{} sequence:{} payload:{}", messageType, sequenceNumber,
payload);
Message message = createMessage(messageType, command, sequenceNumber, payload);
try {
if (message instanceof AcknowledgeMessage && !((AcknowledgeMessage) message).isExtended()) {
logger.debug("Adding to acknowledgedQueue: {}", message);
acknowledgedQueue.put((AcknowledgeMessage) message);
} else {
logger.debug("Adding to receivedQueue: {}", message);
receivedQueue.put(message);
}
} catch (InterruptedException e) {
Thread.interrupted();
}
} else {
logger.error("Plugwise protocol CRC error: {} does not match {} in message", calculatedCRC,
CRC);
}
} else {
logger.debug("Plugwise protocol header error: {} in message {}", protocolHeader, response);
}
} else {
if (!response.contains("APSRequestNodeInfo")) {
logger.error("Plugwise protocol message error: {}", response);
}
}
}
}
private Message createMessage(MessageType messageType, String command, int sequenceNumber, String payLoad) {
switch (messageType) {
case ACKNOWLEDGEMENT:
return new AcknowledgeMessage(sequenceNumber, payLoad);
case NODE_AVAILABLE:
return new NodeAvailableMessage(sequenceNumber, payLoad);
case INITIALISE_RESPONSE:
return new InitialiseResponseMessage(sequenceNumber, payLoad);
case DEVICE_ROLECALL_RESPONSE:
return new RoleCallResponseMessage(sequenceNumber, payLoad);
case DEVICE_CALIBRATION_RESPONSE:
return new CalibrationResponseMessage(sequenceNumber, payLoad);
case DEVICE_INFORMATION_RESPONSE:
return new InformationResponseMessage(sequenceNumber, payLoad);
case REALTIMECLOCK_GET_RESPONSE:
return new RealTimeClockGetResponseMessage(sequenceNumber, payLoad);
case CLOCK_GET_RESPONSE:
return new ClockGetResponseMessage(sequenceNumber, payLoad);
case POWER_BUFFER_RESPONSE:
return new PowerBufferResponseMessage(sequenceNumber, payLoad);
case POWER_INFORMATION_RESPONSE:
return new PowerInformationResponseMessage(sequenceNumber, payLoad);
case ANNOUNCE_AWAKE_REQUEST:
return new AnnounceAwakeRequestMessage(sequenceNumber, payLoad);
case BROADCAST_GROUP_SWITCH_RESPONSE:
return new BroadcastGroupSwitchResponseMessage(sequenceNumber, payLoad);
case MODULE_JOINED_NETWORK_REQUEST:
return new ModuleJoinedNetworkRequestMessage(sequenceNumber, payLoad);
case SENSE_REPORT_REQUEST:
return new SenseReportRequestMessage(sequenceNumber, payLoad);
default:
logger.debug("Received unrecognized command: {}", command);
return null;
}
}
@Override
public boolean processMessage(Message message) {
if (message != null) {
// deal with the messages that are destined to a very specific plugwise device, and only if we already have
// a reference to them
switch (message.getType()) {
case ACKNOWLEDGEMENT:
if (((AcknowledgeMessage) message).isExtended()) {
String cpMAC = ((AcknowledgeMessage) message).getCirclePlusMAC();
switch (((AcknowledgeMessage) message).getExtensionCode()) {
case CIRCLEPLUS:
CirclePlus cp = (CirclePlus) getDeviceByMAC(cpMAC);
if (!cpMAC.equals("") && cp == null) {
cp = new CirclePlus(cpMAC, this, cpMAC);
plugwiseDeviceCache.add(cp);
logger.debug("Added a CirclePlus with MAC {} to the cache", cp.getMAC());
}
if (cp != null) {
cp.updateInformation();
cp.calibrate();
cp.setClock();
// initiate a "role call" request in the network
cp.roleCall(0);
}
break;
case TIMEOUT:
// Get the original request message from sentQueue and resend it
logger.error("Received timeout ack for: {}", message);
Message sentMessage = null;
sentQueueLock.lock();
try {
Iterator<Message> messageIterator = sentQueue.iterator();
while (messageIterator.hasNext()) {
sentMessage = messageIterator.next();
if (sentMessage.getSequenceNumber() == message.getSequenceNumber()) {
logger.debug("Timeout: removing from the sentQueue: {}", sentMessage);
sentQueue.remove(sentMessage);
break;
}
}
} finally {
sentQueueLock.unlock();
}
if (sentMessage != null) {
sentMessage.setSequenceNumber(0);
sendMessage(sentMessage);
}
return false;
case ON:
// Protocol Reverse Engineering: We have to decide whether we trust the ACK messages
// sent back to the Stick or not.
// If we do, then uncomment this line. If not, we will rely on a formal
// DEVICE_INFORMATION_REQUEST to get
// the real state of the Circle(+)
// postUpdate(((AcknowledgeMessage)message).getExtendedMAC(),
// PlugwiseCommandType.CURRENTSTATE, ((AcknowledgeMessage)message).isOn());
break;
case OFF:
// Protocol Reverse Engineering: : Idem as in ON
// postUpdate(((AcknowledgeMessage)message).getExtendedMAC(),
// PlugwiseCommandType.CURRENTSTATE, ((AcknowledgeMessage)message).isOff());
break;
default:
logger.debug("Plugwise Unknown Acknowledgement message Extension");
break;
}
}
return true;
case INITIALISE_RESPONSE:
MAC = ((InitialiseResponseMessage) message).getMAC();
initialised = true;
// is the network online?
if (((InitialiseResponseMessage) message).isOnline()) {
String cpMAC = ((InitialiseResponseMessage) message).getCirclePlusMAC();
CirclePlus cp = (CirclePlus) getDeviceByMAC(cpMAC);
if (!cpMAC.equals("") && cp == null) {
cp = new CirclePlus(cpMAC, this, cpMAC);
plugwiseDeviceCache.add(cp);
logger.debug("Added a CirclePlus with MAC {} to the cache", cp.getMAC());
}
if (cp != null) {
cp.updateInformation();
cp.calibrate();
cp.setClock();
// initiate a "role call" request in the network
cp.roleCall(0);
}
} else {
logger.debug("The network is not online. nothing to do here");
}
return true;
case NODE_AVAILABLE:
String node = ((NodeAvailableMessage) message).getMAC();
Circle someCircle = (Circle) getDeviceByMAC(node);
if (someCircle == null) {
Circle newCircle = new Circle(node, this, node);
plugwiseDeviceCache.add(newCircle);
// confirm to the new node that it is added to the network
NodeAvailableResponseMessage response = new NodeAvailableResponseMessage(true, node);
sendMessage(response);
newCircle.updateInformation();
newCircle.calibrate();
}
return true;
default:
return super.processMessage(message);
}
}
return false;
}
private String getCRCFromString(String buffer) {
int crc = 0x0000;
int polynomial = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
byte[] bytes = new byte[0];
try {
bytes = buffer.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
logger.debug("Could not fetch ASCII bytes from String ", buffer);
}
for (byte b : bytes) {
for (int i = 0; i < 8; i++) {
boolean bit = ((b >> (7 - i) & 1) == 1);
boolean c15 = ((crc >> 15 & 1) == 1);
crc <<= 1;
if (c15 ^ bit) {
crc ^= polynomial;
}
}
}
crc &= 0xffff;
return (String.format("%04X", crc).toUpperCase());
}
private static class SendThread extends Thread {
private final Stick stick;
public SendThread(Stick stick) {
super("Plugwise SendThread");
this.stick = stick;
setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
Message message = stick.prioritySendQueue.poll();
if (message == null) {
message = stick.sendQueue.poll(100, TimeUnit.MILLISECONDS);
}
if (message == null) {
continue;
}
sendMessage(message);
sleep(stick.interval);
} catch (InterruptedException e) {
// That's our signal to stop
break;
} catch (Exception e) {
logger.error("SendJob: unexpected error", e);
}
}
}
private void sendMessage(Message message) throws InterruptedException {
if (message.getAttempts() < stick.maxRetries) {
message.increaseAttempts();
String messageHexString = message.toHexString();
String packetString = PROTOCOL_HEADER + messageHexString + PROTOCOL_TRAILER;
ByteBuffer bytebuffer = ByteBuffer.allocate(packetString.length());
bytebuffer.put(packetString.getBytes());
bytebuffer.rewind();
try {
logger.debug("Sending: {} as {}", message, messageHexString);
stick.outputChannel.write(bytebuffer);
} catch (IOException e) {
logger.error("Error writing '{}' to serial port {}: {}", packetString, stick.port, e.getMessage());
return;
}
// Poll the acknowledgement message for at most 1 second, normally it is received within 75ms
AcknowledgeMessage ack = stick.acknowledgedQueue.poll(1, TimeUnit.SECONDS);
logger.debug("Removing from acknowledgedQueue: {}", ack);
if (ack == null) {
logger.error("Error sending: No ACK received after 1 second: {}", packetString);
} else if (!ack.isSuccess()) {
if (ack.isError()) {
logger.error("Error sending: Negative ACK: {}", packetString);
}
} else {
// update the sent message with the new sequence number
message.setSequenceNumber(ack.getSequenceNumber());
// place the sent message in the sent Q
logger.debug("Adding to sentQueue: {}", message);
stick.sentQueueLock.lock();
try {
if (stick.sentQueue.size() == maxBufferSize) {
// For some @#$@#$ reason plugwise devices, or the Stick, does not send responses
// to Requests. They clog the sentQueue. Let's flush some part of the queue
Message someMessage = stick.sentQueue.poll();
logger.debug("Flushing from sentQueue: {}", someMessage);
}
stick.sentQueue.put(message);
} finally {
stick.sentQueueLock.unlock();
}
}
} else {
// max attempts reached
// we give up, and to a network reset
logger.error(
"Giving finally up on Plugwise protocol data unit after attempts: {} MAC:{} command:{} sequence:{} payload:{}",
message.getAttempts(), message.getMAC(), message.getType(), message.getSequenceNumber(),
message.getPayLoad());
}
}
}
private static class ProcessMessageThread extends Thread {
private Stick stick;
public ProcessMessageThread(Stick stick) {
super("Plugwise ProcessMessageThread");
this.stick = stick;
setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
Message message = stick.receivedQueue.take();
processMessage(message);
} catch (InterruptedException e) {
// That's our signal to stop
break;
} catch (Exception e) {
logger.error("SendJob: unexpected error", e);
}
}
}
private void processMessage(Message message) {
PlugwiseDevice target = stick.getDeviceByMAC(message.getMAC());
boolean result = false;
if (target != null) {
result = target.processMessage(message);
} else {
// if we can not find the target MAC for this message, we let the stick deal with it
result = stick.processMessage(message);
}
// after processing the response to a message, we remove any reference to the original request
// stored in the sentQueue
// WARNING: We assume that each request sent out can only be followed bye EXACTLY ONE response - so
// far it seems that the PW protocol is operating in that way
if (result) {
stick.sentQueueLock.lock();
try {
Iterator<Message> messageIterator = stick.sentQueue.iterator();
while (messageIterator.hasNext()) {
Message sentMessage = messageIterator.next();
if (sentMessage.getSequenceNumber() == message.getSequenceNumber()) {
logger.debug("Removing from sentQueue: {}", sentMessage);
stick.sentQueue.remove(sentMessage);
break;
}
}
} finally {
stick.sentQueueLock.unlock();
}
}
}
}
public static abstract class AbstractPlugwiseDeviceJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
Stick theStick = (Stick) dataMap.get(STICK_JOB_DATA_KEY);
if (theStick.isInitialised()) {
String MAC = (String) dataMap.get(MAC_JOB_DATA_KEY);
PlugwiseDevice device = theStick.getDeviceByMAC(MAC);
if (device != null) {
executeDeviceJob(device);
}
}
}
abstract protected void executeDeviceJob(PlugwiseDevice device);
}
public static class PowerInformationJob extends AbstractPlugwiseDeviceJob {
@Override
protected void executeDeviceJob(PlugwiseDevice device) {
if (device instanceof Circle) {
((Circle) device).updateCurrentEnergy();
}
}
}
public static class PowerBufferJob extends AbstractPlugwiseDeviceJob {
@Override
protected void executeDeviceJob(PlugwiseDevice device) {
if (device instanceof Circle) {
((Circle) device).updateEnergy(false);
}
}
}
public static class ClockJob extends AbstractPlugwiseDeviceJob {
@Override
protected void executeDeviceJob(PlugwiseDevice device) {
if (device instanceof Circle) {
((Circle) device).updateSystemClock();
}
}
}
public static class RealTimeClockJob extends AbstractPlugwiseDeviceJob {
@Override
protected void executeDeviceJob(PlugwiseDevice device) {
if (device instanceof CirclePlus) {
((CirclePlus) device).updateRealTimeClock();
}
}
}
public static class InformationJob extends AbstractPlugwiseDeviceJob {
@Override
protected void executeDeviceJob(PlugwiseDevice device) {
if (device instanceof Circle) {
((Circle) device).updateInformation();
}
}
}
}