added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.lifx/.classpath
Normal file
32
bundles/org.openhab.binding.lifx/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.lifx/.project
Normal file
23
bundles/org.openhab.binding.lifx/.project
Normal 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>
|
||||
13
bundles/org.openhab.binding.lifx/NOTICE
Normal file
13
bundles/org.openhab.binding.lifx/NOTICE
Normal 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
|
||||
236
bundles/org.openhab.binding.lifx/README.md
Normal file
236
bundles/org.openhab.binding.lifx/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
}
|
||||
}
|
||||
```
|
||||
BIN
bundles/org.openhab.binding.lifx/doc/lifx_e27.jpg
Normal file
BIN
bundles/org.openhab.binding.lifx/doc/lifx_e27.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
17
bundles/org.openhab.binding.lifx/pom.xml
Normal file
17
bundles/org.openhab.binding.lifx/pom.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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()) + "]";
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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[] {};
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user