added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.nanoleaf-${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-nanoleaf" description="Nanoleaf Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.nanoleaf/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if request to Nanoleaf OpenAPI does not expect the given content
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafBadRequestException extends NanoleafException {
|
||||
|
||||
private static final long serialVersionUID = -6941678941424573256L;
|
||||
|
||||
public NanoleafBadRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "nanoleaf";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
|
||||
public static final ThingTypeUID THING_TYPE_LIGHT_PANEL = new ThingTypeUID(BINDING_ID, "lightpanel");
|
||||
|
||||
// Controller configuration settings
|
||||
public static final String CONFIG_ADDRESS = "address";
|
||||
public static final String CONFIG_PORT = "port";
|
||||
public static final String CONFIG_AUTH_TOKEN = "authToken";
|
||||
public static final String CONFIG_DEVICE_TYPE_CANVAS = "canvas";
|
||||
public static final String CONFIG_DEVICE_TYPE_LIGHTPANELS = "lightPanels";
|
||||
|
||||
// Panel configuration settings
|
||||
public static final String CONFIG_PANEL_ID = "id";
|
||||
|
||||
// List of controller channels
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_COLOR = "color";
|
||||
public static final String CHANNEL_COLOR_TEMPERATURE = "colorTemperature";
|
||||
public static final String CHANNEL_COLOR_TEMPERATURE_ABS = "colorTemperatureAbs";
|
||||
public static final String CHANNEL_COLOR_MODE = "colorMode";
|
||||
public static final String CHANNEL_EFFECT = "effect";
|
||||
public static final String CHANNEL_RHYTHM_STATE = "rhythmState";
|
||||
public static final String CHANNEL_RHYTHM_ACTIVE = "rhythmActive";
|
||||
public static final String CHANNEL_RHYTHM_MODE = "rhythmMode";
|
||||
public static final String CHANNEL_PANEL_LAYOUT = "panelLayout";
|
||||
|
||||
// List of light panel channels
|
||||
public static final String CHANNEL_PANEL_COLOR = "panelColor";
|
||||
public static final String CHANNEL_PANEL_SINGLE_TAP = "singleTap";
|
||||
public static final String CHANNEL_PANEL_DOUBLE_TAP = "doubleTap";
|
||||
|
||||
// Nanoleaf OpenAPI URLs
|
||||
public static final String API_V1_BASE_URL = "/api/v1";
|
||||
public static final String API_GET_CONTROLLER_INFO = "/";
|
||||
public static final String API_ADD_USER = "/new";
|
||||
public static final String API_EVENTS = "/events";
|
||||
public static final String API_DELETE_USER = "";
|
||||
public static final String API_SET_VALUE = "/state";
|
||||
public static final String API_EFFECT = "/effects";
|
||||
public static final String API_RHYTHM_MODE = "/rhythm/rhythmMode";
|
||||
|
||||
// Nanoleaf model IDs and minimum required firmware versions
|
||||
public static final String API_MIN_FW_VER_LIGHTPANELS = "1.5.0";
|
||||
public static final String API_MIN_FW_VER_CANVAS = "1.1.0";
|
||||
public static final String MODEL_ID_LIGHTPANELS = "NL22";
|
||||
public static final String MODEL_ID_CANVAS = "NL29";
|
||||
public static final String DEVICE_TYPE_LIGHTPANELS = "lightPanels";
|
||||
public static final String DEVICE_TYPE_CANVAS = "canvas";
|
||||
|
||||
// mDNS discovery service type
|
||||
// see http://forum.nanoleaf.me/docs/openapi#_gf9l5guxt8r0
|
||||
public static final String SERVICE_TYPE = "_nanoleafapi._tcp.local.";
|
||||
|
||||
// Effect/scene name for static color
|
||||
public static final String EFFECT_NAME_STATIC_COLOR = "*Dynamic*";
|
||||
|
||||
// Color channels increase/decrease brightness step size
|
||||
public static final int BRIGHTNESS_STEP_SIZE = 5;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nanoleaf.internal.model.ControllerInfo;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* A {@link NanoleafControllerListener} is notified by the controller thing handler.
|
||||
* A listener may use it to discover additional things connected to the controller (bridge), such as individual panels.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public interface NanoleafControllerListener {
|
||||
|
||||
/**
|
||||
* This method is called after the bridge thing handler fetched the controller info
|
||||
*
|
||||
* @param bridge the Nanoleaf controller.
|
||||
* @param controllerInfo the controller data with panel information
|
||||
*/
|
||||
void onControllerInfoFetched(ThingUID bridge, ControllerInfo controllerInfo);
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* General binding exception if something goes wrong.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NanoleafException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NanoleafException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public NanoleafException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService;
|
||||
import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler;
|
||||
import org.openhab.binding.nanoleaf.internal.handler.NanoleafPanelHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafHandlerFactory} is responsible for creating the controller (bridge)
|
||||
* and panel (thing) handlers.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.nanoleaf", service = ThingHandlerFactory.class)
|
||||
public class NanoleafHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_LIGHT_PANEL, THING_TYPE_CONTROLLER).collect(Collectors.toSet()));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NanoleafHandlerFactory.class);
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public NanoleafHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_CONTROLLER.equals(thingTypeUID)) {
|
||||
NanoleafControllerHandler handler = new NanoleafControllerHandler((Bridge) thing, httpClient);
|
||||
registerDiscoveryService(handler);
|
||||
logger.debug("Nanoleaf controller handler created.");
|
||||
return handler;
|
||||
} else if (THING_TYPE_LIGHT_PANEL.equals(thingTypeUID)) {
|
||||
NanoleafPanelHandler handler = new NanoleafPanelHandler(thing, httpClient);
|
||||
logger.debug("Nanoleaf panel handler created.");
|
||||
return handler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof NanoleafControllerHandler) {
|
||||
unregisterDiscoveryService(thingHandler.getThing());
|
||||
logger.debug("Nanoleaf controller handler removed.");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(NanoleafControllerHandler bridgeHandler) {
|
||||
NanoleafPanelsDiscoveryService discoveryService = new NanoleafPanelsDiscoveryService(bridgeHandler);
|
||||
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
logger.debug("Discovery service for panels registered.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private synchronized void unregisterDiscoveryService(Thing thing) {
|
||||
@Nullable
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thing.getUID());
|
||||
// would require null check but "if (response!=null)" throws warning on comoile time :´-(
|
||||
if (serviceReg != null) {
|
||||
serviceReg.unregister();
|
||||
}
|
||||
logger.debug("Discovery service for panels removed.");
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if request to Nanoleaf OpenAPI has been interrupted which is normally intended
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafInterruptedException extends NanoleafException {
|
||||
|
||||
private static final long serialVersionUID = -6941678941424234257L;
|
||||
|
||||
public NanoleafInterruptedException(String message, InterruptedException interruptedException) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if request to Nanoleaf OpenAPI does not return any data
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafNotFoundException extends NanoleafException {
|
||||
|
||||
private static final long serialVersionUID = -6941678941424573256L;
|
||||
|
||||
public NanoleafNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception if request to Nanoleaf OpenAPI is unauthorized (e.g. invalid or missing auth token)
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafUnauthorizedException extends NanoleafException {
|
||||
|
||||
private static final long serialVersionUID = -6941678941424573257L;
|
||||
|
||||
public NanoleafUnauthorizedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link OpenAPIUtils} offers helper methods to support API communication with the controller
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OpenAPIUtils {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIUtils.class);
|
||||
|
||||
// Regular expression for firmware version
|
||||
private static final Pattern FIRMWARE_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)");
|
||||
|
||||
public static Request requestBuilder(HttpClient httpClient, NanoleafControllerConfig controllerConfig,
|
||||
String apiOperation, HttpMethod method) throws NanoleafException {
|
||||
URI requestURI = getUri(controllerConfig, apiOperation, null);
|
||||
LOGGER.trace("RequestBuilder: Sending Request {}:{} {} ", requestURI.getHost(), requestURI.getPort(),
|
||||
requestURI.getPath());
|
||||
|
||||
return httpClient.newRequest(requestURI).method(method);
|
||||
}
|
||||
|
||||
public static URI getUri(NanoleafControllerConfig controllerConfig, String apiOperation, @Nullable String query)
|
||||
throws NanoleafException {
|
||||
String path;
|
||||
|
||||
// get network settings from configuration
|
||||
String address = controllerConfig.address;
|
||||
int port = controllerConfig.port;
|
||||
|
||||
if (API_ADD_USER.equals(apiOperation)) {
|
||||
path = String.format("%s%s", API_V1_BASE_URL, apiOperation);
|
||||
} else {
|
||||
String authToken = controllerConfig.authToken;
|
||||
if (authToken != null) {
|
||||
path = String.format("%s/%s%s", API_V1_BASE_URL, authToken, apiOperation);
|
||||
} else {
|
||||
throw new NanoleafUnauthorizedException("No authentication token found in configuration");
|
||||
}
|
||||
}
|
||||
URI requestURI;
|
||||
try {
|
||||
requestURI = new URI(HttpScheme.HTTP.asString(), null, address, port, path, query, null);
|
||||
} catch (URISyntaxException use) {
|
||||
LOGGER.warn("URI could not be parsed with path {}", path);
|
||||
throw new NanoleafException("Wrong URI format for API request");
|
||||
}
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
public static ContentResponse sendOpenAPIRequest(Request request) throws NanoleafException {
|
||||
try {
|
||||
traceSendRequest(request);
|
||||
ContentResponse openAPIResponse;
|
||||
openAPIResponse = request.send();
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("API response from Nanoleaf controller: {}", openAPIResponse.getContentAsString());
|
||||
}
|
||||
LOGGER.debug("API response code: {}", openAPIResponse.getStatus());
|
||||
int responseStatus = openAPIResponse.getStatus();
|
||||
if (responseStatus == HttpStatus.OK_200 || responseStatus == HttpStatus.NO_CONTENT_204) {
|
||||
return openAPIResponse;
|
||||
} else {
|
||||
if (openAPIResponse.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||
throw new NanoleafUnauthorizedException("OpenAPI request unauthorized");
|
||||
} else if (openAPIResponse.getStatus() == HttpStatus.NOT_FOUND_404) {
|
||||
throw new NanoleafNotFoundException("OpenAPI request did not get any result back");
|
||||
} else if (openAPIResponse.getStatus() == HttpStatus.BAD_REQUEST_400) {
|
||||
throw new NanoleafBadRequestException(
|
||||
String.format("Nanoleaf did not expect this request. HTTP response code %s",
|
||||
openAPIResponse.getStatus()));
|
||||
} else {
|
||||
throw new NanoleafException(String.format("OpenAPI request failed. HTTP response code %s",
|
||||
openAPIResponse.getStatus()));
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | TimeoutException clientException) {
|
||||
if (clientException.getCause() instanceof HttpResponseException
|
||||
&& ((HttpResponseException) clientException.getCause()).getResponse()
|
||||
.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||
LOGGER.warn("OpenAPI request unauthorized. Invalid authorization token.");
|
||||
throw new NanoleafUnauthorizedException("Invalid authorization token");
|
||||
}
|
||||
throw new NanoleafException("Failed to send OpenAPI request", clientException);
|
||||
} catch (InterruptedException interruptedException) {
|
||||
throw new NanoleafInterruptedException("OpenAPI request has been interrupted", interruptedException);
|
||||
}
|
||||
}
|
||||
|
||||
private static void traceSendRequest(Request request) {
|
||||
if (!LOGGER.isTraceEnabled()) {
|
||||
return;
|
||||
}
|
||||
LOGGER.trace("Sending Request {} {}", request.getURI(),
|
||||
request.getQuery() == null ? "no query parameters" : request.getQuery());
|
||||
LOGGER.trace("Request method:{} uri:{} params{}\n", request.getMethod(), request.getURI(), request.getParams());
|
||||
if (request.getContent() != null) {
|
||||
Iterator<ByteBuffer> iter = request.getContent().iterator();
|
||||
if (iter != null) {
|
||||
while (iter.hasNext()) {
|
||||
@Nullable
|
||||
ByteBuffer buffer = iter.next();
|
||||
LOGGER.trace("Content {}", StandardCharsets.UTF_8.decode(buffer).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkRequiredFirmware(String modelId, String currentFirmwareVersion) {
|
||||
int[] currentVer = getFirmwareVersionNumbers(currentFirmwareVersion);
|
||||
|
||||
int[] requiredVer = getFirmwareVersionNumbers(
|
||||
MODEL_ID_LIGHTPANELS.equals(modelId) ? API_MIN_FW_VER_LIGHTPANELS : API_MIN_FW_VER_CANVAS);
|
||||
|
||||
for (int i = 0; i < currentVer.length; i++) {
|
||||
if (currentVer[i] != requiredVer[i]) {
|
||||
return currentVer[i] > requiredVer[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int[] getFirmwareVersionNumbers(String firmwareVersion) throws IllegalArgumentException {
|
||||
Matcher m = FIRMWARE_VERSION_PATTERN.matcher(firmwareVersion);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException("Malformed controller firmware version");
|
||||
}
|
||||
return new int[] { Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)), Integer.parseInt(m.group(3)) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafControllerConfig} class contains fields mapping controller configuration parameters.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafControllerConfig {
|
||||
|
||||
/** IP address or hostname of the light panels controller */
|
||||
public static final String ADDRESS = "address";
|
||||
public String address = "";
|
||||
/** Port number of the light panels controller */
|
||||
public static final String PORT = "port";
|
||||
public int port = 16021;
|
||||
/** Authorization token for controller API */
|
||||
public static final String AUTH_TOKEN = "authToken";
|
||||
public @Nullable String authToken;
|
||||
/** Light panels status refresh interval */
|
||||
public static final String REFRESH_INTERVAL = "refreshInterval";
|
||||
public int refreshInterval = 60;
|
||||
/** Nanoleaf device type: Light panels or Canvas */
|
||||
public static final String DEVICE_TYPE = "deviceType";
|
||||
public String deviceType = "lightPanels";
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafPanelConfig} class contains fields mapping an individual panel configuration parameters.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafPanelConfig {
|
||||
/** ID of the light panel assigned by the controller */
|
||||
public static final String ID = "id";
|
||||
|
||||
public Integer id = 0;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nanoleaf.internal.NanoleafHandlerFactory;
|
||||
import org.openhab.binding.nanoleaf.internal.OpenAPIUtils;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafMDNSDiscoveryParticipant} is responsible for discovering new Nanoleaf controllers (bridges).
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
* @author Stefan Höhn - further improvements for static defined things
|
||||
* @see <a href="https://openhab.org/documentation/development/bindings/discovery-services.html">MSDN
|
||||
* Discovery</a>
|
||||
*/
|
||||
@Component(immediate = true, configurationPid = "discovery.nanoleaf")
|
||||
@NonNullByDefault
|
||||
public class NanoleafMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NanoleafMDNSDiscoveryParticipant.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return NanoleafHandlerFactory.SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||
final ThingUID uid = getThingUID(service);
|
||||
if (uid == null) {
|
||||
return null;
|
||||
}
|
||||
final Map<String, Object> properties = new HashMap<>(2);
|
||||
String host = service.getHostAddresses()[0];
|
||||
properties.put(CONFIG_ADDRESS, host);
|
||||
int port = service.getPort();
|
||||
properties.put(CONFIG_PORT, port);
|
||||
String firmwareVersion = service.getPropertyString("srcvers");
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
|
||||
String modelId = service.getPropertyString("md");
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, modelId);
|
||||
properties.put(Thing.PROPERTY_VENDOR, "Nanoleaf");
|
||||
String qualifiedName = service.getQualifiedName();
|
||||
logger.debug("AVR found: {}", qualifiedName);
|
||||
|
||||
logger.trace("Discovered nanoleaf host: {} port: {} firmWare: {} modelId: {} qualifiedName: {}", host, port,
|
||||
firmwareVersion, modelId, qualifiedName);
|
||||
logger.debug("Adding Nanoleaf controller {} with FW version {} found at {} {} to inbox", qualifiedName,
|
||||
firmwareVersion, host, port);
|
||||
if (!OpenAPIUtils.checkRequiredFirmware(service.getPropertyString("md"), firmwareVersion)) {
|
||||
logger.warn("Nanoleaf controller firmware is too old. Must be {} or higher",
|
||||
MODEL_ID_LIGHTPANELS.equals(modelId) ? API_MIN_FW_VER_LIGHTPANELS : API_MIN_FW_VER_CANVAS);
|
||||
}
|
||||
|
||||
final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withThingType(getThingType(service))
|
||||
.withProperties(properties).withLabel(service.getName()).withRepresentationProperty(CONFIG_ADDRESS)
|
||||
.build();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
ThingTypeUID thingTypeUID = getThingType(service);
|
||||
if (thingTypeUID != null) {
|
||||
String id = service.getPropertyString("id").replace(":", "");
|
||||
return new ThingUID(thingTypeUID, id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable ThingTypeUID getThingType(final ServiceInfo service) {
|
||||
String model = service.getPropertyString("md"); // model
|
||||
logger.debug("Nanoleaf Type: {}", model);
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
return THING_TYPE_CONTROLLER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.CONFIG_PANEL_ID;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants;
|
||||
import org.openhab.binding.nanoleaf.internal.NanoleafControllerListener;
|
||||
import org.openhab.binding.nanoleaf.internal.NanoleafHandlerFactory;
|
||||
import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler;
|
||||
import org.openhab.binding.nanoleaf.internal.model.ControllerInfo;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Layout;
|
||||
import org.openhab.binding.nanoleaf.internal.model.PanelLayout;
|
||||
import org.openhab.binding.nanoleaf.internal.model.PositionDatum;
|
||||
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.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafPanelsDiscoveryService} is responsible for discovering the individual
|
||||
* panels connected to the controller.
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService implements NanoleafControllerListener {
|
||||
|
||||
private static final int SEARCH_TIMEOUT_SECONDS = 60;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NanoleafPanelsDiscoveryService.class);
|
||||
private final NanoleafControllerHandler bridgeHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link NanoleafPanelsDiscoveryService} attached to the given bridge handler.
|
||||
*
|
||||
* @param nanoleafControllerHandler The bridge handler this discovery service is attached to
|
||||
*/
|
||||
public NanoleafPanelsDiscoveryService(NanoleafControllerHandler nanoleafControllerHandler) {
|
||||
super(NanoleafHandlerFactory.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIMEOUT_SECONDS, false);
|
||||
this.bridgeHandler = nanoleafControllerHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting Nanoleaf panel discovery");
|
||||
bridgeHandler.registerControllerListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
logger.debug("Stopping Nanoleaf panel discovery");
|
||||
super.stopScan();
|
||||
bridgeHandler.unregisterControllerListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the controller handler with bridge and panel data
|
||||
*
|
||||
* @param bridge The controller
|
||||
* @param controllerInfo Panel data (and more)
|
||||
*/
|
||||
@Override
|
||||
public void onControllerInfoFetched(ThingUID bridge, ControllerInfo controllerInfo) {
|
||||
logger.debug("Discover panels connected to controller with id {}", bridge.getAsString());
|
||||
final PanelLayout panelLayout = controllerInfo.getPanelLayout();
|
||||
@Nullable
|
||||
Layout layout = panelLayout.getLayout();
|
||||
|
||||
if (layout != null && layout.getNumPanels() > 0) {
|
||||
@Nullable
|
||||
final List<PositionDatum> positionData = layout.getPositionData();
|
||||
if (positionData != null) {
|
||||
Iterator<PositionDatum> iterator = positionData.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
@Nullable
|
||||
PositionDatum panel = iterator.next();
|
||||
ThingUID newPanelThingUID = new ThingUID(NanoleafBindingConstants.THING_TYPE_LIGHT_PANEL, bridge,
|
||||
Integer.toString(panel.getPanelId()));
|
||||
|
||||
final Map<String, Object> properties = new HashMap<>(1);
|
||||
properties.put(CONFIG_PANEL_ID, panel.getPanelId());
|
||||
|
||||
DiscoveryResult newPanel = DiscoveryResultBuilder.create(newPanelThingUID).withBridge(bridge)
|
||||
.withProperties(properties).withLabel("Light Panel " + panel.getPanelId())
|
||||
.withRepresentationProperty(CONFIG_PANEL_ID).build();
|
||||
|
||||
logger.debug("Adding panel with id {} to inbox", panel.getPanelId());
|
||||
thingDiscovered(newPanel);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Couldn't add panels to inbox as layout position data was null");
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.info("No panels found or connected to controller");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,855 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.nanoleaf.internal.*;
|
||||
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
|
||||
import org.openhab.binding.nanoleaf.internal.model.*;
|
||||
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.Bridge;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafControllerHandler} is responsible for handling commands to the controller which
|
||||
* affect all panels connected to it (e.g. selected effect)
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
* @author Stefan Höhn - Canvas Touch Support
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafControllerHandler extends BaseBridgeHandler {
|
||||
|
||||
// Pairing interval in seconds
|
||||
private static final int PAIRING_INTERVAL = 25;
|
||||
|
||||
// Panel discovery interval in seconds
|
||||
private static final int PANEL_DISCOVERY_INTERVAL = 30;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NanoleafControllerHandler.class);
|
||||
private HttpClient httpClient;
|
||||
private List<NanoleafControllerListener> controllerListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
// Pairing, update and panel discovery jobs and touch event job
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> pairingJob;
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> updateJob;
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> panelDiscoveryJob;
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> touchJob;
|
||||
|
||||
// JSON parser for API responses
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
// Controller configuration settings and channel values
|
||||
private @Nullable String address;
|
||||
private int port;
|
||||
private int refreshIntervall;
|
||||
private @Nullable String authToken;
|
||||
private @Nullable String deviceType;
|
||||
private @NonNullByDefault({}) ControllerInfo controllerInfo;
|
||||
|
||||
public NanoleafControllerHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing the controller (bridge)");
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class);
|
||||
setAddress(config.address);
|
||||
setPort(config.port);
|
||||
setRefreshIntervall(config.refreshInterval);
|
||||
setAuthToken(config.authToken);
|
||||
|
||||
@Nullable
|
||||
String property = getThing().getProperties().get(Thing.PROPERTY_MODEL_ID);
|
||||
if (MODEL_ID_CANVAS.equals(property)) {
|
||||
config.deviceType = DEVICE_TYPE_CANVAS;
|
||||
} else {
|
||||
config.deviceType = DEVICE_TYPE_LIGHTPANELS;
|
||||
}
|
||||
setDeviceType(config.deviceType);
|
||||
|
||||
try {
|
||||
if (StringUtils.isEmpty(getAddress()) || StringUtils.isEmpty(String.valueOf(getPort()))) {
|
||||
logger.warn("No IP address and port configured for the Nanoleaf controller");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/error.nanoleaf.controller.noIp");
|
||||
stopAllJobs();
|
||||
} else if (!StringUtils.isEmpty(getThing().getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION))
|
||||
&& !OpenAPIUtils.checkRequiredFirmware(getThing().getProperties().get(Thing.PROPERTY_MODEL_ID),
|
||||
getThing().getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION))) {
|
||||
logger.warn("Nanoleaf controller firmware is too old: {}. Must be equal or higher than {}",
|
||||
getThing().getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION), API_MIN_FW_VER_LIGHTPANELS);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.incompatibleFirmware");
|
||||
stopAllJobs();
|
||||
} else if (StringUtils.isEmpty(getAuthToken())) {
|
||||
logger.debug("No token found. Start pairing background job");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/error.nanoleaf.controller.noToken");
|
||||
startPairingJob();
|
||||
stopUpdateJob();
|
||||
stopPanelDiscoveryJob();
|
||||
} else {
|
||||
logger.debug("Controller is online. Stop pairing job, start update & panel discovery jobs");
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
stopPairingJob();
|
||||
startUpdateJob();
|
||||
startPanelDiscoveryJob();
|
||||
startTouchJob();
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn("Nanoleaf controller firmware version not in format x.y.z: {}",
|
||||
getThing().getProperties().get(Thing.PROPERTY_FIRMWARE_VERSION));
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.incompatibleFirmware");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Received command {} for channel {}", command, channelUID);
|
||||
if (!ThingStatus.ONLINE.equals(getThing().getStatusInfo().getStatus())) {
|
||||
logger.debug("Cannot handle command. Bridge is not online.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
updateFromControllerInfo();
|
||||
} else {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_POWER:
|
||||
case CHANNEL_COLOR:
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
case CHANNEL_COLOR_TEMPERATURE_ABS:
|
||||
case CHANNEL_PANEL_LAYOUT:
|
||||
sendStateCommand(channelUID.getId(), command);
|
||||
break;
|
||||
case CHANNEL_EFFECT:
|
||||
sendEffectCommand(command);
|
||||
break;
|
||||
case CHANNEL_RHYTHM_MODE:
|
||||
sendRhythmCommand(command);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Channel with id {} not handled", channelUID.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (NanoleafUnauthorizedException nae) {
|
||||
logger.warn("Authorization for command {} to channelUID {} failed: {}", command, channelUID,
|
||||
nae.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.invalidToken");
|
||||
} catch (NanoleafException ne) {
|
||||
logger.warn("Handling command {} to channelUID {} failed: {}", command, channelUID, ne.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.communication");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
// delete token for openHAB
|
||||
ContentResponse deleteTokenResponse;
|
||||
try {
|
||||
Request deleteTokenRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), API_DELETE_USER,
|
||||
HttpMethod.DELETE);
|
||||
deleteTokenResponse = OpenAPIUtils.sendOpenAPIRequest(deleteTokenRequest);
|
||||
if (deleteTokenResponse.getStatus() != HttpStatus.NO_CONTENT_204) {
|
||||
logger.warn("Failed to delete token for openHAB. Response code is {}", deleteTokenResponse.getStatus());
|
||||
return;
|
||||
}
|
||||
logger.debug("Successfully deleted token for openHAB from controller");
|
||||
} catch (NanoleafUnauthorizedException e) {
|
||||
logger.warn("Attempt to delete token for openHAB failed. Token unauthorized.");
|
||||
} catch (NanoleafException ne) {
|
||||
logger.warn("Attempt to delete token for openHAB failed : {}", ne.getMessage());
|
||||
}
|
||||
stopAllJobs();
|
||||
super.handleRemoval();
|
||||
logger.debug("Nanoleaf controller removed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
stopAllJobs();
|
||||
super.dispose();
|
||||
logger.debug("Disposing handler for Nanoleaf controller {}", getThing().getUID());
|
||||
}
|
||||
|
||||
public boolean registerControllerListener(NanoleafControllerListener controllerListener) {
|
||||
logger.debug("Register new listener for controller {}", getThing().getUID());
|
||||
boolean result = controllerListeners.add(controllerListener);
|
||||
if (result) {
|
||||
startPanelDiscoveryJob();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean unregisterControllerListener(NanoleafControllerListener controllerListener) {
|
||||
logger.debug("Unregister listener for controller {}", getThing().getUID());
|
||||
boolean result = controllerListeners.remove(controllerListener);
|
||||
if (result) {
|
||||
stopPanelDiscoveryJob();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public NanoleafControllerConfig getControllerConfig() {
|
||||
NanoleafControllerConfig config = new NanoleafControllerConfig();
|
||||
config.address = this.getAddress();
|
||||
config.port = this.getPort();
|
||||
config.refreshInterval = this.getRefreshIntervall();
|
||||
config.authToken = this.getAuthToken();
|
||||
config.deviceType = this.getDeviceType();
|
||||
return config;
|
||||
}
|
||||
|
||||
public synchronized void startPairingJob() {
|
||||
if (pairingJob == null || pairingJob.isCancelled()) {
|
||||
logger.debug("Start pairing job, interval={} sec", PAIRING_INTERVAL);
|
||||
pairingJob = scheduler.scheduleWithFixedDelay(this::runPairing, 0, PAIRING_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stopPairingJob() {
|
||||
if (pairingJob != null && !pairingJob.isCancelled()) {
|
||||
logger.debug("Stop pairing job");
|
||||
pairingJob.cancel(true);
|
||||
this.pairingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startUpdateJob() {
|
||||
if (StringUtils.isNotEmpty(getAuthToken())) {
|
||||
if (updateJob == null || updateJob.isCancelled()) {
|
||||
logger.debug("Start controller status job, repeat every {} sec", getRefreshIntervall());
|
||||
updateJob = scheduler.scheduleWithFixedDelay(this::runUpdate, 0, getRefreshIntervall(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/error.nanoleaf.controller.noToken");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stopUpdateJob() {
|
||||
if (updateJob != null && !updateJob.isCancelled()) {
|
||||
logger.debug("Stop status job");
|
||||
updateJob.cancel(true);
|
||||
this.updateJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void startPanelDiscoveryJob() {
|
||||
logger.debug("Starting panel discovery job. Has Controller-Listeners: {} panelDiscoveryJob: {}",
|
||||
!controllerListeners.isEmpty(), panelDiscoveryJob);
|
||||
if (!controllerListeners.isEmpty() && (panelDiscoveryJob == null || panelDiscoveryJob.isCancelled())) {
|
||||
logger.debug("Start panel discovery job, interval={} sec", PANEL_DISCOVERY_INTERVAL);
|
||||
panelDiscoveryJob = scheduler.scheduleWithFixedDelay(this::runPanelDiscovery, 0, PANEL_DISCOVERY_INTERVAL,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stopPanelDiscoveryJob() {
|
||||
if (controllerListeners.isEmpty() && panelDiscoveryJob != null && !panelDiscoveryJob.isCancelled()) {
|
||||
logger.debug("Stop panel discovery job");
|
||||
panelDiscoveryJob.cancel(true);
|
||||
this.panelDiscoveryJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startTouchJob() {
|
||||
NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class);
|
||||
if (!config.deviceType.equals(DEVICE_TYPE_CANVAS)) {
|
||||
logger.debug("NOT starting TouchJob for Panel {} because it has wrong device type '{}' vs required '{}'",
|
||||
this.getThing().getUID(), config.deviceType, DEVICE_TYPE_CANVAS);
|
||||
return;
|
||||
} else
|
||||
logger.debug("Starting TouchJob for Panel {}", this.getThing().getUID());
|
||||
|
||||
if (StringUtils.isNotEmpty(getAuthToken())) {
|
||||
if (touchJob == null || touchJob.isCancelled()) {
|
||||
logger.debug("Starting Touchjob now");
|
||||
touchJob = scheduler.schedule(this::runTouchDetection, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
} else {
|
||||
logger.error("starting TouchJob for Controller {} failed - missing token", this.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stopTouchJob() {
|
||||
if (touchJob != null && !touchJob.isCancelled()) {
|
||||
logger.debug("Stop touch job");
|
||||
touchJob.cancel(true);
|
||||
this.touchJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void runUpdate() {
|
||||
logger.debug("Run update job");
|
||||
try {
|
||||
updateFromControllerInfo();
|
||||
startTouchJob(); // if device type has changed, start touch detection.
|
||||
// controller might have been offline, e.g. for firmware update. In this case, return to online state
|
||||
if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
|
||||
logger.debug("Controller {} is back online", thing.getUID());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} catch (NanoleafUnauthorizedException nae) {
|
||||
logger.warn("Status update unauthorized: {}", nae.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.invalidToken");
|
||||
if (StringUtils.isEmpty(getAuthToken())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/error.nanoleaf.controller.noToken");
|
||||
}
|
||||
} catch (NanoleafException ne) {
|
||||
logger.warn("Status update failed: {}", ne.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.communication");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Update job failed", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.nanoleaf.controller.runtime");
|
||||
}
|
||||
}
|
||||
|
||||
private void runPairing() {
|
||||
logger.debug("Run pairing job");
|
||||
try {
|
||||
if (StringUtils.isNotEmpty(getAuthToken())) {
|
||||
if (pairingJob != null) {
|
||||
pairingJob.cancel(false);
|
||||
}
|
||||
logger.debug("Authentication token found. Canceling pairing job");
|
||||
return;
|
||||
}
|
||||
ContentResponse authTokenResponse = OpenAPIUtils
|
||||
.requestBuilder(httpClient, getControllerConfig(), API_ADD_USER, HttpMethod.POST).send();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Auth token response: {}", authTokenResponse.getContentAsString());
|
||||
}
|
||||
|
||||
if (authTokenResponse.getStatus() != HttpStatus.OK_200) {
|
||||
logger.debug("Pairing pending for {}. Controller returns status code {}", this.getThing().getUID(),
|
||||
authTokenResponse.getStatus());
|
||||
} else {
|
||||
// get auth token from response
|
||||
@Nullable
|
||||
AuthToken authToken = gson.fromJson(authTokenResponse.getContentAsString(), AuthToken.class);
|
||||
|
||||
if (StringUtils.isNotEmpty(authToken.getAuthToken())) {
|
||||
logger.debug("Pairing succeeded.");
|
||||
|
||||
// Update and save the auth token in the thing configuration
|
||||
Configuration config = editConfiguration();
|
||||
config.put(NanoleafControllerConfig.AUTH_TOKEN, authToken.getAuthToken());
|
||||
updateConfiguration(config);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
// Update local field
|
||||
setAuthToken(authToken.getAuthToken());
|
||||
|
||||
stopPairingJob();
|
||||
startUpdateJob();
|
||||
startPanelDiscoveryJob();
|
||||
startTouchJob();
|
||||
} else {
|
||||
logger.debug("No auth token found in response: {}", authTokenResponse.getContentAsString());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.pairingFailed");
|
||||
throw new NanoleafException(authTokenResponse.getContentAsString());
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("Received invalid data", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.invalidData");
|
||||
} catch (NanoleafException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.noTokenReceived");
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
logger.warn("Cannot send authorization request to controller: ", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.authRequest");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Pairing job failed", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.nanoleaf.controller.runtime");
|
||||
} catch (Exception e) {
|
||||
logger.warn("Cannot start http client", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.noClient");
|
||||
}
|
||||
}
|
||||
|
||||
private void runPanelDiscovery() {
|
||||
logger.debug("Run panel discovery job");
|
||||
// Trigger a new discovery of connected panels
|
||||
for (NanoleafControllerListener controllerListener : controllerListeners) {
|
||||
try {
|
||||
controllerListener.onControllerInfoFetched(getThing().getUID(), receiveControllerInfo());
|
||||
} catch (NanoleafUnauthorizedException nue) {
|
||||
logger.warn("Panel discovery unauthorized: {}", nue.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.invalidToken");
|
||||
if (StringUtils.isEmpty(getAuthToken())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"@text/error.nanoleaf.controller.noToken");
|
||||
}
|
||||
} catch (NanoleafInterruptedException nie) {
|
||||
logger.info("Panel discovery has been stopped.");
|
||||
} catch (NanoleafException ne) {
|
||||
logger.warn("Failed to discover panels: ", ne);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.communication");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Panel discovery job failed", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.nanoleaf.controller.runtime");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is based on the touch event detection described in https://forum.nanoleaf.me/docs/openapi#_842h3097vbgq
|
||||
*/
|
||||
private static boolean touchJobRunning = false;
|
||||
|
||||
private void runTouchDetection() {
|
||||
if (touchJobRunning) {
|
||||
logger.debug("touch job already running. quitting.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
touchJobRunning = true;
|
||||
URI eventUri = OpenAPIUtils.getUri(getControllerConfig(), API_EVENTS, "id=4");
|
||||
logger.debug("touch job registered on: {}", eventUri.toString());
|
||||
httpClient.newRequest(eventUri).send(new Response.Listener.Adapter() // request runs forever
|
||||
{
|
||||
@Override
|
||||
public void onContent(@Nullable Response response, @Nullable ByteBuffer content) {
|
||||
String s = StandardCharsets.UTF_8.decode(content).toString();
|
||||
logger.trace("content {}", s);
|
||||
|
||||
Scanner eventContent = new Scanner(s);
|
||||
while (eventContent.hasNextLine()) {
|
||||
String line = eventContent.nextLine().trim();
|
||||
// we don't expect anything than content id:4, so we do not check that but only care about the
|
||||
// data part
|
||||
if (line.startsWith("data:")) {
|
||||
String json = line.substring(5).trim(); // supposed to be JSON
|
||||
try {
|
||||
@Nullable
|
||||
TouchEvents touchEvents = gson.fromJson(json, TouchEvents.class);
|
||||
handleTouchEvents(touchEvents);
|
||||
} catch (JsonSyntaxException jse) {
|
||||
logger.error("couldn't parse touch event json {}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
eventContent.close();
|
||||
logger.debug("leaving touch onContent");
|
||||
super.onContent(response, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable Response response) {
|
||||
logger.trace("touch event SUCCESS: {}", response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nullable Response response, @Nullable Throwable failure) {
|
||||
logger.trace("touch event FAILURE: {}", response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.trace("touch event COMPLETE: {}", result);
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException | NanoleafException e) {
|
||||
logger.warn("setting up TouchDetection failed", e);
|
||||
} finally {
|
||||
touchJobRunning = false;
|
||||
}
|
||||
logger.debug("leaving run touch detection");
|
||||
}
|
||||
|
||||
/**
|
||||
* Interate over all gathered touch events and apply them to the panel they belong to
|
||||
*
|
||||
* @param touchEvents
|
||||
*/
|
||||
private void handleTouchEvents(TouchEvents touchEvents) {
|
||||
touchEvents.getEvents().forEach(event -> {
|
||||
logger.info("panel: {} gesture id: {}", event.getPanelId(), event.getGesture());
|
||||
|
||||
// Iterate over all child things = all panels of that controller
|
||||
this.getThing().getThings().forEach(child -> {
|
||||
NanoleafPanelHandler panelHandler = (NanoleafPanelHandler) child.getHandler();
|
||||
if (panelHandler != null) {
|
||||
logger.trace("Checking available panel -{}- versus event panel -{}-", panelHandler.getPanelID(),
|
||||
event.getPanelId());
|
||||
if (panelHandler.getPanelID().equals(event.getPanelId())) {
|
||||
logger.debug("Panel {} found. Triggering item with gesture {}.", panelHandler.getPanelID(),
|
||||
event.getGesture());
|
||||
panelHandler.updatePanelGesture(event.getGesture());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFromControllerInfo() throws NanoleafException {
|
||||
logger.debug("Update channels for controller {}", thing.getUID());
|
||||
this.controllerInfo = receiveControllerInfo();
|
||||
if (controllerInfo == null) {
|
||||
logger.debug("No Controller Info has been provided");
|
||||
return;
|
||||
}
|
||||
final State state = controllerInfo.getState();
|
||||
|
||||
OnOffType powerState = state.getOnOff();
|
||||
updateState(CHANNEL_POWER, powerState);
|
||||
|
||||
@Nullable
|
||||
Ct colorTemperature = state.getColorTemperature();
|
||||
|
||||
float colorTempPercent = 0f;
|
||||
if (colorTemperature != null) {
|
||||
updateState(CHANNEL_COLOR_TEMPERATURE_ABS, new DecimalType(colorTemperature.getValue()));
|
||||
|
||||
@Nullable
|
||||
Integer min = colorTemperature.getMin();
|
||||
int colorMin = (min == null) ? 0 : min;
|
||||
|
||||
@Nullable
|
||||
Integer max = colorTemperature.getMax();
|
||||
int colorMax = (max == null) ? 0 : max;
|
||||
|
||||
colorTempPercent = (colorTemperature.getValue() - colorMin) / (colorMax - colorMin)
|
||||
* PercentType.HUNDRED.intValue();
|
||||
}
|
||||
|
||||
updateState(CHANNEL_COLOR_TEMPERATURE, new PercentType(Float.toString(colorTempPercent)));
|
||||
updateState(CHANNEL_EFFECT, new StringType(controllerInfo.getEffects().getSelect()));
|
||||
|
||||
@Nullable
|
||||
Hue stateHue = state.getHue();
|
||||
int hue = (stateHue != null) ? stateHue.getValue() : 0;
|
||||
@Nullable
|
||||
Sat stateSaturation = state.getSaturation();
|
||||
int saturation = (stateSaturation != null) ? stateSaturation.getValue() : 0;
|
||||
@Nullable
|
||||
Brightness stateBrightness = state.getBrightness();
|
||||
int brightness = (stateBrightness != null) ? stateBrightness.getValue() : 0;
|
||||
|
||||
updateState(CHANNEL_COLOR, new HSBType(new DecimalType(hue), new PercentType(saturation),
|
||||
new PercentType(powerState == OnOffType.ON ? brightness : 0)));
|
||||
updateState(CHANNEL_COLOR_MODE, new StringType(state.getColorMode()));
|
||||
updateState(CHANNEL_RHYTHM_ACTIVE, controllerInfo.getRhythm().getRhythmActive() ? OnOffType.ON : OnOffType.OFF);
|
||||
updateState(CHANNEL_RHYTHM_MODE, new DecimalType(controllerInfo.getRhythm().getRhythmMode()));
|
||||
updateState(CHANNEL_RHYTHM_STATE,
|
||||
controllerInfo.getRhythm().getRhythmConnected() ? OnOffType.ON : OnOffType.OFF);
|
||||
// update bridge properties which may have changed, or are not present during discovery
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, controllerInfo.getSerialNo());
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, controllerInfo.getFirmwareVersion());
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, controllerInfo.getModel());
|
||||
properties.put(Thing.PROPERTY_VENDOR, controllerInfo.getManufacturer());
|
||||
updateProperties(properties);
|
||||
|
||||
Configuration config = editConfiguration();
|
||||
|
||||
if (MODEL_ID_CANVAS.equals(controllerInfo.getModel())) {
|
||||
config.put(NanoleafControllerConfig.DEVICE_TYPE, DEVICE_TYPE_CANVAS);
|
||||
logger.debug("Set to device type {}", DEVICE_TYPE_CANVAS);
|
||||
} else {
|
||||
config.put(NanoleafControllerConfig.DEVICE_TYPE, DEVICE_TYPE_LIGHTPANELS);
|
||||
logger.debug("Set to device type {}", DEVICE_TYPE_LIGHTPANELS);
|
||||
}
|
||||
updateConfiguration(config);
|
||||
|
||||
getConfig().getProperties().forEach((key, value) -> {
|
||||
logger.trace("Configuration property: key {} value {}", key, value);
|
||||
});
|
||||
|
||||
getThing().getProperties().forEach((key, value) -> {
|
||||
logger.debug("Thing property: key {} value {}", key, value);
|
||||
});
|
||||
|
||||
// update the color channels of each panel
|
||||
this.getThing().getThings().forEach(child -> {
|
||||
NanoleafPanelHandler panelHandler = (NanoleafPanelHandler) child.getHandler();
|
||||
if (panelHandler != null) {
|
||||
logger.debug("Update color channel for panel {}", panelHandler.getThing().getUID());
|
||||
panelHandler.updatePanelColorChannel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ControllerInfo receiveControllerInfo() throws NanoleafException, NanoleafUnauthorizedException {
|
||||
ContentResponse controllerlInfoJSON = OpenAPIUtils.sendOpenAPIRequest(OpenAPIUtils.requestBuilder(httpClient,
|
||||
getControllerConfig(), API_GET_CONTROLLER_INFO, HttpMethod.GET));
|
||||
@Nullable
|
||||
ControllerInfo controllerInfo = gson.fromJson(controllerlInfoJSON.getContentAsString(), ControllerInfo.class);
|
||||
return controllerInfo;
|
||||
}
|
||||
|
||||
private void sendStateCommand(String channel, Command command) throws NanoleafException {
|
||||
State stateObject = new State();
|
||||
switch (channel) {
|
||||
case CHANNEL_POWER:
|
||||
if (command instanceof OnOffType) {
|
||||
// On/Off command - turns controller on/off
|
||||
BooleanState state = new On();
|
||||
state.setValue(OnOffType.ON.equals(command));
|
||||
stateObject.setState(state);
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
if (command instanceof OnOffType) {
|
||||
// On/Off command - turns controller on/off
|
||||
BooleanState state = new On();
|
||||
state.setValue(OnOffType.ON.equals(command));
|
||||
stateObject.setState(state);
|
||||
} else if (command instanceof HSBType) {
|
||||
// regular color HSB command
|
||||
IntegerState h = new Hue();
|
||||
IntegerState s = new Sat();
|
||||
IntegerState b = new Brightness();
|
||||
h.setValue(((HSBType) command).getHue().intValue());
|
||||
s.setValue(((HSBType) command).getSaturation().intValue());
|
||||
b.setValue(((HSBType) command).getBrightness().intValue());
|
||||
stateObject.setState(h);
|
||||
stateObject.setState(s);
|
||||
stateObject.setState(b);
|
||||
} else if (command instanceof PercentType) {
|
||||
// brightness command
|
||||
IntegerState b = new Brightness();
|
||||
b.setValue(((PercentType) command).intValue());
|
||||
stateObject.setState(b);
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
// increase/decrease brightness
|
||||
if (controllerInfo != null) {
|
||||
@Nullable
|
||||
Brightness brightness = controllerInfo.getState().getBrightness();
|
||||
int brightnessMin = 0;
|
||||
int brightnessMax = 0;
|
||||
if (brightness != null) {
|
||||
@Nullable
|
||||
Integer min = brightness.getMin();
|
||||
brightnessMin = (min == null) ? 0 : min;
|
||||
@Nullable
|
||||
Integer max = brightness.getMax();
|
||||
brightnessMax = (max == null) ? 0 : max;
|
||||
|
||||
if (IncreaseDecreaseType.INCREASE.equals(command)) {
|
||||
brightness.setValue(
|
||||
Math.min(brightnessMax, brightness.getValue() + BRIGHTNESS_STEP_SIZE));
|
||||
} else {
|
||||
brightness.setValue(
|
||||
Math.max(brightnessMin, brightness.getValue() - BRIGHTNESS_STEP_SIZE));
|
||||
}
|
||||
stateObject.setState(brightness);
|
||||
logger.debug("Setting controller brightness to {}", brightness.getValue());
|
||||
// update controller info in case new command is sent before next update job interval
|
||||
controllerInfo.getState().setBrightness(brightness);
|
||||
} else {
|
||||
logger.debug("Couldn't set brightness as it was null!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
if (command instanceof PercentType) {
|
||||
// Color temperature (percent)
|
||||
IntegerState state = new Ct();
|
||||
@Nullable
|
||||
Ct colorTemperature = controllerInfo.getState().getColorTemperature();
|
||||
|
||||
int colorMin = 0;
|
||||
int colorMax = 0;
|
||||
if (colorTemperature != null) {
|
||||
@Nullable
|
||||
Integer min = colorTemperature.getMin();
|
||||
colorMin = (min == null) ? 0 : min;
|
||||
|
||||
@Nullable
|
||||
Integer max = colorTemperature.getMax();
|
||||
colorMax = (max == null) ? 0 : max;
|
||||
}
|
||||
|
||||
state.setValue(Math.round((colorMax - colorMin) * ((PercentType) command).intValue()
|
||||
/ PercentType.HUNDRED.floatValue() + colorMin));
|
||||
stateObject.setState(state);
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE_ABS:
|
||||
if (command instanceof DecimalType) {
|
||||
// Color temperature (absolute)
|
||||
IntegerState state = new Ct();
|
||||
state.setValue(((DecimalType) command).intValue());
|
||||
stateObject.setState(state);
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_PANEL_LAYOUT:
|
||||
@Nullable
|
||||
Layout layout = controllerInfo.getPanelLayout().getLayout();
|
||||
String layoutView = (layout != null) ? layout.getLayoutView() : "";
|
||||
logger.info("Panel layout and ids for controller {} \n{}", thing.getUID(), layoutView);
|
||||
updateState(CHANNEL_PANEL_LAYOUT, OnOffType.OFF);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
Request setNewStateRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), API_SET_VALUE,
|
||||
HttpMethod.PUT);
|
||||
setNewStateRequest.content(new StringContentProvider(gson.toJson(stateObject)), "application/json");
|
||||
OpenAPIUtils.sendOpenAPIRequest(setNewStateRequest);
|
||||
}
|
||||
|
||||
private void sendEffectCommand(Command command) throws NanoleafException {
|
||||
Effects effects = new Effects();
|
||||
if (command instanceof StringType) {
|
||||
effects.setSelect(command.toString());
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
Request setNewEffectRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), API_EFFECT,
|
||||
HttpMethod.PUT);
|
||||
String content = gson.toJson(effects);
|
||||
logger.debug("sending effect command from controller {}: {}", getThing().getUID(), content);
|
||||
setNewEffectRequest.content(new StringContentProvider(content), "application/json");
|
||||
OpenAPIUtils.sendOpenAPIRequest(setNewEffectRequest);
|
||||
}
|
||||
|
||||
private void sendRhythmCommand(Command command) throws NanoleafException {
|
||||
Rhythm rhythm = new Rhythm();
|
||||
if (command instanceof DecimalType) {
|
||||
rhythm.setRhythmMode(((DecimalType) command).intValue());
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
Request setNewRhythmRequest = OpenAPIUtils.requestBuilder(httpClient, getControllerConfig(), API_RHYTHM_MODE,
|
||||
HttpMethod.PUT);
|
||||
setNewRhythmRequest.content(new StringContentProvider(gson.toJson(rhythm)), "application/json");
|
||||
OpenAPIUtils.sendOpenAPIRequest(setNewRhythmRequest);
|
||||
}
|
||||
|
||||
private String getAddress() {
|
||||
return StringUtils.defaultString(this.address);
|
||||
}
|
||||
|
||||
private void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
private int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
private void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
private int getRefreshIntervall() {
|
||||
return refreshIntervall;
|
||||
}
|
||||
|
||||
private void setRefreshIntervall(int refreshIntervall) {
|
||||
this.refreshIntervall = refreshIntervall;
|
||||
}
|
||||
|
||||
private String getAuthToken() {
|
||||
return StringUtils.defaultString(authToken);
|
||||
}
|
||||
|
||||
private void setAuthToken(@Nullable String authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
private String getDeviceType() {
|
||||
return StringUtils.defaultString(deviceType);
|
||||
}
|
||||
|
||||
private void setDeviceType(String deviceType) {
|
||||
this.deviceType = deviceType;
|
||||
}
|
||||
|
||||
private void stopAllJobs() {
|
||||
stopPairingJob();
|
||||
stopUpdateJob();
|
||||
stopPanelDiscoveryJob();
|
||||
stopTouchJob();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.handler;
|
||||
|
||||
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.nanoleaf.internal.*;
|
||||
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Effects;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Write;
|
||||
import org.openhab.core.library.types.*;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link NanoleafPanelHandler} is responsible for handling commands to the controller which
|
||||
* affect an individual panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
* @author Stefan Höhn - Canvas Touch Support
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NanoleafPanelHandler extends BaseThingHandler {
|
||||
|
||||
private static final PercentType MIN_PANEL_BRIGHTNESS = PercentType.ZERO;
|
||||
private static final PercentType MAX_PANEL_BRIGHTNESS = PercentType.HUNDRED;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NanoleafPanelHandler.class);
|
||||
|
||||
private HttpClient httpClient;
|
||||
// JSON parser for API responses
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
// holds current color data per panel
|
||||
private Map<String, HSBType> panelInfo = new HashMap<>();
|
||||
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> singleTapJob;
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> doubleTapJob;
|
||||
|
||||
public NanoleafPanelHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for panel {}", getThing().getUID());
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
Bridge controller = getBridge();
|
||||
if (controller == null) {
|
||||
initializePanel(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, ""));
|
||||
} else if (ThingStatus.OFFLINE.equals(controller.getStatus())) {
|
||||
initializePanel(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"@text/error.nanoleaf.panel.controllerOffline"));
|
||||
} else {
|
||||
initializePanel(controller.getStatusInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo controllerStatusInfo) {
|
||||
logger.debug("Controller status changed to {} -- {}", controllerStatusInfo,
|
||||
controllerStatusInfo.getDescription() + "/" + controllerStatusInfo.getStatus() + "/"
|
||||
+ controllerStatusInfo.hashCode());
|
||||
if (controllerStatusInfo.getStatus().equals(ThingStatus.OFFLINE)) {
|
||||
initializePanel(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"@text/error.nanoleaf.panel.controllerOffline"));
|
||||
} else {
|
||||
initializePanel(controllerStatusInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Received command {} for channel {}", command, channelUID);
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_PANEL_COLOR:
|
||||
sendRenderedEffectCommand(command);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Channel with id {} not handled", channelUID.getId());
|
||||
break;
|
||||
}
|
||||
} catch (NanoleafUnauthorizedException nae) {
|
||||
logger.warn("Authorization for command {} for channelUID {} failed: {}", command, channelUID,
|
||||
nae.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.invalidToken");
|
||||
} catch (NanoleafException ne) {
|
||||
logger.warn("Handling command {} for channelUID {} failed: {}", command, channelUID, ne.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.controller.communication");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
logger.debug("Nanoleaf panel {} removed", getThing().getUID());
|
||||
super.handleRemoval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing handler for Nanoleaf panel {}", getThing().getUID());
|
||||
stopAllJobs();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void stopAllJobs() {
|
||||
if (singleTapJob != null && !singleTapJob.isCancelled()) {
|
||||
logger.debug("Stop single touch job");
|
||||
singleTapJob.cancel(true);
|
||||
this.singleTapJob = null;
|
||||
}
|
||||
if (doubleTapJob != null && !doubleTapJob.isCancelled()) {
|
||||
logger.debug("Stop double touch job");
|
||||
doubleTapJob.cancel(true);
|
||||
this.doubleTapJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePanel(ThingStatusInfo panelStatus) {
|
||||
updateStatus(panelStatus.getStatus(), panelStatus.getStatusDetail());
|
||||
logger.debug("Panel {} status changed to {}-{}", this.getThing().getUID(), panelStatus.getStatus(),
|
||||
panelStatus.getStatusDetail());
|
||||
}
|
||||
|
||||
private void sendRenderedEffectCommand(Command command) throws NanoleafException {
|
||||
logger.debug("Command Type: {}", command.getClass());
|
||||
HSBType currentPanelColor = getPanelColor();
|
||||
if (currentPanelColor != null)
|
||||
logger.debug("currentPanelColor: {}", currentPanelColor.toString());
|
||||
HSBType newPanelColor = new HSBType();
|
||||
|
||||
if (command instanceof HSBType) {
|
||||
newPanelColor = (HSBType) command;
|
||||
} else if (command instanceof OnOffType && (currentPanelColor != null)) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(),
|
||||
MAX_PANEL_BRIGHTNESS);
|
||||
} else {
|
||||
newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(),
|
||||
MIN_PANEL_BRIGHTNESS);
|
||||
}
|
||||
} else if (command instanceof PercentType && (currentPanelColor != null)) {
|
||||
PercentType brightness = new PercentType(
|
||||
Math.max(MIN_PANEL_BRIGHTNESS.intValue(), ((PercentType) command).intValue()));
|
||||
newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(), brightness);
|
||||
} else if (command instanceof IncreaseDecreaseType && (currentPanelColor != null)) {
|
||||
int brightness = currentPanelColor.getBrightness().intValue();
|
||||
if (command.equals(IncreaseDecreaseType.INCREASE)) {
|
||||
brightness = Math.min(MAX_PANEL_BRIGHTNESS.intValue(), brightness + BRIGHTNESS_STEP_SIZE);
|
||||
} else {
|
||||
brightness = Math.max(MIN_PANEL_BRIGHTNESS.intValue(), brightness - BRIGHTNESS_STEP_SIZE);
|
||||
}
|
||||
newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(),
|
||||
new PercentType(brightness));
|
||||
} else if (command instanceof RefreshType) {
|
||||
logger.debug("Refresh command received");
|
||||
return;
|
||||
} else {
|
||||
logger.warn("Unhandled command type: {}", command.getClass().getName());
|
||||
return;
|
||||
}
|
||||
// store panel's new HSB value
|
||||
logger.trace("Setting new color {}", newPanelColor);
|
||||
panelInfo.put(getThing().getConfiguration().get(CONFIG_PANEL_ID).toString(), newPanelColor);
|
||||
// transform to RGB
|
||||
PercentType[] rgbPercent = newPanelColor.toRGB();
|
||||
logger.trace("Setting new rgbpercent {} {} {}", rgbPercent[0], rgbPercent[1], rgbPercent[2]);
|
||||
int red = rgbPercent[0].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
|
||||
.multiply(new BigDecimal(255)).intValue();
|
||||
int green = rgbPercent[1].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
|
||||
.multiply(new BigDecimal(255)).intValue();
|
||||
int blue = rgbPercent[2].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
|
||||
.multiply(new BigDecimal(255)).intValue();
|
||||
logger.trace("Setting new rgb {} {} {}", red, green, blue);
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
Effects effects = new Effects();
|
||||
Write write = new Write();
|
||||
write.setCommand("display");
|
||||
write.setAnimType("static");
|
||||
String panelID = this.thing.getConfiguration().get(CONFIG_PANEL_ID).toString();
|
||||
@Nullable
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if (handler != null) {
|
||||
NanoleafControllerConfig config = ((NanoleafControllerHandler) handler).getControllerConfig();
|
||||
// Light Panels and Canvas use different stream commands
|
||||
if (config.deviceType.equals(CONFIG_DEVICE_TYPE_LIGHTPANELS)
|
||||
|| config.deviceType.equals(CONFIG_DEVICE_TYPE_CANVAS)) {
|
||||
logger.trace("Anim Data rgb {} {} {} {}", panelID, red, green, blue);
|
||||
write.setAnimData(String.format("1 %s 1 %d %d %d 0 10", panelID, red, green, blue));
|
||||
} else {
|
||||
// this is only used in special streaming situations with canvas which is not yet supported
|
||||
int quotient = Integer.divideUnsigned(Integer.valueOf(panelID), 256);
|
||||
int remainder = Integer.remainderUnsigned(Integer.valueOf(panelID), 256);
|
||||
write.setAnimData(
|
||||
String.format("0 1 %d %d %d %d %d 0 0 10", quotient, remainder, red, green, blue));
|
||||
}
|
||||
write.setLoop(false);
|
||||
effects.setWrite(write);
|
||||
Request setNewRenderedEffectRequest = OpenAPIUtils.requestBuilder(httpClient, config, API_EFFECT,
|
||||
HttpMethod.PUT);
|
||||
String content = gson.toJson(effects);
|
||||
logger.debug("sending effect command from panel {}: {}", getThing().getUID(), content);
|
||||
setNewRenderedEffectRequest.content(new StringContentProvider(content), "application/json");
|
||||
OpenAPIUtils.sendOpenAPIRequest(setNewRenderedEffectRequest);
|
||||
} else {
|
||||
logger.warn("Couldn't set rendering effect as Bridge-Handler {} is null", bridge.getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePanelColorChannel() {
|
||||
@Nullable
|
||||
HSBType panelColor = getPanelColor();
|
||||
logger.trace("updatePanelColorChannel: panelColor: {}", panelColor);
|
||||
if (panelColor != null)
|
||||
updateState(CHANNEL_PANEL_COLOR, panelColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the gesture to the panel
|
||||
*
|
||||
* @param gesture Only 0=single tap and 1=double tap are supported
|
||||
*/
|
||||
public void updatePanelGesture(int gesture) {
|
||||
switch (gesture) {
|
||||
case 0:
|
||||
updateState(CHANNEL_PANEL_SINGLE_TAP, OnOffType.ON);
|
||||
singleTapJob = scheduler.schedule(this::resetSingleTap, 1, TimeUnit.SECONDS);
|
||||
logger.debug("Asserting single tap of panel {} to ON", getPanelID());
|
||||
break;
|
||||
case 1:
|
||||
updateState(CHANNEL_PANEL_DOUBLE_TAP, OnOffType.ON);
|
||||
doubleTapJob = scheduler.schedule(this::resetDoubleTap, 1, TimeUnit.SECONDS);
|
||||
logger.debug("Asserting double tap of panel {} to ON", getPanelID());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSingleTap() {
|
||||
updateState(CHANNEL_PANEL_SINGLE_TAP, OnOffType.OFF);
|
||||
logger.debug("Resetting single tap of panel {} to OFF", getPanelID());
|
||||
}
|
||||
|
||||
private void resetDoubleTap() {
|
||||
updateState(CHANNEL_PANEL_DOUBLE_TAP, OnOffType.OFF);
|
||||
logger.debug("Resetting double tap of panel {} to OFF", getPanelID());
|
||||
}
|
||||
|
||||
public String getPanelID() {
|
||||
String panelID = getThing().getConfiguration().get(CONFIG_PANEL_ID).toString();
|
||||
return panelID;
|
||||
}
|
||||
|
||||
private @Nullable HSBType getPanelColor() {
|
||||
String panelID = getPanelID();
|
||||
|
||||
// get panel color data from controller
|
||||
try {
|
||||
Effects effects = new Effects();
|
||||
Write write = new Write();
|
||||
write.setCommand("request");
|
||||
write.setAnimName("*Static*");
|
||||
effects.setWrite(write);
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
NanoleafControllerHandler handler = (NanoleafControllerHandler) bridge.getHandler();
|
||||
if (handler != null) {
|
||||
NanoleafControllerConfig config = handler.getControllerConfig();
|
||||
logger.debug("Sending Request from Panel for getColor()");
|
||||
Request setPanelUpdateRequest = OpenAPIUtils.requestBuilder(httpClient, config, API_EFFECT,
|
||||
HttpMethod.PUT);
|
||||
setPanelUpdateRequest.content(new StringContentProvider(gson.toJson(effects)), "application/json");
|
||||
ContentResponse panelData = OpenAPIUtils.sendOpenAPIRequest(setPanelUpdateRequest);
|
||||
// parse panel data
|
||||
|
||||
parsePanelData(panelID, config, panelData);
|
||||
}
|
||||
}
|
||||
} catch (NanoleafNotFoundException nfe) {
|
||||
logger.warn("Panel data could not be retrieved as no data was returned (static type missing?) : {}",
|
||||
nfe.getMessage());
|
||||
} catch (NanoleafBadRequestException nfe) {
|
||||
logger.debug(
|
||||
"Panel data could not be retrieved as request not expected(static type missing / dynamic type on) : {}",
|
||||
nfe.getMessage());
|
||||
} catch (NanoleafException nue) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/error.nanoleaf.panel.communication");
|
||||
logger.warn("Panel data could not be retrieved: {}", nue.getMessage());
|
||||
}
|
||||
|
||||
return panelInfo.get(panelID);
|
||||
}
|
||||
|
||||
void parsePanelData(String panelID, NanoleafControllerConfig config, ContentResponse panelData) {
|
||||
// panelData is in format (numPanels, (PanelId, 1, R, G, B, W, TransitionTime) * numPanel)
|
||||
@Nullable
|
||||
Write response = gson.fromJson(panelData.getContentAsString(), Write.class);
|
||||
if (response != null) {
|
||||
String[] tokenizedData = response.getAnimData().split(" ");
|
||||
if (config.deviceType.equals(CONFIG_DEVICE_TYPE_LIGHTPANELS)
|
||||
|| config.deviceType.equals(CONFIG_DEVICE_TYPE_CANVAS)) {
|
||||
// panelData is in format (numPanels (PanelId 1 R G B W TransitionTime) * numPanel)
|
||||
String[] panelDataPoints = Arrays.copyOfRange(tokenizedData, 1, tokenizedData.length);
|
||||
for (int i = 0; i < panelDataPoints.length; i++) {
|
||||
if (i % 7 == 0) {
|
||||
String id = panelDataPoints[i];
|
||||
if (id.equals(panelID)) {
|
||||
// found panel data - store it
|
||||
panelInfo.put(panelID,
|
||||
HSBType.fromRGB(Integer.parseInt(panelDataPoints[i + 2]),
|
||||
Integer.parseInt(panelDataPoints[i + 3]),
|
||||
Integer.parseInt(panelDataPoints[i + 4])));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// panelData is in format (0 numPanels (quotient(panelID) remainder(panelID) R G B W 0
|
||||
// quotient(TransitionTime) remainder(TransitionTime)) * numPanel)
|
||||
String[] panelDataPoints = Arrays.copyOfRange(tokenizedData, 2, tokenizedData.length);
|
||||
for (int i = 0; i < panelDataPoints.length; i++) {
|
||||
if (i % 8 == 0) {
|
||||
String idQuotient = panelDataPoints[i];
|
||||
String idRemainder = panelDataPoints[i + 1];
|
||||
Integer idNum = Integer.valueOf(idQuotient) * 256 + Integer.valueOf(idRemainder);
|
||||
if (String.valueOf(idNum).equals(panelID)) {
|
||||
// found panel data - store it
|
||||
panelInfo.put(panelID,
|
||||
HSBType.fromRGB(Integer.parseInt(panelDataPoints[i + 3]),
|
||||
Integer.parseInt(panelDataPoints[i + 4]),
|
||||
Integer.parseInt(panelDataPoints[i + 5])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents an Authorization Token
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AuthToken {
|
||||
|
||||
@SerializedName("auth_token")
|
||||
private @Nullable String authToken;
|
||||
|
||||
public @Nullable String getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
public void setAuthToken(String authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for boolean value states
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface BooleanState {
|
||||
|
||||
boolean getValue();
|
||||
|
||||
void setValue(boolean value);
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents brightness setting of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Brightness implements IntegerState {
|
||||
|
||||
private int value;
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents color temperature of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Command {
|
||||
|
||||
@SerializedName("write")
|
||||
private @Nullable Write write;
|
||||
|
||||
public @Nullable Write getWrite() {
|
||||
return write;
|
||||
}
|
||||
|
||||
public void setWrite(Write write) {
|
||||
this.write = write;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents the light panels controller information
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ControllerInfo {
|
||||
|
||||
private String name = "";
|
||||
private String serialNo = "";
|
||||
private String manufacturer = "";
|
||||
private String firmwareVersion = "";
|
||||
private String model = "";
|
||||
private State state = new State();
|
||||
private Effects effects = new Effects();
|
||||
private PanelLayout panelLayout = new PanelLayout();
|
||||
private Rhythm rhythm = new Rhythm();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getSerialNo() {
|
||||
return serialNo;
|
||||
}
|
||||
|
||||
public void setSerialNo(String serialNo) {
|
||||
this.serialNo = serialNo;
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
return firmwareVersion;
|
||||
}
|
||||
|
||||
public void setFirmwareVersion(String firmwareVersion) {
|
||||
this.firmwareVersion = firmwareVersion;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public Effects getEffects() {
|
||||
return effects;
|
||||
}
|
||||
|
||||
public void setEffects(Effects effects) {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
public PanelLayout getPanelLayout() {
|
||||
return panelLayout;
|
||||
}
|
||||
|
||||
public void setPanelLayout(PanelLayout panelLayout) {
|
||||
this.panelLayout = panelLayout;
|
||||
}
|
||||
|
||||
public Rhythm getRhythm() {
|
||||
return rhythm;
|
||||
}
|
||||
|
||||
public void setRhythm(Rhythm rhythm) {
|
||||
this.rhythm = rhythm;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents color temperature of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Ct implements IntegerState {
|
||||
|
||||
private int value;
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
public @Nullable Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(@Nullable Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(@Nullable Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents effect commands for select and write
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Effects {
|
||||
|
||||
private @Nullable String select;
|
||||
private @Nullable List<String> effectsList = null;
|
||||
private @Nullable Write write;
|
||||
|
||||
public @Nullable String getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
public void setSelect(@Nullable String select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
public @Nullable List<String> getEffectsList() {
|
||||
return effectsList;
|
||||
}
|
||||
|
||||
public void setEffectsList(@Nullable List<String> effectsList) {
|
||||
this.effectsList = effectsList;
|
||||
}
|
||||
|
||||
public @Nullable Write getWrite() {
|
||||
return write;
|
||||
}
|
||||
|
||||
public void setWrite(@Nullable Write write) {
|
||||
this.write = write;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents global orientation settings of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GlobalOrientation {
|
||||
|
||||
private int value;
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents hue setting of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Hue implements IntegerState {
|
||||
|
||||
private int value;
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for settings with integer value
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IntegerState {
|
||||
|
||||
void setValue(int value);
|
||||
|
||||
int getValue();
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents layout of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Layout {
|
||||
|
||||
private int numPanels;
|
||||
private int sideLength;
|
||||
|
||||
private @Nullable List<PositionDatum> positionData = null;
|
||||
|
||||
public int getNumPanels() {
|
||||
return numPanels;
|
||||
}
|
||||
|
||||
public void setNumPanels(int numPanels) {
|
||||
this.numPanels = numPanels;
|
||||
}
|
||||
|
||||
public int getSideLength() {
|
||||
return sideLength;
|
||||
}
|
||||
|
||||
public void setSideLength(int sideLength) {
|
||||
this.sideLength = sideLength;
|
||||
}
|
||||
|
||||
public @Nullable List<PositionDatum> getPositionData() {
|
||||
return positionData;
|
||||
}
|
||||
|
||||
public void setPositionData(List<PositionDatum> positionData) {
|
||||
this.positionData = positionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an text representation for a canvas layout.
|
||||
*
|
||||
* Note only canvas supported currently due to its easy geometry
|
||||
*
|
||||
* @return a String containing the layout
|
||||
*/
|
||||
public String getLayoutView() {
|
||||
if (positionData != null) {
|
||||
String view = "";
|
||||
|
||||
int minx = Integer.MAX_VALUE;
|
||||
int maxx = Integer.MIN_VALUE;
|
||||
int miny = Integer.MAX_VALUE;
|
||||
int maxy = Integer.MIN_VALUE;
|
||||
|
||||
final int noofDefinedPanels = positionData.size();
|
||||
for (int index = 0; index < noofDefinedPanels; index++) {
|
||||
if (positionData != null) {
|
||||
@Nullable
|
||||
PositionDatum panel = positionData.get(index);
|
||||
|
||||
if (panel != null) {
|
||||
if (panel.getPosX() < minx) {
|
||||
minx = panel.getPosX();
|
||||
}
|
||||
if (panel.getPosX() > maxx) {
|
||||
maxx = panel.getPosX();
|
||||
}
|
||||
if (panel.getPosY() < miny) {
|
||||
miny = panel.getPosY();
|
||||
}
|
||||
if (panel.getPosY() > maxy) {
|
||||
maxy = panel.getPosY();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int shiftWidth = getSideLength() / 2;
|
||||
|
||||
int lineY = maxy;
|
||||
Map<Integer, PositionDatum> map;
|
||||
|
||||
while (lineY >= miny) {
|
||||
map = new TreeMap<>();
|
||||
for (int index = 0; index < noofDefinedPanels; index++) {
|
||||
|
||||
if (positionData != null) {
|
||||
@Nullable
|
||||
PositionDatum panel = positionData.get(index);
|
||||
|
||||
if (panel != null && panel.getPosY() == lineY)
|
||||
map.put(panel.getPosX(), panel);
|
||||
}
|
||||
}
|
||||
lineY -= shiftWidth;
|
||||
for (int x = minx; x <= maxx; x += shiftWidth) {
|
||||
if (map.containsKey(x)) {
|
||||
@Nullable
|
||||
PositionDatum panel = map.get(x);
|
||||
view += String.format("%5s ", panel.getPanelId());
|
||||
} else
|
||||
view += " ";
|
||||
}
|
||||
view += "\n";
|
||||
}
|
||||
|
||||
return view;
|
||||
} else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents power state of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class On implements BooleanState {
|
||||
|
||||
private boolean value;
|
||||
|
||||
@Override
|
||||
public boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents color palette in the write command
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Palette {
|
||||
|
||||
private int hue;
|
||||
private int saturation;
|
||||
private int brightness;
|
||||
|
||||
public int getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
public void setHue(int hue) {
|
||||
this.hue = hue;
|
||||
}
|
||||
|
||||
public int getSaturation() {
|
||||
return saturation;
|
||||
}
|
||||
|
||||
public void setSaturation(int saturation) {
|
||||
this.saturation = saturation;
|
||||
}
|
||||
|
||||
public int getBrightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
public void setBrightness(int brightness) {
|
||||
this.brightness = brightness;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents panel layout of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PanelLayout {
|
||||
|
||||
private @Nullable Layout layout;
|
||||
private @Nullable GlobalOrientation globalOrientation;
|
||||
|
||||
public @Nullable Layout getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void setLayout(Layout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
public @Nullable GlobalOrientation getGlobalOrientation() {
|
||||
return globalOrientation;
|
||||
}
|
||||
|
||||
public void setGlobalOrientation(GlobalOrientation globalOrientation) {
|
||||
this.globalOrientation = globalOrientation;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents panel position
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PositionDatum {
|
||||
|
||||
private int panelId;
|
||||
@SerializedName("x")
|
||||
private int posX;
|
||||
@SerializedName("y")
|
||||
private int posY;
|
||||
@SerializedName("o")
|
||||
private int orientation;
|
||||
|
||||
public int getPanelId() {
|
||||
return panelId;
|
||||
}
|
||||
|
||||
public void setPanelId(int panelId) {
|
||||
this.panelId = panelId;
|
||||
}
|
||||
|
||||
public int getPosX() {
|
||||
return posX;
|
||||
}
|
||||
|
||||
public void setPosX(int x) {
|
||||
this.posX = x;
|
||||
}
|
||||
|
||||
public int getPosY() {
|
||||
return posY;
|
||||
}
|
||||
|
||||
public void setPosY(int y) {
|
||||
this.posY = y;
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(int o) {
|
||||
this.orientation = o;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents rhythm module settings
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Rhythm {
|
||||
|
||||
private boolean rhythmConnected;
|
||||
private boolean rhythmActive;
|
||||
private int rhythmId;
|
||||
private String hardwareVersion = "";
|
||||
private String firmwareVersion = "";
|
||||
private boolean auxAvailable;
|
||||
private int rhythmMode;
|
||||
private @Nullable RhythmPos rhythmPos;
|
||||
|
||||
public boolean getRhythmConnected() {
|
||||
return rhythmConnected;
|
||||
}
|
||||
|
||||
public void setRhythmConnected(boolean rhythmConnected) {
|
||||
this.rhythmConnected = rhythmConnected;
|
||||
}
|
||||
|
||||
public boolean getRhythmActive() {
|
||||
return rhythmActive;
|
||||
}
|
||||
|
||||
public void setRhythmActive(boolean rhythmActive) {
|
||||
this.rhythmActive = rhythmActive;
|
||||
}
|
||||
|
||||
public int getRhythmId() {
|
||||
return rhythmId;
|
||||
}
|
||||
|
||||
public void setRhythmId(int rhythmId) {
|
||||
this.rhythmId = rhythmId;
|
||||
}
|
||||
|
||||
public String getHardwareVersion() {
|
||||
return this.hardwareVersion;
|
||||
}
|
||||
|
||||
public void setHardwareVersion(String hardwareVersion) {
|
||||
this.hardwareVersion = hardwareVersion;
|
||||
}
|
||||
|
||||
public String getFirmwareVersion() {
|
||||
return this.firmwareVersion;
|
||||
}
|
||||
|
||||
public void setFirmwareVersion(String firmwareVersion) {
|
||||
this.firmwareVersion = firmwareVersion;
|
||||
}
|
||||
|
||||
public boolean getAuxAvailable() {
|
||||
return auxAvailable;
|
||||
}
|
||||
|
||||
public void setAuxAvailable(boolean auxAvailable) {
|
||||
this.auxAvailable = auxAvailable;
|
||||
}
|
||||
|
||||
public int getRhythmMode() {
|
||||
return rhythmMode;
|
||||
}
|
||||
|
||||
public void setRhythmMode(int rhythmMode) {
|
||||
this.rhythmMode = rhythmMode;
|
||||
}
|
||||
|
||||
public @Nullable RhythmPos getRhythmPos() {
|
||||
return rhythmPos;
|
||||
}
|
||||
|
||||
public void setRhythmPos(RhythmPos rhythmPos) {
|
||||
this.rhythmPos = rhythmPos;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents rhythm module position
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RhythmPos {
|
||||
|
||||
@SerializedName("x")
|
||||
private float posX;
|
||||
@SerializedName("y")
|
||||
private float posY;
|
||||
@SerializedName("o")
|
||||
private float orientation;
|
||||
|
||||
public float getPosX() {
|
||||
return posX;
|
||||
}
|
||||
|
||||
public void setPosX(float x) {
|
||||
this.posX = x;
|
||||
}
|
||||
|
||||
public float getPosY() {
|
||||
return posY;
|
||||
}
|
||||
|
||||
public void setPosY(float y) {
|
||||
this.posY = y;
|
||||
}
|
||||
|
||||
public float getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(float o) {
|
||||
this.orientation = o;
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents saturation setting of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Sat implements IntegerState {
|
||||
|
||||
private int value;
|
||||
private @Nullable Integer max;
|
||||
private @Nullable Integer min;
|
||||
|
||||
@Override
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMax(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public @Nullable Integer getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
public void setMin(Integer min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Represents overall state settings of the light panels
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class State {
|
||||
|
||||
private @Nullable On on;
|
||||
private @Nullable Brightness brightness;
|
||||
private @Nullable Hue hue;
|
||||
@SerializedName("sat")
|
||||
private @Nullable Sat saturation;
|
||||
@SerializedName("ct")
|
||||
private @Nullable Ct colorTemperature;
|
||||
|
||||
private @Nullable String colorMode;
|
||||
|
||||
public @Nullable On getOn() {
|
||||
return on;
|
||||
}
|
||||
|
||||
public OnOffType getOnOff() {
|
||||
return (on != null && on.getValue()) ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
|
||||
public void setOn(On on) {
|
||||
this.on = on;
|
||||
}
|
||||
|
||||
public @Nullable Brightness getBrightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
public void setBrightness(Brightness brightness) {
|
||||
this.brightness = brightness;
|
||||
}
|
||||
|
||||
public @Nullable Hue getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
public void setHue(Hue hue) {
|
||||
this.hue = hue;
|
||||
}
|
||||
|
||||
public @Nullable Sat getSaturation() {
|
||||
return saturation;
|
||||
}
|
||||
|
||||
public void setSaturation(Sat sat) {
|
||||
this.saturation = sat;
|
||||
}
|
||||
|
||||
public @Nullable Ct getColorTemperature() {
|
||||
return colorTemperature;
|
||||
}
|
||||
|
||||
public void setColorTemperature(Ct ct) {
|
||||
this.colorTemperature = ct;
|
||||
}
|
||||
|
||||
public @Nullable String getColorMode() {
|
||||
return colorMode;
|
||||
}
|
||||
|
||||
public void setColorMode(String colorMode) {
|
||||
this.colorMode = colorMode;
|
||||
}
|
||||
|
||||
public void setState(IntegerState value) {
|
||||
if (value instanceof Brightness) {
|
||||
this.setBrightness((Brightness) value);
|
||||
} else if (value instanceof Hue) {
|
||||
this.setHue((Hue) value);
|
||||
} else if (value instanceof Sat) {
|
||||
this.setSaturation((Sat) value);
|
||||
} else if (value instanceof Ct) {
|
||||
this.setColorTemperature((Ct) value);
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(BooleanState value) {
|
||||
if (value instanceof On) {
|
||||
this.setOn((On) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Nanoleaf Panel TouchEvent provided by controller
|
||||
*
|
||||
*
|
||||
* JSON
|
||||
* {"events":
|
||||
* [
|
||||
* { "panelId":48111,
|
||||
* "gesture":0},
|
||||
* { "panelId":48112,
|
||||
* * "gesture":1}
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TouchEvent {
|
||||
|
||||
private String panelId = "";
|
||||
private int gesture = -1;
|
||||
|
||||
public String getPanelId() {
|
||||
return panelId;
|
||||
}
|
||||
|
||||
public void setPanelId(String panelId) {
|
||||
this.panelId = panelId;
|
||||
}
|
||||
|
||||
public int getGesture() {
|
||||
return gesture;
|
||||
}
|
||||
|
||||
public void setGesture(int gesture) {
|
||||
this.gesture = gesture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents effect commands for select and write
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TouchEvents {
|
||||
|
||||
private List<TouchEvent> events = new ArrayList<>();
|
||||
|
||||
public List<TouchEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public void setEvents(List<TouchEvent> events) {
|
||||
this.events = events;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Represents write command to set solid color effect
|
||||
*
|
||||
* @author Martin Raepple - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Write {
|
||||
|
||||
private String command = "";
|
||||
private String animType = "";
|
||||
private String animName = "";
|
||||
private List<Palette> palette = new ArrayList<>();
|
||||
private String colorType = "";
|
||||
private String animData = "";
|
||||
private boolean loop = false;
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public String getAnimType() {
|
||||
return animType;
|
||||
}
|
||||
|
||||
public void setAnimType(String animType) {
|
||||
this.animType = animType;
|
||||
}
|
||||
|
||||
public List<Palette> getPalette() {
|
||||
return palette;
|
||||
}
|
||||
|
||||
public void setPalette(List<Palette> palette) {
|
||||
this.palette = palette;
|
||||
}
|
||||
|
||||
public String getColorType() {
|
||||
return colorType;
|
||||
}
|
||||
|
||||
public void setColorType(String colorType) {
|
||||
this.colorType = colorType;
|
||||
}
|
||||
|
||||
public String getAnimData() {
|
||||
return animData;
|
||||
}
|
||||
|
||||
public void setAnimData(String animData) {
|
||||
this.animData = animData;
|
||||
}
|
||||
|
||||
public boolean getLoop() {
|
||||
return loop;
|
||||
}
|
||||
|
||||
public void setLoop(boolean loop) {
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
public String getAnimName() {
|
||||
return animName;
|
||||
}
|
||||
|
||||
public void setAnimName(String animName) {
|
||||
this.animName = animName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="nanoleaf" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>@text/binding.nanoleaf.name</name>
|
||||
<description>@text/binding.nanoleaf.description</description>
|
||||
<author>Martin Raepple, Stefan Höhn</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:nanoleaf:controller">
|
||||
<parameter name="address" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>@text/thing-type.config.nanoleaf.controller.address.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.controller.address.description</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true" min="1" max="65535">
|
||||
<label>@text/thing-type.config.nanoleaf.controller.port.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.controller.port.description</description>
|
||||
<default>16021</default>
|
||||
</parameter>
|
||||
<parameter name="authToken" type="text">
|
||||
<context>password</context>
|
||||
<label>@text/thing-type.config.nanoleaf.controller.authToken.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.controller.authToken.description</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" unit="s">
|
||||
<label>@text/thing-type.config.nanoleaf.controller.refreshInterval.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.controller.refreshInterval.description</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
<parameter name="deviceType" type="text" readOnly="true">
|
||||
<label>@text/thing-type.config.nanoleaf.controller.deviceType.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.controller.deviceType.description</description>
|
||||
<default>lightPanels</default>
|
||||
<options>
|
||||
<option value="lightPanels">Light Panels</option>
|
||||
<option value="canvas">Canvas</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:nanoleaf:lightpanel">
|
||||
<parameter name="id" type="integer" required="true">
|
||||
<label>@text/thing-type.config.nanoleaf.lightpanel.id.label</label>
|
||||
<description>@text/thing-type.config.nanoleaf.lightpanel.id.description</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,65 @@
|
||||
binding.nanoleaf.name = Nanoleaf Binding
|
||||
binding.nanoleaf.description = Integrates the Nanoleaf Light Panels (v150320)
|
||||
|
||||
# thing types
|
||||
thing-type.nanoleaf.controller.name = Nanoleaf Controller
|
||||
thing-type.nanoleaf.controller.description = The Nanoleaf controller (bridge) device
|
||||
thing-type.nanoleaf.lightpanel.name = Nanoleaf Panel
|
||||
thing-type.nanoleaf.lightpanel.description = A panel connected to the Nanoleaf controller
|
||||
|
||||
# config
|
||||
thing-type.config.nanoleaf.controller.address.label = IP Address Or Hostname
|
||||
thing-type.config.nanoleaf.controller.address.description = IP address or hostname of the Nanoleaf controller, for example 192.168.0.10
|
||||
thing-type.config.nanoleaf.controller.port.label = Port
|
||||
thing-type.config.nanoleaf.controller.port.description = Port number of the Nanoleaf API, for example 16021
|
||||
thing-type.config.nanoleaf.controller.authToken.label = Authorization Token
|
||||
thing-type.config.nanoleaf.controller.authToken.description = Authorization token, required by openHAB to call the controller API. For pairing, press the on-off button of the controller for 5-7 seconds until the LED starts flashing in a pattern.
|
||||
thing-type.config.nanoleaf.controller.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.nanoleaf.controller.refreshInterval.description = Interval (in seconds) to refresh the controller channels status
|
||||
thing-type.config.nanoleaf.controller.deviceType.label = Nanoleaf Device Type
|
||||
thing-type.config.nanoleaf.controller.deviceType.description = Light Panels (triangles) or Canvas (squares)
|
||||
thing-type.config.nanoleaf.lightpanel.id.label = ID Of The Panel
|
||||
thing-type.config.nanoleaf.lightpanel.id.description = The ID of the panel assigned by the controller
|
||||
|
||||
# channel types
|
||||
channel-type.nanoleaf.power.label = Power
|
||||
channel-type.nanoleaf.power.description = Power state of the controller
|
||||
channel-type.nanoleaf.color.label = Color
|
||||
channel-type.nanoleaf.color.description = Color setting for an individual or all panels
|
||||
channel-type.nanoleaf.colorTemperature.label = Color Temperature
|
||||
channel-type.nanoleaf.colorTemperature.description = Color temperature in percent
|
||||
channel-type.nanoleaf.colorTemperatureAbs.label = Color Temperature (K)
|
||||
channel-type.nanoleaf.colorTemperatureAbs.description = Color temperature in Kelvin (K)
|
||||
channel-type.nanoleaf.colorMode.label = Color Mode
|
||||
channel-type.nanoleaf.colorMode.description = Current color mode: Effect, hue/saturation or color temperature
|
||||
channel-type.nanoleaf.effect.label = Effect
|
||||
channel-type.nanoleaf.effect.description = Effect or scene currently playing
|
||||
channel-type.nanoleaf.rhythmState.label = Rhythm State
|
||||
channel-type.nanoleaf.rhythmState.description = Connection state of the rhythm module
|
||||
channel-type.nanoleaf.rhythmActive.label = Rhythm Active
|
||||
channel-type.nanoleaf.rhythmActive.description = Activity state of the rhythm module
|
||||
channel-type.nanoleaf.rhythmMode.label = Rhythm Mode
|
||||
channel-type.nanoleaf.rhythmMode.description = Sound source for the rhythm module (microphone or aux cable)
|
||||
channel-type.nanoleaf.panelLayout.label = Panel Layout
|
||||
channel-type.nanoleaf.panelLayout.description = Creates a panel layout upon request
|
||||
channel-type.nanoleaf.panelColor.label = Panel Color
|
||||
channel-type.nanoleaf.panelColor.description = Color of the individual panel
|
||||
channel-type.nanoleaf.singleTap.label = SingleTap
|
||||
channel-type.nanoleaf.singleTap.description = Single tap on the panel
|
||||
channel-type.nanoleaf.doubleTap.label = DoubleTap
|
||||
channel-type.nanoleaf.doubleTap.description = Double tap on the panel
|
||||
|
||||
# error messages
|
||||
error.nanoleaf.controller.noIp = IP/host address and/or port are not configured for the controller.
|
||||
error.nanoleaf.controller.incompatibleFirmware = Incompatible controller firmware. Remove the device, update the firmware, and add it again.
|
||||
error.nanoleaf.controller.noToken = No authorization token found. To start pairing, press the on-off button of the controller for 5-7 seconds until the LED starts flashing in a pattern.
|
||||
error.nanoleaf.controller.invalidToken = Invalid token. Replace with valid token or start pairing again by removing the invalid token from the configuration.
|
||||
error.nanoleaf.controller.communication = Communication failed. Please check configuration.
|
||||
error.nanoleaf.controller.pairingFailed = Pairing failed. Press the on-off button for 5-7 seconds until the LED starts flashing in a pattern.
|
||||
error.nanoleaf.controller.invalidData = Pairing failed. Received invalid data
|
||||
error.nanoleaf.controller.noTokenReceived = Pairing failed. No authorization token received from controller.
|
||||
error.nanoleaf.controller.authRequest = Pairing failed. Cannot send authorization request.
|
||||
error.nanoleaf.controller.noClient = Pairing failed. Cannot start HTTP client.
|
||||
error.nanoleaf.controller.runtime = Runtime error. See openHAB log for more details.
|
||||
error.nanoleaf.panel.communication = Panel data could not be retrieved. Please check controller configuration.
|
||||
error.nanoleaf.panel.controllerOffline = Controller is offline. Check configuration.
|
||||
@@ -0,0 +1,65 @@
|
||||
binding.nanoleaf.name = Nanoleaf Binding
|
||||
binding.nanoleaf.description = Binding für die Integration des Nanoleaf Light Panels (v150320)
|
||||
|
||||
# thing types
|
||||
thing-type.nanoleaf.controller.name = Nanoleaf Controller
|
||||
thing-type.nanoleaf.controller.description = Nanoleaf Controller (Brücke)
|
||||
thing-type.nanoleaf.lightpanel.name = Nanoleaf Paneel
|
||||
thing-type.nanoleaf.lightpanel.description = Ein mit dem Controller verbundenes Paneel
|
||||
|
||||
# config
|
||||
thing-type.config.nanoleaf.controller.address.label = IP Adresse oder Hostname
|
||||
thing-type.config.nanoleaf.controller.address.description = IP Adresse oder Hostname des Nanoleaf Controllers, z. B. 192.168.0.10
|
||||
thing-type.config.nanoleaf.controller.port.label = Port
|
||||
thing-type.config.nanoleaf.controller.port.description = Portnummer des Controllers, z. B. 16021
|
||||
thing-type.config.nanoleaf.controller.authToken.label = Authentifizierungstoken
|
||||
thing-type.config.nanoleaf.controller.authToken.description = Token zur Authentifizierung. Um openHAB mit dem Nanoleaf Light Panels zu verbinden (Pairing) den An/Aus-Schalter am Controller für 5-7 Sekunden gedrückthalten, bis die LED zu blinken beginnt.
|
||||
thing-type.config.nanoleaf.controller.refreshInterval.label = Aktualisierungsintervall
|
||||
thing-type.config.nanoleaf.controller.refreshInterval.description = Intervall (in Sekunden) in dem die Kanäle aktualisiert werden
|
||||
thing-type.config.nanoleaf.controller.deviceType.label = Nanoleaf Gerätetyp
|
||||
thing-type.config.nanoleaf.controller.deviceType.description = Light Panels (Dreiecke) oder Canvas (Quadrate)
|
||||
thing-type.config.nanoleaf.lightpanel.id.label = Paneel ID
|
||||
thing-type.config.nanoleaf.lightpanel.id.description = Vom Controller vergebene ID eines Paneels
|
||||
|
||||
# channel types
|
||||
channel-type.nanoleaf.power.label = Power
|
||||
channel-type.nanoleaf.power.description = Ermöglicht das An- und Ausschalten des Nanoleaf Light Panels
|
||||
channel-type.nanoleaf.color.label = Farbe
|
||||
channel-type.nanoleaf.color.description = Farbe aller oder eines einzelnen Paneels
|
||||
channel-type.nanoleaf.colorTemperature.label = Farbtemperatur
|
||||
channel-type.nanoleaf.colorTemperature.description = Farbtemperatur in Prozent
|
||||
channel-type.nanoleaf.colorTemperatureAbs.label = Farbtemperatur (K)
|
||||
channel-type.nanoleaf.colorTemperatureAbs.description = Farbtemperatur in Kelvin (K)
|
||||
channel-type.nanoleaf.colorMode.label = Farbmodus
|
||||
channel-type.nanoleaf.colorMode.description = Effekt, Hue/Sat oder Farbtemperatur für alle Paneele.
|
||||
channel-type.nanoleaf.effect.label = Effekt
|
||||
channel-type.nanoleaf.effect.description = Einstellung des Effektes
|
||||
channel-type.nanoleaf.rhythmState.label = Rhythm Status
|
||||
channel-type.nanoleaf.rhythmState.description = Anschlusszustand des Rhythm Moduls
|
||||
channel-type.nanoleaf.rhythmActive.label = Rhythm Aktiv
|
||||
channel-type.nanoleaf.rhythmActive.description = Zeigt an ob das Mikrofon des Rhythm Modules ativ ist.
|
||||
channel-type.nanoleaf.rhythmMode.label = Rhythm Modus
|
||||
channel-type.nanoleaf.rhythmMode.description = Erlaubt den Wechsel zwischen eingebautem Mikrofon und AUX-Kabel.
|
||||
channel-type.nanoleaf.panelLayout.label = PanelLayout
|
||||
channel-type.nanoleaf.panelLayout.description = Erzeugt auf Anfrage ein Panel-Layout
|
||||
channel-type.nanoleaf.panelColor.label = Paneelfarbe
|
||||
channel-type.nanoleaf.panelColor.description = Farbe des einzelnen Paneels
|
||||
channel-type.nanoleaf.singleTap.label = Einzel-Tap
|
||||
channel-type.nanoleaf.singleTap.description = Panel wurde einmal angetippt
|
||||
channel-type.nanoleaf.doubleTap.label = Doppel-Tap
|
||||
channel-type.nanoleaf.doubleTap.description = Panel wurde zweimal hintereinander angetippt
|
||||
|
||||
# error messages
|
||||
error.nanoleaf.controller.noIp = IP/Host-Adresse und/oder Port sind für den Controller nicht konfiguriert.
|
||||
error.nanoleaf.controller.incompatibleFirmware = Inkompatible Controller-Firmware. Firmware aktualisieren und das Gerät neu hinzufügen.
|
||||
error.nanoleaf.controller.noToken = Kein Authentifizierungstoken gefunden. Um das Pairing zu starten, den An/Aus-Knopf am Controller für 5-7 Sekunden lang gedrückt halten, bis die LED anfängt zu blinken.
|
||||
error.nanoleaf.controller.invalidToken = Ungültiges Authentifizierungstoken. Token ändern oder das Pairing neu starten durch Löschen des ungültigen Tokens.
|
||||
error.nanoleaf.controller.communication = Kommunikationsfehler. Konfiguration des Controllers prüfen.
|
||||
error.nanoleaf.controller.pairingFailed = Pairing fehlgeschlagen. Um das Pairing zu starten, den An/Aus-Knopf am Controller für 5-7 Sekunden lang gedrückt halten, bis die LED anfängt zu blinken.
|
||||
error.nanoleaf.controller.invalidData = Pairing fehlgeschlagen. Ungültige Daten vom Controller empfangen.
|
||||
error.nanoleaf.controller.noTokenReceived = Pairing fehlgeschlagen. Kein Authentifizierungstoken empfangen.
|
||||
error.nanoleaf.controller.authRequest = Pairing fehlgeschlagen. Berechtigungsanfrage konnte nicht an den Controller gesendet werden.
|
||||
error.nanoleaf.controller.noClient = Pairing fehlgeschlagen. HTTP Client nicht verfügbar.
|
||||
error.nanoleaf.controller.runtime = Laufzeitfehler. Siehe openHAB Logdatei für mehr Details.
|
||||
error.nanoleaf.panel.communication = Paneeldaten konnten nicht empfangen werden. Konfiguration des Controllers prüfen.
|
||||
error.nanoleaf.panel.controllerOffline = Controller ist nicht erreichbar. Konfiguration prüfen.
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="nanoleaf"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="controller">
|
||||
<label>@text/thing-type.nanoleaf.controller.name</label>
|
||||
<description>@text/thing-type.nanoleaf.controller.description</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="color" typeId="color"/>
|
||||
<channel id="colorTemperature" typeId="colorTemperature"/>
|
||||
<channel id="colorTemperatureAbs" typeId="colorTemperatureAbs"/>
|
||||
<channel id="colorMode" typeId="colorMode"/>
|
||||
<channel id="effect" typeId="effect"/>
|
||||
<channel id="rhythmState" typeId="rhythmState"/>
|
||||
<channel id="rhythmActive" typeId="rhythmActive"/>
|
||||
<channel id="rhythmMode" typeId="rhythmMode"/>
|
||||
<channel id="panelLayout" typeId="panelLayout"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor"/>
|
||||
<property name="serialNumber"/>
|
||||
<property name="firmwareVersion"/>
|
||||
<property name="modelId"/>
|
||||
</properties>
|
||||
|
||||
<representation-property>address</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:nanoleaf:controller"/>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="lightpanel">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="controller"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>@text/thing-type.nanoleaf.lightpanel.name</label>
|
||||
<description>@text/thing-type.nanoleaf.lightpanel.description</description>
|
||||
<channels>
|
||||
<channel id="panelColor" typeId="color"/>
|
||||
<channel id="singleTap" typeId="singleTap"/>
|
||||
<channel id="doubleTap" typeId="doubleTap"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:nanoleaf:lightpanel"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="power">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.power.label</label>
|
||||
<description>@text/channel-type.nanoleaf.power.description</description>
|
||||
<category>Switch</category>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="ON">On</option>
|
||||
<option value="OFF">Off</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="colorTemperature">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>@text/channel-type.nanoleaf.colorTemperature.label</label>
|
||||
<description>@text/channel-type.nanoleaf.colorTemperature.description</description>
|
||||
<state min="0" max="100" step="1"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="colorTemperatureAbs">
|
||||
<item-type>Number</item-type>
|
||||
<label>@text/channel-type.nanoleaf.colorTemperatureAbs.label</label>
|
||||
<description>@text/channel-type.nanoleaf.colorTemperatureAbs.description</description>
|
||||
<category>ColorLight</category>
|
||||
<state min="1200" max="6500" step="100"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="colorMode">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.nanoleaf.colorMode.label</label>
|
||||
<description>@text/channel-type.nanoleaf.colorMode.description</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="effect">Effect mode</option>
|
||||
<option value="hs">Hue/Saturation</option>
|
||||
<option value="ct">Color temperature</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>@text/channel-type.nanoleaf.color.label</label>
|
||||
<description>@text/Color of the Light Panels</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="effect">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.nanoleaf.effect.label</label>
|
||||
<description>@text/channel-type.nanoleaf.effect.description</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rhythmState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.rhythmState.label</label>
|
||||
<description>@text/channel-type.nanoleaf.rhythmState.description</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rhythmActive">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.rhythmActive.label</label>
|
||||
<description>@text/channel-type.nanoleaf.rhythmActive.description</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rhythmMode">
|
||||
<item-type>Number</item-type>
|
||||
<label>@text/channel-type.nanoleaf.rhythmMode.label</label>
|
||||
<description>@text/channel-type.nanoleaf.rhythmMode.description</description>
|
||||
<state min="0" max="1">
|
||||
<options>
|
||||
<option value="0">Microphone</option>
|
||||
<option value="1">Aux cable</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="singleTap">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.singleTap.label</label>
|
||||
<description>@text/channel-type.nanoleaf.singleTap.description</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="doubleTap">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.doubleTap.label</label>
|
||||
<description>@text/channel-type.nanoleaf.doubleTap.description</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="panelLayout">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.nanoleaf.panelLayout.label</label>
|
||||
<description>@text/channel-type.nanoleaf.panelLayout.description</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -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.nanoleaf.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.nanoleaf.internal.model.Layout;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Test for the Layout
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class LayoutTest {
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
String layout1Json = "";
|
||||
String layoutInconsistentPanelNoJson = "";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
layout1Json = "{\"numPanels\":14,\"sideLength\":100,\"positionData\":[{\"panelId\":41451,\"x\":350,\"y\":0,\"o\":0,\"shapeType\":3},{\"panelId\":8134,\"x\":350,\"y\":150,\"o\":0,\"shapeType\":2},{\"panelId\":58086,\"x\":200,\"y\":100,\"o\":270,\"shapeType\":2},{\"panelId\":38724,\"x\":300,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":48111,\"x\":200,\"y\":200,\"o\":270,\"shapeType\":2},{\"panelId\":56093,\"x\":100,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":55836,\"x\":0,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":31413,\"x\":100,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":9162,\"x\":300,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":13276,\"x\":400,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":17870,\"x\":400,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":5164,\"x\":500,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":64279,\"x\":600,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":39755,\"x\":500,\"y\":100,\"o\":90,\"shapeType\":2}]}";
|
||||
// panel number is not consistent to returned panels in array but it should still work
|
||||
layoutInconsistentPanelNoJson = "{\"numPanels\":15,\"sideLength\":100,\"positionData\":[{\"panelId\":41451,\"x\":350,\"y\":0,\"o\":0,\"shapeType\":3},{\"panelId\":8134,\"x\":350,\"y\":150,\"o\":0,\"shapeType\":2},{\"panelId\":58086,\"x\":200,\"y\":100,\"o\":270,\"shapeType\":2},{\"panelId\":38724,\"x\":300,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":48111,\"x\":200,\"y\":200,\"o\":270,\"shapeType\":2},{\"panelId\":56093,\"x\":100,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":55836,\"x\":0,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":31413,\"x\":100,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":9162,\"x\":300,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":13276,\"x\":400,\"y\":300,\"o\":90,\"shapeType\":2},{\"panelId\":17870,\"x\":400,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":5164,\"x\":500,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":64279,\"x\":600,\"y\":200,\"o\":0,\"shapeType\":2},{\"panelId\":39755,\"x\":500,\"y\":100,\"o\":90,\"shapeType\":2}]}";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTheRightLayoutView() {
|
||||
@Nullable
|
||||
Layout layout = gson.fromJson(layout1Json, Layout.class);
|
||||
String layoutView = layout.getLayoutView();
|
||||
assertThat(layoutView,
|
||||
is(equalTo(" 31413 9162 13276 \n"
|
||||
+ " \n"
|
||||
+ "55836 56093 48111 38724 17870 5164 64279 \n"
|
||||
+ " 8134 \n"
|
||||
+ " 58086 39755 \n"
|
||||
+ " \n"
|
||||
+ " 41451 \n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTheInconsistentLayoutView() {
|
||||
@Nullable
|
||||
Layout layout = gson.fromJson(layoutInconsistentPanelNoJson, Layout.class);
|
||||
String layoutView = layout.getLayoutView();
|
||||
assertThat(layoutView,
|
||||
is(equalTo(" 31413 9162 13276 \n"
|
||||
+ " \n"
|
||||
+ "55836 56093 48111 38724 17870 5164 64279 \n"
|
||||
+ " 8134 \n"
|
||||
+ " 58086 39755 \n"
|
||||
+ " \n"
|
||||
+ " 41451 \n")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.nanoleaf.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.nanoleaf.internal.model.TouchEvent;
|
||||
import org.openhab.binding.nanoleaf.internal.model.TouchEvents;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Test for the TouchEvents
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class TouchTest {
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Test
|
||||
public void testTheRightLayoutView() {
|
||||
String json = "{\"events\":[{\"panelId\":48111,\"gesture\":1}]}";
|
||||
@Nullable
|
||||
TouchEvents touchEvents = gson.fromJson(json, TouchEvents.class);
|
||||
assertThat(touchEvents.getEvents().size(), greaterThan(0));
|
||||
assertThat(touchEvents.getEvents().size(), is(1));
|
||||
@Nullable
|
||||
TouchEvent touchEvent = touchEvents.getEvents().get(0);
|
||||
assertThat(touchEvent.getPanelId(), is("48111"));
|
||||
assertThat(touchEvent.getGesture(), is(1));
|
||||
}
|
||||
}
|
||||
@@ -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.nanoleaf.internal.handler;
|
||||
|
||||
import static java.nio.file.Files.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.openhab.binding.nanoleaf.internal.model.ControllerInfo;
|
||||
import org.openhab.binding.nanoleaf.internal.model.State;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Test for the Layout
|
||||
*
|
||||
* @author Stefan Höhn - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class NanoleafControllerHandlerTest {
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
private String controllerInfoJSON = "";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStateOn() {
|
||||
controllerInfoJSON = "{\r\n \"name\":\"Nanoleaf Light Panels 12:34:56\",\r\n \"serialNo\":\"S19082ABCDE\",\r\n \"manufacturer\":\"Nanoleaf\",\r\n \"firmwareVersion\":\"3.3.3\",\r\n \"hardwareVersion\":\"1.6-2\",\r\n \"model\":\"NL22\",\r\n \"cloudHash\":{\r\n\r\n },\r\n \"discovery\":{\r\n\r\n },\r\n \"effects\":{\r\n \"effectsList\":[\r\n \"Color Burst\",\r\n \"Fireworks\",\r\n \"Flames\",\r\n \"Forest\",\r\n \"Inner Peace\",\r\n \"Lightning\",\r\n \"Northern Lights\",\r\n \"Pulse Pop Beats\",\r\n \"Vibrant Sunrise\"\r\n ],\r\n \"select\":\"Flames\"\r\n },\r\n \"firmwareUpgrade\":{\r\n\r\n },\r\n \"panelLayout\":{\r\n \"globalOrientation\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"layout\":{\r\n \"numPanels\":9,\r\n \"sideLength\":150,\r\n \"positionData\":[\r\n {\r\n \"panelId\":1,\r\n \"x\":299,\r\n \"y\":0,\r\n \"o\":300,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":2,\r\n \"x\":299,\r\n \"y\":86,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":3,\r\n \"x\":224,\r\n \"y\":129,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":4,\r\n \"x\":224,\r\n \"y\":216,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":5,\r\n \"x\":149,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":6,\r\n \"x\":74,\r\n \"y\":216,\r\n \"o\":240,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":7,\r\n \"x\":0,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":8,\r\n \"x\":149,\r\n \"y\":346,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":9,\r\n \"x\":374,\r\n \"y\":129,\r\n \"o\":180,\r\n \"shapeType\":0\r\n }\r\n ]\r\n }\r\n },\r\n \"rhythm\":{\r\n \"auxAvailable\":false,\r\n \"firmwareVersion\":\"2.4.3\",\r\n \"hardwareVersion\":\"2.0\",\r\n \"rhythmActive\":false,\r\n \"rhythmConnected\":true,\r\n \"rhythmId\":10,\r\n \"rhythmMode\":0,\r\n \"rhythmPos\":{\r\n \"x\":449.99521692839559,\r\n \"y\":86.60030339609753,\r\n \"o\":0.0\r\n }\r\n },\r\n \"schedules\":{\r\n\r\n },\r\n \"state\":{\r\n \"brightness\":{\r\n \"value\":29,\r\n \"max\":100,\r\n \"min\":0\r\n },\r\n \"colorMode\":\"effect\",\r\n \"ct\":{\r\n \"value\":3000,\r\n \"max\":6500,\r\n \"min\":1200\r\n },\r\n \"hue\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"on\":{\r\n \"value\":true\r\n },\r\n \"sat\":{\r\n \"value\":0,\r\n \"max\":100,\r\n \"min\":0\r\n }\r\n }\r\n}";
|
||||
|
||||
ControllerInfo controllerInfo = gson.fromJson(controllerInfoJSON, ControllerInfo.class);
|
||||
|
||||
final State state = controllerInfo.getState();
|
||||
assertThat(state, is(notNullValue()));
|
||||
|
||||
assertThat(state.getOnOff(), is(OnOffType.ON));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStateOff() {
|
||||
controllerInfoJSON = "{\r\n \"name\":\"Nanoleaf Light Panels 12:34:56\",\r\n \"serialNo\":\"S19082ABCDE\",\r\n \"manufacturer\":\"Nanoleaf\",\r\n \"firmwareVersion\":\"3.3.3\",\r\n \"hardwareVersion\":\"1.6-2\",\r\n \"model\":\"NL22\",\r\n \"cloudHash\":{\r\n\r\n },\r\n \"discovery\":{\r\n\r\n },\r\n \"effects\":{\r\n \"effectsList\":[\r\n \"Color Burst\",\r\n \"Fireworks\",\r\n \"Flames\",\r\n \"Forest\",\r\n \"Inner Peace\",\r\n \"Lightning\",\r\n \"Northern Lights\",\r\n \"Pulse Pop Beats\",\r\n \"Vibrant Sunrise\"\r\n ],\r\n \"select\":\"Flames\"\r\n },\r\n \"firmwareUpgrade\":{\r\n\r\n },\r\n \"panelLayout\":{\r\n \"globalOrientation\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"layout\":{\r\n \"numPanels\":9,\r\n \"sideLength\":150,\r\n \"positionData\":[\r\n {\r\n \"panelId\":1,\r\n \"x\":299,\r\n \"y\":0,\r\n \"o\":300,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":2,\r\n \"x\":299,\r\n \"y\":86,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":3,\r\n \"x\":224,\r\n \"y\":129,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":4,\r\n \"x\":224,\r\n \"y\":216,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":5,\r\n \"x\":149,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":6,\r\n \"x\":74,\r\n \"y\":216,\r\n \"o\":240,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":7,\r\n \"x\":0,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":8,\r\n \"x\":149,\r\n \"y\":346,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":9,\r\n \"x\":374,\r\n \"y\":129,\r\n \"o\":180,\r\n \"shapeType\":0\r\n }\r\n ]\r\n }\r\n },\r\n \"rhythm\":{\r\n \"auxAvailable\":false,\r\n \"firmwareVersion\":\"2.4.3\",\r\n \"hardwareVersion\":\"2.0\",\r\n \"rhythmActive\":false,\r\n \"rhythmConnected\":true,\r\n \"rhythmId\":10,\r\n \"rhythmMode\":0,\r\n \"rhythmPos\":{\r\n \"x\":449.99521692839559,\r\n \"y\":86.60030339609753,\r\n \"o\":0.0\r\n }\r\n },\r\n \"schedules\":{\r\n\r\n },\r\n \"state\":{\r\n \"brightness\":{\r\n \"value\":29,\r\n \"max\":100,\r\n \"min\":0\r\n },\r\n \"colorMode\":\"effect\",\r\n \"ct\":{\r\n \"value\":3000,\r\n \"max\":6500,\r\n \"min\":1200\r\n },\r\n \"hue\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"sat\":{\r\n \"value\":0,\r\n \"max\":100,\r\n \"min\":0\r\n }\r\n }\r\n}";
|
||||
|
||||
ControllerInfo controllerInfo = gson.fromJson(controllerInfoJSON, ControllerInfo.class);
|
||||
|
||||
final State state = controllerInfo.getState();
|
||||
assertThat(state, is(notNullValue()));
|
||||
|
||||
assertThat(state.getOnOff(), is(OnOffType.OFF));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStateOnMissing() {
|
||||
controllerInfoJSON = "{\r\n \"name\":\"Nanoleaf Light Panels 12:34:56\",\r\n \"serialNo\":\"S19082ABCDE\",\r\n \"manufacturer\":\"Nanoleaf\",\r\n \"firmwareVersion\":\"3.3.3\",\r\n \"hardwareVersion\":\"1.6-2\",\r\n \"model\":\"NL22\",\r\n \"cloudHash\":{\r\n\r\n },\r\n \"discovery\":{\r\n\r\n },\r\n \"effects\":{\r\n \"effectsList\":[\r\n \"Color Burst\",\r\n \"Fireworks\",\r\n \"Flames\",\r\n \"Forest\",\r\n \"Inner Peace\",\r\n \"Lightning\",\r\n \"Northern Lights\",\r\n \"Pulse Pop Beats\",\r\n \"Vibrant Sunrise\"\r\n ],\r\n \"select\":\"Flames\"\r\n },\r\n \"firmwareUpgrade\":{\r\n\r\n },\r\n \"panelLayout\":{\r\n \"globalOrientation\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"layout\":{\r\n \"numPanels\":9,\r\n \"sideLength\":150,\r\n \"positionData\":[\r\n {\r\n \"panelId\":1,\r\n \"x\":299,\r\n \"y\":0,\r\n \"o\":300,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":2,\r\n \"x\":299,\r\n \"y\":86,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":3,\r\n \"x\":224,\r\n \"y\":129,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":4,\r\n \"x\":224,\r\n \"y\":216,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":5,\r\n \"x\":149,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":6,\r\n \"x\":74,\r\n \"y\":216,\r\n \"o\":240,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":7,\r\n \"x\":0,\r\n \"y\":259,\r\n \"o\":60,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":8,\r\n \"x\":149,\r\n \"y\":346,\r\n \"o\":120,\r\n \"shapeType\":0\r\n },\r\n {\r\n \"panelId\":9,\r\n \"x\":374,\r\n \"y\":129,\r\n \"o\":180,\r\n \"shapeType\":0\r\n }\r\n ]\r\n }\r\n },\r\n \"rhythm\":{\r\n \"auxAvailable\":false,\r\n \"firmwareVersion\":\"2.4.3\",\r\n \"hardwareVersion\":\"2.0\",\r\n \"rhythmActive\":false,\r\n \"rhythmConnected\":true,\r\n \"rhythmId\":10,\r\n \"rhythmMode\":0,\r\n \"rhythmPos\":{\r\n \"x\":449.99521692839559,\r\n \"y\":86.60030339609753,\r\n \"o\":0.0\r\n }\r\n },\r\n \"schedules\":{\r\n\r\n },\r\n \"state\":{\r\n \"brightness\":{\r\n \"value\":29,\r\n \"max\":100,\r\n \"min\":0\r\n },\r\n \"colorMode\":\"effect\",\r\n \"ct\":{\r\n \"value\":3000,\r\n \"max\":6500,\r\n \"min\":1200\r\n },\r\n \"hue\":{\r\n \"value\":0,\r\n \"max\":360,\r\n \"min\":0\r\n },\r\n \"on\":{\r\n \"value\":false\r\n },\r\n \"sat\":{\r\n \"value\":0,\r\n \"max\":100,\r\n \"min\":0\r\n }\r\n }\r\n}";
|
||||
|
||||
ControllerInfo controllerInfo = gson.fromJson(controllerInfoJSON, ControllerInfo.class);
|
||||
|
||||
final State state = controllerInfo.getState();
|
||||
assertThat(state, is(notNullValue()));
|
||||
|
||||
assertThat(state.getOnOff(), is(OnOffType.OFF));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user