/* * Dog - EnOcean Network Driver * * Copyright 2015 Dario Bonino * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package org.doggateway.drivers.enocean.network; import it.polito.elite.dog.core.library.util.LogHelper; import it.polito.elite.enocean.enj.communication.EnJConnection; import it.polito.elite.enocean.enj.communication.EnJDeviceListener; import it.polito.elite.enocean.enj.communication.EnJTeachInListener; import it.polito.elite.enocean.enj.link.EnJLink; import it.polito.elite.enocean.enj.model.EnOceanDevice; import it.polito.elite.enocean.enj.util.ByteUtils; import java.util.Dictionary; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import org.doggateway.drivers.enocean.network.info.EnOceanDeviceInfo; import org.doggateway.drivers.enocean.network.interfaces.EnOceanDeviceDiscoveryListener; import org.doggateway.drivers.enocean.network.interfaces.EnOceanNetwork; import org.doggateway.drivers.enocean.network.interfaces.EnOceanTeachInActivationListener; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.log.LogService; /** * <p> * The EnOcean Network Driver service implementation, offers facilities to * easily access and interface EnOcean networks, by exploiting the EnJ library. * </p> * * @author <a href="mailto:dario.bonino@gmail.com">Dario Bonino</a> * */ public class EnOceanNetworkDriverImpl implements EnOceanNetwork, ManagedService, EnJDeviceListener, EnJTeachInListener { // -------- the configuration parameters --------- // serial port public static final String SERIAL_PORT = "serialPort"; // persistent low-level device db public static final String DEVICE_DB = "deviceDB"; // ------------------------------------------------ // the bundle context private BundleContext bundleContext; // the service registration handle private ServiceRegistration<?> regServiceEnOceanDriverImpl; // the driver logger private LogHelper logger; // the set of currently available devices (TODO: check if this is not a // little bit overkill) // which might not yet be "assigned" to a specific driver. private ConcurrentHashMap<Integer, EnOceanDeviceInfo> availableDevices; // the set of devices associated to a given driver private ConcurrentHashMap<EnOceanDeviceInfo, EnOceanDriverInstance> connectedDrivers; // the set of device discovery listeners private HashSet<EnOceanDeviceDiscoveryListener> deviceDiscoveryListeners; // the set of teach-in listeners private HashSet<EnOceanTeachInActivationListener> teachInListeners; // the low-level EnOcean communication library private EnJConnection enOceanConnection; // the lowest-level EnOcean link private EnJLink enOceanLink; /** * Initializes the inner data structures, while not instantiating the * underlying EnJConnection object, which instead can be prepared only after * actual configuration occurs. */ public EnOceanNetworkDriverImpl() { // initialize the set of available devices this.availableDevices = new ConcurrentHashMap<Integer, EnOceanDeviceInfo>(); // initialize the set of connected drivers this.connectedDrivers = new ConcurrentHashMap<EnOceanDeviceInfo, EnOceanDriverInstance>(); // initialize the set of device discovery listeners this.deviceDiscoveryListeners = new HashSet<EnOceanDeviceDiscoveryListener>(); // initializa the set of teach-in activation listeners this.teachInListeners = new HashSet<EnOceanTeachInActivationListener>(); } /** * Called when the bundle is activated by the OSGi framework * * @param context * The bundle context to use for activation and registration of * bundle services. */ public void activate(BundleContext context) { // store the bundle context this.bundleContext = context; // initialize the class logger... this.logger = new LogHelper(context); // debug: signal activation... this.logger.log(LogService.LOG_DEBUG, "Activated..."); // register the service this.registerNetworkService(); } /** * Called upon bundle deactivation, enables to accomplish all tasks needed * to perform a clean shutdown of the bundle and of relative services. */ public void deactivate() { // unregister the service this.unregisterNetworkService(); // TODO: perform house keeping stuff here... // log this.logger.log(LogService.LOG_INFO, "Deactivated..."); } @Override public void addDriver(EnOceanDeviceInfo devInfo, EnOceanDriverInstance driver) { // "register" the driver for the given device if (this.availableDevices.containsKey(devInfo.getUid())) { // the device is already known, just add the driver this.connectedDrivers.put(devInfo, driver); } else { // the device does not exist, check if information is complete and // in such a case, add the device, // this will in principle trigger a "discovery cycle, to avoid such // a trigger, register the device in the list of available devices. String address = devInfo.getAddress(); String eep = devInfo.getEep(); if ((address != null) && (!address.isEmpty()) && (eep != null) && (!eep.isEmpty())) { // add the device to the set of available devices this.availableDevices.put(devInfo.getUid(), devInfo); // add the driver this.connectedDrivers.put(devInfo, driver); // create the device this.enOceanConnection.addNewDevice(address, eep); } } // get the low-level device described by the given device info EnOceanDevice device = this.enOceanConnection.getDevice(devInfo .getUid()); // connect the driver instance with the low-level device to enable // direct attachment of EEPListeners and easier configuration set-up driver.setEnoceanDevice(device); } @Override public void removeDriver(EnOceanDriverInstance driver) { // removes all subscriptions related to this driver (not the device as // it might still be reachable on the network) HashSet<EnOceanDeviceInfo> keysToRemove = new HashSet<EnOceanDeviceInfo>(); // find lines to remove for (EnOceanDeviceInfo device : this.connectedDrivers.keySet()) { if (this.connectedDrivers.get(device).equals(driver)) keysToRemove.add(device); } // remove for (EnOceanDeviceInfo key : keysToRemove) { this.connectedDrivers.remove(key); } } @Override public void addDeviceDiscoveryListener( EnOceanDeviceDiscoveryListener listener) { // add the listener to the set of device discovery listeners if (this.deviceDiscoveryListeners != null) this.deviceDiscoveryListeners.add(listener); else this.logger .log(LogService.LOG_ERROR, "The device discovery listener set has not been initialized."); } @Override public void removeDeviceDiscoveryListener( EnOceanDeviceDiscoveryListener listener) { // remove the listener from the list, if exists if ((this.deviceDiscoveryListeners != null) && (!this.deviceDiscoveryListeners.isEmpty())) this.deviceDiscoveryListeners.remove(listener); } @Override public void addTeachInActivationListener( EnOceanTeachInActivationListener listener) { // add the listener to the set of device discovery listeners if (this.teachInListeners != null) this.teachInListeners.add(listener); else this.logger .log(LogService.LOG_ERROR, "The EnOcean teach-in listener set has not been initialized."); } @Override public void removeTeachInActivationListener( EnOceanTeachInActivationListener listener) { // remove the listener from the list, if exists if ((this.teachInListeners != null) && (!this.teachInListeners.isEmpty())) this.teachInListeners.remove(listener); } @Override public void enableTeachIn(int timeoutMillis, boolean smart) { // forward the command to the low-level library this.enOceanConnection.setSmartTeachIn(smart); this.enOceanConnection.enableTeachIn(timeoutMillis); this.notifyTeachIn(true); } @Override public void enableExplicitTeachIn(String deviceLowAddress, String deviceEEP, int timeoutMillis) { // forward the command to the low-level library this.enOceanConnection.enableTeachIn(deviceLowAddress, deviceEEP, timeoutMillis); this.notifyTeachIn(true); } @Override public void addDevice(String deviceLowAddress, String deviceEEP) { // this actually should be performed as part of the device-specific // driver "registration", if the given device is not yet available at // the network level, however we decided to leave an open stub for any // possible unforeseen cases in which device addition should be // performed apart. if ((deviceLowAddress != null) && (!deviceLowAddress.isEmpty()) && (deviceEEP != null) && (!deviceEEP.isEmpty())) { // create the device this.enOceanConnection.addNewDevice(deviceLowAddress, deviceEEP); // this in turn should trigger a call to the registered // EnJDeviceListener, i.e. this class instance. } } /* * (non-Javadoc) * * @see * org.doggateway.dog.drivers.enocean.network.interfaces.EnOceanNetwork# * getConnection() */ @Override public EnJConnection getConnection() { return this.enOceanConnection; } @Override public void teachInEnabled(boolean smart) { // notify listeners for (EnOceanTeachInActivationListener listener : this.teachInListeners) listener.teachInEnabled(); } @Override public void teachInDisabled() { // notify listeners for (EnOceanTeachInActivationListener listener : this.teachInListeners) listener.teachInDisabled(); } @Override public void updated(Dictionary<String, ?> properties) throws ConfigurationException { // get the bundle configuration parameters, e.g., the serial port to // which "connect" and the location for the low-level persistent device // database, if needed. if (properties != null) { // debug log logger.log(LogService.LOG_DEBUG, "Received configuration properties"); // get the serial port to which the physical gateway is connected // TODO: implement a serial port scanner process to remove the need // to // explicitly set the serial port to connect to String serialPort = (String) properties .get(EnOceanNetworkDriverImpl.SERIAL_PORT); // get the device db filename, can be empty String deviceDB = (String) properties .get(EnOceanNetworkDriverImpl.DEVICE_DB); // check if needed parameters are available if ((serialPort != null) && (!serialPort.isEmpty())) { try { // create the lowest link layer this.enOceanLink = new EnJLink(serialPort); // check the low-level device db filename if ((deviceDB != null) && (!deviceDB.isEmpty())) { this.enOceanConnection = new EnJConnection( this.enOceanLink, deviceDB, this); } else { this.enOceanConnection = new EnJConnection( this.enOceanLink, null, this); } if (this.enOceanConnection != null) { // set this network driver as listener for teach-in // status this.enOceanConnection.addEnJTeachInListener(this); // connect to the serial port, i.e. to the EnOcean // gateway this.enOceanLink.connect(); // log this.logger.log(LogService.LOG_INFO, "persistent device set size: " + this.enOceanConnection .getKnownDevices().size()); // update the service registration this.registerNetworkService(); } } catch (Exception e) { this.logger .log(LogService.LOG_ERROR, "Unable to connect to the EnOcean serial interface.", e); } } } } /************************************************* * * EnOcean EnJDeviceListener * ************************************************/ @Override public void addedEnOceanDevice(EnOceanDevice device) { // check if the device has already been seen if (!this.availableDevices.containsKey(device.getDeviceUID())) { // build the corresponding device info final EnOceanDeviceInfo devInfo = new EnOceanDeviceInfo( device.getDeviceUID(), ByteUtils.toHexString(device .getAddress()), device.getEEP().getEEPIdentifier() .asEEPString()); // store the device info this.availableDevices.put(device.getDeviceUID(), devInfo); // trigger device discovery (separate thread to avoid locking) // ***************************** // enable discovery only if the new device was created upon a // teach-in request. Remains to check if it is sufficient to verify // the teach-in status of the lower-level library, and if does not // generates issues at some point. // ***************************** if ((this.enOceanConnection != null) && ((this.enOceanConnection.isTeachInEnabled() || this.enOceanConnection .isSmartTeachInEnabled()))) { // the triggering task Runnable discoveryTask = new Runnable() { @Override public void run() { // iterate over all discovery listeners for (EnOceanDeviceDiscoveryListener listener : deviceDiscoveryListeners) { // notify the addition listener.addedEnOceanDevice(devInfo); } } }; // the worker thread Thread worker = new Thread(discoveryTask); // start the task worker.start(); } } /* * else { //handle device re-connection } */ } @Override public void modifiedEnOceanDevice(EnOceanDevice changedDevice) { // TODO Auto-generated method stub } @Override public void removedEnOceanDevice(EnOceanDevice changedDevice) { // handle device removal // check if the device is "registered" if (this.availableDevices.containsKey(changedDevice.getDeviceUID())) { // check if any driver is "connected to the device" EnOceanDriverInstance driverInstance = this.connectedDrivers .get(this.availableDevices.get(changedDevice.getDeviceUID())); // update the binding // here the driver shall be informed that the device is no more // connected driverInstance.unsetEnOceanDevice(changedDevice); // possible actions: remove subscription fro network and "trigger" a // device status change (more in the DAL view), do nothing } } /************************************************* * * PRIVATE METHODS * ************************************************/ /** * Registers the services described by the {@link EnOceanNetwork} interface * and provided by this class as "available" in the OSGi framework. */ private void registerNetworkService() { // simple registration stuff // avoid multiple registrations if (this.regServiceEnOceanDriverImpl == null) { // register the service, with no properties this.regServiceEnOceanDriverImpl = this.bundleContext .registerService(EnOceanNetwork.class.getName(), this, null); } } /** * Unregisters the services provided by this class from the OSGi framework */ private void unregisterNetworkService() { // performs service de-registration from the framework if (this.regServiceEnOceanDriverImpl != null) { // de-register this.regServiceEnOceanDriverImpl.unregister(); } } private void notifyTeachIn(boolean teachIn) { // notify listeners for (EnOceanTeachInActivationListener listener : this.teachInListeners) if (teachIn) listener.teachInEnabled(); else listener.teachInDisabled(); } }