/**
* 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.lcn.internal;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.lcn.LcnBindingProvider;
import org.openhab.binding.lcn.common.LcnBindingNotification;
import org.openhab.binding.lcn.connection.Connection;
import org.openhab.binding.lcn.connection.ConnectionManager;
import org.openhab.binding.lcn.connection.ConnectionSettings;
import org.openhab.binding.lcn.input.Input;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.types.Command;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Main class of the LCN openHAB binding.
* <p>
* The binding executes everything on the refresh-thread inside {@link #execute()}.
* This assures the required thread-safety.
* Input data is received through {@link #connections}.
*
* @author Tobias J�ttner
*/
public class LcnBinding extends AbstractBinding<LcnGenericBindingProvider>
implements ManagedService, LcnBindingActiveService.Callback, ConnectionManager.Callback {
/** Logger for this class. */
private static final Logger logger = LoggerFactory.getLogger(LcnBinding.class);
/** Refresh interval for the service thread. */
private static final int REFRESH_INTERVAL_MSEC = 1000;
/** embedded active service to allow the binding to have some code executed in a given interval. */
private final LcnBindingActiveService activeService = new LcnBindingActiveService(this);
/**
* Connection settings read from configuration.
* Key is the connection identifier {@link ConnectionSettings#getId()} in upper-case.
*/
private HashMap<String, ConnectionSettings> connectionSettings = new HashMap<String, ConnectionSettings>();
/** Holds all currently existing connections. */
private ConnectionManager connections = new ConnectionManager(this);
/** Semaphore to wait for new notifications to process in refresh thread. */
private Semaphore notificationsSem = new Semaphore(0);
/**
* List of notifications to process in the refresh thread.
* List must be synchronized.
*/
private final LinkedList<LcnBindingNotification> notifications = new LinkedList<LcnBindingNotification>();
/**
* Called whenever the configurations are updated.
*
* @param config the updated configurations
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
logger.info("Loading LCN configuration...");
// Reset to default values
this.connectionSettings.clear();
// Load connection settings
int counter = 0;
ConnectionSettings sets;
while ((sets = ConnectionSettings.tryParse(config, counter)) != null) {
this.connectionSettings.put(sets.getId().toUpperCase(), sets);
++counter;
}
logger.info("LCN configuration loaded.");
// Finished
if (this.providers.size() > 0) {
this.activeService.setProperlyConfigured(true);
}
}
/** {@inheritDoc} */
@Override
protected void internalReceiveCommand(String itemName, Command command) {
final String itemName2 = itemName;
final Command command2 = command;
this.runOnRefreshThreadAsync(new LcnBindingNotification() {
@Override
public void execute() {
for (LcnGenericBindingProvider provider : LcnBinding.this.providers) {
LcnBindingConfig itemConfig = provider.getLcnItemConfig(itemName2);
if (itemConfig != null) {
itemConfig.send(LcnBinding.this.connections, command2);
}
}
}
});
}
/**
* Tells the connections the binding is activated.
* {@inheritDoc}
*/
@Override
public void activate() {
this.connections.activate();
}
/**
* Tells the connections the binding is deactivated.
* {@inheritDoc}
*/
@Override
public void deactivate() {
this.connections.deactivate();
}
/** {@inheritDoc} */
@Override
public void runOnRefreshThreadAsync(LcnBindingNotification n) {
synchronized (this.notifications) {
this.notifications.add(n);
this.notificationsSem.release();
}
}
/** {@inheritDoc} */
@Override
public void updateItems(Connection conn) {
for (LcnGenericBindingProvider provider : this.providers) {
for (String itemName : provider.getItemNames()) {
LcnBindingConfig itemConfig = provider.getLcnItemConfig(itemName);
itemConfig.update(conn);
}
}
}
/** {@inheritDoc} */
@Override
public void onInputReceived(String input, Connection conn) {
logger.debug(String.format("Channel \"%s\" received input: %s", conn.getSets().getId(), input));
for (Input pchkInput : Input.parse(input)) {
pchkInput.process(conn);
for (LcnGenericBindingProvider provider : this.providers) {
for (String itemName : provider.getItemNamesForPchkInput(pchkInput)) {
LcnBindingConfig itemConfig = provider.getLcnItemConfig(itemName); // Might return null.
// "getItemNames" can hold
// (cached) old data
if (itemConfig != null) {
itemConfig.processInput(pchkInput, conn, this.eventPublisher);
}
}
}
}
}
/** {@inheritDoc} */
@Override
public Map<String, ConnectionSettings> getAllSets() {
return this.connectionSettings;
}
/** Processes notifications. */
@Override
public void execute() {
// Update connections
this.connections.update();
this.connections.flush();
// Process notifications
try {
long startTime = System.nanoTime();
int timeoutMSec = REFRESH_INTERVAL_MSEC;
while (timeoutMSec >= 0 && this.notificationsSem.tryAcquire(timeoutMSec, TimeUnit.MILLISECONDS)) {
LcnBindingNotification notification;
synchronized (this.notifications) {
notification = this.notifications.pollFirst();
}
notification.execute();
this.connections.flush();
// Next
timeoutMSec = REFRESH_INTERVAL_MSEC - (int) ((System.nanoTime() - startTime) / 1000000L);
}
} catch (InterruptedException ex) {
}
}
/** {@inheritDoc} */
@Override
public boolean bindingsExists() {
return this.bindingsExist();
}
protected void addBindingProvider(LcnBindingProvider provider) {
super.addBindingProvider(provider);
this.activeService.start();
}
protected void removeBindingProvider(LcnBindingProvider provider) {
super.removeBindingProvider(provider);
// If there are no binding providers left, we can stop the service
if (this.providers.size() == 0) {
this.activeService.stop();
}
}
/** {@inheritDoc} */
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
super.bindingChanged(provider, itemName);
if (this.bindingsExist()) {
this.activeService.start();
} else {
this.activeService.stop();
}
}
/** {@inheritDoc} */
@Override
public void allBindingsChanged(BindingProvider provider) {
super.allBindingsChanged(provider);
if (bindingsExist()) {
this.activeService.start();
} else {
this.activeService.stop();
}
}
}