
com.github.besherman.lifx.impl.light.LFXAllGroups Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lifx-sdk-java Show documentation
Show all versions of lifx-sdk-java Show documentation
A port of "LIFX Android SDK" to Java
The newest version!
/*
* The MIT License
*
* Copyright 2014 Richard Löfberg.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.besherman.lifx.impl.light;
import com.github.besherman.lifx.impl.entities.internal.LFXTagID;
import com.github.besherman.lifx.LFXLight;
import com.github.besherman.lifx.LFXLightCollectionListener;
import com.github.besherman.lifx.LFXGroup;
import com.github.besherman.lifx.LFXGroupCollection;
import com.github.besherman.lifx.LFXGroupCollectionListener;
import com.github.besherman.lifx.impl.entities.internal.LFXDeviceID;
import com.github.besherman.lifx.impl.entities.internal.LFXMessage;
import com.github.besherman.lifx.impl.entities.internal.structle.LxProtocol;
import com.github.besherman.lifx.impl.entities.internal.structle.LxProtocol.Type;
import static com.github.besherman.lifx.impl.entities.internal.structle.LxProtocol.Type.LX_PROTOCOL_DEVICE_STATE_TAGS;
import static com.github.besherman.lifx.impl.entities.internal.structle.LxProtocol.Type.LX_PROTOCOL_DEVICE_STATE_TAG_LABELS;
import com.github.besherman.lifx.impl.entities.internal.structle.LxProtocolDevice;
import com.github.besherman.lifx.impl.network.LFXMessageRouter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Each light has a bitfield (64 bits) which indicates what tags the light has.
* Each light also has a string array (64 elements) which contains the label
* of the corresponding tag. So it is technically possible for two lights to have
* the same tag with different labels. The official apps solve this by always
* sending tag labels to all lights to keep them in sync. We do the same.
*
* The official apps also thinks that en empty label means that the tag does
* not exist. So a light can have a tag but if the label is empty it still does
* not show up.
*
*/
public class LFXAllGroups implements LFXGroupCollection {
private final Map allGroups = new ConcurrentHashMap<>();
private final Set availableGroups = new CopyOnWriteArraySet<>();
private final Object availableLock = new Object();
private final List listeners = new CopyOnWriteArrayList<>();
private final Set hasNotReceivedLabel = Collections.synchronizedSet(EnumSet.allOf(LFXTagID.class));
private volatile CountDownLatch allLabelsLoaded = new CountDownLatch(1);
private LFXAllLights allLights;
private LFXMessageRouter router;
public LFXAllGroups() {
}
public void setRouter(LFXMessageRouter router) {
this.router = router;
}
public void setLights(LFXAllLights lights) {
this.allLights = lights;
//
// Since the tags holds references to lights we have to keep them
// in sync with the lights collection.
//
// TODO: leaking listener
this.allLights.addLightCollectionListener(new LFXLightCollectionListener() {
@Override public void lightAdded(LFXLight light) {}
@Override public void lightRemoved(LFXLight light) {
for(LFXGroupImpl group: allGroups.values()) {
group.removeImpl((LFXLightImpl)light);
}
}
});
}
@Override
public LFXGroup get(String label) {
for(LFXGroupImpl group: availableGroups) {
if(group.getLabel().equals(label)) {
return group;
}
}
return null;
}
@Override
public LFXGroup add(String label) {
if(label == null) {
throw new IllegalArgumentException("label can not be null");
}
if(label.isEmpty()) {
throw new IllegalArgumentException("label can not be empty");
}
LFXGroupImpl firstFreeGroup = null;
synchronized(availableLock) {
// try to find one with the same title first
for(LFXTagID tagID: LFXTagID.values()) {
LFXGroupImpl group = allGroups.get(tagID);
if(group.getLabel().equals(label)) {
return group;
}
}
for(LFXTagID tagID: LFXTagID.values()) {
LFXGroupImpl group = allGroups.get(tagID);
if(!availableGroups.contains(group)) {
firstFreeGroup = group;
break;
}
}
if(firstFreeGroup == null) {
return null;
}
firstFreeGroup.setLabel(label);
updateAvailability(firstFreeGroup);
}
return firstFreeGroup;
}
@Override
public void remove(LFXGroup group) {
synchronized(availableLock) {
LFXGroupImpl impl = (LFXGroupImpl)group;
impl.setLabelImpl("");
Iterator it = group.iterator();
while(it.hasNext()) {
LFXLightImpl light = (LFXLightImpl)it.next();
Set tags = getTagIDsForLight(light);
tags.remove(impl.getTagID());
LxProtocolDevice.SetTags payload = new LxProtocolDevice.SetTags(LFXTagID.pack(tags));
LFXMessage msg = new LFXMessage(LxProtocol.Type.LX_PROTOCOL_DEVICE_SET_TAGS, light.getTarget(), payload);
for(int i = 0; i < 3; i++) {
router.sendMessage(msg);
}
}
impl.clearImpl();
updateAvailability(impl);
}
}
@Override
public int size() {
return availableGroups.size();
}
@Override
public boolean isEmpty() {
return availableGroups.isEmpty();
}
@Override
public Iterator iterator() {
Collection copy = new ArrayList<>();
copy.addAll(availableGroups);
return copy.iterator();
}
@Override
public void addGroupCollectionListener(LFXGroupCollectionListener l) {
listeners.add(l);
}
@Override
public void removeGroupCollectionListener(LFXGroupCollectionListener l) {
listeners.remove(l);
}
public void open() {
for(LFXTagID id: LFXTagID.values()) {
allGroups.put(id, new LFXGroupImpl(router, this, id));
}
}
public void close() {
for(LFXTagID id: LFXTagID.values()) {
LFXGroupImpl group = allGroups.remove(id);
if(group != null && availableGroups.contains(group)) {
availableGroups.remove(group);
fireGroupRemoved(group);
}
}
// TODO: i am not a fan of this
hasNotReceivedLabel.addAll(EnumSet.allOf(LFXTagID.class));
allLabelsLoaded = new CountDownLatch(1);
}
public boolean isLoaded() {
return hasNotReceivedLabel.isEmpty();
}
public boolean waitForInitLoaded(long timeout, TimeUnit unit) throws InterruptedException {
allLabelsLoaded.await(timeout, unit);
return isLoaded();
}
public void sendAddLightToGroup(LFXLightImpl light, LFXGroupImpl group) {
Set tags = getTagIDsForLight(light);
tags.add(group.getTagID());
LxProtocolDevice.SetTags payload = new LxProtocolDevice.SetTags(LFXTagID.pack(tags));
for(int i = 0; i < 3; i++) {
router.sendMessage(new LFXMessage(Type.LX_PROTOCOL_DEVICE_SET_TAGS, light.getTarget(), payload));
}
}
public void sendRemoveLightToGroup(LFXLightImpl light, LFXGroupImpl group) {
Set tags = getTagIDsForLight(light);
tags.remove(group.getTagID());
LxProtocolDevice.SetTags payload = new LxProtocolDevice.SetTags(LFXTagID.pack(tags));
for(int i = 0; i < 3; i++) {
router.sendMessage(new LFXMessage(Type.LX_PROTOCOL_DEVICE_SET_TAGS, light.getTarget(), payload));
}
}
/**
* Returns all tags that the light has.
*/
public Set getTagIDsForLight(LFXLightImpl light) {
Set result = new HashSet<>();
for(LFXGroupImpl group: availableGroups) {
if(group.contains(light)) {
result.add(group.getTagID());
}
}
return result;
}
public void handleMessage(Set targets, LFXMessage message) {
Type type = message.getType();
if(type == LX_PROTOCOL_DEVICE_STATE_TAGS) {
LxProtocolDevice.StateTags payload = message.getPayload();
Set ids = LFXTagID.unpack(payload.getTags());
setLightGroups(targets, ids);
} else if(type == LX_PROTOCOL_DEVICE_STATE_TAG_LABELS) {
LxProtocolDevice.StateTagLabels payload = message.getPayload();
Set tags = LFXTagID.unpack(payload.getTags());
String label = payload.getLabel();
setGroupLabels(tags, label);
}
}
private void setLightGroups(Set targets, Set ids) {
for(LFXDeviceID deviceId: targets) {
LFXLightImpl light = allLights.getLight(deviceId);
for(LFXGroupImpl group: allGroups.values()) {
if(ids.contains(group.getTagID())) {
group.addImpl(light);
} else {
group.removeImpl(light);
}
updateAvailability(group);
}
}
}
private void setGroupLabels(Set ids, String label) {
for(LFXTagID id: ids) {
LFXGroupImpl group = allGroups.get(id);
group.labelDidChangeTo(label);
updateAvailability(group);
}
if(!hasNotReceivedLabel.isEmpty()) {
hasNotReceivedLabel.removeAll(ids);
if(hasNotReceivedLabel.isEmpty()) {
allLabelsLoaded.countDown();
}
}
}
private void updateAvailability(LFXGroupImpl group) {
if(group.isAvaliable() && !availableGroups.contains(group)) {
availableGroups.add(group);
fireGroupAdded(group);
} else if(!group.isAvaliable() && availableGroups.contains(group)) {
availableGroups.remove(group);
fireGroupRemoved(group);
}
}
private void fireGroupAdded(LFXGroupImpl group) {
for(LFXGroupCollectionListener l: listeners) {
try {
l.groupAdded(group);
} catch(Exception ex) {
Logger.getLogger(LFXAllGroups.class.getName()).log(Level.SEVERE,
"GroupCollectionListener failed", ex);
}
}
}
private void fireGroupRemoved(LFXGroupImpl group) {
for(LFXGroupCollectionListener l: listeners) {
try {
l.groupRemoved(group);
} catch(Exception ex) {
Logger.getLogger(LFXAllGroups.class.getName()).log(Level.SEVERE,
"GroupCollectionListener failed", ex);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy