/** * 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.lightwaverf.internal; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.BlockingDeque; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import org.openhab.binding.lightwaverf.internal.command.LightwaveRFCommand; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfCommandOk; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfHeatInfoRequest; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfRoomDeviceMessage; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfRoomMessage; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfSerialMessage; import org.openhab.binding.lightwaverf.internal.command.LightwaveRfVersionMessage; import org.openhab.binding.lightwaverf.internal.message.LightwaveRFMessageListener; import org.openhab.binding.lightwaverf.internal.message.LightwaveRfMessageId; import org.openhab.binding.lightwaverf.internal.message.LightwaveRfRegistrationMessageId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for transmitting LightwaveRF commands on to the * network to the LightwaveRF link which will then act upon these commands. * * This class will also listen for the responses from the wifi link and * notify listeners of these responses. * * This class implements retry logic in case it doesn't get an "OK" response * from the LightwaveRF wifi link. However please note that due to the way the * wifi link works it may receive a command but may not actually action it. In * particular this happens when messages are sent to close together. As such we * also implement a delay between each message we send. * * @author Neil Renaud * @since 1.7.0 */ public class LightwaveRFSenderThread implements Runnable, LightwaveRFMessageListener { private static final Logger logger = LoggerFactory.getLogger(LightwaveRFSenderThread.class); /** Message added to the send queue to indicate that we need to shutdown */ private static final LightwaveRFCommand STOP_MESSAGE = LightwaveRFCommand.STOP_MESSAGE; private static final Integer ONE = new Integer(1); private static final int MAX_RETRY_ATTEMPS = 5; /** * Map of CountDownLatches, used to notify when we have received an ok for * one of our messages */ private final ConcurrentMap<LightwaveRfMessageId, CountDownLatch> latchMap = new ConcurrentHashMap<LightwaveRfMessageId, CountDownLatch>(); /** Queue of messages to send */ private final BlockingDeque<LightwaveRFCommand> queue = new LinkedBlockingDeque<LightwaveRFCommand>(); /** Map of Integers so we can count retry attempts. */ private final ConcurrentMap<LightwaveRfMessageId, Integer> retryCountMap = new ConcurrentHashMap<LightwaveRfMessageId, Integer>(); /** * Timeout for OK Messages - if we don't receive an ok in this time we will * re-send. Set as short as you can without missing replies */ private final int timeoutForOkMessagesMs; /** LightwaveRF WIFI hub port. */ private final int lightwaveWifiLinkTransmitPort; /** LightwaveRF WIFI hub IP Address or broadcast address to send messages to */ private final InetAddress ipAddress; /** Socket to transmit messages */ private final DatagramSocket transmitSocket; /** Boolean to indicate if we are running */ private boolean running = true; public LightwaveRFSenderThread(DatagramSocket socket, String lightwaveWifiLinkIp, int lightwaveWifiLinkTransmitPort, int timeoutForOkMessagesMs) throws UnknownHostException { this.lightwaveWifiLinkTransmitPort = lightwaveWifiLinkTransmitPort; this.timeoutForOkMessagesMs = timeoutForOkMessagesMs; this.ipAddress = InetAddress.getByName(lightwaveWifiLinkIp); this.transmitSocket = socket; } /** * Stop the LightwaveRFReseiver Will set running to false, add a stop * message to the queue so that it stops when empty, close and set the * socket to null */ public synchronized void stopRunning() { running = false; sendLightwaveCommand(STOP_MESSAGE); } /** * Run thread, pulling off any items from the UDP commands buffer, then send * across network */ @Override public void run() { try { LightwaveRFCommand commandToSend = queue.take(); if (!commandToSend.equals(STOP_MESSAGE)) { CountDownLatch latch = new CountDownLatch(1); latchMap.putIfAbsent(commandToSend.getMessageId(), latch); retryCountMap.putIfAbsent(commandToSend.getMessageId(), ONE); netsendUDP(commandToSend); boolean unlatched = latch.await(timeoutForOkMessagesMs, TimeUnit.MILLISECONDS); // logger.info("Unlatched?" + unlatched); latchMap.remove(commandToSend.getMessageId()); if (!unlatched) { Integer sendCount = retryCountMap.get(commandToSend.getMessageId()); if (sendCount.intValue() >= MAX_RETRY_ATTEMPS) { logger.error("Unable to send message {} after {} attempts giving up", commandToSend.getLightwaveRfCommandString(), MAX_RETRY_ATTEMPS); return; } if (!running) { logger.error("Not retrying message {} as we are stopping", commandToSend.getLightwaveRfCommandString()); return; } Integer newRetryCount = Integer.valueOf(sendCount.intValue() + 1); logger.info("Ok message not received for {}, retrying again. Retry count {}", commandToSend.getLightwaveRfCommandString(), newRetryCount); retryCountMap.put(commandToSend.getMessageId(), newRetryCount); queue.addFirst(commandToSend); } else { logger.info("Ok message received for {}", commandToSend.getLightwaveRfCommandString()); } } else { logger.info("Stop message received"); } } catch (InterruptedException e) { logger.error("Error waiting on queue", e); } } /** * Add LightwaveRFCommand command to queue to send. */ public void sendLightwaveCommand(LightwaveRFCommand command) { try { if (running) { queue.put(command); } else { logger.info("Message not added to queue as we are shutting down Message[{}]"); } } catch (InterruptedException e) { logger.error("Error adding command[{}] to queue Throwable {}", command, e); } } @Override public void okMessageReceived(LightwaveRfCommandOk command) { CountDownLatch latch = latchMap.get(command.getMessageId()); if (latch != null) { latch.countDown(); } } @Override public void roomDeviceMessageReceived(LightwaveRfRoomDeviceMessage message) { /* * Do Nothing */ } @Override public void roomMessageReceived(LightwaveRfRoomMessage message) { /* Do Nothing */ } @Override public void serialMessageReceived(LightwaveRfSerialMessage message) { /* Do Nothing */ } @Override public void versionMessageReceived(LightwaveRfVersionMessage message) { // If we receive a vesion message we assume this is in response to our // registration message and attempt to unlatch the sending thread. CountDownLatch latch = latchMap.get(new LightwaveRfRegistrationMessageId()); if (latch != null) { latch.countDown(); } } @Override public void heatInfoMessageReceived(LightwaveRfHeatInfoRequest command) { /* Do Nothing */ } /** * Send the UDP message */ private void netsendUDP(LightwaveRFCommand command) { try { logger.debug("Sending command[{}]", command.getLightwaveRfCommandString()); byte[] sendData = new byte[1024]; sendData = command.getLightwaveRfCommandString().getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ipAddress, lightwaveWifiLinkTransmitPort); transmitSocket.send(sendPacket); } catch (IOException e) { logger.error("Error sending command {}. Throwable {}", command, e); } } }