/**
* 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.tinkerforge.internal;
import java.math.BigDecimal;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.openhab.binding.tinkerforge.TinkerforgeBindingProvider;
import org.openhab.binding.tinkerforge.ecosystem.TinkerforgeContextImpl;
import org.openhab.binding.tinkerforge.internal.config.ConfigurationHandler;
import org.openhab.binding.tinkerforge.internal.model.ColorActor;
import org.openhab.binding.tinkerforge.internal.model.DigitalActor;
import org.openhab.binding.tinkerforge.internal.model.DimmableActor;
import org.openhab.binding.tinkerforge.internal.model.Ecosystem;
import org.openhab.binding.tinkerforge.internal.model.GenericDevice;
import org.openhab.binding.tinkerforge.internal.model.IO4Device;
import org.openhab.binding.tinkerforge.internal.model.IODevice;
import org.openhab.binding.tinkerforge.internal.model.MBaseDevice;
import org.openhab.binding.tinkerforge.internal.model.MBrickd;
import org.openhab.binding.tinkerforge.internal.model.MDevice;
import org.openhab.binding.tinkerforge.internal.model.MInSwitchActor;
import org.openhab.binding.tinkerforge.internal.model.MSensor;
import org.openhab.binding.tinkerforge.internal.model.MSubDevice;
import org.openhab.binding.tinkerforge.internal.model.MSubDeviceHolder;
import org.openhab.binding.tinkerforge.internal.model.MSwitchActor;
import org.openhab.binding.tinkerforge.internal.model.MTFConfigConsumer;
import org.openhab.binding.tinkerforge.internal.model.MTextActor;
import org.openhab.binding.tinkerforge.internal.model.ModelFactory;
import org.openhab.binding.tinkerforge.internal.model.ModelPackage;
import org.openhab.binding.tinkerforge.internal.model.MoveActor;
import org.openhab.binding.tinkerforge.internal.model.NumberActor;
import org.openhab.binding.tinkerforge.internal.model.OHConfig;
import org.openhab.binding.tinkerforge.internal.model.OHTFDevice;
import org.openhab.binding.tinkerforge.internal.model.PercentTypeActor;
import org.openhab.binding.tinkerforge.internal.model.ProgrammableColorActor;
import org.openhab.binding.tinkerforge.internal.model.ProgrammableSwitchActor;
import org.openhab.binding.tinkerforge.internal.model.SetPointActor;
import org.openhab.binding.tinkerforge.internal.model.SimpleColorActor;
import org.openhab.binding.tinkerforge.internal.model.SwitchSensor;
import org.openhab.binding.tinkerforge.internal.model.TFConfig;
import org.openhab.binding.tinkerforge.internal.types.DecimalValue;
import org.openhab.binding.tinkerforge.internal.types.DirectionValue;
import org.openhab.binding.tinkerforge.internal.types.HSBValue;
import org.openhab.binding.tinkerforge.internal.types.HighLowValue;
import org.openhab.binding.tinkerforge.internal.types.OnOffValue;
import org.openhab.binding.tinkerforge.internal.types.PercentValue;
import org.openhab.binding.tinkerforge.internal.types.TinkerforgeValue;
import org.openhab.binding.tinkerforge.internal.types.UnDefValue;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.ColorItem;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This binding connects the TinkerForge devices to the openhab eventbus.
*
* This class uses an EMF model with a TinkerforgeEcosystem object as root. The TinkerforgeEcosystem
* has Brickd child objects. The Brickd object has an IpConnection to the TinkerForge brickd daemon,
* identified by an ip address or host name and a port. The Brickd object has child objects for
* every connected TinkerForge device. The TinkerForge device object holds leaf objects for
* subdevices if available. All the device objects are sharing the IpConnection of the Brickd
* object. The EMF device objects can be interpreted as a facade for the TinkerForge api device
* objects. If available, the EMF device objects implement TinkerForge CallbackListeners for sensor
* value updates and are updating the EMF device object sensor properties accordingly.
*
* The binding adds a listener to the TinkerforgeEcosystem. On the one hand this listener handles
* updated sensor values and propagates them to the openhab eventbus. On the other hand the listener
* is informed about new devices in the TinkerforgeEcosystem and thus can pass configuration
* settings from openhab.cfg to the devices. The callback period of the CallbackListener and a
* threshold value are configurable through openhab.cfg.
*
* All device values are additionally polled by the execute method mainly to get values from
* subdevices which don't have TinkerForge CallbackListeners for getting the sensor values.
*
* Tinkerforge devices which work as actors like relays can be controlled with this binding.
*
* For now only a subset of the TinkerForge devices are supported and not all features of the
* devices are implemented. More devices and features will be added soon. The following devices are
* supported for now:
* <ul>
* <li>Servo Brick</li>
* <li>DC Brick</li>
* <li>Dual Relay Bricklet</li>
* <li>Humidity Bricklet</li>
* <li>Distance IR Bricklet</li>
* <li>Temperature Bricklet</li>
* <li>Barometer Bricklet</li>
* <ul>
* <li>Barometer</li>
* <li>Temperature Device</li>
* </ul>
* <li>Ambient Light Bricklet</li>
* <li>LCD</li>
* <ul>
* <li>LCD 20×4 Bricklet</li>
* <li>4 Buttons</li>
* </ul>
* </ul>
*
* @author Theo Weiss
* @since 1.3.0
*/
public class TinkerforgeBinding extends AbstractActiveBinding<TinkerforgeBindingProvider> implements ManagedService {
private static final String CONFIG_KEY_HOSTS = "hosts";
private static final Logger logger = LoggerFactory.getLogger(TinkerforgeBinding.class);
private static final int BRICKD_DEFAULT_PORT = 4223;
/**
* the refresh interval which is used to poll values from the Tinkerforge server (optional,
* defaults to 60000ms)
*/
private long refreshInterval = 60000;
private Ecosystem tinkerforgeEcosystem;
private ModelFactory modelFactory;
private OHConfig ohConfig;
private boolean isConnected;
private TinkerforgeContextImpl context = (TinkerforgeContextImpl) TinkerforgeContextImpl.getInstance();
public TinkerforgeBinding() {
modelFactory = ModelFactory.eINSTANCE;
}
@Override
public void activate() {
}
@Override
public void deactivate() {
disconnectModel();
}
/**
* Disconnects the IpConnections to all TinkerForge brickds and destroys the TinkerforgeEcosystem.
*/
private void disconnectModel() {
if (isConnected) {
logger.debug("disconnect called");
tinkerforgeEcosystem.disconnect();
tinkerforgeEcosystem = null;
context.setEcosystem(null);
isConnected = false;
}
}
/**
* Creates a Tinkerforge Ecosystem object and adds a listener to it.
*/
private void connectModel() {
tinkerforgeEcosystem = modelFactory.createEcosystem();
context.setEcosystem(tinkerforgeEcosystem);
listen2Model(tinkerforgeEcosystem);
logger.debug("{} connectModel called", LoggerConstants.TFINIT);
isConnected = true;
}
/**
* Searches for a brickd with the given {@code host} and {@code port} in the Ecosystem. If there
* is no brickd found a new Brickd object is created, added to the Ecosystem an the IpConnection
* to the Tinkerforge brickd is established and a device enumeration is triggert.
*
* @param host The host name or ip address of the TinkerForge brickd as String.
* @param port The port of the TinkerForge brickd as int.
* @param authkey
*/
private void connectBrickd(String host, int port, String authkey) {
MBrickd brickd = tinkerforgeEcosystem.getBrickd(host, port);
if (brickd == null) {
brickd = modelFactory.createMBrickd();
brickd.setHost(host);
brickd.setPort(port);
brickd.setAuthkey(authkey);
brickd.setEcosystem(tinkerforgeEcosystem);
tinkerforgeEcosystem.getMbrickds().add(brickd);
brickd.init();
brickd.connect();
logger.debug("{} Tinkerforge new brickd for host: {}", LoggerConstants.TFINIT, host);
} else {
logger.debug("{} Tinkerforge found existing brickd for host: {}", LoggerConstants.TFINIT, host);
}
}
/**
* Adds a listener {@link EContentAdapter} to the {@link Ecosystem}. The listener handles updated
* sensor values and posts them to the openhab eventbus by
* {@link #processTFDeviceValues(Notification) processTFDeviceValues}. Furthermore the addition
* and removal of devices is handled by {@link #initializeTFDevices(Notification)
* initializeTFDevices}.
*
* @param tinkerforgeEcosystem The EMF Ecosystem object.
*/
private void listen2Model(Ecosystem tinkerforgeEcosystem) {
EContentAdapter modelAdapter = new EContentAdapter() {
@Override
public void notifyChanged(Notification notification) {
super.notifyChanged(notification);
logger.debug("TinkerforgeNotifier was notified");
if (notification.getEventType() == Notification.ADD
|| notification.getEventType() == Notification.ADD_MANY
|| notification.getEventType() == Notification.REMOVE
|| notification.getEventType() == Notification.REMOVE_MANY) {
initializeTFDevices(notification);
} else {
processTFDeviceValues(notification);
}
}
};
tinkerforgeEcosystem.eAdapters().add(modelAdapter);
}
private boolean checkDuplicateGenericDevice(GenericDevice device, String uid, String subId) {
boolean isDuplicate = false;
final String genericDeviceId = device.getGenericDeviceId();
final EList<MSubDevice<?>> genericDevicesList = tinkerforgeEcosystem.getDevices4GenericId(uid, genericDeviceId);
if (genericDevicesList.size() != 0) {
for (MSubDevice<?> gd : genericDevicesList) {
if (!gd.getSubId().equals(subId) && gd.getEnabledA().get()) {
isDuplicate = true;
logger.error("{} existing device is uid {} subId {}", LoggerConstants.CONFIG, gd.getUid(),
gd.getSubId());
}
}
}
return isDuplicate;
}
/**
* Configures and enables newly found devices. For sub devices the master device is also enabled.
* Configuration is only added if there is a configuration from openhab.cfg available and the
* device is configurable which is the case for {@link MTFConfigConsumer}. Devices of type
* {@link IODevice} are only enabled if they are configured in openhab.cfg, all other devices are
* always enabled.
*
* @param device A device object as {@link MBaseDevice}.
* @param uid The device uid as String.
* @param subId The device subid as String or <code>null</code> if the device is not a sub device.
*/
@SuppressWarnings("unchecked")
private synchronized void addMDevice(MBaseDevice device, String uid, String subId) {
String logId = subId == null ? uid : uid + " " + subId;
OHTFDevice<?, ?> deviceConfig = ohConfig.getConfigByTFId(uid, subId);
if (deviceConfig == null) {
logger.debug("{} found no device configuration for uid \"{}\" subid \"{}\"", LoggerConstants.TFINITSUB, uid,
subId);
}
if (device.getEnabledA().compareAndSet(false, true)) {
if (subId != null) {
MDevice<?> masterDevice = (MDevice<?>) device.eContainer();
// recursion for adding the master device
if (!masterDevice.getEnabledA().get()) {
logger.debug("{} enabling masterDevice {}", LoggerConstants.TFINITSUB, masterDevice.getUid());
addMDevice(masterDevice, uid, null);
}
}
if (device instanceof MTFConfigConsumer<?> && deviceConfig != null) {
logger.debug("{} found MTFConfigConsumer id {}", LoggerConstants.TFINIT, logId);
if (device instanceof GenericDevice
&& checkDuplicateGenericDevice((GenericDevice) device, uid, subId)) {
logger.error("{} ignoring duplicate device uid: {}, subId {}, genericId {}. Fix your openhab.cfg!",
LoggerConstants.CONFIG, uid, subId);
device.getEnabledA().compareAndSet(true, false);
} else {
TFConfig deviceTfConfig = EcoreUtil.copy(deviceConfig.getTfConfig());
logger.debug("{} setting tfConfig for {}", LoggerConstants.TFINIT, logId);
logger.debug("{} adding/enabling device {} with config: {}", LoggerConstants.TFINIT, logId,
deviceTfConfig);
((MTFConfigConsumer<EObject>) device).setTfConfig(deviceTfConfig);
device.enable();
}
} else if (device instanceof IODevice || device instanceof IO4Device) {
logger.debug("{} ignoring unconfigured IODevice: {}", LoggerConstants.TFINIT, logId);
// set the device disabled, this is needed for not getting
// states
// through execute method
device.getEnabledA().compareAndSet(true, false);
} else {
device.enable();
logger.debug("{} adding/enabling device: {}", LoggerConstants.TFINIT, logId);
}
}
}
/**
* Adds or removes a device to / from the Ecosystem. Notifications from {@link MBrickd} are used
* for adding devices (not sub devices) and removing of devices and their corresponding sub
* devices.
*
* Notifications from {@link MSubDeviceHolder} for adding sub devices.
*
* @param notification The {@link Notification} for add and remove events to the {@link Ecosystem}
* .
*/
private void initializeTFDevices(Notification notification) {
logger.trace("{} notifier {}", LoggerConstants.TFINIT, notification.getNotifier());
if (notification.getNotifier() instanceof MBrickd) {
logger.debug("{} notifier is Brickd", LoggerConstants.TFINIT);
int featureID = notification.getFeatureID(MBrickd.class);
if (featureID == ModelPackage.MBRICKD__MDEVICES) {
if (notification.getEventType() == Notification.ADD) {
MDevice<?> mDevice = (MDevice<?>) notification.getNewValue();
addMDevice(mDevice, mDevice.getUid(), null);
} else if (notification.getEventType() == Notification.ADD_MANY) {
logger.debug("{} Notifier: add many called: ", LoggerConstants.TFINIT);
} else if (notification.getEventType() == Notification.REMOVE) {
logger.debug("{} Notifier: remove called: ", LoggerConstants.TFINIT);
if (notification.getOldValue() instanceof MBaseDevice) {
logger.debug("{} Notifier: remove called for MBaseDevice", LoggerConstants.TFINIT);
MBaseDevice mDevice = (MBaseDevice) notification.getOldValue();
String uid = mDevice.getUid();
String subId = null;
if (searchConfiguredItemName(uid, subId) != null) {
logger.debug("{} Notifier: removing device: uid {} subid {}", LoggerConstants.TFINIT, uid,
subId);
postUpdate(uid, subId, UnDefValue.UNDEF);
}
} else {
logger.debug("{} unknown notification from mdevices {}", LoggerConstants.TFINIT, notification);
}
}
} else {
logger.debug("{} Notifier: unknown feature {}", LoggerConstants.TFINIT, notification.getFeature());
}
} else if (notification.getNotifier() instanceof MSubDeviceHolder<?>) {
int featureID = notification.getFeatureID(MSubDeviceHolder.class);
if (featureID == ModelPackage.MSUB_DEVICE_HOLDER__MSUBDEVICES) {
logger.debug("{} MSubdevices Notifier called", LoggerConstants.TFINITSUB);
if (notification.getEventType() == Notification.ADD) {
MSubDevice<?> mSubDevice = (MSubDevice<?>) notification.getNewValue();
addMDevice(mSubDevice, mSubDevice.getUid(), mSubDevice.getSubId());
}
if (notification.getEventType() == Notification.REMOVE) {
logger.debug("{} remove notification from subdeviceholder", LoggerConstants.TFINIT);
logger.debug("{} Notifier: remove called for MSubDevice", LoggerConstants.TFINIT);
MSubDevice<?> mDevice = (MSubDevice<?>) notification.getOldValue();
String uid = mDevice.getUid();
String subId = mDevice.getSubId();
if (searchConfiguredItemName(uid, subId) != null) {
logger.debug("{} Notifier: removing device: uid {} subid {}", LoggerConstants.TFINIT, uid,
subId);
postUpdate(uid, subId, UnDefValue.UNDEF);
}
}
}
} else {
logger.debug("{} unhandled notifier {}", LoggerConstants.TFINIT, notification.getNotifier());
}
}
/**
* Processes change events from the {@link Ecosystem}. Sensor values from {@link MSensor} are
* handled by {@link #processSensorValue(MSensor, Notification) processSensorValue}, actor values
* from {@link MSwitchActore} are handled by
* {@link #processSwitchActorValue(MSwitchActor, Notification) processSwitchActorValue}. (no add
* or remove events, these are handled in {@link #initializeTFDevices(Notification)
* initializeTFDevices}).
*
*
* @param notification The {@link Notification} about changes to the {@link Ecosystem}.
*/
private void processTFDeviceValues(Notification notification) {
if (notification.getNotifier() instanceof MSensor) {
MSensor<?> sensor = (MSensor<?>) notification.getNotifier();
int featureID = notification.getFeatureID(MSensor.class);
if (featureID == ModelPackage.MSENSOR__SENSOR_VALUE) {
processValue((MBaseDevice) sensor, notification);
}
} else if (notification.getNotifier() instanceof SetPointActor<?>) {
SetPointActor<?> actor = (SetPointActor<?>) notification.getNotifier();
int setpointFeatureID = notification.getFeatureID(SetPointActor.class);
if (setpointFeatureID == ModelPackage.SET_POINT_ACTOR__PERCENT_VALUE) {
processValue((MBaseDevice) actor, notification);
}
} else if (notification.getNotifier() instanceof MoveActor) {
MoveActor actor = (MoveActor) notification.getNotifier();
int moveFeatureID = notification.getFeatureID(MoveActor.class);
if (moveFeatureID == ModelPackage.MOVE_ACTOR__DIRECTION) {
processValue((MBaseDevice) actor, notification);
}
} else if (notification.getNotifier() instanceof MSwitchActor) {
MSwitchActor switchActor = (MSwitchActor) notification.getNotifier();
int featureID = notification.getFeatureID(MSwitchActor.class);
if (featureID == ModelPackage.MSWITCH_ACTOR__SWITCH_STATE) {
processValue((MBaseDevice) switchActor, notification);
}
} else if (notification.getNotifier() instanceof ProgrammableSwitchActor) {
logger.trace("notification {}", notification);
logger.trace("notifier {}", notification.getNotifier());
ProgrammableSwitchActor switchActor = (ProgrammableSwitchActor) notification.getNotifier();
// use the super type class for getting the featureID. Should not be necessary according to
// the docs or I misunderstand it. But this approach works.
int featureID = notification.getFeatureID(SwitchSensor.class);
logger.trace("notification ProgrammableSwitchActor id {}", featureID);
if (featureID == ModelPackage.PROGRAMMABLE_SWITCH_ACTOR__SWITCH_STATE) {
logger.trace("ProgrammableSwitchActor switch state changed sending notification");
processValue((MBaseDevice) switchActor, notification);
}
} else if (notification.getNotifier() instanceof DigitalActor) {
DigitalActor actor = (DigitalActor) notification.getNotifier();
int featureID = notification.getFeatureID(DigitalActor.class);
if (featureID == ModelPackage.DIGITAL_ACTOR__DIGITAL_STATE) {
processValue((MBaseDevice) actor, notification);
}
} else if (notification.getNotifier() instanceof ColorActor) {
ColorActor actor = (ColorActor) notification.getNotifier();
int featureID = notification.getFeatureID(ColorActor.class);
if (featureID == ModelPackage.COLOR_ACTOR__COLOR) {
processValue((MBaseDevice) actor, notification);
}
} else if (notification.getNotifier() instanceof DimmableActor<?>) {
DimmableActor<?> actor = (DimmableActor<?>) notification.getNotifier();
processValue((MBaseDevice) actor, notification);
} else if (notification.getNotifier() instanceof MBrickd) {
MBrickd brickd = (MBrickd) notification.getNotifier();
int featureID = notification.getFeatureID(MBrickd.class);
if (featureID == ModelPackage.MBRICKD__CONNECTED_COUNTER) {
String subId = "connected_counter";
processValue(brickd, notification, subId);
} else if (featureID == ModelPackage.MBRICKD__IS_CONNECTED) {
String subId = "isconnected";
processValue(brickd, notification, subId);
}
}
// TODO hier muss noch was fuer die dimmer und rollershutter rein
else {
logger.trace("{} ignored notifier {}", LoggerConstants.TFMODELUPDATE, notification.getNotifier());
}
}
private void processValue(MBrickd brickd, Notification notification, String subId) {
TinkerforgeValue newValue = (TinkerforgeValue) notification.getNewValue();
String uid = brickd.getHost() + ":" + ((Integer) brickd.getPort()).toString();
logger.trace("{} Notifier found brickd value uid {} subid {}", LoggerConstants.TFMODELUPDATE, uid, subId);
postUpdate(uid, subId, newValue);
}
/**
* Processes changed device values to post them to the openHAB event bus.
*
* @param device The {@link MBaseDevice} device, which has a changed value.
* @param notification The {@link Notification} about changes to the {@link Ecosystem}.
*/
private void processValue(MBaseDevice device, Notification notification) {
TinkerforgeValue newValue = (TinkerforgeValue) notification.getNewValue();
String uid = device.getUid();
String subId = null;
if (device instanceof MSubDevice<?>) {
subId = ((MSubDevice<?>) device).getSubId();
logger.trace("{} Notifier found MSubDevice sensor value for: {}", LoggerConstants.TFMODELUPDATE, subId);
} else {
logger.trace("{} Notifier found mDevice sensor value for: {}", LoggerConstants.TFMODELUPDATE, uid);
}
postUpdate(uid, subId, newValue);
}
/**
* Searches the name of an item which is bound to the device with the given uid and subid.
*
* @param uid The device uid as {@code String}.
* @param subId The device subid as {@code String} or {@code null} if it is not a sub device.
* @return The name of the item which is bound to the device as {@code String} or {@code null} if
* no item was found.
*/
private String searchConfiguredItemName(String uid, String subId) {
for (TinkerforgeBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
String deviceUid = provider.getUid(itemName);
String subDeviceId = provider.getSubId(itemName);
String deviceName = provider.getName(itemName);
if (deviceName != null) {
logger.trace("found item for command: name {}", deviceName);
OHTFDevice<?, ?> ohtfDevice = ohConfig.getConfigByOHId(deviceName);
deviceUid = ohtfDevice.getUid();
deviceName = ohtfDevice.getSubid();
}
if (uid.equals(deviceUid)) {
if (subId == null && subDeviceId == null) {
return itemName;
} else if (subId != null && subId.equals(subDeviceId)) {
return itemName;
}
}
}
}
return null;
}
/**
* Searches the provider which is bound to the device with the given uid and subid.
*
* @param uid The device uid as {@code String}.
* @param subId The device subid as {@code String} or {@code null} if it is not a sub device.
* @return The {@code TinkerforgeBindingProvider} which is bound to the device as {@code Item} or
* {@code null} if no item was found.
*/
private Map<String, TinkerforgeBindingProvider> getBindingProviders(String uid, String subId) {
Map<String, TinkerforgeBindingProvider> providerMap = new HashMap<String, TinkerforgeBindingProvider>();
for (TinkerforgeBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
String deviceUid = provider.getUid(itemName);
String subDeviceId = provider.getSubId(itemName);
String deviceName = provider.getName(itemName);
if (deviceName != null) {
OHTFDevice<?, ?> ohtfDevice = ohConfig.getConfigByOHId(deviceName);
deviceUid = ohtfDevice.getUid();
subDeviceId = ohtfDevice.getSubid();
logger.trace("found deviceName {}, uid={}, subId {}", deviceName, deviceUid, subDeviceId);
}
if (uid.equals(deviceUid)) {
if (subId == null && subDeviceId == null) {
providerMap.put(itemName, provider);
} else if (subId != null && subId.equals(subDeviceId)) {
providerMap.put(itemName, provider);
}
}
}
}
return providerMap;
}
/**
* {@inheritDoc}
*/
@Override
protected long getRefreshInterval() {
return refreshInterval;
}
/**
* {@inheritDoc}
*/
@Override
protected String getName() {
return "Tinkerforge Refresh Service";
}
/**
* The working method which is called by the refresh thread.
*
* Triggers an update of state values for all devices. The update is propagated through the
* {@link Ecosystem} listeners. All OutActors are ignored, they may only send updates if the
* hardware device has updates (think of a pressed switch).
*
*/
@Override
protected void execute() {
for (TinkerforgeBindingProvider provider : providers) {
for (String itemName : provider.getItemNames()) {
updateItemValues(provider, itemName, true);
}
}
}
/**
* Triggers an update of state values for all devices.
*
* @param provider The {@code TinkerforgeBindingProvider} which is bound to the device as
* {@code Item}
* @param itemName The name of the {@code Item} as String
* @param only_poll_enabled Fetch only the values of devices which do not support callback
* listeners. These devices are marked with poll "true" flag.
*/
protected void updateItemValues(TinkerforgeBindingProvider provider, String itemName, boolean only_poll_enabled) {
if (tinkerforgeEcosystem == null) {
logger.warn("tinkerforge ecosystem not yet ready");
return;
}
String deviceUid = provider.getUid(itemName);
Item item = provider.getItem(itemName);
String deviceSubId = provider.getSubId(itemName);
String deviceName = provider.getName(itemName);
if (deviceName != null) {
String[] ids = getDeviceIdsForDeviceName(deviceName);
deviceUid = ids[0];
deviceSubId = ids[1];
}
MBaseDevice mDevice = tinkerforgeEcosystem.getDevice(deviceUid, deviceSubId);
if (mDevice != null && mDevice.getEnabledA().get()) {
if (only_poll_enabled && !mDevice.isPoll()) {
// do nothing
logger.debug("{} omitting fetch value for no poll{}:{}", LoggerConstants.ITEMUPDATE, deviceUid,
deviceSubId);
} else {
if (mDevice instanceof MSensor) {
((MSensor<?>) mDevice).fetchSensorValue();
} else if (mDevice instanceof SwitchSensor && item instanceof SwitchItem) {
((SwitchSensor) mDevice).fetchSwitchState();
} else if (mDevice instanceof DigitalActor) {
((DigitalActor) mDevice).fetchDigitalValue();
}
}
}
}
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
logger.debug("{} bindingChanged item {}", LoggerConstants.ITEMUPDATE, itemName);
updateItemValues((TinkerforgeBindingProvider) provider, itemName, false);
}
private void postUpdate(String uid, String subId, TinkerforgeValue sensorValue) {
// TODO undef handling
logger.trace("postUpdate called for uid {} subid {}", uid, subId);
Map<String, TinkerforgeBindingProvider> providerMap = getBindingProviders(uid, subId);
if (providerMap.size() == 0) {
logger.debug("{} found no item for uid {}, subid {}", LoggerConstants.TFMODELUPDATE, uid, subId);
}
for (Entry<String, TinkerforgeBindingProvider> entry : providerMap.entrySet()) {
String itemName = entry.getKey();
TinkerforgeBindingProvider provider = entry.getValue();
Class<? extends Item> itemType = provider.getItemType(itemName);
State value = UnDefType.UNDEF;
if (sensorValue instanceof DecimalValue) {
if (itemType.isAssignableFrom(NumberItem.class) || itemType.isAssignableFrom(StringItem.class)) {
value = DecimalType.valueOf(String.valueOf(sensorValue));
logger.trace("found item to update for DecimalValue {}", itemName);
} else if (itemType.isAssignableFrom(ContactItem.class)) {
value = sensorValue.equals(DecimalValue.ZERO) ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
} else if (itemType.isAssignableFrom(SwitchItem.class)) {
value = sensorValue.equals(DecimalValue.ZERO) ? OnOffType.OFF : OnOffType.ON;
} else {
logger.trace("no update for DecimalValue for item {}", itemName);
continue;
}
} else if (sensorValue instanceof HighLowValue) {
if (itemType.isAssignableFrom(NumberItem.class) || itemType.isAssignableFrom(StringItem.class)) {
value = sensorValue == HighLowValue.HIGH ? DecimalType.valueOf("1") : DecimalType.valueOf("0");
} else if (itemType.isAssignableFrom(ContactItem.class)) {
value = sensorValue == HighLowValue.HIGH ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
} else if (itemType.isAssignableFrom(SwitchItem.class)) {
value = sensorValue == HighLowValue.HIGH ? OnOffType.ON : OnOffType.OFF;
} else {
continue;
}
} else if (sensorValue instanceof OnOffValue) {
if (itemType.isAssignableFrom(NumberItem.class) || itemType.isAssignableFrom(StringItem.class)) {
value = sensorValue == OnOffValue.ON ? DecimalType.valueOf("1") : DecimalType.valueOf("0");
} else if (itemType.isAssignableFrom(ContactItem.class)) {
value = sensorValue == OnOffValue.ON ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
} else if (itemType.isAssignableFrom(SwitchItem.class)) {
value = sensorValue == OnOffValue.ON ? OnOffType.ON : OnOffType.OFF;
} else {
continue;
}
} else if (sensorValue instanceof PercentValue) {
if (itemType.isAssignableFrom(SwitchItem.class)) {
value = ((PercentValue) sensorValue).toBigDecimal().compareTo(BigDecimal.ZERO) == 1 ? OnOffType.ON
: OnOffType.OFF;
logger.debug("switch found {}", itemName);
} else if (itemType.isAssignableFrom(RollershutterItem.class)
|| itemType.isAssignableFrom(DimmerItem.class)) {
value = new PercentType(((PercentValue) sensorValue).toBigDecimal());
logger.debug("Rollershutter or dimmer found {} {}", itemName);
} else if (itemType.isAssignableFrom(ContactItem.class)) {
value = ((PercentValue) sensorValue).toBigDecimal().compareTo(BigDecimal.ZERO) == -1
? OpenClosedType.OPEN : OpenClosedType.CLOSED;
logger.debug("contact found {}", itemName);
} else {
continue;
}
} else if (sensorValue instanceof DirectionValue) {
if (itemType.isAssignableFrom(RollershutterItem.class)) {
value = sensorValue == DirectionValue.RIGHT ? UpDownType.UP : UpDownType.DOWN;
logger.trace("found item to update for UpDownValue {}", itemName);
} else {
continue;
}
} else if (sensorValue instanceof HSBValue) {
if (itemType.isAssignableFrom(ColorItem.class)) {
logger.trace("found item to update for HSBValue {}", itemName);
value = ((HSBValue) sensorValue).getHsbValue();
}
} else if (sensorValue == UnDefValue.UNDEF || sensorValue == null) {
value = UnDefType.UNDEF;
}
eventPublisher.postUpdate(itemName, value);
logger.debug("{} postupdate: found sensorValue: {} for item {}", LoggerConstants.TFMODELUPDATE, sensorValue,
itemName);
}
}
/**
* Gets the uid and the subid of a device from the openhab.cfg, using the device name as input.
*
* @param deviceName The symbolic device name as {@code String}.
* @return A String array with the device uid as first element as {@code String} and the device
* subid as second element as {@code String} or {@code null}.
*/
private String[] getDeviceIdsForDeviceName(String deviceName) {
logger.trace("searching ids for name {}", deviceName);
OHTFDevice<?, ?> ohtfDevice = ohConfig.getConfigByOHId(deviceName);
String[] ids = { ohtfDevice.getUid(), ohtfDevice.getSubid() };
return ids;
}
/**
* {@inheritDoc}
*
* Searches the item with the given {@code itemName} in the {@link TinkerforgeBindingProvider}
* collection and gets the uid and subid of the device. The appropriate device is searched in the
* ecosystem and the command is executed on the device.
*
* {@code OnOffType} commands are executed on {@link MInSwitchActor} objects. {@code StringType}
* commands are executed on {@link MTextActor} objects.
*
*/
@Override
protected void internalReceiveCommand(String itemName, Command command) {
logger.debug("received command {} for item {}", command, itemName);
for (TinkerforgeBindingProvider provider : providers) {
for (String itemNameP : provider.getItemNames()) {
if (itemNameP.equals(itemName)) {
String deviceUid = provider.getUid(itemName);
String deviceSubId = provider.getSubId(itemName);
String deviceName = provider.getName(itemName);
if (deviceName != null) {
String[] ids = getDeviceIdsForDeviceName(deviceName);
deviceUid = ids[0];
deviceSubId = ids[1];
}
logger.trace("{} found item for command: uid: {}, subid: {}", LoggerConstants.COMMAND, deviceUid,
deviceSubId);
MBaseDevice mDevice = tinkerforgeEcosystem.getDevice(deviceUid, deviceSubId);
if (mDevice != null && mDevice.getEnabledA().get()) {
if (command instanceof OnOffType) {
logger.trace("{} found onoff command", LoggerConstants.COMMAND);
OnOffType cmd = (OnOffType) command;
if (mDevice instanceof MSwitchActor) {
OnOffValue state = cmd == OnOffType.OFF ? OnOffValue.OFF : OnOffValue.ON;
((MSwitchActor) mDevice).turnSwitch(state);
} else if (mDevice instanceof DigitalActor) {
HighLowValue state = cmd == OnOffType.OFF ? HighLowValue.LOW : HighLowValue.HIGH;
((DigitalActor) mDevice).turnDigital(state);
} else if (mDevice instanceof ProgrammableSwitchActor) {
OnOffValue state = cmd == OnOffType.OFF ? OnOffValue.OFF : OnOffValue.ON;
((ProgrammableSwitchActor) mDevice).turnSwitch(state,
provider.getDeviceOptions(itemName));
} else {
logger.error("{} received OnOff command for non-SwitchActor", LoggerConstants.COMMAND);
}
} else if (command instanceof StringType) {
logger.trace("{} found string command", LoggerConstants.COMMAND);
if (mDevice instanceof MTextActor) {
((MTextActor) mDevice).write(command.toString());
}
} else if (command instanceof DecimalType) {
logger.debug("{} found number command", LoggerConstants.COMMAND);
if (command instanceof HSBType) {
logger.debug("{} found HSBType command", LoggerConstants.COMMAND);
if (mDevice instanceof ProgrammableColorActor) {
logger.debug("{} found ProgrammableColorActor {}", itemName);
((ProgrammableColorActor) mDevice).setSelectedColor((HSBType) command,
provider.getDeviceOptions(itemName));
} else if (mDevice instanceof SimpleColorActor) {
logger.debug("{} found SimpleColorActor {}", itemName);
((SimpleColorActor) mDevice).setSelectedColor((HSBType) command);
}
} else if (command instanceof PercentType) {
if (mDevice instanceof SetPointActor) {
((SetPointActor<?>) mDevice).setValue(((PercentType) command),
provider.getDeviceOptions(itemName));
logger.debug("found SetpointActor");
} else if (mDevice instanceof PercentTypeActor) {
((PercentTypeActor) mDevice).setValue(((PercentType) command),
provider.getDeviceOptions(itemName));
logger.debug("found PercentType actor");
} else {
logger.error("found no percenttype actor");
}
} else {
if (mDevice instanceof NumberActor) {
((NumberActor) mDevice).setNumber(((DecimalType) command).toBigDecimal());
} else if (mDevice instanceof SetPointActor) {
((SetPointActor<?>) mDevice).setValue(((DecimalType) command).toBigDecimal(),
provider.getDeviceOptions(itemName));
} else {
logger.error("found no number actor");
}
}
} else if (command instanceof UpDownType) {
UpDownType cmd = (UpDownType) command;
logger.debug("{} UpDownType command {}", itemName, cmd);
if (mDevice instanceof MoveActor) {
((MoveActor) mDevice).move((UpDownType) command, provider.getDeviceOptions(itemName));
}
} else if (command instanceof StopMoveType) {
StopMoveType cmd = (StopMoveType) command;
if (mDevice instanceof MoveActor) {
if (cmd == StopMoveType.STOP) {
((MoveActor) mDevice).stop();
} else {
((MoveActor) mDevice).moveon(provider.getDeviceOptions(itemName));
}
}
logger.debug("{} StopMoveType command {}", itemName, cmd);
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType cmd = (IncreaseDecreaseType) command;
if (mDevice instanceof DimmableActor) {
((DimmableActor<?>) mDevice).dimm((IncreaseDecreaseType) command,
provider.getDeviceOptions(itemName));
}
logger.debug("{} IncreaseDecreaseType command {}", itemName, cmd);
}
else {
logger.error("{} got unknown command type: {}", LoggerConstants.COMMAND,
command.toString());
}
} else {
logger.error("{} no tinkerforge device found for command for item uid: {} subId: {}",
LoggerConstants.COMMAND, deviceUid, deviceSubId);
}
}
}
}
}
protected void addBindingProvider(TinkerforgeBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(TinkerforgeBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* Updates the configuration of the managed service.
*
* Extracts the host and port configuration and connects the appropriate brickds.
*
* The device configuration from openhab.cfg is parsed into a {@code Map} based (temporary)
* structure. This structure is used to generate the {@link OHConfig} EMF model configuration
* store.
*/
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
if (isConnected) {
disconnectModel();
}
connectModel();
String refreshIntervalString = (String) config.get("refresh");
if (StringUtils.isNotBlank(refreshIntervalString)) {
refreshInterval = Long.parseLong(refreshIntervalString);
}
ConfigurationHandler configurationHandler = new ConfigurationHandler();
ohConfig = configurationHandler.createConfig(config);
// read further config parameters here ...
logger.debug("{} updated called", LoggerConstants.CONFIG);
// must be done after all other config has been processed
String cfgHostsLine = (String) config.get(CONFIG_KEY_HOSTS);
parseCfgHostsAndConnect(cfgHostsLine);
setProperlyConfigured(true);
}
}
/**
* Parses the the hosts line from openhab.cfg into hosts and port parts and connects the
* appropriate brickds by calling {@link #connectBrickd(String, int) connectBrickd}.
*
* @param cfgHostsLine The hosts line found in the openhab.cfg as {@code String}.
*/
private void parseCfgHostsAndConnect(String cfgHostsLine) {
String[] cfgHostsEntries = cfgHostsLine.split("\\s");
for (int i = 0; i < cfgHostsEntries.length; i++) {
String cfgHostEntry = cfgHostsEntries[i];
String[] cfgHostAndPort = cfgHostEntry.split(":", 3);
String host = cfgHostAndPort[0];
int port;
String authkey = null;
if (cfgHostAndPort.length > 1) {
if (!cfgHostAndPort[1].equals("")) {
port = Integer.parseInt(cfgHostAndPort[1]);
} else {
port = BRICKD_DEFAULT_PORT;
}
} else {
port = BRICKD_DEFAULT_PORT;
}
if (cfgHostAndPort.length == 3) {
authkey = cfgHostAndPort[2];
}
logger.debug("parse brickd config: host {}, port {}, authkey is set {}", host, port,
authkey != null ? true : false);
connectBrickd(host, port, authkey);
}
}
}