/** * Copyright (c) 2010-2016, 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.upb.internal; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.openhab.binding.upb.internal.UPBReader.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Used to write data to the UPB modem. * * @author cvanorman * @since 1.9.0 */ public class UPBWriter { /** * Time in milliseconds to wait for an ACK from the modem after writing a * message. */ private static long ACK_TIMEOUT = 500; private static final Logger logger = LoggerFactory.getLogger(UPBWriter.class); /** * Asynchronous queue for writing data to the UPB modem. */ private ExecutorService executor = Executors.newSingleThreadExecutor(); /** * The UPB modem's OutputStream. */ private OutputStream outputStream; /** * UPBReader that is monitoring the modem's InputStream. */ private UPBReader upbReader; /** * Instantiates a new {@link UPBWriter} using the given modem * {@link OutputStream}. * * @param outputStream * the {@link OutputStream} from the UPB modem. * @param upbReader * the {@link UPBReader} that is monitoring the same UPB modem. */ public UPBWriter(OutputStream outputStream, UPBReader upbReader) { this.outputStream = outputStream; this.upbReader = upbReader; } /** * Queues a message to be written to the modem. * * @param message * the message to be written. */ public void queueMessage(MessageBuilder message) { String data = message.build(); logger.debug("Queueing message {}.", data); executor.execute(new Message(data.getBytes())); } /** * Cancels all queued messages and releases resources. This instance cannot * be used again and a new {@link UPBWriter} must be instantiated after * calling this method. */ public void shutdown() { executor.shutdownNow(); try { outputStream.close(); } catch (IOException e) { } logger.debug("UPBWriter shutdown"); } /** * {@link Runnable} implementation used to write data to the UPB modem. * * @author cvanorman * */ private class Message implements Runnable, Listener { private boolean waitingOnAck = true; private boolean ackReceived = false; private byte[] data; private Message(byte[] data) { this.data = data; } private synchronized void ackReceived(boolean ack) { waitingOnAck = false; ackReceived = ack; notify(); } private synchronized boolean waitForAck(int retryCount) { long start = System.currentTimeMillis(); while (waitingOnAck && (System.currentTimeMillis() - start) < ACK_TIMEOUT) { try { wait(ACK_TIMEOUT); } catch (InterruptedException e) { } if (!waitingOnAck) { if (ackReceived) { logger.debug("Message {} ack received.", new String(data)); } else { logger.debug("Message {} not ack'd.", new String(data)); } } else { logger.debug("Message {} ack timed out.", new String(data)); } } return ackReceived || retryCount == 0; } /** * {@inheritDoc} */ @Override public void messageReceived(UPBMessage message) { switch (message.getType()) { case BUSY: case NAK: ackReceived(false); break; case ACK: ackReceived(true); break; default: } } /** * {@inheritDoc} */ @Override public void run() { try { upbReader.addListener(this); int retryCount = 3; do { ackReceived = false; waitingOnAck = true; logger.debug("Writing bytes: {}", new String(data)); outputStream.write(0x14); outputStream.write(data); outputStream.write(0x0d); } while (!waitForAck(retryCount--)); } catch (IOException e) { } finally { upbReader.removeListener(this); } } } }