blob: 21a3ed9a81348eccb56508bfc20f9c33dfc56fe6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014-2018 Orange.
* 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
*
* Contributors:
* BAREAU Cyrille <cyrille.bareau@orange.com>
* BONNARDEL Gregory <gbonnardel.ext@orange.com>
* BORAWSKI Pawel <pawel.borawski@orange.com>
* RATUSZEK Przemyslaw <przemyslaw.ratuszek@orange.com>
* WIERZBOWSKI Jacek <jacek.wierzbowski@orange.com>
*******************************************************************************/
package org.eclipse.om2m.hue.impl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Deactivate;
/**
* Implementation class of the OSGi component HueBasedriver. Registers
* discovered Hue Bridge and Lights as OSGi services.
*/
public class HueBasedriver implements ManagedService {
// ------------------------------------------------------------------------
/** Logger */
private static Log Logger = LogFactory.getLog(HueBasedriver.class);
/*
* SSDP port number
*/
private static final int SSDP_PORT = 1900;
private static final int MTIMER_PERIOD = 3 * 60 * 1000;
/*
* Broadcast address for Upnp. Defined by normalization.
*/
private static final String UPNP_BROADCAST_IP = "239.255.255.250";
/*
* UDP packet size
*/
private static final int UDP_PACKET_SIZE = 1024;
/**
* ENABLE_SSDP_DISCOVERY is a boolean configAdmin property. If true, the hue
* base driver must perform SSDP discovery in order to discover the Hue
* Bridge(s).
*/
private static final String ENABLE_SSDP_DISCOVERY = "enable.ssdp.discovery";
/**
* HUE_BRIDGE_DESCRIPTION_URL
*/
private static final String HUE_BRIDGE_DESCRIPTION_URL = "hue.bridge.description.url";
/**
* HUE_BRIDGE_USER_ID
*/
private static final String HUE_BRIDGE_USER_ID = "hue.bridge.userid";
private static final String SERVICE_ID = "service.pid";
// ------------------------------------------------------------------------
/*
* Timer for M-SEARCH
*/
private Timer mSearchTimer = new Timer();
/*
* M-SEARCH thread
*/
private Thread mSearchThread;
/*
* ALIVE thread
*/
private Thread aliveThread;
/**
* Thread for registration process handling
*/
private Thread bridgeRegistrationThread;
// ------------------------------------------------------------------------
private MulticastSocket mSearchSocket;
private MulticastSocket aliveSocket;
private boolean mSearchInitialized;
private boolean aliveInitialized;
private boolean mSearchActivated;
private boolean newMSearchRequired;
private List<NetworkInterface> networkInterfaces;
private HueBridgeDiscovery discovery;
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Constructor
*
*/
public HueBasedriver() {
networkInterfaces = new ArrayList<NetworkInterface>();
mSearchInitialized = false;
aliveInitialized = false;
mSearchActivated = false;
newMSearchRequired = true;
Logger.info("HueBasedriver ctor");
}
// ------------------------------------------------------------------------
// Declarative Service injection methods
// ------------------------------------------------------------------------
/**
* Activate method from Declarative Service
*/
@Activate
protected void activate(final ComponentContext context) {
Logger.info("Activating");
// modified(properties);
discovery = new HueBridgeDiscovery(context.getBundleContext());
// initMSearchTimer();
// discovery.registerBridge(new FakeBridge("xml"));
}
/**
* Deactivate method from Declarative Service
*/
@Deactivate
protected void deactivate(ComponentContext cc) {
Logger.info("Deactivating");
closeSockets();
}
private void initMSearchTimer() {
mSearchTimer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if (mSearchInitialized) {
newMSearchRequired = true;
mSearchSocket.close();
} else {
mSearchActivated = true;
searchHueBridge();
}
if (!aliveInitialized) {
listenAliveHueBridge();
}
}
}, 10, MTIMER_PERIOD); // send first M-SEARCH now
// + new M-SEARCH every 3 min
}
private void cancelMSearchTimer() {
mSearchTimer.cancel();
}
private void closeSockets() {
if (discovery != null) {
discovery.stopBackgroundProcesses();
}
if (bridgeRegistrationThread != null) {
bridgeRegistrationThread.interrupt();
}
if (mSearchThread != null) {
mSearchThread.interrupt();
}
if (aliveThread != null) {
aliveThread.interrupt();
}
mSearchActivated = false;
if (mSearchSocket != null) {
mSearchSocket.close();
}
if (aliveSocket != null) {
aliveSocket.close();
}
}
// ------------------------------------------------------------------------
// Private utility methods
// ------------------------------------------------------------------------
private void initSearch() {
try {
mSearchSocket = new MulticastSocket();
mSearchSocket.setReuseAddress(true);
// Keep a backlog of incoming datagrams if we are not fast enough
mSearchSocket.setReceiveBufferSize(32768);
InetSocketAddress multicastAddress = new InetSocketAddress(UPNP_BROADCAST_IP, SSDP_PORT);
// discover network interface
findNetworkInterfaces();
for (NetworkInterface itf : networkInterfaces) {
try {
mSearchSocket.joinGroup(multicastAddress, itf);
} catch (Exception ignored) {
}
}
} catch (IOException e) {
Logger.warn("Init Search IOException : " + e.getMessage());
}
}
private void initAlive() {
try {
aliveSocket = new MulticastSocket(SSDP_PORT);
aliveSocket.setReuseAddress(true);
// Keep a backlog of incoming datagrams if we are not fast enough
aliveSocket.setReceiveBufferSize(32768);
InetSocketAddress multicastAddress = new InetSocketAddress(UPNP_BROADCAST_IP, SSDP_PORT);
// discover network interface
findNetworkInterfaces();
for (NetworkInterface itf : networkInterfaces) {
try {
aliveSocket.joinGroup(multicastAddress, itf);
} catch (Exception ignored) {
}
}
} catch (IOException e) {
Logger.warn("Init Alive IOException : " + e.getMessage());
}
}
private void searchHueBridge() {
mSearchThread = new Thread() {
public void run() {
while (mSearchActivated) {
initSearch();
mSearchInitialized = true;
// -------------------------------------------------------
// send M-SEARCh message
discovery.clearDescriptions();
sendMSearchMessage();
newMSearchRequired = false;
// -------------------------------------------------------
// waiting for responses
byte[] data = new byte[UDP_PACKET_SIZE];
while (!newMSearchRequired) {
DatagramPacket receivePacket = new DatagramPacket(data, data.length);
try {
mSearchSocket.receive(receivePacket);
} catch (IOException e) {
// socket closed to interrupt receive blocking and launch a new M-SEARCH
break;
}
lookingForHueBridge(receivePacket);
}
}
}
};
mSearchThread.start();
}
private void listenAliveHueBridge() {
aliveThread = new Thread() {
public void run() {
initAlive();
aliveInitialized = true;
byte[] data = new byte[UDP_PACKET_SIZE];
while (true) {
DatagramPacket receivePacket = new DatagramPacket(data, data.length);
try {
aliveSocket.receive(receivePacket);
} catch (IOException e) {
// socket closed to interrupt receive blocking and launch a new M-SEARCH
}
lookingForHueBridge(receivePacket);
}
}
};
aliveThread.start();
}
private void lookingForHueBridge(final DatagramPacket receivePacket) {
String message = new String(receivePacket.getData());
try {
String tag = "location:";
int startIndex = message.toLowerCase().indexOf(tag) + tag.length();
if (startIndex >= tag.length()) {
int endIndex = message.indexOf("\r\n", startIndex);
String location = message.substring(startIndex, endIndex).trim();
discovery.processNewHueBridge(location);
}
} catch (IOException e) {
Logger.warn("Looking for Hue Bridge IOException : ", e);
}
}
@SuppressWarnings("rawtypes")
private void findNetworkInterfaces() {
try {
for (Enumeration itfs = NetworkInterface.getNetworkInterfaces(); itfs.hasMoreElements();) {
NetworkInterface itf = (NetworkInterface) itfs.nextElement();
if (isUsableNetworkInterface(itf)) {
synchronized (networkInterfaces) {
networkInterfaces.add(itf);
}
}
}
} catch (Exception ignored) {
}
}
private List<InetAddress> getInetAddresses(final NetworkInterface networkInterface) {
return Collections.list(networkInterface.getInetAddresses());
}
private boolean isUsableNetworkInterface(final NetworkInterface iface) throws Exception {
if (getInetAddresses(iface).isEmpty()) {
return false;
}
if ((iface.getDisplayName() != null)
&& (iface.getDisplayName().toLowerCase(Locale.ENGLISH).indexOf("vmnet") >= 0)) {
return false;
}
String lower = iface.getName().toLowerCase(Locale.ENGLISH);
if (lower.startsWith("vmnet") || lower.startsWith("vnic") || lower.startsWith("ppp")
|| lower.startsWith("lo") || (lower.indexOf("virtual") != -1)) {
return false;
}
return true;
}
protected boolean isUsableAddress(final NetworkInterface networkInterface,
final InetAddress address) {
return (address instanceof Inet4Address) || address.isLoopbackAddress();
}
private void sendMSearchMessage() {
StringBuffer msearch = new StringBuffer();
msearch.append("M-SEARCH * HTTP/1.1\r\n");
msearch.append("Man: \"ssdp:discover\"\r\n");
msearch.append("Mx: 3\r\n");
msearch.append("Host: " + UPNP_BROADCAST_IP + ":" + SSDP_PORT + "\r\n");
msearch.append("St: ssdp:all\r\n");
msearch.append("\r\n");
try {
InetAddress group = InetAddress.getByName(UPNP_BROADCAST_IP);
int msearch_length = msearch.length();
byte[] msearch_bytes = msearch.toString().getBytes();
DatagramPacket packet = new DatagramPacket(msearch_bytes, msearch_length, group, SSDP_PORT);
mSearchSocket.send(packet);
} catch (SocketException e) {
Logger.warn("M-SEARCH SocketException : ", e);
} catch (IOException e) {
Logger.warn("M-SEARCH IOException : ", e);
} catch (Exception e) {
Logger.warn("M-SEARCH Exception : ", e);
}
}
@SuppressWarnings("rawtypes")
@Override
public void updated(final Dictionary properties) throws ConfigurationException {
closeSockets();
Logger.info("Updating Hue Bridge connection upon the config file");
try {
cancelMSearchTimer();
} catch (Exception e) {
}
if (properties == null) {
// enable SSDP discovery
Logger.info("No found properties... Try SSDP discovery");
initMSearchTimer();
} else {
try {
if (Boolean.valueOf(properties.get(ENABLE_SSDP_DISCOVERY).toString())) {
initMSearchTimer();
return;
}
} catch (Exception ignored) {
}
final String descriptionUrl = (String) properties.get(HUE_BRIDGE_DESCRIPTION_URL);
final String userId = (String) properties.get(HUE_BRIDGE_USER_ID);
Logger.info("Config file description url: " + descriptionUrl
+ ", config file user id: " + userId);
if (userId == null || userId == "") {
Logger.warn("No userId for Hue Bridge update...");
}
if (descriptionUrl == null) {
Logger.warn("No description...");
} else {
bridgeRegistrationThread = new Thread() {
public void run() {
try {
String newUserId = discovery.processHueBridge(descriptionUrl, userId);
if (newUserId != null && !newUserId.equals(userId)) {
updatePropertiesFile(properties.get(SERVICE_ID) + ".properties",
HUE_BRIDGE_USER_ID, newUserId);
}
} catch (IOException e) {
Logger.warn("Error processing new bridge", e);
}
}
};
bridgeRegistrationThread.run();
}
}
}
/**
* Set property and save in (config) file
*
* @param filename: name of config file from configurations/services/ directory
* @param key: name of the property to update
* @param value: value to update
*/
protected void updatePropertiesFile(String filename, String key, String value) {
try {
File file = new File("configurations/services/" + filename);
Logger.info("Updating the: " + filename + " file, to save the: " + key + " property");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
ArrayList<String> lines = new ArrayList<String>();
String line = br.readLine();
while (line != null) {
if (line.contains(key)) {
line = key + " " + value;
}
lines.add(line);
line = br.readLine();
}
fr.close();
FileWriter fw = new FileWriter(file);
BufferedWriter out = new BufferedWriter(fw);
out.write(lines.toString().replace("[", "").replace("]", "").replace(", ", "\n"));
out.close();
}
catch (IOException e) {
Logger.error("Error while updating the: " + filename + " file, to save the: " + key + " property", e);
}
}
}