//
// LFXNetworkContext.java
// LIFX
//
// Created by Jarrod Boyes on 24/03/14.
// Copyright (c) 2014 LIFX Labs. All rights reserved.
//
package lifx.java.android.network_context;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import lifx.java.android.client.LFXClient;
import lifx.java.android.constant.LFXSDKConstants;
import lifx.java.android.entities.internal.LFXBinaryPath;
import lifx.java.android.entities.internal.LFXBinaryTargetID;
import lifx.java.android.entities.internal.LFXDeviceMapping;
import lifx.java.android.entities.internal.LFXGatewayDescriptor;
import lifx.java.android.entities.internal.LFXMessage;
import lifx.java.android.entities.internal.LFXSiteID;
import lifx.java.android.entities.internal.LFXTagMapping;
import lifx.java.android.entities.internal.LFXTarget;
import lifx.java.android.entities.internal.LFXBinaryTargetID.TagField;
import lifx.java.android.entities.internal.LFXMessageObservationDescriptor.LFXMessageObserverCallback;
import lifx.java.android.entities.internal.structle.LxProtocol.Type;
import lifx.java.android.entities.internal.structle.LxProtocolDevice.SetTagLabels;
import lifx.java.android.entities.internal.structle.LxProtocolDevice.SetTags;
import lifx.java.android.entities.internal.structle.LxProtocolDevice;
import lifx.java.android.entities.internal.structle.StructleTypes.UInt64;
import lifx.java.android.light.LFXLight;
import lifx.java.android.light.LFXLightCollection;
import lifx.java.android.light.LFXTaggedLightCollection;
import lifx.java.android.light.internal.LFXAllLightsCollection;
import lifx.java.android.network_context.internal.routing_table.LFXRoutingTable;
import lifx.java.android.network_context.internal.transport_manager.LFXTransportManager;
import lifx.java.android.network_context.internal.transport_manager.LFXTransportManager.LFXTransportManagerListener;
import lifx.java.android.util.LFXByteUtils;
import lifx.java.android.util.LFXLog;
import lifx.java.android.util.LFXTimerUtils;
public class LFXNetworkContext implements LFXTransportManagerListener {
private final static String TAG = LFXNetworkContext.class.getSimpleName();
public interface LFXNetworkContextListener {
public void networkContextDidConnect(LFXNetworkContext networkContext);
public void networkContextDidDisconnect(LFXNetworkContext networkContext);
public void networkContextDidAddTaggedLightCollection(LFXNetworkContext networkContext, LFXTaggedLightCollection collection);
public void networkContextDidRemoveTaggedLightCollection(LFXNetworkContext networkContext, LFXTaggedLightCollection collection);
}
private String name;
private LFXClient client;
private ArrayList<LFXNetworkContextListener> listeners = new ArrayList<LFXNetworkContextListener>();
// Lights
private LFXLightCollection allLightsCollection;
private LFXTransportManager transportManager;
// New Lights
private LFXLightCollection newLightsCollection;
private LFXRoutingTable routingTable;
// Lights and Light States
private ArrayList<LFXTaggedLightCollection> mutableTaggedLightCollections;
private Timer siteScanTimer;
public boolean isConnected() {
return transportManager.isConnected();
}
public String getName() {
return name;
}
public LFXClient getClient() {
return client;
}
public LFXLightCollection getNewLightCollection() {
return newLightsCollection;
}
public void connect() {
transportManager.connect();
}
public void addNetworkContextListener(LFXNetworkContextListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public void removeAllNetworkContextListeners() {
listeners.clear();
}
public void removeNetworkContextListener(LFXNetworkContextListener listener) {
listeners.remove(listener);
}
public void transportManagerDidConnect(LFXTransportManager transportManager) {
LFXLog.d(TAG, "transportManagerDidConnect()");
for (LFXNetworkContextListener aListener : listeners) {
aListener.networkContextDidConnect(this);
}
scanNetworkForLightStates();
if (siteScanTimer != null) {
siteScanTimer.cancel();
siteScanTimer.purge();
}
siteScanTimer = LFXTimerUtils.getTimerTaskWithPeriod(getSiteScanTimerTask(), LFXSDKConstants.LFX_SITE_SCAN_TIMER_INTERVAL, false, "SiteScanTimer");
}
public void transportManagerDidDisconnect(LFXTransportManager transportManager) {
LFXLog.d(TAG, "transportManagerDidDisconnect()");
for (LFXNetworkContextListener aListener : listeners) {
aListener.networkContextDidDisconnect(this);
}
}
public void transportManagerDidConnectToGateway(LFXTransportManager transportManager, LFXGatewayDescriptor gatewayDescriptor) {
scanNetworkForLightStates();
}
public void transportManagerDidDisconnectFromGateway(LFXTransportManager transportManager, LFXGatewayDescriptor gatewayDescriptor) {
}
@SuppressWarnings("unchecked")
public ArrayList<LFXTaggedLightCollection> getTaggedLightCollections() {
return (ArrayList<LFXTaggedLightCollection>) mutableTaggedLightCollections.clone();
}
public void siteScanTimerDidFire() {
if (!isConnected()) {
return;
}
scanNetworkForLightStates();
}
public void handleMessage(LFXMessage message) {
routingTable.updateMappingsFromMessage(message);
updateTaggedCollectionsFromRoutingTable();
updateDeviceTagMembershipsFromRoutingTable();
for (String aDeviceID : routingTable.getDeviceIDsForBinaryPath(message.getPath())) {
forwardMessageToDeviceWithDeviceID(message, aDeviceID);
}
}
public void forwardMessageToDeviceWithDeviceID(LFXMessage message, String deviceID) {
LFXLight light = allLightsCollection.getLightWithDeviceID(deviceID);
if (light == null) {
light = LFXLight.lightWithDeviceID(deviceID, this);
allLightsCollection.addLight(light);
}
light.handleMessage(message);
}
@SuppressWarnings("unchecked")
private void updateTaggedCollectionsFromRoutingTable() {
// Remove any existing tags that don't have a mapping
Set<String> tagsThatHaveMappings = new HashSet<String>(routingTable.getAllTags());
for (LFXTaggedLightCollection anExistingTaggedCollection : ((ArrayList<LFXTaggedLightCollection>) getTaggedLightCollections().clone())) {
if (tagsThatHaveMappings.contains(anExistingTaggedCollection.getTag()) == false) {
mutableTaggedLightCollections.remove(anExistingTaggedCollection);
for (LFXNetworkContextListener aListener : listeners) {
aListener.networkContextDidRemoveTaggedLightCollection(this, anExistingTaggedCollection);
}
}
}
// Create any non-existant tags that do have a mapping
ArrayList<String> tagsTemp = new ArrayList<String>();
for (LFXTaggedLightCollection collection : getTaggedLightCollections()) {
tagsTemp.add(collection.getTag());
}
Set<String> tagsThatHaveACollection = new HashSet<String>(tagsTemp);
for (LFXTagMapping aTagMapping : routingTable.getTagMappings()) {
if (tagsThatHaveACollection.contains(aTagMapping.getTag()) == false) {
LFXTaggedLightCollection newCollection = LFXTaggedLightCollection.getLightCollectionWithNetworkContext(this);
newCollection.setTag(aTagMapping.getTag());
mutableTaggedLightCollections.add(newCollection);
tagsThatHaveACollection.add(aTagMapping.getTag());
for (LFXNetworkContextListener aListener : listeners) {
aListener.networkContextDidAddTaggedLightCollection(this, newCollection);
}
}
}
}
public void updateDeviceTagMembershipsFromRoutingTable() {
// For each device, find out what tags it should be in
//for( LFXLight aLight : allLightsCollection.getLights())
for (int i = 0; i < allLightsCollection.getLights().size(); i++) {
LFXLight aLight = allLightsCollection.getLights().get(i);
// DeviceMapping tells us the SiteID and TagField of this device
LFXDeviceMapping deviceMapping = routingTable.getDeviceMappingForDeviceID(aLight.getDeviceID());
if(deviceMapping!=null) {
// Now we need to find the tags corresponding to the SiteID and Tagfield
ArrayList<LFXTaggedLightCollection> oldTaggedCollections = aLight.getTaggedCollections();
ArrayList<String> tagsForThisLight = new ArrayList<String>();
ArrayList<LFXTaggedLightCollection> taggedCollectionsThisLightShouldBeIn = new ArrayList<LFXTaggedLightCollection>();
ArrayList<LFXTaggedLightCollection> collectionsToAddThisLightTo = new ArrayList<LFXTaggedLightCollection>();
ArrayList<TagField> tagFields = LFXBinaryTargetID.enumerateTagField(deviceMapping.getTagField());
for (TagField singularTagField : tagFields) {
LFXTagMapping tagMapping = routingTable.getTagMappingForSiteIDAndTagField(deviceMapping.getSiteID(), singularTagField);
if (tagMapping == null) {
return;
}
String tag = tagMapping.getTag();
LFXTaggedLightCollection collection = getTaggedLightCollectionForTag(tag);
tagsForThisLight.add(tag);
taggedCollectionsThisLightShouldBeIn.add(collection);
if (collection.getLights().contains(aLight) == false) {
collectionsToAddThisLightTo.add(collection);
}
}
Set<LFXTaggedLightCollection> collectionsToRemoveThisLightFrom = new HashSet<LFXTaggedLightCollection>(oldTaggedCollections);
collectionsToRemoveThisLightFrom.removeAll(new HashSet<LFXTaggedLightCollection>(taggedCollectionsThisLightShouldBeIn));
for (LFXTaggedLightCollection aCollection : collectionsToRemoveThisLightFrom) {
ArrayList<String> tempTags = aLight.getTags();
tempTags.remove(aCollection.getTag());
aLight.setTags(tempTags);
ArrayList<LFXTaggedLightCollection> tempCols = aLight.getTaggedCollections();
tempCols.remove(aCollection);
aLight.setTaggedCollections(tempCols);
aCollection.removeLight(aLight);
}
for (LFXTaggedLightCollection aCollection : collectionsToAddThisLightTo) {
ArrayList<String> tempTags = aLight.getTags();
tempTags.add(aCollection.getTag());
aLight.setTags(tempTags);
ArrayList<LFXTaggedLightCollection> tempCols = aLight.getTaggedCollections();
tempCols.add(aCollection);
aLight.setTaggedCollections(tempCols);
aCollection.addLight(aLight);
}
}
else {
LFXLog.e(TAG,"updateDeviceTagMembershipsFromRoutingTable() - Null mapping for ID:"+aLight.getDeviceID());
}
}
}
private Runnable getSiteScanTimerTask() {
Runnable siteScanTimerTask = new TimerTask() {
public void run() {
siteScanTimerDidFire();
}
};
return siteScanTimerTask;
}
public static LFXNetworkContext initWithClientTransportManagerAndName(LFXClient client, LFXTransportManager transportManager, String name) {
LFXNetworkContext networkContext = new LFXNetworkContext();
networkContext.client = client;
networkContext.name = name;
networkContext.transportManager = transportManager;
networkContext.transportManager.setNetworkContext(networkContext);
networkContext.transportManager.setListener(networkContext);
networkContext.routingTable = new LFXRoutingTable();
networkContext.allLightsCollection = LFXAllLightsCollection.getLightCollectionWithNetworkContext(networkContext);
networkContext.mutableTaggedLightCollections = new ArrayList<LFXTaggedLightCollection>();
networkContext.transportManager.addMessageObserverObjectWithCallback(networkContext, new LFXMessageObserverCallback() {
@Override
public void run(Object context, LFXMessage message) {
LFXNetworkContext networkContext = (LFXNetworkContext) context;
networkContext.handleMessage(message);
}
});
return networkContext;
}
public void resetAllCaches() {
routingTable.resetRoutingTable();
allLightsCollection.removeAllLights();
mutableTaggedLightCollections.clear();
}
public void logEverything() {
LFXLog.d(TAG,"Log Everything Called.");
}
public void sendMessage(LFXMessage message) {
// For messages that have their Target set
if (message.getTarget() != null) {
for (LFXBinaryPath aBinaryPath : routingTable.getBinaryPathsForTarget(message.getTarget())) {
LFXMessage newMessage = (LFXMessage) message.clone();// copy;
newMessage.setPath(aBinaryPath);
transportManager.sendMessage(newMessage);
}
} else // For message that have their Binary Path set explicitly (for internal use only)
{
transportManager.sendMessage(message);
}
}
public void scanNetworkForLightStates() {
LFXMessage lightGet = LFXMessage.messageWithTypeAndTarget(Type.LX_PROTOCOL_LIGHT_GET, LFXTarget.getBroadcastTarget());
sendMessage(lightGet);
Runnable task0 = new Runnable() {
@Override
public void run() {
LFXMessage getTagLabels = LFXMessage.messageWithTypeAndTarget(Type.LX_PROTOCOL_DEVICE_GET_TAG_LABELS, LFXTarget.getBroadcastTarget());
byte[] tags = new byte[8];
tags = LFXByteUtils.inverseByteArrayBits(tags);
LxProtocolDevice.GetTagLabels payload = new LxProtocolDevice.GetTagLabels(new Object(), new UInt64(tags));
getTagLabels.setPayload(payload);
sendMessage(getTagLabels);
}
};
LFXTimerUtils.scheduleDelayedTask(task0, 1500);
Runnable task1 = new Runnable() {
@Override
public void run() {
LFXMessage lightGet = LFXMessage.messageWithTypeAndTarget(Type.LX_PROTOCOL_LIGHT_GET, LFXTarget.getBroadcastTarget());
sendMessage(lightGet);
}
};
LFXTimerUtils.scheduleDelayedTask(task1, 3000);
}
public void claimDevice(LFXLight light) {
}
public LFXLightCollection getAllLightsCollection() {
return allLightsCollection;
}
public void disconnect() {
if (siteScanTimer != null) {
siteScanTimer.cancel();
siteScanTimer.purge();
}
transportManager.disconnect();
}
public void addLightToTaggedLightCollection(LFXLight light, LFXTaggedLightCollection taggedLightCollection) {
String tag = taggedLightCollection.getTag();
LFXDeviceMapping deviceMapping = routingTable.getDeviceMappingForDeviceID(light.getDeviceID());
LFXSiteID siteID = deviceMapping.getSiteID();
ArrayList<LFXTagMapping> mappings = routingTable.getTagMappingsForSiteIDAndTag(siteID, tag);
if (mappings.size() == 0) {
addTagToSiteWithSiteID(tag, siteID);
mappings = routingTable.getTagMappingsForSiteIDAndTag(siteID, tag);
if (mappings.size() == 0) {
return;
}
}
LFXTagMapping tagMapping = mappings.get(0);
if (tagMapping == null) {
return;
}
LFXMessage setTags = LFXMessage.messageWithTypeAndTarget(Type.LX_PROTOCOL_DEVICE_SET_TAGS, light.getTarget());
Object padding = new Object();
UInt64 tags = new UInt64(LFXByteUtils.bitwiseOrByteArrays(deviceMapping.getTagField().tagData, tagMapping.getTagField().tagData));
LxProtocolDevice.SetTags payload = new SetTags(padding, tags);
setTags.setPayload(payload);
sendMessage(setTags);
}
public void removeLightFromTaggedLightCollection(LFXLight light, LFXTaggedLightCollection taggedLightCollection) {
LFXDeviceMapping deviceMapping = routingTable.getDeviceMappingForDeviceID(light.getDeviceID());
LFXSiteID siteID = deviceMapping.getSiteID();
//LFXTagMapping tagMapping = routingTable.getTagMappingsForSiteIDAndTag( siteID, taggedLightCollection.getTag()).get( 0);
ArrayList<LFXTagMapping> mappings = routingTable.getTagMappingsForSiteIDAndTag(siteID, taggedLightCollection.getTag());
if (mappings.size() == 0) {
return;
}
LFXTagMapping tagMapping = mappings.get(0);
if (tagMapping == null) {
return;
}
LFXMessage setTags = LFXMessage.messageWithTypeAndPath(Type.LX_PROTOCOL_DEVICE_SET_TAGS, LFXBinaryPath.getBroadcastBinaryPathWithSiteID(tagMapping.getSiteID()));
Object padding = new Object();
UInt64 tags = new UInt64(LFXByteUtils.bitwiseAndByteArrays(deviceMapping.getTagField().tagData, LFXByteUtils.inverseByteArrayBits(tagMapping.getTagField().tagData)));
LxProtocolDevice.SetTags payload = new SetTags(padding, tags);
setTags.setPayload(payload);
sendMessage(setTags);
}
public boolean renameTaggedLightCollectionWithNewTag(LFXTaggedLightCollection collection, String newTag) {
LFXTaggedLightCollection existingTaggedCollectionWithNewTag = getTaggedLightCollectionForTag(newTag);
if (existingTaggedCollectionWithNewTag != null) {
LFXLog.i(TAG, "Tag " + newTag + " already exists, aborting rename of " + collection.getTag());
return false;
}
LFXLog.i(TAG, "Renaming tag " + collection.getTag() + " to " + newTag);
for (LFXTagMapping aTagMapping : routingTable.getTagMappingsForTag(collection.getTag())) {
LFXMessage setTagLabels = LFXMessage.messageWithTypeAndPath(Type.LX_PROTOCOL_DEVICE_SET_TAG_LABELS, LFXBinaryPath.getBroadcastBinaryPathWithSiteID(aTagMapping.getSiteID()));
Object padding = new Object();
UInt64 tags = new UInt64(aTagMapping.getTagField().tagData);
String label = newTag;
LxProtocolDevice.SetTagLabels payload = new SetTagLabels(padding, tags, label);
setTagLabels.setPayload(payload);
sendMessage(setTagLabels);
}
return true;
}
public LFXTaggedLightCollection getTaggedLightCollectionForTag(String tag) {
for (LFXTaggedLightCollection aCollection : getTaggedLightCollections()) {
if (aCollection.getTag().equals(tag)) {
return aCollection;
}
}
return null;
}
// Creates a new Tagged Light Collection
public LFXTaggedLightCollection addTaggedLightCollectionWithTag(String tag) {
LFXTaggedLightCollection existingCollection = getTaggedLightCollectionForTag(tag);
if (existingCollection != null) {
return existingCollection;
}
// Add the tag to each site
for (LFXSiteID aSiteID : routingTable.getSiteIDs()) {
addTagToSiteWithSiteID(tag, aSiteID);
}
return getTaggedLightCollectionForTag(tag);
}
public void addTagToSiteWithSiteID(String tag, LFXSiteID siteID) {
TagField nextAvailableTagField = new TagField();
nextAvailableTagField.tagData = new byte[8];
for (int tagIndex = 0; tagIndex < 64; tagIndex++) {
LFXByteUtils.clearByteArray(nextAvailableTagField.tagData);
LFXByteUtils.setBit(nextAvailableTagField.tagData, tagIndex);
if (routingTable.getTagMappingForSiteIDAndTagField(siteID, nextAvailableTagField) == null) {
//nextAvailableTagField = tagField;
break;
}
}
if (LFXByteUtils.isByteArrayEmpty(nextAvailableTagField.tagData)) {
LFXLog.e(TAG, "Unable to create tag " + tag + " in site " + siteID.getStringValue() + ", no available tag slots");
} else {
LFXLog.e(TAG, "Creating tag " + tag + " in site " + siteID.getStringValue() + " with tagField " + LFXByteUtils.byteArrayToHexString(nextAvailableTagField.tagData));
LFXMessage setTagLabels = LFXMessage.messageWithTypeAndPath(Type.LX_PROTOCOL_DEVICE_SET_TAG_LABELS, LFXBinaryPath.getBroadcastBinaryPathWithSiteID(siteID));
Object padding = new Object();
UInt64 tags = new UInt64(nextAvailableTagField.tagData);
String label = tag;
LxProtocolDevice.SetTagLabels payload = new SetTagLabels(padding, tags, label);
setTagLabels.setPayload(payload);
sendMessage(setTagLabels);
}
}
public void deleteTaggedLightCollection(LFXTaggedLightCollection taggedLightCollection) {
for (LFXLight aLight : taggedLightCollection.getLights()) {
removeLightFromTaggedLightCollection(aLight, taggedLightCollection);
}
for (LFXTagMapping aTagMapping : routingTable.getTagMappingsForTag(taggedLightCollection.getTag())) {
LFXMessage setTagLabels = LFXMessage.messageWithTypeAndPath(Type.LX_PROTOCOL_DEVICE_SET_TAG_LABELS, LFXBinaryPath.getBroadcastBinaryPathWithSiteID(aTagMapping.getSiteID()));
Object padding = new Object();
UInt64 tags = new UInt64(aTagMapping.getTagField().tagData);
String label = "";
LxProtocolDevice.SetTagLabels payload = new SetTagLabels(padding, tags, label);
setTagLabels.setPayload(payload);
sendMessage(setTagLabels);
}
}
}