/**
* 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.nikobus.internal.core;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.openhab.binding.nikobus.internal.NikobusBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Command receiver. Runs in dedicated thread to receive command from the
* Nikobus interface and broadcast them to all registered listeners.
*
* @author Davy Vanherbergen
* @since 1.3.0
*/
public class NikobusCommandReceiver implements Runnable {
private static Logger log = LoggerFactory.getLogger(NikobusCommandReceiver.class);
private AtomicReference<List<NikobusCommandListener>> atomicListReference;
private LinkedBlockingQueue<byte[]> bufferQueue;
private NikobusCommand command;
private StringBuilder commandBuilder = new StringBuilder();
private boolean stopped;
private NikobusBinding binding;
/**
* Default constructor.
*/
public NikobusCommandReceiver(NikobusBinding binding) {
atomicListReference = new AtomicReference<List<NikobusCommandListener>>();
atomicListReference.set(new ArrayList<NikobusCommandListener>());
this.binding = binding;
}
/**
* Start listening for commands until stopped.
*/
@Override
public void run() {
if (bufferQueue == null) {
log.error("Missing buffer.");
return;
}
try {
commandBuilder = new StringBuilder();
byte[] buffer = null;
while (true && !stopped) {
if (buffer == null) {
// read all incoming chars
buffer = bufferQueue.take();
}
for (int i = 0; i < buffer.length; i++) {
byte b = buffer[i];
char c = (char) b;
if (b == 13) {
// end of command
completeCommandInstance();
continue;
}
if ((c == '#' || c == '$') && commandBuilder.length() > 0) {
// start of new command
completeCommandInstance();
}
commandBuilder.append(c);
if (commandBuilder.length() == 5 && commandBuilder.toString().startsWith("$05")) {
// we received an ACK message
completeCommandInstance();
}
}
buffer = null;
if (command != null && command.isButtonPress()) {
// wait briefly for next command to see if button
// is still being pressed..
buffer = bufferQueue.poll(85, TimeUnit.MILLISECONDS);
}
if (buffer == null && command != null) {
// no more data received..
publishCommand();
command = null;
}
}
} catch (InterruptedException e) {
log.info("Command processor stopped.");
}
}
/**
* Consider the data received so far as an atomic command.
*/
private void completeCommandInstance() {
if (command != null) {
if (command.getCommand().equals(commandBuilder.toString())) {
// same command received as previous
command.incrementCount();
} else {
// different command received, publish previous one
publishCommand();
command = new NikobusCommand(commandBuilder.toString());
}
} else {
// new command received
command = new NikobusCommand(commandBuilder.toString());
}
if (command.getRepeats() >= NikobusCommand.MAX_REPEAT) {
publishCommand();
}
commandBuilder = new StringBuilder();
}
/**
* Broadcast a received command to all listeners.
*/
private void publishCommand() {
if ((command.getRepeats() >= NikobusCommand.MAX_REPEAT)
&& ((command.getRepeats() - NikobusCommand.MAX_REPEAT) % (NikobusCommand.MAX_REPEAT / 2) != 0)) {
// when the button is pressed long e.g > 1 second, the first
// command is send on 1 second and subsequent commands are sent
// every 0.5 seconds.
return;
}
log.debug("Received {}", command.toString());
List<NikobusCommandListener> currentListeners = atomicListReference.get();
for (NikobusCommandListener receiver : currentListeners) {
receiver.processNikobusCommand(command, binding);
}
}
/**
* Add a new Nikobus device which can listen for commands.
*
* @param receiver
* device.
*/
public void register(NikobusCommandListener receiver) {
// rather than using synchronized methods/list, we consider the
// listener list to be unmutable and always create a new copy
// to avoid concurrent access issues.
List<NikobusCommandListener> currentListeners = atomicListReference.get();
if (currentListeners.contains(receiver)) {
return;
}
List<NikobusCommandListener> newListeners = new ArrayList<NikobusCommandListener>();
newListeners.addAll(currentListeners);
newListeners.add(receiver);
atomicListReference.set(newListeners);
}
/**
* Remove a Nikobus device listener.
*
* @param receiver
* device to remove.
*/
public void unregister(NikobusCommandListener receiver) {
List<NikobusCommandListener> currentListeners = atomicListReference.get();
List<NikobusCommandListener> newListeners = new ArrayList<NikobusCommandListener>();
newListeners.addAll(currentListeners);
newListeners.remove(receiver);
atomicListReference.set(newListeners);
}
/**
* @param bufferQueue
* buffer to poll for received characters
*/
public void setBufferQueue(LinkedBlockingQueue<byte[]> bufferQueue) {
this.bufferQueue = bufferQueue;
}
/**
* Stop execution.
*/
public void stop() {
stopped = true;
}
/**
* Drop all registered command listeners.
*/
public void unregisterAll() {
List<NikobusCommandListener> newListeners = new ArrayList<NikobusCommandListener>();
atomicListReference.set(newListeners);
}
/**
* Unregister an command listener by name
*
* @param itemName
*/
public void unregisterItem(String itemName) {
List<NikobusCommandListener> currentListeners = atomicListReference.get();
List<NikobusCommandListener> newListeners = new ArrayList<NikobusCommandListener>();
for (NikobusCommandListener listener : currentListeners) {
if (listener.getName().equals(itemName)) {
continue;
}
newListeners.add(listener);
}
atomicListReference.set(newListeners);
}
}