/** * 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.bluetooth.internal; import java.util.HashMap; import java.util.Map; import org.openhab.binding.bluetooth.BluetoothEventHandler; import org.openhab.core.events.EventPublisher; import org.openhab.core.items.Item; import org.openhab.core.library.items.NumberItem; 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.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.model.item.binding.BindingConfigParseException; import org.openhab.model.item.binding.BindingConfigReader; /** * <p> * This class is the default implementation to link the bluetooth discovery service * to the openHAB event bus by parsing the binding configurations provided by the {@link GenericItemProvider}. * </p> * * <p> * The format of the binding configuration is simple and looks like this: * <ul> * <li>for switch items: bluetooth="<deviceAddress>[!]" where <deviceAddress> is the technical address of * the device, eg. EC935BD417C5 * and the optional exclamation mark defines whether the devices needs to be paired/authenticated with the host or not * </li> * <li>for string items: bluetooth="[*|!|?]", where '!' only regards authenticated devices, '?' only regards * un-authenticated devices and * '*' accepts any device.</li> * <li>for number items: bluetooth="[*|!|?]", where '!' only regards authenticated devices, '?' only regards * un-authenticated devices and * '*' accepts any device.</li> * </p> * <p> * Switch items will receive an ON / OFF update on the bus, String items will be sent a comma separated list of all * device names and * Number items will show the number of bluetooth devices in range. * If a friendly name cannot be resolved for a device, its address will be used instead as its name when listing it on a * String item. * </p> * * @author Kai Kreuzer * @since 0.3.0 * */ public class BluetoothBinding implements BluetoothEventHandler, BindingConfigReader { private static final String BLUETOOTH_BINDING_TYPE = "bluetooth"; /** * stores information about switch items. The map has this content structure: context -> { deviceAddress, itemName } */ private Map<String, Map<String, String>> switchItems = new HashMap<String, Map<String, String>>(); /** * stores information about string items for authenticated devices. The map has this content structure: context -> * itemName */ private Map<String, String> authStringItems = new HashMap<String, String>(); /** * stores information about string items for un-authenticated devices. The map has this content structure: context * -> itemName */ private Map<String, String> unauthStringItems = new HashMap<String, String>(); /** * stores information about string items for all devices. The map has this content structure: context -> itemName */ private Map<String, String> allStringItems = new HashMap<String, String>(); /** * stores information about measurement items for authenticated devices. The map has this content structure: context * -> itemName */ private Map<String, String> authMeasurementItems = new HashMap<String, String>(); /** * stores information about measurement items for un-authenticated devices. The map has this content structure: * context -> itemName */ private Map<String, String> unauthMeasurementItems = new HashMap<String, String>(); /** * stores information about measurement items for all devices. The map has this content structure: context -> * itemName */ private Map<String, String> allMeasurementItems = new HashMap<String, String>(); private EventPublisher eventPublisher; public void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } public void unsetEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = null; } /** * {@inheritDoc} */ @Override public void handleDeviceInRange(BluetoothDevice device) { if (eventPublisher != null) { // find the items associated to this address, if any String itemName = null; for (Map<String, String> map : switchItems.values()) { itemName = map.get(device.getAddress()); if (itemName == null && device.isPaired()) { itemName = map.get(device.getAddress() + "!"); } if (itemName != null) { break; } } if (itemName != null) { eventPublisher.postUpdate(itemName, OnOffType.ON); } } } /** * {@inheritDoc} */ @Override public void handleDeviceOutOfRange(BluetoothDevice device) { if (eventPublisher != null) { // find the item associated to this address, if any String itemName = null; for (Map<String, String> map : switchItems.values()) { itemName = map.get(device.getAddress()); if (itemName == null && device.isPaired()) { itemName = map.get(device.getAddress() + "!"); } if (itemName != null) { break; } } if (itemName != null) { eventPublisher.postUpdate(itemName, OnOffType.OFF); } } } /** * {@inheritDoc} */ @Override public void handleAllDevicesInRange(Iterable<BluetoothDevice> devices) { if (eventPublisher != null) { // build a comma separated list of all devices in range StringBuilder authSb = new StringBuilder(); StringBuilder unauthSb = new StringBuilder(); int noOfAuthDevices = 0; int noOfUnauthDevices = 0; for (BluetoothDevice device : devices) { handleDeviceInRange(device); if (device.isPaired()) { authSb.append(getName(device)); authSb.append(", "); noOfAuthDevices++; } else { unauthSb.append(getName(device)); unauthSb.append(", "); noOfUnauthDevices++; } } String authDeviceList = authSb.length() > 0 ? authSb.substring(0, authSb.length() - 2) : ""; String unauthDeviceList = unauthSb.length() > 0 ? unauthSb.substring(0, unauthSb.length() - 2) : ""; String allDeviceList = unauthDeviceList.isEmpty() ? authDeviceList : authSb.append(unauthDeviceList).toString(); for (String itemName : authStringItems.values()) { eventPublisher.postUpdate(itemName, StringType.valueOf(authDeviceList)); } for (String itemName : unauthStringItems.values()) { eventPublisher.postUpdate(itemName, StringType.valueOf(unauthDeviceList)); } for (String itemName : allStringItems.values()) { eventPublisher.postUpdate(itemName, StringType.valueOf(allDeviceList)); } for (String itemName : authMeasurementItems.values()) { eventPublisher.postUpdate(itemName, new DecimalType(noOfAuthDevices)); } for (String itemName : unauthMeasurementItems.values()) { eventPublisher.postUpdate(itemName, new DecimalType(noOfUnauthDevices)); } for (String itemName : allMeasurementItems.values()) { eventPublisher.postUpdate(itemName, new DecimalType(noOfAuthDevices + noOfUnauthDevices)); } } } /** * {@inheritDoc} */ @Override public String getBindingType() { return BLUETOOTH_BINDING_TYPE; } /** * @{inheritDoc} */ @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { if (!(item instanceof SwitchItem || item instanceof StringItem || item instanceof NumberItem)) { throw new BindingConfigParseException("item '" + item.getName() + "' is of type '" + item.getClass().getSimpleName() + "', only Switch-, String- and NumberItems are allowed - please check your *.items configuration"); } } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(String context, Item item, String bindingConfig) throws BindingConfigParseException { if (item instanceof SwitchItem) { Map<String, String> entry = switchItems.get(context); if (entry == null) { entry = new HashMap<String, String>(); } entry.put(bindingConfig, item.getName()); switchItems.put(context, entry); } else if (item instanceof StringItem) { if (bindingConfig.equals("!")) { authStringItems.put(context, item.getName()); } else if (bindingConfig.equals("?")) { unauthStringItems.put(context, item.getName()); } else if (bindingConfig.equals("*")) { allStringItems.put(context, item.getName()); } } else if (item instanceof NumberItem) { if (bindingConfig.equals("!")) { authMeasurementItems.put(context, item.getName()); } else if (bindingConfig.equals("?")) { unauthMeasurementItems.put(context, item.getName()); } else if (bindingConfig.equals("*")) { allMeasurementItems.put(context, item.getName()); } } } /** * {@inheritDoc} */ @Override public boolean isActive() { // only say that we are active if there are any items registered for that binding return switchItems.size() > 0 || authStringItems.size() > 0 || unauthStringItems.size() > 0 || allStringItems.size() > 0 || authMeasurementItems.size() > 0 || unauthMeasurementItems.size() > 0 || allMeasurementItems.size() > 0; } /** * {@inheritDoc} */ @Override public void removeConfigurations(String context) { switchItems.remove(context); authStringItems.remove(context); unauthStringItems.remove(context); allStringItems.remove(context); authMeasurementItems.remove(context); unauthMeasurementItems.remove(context); allMeasurementItems.remove(context); } private String getName(BluetoothDevice device) { if (!device.getFriendlyName().trim().isEmpty()) { return device.getFriendlyName(); } else { return device.getAddress(); } } }