added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.lifx</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,236 @@
# LIFX Binding
This binding integrates the [LIFX LED Lights](https://www.lifx.com/).
All LIFX lights are directly connected to the WLAN and the binding communicates with them over a UDP protocol.
![LIFX E27](doc/lifx_e27.jpg)
## Supported Things
The following table lists the thing types of the supported LIFX devices:
| Device Type | Thing Type |
|------------------------------|--------------|
| Original 1000 | colorlight |
| Color 650 | colorlight |
| Color 1000 | colorlight |
| Color 1000 BR30 | colorlight |
| LIFX A19 | colorlight |
| LIFX BR30 | colorlight |
| LIFX Candle | colorlight |
| LIFX Downlight | colorlight |
| LIFX GU10 | colorlight |
| LIFX Mini Color | colorlight |
| | |
| LIFX+ A19 | colorirlight |
| LIFX+ BR30 | colorirlight |
| | |
| LIFX Beam | colormzlight |
| LIFX Z | colormzlight |
| | |
| LIFX Tile | tilelight |
| | |
| White 800 (Low Voltage) | whitelight |
| White 800 (High Voltage) | whitelight |
| White 900 BR30 (Low Voltage) | whitelight |
| LIFX Candle Warm to White | whitelight |
| LIFX Filament | whitelight |
| LIFX Mini Day and Dusk | whitelight |
| LIFX Mini White | whitelight |
The thing type determines the capability of a device and with that the possible ways of interacting with it.
The following matrix lists the capabilities (channels) for each type:
| Thing Type | On/Off | Brightness | Color | Color Zone | Color Temperature | Color Temperature Zone | Infrared | Tile Effects |
|--------------|:------:|:----------:|:-----:|:----------:|:-----------------:|:----------------------:|:--------:|:------------:|
| colorlight | X | | X | | X | | | |
| colorirlight | X | | X | | X | | X | |
| colormzlight | X | | X | X | X | X | | |
| tilelight | X | X | X | | X | | | X |
| whitelight | X | X | | | X | | | |
## Discovery
The binding is able to auto-discover all lights in a network over the LIFX UDP protocol.
Therefore all lights must be turned on.
*Note:* To get the binding working, all lights must be added to the WLAN network first with the help of the [LIFX smart phone applications](https://www.lifx.com/pages/app).
The binding is NOT able to add or detect lights outside the network.
## Thing Configuration
Each light needs a Device ID or Host as a configuration parameter.
The device ID is printed as a serial number on the light and can also be found within the native LIFX Android or iOS application.
But usually the discovery works quite reliably, so that a manual configuration is not needed.
However, in the thing file, a manual configuration looks e.g. like
```
Thing lifx:colorlight:living [ deviceId="D073D5A1A1A1", fadetime=200 ]
```
The *fadetime* is an optional thing configuration parameter which configures the time to fade to a new color value (in ms).
When the *fadetime* is not configured, the binding uses 300ms as default.
You can optionally also configure a fixed Host or IP address when lights are in a different subnet and are not discovered.
```
Thing lifx:colorirlight:porch [ host="10.120.130.4", fadetime=0 ]
```
## Channels
All devices support some of the following channels:
| Channel Type ID | Item Type | Description | Thing Types |
|-----------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
| brightness | Dimmer | This channel supports adjusting the brightness value. | whitelight |
| color | Color | This channel supports full color control with hue, saturation and brightness values. | colorlight, colorirlight, colormzlight, tile |
| colorzone | Color | This channel supports full zone color control with hue, saturation and brightness values. | colormzlight |
| effect | String | This channel represents a type of light effect (e.g. for tile light: off, morph, flame) | tilelight |
| infrared | Dimmer | This channel supports adjusting the infrared value. *Note:* IR capable lights only activate their infrared LEDs when the brightness drops below a certain level. | colorirlight |
| signalstrength | Number | This channel represents signal strength with values 0, 1, 2, 3 or 4; 0 being worst strength and 4 being best strength. | colorlight, colorirlight, colormzlight, whitelight, tile |
| temperature | Dimmer | This channel supports adjusting the color temperature from cold (0%) to warm (100%). | colorlight, colorirlight, colormzlight, whitelight, tile |
| temperaturezone | Dimmer | This channel supports adjusting the zone color temperature from cold (0%) to warm (100%). | colormzlight |
The *color* and *brightness* channels have a "Power on brightness" configuration option that is used to determine the brightness when a light is switched on.
When it is left empty, the brightness of a light remains unchanged when a light is switched on or off.
The *color* channels have a "Power on color" configuration option that is used to determine the hue, saturation, brightness levels when a light is switched on.
When it is left empty, the color of a light remains unchanged when a light is switched on or off.
Configuration options contains 3 comma separated values, where first value is hue (0-360), second saturation (0-100) and third brightness (0-100).
If both "Power on brightness" and "Power on color" configuration options are defined, "Power on brightness" option overrides the brightness level defined on the "Power on color" configuration option.
The *temperature* channels have a "Power on temperature" configuration option that is used to determine the color temperature when a light is switched on. When it is left empty, the color temperature of a light remains unchanged when a light is switched on or off.
MultiZone lights (*colormzlight*) have serveral channels (e.g. *colorzone0*, *temperaturezone0*, etc.) that allow for controlling specific zones of the light.
Changing the *color* and *temperature* channels will update the states of all zones.
The *color* and *temperature* channels of MultiZone lights always return the same state as *colorzone0*, *temperaturezone0*.
LIFX Tile (*tilelight*) supports special tile effects: morph and flame.
These effects are predefined to their appearance using LIFX application.
Each effect has a separate speed configurable.
## Full Example
In this example **living** is a Color 1000 light that has a *colorlight* thing type which supports *color* and *temperature* channels.
The **porch** light is a LIFX+ BR30 that has a *colorirlight* thing type which supports *color*, *temperature* and *infrared* channels.
The **ceiling** light is a LIFX Z with 2 strips (16 zones) that has a *colormzlight* thing type which supports *color*, *colorzone*, *temperature* and *temperaturezone* channels.
Finally, **kitchen** is a White 800 (Low Voltage) light that has a *whitelight* thing type which supports *brightness* and *temperature* channels.
Either create a single *Color* item linked to the *color* channel and define *Switch*, *Slider* and *Colorpicker* entries with this item in the sitemap.
Or create items for each type (*Color*, *Switch*, *Dimmer*) and define the correspondent entries in the sitemap.
### demo.things:
```
Thing lifx:colorlight:living [ deviceId="D073D5A1A1A1" ]
Thing lifx:colorlight:living2 [ deviceId="D073D5A2A2A2" ] {
Channels:
Type color : color [ powerOnBrightness=50 ]
}
Thing lifx:colorirlight:porch [ deviceId="D073D5B2B2B2", host="10.120.130.4", fadetime=0 ] {
Channels:
Type color : color [ powerOnBrightness=75 ]
}
Thing lifx:colorirlight:porch [ deviceId="D073D5B2B2B2", host="10.120.130.4", fadetime=0 ] {
Channels:
Type temperature : temperature [ powerOnTemperature=20 ]
}
Thing lifx:colorirlight:porch [ deviceId="D073D5B2B2B2", host="10.120.130.4", fadetime=0 ] {
Channels:
Type color : color [ powerOnColor="120,100,50" ] // Deep green, 50% brightness
}
Thing lifx:colormzlight:ceiling [ host="10.120.130.5" ]
Thing lifx:whitelight:kitchen [ deviceId="D073D5D4D4D4", fadetime=150 ]
```
### demo.items:
```
// Living
Color Living_Color { channel="lifx:colorlight:living:color" }
Dimmer Living_Temperature { channel="lifx:colorlight:living:temperature" }
// Living2 (alternative approach)
Color Living2_Color { channel="lifx:colorlight:living2:color" }
Switch Living2_Switch { channel="lifx:colorlight:living2:color" }
Dimmer Living2_Dimmer { channel="lifx:colorlight:living2:color" }
Dimmer Living2_Temperature { channel="lifx:colorlight:living2:temperature" }
// Porch
Color Porch_Color { channel="lifx:colorirlight:porch:color" }
Dimmer Porch_Infrared { channel="lifx:colorirlight:porch:infrared" }
Dimmer Porch_Temperature { channel="lifx:colorirlight:porch:temperature" }
Number Porch_Signal_Strength { channel="lifx:colorirlight:porch:signalstrength" }
// Ceiling
Color Ceiling_Color { channel="lifx:colormzlight:ceiling:color" }
Dimmer Ceiling_Temperature { channel="lifx:colormzlight:ceiling:temperature" }
Color Ceiling_Color_Zone_0 { channel="lifx:colormzlight:ceiling:colorzone0" }
Dimmer Ceiling_Temperature_Zone_0 { channel="lifx:colormzlight:ceiling:temperaturezone0" }
Color Ceiling_Color_Zone_15 { channel="lifx:colormzlight:ceiling:colorzone15" }
Dimmer Ceiling_Temperature_Zone_15 { channel="lifx:colormzlight:ceiling:temperaturezone15" }
// Kitchen
Switch Kitchen_Toggle { channel="lifx:whitelight:kichen:brightness" }
Dimmer Kitchen_Brightness { channel="lifx:whitelight:kitchen:brightness" }
Dimmer Kitchen_Temperature { channel="lifx:whitelight:kitchen:temperature" }
```
### demo.sitemap:
```
sitemap demo label="Main Menu"
{
Frame label="Living" {
Switch item=Living_Color
Slider item=Living_Color
Colorpicker item=Living_Color
Slider item=Living_Temperature
}
Frame label="Living2" {
Switch item=Living2_Toggle
Slider item=Living2_Dimmer
Colorpicker item=Living2_Color
Slider item=Living2_Temperature
}
Frame label="Porch" {
Switch item=Porch_Color
Slider item=Porch_Color
Colorpicker item=Porch_Color
Slider item=Porch_Temperature
Slider item=Porch_Infrared
Text item=Porch_Signal_Strength
}
Frame label="Ceiling" {
Switch item=Ceiling_Color
Slider item=Ceiling_Color
Colorpicker item=Ceiling_Color
Slider item=Ceiling_Temperature
Colorpicker item=Ceiling_Color_Zone_0
Slider item=Ceiling_Temperature_Zone_0
Colorpicker item=Ceiling_Color_Zone_15
Slider item=Ceiling_Temperature_Zone_15
}
Frame label="Kitchen" {
Switch item=Kitchen_Toggle
Slider item=Kitchen_Brightness
Slider item=Kitchen_Temperature
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.lifx</artifactId>
<name>openHAB Add-ons :: Bundles :: LIFX Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.lifx-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-lifx" description="LIFX Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.lifx/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link LifxBinding} class defines common constants, which are used across
* the whole binding.
*
* @author Dennis Nobel - Initial contribution
* @author Wouter Born - Added packet interval, power on brightness constants
*/
@NonNullByDefault
public class LifxBindingConstants {
public static final String BINDING_ID = "lifx";
// The LIFX LAN Protocol Specification states that lights can process up to 20 messages per second, not more.
public static final long PACKET_INTERVAL = 50;
// Port constants
public static final int BROADCAST_PORT = 56700;
public static final int UNICAST_PORT = 56700;
// Minimum and maximum of MultiZone light indices
public static final int MIN_ZONE_INDEX = 0;
public static final int MAX_ZONE_INDEX = 255;
// Fallback light state defaults
public static final HSBK DEFAULT_COLOR = new HSBK(HSBType.WHITE, 3000);
public static final PercentType DEFAULT_BRIGHTNESS = PercentType.HUNDRED;
// List of all Channel IDs
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_ZONE = "colorzone";
public static final String CHANNEL_EFFECT = "effect";
public static final String CHANNEL_INFRARED = "infrared";
public static final String CHANNEL_SIGNAL_STRENGTH = "signalstrength";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_TEMPERATURE_ZONE = "temperaturezone";
// List of all Channel Type UIDs
public static final ChannelTypeUID CHANNEL_TYPE_BRIGHTNESS = new ChannelTypeUID(BINDING_ID, CHANNEL_BRIGHTNESS);
public static final ChannelTypeUID CHANNEL_TYPE_COLOR = new ChannelTypeUID(BINDING_ID, CHANNEL_COLOR);
public static final ChannelTypeUID CHANNEL_TYPE_COLOR_ZONE = new ChannelTypeUID(BINDING_ID, CHANNEL_COLOR_ZONE);
public static final ChannelTypeUID CHANNEL_TYPE_EFFECT = new ChannelTypeUID(BINDING_ID, CHANNEL_EFFECT);
public static final ChannelTypeUID CHANNEL_TYPE_INFRARED = new ChannelTypeUID(BINDING_ID, CHANNEL_INFRARED);
public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID(BINDING_ID, CHANNEL_TEMPERATURE);
public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE_ZONE = new ChannelTypeUID(BINDING_ID,
CHANNEL_TEMPERATURE_ZONE);
// List of options for effect channel
public static final String CHANNEL_TYPE_EFFECT_OPTION_OFF = "off";
public static final String CHANNEL_TYPE_EFFECT_OPTION_MORPH = "morph";
public static final String CHANNEL_TYPE_EFFECT_OPTION_FLAME = "flame";
// Config property for the LIFX device id
public static final String CONFIG_PROPERTY_DEVICE_ID = "deviceId";
public static final String CONFIG_PROPERTY_FADETIME = "fadetime";
// Config property for channel configuration
public static final String CONFIG_PROPERTY_POWER_ON_BRIGHTNESS = "powerOnBrightness";
public static final String CONFIG_PROPERTY_POWER_ON_COLOR = "powerOnColor";
public static final String CONFIG_PROPERTY_POWER_ON_TEMPERATURE = "powerOnTemperature";
public static final String CONFIG_PROPERTY_EFFECT_MORPH_SPEED = "effectMorphSpeed";
public static final String CONFIG_PROPERTY_EFFECT_FLAME_SPEED = "effectFlameSpeed";
// Property keys
public static final String PROPERTY_HOST = "host";
public static final String PROPERTY_HOST_VERSION = "hostVersion";
public static final String PROPERTY_MAC_ADDRESS = "macAddress";
public static final String PROPERTY_PRODUCT_ID = "productId";
public static final String PROPERTY_PRODUCT_NAME = "productName";
public static final String PROPERTY_PRODUCT_VERSION = "productVersion";
public static final String PROPERTY_VENDOR_ID = "vendorId";
public static final String PROPERTY_VENDOR_NAME = "vendorName";
public static final String PROPERTY_WIFI_VERSION = "wifiVersion";
public static final String PROPERTY_ZONES = "zones";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_COLORLIGHT = new ThingTypeUID(BINDING_ID, "colorlight");
public static final ThingTypeUID THING_TYPE_COLORIRLIGHT = new ThingTypeUID(BINDING_ID, "colorirlight");
public static final ThingTypeUID THING_TYPE_COLORMZLIGHT = new ThingTypeUID(BINDING_ID, "colormzlight");
public static final ThingTypeUID THING_TYPE_WHITELIGHT = new ThingTypeUID(BINDING_ID, "whitelight");
public static final ThingTypeUID THING_TYPE_TILELIGHT = new ThingTypeUID(BINDING_ID, "tilelight");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream.of(THING_TYPE_COLORLIGHT,
THING_TYPE_COLORIRLIGHT, THING_TYPE_COLORMZLIGHT, THING_TYPE_WHITELIGHT, THING_TYPE_TILELIGHT)
.collect(Collectors.toSet());
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ThingUID;
/**
* The {@link LifxChannelFactory} creates dynamic LIFX channels.
*
* @author Wouter Born - Add i18n support
*/
@NonNullByDefault
public interface LifxChannelFactory {
Channel createColorZoneChannel(ThingUID thingUID, int index);
Channel createTemperatureZoneChannel(ThingUID thingUID, int index);
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link LifxChannelFactoryImpl} creates dynamic LIFX channels.
*
* @author Wouter Born - Add i18n support
*/
@NonNullByDefault
@Component(service = LifxChannelFactory.class, immediate = true)
public class LifxChannelFactoryImpl implements LifxChannelFactory {
private static final String COLOR_ZONE_LABEL_KEY = "channel-type.lifx.colorzone.label";
private static final String COLOR_ZONE_DESCRIPTION_KEY = "channel-type.lifx.colorzone.description";
private static final String TEMPERATURE_ZONE_LABEL_KEY = "channel-type.lifx.temperaturezone.label";
private static final String TEMPERATURE_ZONE_DESCRIPTION_KEY = "channel-type.lifx.temperaturezone.description";
private @NonNullByDefault({}) Bundle bundle;
private @NonNullByDefault({}) TranslationProvider i18nProvider;
private @NonNullByDefault({}) LocaleProvider localeProvider;
@Override
public Channel createColorZoneChannel(ThingUID thingUID, int index) {
String label = getText(COLOR_ZONE_LABEL_KEY, index);
String description = getText(COLOR_ZONE_DESCRIPTION_KEY, index);
return ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_COLOR_ZONE + index), "Color")
.withType(CHANNEL_TYPE_COLOR_ZONE).withLabel(label).withDescription(description).build();
}
@Override
public Channel createTemperatureZoneChannel(ThingUID thingUID, int index) {
String label = getText(TEMPERATURE_ZONE_LABEL_KEY, index);
String description = getText(TEMPERATURE_ZONE_DESCRIPTION_KEY, index);
return ChannelBuilder.create(new ChannelUID(thingUID, CHANNEL_TEMPERATURE_ZONE + index), "Dimmer")
.withType(CHANNEL_TYPE_TEMPERATURE_ZONE).withLabel(label).withDescription(description).build();
}
private @Nullable String getDefaultText(String key) {
return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
}
private String getText(String key, Object... arguments) {
Locale locale = localeProvider != null ? localeProvider.getLocale() : Locale.ENGLISH;
if (i18nProvider == null) {
return key;
}
String text = i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
return text != null ? text : key;
}
@Activate
protected void activate(ComponentContext componentContext) {
this.bundle = componentContext.getBundleContext().getBundle();
}
@Deactivate
protected void deactivate(ComponentContext componentContext) {
this.bundle = null;
}
@Reference
protected void setTranslationProvider(TranslationProvider i18nProvider) {
this.i18nProvider = i18nProvider;
}
protected void unsetTranslationProvider(TranslationProvider i18nProvider) {
this.i18nProvider = null;
}
@Reference
protected void setLocaleProvider(LocaleProvider localeProvider) {
this.localeProvider = localeProvider;
}
protected void unsetLocaleProvider(LocaleProvider localeProvider) {
this.localeProvider = null;
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.SUPPORTED_THING_TYPES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link LifxHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Dennis Nobel - Initial contribution
* @author Karel Goderis - Remove dependency on external libraries
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.lifx")
public class LifxHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) LifxChannelFactory channelFactory;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (supportsThingType(thing.getThingTypeUID())) {
return new LifxLightHandler(thing, channelFactory);
}
return null;
}
@Override
protected void deactivate(ComponentContext componentContext) {
super.deactivate(componentContext);
}
@Reference
protected void setChannelFactory(LifxChannelFactory channelFactory) {
this.channelFactory = channelFactory;
}
protected void unsetChannelFactory(LifxChannelFactory channelFactory) {
this.channelFactory = null;
}
}

View File

@@ -0,0 +1,283 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.PACKET_INTERVAL;
import static org.openhab.binding.lifx.internal.fields.MACAddress.BROADCAST_ADDRESS;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.randomSourceId;
import static org.openhab.binding.lifx.internal.util.LifxSelectorUtil.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.MACAddress;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
import org.openhab.binding.lifx.internal.listener.LifxResponsePacketListener;
import org.openhab.binding.lifx.internal.protocol.GetServiceRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.StateServiceResponse;
import org.openhab.binding.lifx.internal.util.LifxNetworkUtil;
import org.openhab.binding.lifx.internal.util.LifxSelectorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightCommunicationHandler} is responsible for the communications with a light.
*
* @author Wouter Born - Extracted class from LifxLightHandler
*/
@NonNullByDefault
public class LifxLightCommunicationHandler {
private final Logger logger = LoggerFactory.getLogger(LifxLightCommunicationHandler.class);
private final String logId;
private final CurrentLightState currentLightState;
private final ScheduledExecutorService scheduler;
private final ReentrantLock lock = new ReentrantLock();
private final long sourceId = randomSourceId();
private final Supplier<Integer> sequenceNumberSupplier = new LifxSequenceNumberSupplier();
private int service;
private int unicastPort;
private final int broadcastPort = LifxNetworkUtil.getNewBroadcastPort();
private @Nullable ScheduledFuture<?> networkJob;
private @Nullable MACAddress macAddress;
private @Nullable InetSocketAddress host;
private boolean broadcastEnabled;
private @Nullable Selector selector;
private @Nullable SelectionKey broadcastKey;
private @Nullable SelectionKey unicastKey;
private @Nullable LifxSelectorContext selectorContext;
public LifxLightCommunicationHandler(LifxLightContext context) {
this.logId = context.getLogId();
this.macAddress = context.getConfiguration().getMACAddress();
this.host = context.getConfiguration().getHost();
this.currentLightState = context.getCurrentLightState();
this.scheduler = context.getScheduler();
this.broadcastEnabled = context.getConfiguration().getHost() == null;
}
private List<LifxResponsePacketListener> responsePacketListeners = new CopyOnWriteArrayList<>();
public void addResponsePacketListener(LifxResponsePacketListener listener) {
responsePacketListeners.add(listener);
}
public void removeResponsePacketListener(LifxResponsePacketListener listener) {
responsePacketListeners.remove(listener);
}
public void start() {
try {
lock.lock();
logger.debug("{} : Starting communication handler", logId);
logger.debug("{} : Using '{}' as source identifier", logId, Long.toString(sourceId, 16));
ScheduledFuture<?> localNetworkJob = networkJob;
if (localNetworkJob == null || localNetworkJob.isCancelled()) {
networkJob = scheduler.scheduleWithFixedDelay(this::receiveAndHandlePackets, 0, PACKET_INTERVAL,
TimeUnit.MILLISECONDS);
}
currentLightState.setOffline();
Selector localSelector = Selector.open();
selector = localSelector;
if (isBroadcastEnabled()) {
broadcastKey = openBroadcastChannel(selector, logId, broadcastPort);
selectorContext = new LifxSelectorContext(localSelector, sourceId, sequenceNumberSupplier, logId, host,
macAddress, broadcastKey, unicastKey);
broadcastPacket(new GetServiceRequest());
} else {
unicastKey = openUnicastChannel(selector, logId, host);
selectorContext = new LifxSelectorContext(localSelector, sourceId, sequenceNumberSupplier, logId, host,
macAddress, broadcastKey, unicastKey);
sendPacket(new GetServiceRequest());
}
} catch (IOException e) {
logger.error("{} while starting LIFX communication handler for light '{}' : {}",
e.getClass().getSimpleName(), logId, e.getMessage(), e);
} finally {
lock.unlock();
}
}
public void stop() {
try {
lock.lock();
ScheduledFuture<?> localNetworkJob = networkJob;
if (localNetworkJob != null && !localNetworkJob.isCancelled()) {
localNetworkJob.cancel(true);
networkJob = null;
}
closeSelector(selector, logId);
selector = null;
broadcastKey = null;
unicastKey = null;
selectorContext = null;
} finally {
lock.unlock();
}
}
public @Nullable InetSocketAddress getIpAddress() {
return host;
}
public @Nullable MACAddress getMACAddress() {
return macAddress;
}
public void receiveAndHandlePackets() {
try {
lock.lock();
Selector localSelector = selector;
if (localSelector == null || !localSelector.isOpen()) {
logger.debug("{} : Unable to receive and handle packets with null or closed selector", logId);
} else {
LifxSelectorUtil.receiveAndHandlePackets(localSelector, logId,
(packet, address) -> handlePacket(packet, address));
}
} catch (Exception e) {
logger.error("{} while receiving a packet from the light ({}): {}", e.getClass().getSimpleName(), logId,
e.getMessage());
} finally {
lock.unlock();
}
}
private void handlePacket(Packet packet, InetSocketAddress address) {
boolean packetFromConfiguredMAC = macAddress != null && (packet.getTarget().equals(macAddress));
boolean packetFromConfiguredHost = host != null && (address.equals(host));
boolean broadcastPacket = packet.getTarget().equals(BROADCAST_ADDRESS);
boolean packetSourceIsHandler = (packet.getSource() == sourceId || packet.getSource() == 0);
if ((packetFromConfiguredMAC || packetFromConfiguredHost || broadcastPacket) && packetSourceIsHandler) {
logger.trace("{} : Packet type '{}' received from '{}' for '{}' with sequence '{}' and source '{}'",
new Object[] { logId, packet.getClass().getSimpleName(), address.toString(),
packet.getTarget().getHex(), packet.getSequence(), Long.toString(packet.getSource(), 16) });
if (packet instanceof StateServiceResponse) {
StateServiceResponse response = (StateServiceResponse) packet;
MACAddress discoveredAddress = response.getTarget();
if (packetFromConfiguredHost && macAddress == null) {
macAddress = discoveredAddress;
currentLightState.setOnline(discoveredAddress);
LifxSelectorContext context = selectorContext;
if (context != null) {
context.setMACAddress(macAddress);
}
return;
} else if (macAddress != null && macAddress.equals(discoveredAddress)) {
boolean newHost = host == null || !address.equals(host);
boolean newPort = unicastPort != (int) response.getPort();
boolean newService = service != response.getService();
if (newHost || newPort || newService || currentLightState.isOffline()) {
this.unicastPort = (int) response.getPort();
this.service = response.getService();
if (unicastPort == 0) {
logger.warn("Light ({}) service with ID '{}' is currently not available", logId, service);
currentLightState.setOfflineByCommunicationError();
} else {
this.host = new InetSocketAddress(address.getAddress(), unicastPort);
try {
cancelKey(unicastKey, logId);
unicastKey = openUnicastChannel(selector, logId, host);
LifxSelectorContext context = selectorContext;
if (context != null) {
context.setHost(host);
context.setUnicastKey(unicastKey);
}
} catch (IOException e) {
logger.warn("{} while opening the unicast channel of the light ({}): {}",
e.getClass().getSimpleName(), logId, e.getMessage());
currentLightState.setOfflineByCommunicationError();
return;
}
currentLightState.setOnline();
}
}
}
}
// Listeners are notified in a separate thread for better concurrency and to prevent deadlock.
scheduler.schedule(() -> {
responsePacketListeners.forEach(listener -> listener.handleResponsePacket(packet));
}, 0, TimeUnit.MILLISECONDS);
}
}
public boolean isBroadcastEnabled() {
return broadcastEnabled;
}
public void broadcastPacket(Packet packet) {
wrappedPacketSend((s, p) -> LifxSelectorUtil.broadcastPacket(s, p), packet);
}
public void sendPacket(Packet packet) {
if (host != null) {
wrappedPacketSend((s, p) -> LifxSelectorUtil.sendPacket(s, p), packet);
}
}
public void resendPacket(Packet packet) {
if (host != null) {
wrappedPacketSend((s, p) -> LifxSelectorUtil.resendPacket(s, p), packet);
}
}
private void wrappedPacketSend(BiFunction<LifxSelectorContext, Packet, Boolean> function, Packet packet) {
LifxSelectorContext localSelectorContext = selectorContext;
if (localSelectorContext != null) {
boolean result = false;
try {
lock.lock();
result = function.apply(localSelectorContext, packet);
} finally {
lock.unlock();
if (!result) {
currentLightState.setOfflineByCommunicationError();
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.net.InetSocketAddress;
import java.time.Duration;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.MACAddress;
/**
* Configuration class for LIFX lights.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class LifxLightConfig {
private @Nullable String deviceId;
private @Nullable String host;
private long fadetime = 300; // milliseconds
public @Nullable MACAddress getMACAddress() {
String localDeviceId = deviceId;
return localDeviceId == null ? null : new MACAddress(localDeviceId, true);
}
public @Nullable InetSocketAddress getHost() {
return host == null ? null : new InetSocketAddress(host, LifxBindingConstants.UNICAST_PORT);
}
public Duration getFadeTime() {
return Duration.ofMillis(fadetime);
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
import org.openhab.binding.lifx.internal.protocol.Product;
/**
* The {@link LifxLightContext} shares the context of a light with {@link LifxLightHandler} helper objects.
*
* @author Wouter Born - Add optional host configuration parameter
*/
@NonNullByDefault
public class LifxLightContext {
private final String logId;
private final LifxLightConfig configuration;
private final CurrentLightState currentLightState;
private final LifxLightState pendingLightState;
private final Product product;
private final ScheduledExecutorService scheduler;
public LifxLightContext(String logId, Product product, LifxLightConfig configuration,
CurrentLightState currentLightState, LifxLightState pendingLightState, ScheduledExecutorService scheduler) {
this.logId = logId;
this.configuration = configuration;
this.product = product;
this.currentLightState = currentLightState;
this.pendingLightState = pendingLightState;
this.scheduler = scheduler;
}
public String getLogId() {
return logId;
}
public LifxLightConfig getConfiguration() {
return configuration;
}
public Product getProduct() {
return product;
}
public CurrentLightState getCurrentLightState() {
return currentLightState;
}
public LifxLightState getPendingLightState() {
return pendingLightState;
}
public ScheduledExecutorService getScheduler() {
return scheduler;
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.MIN_ZONE_INDEX;
import static org.openhab.binding.lifx.internal.protocol.Product.Feature.*;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.infraredToPercentType;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
import org.openhab.binding.lifx.internal.protocol.GetColorZonesRequest;
import org.openhab.binding.lifx.internal.protocol.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.protocol.GetRequest;
import org.openhab.binding.lifx.internal.protocol.GetTileEffectRequest;
import org.openhab.binding.lifx.internal.protocol.GetWifiInfoRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.Product;
import org.openhab.binding.lifx.internal.protocol.StateLightInfraredResponse;
import org.openhab.binding.lifx.internal.protocol.StateLightPowerResponse;
import org.openhab.binding.lifx.internal.protocol.StateMultiZoneResponse;
import org.openhab.binding.lifx.internal.protocol.StatePowerResponse;
import org.openhab.binding.lifx.internal.protocol.StateResponse;
import org.openhab.binding.lifx.internal.protocol.StateTileEffectResponse;
import org.openhab.binding.lifx.internal.protocol.StateWifiInfoResponse;
import org.openhab.core.library.types.PercentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightCurrentStateUpdater} sends packets to a light in order to update the {@code currentLightState} to
* the actual light state.
*
* @author Wouter Born - Extracted class from LifxLightHandler
*/
@NonNullByDefault
public class LifxLightCurrentStateUpdater {
private static final int STATE_POLLING_INTERVAL = 3;
private final Logger logger = LoggerFactory.getLogger(LifxLightCurrentStateUpdater.class);
private final String logId;
private final Product product;
private final CurrentLightState currentLightState;
private final ScheduledExecutorService scheduler;
private final LifxLightCommunicationHandler communicationHandler;
private final ReentrantLock lock = new ReentrantLock();
private boolean wasOnline;
private boolean updateSignalStrength;
private @Nullable ScheduledFuture<?> statePollingJob;
public LifxLightCurrentStateUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.product = context.getProduct();
this.currentLightState = context.getCurrentLightState();
this.scheduler = context.getScheduler();
this.communicationHandler = communicationHandler;
}
public void pollLightState() {
try {
lock.lock();
if (currentLightState.isOnline()) {
logger.trace("{} : Polling the state of the light", logId);
sendLightStateRequests();
} else {
logger.trace("{} : The light is not online, there is no point polling it", logId);
}
wasOnline = currentLightState.isOnline();
} catch (Exception e) {
logger.error("Error occurred while polling light state", e);
} finally {
lock.unlock();
}
}
public void setUpdateSignalStrength(boolean updateSignalStrength) {
this.updateSignalStrength = updateSignalStrength;
}
public void start() {
try {
lock.lock();
communicationHandler.addResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localStatePollingJob = statePollingJob;
if (localStatePollingJob == null || localStatePollingJob.isCancelled()) {
statePollingJob = scheduler.scheduleWithFixedDelay(this::pollLightState, 0, STATE_POLLING_INTERVAL,
TimeUnit.SECONDS);
}
} catch (Exception e) {
logger.error("Error occurred while starting light state updater", e);
} finally {
lock.unlock();
}
}
public void stop() {
try {
lock.lock();
communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localStatePollingJob = statePollingJob;
if (localStatePollingJob != null && !localStatePollingJob.isCancelled()) {
localStatePollingJob.cancel(true);
statePollingJob = null;
}
} catch (Exception e) {
logger.error("Error occurred while stopping light state updater", e);
} finally {
lock.unlock();
}
}
private void sendLightStateRequests() {
communicationHandler.sendPacket(new GetRequest());
if (product.hasFeature(INFRARED)) {
communicationHandler.sendPacket(new GetLightInfraredRequest());
}
if (product.hasFeature(MULTIZONE)) {
communicationHandler.sendPacket(new GetColorZonesRequest());
}
if (product.hasFeature(TILE_EFFECT)) {
communicationHandler.sendPacket(new GetTileEffectRequest());
}
if (updateSignalStrength) {
communicationHandler.sendPacket(new GetWifiInfoRequest());
}
}
public void handleResponsePacket(Packet packet) {
try {
lock.lock();
if (packet instanceof StateResponse) {
handleLightStatus((StateResponse) packet);
} else if (packet instanceof StatePowerResponse) {
handlePowerStatus((StatePowerResponse) packet);
} else if (packet instanceof StateLightPowerResponse) {
handleLightPowerStatus((StateLightPowerResponse) packet);
} else if (packet instanceof StateLightInfraredResponse) {
handleInfraredStatus((StateLightInfraredResponse) packet);
} else if (packet instanceof StateMultiZoneResponse) {
handleMultiZoneStatus((StateMultiZoneResponse) packet);
} else if (packet instanceof StateWifiInfoResponse) {
handleWifiInfoStatus((StateWifiInfoResponse) packet);
} else if (packet instanceof StateTileEffectResponse) {
handleTileEffectStatus((StateTileEffectResponse) packet);
}
currentLightState.setOnline();
if (currentLightState.isOnline() && !wasOnline) {
wasOnline = true;
logger.trace("{} : The light just went online, immediately polling the state of the light", logId);
sendLightStateRequests();
}
} finally {
lock.unlock();
}
}
private void handleLightStatus(StateResponse packet) {
currentLightState.setColor(packet.getColor(), MIN_ZONE_INDEX);
currentLightState.setPowerState(packet.getPower());
}
private void handlePowerStatus(StatePowerResponse packet) {
currentLightState.setPowerState(packet.getState());
}
private void handleLightPowerStatus(StateLightPowerResponse packet) {
currentLightState.setPowerState(packet.getState());
}
private void handleInfraredStatus(StateLightInfraredResponse packet) {
PercentType infrared = infraredToPercentType(packet.getInfrared());
currentLightState.setInfrared(infrared);
}
private void handleMultiZoneStatus(StateMultiZoneResponse packet) {
HSBK[] colors = currentLightState.getColors();
if (colors.length != packet.getCount()) {
colors = new HSBK[packet.getCount()];
}
for (int i = 0; i < packet.getColors().length && packet.getIndex() + i < colors.length; i++) {
colors[packet.getIndex() + i] = packet.getColors()[i];
}
currentLightState.setColors(colors);
}
private void handleWifiInfoStatus(StateWifiInfoResponse packet) {
currentLightState.setSignalStrength(packet.getSignalStrength());
}
private void handleTileEffectStatus(StateTileEffectResponse packet) {
currentLightState.setTileEffect(packet.getEffect());
}
}

View File

@@ -0,0 +1,357 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.randomSourceId;
import static org.openhab.binding.lifx.internal.util.LifxSelectorUtil.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.MACAddress;
import org.openhab.binding.lifx.internal.protocol.GetLabelRequest;
import org.openhab.binding.lifx.internal.protocol.GetServiceRequest;
import org.openhab.binding.lifx.internal.protocol.GetVersionRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.Product;
import org.openhab.binding.lifx.internal.protocol.StateLabelResponse;
import org.openhab.binding.lifx.internal.protocol.StateServiceResponse;
import org.openhab.binding.lifx.internal.protocol.StateVersionResponse;
import org.openhab.binding.lifx.internal.util.LifxSelectorUtil;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightDiscovery} provides support for auto-discovery of LIFX
* lights.
*
* @author Dennis Nobel - Initial contribution
* @author Karel Goderis - Rewrite for Firmware V2, and remove dependency on external libraries
* @author Wouter Born - Discover light labels, improve locking, optimize packet handling
*/
@Component(immediate = true, service = DiscoveryService.class, configurationPid = "discovery.lifx")
@NonNullByDefault
public class LifxLightDiscovery extends AbstractDiscoveryService {
private static final String LOG_ID = "Discovery";
private static final long REFRESH_INTERVAL = TimeUnit.MINUTES.toSeconds(1);
private static final long SELECTOR_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private final Logger logger = LoggerFactory.getLogger(LifxLightDiscovery.class);
private final Map<MACAddress, @Nullable DiscoveredLight> discoveredLights = new HashMap<>();
private final long sourceId = randomSourceId();
private final Supplier<Integer> sequenceNumberSupplier = new LifxSequenceNumberSupplier();
private @Nullable Selector selector;
private @Nullable SelectionKey broadcastKey;
private @Nullable ScheduledFuture<?> discoveryJob;
private @Nullable ScheduledFuture<?> networkJob;
private boolean isScanning = false;
private class DiscoveredLight {
private MACAddress macAddress;
private InetSocketAddress socketAddress;
private String logId;
private @Nullable String label;
private @Nullable Product product;
private long productVersion;
private boolean supportedProduct = true;
private LifxSelectorContext selectorContext;
private long lastRequestTimeMillis;
public DiscoveredLight(Selector lightSelector, MACAddress macAddress, InetSocketAddress socketAddress,
String logId, @Nullable SelectionKey unicastKey) {
this.macAddress = macAddress;
this.logId = logId;
this.socketAddress = socketAddress;
this.selectorContext = new LifxSelectorContext(lightSelector, sourceId, sequenceNumberSupplier, logId,
socketAddress, macAddress, broadcastKey, unicastKey);
}
public boolean isDataComplete() {
return label != null && product != null;
}
public void cancelUnicastKey() {
SelectionKey unicastKey = selectorContext.getUnicastKey();
if (unicastKey != null) {
cancelKey(unicastKey, selectorContext.getLogId());
}
}
}
public LifxLightDiscovery() throws IllegalArgumentException {
super(LifxBindingConstants.SUPPORTED_THING_TYPES, 1, true);
}
@Activate
@Override
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Modified
@Override
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
super.modified(configProperties);
}
@Deactivate
@Override
protected void deactivate() {
super.deactivate();
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting the LIFX device background discovery");
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
discoveryJob = scheduler.scheduleWithFixedDelay(this::doScan, 0, REFRESH_INTERVAL, TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping LIFX device background discovery");
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob != null && !localDiscoveryJob.isCancelled()) {
localDiscoveryJob.cancel(true);
discoveryJob = null;
}
ScheduledFuture<?> localNetworkJob = networkJob;
if (localNetworkJob != null && !localNetworkJob.isCancelled()) {
localNetworkJob.cancel(true);
networkJob = null;
}
}
@Override
protected void startScan() {
doScan();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
protected void doScan() {
try {
if (!isScanning) {
isScanning = true;
if (selector != null) {
closeSelector(selector, LOG_ID);
}
logger.debug("The LIFX discovery service will use '{}' as source identifier",
Long.toString(sourceId, 16));
Selector localSelector = Selector.open();
selector = localSelector;
broadcastKey = openBroadcastChannel(localSelector, LOG_ID, BROADCAST_PORT);
networkJob = scheduler.schedule(this::receiveAndHandlePackets, 0, TimeUnit.MILLISECONDS);
LifxSelectorContext selectorContext = new LifxSelectorContext(localSelector, sourceId,
sequenceNumberSupplier, LOG_ID, broadcastKey);
broadcastPacket(selectorContext, new GetServiceRequest());
} else {
logger.info("A discovery scan for LIFX lights is already underway");
}
} catch (IOException e) {
logger.debug("{} while discovering LIFX lights : {}", e.getClass().getSimpleName(), e.getMessage());
isScanning = false;
}
}
public void receiveAndHandlePackets() {
Selector localSelector = selector;
try {
if (localSelector == null || !localSelector.isOpen()) {
logger.debug("Unable to receive and handle packets with null or closed selector");
return;
}
discoveredLights.clear();
logger.trace("Entering read loop");
long startStamp = System.currentTimeMillis();
while (System.currentTimeMillis() - startStamp < SELECTOR_TIMEOUT) {
int lightCount = discoveredLights.size();
long selectStamp = System.currentTimeMillis();
LifxSelectorUtil.receiveAndHandlePackets(localSelector, LOG_ID,
(packet, address) -> handlePacket(packet, address));
requestAdditionalLightData();
boolean discoveredNewLights = lightCount < discoveredLights.size();
if (!discoveredNewLights) {
boolean preventBusyWaiting = System.currentTimeMillis() - selectStamp < PACKET_INTERVAL;
if (preventBusyWaiting) {
Thread.sleep(PACKET_INTERVAL);
}
}
}
logger.trace("Exited read loop");
} catch (Exception e) {
logger.debug("{} while receiving and handling discovery packets: {}", e.getClass().getSimpleName(),
e.getMessage(), e);
} finally {
LifxSelectorUtil.closeSelector(localSelector, LOG_ID);
selector = null;
isScanning = false;
}
}
private void requestAdditionalLightData() {
// Iterate through the discovered lights that have to be set up, and the packets that have to be sent
// Workaround to avoid a ConcurrentModifictionException on the selector.SelectedKeys() Set
for (DiscoveredLight light : discoveredLights.values()) {
if (light == null) {
continue;
}
boolean waitingForLightResponse = System.currentTimeMillis() - light.lastRequestTimeMillis < 200;
if (light.supportedProduct && !light.isDataComplete() && !waitingForLightResponse) {
if (light.product == null) {
sendPacket(light.selectorContext, new GetVersionRequest());
}
if (light.label == null) {
sendPacket(light.selectorContext, new GetLabelRequest());
}
light.lastRequestTimeMillis = System.currentTimeMillis();
}
}
}
private void handlePacket(Packet packet, InetSocketAddress address) {
logger.trace("Discovery : Packet type '{}' received from '{}' for '{}' with sequence '{}' and source '{}'",
new Object[] { packet.getClass().getSimpleName(), address.toString(), packet.getTarget().getHex(),
packet.getSequence(), Long.toString(packet.getSource(), 16) });
if (packet.getSource() == sourceId || packet.getSource() == 0) {
MACAddress macAddress = packet.getTarget();
DiscoveredLight light = discoveredLights.get(macAddress);
if (packet instanceof StateServiceResponse) {
int port = (int) ((StateServiceResponse) packet).getPort();
if (port != 0) {
try {
InetSocketAddress socketAddress = new InetSocketAddress(address.getAddress(), port);
if (light == null || (!socketAddress.equals(light.socketAddress))) {
if (light != null) {
light.cancelUnicastKey();
}
Selector lightSelector = selector;
if (lightSelector != null) {
String logId = getLogId(macAddress, socketAddress);
light = new DiscoveredLight(lightSelector, macAddress, socketAddress, logId,
openUnicastChannel(lightSelector, logId, socketAddress));
discoveredLights.put(macAddress, light);
}
}
} catch (Exception e) {
logger.warn("{} while connecting to IP address: {}", e.getClass().getSimpleName(),
e.getMessage());
return;
}
}
} else if (light != null) {
if (packet instanceof StateLabelResponse) {
light.label = ((StateLabelResponse) packet).getLabel().trim();
} else if (packet instanceof StateVersionResponse) {
try {
light.product = Product.getProductFromProductID(((StateVersionResponse) packet).getProduct());
light.productVersion = ((StateVersionResponse) packet).getVersion();
} catch (IllegalArgumentException e) {
logger.debug("Discovered an unsupported light ({}): {}", light.macAddress.getAsLabel(),
e.getMessage());
light.supportedProduct = false;
}
}
}
if (light != null && light.isDataComplete()) {
try {
thingDiscovered(createDiscoveryResult(light));
} catch (IllegalArgumentException e) {
logger.trace("{} while creating discovery result of light ({})", e.getClass().getSimpleName(),
light.logId, e);
}
}
}
}
private DiscoveryResult createDiscoveryResult(DiscoveredLight light) throws IllegalArgumentException {
Product product = light.product;
if (product == null) {
throw new IllegalArgumentException("Product of discovered light is null");
}
String macAsLabel = light.macAddress.getAsLabel();
ThingUID thingUID = new ThingUID(product.getThingTypeUID(), macAsLabel);
String label = light.label;
if (StringUtils.isBlank(label)) {
label = product.getName();
}
logger.trace("Discovered a LIFX light: {}", label);
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(thingUID);
builder.withRepresentationProperty(LifxBindingConstants.PROPERTY_MAC_ADDRESS);
builder.withLabel(label);
builder.withProperty(LifxBindingConstants.CONFIG_PROPERTY_DEVICE_ID, macAsLabel);
builder.withProperty(LifxBindingConstants.PROPERTY_MAC_ADDRESS, macAsLabel);
builder.withProperty(LifxBindingConstants.PROPERTY_PRODUCT_ID, product.getID());
builder.withProperty(LifxBindingConstants.PROPERTY_PRODUCT_NAME, product.getName());
builder.withProperty(LifxBindingConstants.PROPERTY_PRODUCT_VERSION, light.productVersion);
builder.withProperty(LifxBindingConstants.PROPERTY_VENDOR_ID, product.getVendor().getID());
builder.withProperty(LifxBindingConstants.PROPERTY_VENDOR_NAME, product.getVendor().getName());
return builder.build();
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
import org.openhab.binding.lifx.internal.protocol.GetEchoRequest;
import org.openhab.binding.lifx.internal.protocol.GetServiceRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightOnlineStateUpdater} sets the state of a light offline when it no longer responds to echo packets.
*
* @author Wouter Born - Extracted class from LifxLightHandler
*/
@NonNullByDefault
public class LifxLightOnlineStateUpdater {
private static final int ECHO_POLLING_INTERVAL = 15;
private static final int MAXIMUM_POLLING_RETRIES = 3;
private final Logger logger = LoggerFactory.getLogger(LifxLightOnlineStateUpdater.class);
private final String logId;
private final CurrentLightState currentLightState;
private final ScheduledExecutorService scheduler;
private final LifxLightCommunicationHandler communicationHandler;
private final ReentrantLock lock = new ReentrantLock();
private @Nullable ScheduledFuture<?> echoJob;
private LocalDateTime lastSeen = LocalDateTime.MIN;
private int unansweredEchoPackets;
public LifxLightOnlineStateUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.scheduler = context.getScheduler();
this.currentLightState = context.getCurrentLightState();
this.communicationHandler = communicationHandler;
}
public void sendEchoPackets() {
try {
lock.lock();
logger.trace("{} : Polling light state", logId);
if (currentLightState.isOnline()) {
if (Duration.between(lastSeen, LocalDateTime.now()).getSeconds() > ECHO_POLLING_INTERVAL) {
if (unansweredEchoPackets < MAXIMUM_POLLING_RETRIES) {
communicationHandler.sendPacket(GetEchoRequest.currentTimeEchoRequest());
unansweredEchoPackets++;
} else {
currentLightState.setOfflineByCommunicationError();
unansweredEchoPackets = 0;
}
}
} else {
if (communicationHandler.isBroadcastEnabled()) {
logger.trace("{} : Light is not online, broadcasting request", logId);
communicationHandler.broadcastPacket(new GetServiceRequest());
} else {
logger.trace("{} : Light is not online, unicasting request", logId);
communicationHandler.sendPacket(new GetServiceRequest());
}
}
} catch (Exception e) {
logger.error("Error occurred while polling the online state of a light ({})", logId, e);
} finally {
lock.unlock();
}
}
public void start() {
try {
lock.lock();
communicationHandler.addResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localEchoJob = echoJob;
if (localEchoJob == null || localEchoJob.isCancelled()) {
echoJob = scheduler.scheduleWithFixedDelay(this::sendEchoPackets, 0, ECHO_POLLING_INTERVAL,
TimeUnit.SECONDS);
}
} catch (Exception e) {
logger.error("Error occurred while starting online state poller for a light ({})", logId, e);
} finally {
lock.unlock();
}
}
public void stop() {
try {
lock.lock();
communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localEchoJob = echoJob;
if (localEchoJob != null && !localEchoJob.isCancelled()) {
localEchoJob.cancel(true);
echoJob = null;
}
} catch (Exception e) {
logger.error("Error occurred while stopping online state poller for a light ({})", logId, e);
} finally {
lock.unlock();
}
}
public void handleResponsePacket(Packet packet) {
lastSeen = LocalDateTime.now();
unansweredEchoPackets = 0;
currentLightState.setOnline();
}
}

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.MACAddress;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
import org.openhab.binding.lifx.internal.listener.LifxPropertiesUpdateListener;
import org.openhab.binding.lifx.internal.protocol.GetHostFirmwareRequest;
import org.openhab.binding.lifx.internal.protocol.GetVersionRequest;
import org.openhab.binding.lifx.internal.protocol.GetWifiFirmwareRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.Product;
import org.openhab.binding.lifx.internal.protocol.StateHostFirmwareResponse;
import org.openhab.binding.lifx.internal.protocol.StateVersionResponse;
import org.openhab.binding.lifx.internal.protocol.StateWifiFirmwareResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightPropertiesUpdater} updates the light properties when a light goes online. When packets get lost
* the requests are resent when the {@code UPDATE_INTERVAL} elapses.
*
* @author Wouter Born - Update light properties when online
*/
@NonNullByDefault
public class LifxLightPropertiesUpdater {
private final Logger logger = LoggerFactory.getLogger(LifxLightPropertiesUpdater.class);
private static final int UPDATE_INTERVAL = 15;
private final String logId;
private final @Nullable InetSocketAddress ipAddress;
private final @Nullable MACAddress macAddress;
private final CurrentLightState currentLightState;
private final LifxLightCommunicationHandler communicationHandler;
private final List<LifxPropertiesUpdateListener> propertiesUpdateListeners = new CopyOnWriteArrayList<>();
private final List<Packet> requestPackets = Arrays.asList(new GetVersionRequest(), new GetHostFirmwareRequest(),
new GetWifiFirmwareRequest());
private final Set<Integer> receivedPacketTypes = new HashSet<>();
private final ReentrantLock lock = new ReentrantLock();
private final ScheduledExecutorService scheduler;
private @Nullable ScheduledFuture<?> updateJob;
private final Map<String, String> properties = new HashMap<>();
private boolean updating;
private boolean wasOnline;
public LifxLightPropertiesUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.macAddress = context.getConfiguration().getMACAddress();
this.ipAddress = context.getConfiguration().getHost();
this.currentLightState = context.getCurrentLightState();
this.scheduler = context.getScheduler();
this.communicationHandler = communicationHandler;
}
public void updateProperties() {
if (propertiesUpdateListeners.isEmpty()) {
logger.debug("{} : Not updating properties because there are no listeners", logId);
return;
}
try {
lock.lock();
boolean isOnline = currentLightState.isOnline();
if (isOnline) {
if (!wasOnline) {
logger.debug("{} : Updating light properties", logId);
properties.clear();
receivedPacketTypes.clear();
updating = true;
updateHostProperty();
updateMACAddressProperty();
sendPropertyRequestPackets();
} else if (updating && !receivedAllResponsePackets()) {
logger.debug("{} : Resending requests for missing response packets", logId);
sendPropertyRequestPackets();
}
}
wasOnline = isOnline;
} catch (Exception e) {
logger.error("Error occurred while polling online state of a light ({})", logId, e);
} finally {
lock.unlock();
}
}
private void updateHostProperty() {
InetSocketAddress host = communicationHandler.getIpAddress();
if (host == null) {
host = ipAddress;
}
if (host != null) {
properties.put(LifxBindingConstants.PROPERTY_HOST, host.getHostString());
}
}
private void updateMACAddressProperty() {
MACAddress mac = communicationHandler.getMACAddress();
if (mac == null) {
mac = macAddress;
}
if (mac != null) {
properties.put(LifxBindingConstants.PROPERTY_MAC_ADDRESS, mac.getAsLabel());
}
}
private void sendPropertyRequestPackets() {
for (Packet packet : requestPackets) {
if (!receivedPacketTypes.contains(packet.expectedResponses()[0])) {
communicationHandler.sendPacket(packet);
}
}
}
public void handleResponsePacket(Packet packet) {
if (!updating) {
return;
}
if (packet instanceof StateVersionResponse) {
Product product = Product.getProductFromProductID(((StateVersionResponse) packet).getProduct());
long productVersion = ((StateVersionResponse) packet).getVersion();
properties.put(LifxBindingConstants.PROPERTY_PRODUCT_ID, Long.toString(product.getID()));
properties.put(LifxBindingConstants.PROPERTY_PRODUCT_NAME, product.getName());
properties.put(LifxBindingConstants.PROPERTY_PRODUCT_VERSION, Long.toString(productVersion));
properties.put(LifxBindingConstants.PROPERTY_VENDOR_ID, Long.toString(product.getVendor().getID()));
properties.put(LifxBindingConstants.PROPERTY_VENDOR_NAME, product.getVendor().getName());
receivedPacketTypes.add(packet.getPacketType());
} else if (packet instanceof StateHostFirmwareResponse) {
String hostVersion = ((StateHostFirmwareResponse) packet).getVersion().toString();
properties.put(LifxBindingConstants.PROPERTY_HOST_VERSION, hostVersion);
receivedPacketTypes.add(packet.getPacketType());
} else if (packet instanceof StateWifiFirmwareResponse) {
String wifiVersion = ((StateWifiFirmwareResponse) packet).getVersion().toString();
properties.put(LifxBindingConstants.PROPERTY_WIFI_VERSION, wifiVersion);
receivedPacketTypes.add(packet.getPacketType());
}
if (receivedAllResponsePackets()) {
updating = false;
propertiesUpdateListeners.forEach(listener -> listener.handlePropertiesUpdate(properties));
logger.debug("{} : Finished updating light properties", logId);
}
}
private boolean receivedAllResponsePackets() {
return requestPackets.size() == receivedPacketTypes.size();
}
public void addPropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
propertiesUpdateListeners.add(listener);
}
public void removePropertiesUpdateListener(LifxPropertiesUpdateListener listener) {
propertiesUpdateListeners.remove(listener);
}
public void start() {
try {
lock.lock();
communicationHandler.addResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localUpdateJob = updateJob;
if (localUpdateJob == null || localUpdateJob.isCancelled()) {
updateJob = scheduler.scheduleWithFixedDelay(this::updateProperties, 0, UPDATE_INTERVAL,
TimeUnit.SECONDS);
}
} catch (Exception e) {
logger.error("Error occurred while starting properties update job for a light ({})", logId, e);
} finally {
lock.unlock();
}
}
public void stop() {
try {
lock.lock();
communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
ScheduledFuture<?> localUpdateJob = updateJob;
if (localUpdateJob != null && !localUpdateJob.isCancelled()) {
localUpdateJob.cancel(true);
updateJob = null;
}
} catch (Exception e) {
logger.error("Error occurred while stopping properties update job for a light ({})", logId, e);
} finally {
lock.unlock();
}
}
}

View File

@@ -0,0 +1,198 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.DEFAULT_COLOR;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.listener.LifxLightStateListener;
import org.openhab.binding.lifx.internal.protocol.Effect;
import org.openhab.binding.lifx.internal.protocol.PowerState;
import org.openhab.binding.lifx.internal.protocol.SignalStrength;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
/**
* The {@link LifxLightState} stores the properties that represent the state of a light.
*
* @author Wouter Born - Extracted class from LifxLightHandler, added listener logic
*/
@NonNullByDefault
public class LifxLightState {
private HSBK[] colors = new HSBK[] { new HSBK(DEFAULT_COLOR) };
private @Nullable PercentType infrared;
private @Nullable PowerState powerState;
private @Nullable SignalStrength signalStrength;
private @Nullable Effect tileEffect;
private LocalDateTime lastChange = LocalDateTime.MIN;
private List<LifxLightStateListener> listeners = new CopyOnWriteArrayList<>();
public void copy(LifxLightState other) {
this.powerState = other.getPowerState();
this.colors = other.getColors();
this.infrared = other.getInfrared();
this.signalStrength = other.getSignalStrength();
this.tileEffect = other.getTileEffect();
}
public @Nullable PowerState getPowerState() {
return powerState;
}
public HSBK getColor() {
return colors.length > 0 ? new HSBK(colors[0]) : new HSBK(DEFAULT_COLOR);
}
public HSBK getColor(int zoneIndex) {
return zoneIndex < colors.length ? new HSBK(colors[zoneIndex]) : new HSBK(DEFAULT_COLOR);
}
public HSBK[] getColors() {
HSBK[] colorsCopy = new HSBK[colors.length];
for (int i = 0; i < colors.length; i++) {
colorsCopy[i] = colors[i] != null ? new HSBK(colors[i]) : null;
}
return colorsCopy;
}
public @Nullable PercentType getInfrared() {
return infrared;
}
public @Nullable SignalStrength getSignalStrength() {
return signalStrength;
}
public @Nullable Effect getTileEffect() {
return tileEffect;
}
public void setColor(HSBType newHSB) {
HSBK newColor = getColor();
newColor.setHSB(newHSB);
setColor(newColor);
}
public void setColor(HSBType newHSB, int zoneIndex) {
HSBK newColor = getColor(zoneIndex);
newColor.setHSB(newHSB);
setColor(newColor, zoneIndex);
}
public void setBrightness(PercentType brightness) {
HSBK[] newColors = getColors();
for (HSBK newColor : newColors) {
newColor.setBrightness(brightness);
}
setColors(newColors);
}
public void setBrightness(PercentType brightness, int zoneIndex) {
HSBK newColor = getColor(zoneIndex);
newColor.setBrightness(brightness);
setColor(newColor, zoneIndex);
}
public void setColor(HSBK newColor) {
HSBK[] newColors = getColors();
Arrays.fill(newColors, newColor);
setColors(newColors);
}
public void setColor(HSBK newColor, int zoneIndex) {
HSBK[] newColors = getColors();
newColors[zoneIndex] = newColor;
setColors(newColors);
}
public void setColors(HSBK[] newColors) {
HSBK[] oldColors = this.colors;
this.colors = newColors;
updateLastChange();
listeners.forEach(listener -> listener.handleColorsChange(oldColors, newColors));
}
public void setPowerState(OnOffType newOnOff) {
setPowerState(PowerState.fromOnOffType(newOnOff));
}
public void setPowerState(PowerState newPowerState) {
PowerState oldPowerState = this.powerState;
this.powerState = newPowerState;
updateLastChange();
listeners.forEach(listener -> listener.handlePowerStateChange(oldPowerState, newPowerState));
}
public void setTemperature(int kelvin) {
HSBK[] newColors = getColors();
for (HSBK newColor : newColors) {
newColor.setKelvin(kelvin);
}
setColors(newColors);
}
public void setTemperature(int kelvin, int zoneIndex) {
HSBK newColor = getColor(zoneIndex);
newColor.setKelvin(kelvin);
setColor(newColor, zoneIndex);
}
public void setInfrared(PercentType newInfrared) {
PercentType oldInfrared = this.infrared;
this.infrared = newInfrared;
updateLastChange();
listeners.forEach(listener -> listener.handleInfraredChange(oldInfrared, newInfrared));
}
public void setSignalStrength(SignalStrength newSignalStrength) {
SignalStrength oldSignalStrength = this.signalStrength;
this.signalStrength = newSignalStrength;
updateLastChange();
listeners.forEach(listener -> listener.handleSignalStrengthChange(oldSignalStrength, newSignalStrength));
}
public void setTileEffect(Effect newEffect) {
// Caller has to take care that newEffect is another object
Effect oldEffect = tileEffect;
tileEffect = newEffect;
updateLastChange();
listeners.forEach(listener -> listener.handleTileEffectChange(oldEffect, newEffect));
}
private void updateLastChange() {
lastChange = LocalDateTime.now();
}
public Duration getDurationSinceLastChange() {
return Duration.between(lastChange, LocalDateTime.now());
}
public void addListener(LifxLightStateListener listener) {
listeners.add(listener);
}
public void removeListener(LifxLightStateListener listener) {
listeners.remove(listener);
}
}

View File

@@ -0,0 +1,389 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.PACKET_INTERVAL;
import static org.openhab.binding.lifx.internal.protocol.Product.Feature.MULTIZONE;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.listener.LifxLightStateListener;
import org.openhab.binding.lifx.internal.protocol.AcknowledgementResponse;
import org.openhab.binding.lifx.internal.protocol.ApplicationRequest;
import org.openhab.binding.lifx.internal.protocol.Effect;
import org.openhab.binding.lifx.internal.protocol.GetColorZonesRequest;
import org.openhab.binding.lifx.internal.protocol.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.protocol.GetLightPowerRequest;
import org.openhab.binding.lifx.internal.protocol.GetRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.PowerState;
import org.openhab.binding.lifx.internal.protocol.Product;
import org.openhab.binding.lifx.internal.protocol.SetColorRequest;
import org.openhab.binding.lifx.internal.protocol.SetColorZonesRequest;
import org.openhab.binding.lifx.internal.protocol.SetLightInfraredRequest;
import org.openhab.binding.lifx.internal.protocol.SetLightPowerRequest;
import org.openhab.binding.lifx.internal.protocol.SetPowerRequest;
import org.openhab.binding.lifx.internal.protocol.SetTileEffectRequest;
import org.openhab.binding.lifx.internal.protocol.SignalStrength;
import org.openhab.core.library.types.PercentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightStateChanger} listens to state changes of the {@code pendingLightState}. It sends packets to a
* light so the change the actual light state to that of the {@code pendingLightState}. When the light does not
* acknowledge a packet, it resends it (max 3 times).
*
* @author Wouter Born - Extracted class from LifxLightHandler, added logic for handling packet loss
*/
@NonNullByDefault
public class LifxLightStateChanger implements LifxLightStateListener {
/**
* Milliseconds before a packet is considered to be lost (unacknowledged).
*/
private static final int PACKET_ACKNOWLEDGE_INTERVAL = 250;
/**
* The number of times a lost packet will be resent.
*/
private static final int MAX_RETRIES = 3;
private final Logger logger = LoggerFactory.getLogger(LifxLightStateChanger.class);
private final String logId;
private final Product product;
private final Duration fadeTime;
private final LifxLightState pendingLightState;
private final ScheduledExecutorService scheduler;
private final LifxLightCommunicationHandler communicationHandler;
private final ReentrantLock lock = new ReentrantLock();
private @Nullable ScheduledFuture<?> sendJob;
private Map<Integer, @Nullable List<PendingPacket>> pendingPacketsMap = new ConcurrentHashMap<>();
private class PendingPacket {
long lastSend;
int sendCount;
final Packet packet;
private PendingPacket(Packet packet) {
this.packet = packet;
}
private boolean hasAcknowledgeIntervalElapsed() {
long millisSinceLastSend = System.currentTimeMillis() - lastSend;
return millisSinceLastSend > PACKET_ACKNOWLEDGE_INTERVAL;
}
}
public LifxLightStateChanger(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.product = context.getProduct();
this.fadeTime = context.getConfiguration().getFadeTime();
this.pendingLightState = context.getPendingLightState();
this.scheduler = context.getScheduler();
this.communicationHandler = communicationHandler;
}
private void sendPendingPackets() {
try {
lock.lock();
removeFailedPackets();
PendingPacket pendingPacket = findPacketToSend();
if (pendingPacket != null) {
Packet packet = pendingPacket.packet;
if (pendingPacket.sendCount == 0) {
// sendPacket will set the sequence number
logger.debug("{} : Sending {} packet", logId, packet.getClass().getSimpleName());
communicationHandler.sendPacket(packet);
} else {
// resendPacket will reuse the sequence number
logger.debug("{} : Resending {} packet", logId, packet.getClass().getSimpleName());
communicationHandler.resendPacket(packet);
}
pendingPacket.lastSend = System.currentTimeMillis();
pendingPacket.sendCount++;
}
} catch (Exception e) {
logger.error("Error occurred while sending packet", e);
} finally {
lock.unlock();
}
}
public void start() {
try {
lock.lock();
communicationHandler.addResponsePacketListener(this::handleResponsePacket);
pendingLightState.addListener(this);
ScheduledFuture<?> localSendJob = sendJob;
if (localSendJob == null || localSendJob.isCancelled()) {
sendJob = scheduler.scheduleWithFixedDelay(this::sendPendingPackets, 0, PACKET_INTERVAL,
TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
logger.error("Error occurred while starting send packets job", e);
} finally {
lock.unlock();
}
}
public void stop() {
try {
lock.lock();
communicationHandler.removeResponsePacketListener(this::handleResponsePacket);
pendingLightState.removeListener(this);
ScheduledFuture<?> localSendJob = sendJob;
if (localSendJob != null && !localSendJob.isCancelled()) {
localSendJob.cancel(true);
sendJob = null;
}
pendingPacketsMap.clear();
} catch (Exception e) {
logger.error("Error occurred while stopping send packets job", e);
} finally {
lock.unlock();
}
}
private List<PendingPacket> createPendingPackets(Packet... packets) {
Integer packetType = null;
List<PendingPacket> pendingPackets = new ArrayList<>();
for (Packet packet : packets) {
// the acknowledgement is used to resend the packet in case of packet loss
packet.setAckRequired(true);
// the LIFX LAN protocol spec indicates that the response returned for a request would be the
// previous value
packet.setResponseRequired(false);
pendingPackets.add(new PendingPacket(packet));
if (packetType == null) {
packetType = packet.getPacketType();
} else if (packetType != packet.getPacketType()) {
throw new IllegalArgumentException("Packets should have same packet type");
}
}
return pendingPackets;
}
private void addPacketsToMap(Packet... packets) {
List<PendingPacket> newPendingPackets = createPendingPackets(packets);
int packetType = packets[0].getPacketType();
try {
lock.lock();
List<PendingPacket> pendingPackets = pendingPacketsMap.get(packetType);
if (pendingPackets == null) {
pendingPacketsMap.put(packetType, newPendingPackets);
} else {
pendingPackets.addAll(newPendingPackets);
}
} finally {
lock.unlock();
}
}
private void replacePacketsInMap(Packet... packets) {
List<PendingPacket> pendingPackets = createPendingPackets(packets);
int packetType = packets[0].getPacketType();
try {
lock.lock();
pendingPacketsMap.put(packetType, pendingPackets);
} finally {
lock.unlock();
}
}
private @Nullable PendingPacket findPacketToSend() {
PendingPacket result = null;
for (List<PendingPacket> pendingPackets : pendingPacketsMap.values()) {
if (pendingPackets != null) {
for (PendingPacket pendingPacket : pendingPackets) {
if (pendingPacket.hasAcknowledgeIntervalElapsed()
&& (result == null || pendingPacket.lastSend < result.lastSend)) {
result = pendingPacket;
}
}
}
}
return result;
}
private void removePacketsByType(int packetType) {
try {
lock.lock();
pendingPacketsMap.remove(packetType);
} finally {
lock.unlock();
}
}
private void removeFailedPackets() {
for (List<PendingPacket> pendingPackets : pendingPacketsMap.values()) {
if (pendingPackets != null) {
Iterator<PendingPacket> it = pendingPackets.iterator();
while (it.hasNext()) {
PendingPacket pendingPacket = it.next();
if (pendingPacket.sendCount > MAX_RETRIES && pendingPacket.hasAcknowledgeIntervalElapsed()) {
logger.warn("{} failed (unacknowledged {} times to light {})",
pendingPacket.packet.getClass().getSimpleName(), pendingPacket.sendCount, logId);
it.remove();
}
}
}
}
}
private @Nullable PendingPacket removeAcknowledgedPacket(int sequenceNumber) {
for (List<PendingPacket> pendingPackets : pendingPacketsMap.values()) {
if (pendingPackets != null) {
Iterator<PendingPacket> it = pendingPackets.iterator();
while (it.hasNext()) {
PendingPacket pendingPacket = it.next();
if (pendingPacket.packet.getSequence() == sequenceNumber) {
it.remove();
return pendingPacket;
}
}
}
}
return null;
}
@Override
public void handleColorsChange(HSBK[] oldColors, HSBK[] newColors) {
if (sameColors(newColors)) {
SetColorRequest packet = new SetColorRequest(pendingLightState.getColors()[0], fadeTime.toMillis());
removePacketsByType(SetColorZonesRequest.TYPE);
replacePacketsInMap(packet);
} else {
List<SetColorZonesRequest> packets = new ArrayList<>();
for (int i = 0; i < newColors.length; i++) {
if (newColors[i] != null && !newColors[i].equals(oldColors[i])) {
packets.add(
new SetColorZonesRequest(i, newColors[i], fadeTime.toMillis(), ApplicationRequest.APPLY));
}
}
if (!packets.isEmpty()) {
removePacketsByType(SetColorRequest.TYPE);
addPacketsToMap(packets.toArray(new SetColorZonesRequest[packets.size()]));
}
}
}
@Override
public void handlePowerStateChange(@Nullable PowerState oldPowerState, PowerState newPowerState) {
if (!newPowerState.equals(oldPowerState)) {
SetLightPowerRequest packet = new SetLightPowerRequest(pendingLightState.getPowerState());
replacePacketsInMap(packet);
}
}
@Override
public void handleInfraredChange(@Nullable PercentType oldInfrared, PercentType newInfrared) {
PercentType infrared = pendingLightState.getInfrared();
if (infrared != null) {
SetLightInfraredRequest packet = new SetLightInfraredRequest(percentTypeToInfrared(infrared));
replacePacketsInMap(packet);
}
}
@Override
public void handleSignalStrengthChange(@Nullable SignalStrength oldSignalStrength,
SignalStrength newSignalStrength) {
// Nothing to handle
}
@Override
public void handleTileEffectChange(@Nullable Effect oldEffect, Effect newEffect) {
if (oldEffect == null || !oldEffect.equals(newEffect)) {
SetTileEffectRequest packet = new SetTileEffectRequest(newEffect);
replacePacketsInMap(packet);
}
}
public void handleResponsePacket(Packet packet) {
if (packet instanceof AcknowledgementResponse) {
long ackTimestamp = System.currentTimeMillis();
PendingPacket pendingPacket;
try {
lock.lock();
pendingPacket = removeAcknowledgedPacket(packet.getSequence());
} finally {
lock.unlock();
}
if (pendingPacket != null) {
Packet sentPacket = pendingPacket.packet;
logger.debug("{} : {} packet was acknowledged in {}ms", logId, sentPacket.getClass().getSimpleName(),
ackTimestamp - pendingPacket.lastSend);
// when these packets get lost the current state will still be updated by the
// LifxLightCurrentStateUpdater
if (sentPacket instanceof SetPowerRequest) {
GetLightPowerRequest powerPacket = new GetLightPowerRequest();
communicationHandler.sendPacket(powerPacket);
} else if (sentPacket instanceof SetColorRequest) {
GetRequest colorPacket = new GetRequest();
communicationHandler.sendPacket(colorPacket);
getZonesIfZonesAreSet();
} else if (sentPacket instanceof SetColorZonesRequest) {
getZonesIfZonesAreSet();
} else if (sentPacket instanceof SetLightInfraredRequest) {
GetLightInfraredRequest infraredPacket = new GetLightInfraredRequest();
communicationHandler.sendPacket(infraredPacket);
}
} else {
logger.debug("{} : No pending packet found for ack with sequence number: {}", logId,
packet.getSequence());
}
}
}
private void getZonesIfZonesAreSet() {
if (product.hasFeature(MULTIZONE)) {
List<PendingPacket> pending = pendingPacketsMap.get(SetColorZonesRequest.TYPE);
if (pending == null || pending.isEmpty()) {
GetColorZonesRequest zoneColorPacket = new GetColorZonesRequest();
communicationHandler.sendPacket(zoneColorPacket);
}
}
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.fields.MACAddress;
/**
* The {@link LifxSelectorContext} stores the context that is used for broadcast and unicast communications with a
* light using a {@link Selector}.
*
* @author Wouter Born - Make selector logic reusable between discovery and handlers
*/
@NonNullByDefault
public class LifxSelectorContext {
private final Selector selector;
private final long sourceId;
private final Supplier<Integer> sequenceNumberSupplier;
private final String logId;
private @Nullable InetSocketAddress host;
private @Nullable MACAddress macAddress;
private @Nullable SelectionKey broadcastKey;
private @Nullable SelectionKey unicastKey;
public LifxSelectorContext(Selector selector, long sourceId, Supplier<Integer> sequenceNumberSupplier, String logId,
@Nullable SelectionKey broadcastKey) {
this(selector, sourceId, sequenceNumberSupplier, logId, null, null, broadcastKey, null);
}
public LifxSelectorContext(Selector selector, long sourceId, Supplier<Integer> sequenceNumberSupplier, String logId,
@Nullable InetSocketAddress host, @Nullable MACAddress macAddress, @Nullable SelectionKey broadcastKey,
@Nullable SelectionKey unicastKey) {
this.selector = selector;
this.sourceId = sourceId;
this.sequenceNumberSupplier = sequenceNumberSupplier;
this.logId = logId;
this.host = host;
this.macAddress = macAddress;
this.broadcastKey = broadcastKey;
this.unicastKey = unicastKey;
}
public Selector getSelector() {
return selector;
}
public long getSourceId() {
return sourceId;
}
public Supplier<Integer> getSequenceNumberSupplier() {
return sequenceNumberSupplier;
}
public String getLogId() {
return logId;
}
public @Nullable InetSocketAddress getHost() {
return host;
}
public @Nullable MACAddress getMACAddress() {
return macAddress;
}
public @Nullable SelectionKey getBroadcastKey() {
return broadcastKey;
}
public @Nullable SelectionKey getUnicastKey() {
return unicastKey;
}
public void setHost(@Nullable InetSocketAddress host) {
this.host = host;
}
public void setMACAddress(@Nullable MACAddress macAddress) {
this.macAddress = macAddress;
}
public void setBroadcastKey(@Nullable SelectionKey broadcastKey) {
this.broadcastKey = broadcastKey;
}
public void setUnicastKey(@Nullable SelectionKey unicastKey) {
this.unicastKey = unicastKey;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Supplies sequence numbers for packets in the range [0, 255].
*
* @author Wouter Born - Make selector logic reusable between discovery and handlers
*/
@NonNullByDefault
public class LifxSequenceNumberSupplier implements Supplier<Integer> {
private static final int SEQUENCE_NUMBER_DIVISOR = 256;
private final AtomicInteger sequenceNumber = new AtomicInteger(1);
@Override
public Integer get() {
return sequenceNumber.getAndUpdate((value) -> {
return (value + 1) % SEQUENCE_NUMBER_DIVISOR;
});
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class ByteField extends Field<ByteBuffer> {
public ByteField() {
}
public ByteField(int length) {
super(length);
}
@Override
public int defaultLength() {
return 2;
}
@Override
public ByteBuffer value(ByteBuffer bytes) {
byte[] data = new byte[length];
bytes.get(data);
return ByteBuffer.wrap(data);
}
@Override
public ByteBuffer bytesInternal(ByteBuffer value) {
return value;
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Defines an abstract field that can be used to convert between native
* datatypes and a LIFX-compatible byte representation.
*
* @param <T> the field datatype
*
* @author Tim Buckley
*/
@NonNullByDefault
public abstract class Field<T> {
protected final int length;
public int getLength() {
return length;
}
/**
* Creates a new Field instance using the default length.
*/
public Field() {
length = defaultLength();
}
/**
* Creates a new Field instance using the specified length.
*
* @param length the field length to use
*/
public Field(int length) {
this.length = length;
}
/**
* Returns a default field length. Client classes should always use
* the {@code length} field (via {@code getLength()} to get the actual field
* length.
*
* @return the default length of this field to use if none is specified
*/
public abstract int defaultLength();
/**
* Converts the given ByteBuffer to a native datatype. The actual behavior
* of this method is left to the implementation.
*
* @param bytes the buffer to convert
* @return a native representation of the contents of the buffer
*/
public abstract T value(ByteBuffer bytes);
/**
* Converts the given value to a ByteBuffer. Actual behavior is determined
* by the concrete implementation.
*
* @param value the value to convert
* @return a buffer containing a representation of the value
*/
public ByteBuffer bytes(T value) {
ByteBuffer buf = bytesInternal(value);
buf.rewind();
return buf;
}
/**
* Called by {@link #bytes(Object)} to create a ByteBuffer containing an
* encoded representation of the given value. The buffer will be have
* {@link ByteBuffer#rewind()} called automatically by {@code bytes()}.
*
* @param value the value to convert
* @return a ByteBuffer containing the converted value
*/
protected abstract ByteBuffer bytesInternal(T value);
/**
* Returns a {@link LittleField} wrapping this field, effectively converting
* it to little endian.
*
* @return a little-endian version of this field
*/
public Field<T> little() {
return new LittleField<>(this);
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class FloatField extends Field<Float> {
@Override
public int defaultLength() {
return 4;
}
@Override
public Float value(ByteBuffer bytes) {
return bytes.getFloat();
}
@Override
protected ByteBuffer bytesInternal(Float value) {
return ByteBuffer.allocate(4).putFloat(value);
}
}

View File

@@ -0,0 +1,143 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
@NonNullByDefault
public class HSBK {
private static final String DEFAULT_PROPERTY_NAME = "hsbk";
private int hue;
private int saturation;
private int brightness;
private int kelvin;
public HSBK(int hue, int saturation, int brightness, int kelvin) {
this.hue = hue;
this.saturation = saturation;
this.brightness = brightness;
this.kelvin = kelvin;
}
public HSBK(HSBK other) {
this(other.hue, other.saturation, other.brightness, other.kelvin);
}
public HSBK(HSBType hsb, int kelvin) {
setHSB(hsb);
this.kelvin = kelvin;
}
public int getHue() {
return hue;
}
public int getSaturation() {
return saturation;
}
public int getBrightness() {
return brightness;
}
public int getKelvin() {
return kelvin;
}
public HSBType getHSB() {
DecimalType hue = hueToDecimalType(this.hue);
PercentType saturation = saturationToPercentType(this.saturation);
PercentType brightness = brightnessToPercentType(this.brightness);
return new HSBType(hue, saturation, brightness);
}
public void setHSB(HSBType hsb) {
setHue(hsb.getHue());
setSaturation(hsb.getSaturation());
setBrightness(hsb.getBrightness());
}
public void setHue(DecimalType hue) {
this.hue = decimalTypeToHue(hue);
}
public void setSaturation(PercentType saturation) {
this.saturation = percentTypeToSaturation(saturation);
}
public void setBrightness(PercentType brightness) {
this.brightness = percentTypeToBrightness(brightness);
}
public void setKelvin(int kelvin) {
this.kelvin = kelvin;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + hue;
result = prime * result + saturation;
result = prime * result + brightness;
result = prime * result + kelvin;
return result;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
HSBK other = (HSBK) obj;
if (hue != other.hue) {
return false;
}
if (saturation != other.saturation) {
return false;
}
if (brightness != other.brightness) {
return false;
}
if (kelvin != other.kelvin) {
return false;
}
return true;
}
@Override
public String toString() {
return toString(DEFAULT_PROPERTY_NAME);
}
public String toString(String propertyName) {
return String.format("%s=%d,%d,%d,%d", propertyName, hue, saturation, brightness, kelvin);
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
@NonNullByDefault
public class HSBKField extends Field<HSBK> {
public static final Field<Integer> FIELD_HUE = new UInt16Field().little();
public static final Field<Integer> FIELD_SATURATION = new UInt16Field().little();
public static final Field<Integer> FIELD_BRIGHTNESS = new UInt16Field().little();
public static final Field<Integer> FIELD_KELVIN = new UInt16Field().little();
@Override
public int defaultLength() {
return 8;
}
@Override
public HSBK value(ByteBuffer bytes) {
int hue = FIELD_HUE.value(bytes);
int saturation = FIELD_SATURATION.value(bytes);
int brightness = FIELD_BRIGHTNESS.value(bytes);
int kelvin = FIELD_KELVIN.value(bytes);
return new HSBK(hue, saturation, brightness, kelvin);
}
@Override
protected ByteBuffer bytesInternal(HSBK value) {
return ByteBuffer.allocate(defaultLength()).put(FIELD_HUE.bytes(value.getHue()))
.put(FIELD_SATURATION.bytes(value.getSaturation())).put(FIELD_BRIGHTNESS.bytes(value.getBrightness()))
.put(FIELD_KELVIN.bytes(value.getKelvin()));
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Reads a wrapped field in reversed byte order.
*
* @author Tim Buckley
*/
@NonNullByDefault
public class LittleField<T> extends Field<T> {
private final Field<T> wrapped;
public LittleField(Field<T> wrapped) {
super(wrapped.length);
this.wrapped = wrapped;
}
@Override
public int defaultLength() {
return wrapped.defaultLength();
}
@Override
public T value(ByteBuffer bytes) {
byte[] field = new byte[wrapped.length];
bytes.get(field);
ByteBuffer flipped = flip(ByteBuffer.wrap(field));
T value = wrapped.value(flipped);
return value;
}
@Override
public ByteBuffer bytesInternal(T value) {
return flip(wrapped.bytes(value));
}
public static ByteBuffer flip(ByteBuffer buf) {
buf.rewind();
ByteBuffer ret = ByteBuffer.allocate(buf.limit());
for (int i = buf.limit() - 1; i >= 0; i--) {
ret.put(buf.get(i));
}
ret.rewind();
return ret;
}
}

View File

@@ -0,0 +1,150 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Tim Buckley
* @author Karel Goderis
*/
@NonNullByDefault
public class MACAddress {
public static final MACAddress BROADCAST_ADDRESS = new MACAddress("000000000000", true);
private final Logger logger = LoggerFactory.getLogger(MACAddress.class);
private ByteBuffer bytes;
private String hex = "";
public ByteBuffer getBytes() {
return bytes;
}
public String getHex() {
return hex;
}
public MACAddress(ByteBuffer bytes) {
this.bytes = bytes;
createHex();
}
public MACAddress(String string, boolean isHex) {
if (!isHex) {
this.bytes = ByteBuffer.wrap(string.getBytes());
createHex();
} else {
this.bytes = ByteBuffer.wrap(parseHexBinary(string));
try {
formatHex(string, 2, ":");
} catch (IOException e) {
logger.error("An exception occurred while formatting an HEX string : '{}'", e.getMessage());
}
}
}
private byte[] parseHexBinary(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
public MACAddress() {
this(ByteBuffer.allocate(6));
}
private void createHex() {
bytes.rewind();
List<String> byteStrings = new LinkedList<>();
while (bytes.hasRemaining()) {
byteStrings.add(String.format("%02X", bytes.get()));
}
hex = StringUtils.join(byteStrings, ':');
bytes.rewind();
}
public String getAsLabel() {
bytes.rewind();
StringBuilder hex = new StringBuilder();
while (bytes.hasRemaining()) {
hex.append(String.format("%02X", bytes.get()));
}
bytes.rewind();
return hex.toString();
}
private void formatHex(String original, int length, String separator) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(original.getBytes());
byte[] buffer = new byte[length];
String result = "";
while (bis.read(buffer) > 0) {
for (byte b : buffer) {
result += (char) b;
}
Arrays.fill(buffer, (byte) 0);
result += separator;
}
hex = StringUtils.left(result, result.length() - 1);
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.hex);
return hash;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MACAddress other = (MACAddress) obj;
if (!this.hex.equalsIgnoreCase(other.hex)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
* @author Karel Goderis
*/
@NonNullByDefault
public class MACAddressField extends Field<MACAddress> {
public MACAddressField() {
super(8);
}
@Override
public int defaultLength() {
return 8;
}
@Override
public MACAddress value(ByteBuffer bytes) {
byte[] data = new byte[length];
bytes.get(data);
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.limit(length - 2);
return new MACAddress(buffer);
}
@Override
protected ByteBuffer bytesInternal(MACAddress value) {
return value.getBytes().duplicate();
}
@Override
public ByteBuffer bytes(MACAddress value) {
byte[] data = new byte[length];
ByteBuffer bytes = bytesInternal(value);
bytes.rewind();
bytes.get(data, 0, bytes.limit());
ByteBuffer buf = ByteBuffer.wrap(data);
buf.rewind();
return buf;
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class StringField extends Field<String> {
public static final Charset CHARSET = StandardCharsets.US_ASCII;
private Charset charset;
public StringField() {
charset = StandardCharsets.US_ASCII;
}
public StringField(int length) {
super(length);
charset = StandardCharsets.US_ASCII;
}
public StringField(int length, Charset charset) {
super(length);
this.charset = charset;
}
@Override
public int defaultLength() {
return 3;
}
@Override
public String value(ByteBuffer bytes) {
byte[] buf = new byte[length];
bytes.get(buf);
ByteBuffer field = ByteBuffer.wrap(buf);
String ret = charset.decode(field).toString();
ret = ret.replace("\0", "");
return ret;
}
@Override
public ByteBuffer bytesInternal(String value) {
return CHARSET.encode(value);
}
public StringField ascii() {
charset = StandardCharsets.US_ASCII;
return this;
}
public StringField utf8() {
charset = StandardCharsets.UTF_8;
return this;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class UInt16Field extends Field<Integer> {
@Override
public int defaultLength() {
return 2;
}
@Override
public Integer value(ByteBuffer bytes) {
return bytes.getShort() & 0xFFFF;
}
@Override
public ByteBuffer bytesInternal(Integer value) {
return ByteBuffer.allocate(2).putShort((short) (value & 0xFFFF));
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class UInt32Field extends Field<Long> {
@Override
public int defaultLength() {
return 4;
}
@Override
public Long value(ByteBuffer bytes) {
return bytes.getInt() & 0xFFFFFFFFL;
}
@Override
public ByteBuffer bytesInternal(Long value) {
return ByteBuffer.allocate(4).putInt((int) (value & 0xFFFFFFFFL));
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* A pseudo-uint64 field. Bytes will be stored directly in a long value, so
* unexpected values will likely be shown if exposed to users. Most bit-level
* operations should still work (addition, multiplication, shifting, etc).
*
* @author Tim Buckley
*/
@NonNullByDefault
public class UInt64Field extends Field<Long> {
@Override
public int defaultLength() {
return 8;
}
@Override
public Long value(ByteBuffer bytes) {
return bytes.getLong();
}
@Override
protected ByteBuffer bytesInternal(Long value) {
return ByteBuffer.allocate(8).putLong(value);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Tim Buckley
*/
@NonNullByDefault
public class UInt8Field extends Field<Integer> {
public UInt8Field() {
super(1);
}
@Override
public int defaultLength() {
return 1;
}
@Override
public Integer value(ByteBuffer bytes) {
return (int) (bytes.get() & 0xFF);
}
@Override
public ByteBuffer bytesInternal(Integer value) {
return ByteBuffer.allocate(1).put((byte) (value & 0xFF));
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Wouter Born - Add Thing properties
*/
@NonNullByDefault
public class Version {
private long major;
private long minor;
public Version(long major, long minor) {
this.major = major;
this.minor = minor;
}
public long getMajor() {
return major;
}
public long getMinor() {
return minor;
}
@Override
public String toString() {
return major + "." + minor;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Wouter Born - Add Thing properties
*/
@NonNullByDefault
public class VersionField extends Field<Version> {
@Override
public int defaultLength() {
return 4;
}
@Override
public Version value(ByteBuffer bytes) {
long value = bytes.getInt() & 0xFFFFFFFFL;
long major = (value >> 16) & 0xFFL;
long minor = value & 0xFFL;
return new Version(major, minor);
}
@Override
public ByteBuffer bytesInternal(Version value) {
return ByteBuffer.allocate(4).putInt((int) (((value.getMajor() << 16) | value.getMinor()) & 0xFFFFFFFFL));
}
}

View File

@@ -0,0 +1,721 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.handler;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
import static org.openhab.binding.lifx.internal.protocol.Product.Feature.*;
import static org.openhab.binding.lifx.internal.util.LifxMessageUtil.*;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.LifxBindingConstants;
import org.openhab.binding.lifx.internal.LifxChannelFactory;
import org.openhab.binding.lifx.internal.LifxLightCommunicationHandler;
import org.openhab.binding.lifx.internal.LifxLightConfig;
import org.openhab.binding.lifx.internal.LifxLightContext;
import org.openhab.binding.lifx.internal.LifxLightCurrentStateUpdater;
import org.openhab.binding.lifx.internal.LifxLightOnlineStateUpdater;
import org.openhab.binding.lifx.internal.LifxLightPropertiesUpdater;
import org.openhab.binding.lifx.internal.LifxLightState;
import org.openhab.binding.lifx.internal.LifxLightStateChanger;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.MACAddress;
import org.openhab.binding.lifx.internal.protocol.Effect;
import org.openhab.binding.lifx.internal.protocol.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.protocol.GetLightPowerRequest;
import org.openhab.binding.lifx.internal.protocol.GetRequest;
import org.openhab.binding.lifx.internal.protocol.GetTileEffectRequest;
import org.openhab.binding.lifx.internal.protocol.GetWifiInfoRequest;
import org.openhab.binding.lifx.internal.protocol.Packet;
import org.openhab.binding.lifx.internal.protocol.PowerState;
import org.openhab.binding.lifx.internal.protocol.Product;
import org.openhab.binding.lifx.internal.protocol.SignalStrength;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LifxLightHandler} is responsible for handling commands, which are
* sent to one of the light channels.
*
* @author Dennis Nobel - Initial contribution
* @author Stefan Bußweiler - Added new thing status handling
* @author Karel Goderis - Rewrite for Firmware V2, and remove dependency on external libraries
* @author Kai Kreuzer - Added configurable transition time and small fixes
* @author Wouter Born - Decomposed class into separate objects
* @author Pauli Anttila - Added power on temperature and color features.
*/
@NonNullByDefault
public class LifxLightHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(LifxLightHandler.class);
private static final Duration MIN_STATUS_INFO_UPDATE_INTERVAL = Duration.ofSeconds(1);
private static final Duration MAX_STATE_CHANGE_DURATION = Duration.ofSeconds(4);
private final LifxChannelFactory channelFactory;
private @NonNullByDefault({}) Product product;
private @Nullable PercentType powerOnBrightness;
private @Nullable HSBType powerOnColor;
private @Nullable PercentType powerOnTemperature;
private Double effectMorphSpeed = 3.0;
private Double effectFlameSpeed = 4.0;
private @NonNullByDefault({}) String logId;
private final ReentrantLock lock = new ReentrantLock();
private @NonNullByDefault({}) CurrentLightState currentLightState;
private @NonNullByDefault({}) LifxLightState pendingLightState;
private Map<String, @Nullable State> channelStates = new HashMap<>();
private @Nullable ThingStatusInfo statusInfo;
private LocalDateTime lastStatusInfoUpdate = LocalDateTime.MIN;
private @NonNullByDefault({}) LifxLightCommunicationHandler communicationHandler;
private @NonNullByDefault({}) LifxLightCurrentStateUpdater currentStateUpdater;
private @NonNullByDefault({}) LifxLightStateChanger lightStateChanger;
private @NonNullByDefault({}) LifxLightOnlineStateUpdater onlineStateUpdater;
private @NonNullByDefault({}) LifxLightPropertiesUpdater propertiesUpdater;
public class CurrentLightState extends LifxLightState {
public boolean isOnline() {
return thing.getStatus() == ThingStatus.ONLINE;
}
public boolean isOffline() {
return thing.getStatus() == ThingStatus.OFFLINE;
}
public void setOnline() {
updateStatusIfChanged(ThingStatus.ONLINE);
}
public void setOnline(MACAddress macAddress) {
updateStatusIfChanged(ThingStatus.ONLINE);
Configuration configuration = editConfiguration();
configuration.put(LifxBindingConstants.CONFIG_PROPERTY_DEVICE_ID, macAddress.getAsLabel());
updateConfiguration(configuration);
}
public void setOffline() {
updateStatusIfChanged(ThingStatus.OFFLINE);
}
public void setOfflineByCommunicationError() {
updateStatusIfChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
@Override
public void setColors(HSBK[] colors) {
if (!isStateChangePending() || isPendingColorStateChangesApplied(getPowerState(), colors)) {
PowerState powerState = isStateChangePending() ? pendingLightState.getPowerState() : getPowerState();
updateColorChannels(powerState, colors);
}
super.setColors(colors);
}
@Override
public void setPowerState(PowerState powerState) {
if (!isStateChangePending() || isPendingColorStateChangesApplied(powerState, getColors())) {
HSBK[] colors = isStateChangePending() ? pendingLightState.getColors() : getColors();
updateColorChannels(powerState, colors);
}
super.setPowerState(powerState);
}
private boolean isPendingColorStateChangesApplied(@Nullable PowerState powerState, HSBK[] colors) {
return powerState != null && powerState.equals(pendingLightState.getPowerState())
&& Arrays.equals(colors, pendingLightState.getColors());
}
private void updateColorChannels(@Nullable PowerState powerState, HSBK[] colors) {
HSBK color = colors.length > 0 ? colors[0] : null;
HSBK updateColor = nullSafeUpdateColor(powerState, color);
HSBType hsb = updateColor.getHSB();
updateStateIfChanged(CHANNEL_COLOR, hsb);
updateStateIfChanged(CHANNEL_BRIGHTNESS, hsb.getBrightness());
updateStateIfChanged(CHANNEL_TEMPERATURE,
kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
updateZoneChannels(powerState, colors);
}
private HSBK nullSafeUpdateColor(@Nullable PowerState powerState, @Nullable HSBK color) {
HSBK updateColor = color != null ? color : DEFAULT_COLOR;
if (powerState == PowerState.OFF) {
updateColor = new HSBK(updateColor);
updateColor.setBrightness(PercentType.ZERO);
}
return updateColor;
}
@Override
public void setInfrared(PercentType infrared) {
if (!isStateChangePending() || infrared.equals(pendingLightState.getInfrared())) {
updateStateIfChanged(CHANNEL_INFRARED, infrared);
}
super.setInfrared(infrared);
}
@Override
public void setSignalStrength(SignalStrength signalStrength) {
updateStateIfChanged(CHANNEL_SIGNAL_STRENGTH, new DecimalType(signalStrength.toQualityRating()));
super.setSignalStrength(signalStrength);
}
@Override
public void setTileEffect(Effect effect) {
updateStateIfChanged(CHANNEL_EFFECT, new StringType(effect.getType().stringValue()));
super.setTileEffect(effect);
}
private void updateZoneChannels(@Nullable PowerState powerState, HSBK[] colors) {
if (!product.hasFeature(MULTIZONE) || colors.length == 0) {
return;
}
int oldZones = getColors().length;
int newZones = colors.length;
if (oldZones != newZones) {
addRemoveZoneChannels(newZones);
}
for (int i = 0; i < colors.length; i++) {
HSBK color = colors[i];
HSBK updateColor = nullSafeUpdateColor(powerState, color);
updateStateIfChanged(CHANNEL_COLOR_ZONE + i, updateColor.getHSB());
updateStateIfChanged(CHANNEL_TEMPERATURE_ZONE + i,
kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
}
}
}
public LifxLightHandler(Thing thing, LifxChannelFactory channelFactory) {
super(thing);
this.channelFactory = channelFactory;
}
@Override
public void initialize() {
try {
lock.lock();
LifxLightConfig configuration = getConfigAs(LifxLightConfig.class);
logId = getLogId(configuration.getMACAddress(), configuration.getHost());
product = getProduct();
logger.debug("{} : Initializing handler for product {}", logId, product.getName());
powerOnBrightness = getPowerOnBrightness();
powerOnColor = getPowerOnColor();
powerOnTemperature = getPowerOnTemperature();
Double speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_MORPH_SPEED);
if (speed != null) {
effectMorphSpeed = speed;
}
speed = getEffectSpeed(LifxBindingConstants.CONFIG_PROPERTY_EFFECT_FLAME_SPEED);
if (speed != null) {
effectFlameSpeed = speed;
}
channelStates.clear();
currentLightState = new CurrentLightState();
pendingLightState = new LifxLightState();
LifxLightContext context = new LifxLightContext(logId, product, configuration, currentLightState,
pendingLightState, scheduler);
communicationHandler = new LifxLightCommunicationHandler(context);
currentStateUpdater = new LifxLightCurrentStateUpdater(context, communicationHandler);
onlineStateUpdater = new LifxLightOnlineStateUpdater(context, communicationHandler);
propertiesUpdater = new LifxLightPropertiesUpdater(context, communicationHandler);
propertiesUpdater.addPropertiesUpdateListener(this::updateProperties);
lightStateChanger = new LifxLightStateChanger(context, communicationHandler);
if (configuration.getMACAddress() != null || configuration.getHost() != null) {
communicationHandler.start();
currentStateUpdater.start();
onlineStateUpdater.start();
propertiesUpdater.start();
lightStateChanger.start();
startOrStopSignalStrengthUpdates();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Configure a Device ID or Host");
}
} catch (Exception e) {
logger.debug("{} : Error occurred while initializing handler: {}", logId, e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} finally {
lock.unlock();
}
}
@Override
public void dispose() {
try {
lock.lock();
logger.debug("{} : Disposing handler", logId);
if (communicationHandler != null) {
communicationHandler.stop();
communicationHandler = null;
}
if (currentStateUpdater != null) {
currentStateUpdater.stop();
currentStateUpdater = null;
}
if (onlineStateUpdater != null) {
onlineStateUpdater.stop();
onlineStateUpdater = null;
}
if (propertiesUpdater != null) {
propertiesUpdater.stop();
propertiesUpdater.removePropertiesUpdateListener(this::updateProperties);
propertiesUpdater = null;
}
if (lightStateChanger != null) {
lightStateChanger.stop();
lightStateChanger = null;
}
currentLightState = null;
pendingLightState = null;
} finally {
lock.unlock();
}
}
public String getLogId(@Nullable MACAddress macAddress, @Nullable InetSocketAddress host) {
return (macAddress != null ? macAddress.getHex() : (host != null ? host.getHostString() : "Unknown"));
}
private @Nullable PercentType getPowerOnBrightness() {
Channel channel = null;
if (product.hasFeature(COLOR)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
channel = getThing().getChannel(channelUID.getId());
} else {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_BRIGHTNESS);
channel = getThing().getChannel(channelUID.getId());
}
if (channel == null) {
return null;
}
Configuration configuration = channel.getConfiguration();
Object powerOnBrightness = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_BRIGHTNESS);
return powerOnBrightness == null ? null : new PercentType(powerOnBrightness.toString());
}
private @Nullable HSBType getPowerOnColor() {
Channel channel = null;
if (product.hasFeature(COLOR)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
channel = getThing().getChannel(channelUID.getId());
}
if (channel == null) {
return null;
}
Configuration configuration = channel.getConfiguration();
Object powerOnColor = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_COLOR);
return powerOnColor == null ? null : new HSBType(powerOnColor.toString());
}
private @Nullable PercentType getPowerOnTemperature() {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_TEMPERATURE);
Channel channel = getThing().getChannel(channelUID.getId());
if (channel == null) {
return null;
}
Configuration configuration = channel.getConfiguration();
Object powerOnTemperature = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_POWER_ON_TEMPERATURE);
if (powerOnTemperature != null) {
return new PercentType(powerOnTemperature.toString());
}
return null;
}
private @Nullable Double getEffectSpeed(String parameter) {
Channel channel = null;
if (product.hasFeature(TILE_EFFECT)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_EFFECT);
channel = getThing().getChannel(channelUID.getId());
}
if (channel == null) {
return null;
}
Configuration configuration = channel.getConfiguration();
Object speed = configuration.get(parameter);
return speed == null ? null : new Double(speed.toString());
}
private Product getProduct() {
Object propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_PRODUCT_ID);
if (propertyValue == null) {
return Product.getLikelyProduct(getThing().getThingTypeUID());
}
try {
// Without first conversion to double, on a very first thing creation from discovery inbox,
// the product type is incorrectly parsed, as framework passed it as a floating point number
// (e.g. 50.0 instead of 50)
Double d = Double.parseDouble((String) propertyValue);
long productID = d.longValue();
return Product.getProductFromProductID(productID);
} catch (IllegalArgumentException e) {
return Product.getLikelyProduct(getThing().getThingTypeUID());
}
}
private void addRemoveZoneChannels(int zones) {
List<Channel> newChannels = new ArrayList<>();
// retain non-zone channels
for (Channel channel : getThing().getChannels()) {
String channelId = channel.getUID().getId();
if (!channelId.startsWith(CHANNEL_COLOR_ZONE) && !channelId.startsWith(CHANNEL_TEMPERATURE_ZONE)) {
newChannels.add(channel);
}
}
// add zone channels
for (int i = 0; i < zones; i++) {
newChannels.add(channelFactory.createColorZoneChannel(getThing().getUID(), i));
newChannels.add(channelFactory.createTemperatureZoneChannel(getThing().getUID(), i));
}
updateThing(editThing().withChannels(newChannels).build());
Map<String, String> properties = editProperties();
properties.put(LifxBindingConstants.PROPERTY_ZONES, Integer.toString(zones));
updateProperties(properties);
}
@Override
public void channelLinked(ChannelUID channelUID) {
super.channelLinked(channelUID);
startOrStopSignalStrengthUpdates();
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
startOrStopSignalStrengthUpdates();
}
private void startOrStopSignalStrengthUpdates() {
currentStateUpdater.setUpdateSignalStrength(isLinked(CHANNEL_SIGNAL_STRENGTH));
}
private void sendPacket(Packet packet) {
communicationHandler.sendPacket(packet);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
channelStates.remove(channelUID.getId());
switch (channelUID.getId()) {
case CHANNEL_COLOR:
case CHANNEL_BRIGHTNESS:
sendPacket(new GetLightPowerRequest());
sendPacket(new GetRequest());
break;
case CHANNEL_TEMPERATURE:
sendPacket(new GetRequest());
break;
case CHANNEL_INFRARED:
sendPacket(new GetLightInfraredRequest());
break;
case CHANNEL_SIGNAL_STRENGTH:
sendPacket(new GetWifiInfoRequest());
break;
case CHANNEL_EFFECT:
if (product.hasFeature(TILE_EFFECT)) {
sendPacket(new GetTileEffectRequest());
}
break;
default:
break;
}
} else {
boolean supportedCommand = true;
switch (channelUID.getId()) {
case CHANNEL_COLOR:
if (command instanceof HSBType) {
handleHSBCommand((HSBType) command);
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) {
handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_TEMPERATURE:
if (command instanceof PercentType) {
handleTemperatureCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_INFRARED:
if (command instanceof PercentType) {
handleInfraredCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseInfraredCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_EFFECT:
if (command instanceof StringType && product.hasFeature(TILE_EFFECT)) {
handleTileEffectCommand((StringType) command);
} else {
supportedCommand = false;
}
break;
default:
try {
if (channelUID.getId().startsWith(CHANNEL_COLOR_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_COLOR_ZONE, ""));
if (command instanceof HSBType) {
handleHSBCommand((HSBType) command, zoneIndex);
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command, zoneIndex);
} else {
supportedCommand = false;
}
} else if (channelUID.getId().startsWith(CHANNEL_TEMPERATURE_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_TEMPERATURE_ZONE, ""));
if (command instanceof PercentType) {
handleTemperatureCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command, zoneIndex);
} else {
supportedCommand = false;
}
} else {
supportedCommand = false;
}
} catch (NumberFormatException e) {
logger.error("Failed to parse zone index for a command of a light ({}) : {}", logId,
e.getMessage());
supportedCommand = false;
}
break;
}
if (supportedCommand && !(command instanceof OnOffType) && !CHANNEL_INFRARED.equals(channelUID.getId())) {
getLightStateForCommand().setPowerState(PowerState.ON);
}
}
}
private LifxLightState getLightStateForCommand() {
if (!isStateChangePending()) {
pendingLightState.copy(currentLightState);
}
return pendingLightState;
}
private boolean isStateChangePending() {
return pendingLightState.getDurationSinceLastChange().minus(MAX_STATE_CHANGE_DURATION).isNegative();
}
private void handleTemperatureCommand(PercentType temperature) {
HSBK newColor = getLightStateForCommand().getColor();
newColor.setSaturation(PercentType.ZERO);
newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
getLightStateForCommand().setColor(newColor);
}
private void handleTemperatureCommand(PercentType temperature, int zoneIndex) {
HSBK newColor = getLightStateForCommand().getColor(zoneIndex);
newColor.setSaturation(PercentType.ZERO);
newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
getLightStateForCommand().setColor(newColor, zoneIndex);
}
private void handleHSBCommand(HSBType hsb) {
getLightStateForCommand().setColor(hsb);
}
private void handleHSBCommand(HSBType hsb, int zoneIndex) {
getLightStateForCommand().setColor(hsb, zoneIndex);
}
private void handlePercentCommand(PercentType brightness) {
getLightStateForCommand().setBrightness(brightness);
}
private void handlePercentCommand(PercentType brightness, int zoneIndex) {
getLightStateForCommand().setBrightness(brightness, zoneIndex);
}
private void handleOnOffCommand(OnOffType onOff) {
HSBType localPowerOnColor = powerOnColor;
if (localPowerOnColor != null && onOff == OnOffType.ON) {
getLightStateForCommand().setColor(localPowerOnColor);
}
PercentType localPowerOnTemperature = powerOnTemperature;
if (localPowerOnTemperature != null && onOff == OnOffType.ON) {
getLightStateForCommand()
.setTemperature(percentTypeToKelvin(localPowerOnTemperature, product.getTemperatureRange()));
}
if (powerOnBrightness != null) {
PercentType newBrightness = onOff == OnOffType.ON ? powerOnBrightness : new PercentType(0);
getLightStateForCommand().setBrightness(newBrightness);
}
getLightStateForCommand().setPowerState(onOff);
}
private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease) {
HSBK baseColor = getLightStateForCommand().getColor();
PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
handlePercentCommand(newBrightness);
}
private void handleIncreaseDecreaseCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
HSBK baseColor = getLightStateForCommand().getColor(zoneIndex);
PercentType newBrightness = increaseDecreasePercentType(increaseDecrease, baseColor.getHSB().getBrightness());
handlePercentCommand(newBrightness, zoneIndex);
}
private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor().getKelvin(),
product.getTemperatureRange());
PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
handleTemperatureCommand(newTemperature);
}
private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor(zoneIndex).getKelvin(),
product.getTemperatureRange());
PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
handleTemperatureCommand(newTemperature, zoneIndex);
}
private void handleInfraredCommand(PercentType infrared) {
getLightStateForCommand().setInfrared(infrared);
}
private void handleIncreaseDecreaseInfraredCommand(IncreaseDecreaseType increaseDecrease) {
PercentType baseInfrared = getLightStateForCommand().getInfrared();
if (baseInfrared != null) {
PercentType newInfrared = increaseDecreasePercentType(increaseDecrease, baseInfrared);
handleInfraredCommand(newInfrared);
}
}
private void handleTileEffectCommand(StringType type) {
logger.debug("handleTileEffectCommand mode={}", type);
Double morphSpeedInMSecs = effectMorphSpeed * 1000.0;
Double flameSpeedInMSecs = effectFlameSpeed * 1000.0;
try {
Effect effect = Effect.createDefault(type.toString(), morphSpeedInMSecs.longValue(),
flameSpeedInMSecs.longValue());
getLightStateForCommand().setTileEffect(effect);
} catch (IllegalArgumentException e) {
logger.debug("Wrong effect type received as command: {}", type);
}
}
private void updateStateIfChanged(String channel, State newState) {
State oldState = channelStates.get(channel);
if (oldState == null || !oldState.equals(newState)) {
updateState(channel, newState);
channelStates.put(channel, newState);
}
}
private void updateStatusIfChanged(ThingStatus status) {
updateStatusIfChanged(status, ThingStatusDetail.NONE);
}
private void updateStatusIfChanged(ThingStatus status, ThingStatusDetail statusDetail) {
ThingStatusInfo newStatusInfo = new ThingStatusInfo(status, statusDetail, null);
Duration durationSinceLastUpdate = Duration.between(lastStatusInfoUpdate, LocalDateTime.now());
boolean intervalElapsed = MIN_STATUS_INFO_UPDATE_INTERVAL.minus(durationSinceLastUpdate).isNegative();
ThingStatusInfo oldStatusInfo = statusInfo;
if (oldStatusInfo == null || !oldStatusInfo.equals(newStatusInfo) || intervalElapsed) {
statusInfo = newStatusInfo;
lastStatusInfoUpdate = LocalDateTime.now();
updateStatus(status, statusDetail);
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.listener;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.LifxLightState;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.protocol.Effect;
import org.openhab.binding.lifx.internal.protocol.PowerState;
import org.openhab.binding.lifx.internal.protocol.SignalStrength;
import org.openhab.core.library.types.PercentType;
/**
* The {@link LifxLightStateListener} is notified when the properties of a {@link LifxLightState} change.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public interface LifxLightStateListener {
/**
* Called when the colors property changes.
*
* @param oldColors the old colors value
* @param newColors the new colors value
*/
void handleColorsChange(HSBK[] oldColors, HSBK[] newColors);
/**
* Called when the power state property changes.
*
* @param oldPowerState the old power state value
* @param newPowerState the new power state value
*/
void handlePowerStateChange(@Nullable PowerState oldPowerState, PowerState newPowerState);
/**
* Called when the infrared property changes.
*
* @param oldInfrared the old infrared value
* @param newInfrared the new infrared value
*/
void handleInfraredChange(@Nullable PercentType oldInfrared, PercentType newInfrared);
/**
* Called when the signal strength property changes.
*
* @param oldSignalStrength the old signal strength value
* @param newSignalStrength the new signal strength value
*/
void handleSignalStrengthChange(@Nullable SignalStrength oldSignalStrength, SignalStrength newSignalStrength);
/**
* Called when the tile effect changes.
*
* @param oldEffect the old tile effect value
* @param newEffect new tile effectvalue
*/
void handleTileEffectChange(@Nullable Effect oldEffect, Effect newEffect);
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.listener;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.LifxLightPropertiesUpdater;
/**
* The {@link LifxPropertiesUpdateListener} is notified when the {@link LifxLightPropertiesUpdater} has
* updated light properties.
*
* @author Wouter Born - Update light properties when online
*/
@NonNullByDefault
public interface LifxPropertiesUpdateListener {
/**
* Called when the {@link LifxLightPropertiesUpdater} has updated light properties.
*
* @param packet the updated properties
*/
public void handlePropertiesUpdate(Map<String, String> properties);
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.listener;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.LifxLightCommunicationHandler;
import org.openhab.binding.lifx.internal.protocol.Packet;
/**
* The {@link LifxResponsePacketListener} is notified when the {@link LifxLightCommunicationHandler} receives a response
* packet.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public interface LifxResponsePacketListener {
/**
* Called when the {@link LifxLightCommunicationHandler} receives a response packet.
*
* @param packet the received packet
*/
public void handleResponsePacket(Packet packet);
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class AcknowledgementResponse extends Packet {
public static final int TYPE = 0x2D;
public AcknowledgementResponse() {
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
public enum ApplicationRequest {
/**
* Don't apply the requested changes until a message with APPLY or APPLY_ONLY is sent.
*/
NO_APPLY(0x00),
/**
* Apply the changes immediately and apply any pending changes.
*/
APPLY(0x01),
/**
* Ignore the requested changes in this message and only apply pending changes.
*/
APPLY_ONLY(0x02);
private final int value;
private ApplicationRequest(int value) {
this.value = value;
}
/**
* Gets the integer value of this application request.
*
* @return the integer value
*/
public int getValue() {
return value;
}
/**
* Returns the {@link ApplicationRequest} for the given integer value.
*
* @param value the integer value
* @return the {@link ApplicationRequest} or <code>null</code>, if no {@link ApplicationRequest} exists for the
* given value
*/
public static ApplicationRequest fromValue(int value) {
for (ApplicationRequest ar : values()) {
if (ar.getValue() == value) {
return ar;
}
}
return null;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class EchoRequestResponse extends Packet {
public static final int TYPE = 0x3B;
public static final Field<ByteBuffer> FIELD_PAYLOAD = new ByteField(64);
private ByteBuffer payload;
public EchoRequestResponse() {
}
public ByteBuffer getPayload() {
return payload;
}
public void setPayload(ByteBuffer location) {
this.payload = location;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 64;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
payload = FIELD_PAYLOAD.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_PAYLOAD.bytes(payload));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,169 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.LifxBindingConstants;
import org.openhab.binding.lifx.internal.fields.HSBK;
/**
* This class represents LIFX Tile effect
*
* @author Pawel Pieczul - initial contribution
*/
@NonNullByDefault
public class Effect {
public enum EffectType {
OFF(0),
MORPH(2),
FLAME(3);
Integer type;
EffectType(Integer type) {
this.type = type;
}
public static EffectType fromValue(Integer value) {
switch (value) {
case 0:
return OFF;
case 2:
return MORPH;
case 3:
return FLAME;
default:
throw new IllegalArgumentException("Unknown effect type");
}
}
public static EffectType fromValue(String value) {
if (LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_OFF.equals(value)) {
return OFF;
} else if (LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_MORPH.equals(value)) {
return MORPH;
} else if (LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_FLAME.equals(value)) {
return FLAME;
}
throw new IllegalArgumentException("Unknown effect type");
}
public Integer intValue() {
return type;
}
public String stringValue() {
switch (type) {
case 2:
return LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_MORPH;
case 3:
return LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_FLAME;
default:
return LifxBindingConstants.CHANNEL_TYPE_EFFECT_OPTION_OFF;
}
}
}
final EffectType type;
final Long speed;
final Long duration;
final HSBK[] palette;
public EffectType getType() {
return type;
}
public Long getSpeed() {
return speed;
}
public Long getDuration() {
return duration;
}
HSBK[] getPalette() {
return palette;
}
public Effect(EffectType type, Long speed, Long duration, HSBK[] palette) {
this.type = type;
this.palette = palette;
this.duration = duration;
this.speed = speed;
}
public Effect(Integer type, Long speed, Long duration, HSBK[] palette) {
this(EffectType.fromValue(type), speed, duration, palette);
}
public Effect() {
this(EffectType.OFF, 3000L, 0L, new HSBK[0]);
}
public static Effect createDefault(String type, @Nullable Long morphSpeed, @Nullable Long flameSpeed) {
Long speed;
EffectType effectType = EffectType.fromValue(type);
switch (effectType) {
case OFF:
return new Effect(effectType, 0L, 0L, new HSBK[0]);
case MORPH:
if (morphSpeed == null) {
speed = 3000L;
} else {
speed = morphSpeed;
}
HSBK[] p = { new HSBK(0, 65535, 65535, 3500), new HSBK(7281, 65535, 65535, 3500),
new HSBK(10922, 65535, 65535, 3500), new HSBK(22209, 65535, 65535, 3500),
new HSBK(43507, 65535, 65535, 3500), new HSBK(49333, 65535, 65535, 3500),
new HSBK(53520, 65535, 65535, 3500) };
return new Effect(effectType, speed, 0L, p);
case FLAME:
if (flameSpeed == null) {
speed = 4000L;
} else {
speed = flameSpeed;
}
return new Effect(effectType, speed, 0L, new HSBK[0]);
default:
throw new IllegalArgumentException("Unknown effect type");
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (o.getClass() != getClass()) {
return false;
}
Effect n = (Effect) o;
return n.getType().equals(this.getType()) && n.duration.equals(this.duration) && n.speed.equals(this.speed)
&& n.palette == this.palette;
}
@Override
public int hashCode() {
Long hash = 1L;
int prime = 31;
hash = prime * hash + type.hashCode();
hash = prime * hash + duration;
hash = prime * hash + speed;
hash = prime * hash + palette.hashCode();
return hash.intValue();
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* A generic handler that dynamically creates "standard" packet instances.
*
* <p>
* Packet types must have an empty constructor and cannot require any
* additional logic (other than parsing).
*
* @param <T> the packet subtype this handler constructs
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
@NonNullByDefault
public class GenericHandler<T extends Packet> implements PacketHandler<T> {
private Constructor<T> constructor;
private boolean typeFound;
private int type;
public boolean isTypeFound() {
return typeFound;
}
public int getType() {
return type;
}
public GenericHandler(Class<T> clazz) {
try {
constructor = clazz.getConstructor();
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Packet class cannot be handled by GenericHandler", ex);
}
try {
Field typeField = clazz.getField("TYPE");
type = (int) typeField.get(null);
typeFound = true;
} catch (NoSuchFieldException | IllegalAccessException ex) {
// silently ignore
typeFound = false;
}
}
@Override
public T handle(ByteBuffer buf) {
try {
T ret = constructor.newInstance();
ret.parse(buf);
return ret;
} catch (ReflectiveOperationException ex) {
throw new IllegalArgumentException("Unable to instantiate empty packet", ex);
}
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GenericPacket extends Packet {
@Override
public int packetType() {
return 0;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
public class GetColorZonesRequest extends Packet {
public static final int TYPE = 0x1F6;
public static final Field<Integer> FIELD_START_INDEX = new UInt8Field();
public static final Field<Integer> FIELD_END_INDEX = new UInt8Field();
private int startIndex = MIN_ZONE_INDEX;
private int endIndex = MAX_ZONE_INDEX;
public GetColorZonesRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public GetColorZonesRequest(int index) {
this(index, index);
}
public GetColorZonesRequest(int startIndex, int endIndex) {
this();
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public int getStartIndex() {
return startIndex;
}
public int getEndIndex() {
return endIndex;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
startIndex = FIELD_START_INDEX.value(bytes);
endIndex = FIELD_END_INDEX.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_START_INDEX.bytes(startIndex))
.put(FIELD_END_INDEX.bytes(endIndex));
}
@Override
public int[] expectedResponses() {
return new int[] { StateMultiZoneResponse.TYPE, StateZoneResponse.TYPE };
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetEchoRequest extends Packet {
public static final int TYPE = 0x3A;
public static final Field<ByteBuffer> FIELD_PAYLOAD = new ByteField(64);
private ByteBuffer payload;
public GetEchoRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public ByteBuffer getPayload() {
return payload;
}
public void setPayload(ByteBuffer payload) {
this.payload = payload;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 64;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_PAYLOAD.bytes(payload));
}
@Override
public int[] expectedResponses() {
return new int[] { EchoRequestResponse.TYPE };
}
public static GetEchoRequest currentTimeEchoRequest() {
ByteBuffer payload = ByteBuffer.allocate(Long.SIZE / 8);
payload.putLong(System.currentTimeMillis());
GetEchoRequest request = new GetEchoRequest();
request.setResponseRequired(true);
request.setPayload(payload);
return request;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetGroupRequest extends Packet {
public static final int TYPE = 0x33;
public GetGroupRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateGroupResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetHostFirmwareRequest extends Packet {
public static final int TYPE = 0x0E;
public GetHostFirmwareRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateHostFirmwareResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetHostInfoRequest extends Packet {
public static final int TYPE = 0x0C;
public GetHostInfoRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateHostInfoResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetInfoRequest extends Packet {
public static final int TYPE = 0x22;
public GetInfoRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateInfoResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetLabelRequest extends Packet {
public static final int TYPE = 0x17;
public GetLabelRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateLabelResponse.TYPE };
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Wouter Born - Support LIFX 2016 product line-up and infrared functionality
*/
public class GetLightInfraredRequest extends Packet {
public static final int TYPE = 0x78;
public GetLightInfraredRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateLightInfraredResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetLightPowerRequest extends Packet {
public static final int TYPE = 0x74;
public GetLightPowerRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateLightPowerResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetLocationRequest extends Packet {
public static final int TYPE = 0x30;
public GetLocationRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateLocationResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetPowerRequest extends Packet {
public static final int TYPE = 0x14;
public GetPowerRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StatePowerResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetRequest extends Packet {
public static final int TYPE = 0x65;
public GetRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateResponse.TYPE };
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetServiceRequest extends Packet {
public static final int TYPE = 0x02;
public GetServiceRequest() {
setTagged(true);
setAddressable(true);
}
@Override
public int packetLength() {
return 0;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// empty
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateServiceResponse.TYPE };
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetTagLabelsRequest extends Packet {
public static final int TYPE = 0x1D;
public static final Field<Long> FIELD_TAGS = new UInt64Field();
private long tags;
public long getTags() {
return tags;
}
public GetTagLabelsRequest() {
}
public GetTagLabelsRequest(long tags) {
this.tags = tags;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 8;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
tags = FIELD_TAGS.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_TAGS.bytes(tags));
}
@Override
public int[] expectedResponses() {
return new int[] { TagLabelsResponse.TYPE };
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetTagsRequest extends Packet {
public static final int TYPE = 0x1A;
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { TagsResponse.TYPE };
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* Implementation of GetTileEffect packet
*
* @author Pawel Pieczul - Add support for Tile Effects
*/
public class GetTileEffectRequest extends Packet {
public static final int TYPE = 0x2CE;
public GetTileEffectRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateTileEffectResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetVersionRequest extends Packet {
public static final int TYPE = 0x20;
public GetVersionRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateVersionResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetWifiFirmwareRequest extends Packet {
public static final int TYPE = 0x12;
public GetWifiFirmwareRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateWifiFirmwareResponse.TYPE };
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class GetWifiInfoRequest extends Packet {
public static final int TYPE = 0x10;
public GetWifiInfoRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateWifiInfoResponse.TYPE };
}
}

View File

@@ -0,0 +1,397 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.MACAddress;
import org.openhab.binding.lifx.internal.fields.MACAddressField;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* Represents an abstract packet, providing conversion functionality to and from
* {@link ByteBuffer}s for common packet (preamble) fields. Subtypes of this
* class can provide conversion functionality for specialized fields.
*
* <p>
* Defining new packet types essentially involves extending this class,
* defining the fields and implementing {@link #packetType()},
* {@link #packetLength()}, and {@link #packetBytes()}. By convention, packet
* type should be stored in a {@code public static final int PACKET_TYPE} field
* in each subtype, followed by a listing of fields contained in the packet.
* Field definitions should remain accessible to outside classes in the event
* they need to worked with directly elsewhere.
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public abstract class Packet {
public static final Field<Integer> FIELD_SIZE = new UInt16Field().little();
public static final Field<Integer> FIELD_PROTOCOL = new UInt16Field().little();
public static final Field<Long> FIELD_SOURCE = new UInt32Field().little();
public static final Field<MACAddress> FIELD_TARGET = new MACAddressField();
public static final Field<ByteBuffer> FIELD_RESERVED_1 = new ByteField(6);
public static final Field<Integer> FIELD_ACK = new UInt8Field();
public static final Field<Integer> FIELD_SEQUENCE = new UInt8Field().little();
public static final Field<ByteBuffer> FIELD_RESERVED_2 = new ByteField(8);
public static final Field<Integer> FIELD_PACKET_TYPE = new UInt16Field().little();
public static final Field<ByteBuffer> FIELD_RESERVED_3 = new ByteField(2);
/**
* An ordered array of all fields contained in the common packet preamble.
*/
public static final Field<?>[] PREAMBLE_FIELDS = new Field[] { FIELD_SIZE, FIELD_PROTOCOL, FIELD_SOURCE,
FIELD_TARGET, FIELD_RESERVED_1, FIELD_ACK, FIELD_SEQUENCE, FIELD_RESERVED_2, FIELD_PACKET_TYPE,
FIELD_RESERVED_3 };
protected int size;
protected int protocol;
protected long source;
protected MACAddress target;
protected ByteBuffer reserved1;
protected int ackbyte;
protected int sequence;
protected ByteBuffer reserved2;
protected int packetType;
protected ByteBuffer reserved3;
protected long timeStamp;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getOrigin() {
return (protocol & 0xC000) >> 14;
}
public void setOrigin(int origin) {
protocol = (protocol & ~(1 << 14)) | (origin << 14);
}
public boolean getTagged() {
return (protocol & 0x2000) >> 13 == 1 ? true : false;
}
public void setTagged(boolean flag) {
protocol = (protocol & ~(1 << 13)) | ((flag ? 1 : 0) << 13);
}
public boolean getAddressable() {
return (protocol & 0x1000) >> 12 == 1 ? true : false;
}
public void setAddressable(boolean flag) {
this.protocol = (protocol & ~(1 << 12)) | ((flag ? 1 : 0) << 12);
}
public int getProtocol() {
return protocol & 0x0FFF;
}
public void setProtocol(int protocol) {
this.protocol = this.protocol | protocol;
}
public long getSource() {
return source;
}
public void setSource(long source) {
this.source = source;
}
public MACAddress getTarget() {
return target;
}
public void setTarget(MACAddress lightAddress) {
this.target = lightAddress != null ? lightAddress : MACAddress.BROADCAST_ADDRESS;
}
public ByteBuffer getReserved1() {
return reserved1;
}
public void setReserved1(ByteBuffer reserved2) {
this.reserved1 = reserved2;
}
public boolean getAckRequired() {
return (ackbyte & 0x02) >> 1 == 1 ? true : false;
}
public void setAckRequired(boolean flag) {
this.ackbyte = (ackbyte & ~(1 << 1)) | ((flag ? 1 : 0) << 1);
}
public boolean getResponseRequired() {
return (ackbyte & 0x01) >> 0 == 1 ? true : false;
}
public void setResponseRequired(boolean flag) {
this.ackbyte = (ackbyte & ~(1 << 0)) | ((flag ? 1 : 0) << 0);
}
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
if (0 <= sequence && sequence < 256) {
this.sequence = sequence;
} else {
throw new IllegalArgumentException("Sequence number '" + sequence + "' is not in range [0, 255]");
}
}
public ByteBuffer getReserved2() {
return reserved2;
}
public void setReserved2(ByteBuffer reserved3) {
this.reserved2 = reserved3;
}
public int getPacketType() {
return packetType;
}
public void setPacketType(int packetType) {
this.packetType = packetType;
}
public ByteBuffer getReserved3() {
return reserved3;
}
public void setReserved3(ByteBuffer reserved4) {
this.reserved3 = reserved4;
}
public long getTimeStamp() {
return timeStamp;
}
/**
* Creates an empty packet, setting some default values via
* {@link #preambleDefaults()}.
*/
public Packet() {
preambleDefaults();
timeStamp = System.currentTimeMillis();
}
/**
* Parses, in order, the defined preamble fields, storing collected values.
* The buffer's position will be left at the end of the parsed fields and
* should be equal to the value returned by {@link #preambleLength()}.
*
* @param bytes the buffer to read from.
*/
protected void parsePreamble(ByteBuffer bytes) {
size = FIELD_SIZE.value(bytes);
protocol = FIELD_PROTOCOL.value(bytes);
source = FIELD_SOURCE.value(bytes);
target = FIELD_TARGET.value(bytes);
reserved1 = FIELD_RESERVED_1.value(bytes);
ackbyte = FIELD_ACK.value(bytes);
sequence = FIELD_SEQUENCE.value(bytes);
reserved2 = FIELD_RESERVED_2.value(bytes);
packetType = FIELD_PACKET_TYPE.value(bytes);
reserved3 = FIELD_RESERVED_3.value(bytes);
}
/**
* Calculates the length of the packet header, defined as the sum of the
* lengths of all defined fields (see {@link #PREAMBLE_FIELDS}).
*
* @return the sum of the length of preamble fields
*/
protected int preambleLength() {
int sum = 0;
for (Field<?> f : PREAMBLE_FIELDS) {
sum += f.getLength();
}
return sum;
}
/**
* Returns a new {@code ByteBuffer} containing the encoded preamble. Note
* that the returned buffer will have its position set at the end of the
* buffer and will need to have {@link ByteBuffer#rewind()} called before
* use.
*
* <p>
* The length of the buffer is the sum of the lengths of the defined
* preamble fields (see {@link #PREAMBLE_FIELDS} for an ordered list), which
* may also be accessed via {@link #preambleLength()}.
*
* <p>
* Certain fields are set to default values based on other class methods.
* For example, the size and packet type fields will be set to the values
* returned from {@link #length()} and {@link #packetType()}, respectively.
* Other defaults (such as the protocol, light address, site, and timestamp)
* may be specified either by directly setting the relevant protected
* variables or by overriding {@link #preambleDefaults()}.
*
* @return a new buffer containing the encoded preamble
*/
protected ByteBuffer preambleBytes() {
return ByteBuffer.allocate(preambleLength()).put(FIELD_SIZE.bytes(length())).put(FIELD_PROTOCOL.bytes(protocol))
.put(FIELD_SOURCE.bytes(source)).put(FIELD_TARGET.bytes(target))
.put(ByteBuffer.allocate(FIELD_RESERVED_1.getLength())) // empty
.put(FIELD_ACK.bytes(ackbyte)).put(FIELD_SEQUENCE.bytes(sequence))
.put(ByteBuffer.allocate(FIELD_RESERVED_2.getLength())) // empty
.put(FIELD_PACKET_TYPE.bytes(packetType())).put(ByteBuffer.allocate(FIELD_RESERVED_3.getLength())); // empty
}
/**
* Sets default preamble values. If needed, subclasses may override these
* values by specifically overriding this method, or by setting individual
* values within the constructor, as this method is called automatically
* during initialization.
*/
protected void preambleDefaults() {
size = 0;
protocol = 1024;
target = new MACAddress();
sequence = 0;
packetType = packetType();
}
/**
* Returns the packet type. Note that this value is technically distinct
* from {@code getPacketType()} in that it returns the packet type the
* current {@code Packet} subtype is designed to parse, while
* {@code getPacketType()} returns the actual {@code packetType} field of
* a parsed packet. However, these values should always match.
*
* @return the packet type intended to be handled by this Packet subtype
*/
public abstract int packetType();
/**
* Returns the length of the payload specific to this packet subtype. The
* length of the preamble is specifically excluded.
*
* @return the length of this specialized packet payload
*/
protected abstract int packetLength();
/**
* Parses the given {@link ByteBuffer} into class fields. Subtypes may
* implement {@link #parsePacket(ByteBuffer)} to parse additional fields;
* the preamble by default is always parsed.
*
* @param bytes the buffer to extract data from
*/
public void parse(ByteBuffer bytes) {
bytes.rewind();
parsePreamble(bytes);
parsePacket(bytes);
}
/**
* Extracts data from the given {@link ByteBuffer} into fields specific to
* this packet subtype. The preamble will already have been parsed; as such,
* the buffer will be positioned at the end of the preamble. If needed,
* {@link #preambleLength()} may be used to restore the position of the
* buffer.
*
* @param bytes the raw bytes to parse
*/
protected abstract void parsePacket(ByteBuffer bytes);
/**
* Returns a {@link ByteBuffer} containing the full payload for this packet,
* including the populated preamble and any specialized packet payload. The
* returned buffer will be at position zero.
*
* @return the full packet payload
*/
public ByteBuffer bytes() {
ByteBuffer preamble = preambleBytes();
preamble.rewind();
ByteBuffer packet = packetBytes();
packet.rewind();
ByteBuffer ret = ByteBuffer.allocate(length()).put(preamble).put(packet);
ret.rewind();
return ret;
}
/**
* Returns a {@link ByteBuffer} containing the payload for this packet. Its
* length must match the value of {@link #packetLength()}. This specifically
* excludes preamble fields and should contain only data specific to the
* packet subtype.
* <p>
* Note that returned ByteBuffers will have {@link ByteBuffer#rewind()}
* called automatically before they are appended to the final packet
* buffer.
*
* @return the packet payload
*/
protected abstract ByteBuffer packetBytes();
/**
* Gets the total length of this packet, in bytes. Specifically, this method
* is the sum of the preamble ({@link #preambleLength()}) and the payload
* length ({@link #packetLength()}); subtypes should override methods for
* those values if desired.
*
* @return the total length of this packet
*/
public int length() {
return preambleLength() + packetLength();
}
/**
* Returns a list of expected response packet types. An empty array means
* no responses are expected (suitable for response packet definitions),
*
* @return a list of expected responses
*/
public abstract int[] expectedResponses();
public boolean isExpectedResponse(int type) {
for (int a : expectedResponses()) {
if (a == type) {
return true;
}
}
return false;
}
public boolean isFulfilled(Packet somePacket) {
if (isExpectedResponse(somePacket.getPacketType())) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,157 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* A static factory for registering packet types that may be received and
* dispatched to client cod * request types, like {@code PowerStateRequest}) or types received only via UDP
* e. Packet handlers (used to construct actual packet
* instances) may be retrieved via their packet type.
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
* @author Wouter Born - Support LIFX 2016 product line-up and infrared functionality
*/
@NonNullByDefault
public class PacketFactory {
private static @Nullable PacketFactory instance;
public static synchronized PacketFactory getInstance() {
PacketFactory result = instance;
if (result == null) {
result = new PacketFactory();
instance = result;
}
return result;
}
private final Map<Integer, PacketHandler<?>> handlers;
private PacketFactory() {
handlers = new HashMap<>();
register(AcknowledgementResponse.class);
register(EchoRequestResponse.class);
register(GetColorZonesRequest.class);
register(GetEchoRequest.class);
register(GetGroupRequest.class);
register(GetHostFirmwareRequest.class);
register(GetHostInfoRequest.class);
register(GetInfoRequest.class);
register(GetLabelRequest.class);
register(GetLightInfraredRequest.class);
register(GetLightPowerRequest.class);
register(GetLocationRequest.class);
register(GetPowerRequest.class);
register(GetRequest.class);
register(GetServiceRequest.class);
register(GetTagLabelsRequest.class);
register(GetTagsRequest.class);
register(GetTileEffectRequest.class);
register(GetVersionRequest.class);
register(GetWifiFirmwareRequest.class);
register(GetWifiInfoRequest.class);
register(SetColorRequest.class);
register(SetColorZonesRequest.class);
register(SetDimAbsoluteRequest.class);
register(SetLabelRequest.class);
register(SetLightInfraredRequest.class);
register(SetLightPowerRequest.class);
register(SetPowerRequest.class);
register(SetTagsRequest.class);
register(StateGroupResponse.class);
register(StateHostFirmwareResponse.class);
register(StateHostInfoResponse.class);
register(StateInfoResponse.class);
register(StateLabelResponse.class);
register(StateLightInfraredResponse.class);
register(StateLightPowerResponse.class);
register(StateLocationResponse.class);
register(StateMultiZoneResponse.class);
register(StatePowerResponse.class);
register(StateResponse.class);
register(StateServiceResponse.class);
register(StateTileEffectResponse.class);
register(StateVersionResponse.class);
register(StateWifiFirmwareResponse.class);
register(StateWifiInfoResponse.class);
register(StateZoneResponse.class);
register(TagLabelsResponse.class);
register(TagsResponse.class);
}
/**
* Registers a packet handler for the given packet type.
*
* @param type the type to register
* @param handler the packet handler to associate with the type
*/
public final void register(int type, PacketHandler<?> handler) {
handlers.put(type, handler);
}
/**
* Registers a new generic packet handler for the given packet class. The
* packet class must meet the criteria for {@link GenericHandler};
* specifically, it must have an no-argument constructor and require no
* parsing logic outside of an invocation of
* {@link Packet#parse(java.nio.ByteBuffer)}.
*
* @param type the type of the packet to register
* @param clazz the class of the packet to register
*/
public final void register(int type, Class<? extends Packet> clazz) {
handlers.put(type, new GenericHandler<>(clazz));
}
/**
* Registers a generic packet type. All requirements of
* {@link GenericHandler} must met; specifically, classes must have a
* no-args constructor and require no additional parsing logic.
* Additionally, a public static integer {@code TYPE} field must be defined.
*
* @param <T> the packet type to register
* @param clazz the packet class to register
*/
public final <T extends Packet> void register(Class<T> clazz) {
GenericHandler<T> handler = new GenericHandler<>(clazz);
if (!handler.isTypeFound()) {
throw new IllegalArgumentException("Unable to register generic packet with no TYPE field.");
}
handlers.put(handler.getType(), handler);
}
/**
* Gets a registered handler for the given packet type, if any exists. If
* no matching handler can be found, {@code null} is returned.
*
* @param packetType the packet type of the handler to retrieve
* @return a packet handler, or null
*/
public @Nullable PacketHandler<?> getHandler(int packetType) {
return handlers.get(packetType);
}
public static @Nullable PacketHandler<?> createHandler(int packetType) {
return getInstance().getHandler(packetType);
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* A packet handler responsible for converting a ByteBuffer into a Packet
* instance.
*
* @param <T> the generic packet type
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
@NonNullByDefault
public interface PacketHandler<T extends Packet> {
/**
* Creates a packet from the given buffer.
*
* @param buf the buffer used for creating the packet
* @return the packet created from the buffer
* @throws IllegalArgumentException when an empty packet could not be created or the data in the buffer
* could not be parsed
*/
public abstract T handle(ByteBuffer buf);
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import org.openhab.core.library.types.OnOffType;
/**
* Represents light power states (on or off).
*
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
* @author Wouter Born - Added OnOffType conversion methods
*/
public enum PowerState {
ON(0xFFFF),
OFF(0x0000);
private final int value;
private PowerState(int value) {
this.value = value;
}
/**
* Gets the integer value of this power state.
*
* @return the integer value
*/
public int getValue() {
return value;
}
public static PowerState fromValue(int value) {
// a response can have a power level between 0 and 65535 when the light
// has just been switched ON or OFF
return value == OFF.value ? OFF : ON;
}
public static PowerState fromOnOffType(OnOffType onOff) {
return onOff == OnOffType.ON ? ON : OFF;
}
public OnOffType toOnOffType() {
return this == ON ? OnOffType.ON : OnOffType.OFF;
}
}

View File

@@ -0,0 +1,284 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import static org.openhab.binding.lifx.internal.protocol.Product.Feature.*;
import static org.openhab.binding.lifx.internal.protocol.Product.TemperatureRange.*;
import static org.openhab.binding.lifx.internal.protocol.Product.Vendor.LIFX;
import java.util.Arrays;
import java.util.EnumSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.LifxBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* Enumerates the LIFX products, their IDs and feature set.
*
* @see https://lan.developer.lifx.com/docs/lifx-products
*
* @author Wouter Born - Support LIFX 2016 product line-up and infrared functionality
* @author Wouter Born - Add temperature ranges and simplify feature definitions
*/
@NonNullByDefault
public enum Product {
PRODUCT_1(1, "Original 1000", TR_2500_9000, COLOR),
PRODUCT_3(3, "Color 650", TR_2500_9000, COLOR),
PRODUCT_10(10, "White 800 (Low Voltage)", TR_2700_6500),
PRODUCT_11(11, "White 800 (High Voltage)", TR_2700_6500),
PRODUCT_18(18, "White 900 BR30 (Low Voltage)", TR_2700_6500),
PRODUCT_20(20, "Color 1000 BR30", TR_2500_9000, COLOR),
PRODUCT_22(22, "Color 1000", TR_2500_9000, COLOR),
PRODUCT_27(27, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_28(28, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_29(29, "LIFX+ A19", TR_2500_9000, COLOR, INFRARED),
PRODUCT_30(30, "LIFX+ BR30", TR_2500_9000, COLOR, INFRARED),
PRODUCT_31(31, "LIFX Z", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_32(32, "LIFX Z 2", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_36(36, "LIFX Downlight", TR_2500_9000, COLOR),
PRODUCT_37(37, "LIFX Downlight", TR_2500_9000, COLOR),
PRODUCT_38(38, "LIFX Beam", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_43(43, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_44(44, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_45(45, "LIFX+ A19", TR_2500_9000, COLOR, INFRARED),
PRODUCT_46(46, "LIFX+ BR30", TR_2500_9000, COLOR, INFRARED),
PRODUCT_49(49, "LIFX Mini", TR_2500_9000, COLOR),
PRODUCT_50(50, "LIFX Mini Day and Dusk", TR_1500_4000),
PRODUCT_51(51, "LIFX Mini White", TR_2700_2700),
PRODUCT_52(52, "LIFX GU10", TR_2500_9000, COLOR),
PRODUCT_55(55, "LIFX Tile", TR_2500_9000, CHAIN, COLOR, MATRIX, TILE_EFFECT),
PRODUCT_57(57, "LIFX Candle", TR_1500_9000, COLOR, MATRIX),
PRODUCT_59(59, "LIFX Mini Color", TR_2500_9000, COLOR),
PRODUCT_60(60, "LIFX Mini Day and Dusk", TR_1500_4000),
PRODUCT_61(61, "LIFX Mini White", TR_2700_2700),
PRODUCT_62(62, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_63(63, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_64(64, "LIFX+ A19", TR_2500_9000, COLOR, INFRARED),
PRODUCT_65(65, "LIFX+ BR30", TR_2500_9000, COLOR, INFRARED),
PRODUCT_68(68, "LIFX Candle", TR_1500_9000, COLOR, MATRIX),
PRODUCT_81(81, "LIFX Candle Warm to White", TR_2200_6500, MATRIX),
PRODUCT_82(82, "LIFX Filament", TR_2000_2000);
/**
* Enumerates the product features.
*/
public enum Feature {
CHAIN,
COLOR,
INFRARED,
MATRIX,
MULTIZONE,
TILE_EFFECT
}
/**
* Enumerates the product vendors.
*/
public enum Vendor {
LIFX(1, "LIFX");
private final int id;
private final String name;
Vendor(int id, String name) {
this.id = id;
this.name = name;
}
public int getID() {
return id;
}
public String getName() {
return name;
}
}
/**
* Enumerates the color temperature ranges of lights.
*/
public enum TemperatureRange {
/**
* 1500-4000K
*/
TR_1500_4000(1500, 4000),
/**
* 1500-9000K
*/
TR_1500_9000(1500, 9000),
/**
* 2000-2000K
*/
TR_2000_2000(2000, 2000),
/**
* 2200-6500K
*/
TR_2200_6500(2200, 6500),
/**
* 2500-9000K
*/
TR_2500_9000(2500, 9000),
/**
* 2700-2700K
*/
TR_2700_2700(2700, 2700),
/**
* 2700-6500K
*/
TR_2700_6500(2700, 6500);
private final int minimum;
private final int maximum;
TemperatureRange(int minimum, int maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
/**
* The minimum color temperature in degrees Kelvin.
*
* @return minimum color temperature (K)
*/
public int getMinimum() {
return minimum;
}
/**
* The maxiumum color temperature in degrees Kelvin.
*
* @return maximum color temperature (K)
*/
public int getMaximum() {
return maximum;
}
/**
* The color temperature range in degrees Kelvin.
*
* @return difference between maximum and minimum color temperature values
*/
public int getRange() {
return maximum - minimum;
}
}
private final Vendor vendor;
private final long id;
private final String name;
private final TemperatureRange temperatureRange;
private final EnumSet<Feature> features = EnumSet.noneOf(Feature.class);
private Product(long id, String name, TemperatureRange temperatureRange) {
this(LIFX, id, name, temperatureRange);
}
private Product(long id, String name, TemperatureRange temperatureRange, Feature... features) {
this(LIFX, id, name, temperatureRange, features);
}
private Product(Vendor vendor, long id, String name, TemperatureRange temperatureRange) {
this(vendor, id, name, temperatureRange, new Feature[0]);
}
private Product(Vendor vendor, long id, String name, TemperatureRange temperatureRange, Feature... features) {
this.vendor = vendor;
this.id = id;
this.name = name;
this.temperatureRange = temperatureRange;
this.features.addAll(Arrays.asList(features));
}
@Override
public String toString() {
return name;
}
public Vendor getVendor() {
return vendor;
}
public long getID() {
return id;
}
public String getName() {
return name;
}
public TemperatureRange getTemperatureRange() {
return temperatureRange;
}
public ThingTypeUID getThingTypeUID() {
if (hasFeature(COLOR)) {
if (hasFeature(TILE_EFFECT)) {
return LifxBindingConstants.THING_TYPE_TILELIGHT;
} else if (hasFeature(INFRARED)) {
return LifxBindingConstants.THING_TYPE_COLORIRLIGHT;
} else if (hasFeature(MULTIZONE)) {
return LifxBindingConstants.THING_TYPE_COLORMZLIGHT;
} else {
return LifxBindingConstants.THING_TYPE_COLORLIGHT;
}
} else {
return LifxBindingConstants.THING_TYPE_WHITELIGHT;
}
}
public boolean hasFeature(Feature feature) {
return features.contains(feature);
}
/**
* Returns a product that has the given thing type UID.
*
* @param uid a thing type UID
* @return a product that has the given thing type UID
* @throws IllegalArgumentException when <code>uid</code> is not a valid LIFX thing type UID
*/
public static Product getLikelyProduct(ThingTypeUID uid) throws IllegalArgumentException {
for (Product product : Product.values()) {
if (product.getThingTypeUID().equals(uid)) {
return product;
}
}
throw new IllegalArgumentException(uid + " is not a valid product thing type UID");
}
/**
* Returns the product that has the given product ID.
*
* @param id the product ID
* @return the product that has the given product ID
* @throws IllegalArgumentException when <code>id</code> is not a valid LIFX product ID
*/
public static Product getProductFromProductID(long id) throws IllegalArgumentException {
for (Product product : Product.values()) {
if (product.id == id) {
return product;
}
}
throw new IllegalArgumentException(id + " is not a valid product ID");
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetColorRequest extends Packet {
public static final int TYPE = 0x66;
public static final Field<ByteBuffer> FIELD_STREAM = new ByteField(1);
public static final HSBKField FIELD_COLOR = new HSBKField();
public static final Field<Long> FIELD_FADE_TIME = new UInt32Field().little();
private ByteBuffer stream;
private HSBK color;
private long fadeTime;
public ByteBuffer getStream() {
return stream;
}
public HSBK getColor() {
return color;
}
public long getFadeTime() {
return fadeTime;
}
public SetColorRequest() {
stream = ByteBuffer.allocate(1);
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetColorRequest(HSBK color, long fadeTime) {
this();
this.color = color;
this.fadeTime = fadeTime;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 13;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
stream = FIELD_STREAM.value(bytes);
color = FIELD_COLOR.value(bytes);
fadeTime = FIELD_FADE_TIME.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_STREAM.bytes(stream)).put(FIELD_COLOR.bytes(color))
.put(FIELD_FADE_TIME.bytes(fadeTime));
}
@Override
public int[] expectedResponses() {
return new int[] { StateResponse.TYPE };
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import static org.openhab.binding.lifx.internal.LifxBindingConstants.*;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
public class SetColorZonesRequest extends Packet {
public static final int TYPE = 0x1F5;
public static final Field<Integer> FIELD_START_INDEX = new UInt8Field();
public static final Field<Integer> FIELD_END_INDEX = new UInt8Field();
public static final HSBKField FIELD_COLOR = new HSBKField();
public static final Field<Long> FIELD_FADE_TIME = new UInt32Field().little();
public static final Field<Integer> FIELD_APPLY = new UInt8Field();
private int startIndex = MIN_ZONE_INDEX;
private int endIndex = MAX_ZONE_INDEX;
private HSBK color;
private long fadeTime;
private ApplicationRequest apply;
public HSBK getColor() {
return color;
}
public int getHue() {
return color.getHue();
}
public int getSaturation() {
return color.getSaturation();
}
public int getBrightness() {
return color.getBrightness();
}
public int getKelvin() {
return color.getKelvin();
}
public long getFadeTime() {
return fadeTime;
}
public ApplicationRequest getApply() {
return apply;
}
public SetColorZonesRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetColorZonesRequest(HSBK color, long fadeTime, ApplicationRequest apply) {
this(MIN_ZONE_INDEX, MAX_ZONE_INDEX, color, fadeTime, apply);
}
public SetColorZonesRequest(int index, HSBK color, long fadeTime, ApplicationRequest apply) {
this(index, index, color, fadeTime, apply);
}
public SetColorZonesRequest(int startIndex, int endIndex, HSBK color, long fadeTime, ApplicationRequest apply) {
this();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.color = color;
this.fadeTime = fadeTime;
this.apply = apply;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 15;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
startIndex = FIELD_START_INDEX.value(bytes);
endIndex = FIELD_END_INDEX.value(bytes);
color = FIELD_COLOR.value(bytes);
fadeTime = FIELD_FADE_TIME.value(bytes);
apply = ApplicationRequest.fromValue(FIELD_APPLY.value(bytes));
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_START_INDEX.bytes(startIndex))
.put(FIELD_END_INDEX.bytes(endIndex)).put(FIELD_COLOR.bytes(color)).put(FIELD_FADE_TIME.bytes(fadeTime))
.put(FIELD_APPLY.bytes(apply.getValue()));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetDimAbsoluteRequest extends Packet {
public static final int TYPE = 0x68;
public static final Field<Integer> FIELD_DIM = new UInt16Field().little();
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
private int dim;
private long duration;
public int getDim() {
return dim;
}
public long getDuration() {
return duration;
}
public SetDimAbsoluteRequest() {
}
public SetDimAbsoluteRequest(int dim, long duration) {
this.dim = dim;
this.duration = duration;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 6;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
dim = FIELD_DIM.value(bytes);
duration = FIELD_DURATION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_DIM.bytes(dim)).put(FIELD_DURATION.bytes(duration));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.StringField;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetLabelRequest extends Packet {
public static final int TYPE = 0x18;
public static final Field<String> FIELD_LABEL = new StringField(32).utf8();
private String label;
public SetLabelRequest() {
setTagged(false);
setAddressable(true);
setAckRequired(true);
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 32;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
label = FIELD_LABEL.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return FIELD_LABEL.bytes(label);
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
/**
* @author Wouter Born - Support LIFX 2016 product line-up and infrared functionality
*/
public class SetLightInfraredRequest extends Packet {
public static final int TYPE = 0x7A;
public static final Field<Integer> FIELD_STATE = new UInt16Field().little();
private int infrared;
public int getInfrared() {
return infrared;
}
public SetLightInfraredRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetLightInfraredRequest(int infrared) {
this();
this.infrared = infrared;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
infrared = FIELD_STATE.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(2).put(FIELD_STATE.bytes(infrared));
}
@Override
public int[] expectedResponses() {
return new int[] { StateLightInfraredResponse.TYPE };
}
}

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetLightPowerRequest extends Packet {
public static final int TYPE = 0x75;
public static final Field<Integer> FIELD_STATE = new UInt16Field();
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
private PowerState state;
private long duration;
public PowerState getState() {
return state;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public SetLightPowerRequest() {
state = PowerState.OFF;
this.duration = 0;
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetLightPowerRequest(PowerState state) {
this.state = state;
this.duration = 0;
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 6;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
state = PowerState.fromValue(FIELD_STATE.value(bytes));
duration = FIELD_DURATION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_STATE.bytes(state.getValue()))
.put(FIELD_DURATION.bytes(duration));
}
@Override
public int[] expectedResponses() {
return new int[] { StateLightPowerResponse.TYPE };
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetPowerRequest extends Packet {
public static final int TYPE = 0x15;
public static final Field<Integer> FIELD_STATE = new UInt16Field();
private PowerState state;
public PowerState getState() {
return state;
}
public SetPowerRequest() {
state = PowerState.OFF;
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetPowerRequest(PowerState state) {
this.state = state;
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
state = PowerState.fromValue(FIELD_STATE.value(bytes));
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(2).put(FIELD_STATE.bytes(state.getValue()));
}
@Override
public int[] expectedResponses() {
return new int[] { StatePowerResponse.TYPE };
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class SetTagsRequest extends Packet {
public static final int TYPE = 0x1B;
public static final Field<Long> FIELD_TAGS = new UInt64Field();
private long tags;
public long getTags() {
return tags;
}
public void setTags(long tags) {
this.tags = tags;
}
public SetTagsRequest() {
}
public SetTagsRequest(long tags) {
this.tags = tags;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 8;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
tags = FIELD_TAGS.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_TAGS.bytes(tags));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,153 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of SetTileEffect packet
*
* @author Pawel Pieczul - Initial contribution
*/
public class SetTileEffectRequest extends Packet {
public static final int TYPE = 0x2CF;
// size.from.to....what
// ------------------------------
// 1....0....0.....reserved
// 1....1....1.....reserved
// 4....2....5.....instance ID
// 1....6....6.....effect type
// 4....7....10....speed
// 8....11...18....duration
// 4....19...22....reserved
// 4....23...26....reserved
// 32...27...58....parameters (8*32 bits)
// 1....59...59....palette count
// 128..60...187...palette (16*8 bits)
private static final Field<Integer> FIELD_RESERVED_0 = new UInt8Field();
private static final Field<Integer> FIELD_RESERVED_1 = new UInt8Field();
private static final Field<Long> FIELD_INSTANCE_ID = new UInt32Field().little();
private static final Field<Integer> FIELD_TYPE = new UInt8Field();
private static final Field<Long> FIELD_SPEED = new UInt32Field().little();
private static final Field<Long> FIELD_DURATION = new UInt64Field().little();
private static final Field<Long> FIELD_RESERVED_19_TO_26 = new UInt32Field().little();
private static final Field<Long> FIELD_PARAMETER_27_TO_58 = new UInt32Field().little();
private static final Field<Integer> FIELD_PALETTE_COUNT = new UInt8Field();
private static final Field<HSBK> FIELD_PALETTE_60_TO_187 = new HSBKField();
private final Logger logger = LoggerFactory.getLogger(SetTileEffectRequest.class);
private Integer reserved0 = 0;
private Integer reserved1 = 0;
private Long reserved19to22 = 0L;
private Long reserved23to26 = 0L;
private Effect effect;
public SetTileEffectRequest() {
setTagged(false);
setAddressable(true);
setAckRequired(true);
}
public SetTileEffectRequest(Effect effect) {
this();
this.effect = effect;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 188;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
reserved0 = FIELD_RESERVED_0.value(bytes);
reserved1 = FIELD_RESERVED_1.value(bytes);
FIELD_INSTANCE_ID.value(bytes);
Integer effectType = FIELD_TYPE.value(bytes);
Long speed = FIELD_SPEED.value(bytes);
Long duration = FIELD_DURATION.value(bytes);
reserved19to22 = FIELD_RESERVED_19_TO_26.value(bytes);
reserved23to26 = FIELD_RESERVED_19_TO_26.value(bytes);
Long[] parameters = new Long[8];
for (int i = 0; i < parameters.length; i++) {
parameters[i] = FIELD_PARAMETER_27_TO_58.value(bytes);
}
Integer paletteCount = FIELD_PALETTE_COUNT.value(bytes);
HSBK[] palette = new HSBK[paletteCount];
for (int i = 0; i < palette.length; i++) {
palette[i] = FIELD_PALETTE_60_TO_187.value(bytes);
}
try {
effect = new Effect(effectType, speed, duration, palette);
} catch (IllegalArgumentException e) {
logger.debug("Wrong effect type received: {}", effectType);
effect = null;
}
}
@Override
protected ByteBuffer packetBytes() {
ByteBuffer buffer = ByteBuffer.allocate(packetLength());
buffer.put(FIELD_RESERVED_0.bytes(reserved0));
buffer.put(FIELD_RESERVED_1.bytes(reserved1));
buffer.put(FIELD_INSTANCE_ID.bytes(0L));
buffer.put(FIELD_TYPE.bytes(effect.getType().intValue()));
buffer.put(FIELD_SPEED.bytes(effect.getSpeed()));
buffer.put(FIELD_DURATION.bytes(effect.getDuration()));
buffer.put(FIELD_RESERVED_19_TO_26.bytes(reserved19to22));
buffer.put(FIELD_RESERVED_19_TO_26.bytes(reserved23to26));
for (int i = 0; i < 8; i++) {
buffer.put(FIELD_PARAMETER_27_TO_58.bytes(0L));
}
buffer.put(FIELD_PALETTE_COUNT.bytes(effect.getPalette().length));
HSBK[] palette = effect.getPalette();
for (int i = 0; i < palette.length; i++) {
buffer.put(FIELD_PALETTE_60_TO_187.bytes(palette[i]));
}
HSBK hsbkZero = new HSBK(0, 0, 0, 0);
for (int i = 0; i < 16 - palette.length; i++) {
buffer.put(FIELD_PALETTE_60_TO_187.bytes(hsbkZero));
}
if (logger.isDebugEnabled()) {
logger.debug("SetTileEffectRequest: type={}, speed={}, duration={}, palette_count={}", effect.getType(),
effect.getSpeed(), effect.getDuration(), palette.length);
for (int i = 0; i < palette.length; i++) {
logger.debug("SetTileEffectRequest palette[{}] = {}", i, palette[i]);
}
}
return buffer;
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
/**
* The signal strength of a light.
*
* @author Wouter Born - Add signal strength channel
*/
public class SignalStrength {
private double milliWatts;
public SignalStrength(double milliWatts) {
this.milliWatts = milliWatts;
}
/**
* Returns the signal strength.
*
* @return the signal strength in milliwatts (mW).
*/
public double getMilliWatts() {
return milliWatts;
}
/**
* Returns the signal strength as a quality percentage:
* <ul>
* <li>RSSI <= -100: returns 0
* <li>-100 < RSSI < -50: returns a value between 0 and 1 (linearly distributed)
* <li>RSSI >= -50: returns 1
* <ul>
*
* @return a value between 0 and 1. 0 being worst strength and 1
* being best strength.
*/
public double toQualityPercentage() {
return Math.min(100, Math.max(0, 2 * (toRSSI() + 100))) / 100;
}
/**
* Returns the signal strength as a quality rating.
*
* @return one of the values: 0, 1, 2, 3 or 4. 0 being worst strength and 4
* being best strength.
*/
public byte toQualityRating() {
return (byte) Math.round(toQualityPercentage() * 4);
}
/**
* Returns the received signal strength indicator (RSSI).
*
* @return a value <= 0. 0 being best strength and more negative values indicate worser strength.
*/
public double toRSSI() {
return 10 * Math.log10(milliWatts);
}
@Override
public String toString() {
return "SignalStrength [milliWatts=" + milliWatts + ", rssi=" + Math.round(toRSSI()) + "]";
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.StringField;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateGroupResponse extends Packet {
public static final int TYPE = 0x35;
public static final Field<ByteBuffer> FIELD_GROUP = new ByteField(16);
public static final Field<String> FIELD_LABEL = new StringField(32).utf8();
public static final Field<Long> FIELD_UPDATED_AT = new UInt64Field().little();
private ByteBuffer group;
private String label;
private long updatedAt;
public static int getType() {
return TYPE;
}
public ByteBuffer getGroup() {
return group;
}
public void setGroup(ByteBuffer group) {
this.group = group;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(long updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 14;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
group = FIELD_GROUP.value(bytes);
label = FIELD_LABEL.value(bytes);
updatedAt = FIELD_UPDATED_AT.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_GROUP.bytes(group)).put(FIELD_LABEL.bytes(label))
.put(FIELD_UPDATED_AT.bytes(updatedAt));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
import org.openhab.binding.lifx.internal.fields.Version;
import org.openhab.binding.lifx.internal.fields.VersionField;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateHostFirmwareResponse extends Packet {
public static final int TYPE = 0x0F;
public static final Field<Long> FIELD_BUILD = new UInt64Field().little();
public static final Field<Long> FIELD_RESERVED = new UInt64Field().little();
public static final Field<Version> FIELD_VERSION = new VersionField().little();
private long build;
private long reserved;
private Version version;
public long getBuild() {
return build;
}
public void setBuild(long build) {
this.build = build;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public StateHostFirmwareResponse() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 20;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
build = FIELD_BUILD.value(bytes);
reserved = FIELD_RESERVED.value(bytes);
version = FIELD_VERSION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_BUILD.bytes(build)).put(FIELD_RESERVED.bytes(reserved))
.put(FIELD_VERSION.bytes(version));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.FloatField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateHostInfoResponse extends Packet {
public static final int TYPE = 0x0D;
public static final Field<Float> FIELD_SIGNAL = new FloatField().little();
public static final Field<Long> FIELD_TX = new UInt32Field().little();
public static final Field<Long> FIELD_RX = new UInt32Field().little();
public static final Field<ByteBuffer> FIELD_RESERVED_5 = new ByteField(2);
private float signal;
private long tx;
private long rx;
public SignalStrength getSignalStrength() {
return new SignalStrength(signal);
}
public long getTx() {
return tx;
}
public long getRx() {
return rx;
}
public StateHostInfoResponse() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 14;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
signal = FIELD_SIGNAL.value(bytes);
tx = FIELD_TX.value(bytes);
rx = FIELD_RX.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_SIGNAL.bytes(signal)).put(FIELD_TX.bytes(tx))
.put(FIELD_RX.bytes(rx));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateInfoResponse extends Packet {
public static final int TYPE = 0x23;
public static final Field<Long> FIELD_TIME = new UInt64Field().little();
public static final Field<Long> FIELD_UPTIME = new UInt64Field().little();
public static final Field<Long> FIELD_DOWNTIME = new UInt64Field().little();
private long time;
private long uptime;
private long downtime;
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public long getUptime() {
return uptime;
}
public void setUptime(long uptime) {
this.uptime = uptime;
}
public long getDowntime() {
return downtime;
}
public void setDowntime(long downtime) {
this.downtime = downtime;
}
public StateInfoResponse() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 24;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
time = FIELD_TIME.value(bytes);
uptime = FIELD_UPTIME.value(bytes);
downtime = FIELD_DOWNTIME.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_TIME.bytes(time)).put(FIELD_UPTIME.bytes(uptime))
.put(FIELD_DOWNTIME.bytes(downtime));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.StringField;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateLabelResponse extends Packet {
public static final int TYPE = 0x19;
public static final Field<String> FIELD_LABEL = new StringField(32).utf8();
private String label;
public static int getType() {
return TYPE;
}
public static Field<String> getFieldLabel() {
return FIELD_LABEL;
}
public String getLabel() {
return label;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 32;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
label = FIELD_LABEL.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return FIELD_LABEL.bytes(label);
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
/**
* @author Wouter Born - Support LIFX 2016 product line-up and infrared functionality
*/
public class StateLightInfraredResponse extends Packet {
public static final int TYPE = 0x79;
public static final Field<Integer> FIELD_STATE = new UInt16Field().little();
private int infrared;
public int getInfrared() {
return infrared;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
infrared = FIELD_STATE.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_STATE.bytes(infrared));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateLightPowerResponse extends Packet {
public static final int TYPE = 0x76;
public static final Field<Integer> FIELD_STATE = new UInt16Field();
private PowerState state;
public PowerState getState() {
return state;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
int stateValue = FIELD_STATE.value(bytes);
state = PowerState.fromValue(stateValue);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_STATE.bytes(state.getValue()));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.ByteField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.StringField;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateLocationResponse extends Packet {
public static final int TYPE = 0x32;
public static final Field<ByteBuffer> FIELD_LOCATION = new ByteField(16);
public static final Field<String> FIELD_LABEL = new StringField(32).utf8();
public static final Field<Long> FIELD_UPDATED_AT = new UInt64Field().little();
private ByteBuffer location;
private String label;
private long updatedAt;
public static int getType() {
return TYPE;
}
public ByteBuffer getLocation() {
return location;
}
public void setLocation(ByteBuffer location) {
this.location = location;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(long updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 14;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
location = FIELD_LOCATION.value(bytes);
label = FIELD_LABEL.value(bytes);
updatedAt = FIELD_UPDATED_AT.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_LOCATION.bytes(location)).put(FIELD_LABEL.bytes(label))
.put(FIELD_UPDATED_AT.bytes(updatedAt));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
public class StateMultiZoneResponse extends Packet {
public static final int TYPE = 0x1FA;
public static final int ZONES = 8;
public static final Field<Integer> FIELD_COUNT = new UInt8Field();
public static final Field<Integer> FIELD_INDEX = new UInt8Field();
public static final HSBKField FIELD_COLOR = new HSBKField();
private int count;
private int index;
private HSBK[] colors = new HSBK[ZONES];
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("count=");
sb.append(count);
sb.append(", index=");
sb.append(index);
for (int i = 0; i < ZONES; i++) {
sb.append(", ");
sb.append(colors[i].toString("color[" + i + "]"));
}
return sb.toString();
}
public int getCount() {
return count;
}
public int getIndex() {
return index;
}
public HSBK[] getColors() {
return colors;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 66;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
count = FIELD_COUNT.value(bytes);
index = FIELD_INDEX.value(bytes);
for (int i = 0; i < ZONES; i++) {
colors[i] = FIELD_COLOR.value(bytes);
}
}
@Override
protected ByteBuffer packetBytes() {
ByteBuffer bb = ByteBuffer.allocate(packetLength()).put(FIELD_COUNT.bytes(count)).put(FIELD_INDEX.bytes(index));
for (int i = 0; i < ZONES; i++) {
bb.put(FIELD_COLOR.bytes(colors[i]));
}
return bb;
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StatePowerResponse extends Packet {
public static final int TYPE = 0x16;
public static final Field<Integer> FIELD_STATE = new UInt16Field();
private PowerState state;
public PowerState getState() {
return state;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 2;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
int stateValue = FIELD_STATE.value(bytes);
state = PowerState.fromValue(stateValue);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_STATE.bytes(state.getValue()));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.StringField;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateResponse extends Packet {
public static final int TYPE = 0x6B;
public static final HSBKField FIELD_COLOR = new HSBKField();
public static final Field<Integer> FIELD_DIM = new UInt16Field().little();
public static final Field<Integer> FIELD_POWER = new UInt16Field();
public static final Field<String> FIELD_LABEL = new StringField(32);
public static final Field<Long> FIELD_TAGS = new UInt64Field();
private HSBK color;
private int dim;
private PowerState power;
private String label;
private long tags;
@Override
public String toString() {
return color.toString("color") + ", dim=" + dim + ", power=" + power + ", label=" + label;
}
public HSBK getColor() {
return color;
}
public int getDim() {
return dim;
}
public PowerState getPower() {
return power;
}
public String getLabel() {
return label;
}
public long getTags() {
return tags;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 52;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
color = FIELD_COLOR.value(bytes);
dim = FIELD_DIM.value(bytes);
power = PowerState.fromValue(FIELD_POWER.value(bytes));
label = FIELD_LABEL.value(bytes);
tags = FIELD_TAGS.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_COLOR.bytes(color)).put(FIELD_DIM.bytes(dim))
.put(FIELD_POWER.bytes(power.getValue())).put(FIELD_LABEL.bytes(label)).put(FIELD_TAGS.bytes(tags));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.LittleField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateServiceResponse extends Packet {
public static final int TYPE = 0x03;
public static final Field<Integer> FIELD_SERVICE = new UInt8Field();
public static final Field<Long> FIELD_PORT = new LittleField<>(new UInt32Field());
private int service;
private long port;
public int getService() {
return service;
}
public long getPort() {
return port;
}
public StateServiceResponse() {
}
@Override
protected void parsePacket(ByteBuffer bytes) {
service = FIELD_SERVICE.value(bytes);
port = FIELD_PORT.value(bytes);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 5;
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_SERVICE.bytes(service)).put(FIELD_PORT.bytes(port));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of StateTileEffect packet
*
* @author Pawel Pieczul - Initial Contribution
*/
public class StateTileEffectResponse extends Packet {
public static final int TYPE = 0x2D0;
// size.from.to....what
// ------------------------------
// 1....0....0.....reserved
// 4....1....4.....instance ID
// 1....5....5.....effect type
// 4....6....9.....speed
// 8....10...17....duration
// 4....18...21....reserved
// 4....22...25....reserved
// 32...26...57....parameters (8*32 bits)
// 1....58...58....palette count
// 128..59...186...palette (16*8 bits)
private static final Field<Integer> FIELD_RESERVED_0 = new UInt8Field();
private static final Field<Long> FIELD_INSTANCE_ID = new UInt32Field().little();
private static final Field<Integer> FIELD_TYPE = new UInt8Field();
private static final Field<Long> FIELD_SPEED = new UInt32Field().little();
private static final Field<Long> FIELD_DURATION = new UInt64Field().little();
private static final Field<Long> FIELD_RESERVED_18_TO_25 = new UInt32Field().little();
private static final Field<Long> FIELD_PARAMETER_26_TO_57 = new UInt32Field().little();
private static final Field<Integer> FIELD_PALETTE_COUNT = new UInt8Field();
private static final Field<HSBK> FIELD_PALETTE_59_TO_186 = new HSBKField();
private Integer reserved0 = 0;
private Long reserved18to21 = 0L;
private Long reserved22to25 = 0L;
private Effect effect;
private final Logger logger = LoggerFactory.getLogger(StateTileEffectResponse.class);
public Effect getEffect() {
return effect;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 187;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
reserved0 = FIELD_RESERVED_0.value(bytes);
Long instanceId = FIELD_INSTANCE_ID.value(bytes);
Integer effectType = FIELD_TYPE.value(bytes);
Long speed = FIELD_SPEED.value(bytes);
Long duration = FIELD_DURATION.value(bytes);
reserved18to21 = FIELD_RESERVED_18_TO_25.value(bytes);
reserved22to25 = FIELD_RESERVED_18_TO_25.value(bytes);
Long[] parameters = new Long[8];
for (int i = 0; i < 8; i++) {
parameters[i] = FIELD_PARAMETER_26_TO_57.value(bytes);
}
Integer paletteCount = FIELD_PALETTE_COUNT.value(bytes);
HSBK[] palette = new HSBK[paletteCount];
for (int i = 0; i < palette.length; i++) {
palette[i] = FIELD_PALETTE_59_TO_186.value(bytes);
}
try {
effect = new Effect(effectType, speed, duration, palette);
} catch (IllegalArgumentException e) {
logger.debug("Wrong effect type received: {}", effectType);
effect = null;
}
if (logger.isDebugEnabled()) {
logger.debug("StateTileEffectResponse: instanceId={}, type={}, speed={}, duration={}, palette_count={}",
instanceId, effectType, speed, duration, paletteCount);
logger.debug("StateTileEffectResponse parameters=[{}, {}, {}, {}, {}, {}, {}, {}]", parameters[0],
parameters[1], parameters[2], parameters[3], parameters[4], parameters[5], parameters[6],
parameters[7]);
for (int i = 0; i < palette.length; i++) {
logger.debug("StateTileEffectResponse palette[{}] = {}", i, palette[i]);
}
}
}
@Override
protected ByteBuffer packetBytes() {
ByteBuffer buffer = ByteBuffer.allocate(packetLength());
buffer.put(FIELD_RESERVED_0.bytes(reserved0));
buffer.put(FIELD_INSTANCE_ID.bytes(0L));
buffer.put(FIELD_TYPE.bytes(effect.getType().intValue()));
buffer.put(FIELD_SPEED.bytes(effect.getSpeed()));
buffer.put(FIELD_DURATION.bytes(effect.getDuration()));
buffer.put(FIELD_RESERVED_18_TO_25.bytes(reserved18to21));
buffer.put(FIELD_RESERVED_18_TO_25.bytes(reserved22to25));
for (int i = 0; i < 8; i++) {
buffer.put(FIELD_PARAMETER_26_TO_57.bytes(0L));
}
HSBK[] palette = effect.getPalette();
buffer.put(FIELD_PALETTE_COUNT.bytes(palette.length));
for (int i = 0; i < palette.length; i++) {
buffer.put(FIELD_PALETTE_59_TO_186.bytes(palette[i]));
}
HSBK hsbkZero = new HSBK(0, 0, 0, 0);
for (int i = 0; i < 16 - palette.length; i++) {
buffer.put(FIELD_PALETTE_59_TO_186.bytes(hsbkZero));
}
return buffer;
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateVersionResponse extends Packet {
public static final int TYPE = 0x21;
public static final Field<Long> FIELD_VENDOR = new UInt32Field().little();
public static final Field<Long> FIELD_PRODUCT = new UInt32Field().little();
public static final Field<Long> FIELD_VERSION = new UInt32Field().little();
private long vendor;
private long product;
private long version;
public long getVendor() {
return vendor;
}
public void setVendor(long build) {
this.vendor = build;
}
public long getProduct() {
return product;
}
public void setProduct(long product) {
this.product = product;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public StateVersionResponse() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 12;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
vendor = FIELD_VENDOR.value(bytes);
product = FIELD_PRODUCT.value(bytes);
version = FIELD_VERSION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_VENDOR.bytes(vendor)).put(FIELD_PRODUCT.bytes(product))
.put(FIELD_VERSION.bytes(version));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
import org.openhab.binding.lifx.internal.fields.Version;
import org.openhab.binding.lifx.internal.fields.VersionField;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateWifiFirmwareResponse extends Packet {
public static final int TYPE = 0x13;
public static final Field<Long> FIELD_BUILD = new UInt64Field().little();
public static final Field<Long> FIELD_RESERVED = new UInt64Field().little();
public static final Field<Version> FIELD_VERSION = new VersionField().little();
private long build;
private long reserved;
private Version version;
public long getBuild() {
return build;
}
public void setBuild(long build) {
this.build = build;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public StateWifiFirmwareResponse() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 20;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
build = FIELD_BUILD.value(bytes);
reserved = FIELD_RESERVED.value(bytes);
version = FIELD_VERSION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_BUILD.bytes(build)).put(FIELD_RESERVED.bytes(reserved))
.put(FIELD_VERSION.bytes(version));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.FloatField;
import org.openhab.binding.lifx.internal.fields.UInt16Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class StateWifiInfoResponse extends Packet {
public static final int TYPE = 0x11;
public static final Field<Float> FIELD_SIGNAL = new FloatField().little();
public static final Field<Long> FIELD_RX = new UInt32Field().little();
public static final Field<Long> FIELD_TX = new UInt32Field().little();
public static final Field<Integer> FIELD_TEMP = new UInt16Field();
private float signal;
private long rx;
private long tx;
private int mcuTemperature;
public SignalStrength getSignalStrength() {
return new SignalStrength(signal);
}
public long getRx() {
return rx;
}
public long getTx() {
return tx;
}
public int getMcuTemperature() {
return mcuTemperature;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 14;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
signal = FIELD_SIGNAL.value(bytes);
rx = FIELD_RX.value(bytes);
tx = FIELD_TX.value(bytes);
mcuTemperature = FIELD_TEMP.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_SIGNAL.bytes(signal)).put(FIELD_RX.bytes(rx))
.put(FIELD_TX.bytes(tx)).put(FIELD_TEMP.bytes(mcuTemperature));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.HSBK;
import org.openhab.binding.lifx.internal.fields.HSBKField;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Wouter Born - Add support for MultiZone light control
*/
public class StateZoneResponse extends Packet {
public static final int TYPE = 0x1F7;
public static final Field<Integer> FIELD_COUNT = new UInt8Field();
public static final Field<Integer> FIELD_INDEX = new UInt8Field();
public static final HSBKField FIELD_COLOR = new HSBKField();
private int count;
private int index;
private HSBK color;
@Override
public String toString() {
return "count=" + count + ", index=" + index + ", " + color.toString("color");
}
public int getCount() {
return count;
}
public int getIndex() {
return index;
}
public HSBK getColor() {
return color;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 10;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
count = FIELD_COUNT.value(bytes);
index = FIELD_INDEX.value(bytes);
color = FIELD_COLOR.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_COUNT.bytes(count)).put(FIELD_INDEX.bytes(index))
.put(FIELD_COLOR.bytes(color));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.StringField;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class TagLabelsResponse extends Packet {
public static final int TYPE = 0x1F;
public static final Field<Long> FIELD_TAGS = new UInt64Field();
public static final Field<String> FIELD_LABEL = new StringField(32).utf8();
private long tags;
private String label;
public long getTags() {
return tags;
}
public String getLabel() {
return label;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 40;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
tags = FIELD_TAGS.value(bytes);
label = FIELD_LABEL.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_TAGS.bytes(tags)).put(FIELD_LABEL.bytes(label));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.lifx.internal.protocol;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt64Field;
/**
* @author Tim Buckley - Initial Contribution
* @author Karel Goderis - Enhancement for the V2 LIFX Firmware and LAN Protocol Specification
*/
public class TagsResponse extends Packet {
public static final int TYPE = 0x1C;
public static final Field<Long> FIELD_TAGS = new UInt64Field();
private long tags;
public long getTags() {
return tags;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 8;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
tags = FIELD_TAGS.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_TAGS.bytes(tags));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

Some files were not shown because too many files have changed in this diff Show More