Java tutorial
/** * Copyright (c) 2010-2015, openHAB.org and others. * * 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 java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; 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.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.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.quartz.JobBuilder.*; import static org.quartz.TriggerBuilder.*; import static org.quartz.SimpleScheduleBuilder.*; import org.quartz.impl.matchers.KeyMatcher; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; 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 final static String PROTOCOL_HEADER = "\u0005\u0005\u0003\u0003"; /** Plugwise protocol trailer code (hex) */ private final static String PROTOCOL_TRAILER = "\r\n"; /** counter to track Quartz Jobs */ private static int counter = 0; // Serial communication fields private String port; private CommPortIdentifier portId; private SerialPort serialPort; private WritableByteChannel outputChannel; private ByteBuffer readBuffer; // Queue fields protected static int maxBufferSize = 1024; protected final ReentrantLock queueLock = new ReentrantLock(); protected final ReentrantLock receiveLock = new ReentrantLock(); protected ArrayBlockingQueue<Message> sendQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true); protected ArrayBlockingQueue<Message> sentQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true); protected ArrayBlockingQueue<Message> receivedQueue = new ArrayBlockingQueue<Message>(maxBufferSize, true); // Stick fields private boolean initialised = false; protected List<PlugwiseDevice> plugwiseDeviceCache = Collections .synchronizedList(new ArrayList<PlugwiseDevice>()); 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); try { initialize(); } catch (PlugwiseInitializationException e) { logger.error("Failed to initialize Plugwise stick: {}", e.getLocalizedMessage()); initialised = false; } } protected static Comparator<PlugwiseDevice> plugComparator = new Comparator<PlugwiseDevice>() { public int compare(PlugwiseDevice u1, PlugwiseDevice u2) { return u1.getMAC().compareTo(u2.getMAC()); } }; protected static Comparator<PlugwiseDevice> friendlyPlugComparator = new Comparator<PlugwiseDevice>() { public int compare(PlugwiseDevice u1, PlugwiseDevice u2) { return u1.getFriendlyName().compareTo(u2.getFriendlyName()); } }; protected PlugwiseDevice getDevice(String id) { PlugwiseDevice someDevice = getDeviceByMAC(id); if (someDevice == null) { return getDeviceByName(id); } else { return someDevice; } } protected PlugwiseDevice getDeviceByMAC(String MAC) { PlugwiseDevice queryDevice = new PlugwiseDevice(MAC, null, ""); Collections.sort(plugwiseDeviceCache, plugComparator); int index = Collections.binarySearch(plugwiseDeviceCache, queryDevice, plugComparator); if (index >= 0) { return plugwiseDeviceCache.get(index); } else { return null; } } protected PlugwiseDevice getDeviceByName(String name) { PlugwiseDevice queryDevice = new PlugwiseDevice(null, null, name); Collections.sort(plugwiseDeviceCache, friendlyPlugComparator); int index = Collections.binarySearch(plugwiseDeviceCache, queryDevice, friendlyPlugComparator); if (index >= 0) { return plugwiseDeviceCache.get(index); } else { return null; } } 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 if (this.plugwiseDeviceCache != null) { plugwiseDeviceCache = Collections.synchronizedList(new ArrayList<PlugwiseDevice>()); } // 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 = (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.toString()); } // set up the Quartz jobs Scheduler sched = null; try { sched = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e) { logger.error("Error getting a reference to the Quartz Scheduler"); } JobDataMap map = new JobDataMap(); map.put("Stick", this); JobDetail job = newJob(SendJob.class).withIdentity("Send-0", "Plugwise").usingJobData(map).build(); Trigger trigger = newTrigger().withIdentity("Send-0", "Plugwise").startNow().build(); try { sched.getListenerManager().addJobListener(new SendJobListener("JobListener-" + job.getKey().toString()), KeyMatcher.keyEquals(job.getKey())); } catch (SchedulerException e1) { logger.error("An exception occured while attaching a Quartz Send Job Listener"); } try { sched.scheduleJob(job, trigger); } catch (SchedulerException e) { logger.error("Error scheduling a job with the Quartz Scheduler"); } map = new JobDataMap(); map.put("Stick", this); job = newJob(ProcessMessageJob.class).withIdentity("ProcessMessage", "Plugwise").usingJobData(map).build(); trigger = newTrigger().withIdentity("ProcessMessage", "Plugwise").startNow() .withSchedule(simpleSchedule().repeatForever().withIntervalInMilliseconds(50)).build(); try { sched.scheduleJob(job, trigger); } catch (SchedulerException e) { logger.error("Error scheduling a job with the Quartz Scheduler"); } // initialise the Stick initialised = true; InitialiseRequestMessage message = new InitialiseRequestMessage(); sendMessage(message); } /** * Close this serial device associated with the Stick */ public void close() { 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; } 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 boolean newlineFound = false; if (readBuffer == null) { readBuffer = ByteBuffer.allocate(maxBufferSize); } try { // read data from serial device while (serialPort.getInputStream().available() > 0) { int aByte = serialPort.getInputStream().read(); if ((aByte) == 13) { readBuffer.put((byte) aByte); int cr = serialPort.getInputStream().read(); readBuffer.put((byte) cr); newlineFound = true; break; } //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 (aByte < 128) { readBuffer.put((byte) aByte); } } // process data if (readBuffer.position() != 0 && newlineFound == true) { readBuffer.flip(); parseAndQueue(readBuffer); readBuffer = null; } } catch (IOException e) { logger.debug("Error receiving data on serial port {}: {}", new String[] { port, e.getMessage() }); } break; } } public void sendMessage(Message message) { if (message != null && isInitialised()) { try { logger.debug("sendMessage: Stick send message: {}", message.toString()); sendQueue.put(message); } catch (InterruptedException e) { logger.error("Error sending Plugwise message: {}", message.toString()); } } } 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) { Pattern RESPONSE_PATTERN = Pattern.compile("(.{4})(\\w{4})(\\w{4})(\\w*?)(\\w{4})"); 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)) { logger.debug( "parseAndQueue: Parsing Plugwise protocol data unit: command:{} sequence:{} payload:{}", new String[] { MessageType.forValue(Integer.parseInt(command, 16)).toString(), Integer.toString(Integer.parseInt(sequence, 16)), payload }); Message theMessage = null; switch (MessageType.forValue(Integer.parseInt(command, 16))) { case ACKNOWLEDGEMENT: theMessage = new AcknowledgeMessage(Integer.parseInt(sequence, 16), payload); break; case NODE_AVAILABLE: theMessage = new NodeAvailableMessage(Integer.parseInt(sequence, 16), payload); break; case INITIALISE_RESPONSE: theMessage = new InitialiseResponseMessage(Integer.parseInt(sequence, 16), payload); break; case DEVICE_ROLECALL_RESPONSE: theMessage = new RoleCallResponseMessage(Integer.parseInt(sequence, 16), payload); break; case DEVICE_CALIBRATION_RESPONSE: theMessage = new CalibrationResponseMessage(Integer.parseInt(sequence, 16), payload); break; case DEVICE_INFORMATION_RESPONSE: theMessage = new InformationResponseMessage(Integer.parseInt(sequence, 16), payload); break; case REALTIMECLOCK_GET_RESPONSE: theMessage = new RealTimeClockGetResponseMessage(Integer.parseInt(sequence, 16), payload); break; case CLOCK_GET_RESPONSE: theMessage = new ClockGetResponseMessage(Integer.parseInt(sequence, 16), payload); break; case POWER_BUFFER_RESPONSE: theMessage = new PowerBufferResponseMessage(Integer.parseInt(sequence, 16), payload); break; case POWER_INFORMATION_RESPONSE: theMessage = new PowerInformationResponseMessage(Integer.parseInt(sequence, 16), payload); break; default: logger.debug( "parseAndQueue: Received unrecognized Plugwise protocol data unit: command:{} sequence:{} payload:{}", new String[] { command, Integer.toString(Integer.parseInt(sequence, 16)), payload }); break; } ; if (theMessage != null) { try { receiveLock.lock(); logger.debug( "parseAndQueue: {} messages before the message ({}) put in the receiveQ", receivedQueue.size(), theMessage.toString()); receivedQueue.put(theMessage); receiveLock.unlock(); } catch (InterruptedException e) { logger.error( "Error queueing Plugwise protocol data unit: command:{} sequence:{} payload:{}", new String[] { MessageType.forValue(Integer.parseInt(command, 16)).toString(), Integer.toString(Integer.parseInt(sequence, 16)), payload }); } } } else { logger.error("Plugwise protocol CRC error: {} does not match {} in message", new String[] { calculatedCRC, CRC }); } } else { logger.debug("parseAndQueue: Plugwise protocol header error: {} in message {}", new String[] { protocolHeader, response }); } } else { if (!response.contains("APSRequestNodeInfo")) { logger.error("Plugwise protocol message error: {} ", response); } } } } 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()) { switch (((AcknowledgeMessage) message).getExtensionCode()) { case CIRCLEPLUS: CirclePlus circlePlus11 = (CirclePlus) getDeviceByMAC( ((AcknowledgeMessage) message).getCirclePlusMAC()); if (!((AcknowledgeMessage) message).getCirclePlusMAC().equals("") && circlePlus11 == null) { circlePlus11 = new CirclePlus(((AcknowledgeMessage) message).getCirclePlusMAC(), this); plugwiseDeviceCache.add(circlePlus11); logger.debug("Added a CirclePlus with MAC {} to the cache", circlePlus11.getMAC()); } circlePlus11.updateInformation(); circlePlus11.calibrate(); circlePlus11.setClock(); if (circlePlus11 != null) { // initiate a "role call" request in the network circlePlus11.roleCall(0); } break; case TIMEOUT: // we put the message back in the queue, without tagging it logger.error("Timeout sending Plugwise message : {}", ((AcknowledgeMessage) message).toString()); // traverse the sent Q for the Iterator<Message> messageIterator = sentQueue.iterator(); Message aMessage = null; while (messageIterator.hasNext()) { aMessage = messageIterator.next(); if (aMessage.getSequenceNumber() == message.getSequenceNumber()) { logger.debug("processMessage: timeout : removing a msg from the senTq: {}", aMessage.toString()); sentQueue.remove(aMessage); break; } } if (aMessage != null) { //reset the sequence number and put it back in the send Q aMessage.setSequenceNumber(0); sendMessage(aMessage); } 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()) { CirclePlus circlePlus = (CirclePlus) getDeviceByMAC( ((InitialiseResponseMessage) message).getCirclePlusMAC()); if (!((InitialiseResponseMessage) message).getCirclePlusMAC().equals("") && circlePlus == null) { circlePlus = new CirclePlus(((InitialiseResponseMessage) message).getCirclePlusMAC(), this); plugwiseDeviceCache.add(circlePlus); logger.debug("Added a CirclePlus with MAC {} to the cache", circlePlus.getMAC()); } circlePlus.updateInformation(); circlePlus.calibrate(); circlePlus.setClock(); if (circlePlus != null) { // initiate a "role call" request in the network circlePlus.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()); } public static class PowerInformationJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); String MAC = (String) dataMap.get("MAC"); if (theStick.isInitialised()) { PlugwiseDevice device = theStick.getDeviceByMAC(MAC); if (device != null) { if (device.getType().equals(DeviceType.Circle) || device.getType().equals(DeviceType.CirclePlus)) { ((Circle) device).updateCurrentEnergy(); } } } } } public static class SendJob implements Job { private Stick theStick; public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); theStick = (Stick) dataMap.get("Stick"); // logger.debug("SendJob: Executing Quartz Send Job"); if (theStick.isInitialised()) { // loop through the send queue and send out all messages logger.debug("SendJob: {} messages in the sendQ ", theStick.sendQueue.size()); Message message = theStick.sendQueue.poll(); while (message != null) { sendMessage(message); try { Thread.sleep(theStick.interval); } catch (InterruptedException e) { logger.debug( "An exception occurred while putting the Plugwise SendJob thread to sleep : {}", e.getMessage()); } logger.debug("SendJob: in loop: {} messages in the sendQ ", theStick.sendQueue.size()); message = theStick.sendQueue.poll(); } } } private boolean sendMessage(Message message) { if (message != null) { if (message.getAttempts() < theStick.maxRetries) { message.increaseAttempts(); logger.debug( "sendMessage: Sending Plugwise protocol data unit: attempts: {} MAC:{} command:{} sequence:{} full HEX:{}", new String[] { Integer.toString(message.getAttempts()), message.getMAC(), message.getType().toString(), Integer.toString(message.getSequenceNumber()), message.toHexString() }); String packedString = PROTOCOL_HEADER + message.toHexString() + PROTOCOL_TRAILER; ByteBuffer bytebuffer = ByteBuffer.allocate(packedString.length()); bytebuffer.put(packedString.getBytes()); bytebuffer.rewind(); logger.debug("sendMessage: Locking the queues"); theStick.queueLock.lock(); try { logger.debug("sendMessage: Writing message to the outputchannel of the stick : {}", message.toString()); theStick.outputChannel.write(bytebuffer); } catch (IOException e) { logger.error("Error writing '{}' to serial port {}: {}", new String[] { packedString, theStick.port, e.getMessage() }); } // wait for the confirmation message by inspecting the received Q Message lastMessage = null; logger.debug("sendMessage: Entering loop for lastMessage"); while (lastMessage == null) { // logger.debug("sendMessage: Locking the Receive queues"); theStick.receiveLock.lock(); Iterator<Message> messageIterator = theStick.receivedQueue.iterator(); while (messageIterator.hasNext()) { Message aMessage = messageIterator.next(); if (aMessage.getType().equals(MessageType.ACKNOWLEDGEMENT)) { if (!((AcknowledgeMessage) aMessage).isExtended()) { lastMessage = aMessage; logger.debug("sendMessage: Removing an ACK from the RecQ: {}", lastMessage.toString()); theStick.receivedQueue.remove(lastMessage); break; } } } // logger.debug("sendMessage: Unlocking the Receive queues"); theStick.receiveLock.unlock(); } logger.debug("sendMessage: Exiting loop for lastMessage"); AcknowledgeMessage ack = (AcknowledgeMessage) lastMessage; if (!ack.isSuccess()) { if (ack.isError()) { logger.error("Error sending Plugwise message: Negative ACK: {}", packedString); } } else { // update the sent message with the new sequence number message.setSequenceNumber(ack.getSequenceNumber()); // place the sent message in the sent Q try { logger.debug("sendMessage: putting message in the senTq : {}", message.toString()); if (theStick.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 = theStick.sentQueue.poll(); logger.debug("Flushing a message from the sentQueue: {}", someMessage); } theStick.sentQueue.put(message); logger.debug("sendMessage: there are now {} msg in the senTq", theStick.sentQueue.size()); //logger.debug("senTq is now {}",theStick.sentQueue); } catch (InterruptedException e) { logger.error("Error storing Plugwise message in the sent queue: {}", message.toString()); } } logger.debug("sendMessage: Unlocking the queues"); theStick.queueLock.unlock(); return true; } 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:{}", new String[] { Integer.toString(message.getAttempts()), message.getMAC(), message.getType().toString(), Integer.toString(message.getSequenceNumber()), message.getPayLoad() }); } } return false; } } public class SendJobListener implements JobListener { private String name; public SendJobListener(String name) { this.name = name; } public String getName() { return name; } public void jobToBeExecuted(JobExecutionContext context) { // do something with the event } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); // logger.debug("SendJobListener: SJobListeren reschedule a job"); Scheduler sched = null; try { sched = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e) { logger.error("Error getting a reference to the Quartz Scheduler"); } JobDataMap map = new JobDataMap(); map.put("Stick", theStick); Stick.counter++; JobDetail job = newJob(SendJob.class).withIdentity("Send-" + Stick.counter, "Plugwise") .usingJobData(map).build(); Trigger trigger = newTrigger().withIdentity("Send-" + Stick.counter, "Plugwise").startNow().build(); try { sched.getListenerManager().addJobListener( new SendJobListener("JobListener-" + job.getKey().toString()), KeyMatcher.keyEquals(job.getKey())); } catch (SchedulerException e1) { logger.error("An exception occured while attaching a Quartz Send Job Listener"); } try { sched.scheduleJob(job, trigger); } catch (SchedulerException e) { logger.error("Error scheduling a job with the Quartz Scheduler : {}", e.getMessage()); } } public void jobExecutionVetoed(JobExecutionContext context) { // do something with the event } } public static class ProcessMessageJob implements Job { private Stick theStick; public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); theStick = (Stick) dataMap.get("Stick"); if (theStick.isInitialised()) { logger.debug("ProcessMessageJob: Locking the queues"); theStick.queueLock.lock(); logger.debug("ProcessMessageJob: there are {} msg in the receivedQ", theStick.receivedQueue.size()); Message message = theStick.receivedQueue.poll(); logger.debug("ProcessMessageJob: Unlocking the queues"); theStick.queueLock.unlock(); if (message != null) { PlugwiseDevice target = theStick.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 = theStick.processMessage(message); } // after processing the response to a message, we remove any reference to the original request stored in the sent Q // 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) { Iterator<Message> messageIterator = theStick.sentQueue.iterator(); while (messageIterator.hasNext()) { Message aMessage = messageIterator.next(); if (aMessage.getSequenceNumber() == message.getSequenceNumber()) { logger.debug("execute: removing a msg from the senTq: {}", aMessage.toString()); theStick.sentQueue.remove(aMessage); break; } } } } } } } public static class PowerBufferJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); String MAC = (String) dataMap.get("MAC"); if (theStick.isInitialised()) { PlugwiseDevice device = theStick.getDeviceByMAC(MAC); if (device != null) { if (device.getType().equals(DeviceType.Circle) || device.getType().equals(DeviceType.CirclePlus)) { ((Circle) device).updateEnergy(false); } } } } } public static class ClockJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); String MAC = (String) dataMap.get("MAC"); if (theStick.isInitialised()) { PlugwiseDevice device = theStick.getDeviceByMAC(MAC); if (device != null) { if (device.getType().equals(DeviceType.Circle) || device.getType().equals(DeviceType.CirclePlus)) { ((Circle) device).updateSystemClock(); } } } } } public static class RealTimeClockJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); String MAC = (String) dataMap.get("MAC"); if (theStick.isInitialised()) { PlugwiseDevice device = theStick.getDeviceByMAC(MAC); if (device != null) { if (device.getType().equals(DeviceType.CirclePlus)) { ((CirclePlus) device).updateRealTimeClock(); } } } } } public static class InformationJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // get the reference to the Stick JobDataMap dataMap = context.getJobDetail().getJobDataMap(); Stick theStick = (Stick) dataMap.get("Stick"); String MAC = (String) dataMap.get("MAC"); if (theStick.isInitialised()) { PlugwiseDevice device = theStick.getDeviceByMAC(MAC); if (device != null) { if (device.getType().equals(DeviceType.Circle) || device.getType().equals(DeviceType.CirclePlus)) { ((Circle) device).updateInformation(); } } } } } }