package org.ow2.chameleon.fuchsia.discovery.philipshue;
/*
* #%L
* OW2 Chameleon - Fuchsia Discovery Philips Hue
* %%
* Copyright (C) 2009 - 2014 OW2 Chameleon
* %%
* 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.
* #L%
*/
import com.philips.lighting.hue.sdk.connection.impl.PHBridgeInternal;
import com.philips.lighting.hue.sdk.utilities.impl.PHLog;
import com.philips.lighting.model.PHBridge;
import com.philips.lighting.model.PHHueError;
import com.philips.lighting.model.PHHueParsingError;
import org.apache.felix.ipojo.annotations.*;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.upnp.UPnPDevice;
import org.ow2.chameleon.fuchsia.core.component.AbstractDiscoveryComponent;
import org.ow2.chameleon.fuchsia.core.component.DiscoveryService;
import org.ow2.chameleon.fuchsia.core.declaration.ImportDeclaration;
import org.ow2.chameleon.fuchsia.core.declaration.ImportDeclarationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.philips.lighting.hue.sdk.*;
import java.util.*;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import static org.apache.felix.ipojo.Factory.INSTANCE_NAME_PROPERTY;
@Component
@Provides(specifications = {PhilipsDiscoveryService.class,DiscoveryService.class})
@Instantiate
public class PhilipsHueBridgeDiscovery extends AbstractDiscoveryComponent implements PHSDKListener,PhilipsDiscoveryService {
private static final Logger LOG = LoggerFactory.getLogger(PhilipsHueBridgeDiscovery.class);
private static final String EVENT_CACHE_UPDATED="philips/hue/bridge/cache_updated";
@ServiceProperty(name = INSTANCE_NAME_PROPERTY)
private String name;
@ServiceProperty(name = "philips.hue.discovery.pooling", value = "10000")
private Long pollingTime;
private PHLog philipsLog;
private Boolean pollingDisabled;
private Boolean scanLocalNetwork;
private static Preferences preferences = PhilipsPreference.getInstance();
private Map<String, ImportDeclaration> ipImportDeclarationMap = new HashMap<String, ImportDeclaration>();
private Set<String> ipAuthenticationInProgress = new HashSet<String>();
private PHHueSDK philipsSDK;
private BridgeSearchScheduler bridgeSearchScheduler;
private static PHBridgeSearchManager philipsSearchManager;
private final String bridgeUsernameKey = "username";
@Requires
EventAdmin eventAdmin;
public PhilipsHueBridgeDiscovery(BundleContext bundleContext) {
super(bundleContext);
philipsSDK = PHHueSDK.getInstance();
philipsSDK.getNotificationManager().registerSDKListener(this);
pollingDisabled=Boolean.getBoolean("philips.discovery.pooling.disable");
scanLocalNetwork=Boolean.getBoolean("philips.discovery.scanNetwork");
philipsLog =(PHLog) philipsSDK.getSDKService(PHHueSDK.LOG);
// philipsLog.setSdkLogLevel(PHLog.DEBUG);
philipsSearchManager =(PHBridgeSearchManager) philipsSDK.getSDKService(PHHueSDK.SEARCH_BRIDGE);
}
@Validate
public void start() {
LOG.info("Philips Hue discovery is up and running.");
if(pollingDisabled){
LOG.warn("Polling Disabled, API will use UPnP to detect the PhilipsHue bridge");
//philipsSearchManager.search(true, false, scanLocalNetwork);
}else {
LOG.info("Polling Enabled");
bridgeSearchScheduler=new BridgeSearchScheduler(this.pollingTime, philipsSearchManager,scanLocalNetwork);
philipsSDK.getNotificationManager().registerSDKListener(bridgeSearchScheduler);
bridgeSearchScheduler.activate();
}
}
@Bind(id="philipsBind",filter = "(&(UPnP.device.modelName=Philips*))",aggregate = true,optional = true,specification = UPnPDevice.class)
public void bindPhilipsBridge(){
LOG.trace("Binding new UPnP device");
if(pollingDisabled){
LOG.trace("Firing lamp detection..");
searchForBridges();
}else {
LOG.trace("Not firing lamp detection, polling is enabled.");
}
}
@Unbind(id="philipsBind")
public void unbindPhilipsBridge(){
LOG.trace("Unbinding UPnP device");
if(pollingDisabled){
searchForBridges();
}
}
@Invalidate
public void stop() {
philipsSDK.destroySDK();
if(!pollingDisabled) {
bridgeSearchScheduler.desactivate();
}
}
public String getName() {
return name;
}
public void onCacheUpdated(List<Integer> list, PHBridge phBridge) {
Dictionary metatable = new Hashtable();
metatable.put("bridge", phBridge);
Event eventAdminMessage = new Event(EVENT_CACHE_UPDATED, metatable);
eventAdmin.sendEvent(eventAdminMessage);
}
public void onBridgeConnected(PHBridge phBridge, String s) {
if (s != null) {
preferences.put(bridgeUsernameKey, s);
try {
preferences.flush();
} catch (BackingStoreException e) {
LOG.error("failed to store username in java preferences, this will force you to push the bridge button everytime to authenticate", e);
}
}
String bridgeIP = phBridge.getResourceCache().getBridgeConfiguration().getIpAddress();
LOG.info("Removing brigde {} from the list of authentication in progress, there {} bridges in authentication progress", bridgeIP, ipAuthenticationInProgress.size());
ipAuthenticationInProgress.remove(bridgeIP);
LOG.info("Fetching IP {} for connection", bridgeIP);
philipsSDK.enableHeartbeat(phBridge, pollingTime);
philipsSDK.setSelectedBridge(phBridge);
ImportDeclaration declaration = generateImportDeclaration(phBridge);
super.registerImportDeclaration(declaration);
ipImportDeclarationMap.put(bridgeIP, declaration);
}
public void onAuthenticationRequired(PHAccessPoint phAccessPoint) {
final String MSG = "authentication required, you have 30 seconds to push the button on the bridge";
LOG.warn(MSG);
Dictionary metatable = new Hashtable();
metatable.put("message", MSG);
metatable.put("ip", phAccessPoint.getIpAddress());
metatable.put("mac", phAccessPoint.getMacAddress());
Event eventAdminMessage = new Event("philips/hue/bridge/authentication_required", metatable);
eventAdmin.sendEvent(eventAdminMessage);
ipAuthenticationInProgress.add(phAccessPoint.getIpAddress());
philipsSDK.startPushlinkAuthentication(phAccessPoint);
}
public void onAccessPointsFound(List<PHAccessPoint> phAccessPoints) {
for (PHAccessPoint foundAP : phAccessPoints) {
if (!philipsSDK.isAccessPointConnected(foundAP)) {
LOG.trace("AP not connected.");
LOG.info("Auth in progress for " + foundAP.getIpAddress());
for (String value : ipAuthenticationInProgress) {
LOG.info("\tIP:" + value);
}
LOG.info("/Auth in progress:");
if (!ipAuthenticationInProgress.contains(foundAP.getIpAddress())) {
LOG.trace("Requesting connection for " + foundAP.getIpAddress());
ipAuthenticationInProgress.add(foundAP.getIpAddress());
connect(foundAP);
} else {
LOG.trace("not requesting connection for {}, it was already in progress", foundAP.getIpAddress());
}
} else {
LOG.trace("access point already connected {}",foundAP.getIpAddress());
}
}
}
public void onSuccess() {
LOG.trace("Connected with success");
}
public void onError(int code, String s) {
LOG.trace("Bridge failed with the code {} and message {}",code,s);
if (code == PHHueError.BRIDGE_NOT_RESPONDING) {
}else if (code == PHMessageType.PUSHLINK_BUTTON_NOT_PRESSED) {
}else if (code == PHMessageType.PUSHLINK_AUTHENTICATION_FAILED) {
LOG.trace("Button not pressed, code {}",code);
ipAuthenticationInProgress.clear();
Dictionary metatable = new Hashtable();
metatable.put("message", s);
metatable.put("code", code);
Event eventAdminMessage = new Event("philips/hue/bridge/error", metatable);
eventAdmin.sendEvent(eventAdminMessage);
}else if (code == PHHueError.AUTHENTICATION_FAILED){
preferences.remove(bridgeUsernameKey);
LOG.warn("removing invalid user registered in the preferences");
try {
preferences.flush();
} catch (BackingStoreException e) {
LOG.error("failed removing invalid user registered in the preferences, you might have problems using hue lamp");
}
}
}
public void onStateUpdate(Hashtable<String, String> hashtable, List<PHHueError> phHueErrors) {
// not used
LOG.trace("State updated {} and errors {}",hashtable,phHueErrors);
}
public void onConnectionResumed(PHBridge phBridge) {
//This is called every 1'40" which is too verbose (in long term)
//LOG.trace("Connection resumed with the bridge {}",phBridge);
}
public void onConnectionLost(PHAccessPoint phAccessPoint) {
LOG.trace("Fetching IP {} for disconnection", phAccessPoint.getIpAddress());
ImportDeclaration declaration = ipImportDeclarationMap.get(phAccessPoint.getIpAddress());
if (declaration != null) {
super.unregisterImportDeclaration(declaration);
} else {
LOG.warn("No such ip found for disconnection");
}
}
public void onParsingErrors(List<PHHueParsingError> list) {
}
public void searchForBridges() {
LOG.trace("Searching for bridges..");
philipsSearchManager.search(true, false, scanLocalNetwork);
}
private ImportDeclaration generateImportDeclaration(PHBridge bridge) {
Map<String, Object> metadata = new HashMap<String, Object>();
metadata.put("id", "bridge-"+bridge.getResourceCache().getBridgeConfiguration().getBridgeID());
metadata.put("discovery.philips.bridge.type", PHBridge.class.getName());
metadata.put("discovery.philips.bridge.object", bridge);
metadata.put("scope", "generic");
return ImportDeclarationBuilder.fromMetadata(metadata).build();
}
private void connect(PHAccessPoint ap) {
LOG.trace("Asking connection for the AP {}", ap);
String username = preferences.get(bridgeUsernameKey, null);
if (username != null) {
ap.setUsername(username);
}
philipsSDK.connect(ap);
}
}