/** * 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.io.transport.cul.internal; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.openhab.io.transport.cul.CULCommunicationException; import org.openhab.io.transport.cul.CULDeviceException; import org.openhab.io.transport.cul.CULListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base class for all CULHandler which brings some convenience * regarding registering listeners and detecting forbidden messages. * * @author Till Klocke * @since 1.4.0 */ public abstract class AbstractCULHandler<T extends CULConfig> implements CULHandlerInternal<T> { private final static Logger log = LoggerFactory.getLogger(AbstractCULHandler.class); /** * Thread which sends all queued commands to the CUL. * * @author Till Klocke * @since 1.4.0 * */ private class SendThread extends Thread { private final Logger logger = LoggerFactory.getLogger(SendThread.class); @Override public void run() { while (!isInterrupted()) { String command = sendQueue.poll(); if (command != null) { if (!command.endsWith("\r\n")) { command = command + "\r\n"; } try { writeMessage(command); } catch (CULCommunicationException e) { logger.warn("Error while writing command to CUL", e); } } try { Thread.sleep(10); } catch (InterruptedException e) { logger.debug("Error while sleeping in SendThread", e); } } } } /** * Wrapper class wraps a CULListener and a received Strings and gets * executed by a executor in its own thread. * * @author Till Klocke * @since 1.4.0 * */ private static class NotifyDataReceivedRunner implements Runnable { private String message; private CULListener listener; public NotifyDataReceivedRunner(CULListener listener, String message) { this.message = message; this.listener = listener; } @Override public void run() { listener.dataReceived(message); } } /** * Executor to handle received messages. Every listern should be called in * its own thread. */ protected Executor receiveExecutor = Executors.newCachedThreadPool(); protected SendThread sendThread = new SendThread(); protected T config; protected List<CULListener> listeners = new ArrayList<CULListener>(); protected Queue<String> sendQueue = new ConcurrentLinkedQueue<String>(); protected int credit10ms = 0; protected AbstractCULHandler(T config) { this.config = config; } @Override public void registerListener(CULListener listener) { if (listener != null) { listeners.add(listener); } } @Override public void unregisterListener(CULListener listener) { if (listener != null) { listeners.remove(listener); } } @Override public boolean hasListeners() { return listeners.size() > 0; } @Override public void open() throws CULDeviceException { openHardware(); sendThread.start(); } @Override public void close() { sendThread.interrupt(); closeHardware(); } /** * initialize the CUL hardware and open the connection * * @throws CULDeviceException */ protected abstract void openHardware() throws CULDeviceException; /** * Close the connection to the hardware and clean up all resources. */ protected abstract void closeHardware(); protected abstract void write(String command); @Override public void send(String command) { if (isMessageAllowed(command)) { sendQueue.add(command); } } @Override public void sendWithoutCheck(String message) throws CULCommunicationException { sendQueue.add(message); } /** * Checks if the message would alter the RF mode of this device. * * @param message * The message to check * @return true if the message doesn't alter the RF mode, false if it does. */ protected boolean isMessageAllowed(String message) { if (message.startsWith("X") || message.startsWith("x")) { return false; } if (message.startsWith("Ar")) { return false; } return true; } /** * Notifies each CULListener about the received data in its own thread. * * @param data */ protected void notifyDataReceived(String data) { for (final CULListener listener : listeners) { receiveExecutor.execute(new NotifyDataReceivedRunner(listener, data)); } } protected void notifyError(Exception e) { for (CULListener listener : listeners) { listener.error(e); } } /** * read and process next line from underlying transport. * * @throws CULCommunicationException * if */ protected void processNextLine(String data) { log.debug("Received raw message from CUL: " + data); if ("EOB".equals(data)) { log.warn("(EOB) End of Buffer. Last message lost. Try sending less messages per time slot to the CUL"); return; } else if ("LOVF".equals(data)) { log.warn( "(LOVF) Limit Overflow: Last message lost. You are using more than 1% transmitting time. Reduce the number of rf messages"); return; } else if (data.matches("^\\d+\\s+\\d+")) { processCreditReport(data); return; } notifyDataReceived(data); requestCreditReport(); } /** * process data received from credit report * * @param data */ private void processCreditReport(String data) { // Credit report received String[] report = data.split(" "); credit10ms = Integer.parseInt(report[report.length - 1]); log.debug("credit10ms = " + credit10ms); } /** * get the remaining send time on channel as seen at the last send/receive * event. * * @return remaining send time in 10ms units */ @Override public int getCredit10ms() { return credit10ms; } /** * write out request for a credit report directly to CUL */ private void requestCreditReport() { /* this requests a report which provides credit10ms */ log.debug("Requesting credit report"); write("X\r\n"); } /** * Write a message to the CUL. * * @param message * @throws CULCommunicationException */ private void writeMessage(String message) throws CULCommunicationException { write(message); requestCreditReport(); } @Override public T getConfig() { return config; } }