[nanoleaf] Refactored code to use core features and more (#10101)

This is a bigger refactoring bringing these (breaking) changes:
- System channel types are used where applicable
- Obsolete channels (such as power) were removed
- Some channel types were marked "advanced"
- "Tap" channels were converted to a trigger channel type providing a "system button" behavior
- Layout can now be requested by a console command
- Command options for effect channel are dynamically provided
- Log level has been reduced where appropriate
- HTTP request timeouts were reduced
- handleRemoval now returns quickly as expected
- Fixed hanging thread / infinite loop when requesting layouts for non-square panels
- Various other smaller enhancements and fixes
- Documentation has been adapted accordingly

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2021-03-17 09:12:04 +01:00
committed by GitHub
parent c582dda1d5
commit 009208adee
15 changed files with 411 additions and 407 deletions

View File

@@ -27,7 +27,7 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault
public class NanoleafBindingConstants {
private static final String BINDING_ID = "nanoleaf";
public static final String BINDING_ID = "nanoleaf";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CONTROLLER = new ThingTypeUID(BINDING_ID, "controller");
@@ -44,7 +44,6 @@ public class NanoleafBindingConstants {
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";
@@ -53,12 +52,10 @@ public class NanoleafBindingConstants {
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";
public static final String CHANNEL_PANEL_COLOR = "color";
public static final String CHANNEL_PANEL_TAP = "tap";
// Nanoleaf OpenAPI URLs
public static final String API_V1_BASE_URL = "/api/v1";

View File

@@ -15,9 +15,6 @@ 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;
@@ -25,19 +22,15 @@ 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;
@@ -49,6 +42,7 @@ import org.slf4j.LoggerFactory;
* and panel (thing) handlers.
*
* @author Martin Raepple - Initial contribution
* @author Kai Kreuzer - made discovery a handler service
*/
@NonNullByDefault
@Component(configurationPid = "binding.nanoleaf", service = ThingHandlerFactory.class)
@@ -58,7 +52,6 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory {
.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
@@ -77,7 +70,6 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory {
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)) {
@@ -87,30 +79,4 @@ public class NanoleafHandlerFactory extends BaseThingHandlerFactory {
}
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.");
}
}

View File

@@ -20,6 +20,7 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,7 +58,7 @@ public class OpenAPIUtils {
LOGGER.trace("RequestBuilder: Sending Request {}:{} {} ", requestURI.getHost(), requestURI.getPort(),
requestURI.getPath());
return httpClient.newRequest(requestURI).method(method);
return httpClient.newRequest(requestURI).method(method).timeout(10, TimeUnit.SECONDS);
}
public static URI getUri(NanoleafControllerConfig controllerConfig, String apiOperation, @Nullable String query)

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2021 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.command;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants;
import org.openhab.binding.nanoleaf.internal.handler.NanoleafControllerHandler;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Console commands for interacting with Nanoleaf integration
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
@Component(service = ConsoleCommandExtension.class)
public class NanoleafCommandExtension extends AbstractConsoleCommandExtension {
private static final String CMD_LAYOUT = "layout";
private final ThingRegistry thingRegistry;
@Activate
public NanoleafCommandExtension(@Reference ThingRegistry thingRegistry) {
super("nanoleaf", "Interact with the Nanoleaf integration.");
this.thingRegistry = thingRegistry;
}
@Override
public void execute(String[] args, Console console) {
if (args.length > 0) {
String subCommand = args[0];
switch (subCommand) {
case CMD_LAYOUT:
if (args.length == 1) {
thingRegistry.getAll().forEach(thing -> {
if (thing.getUID().getBindingId().equals(NanoleafBindingConstants.BINDING_ID)) {
ThingHandler handler = thing.getHandler();
if (handler instanceof NanoleafControllerHandler) {
NanoleafControllerHandler nanoleafControllerHandler = (NanoleafControllerHandler) handler;
String layout = nanoleafControllerHandler.getLayout();
console.println("Layout of Nanoleaf controller '" + thing.getUID().getAsString()
+ "' with label '" + thing.getLabel() + "':" + System.lineSeparator());
console.println(layout);
console.println(System.lineSeparator());
}
}
});
} else if (args.length == 2) {
String uid = args[1];
Thing thing = thingRegistry.get(new ThingUID(uid));
if (thing != null) {
ThingHandler handler = thing.getHandler();
if (handler instanceof NanoleafControllerHandler) {
NanoleafControllerHandler nanoleafControllerHandler = (NanoleafControllerHandler) handler;
String layout = nanoleafControllerHandler.getLayout();
console.println(layout);
} else {
console.println("Thing with UID '" + uid.toString()
+ "' is not an initialized Nanoleaf controller.");
}
} else {
console.println("Thing with UID '" + uid.toString() + "' does not exist.");
}
} else {
printUsage(console);
}
break;
default:
console.println("Unknown command '" + subCommand + "'");
printUsage(console);
break;
}
}
}
@Override
public List<String> getUsages() {
return Arrays.asList(buildCommandUsage(CMD_LAYOUT + " <thingUID>", "Prints the panel layout on the console."));
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2021 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.commanddescription;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
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.handler.NanoleafControllerHandler;
import org.openhab.binding.nanoleaf.internal.model.ControllerInfo;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.openhab.core.types.CommandOption;
import org.osgi.service.component.annotations.Component;
/**
* This class provides the available effects as dynamic options as they are read from the Nanoleaf controller.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
@Component(service = { DynamicCommandDescriptionProvider.class })
public class NanoleafCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider
implements NanoleafControllerListener, ThingHandlerService {
private @Nullable ChannelUID effectChannelUID;
private @Nullable NanoleafControllerHandler bridgeHandler;
@Override
public void setThingHandler(ThingHandler handler) {
this.bridgeHandler = (NanoleafControllerHandler) handler;
bridgeHandler.registerControllerListener(this);
effectChannelUID = new ChannelUID(handler.getThing().getUID(), NanoleafBindingConstants.CHANNEL_EFFECT);
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void deactivate() {
if (bridgeHandler != null) {
bridgeHandler.unregisterControllerListener(this);
}
super.deactivate();
}
@Override
public void onControllerInfoFetched(@NonNull ThingUID bridge, @NonNull ControllerInfo controllerInfo) {
List<@NonNull String> effects = controllerInfo.getEffects().getEffectsList();
ChannelUID uid = effectChannelUID;
if (effects != null && uid != null && uid.getThingUID().equals(bridge)) {
List<@NonNull CommandOption> commandOptions = effects.stream() //
.map(effect -> new CommandOption(effect, effect)) //
.collect(Collectors.toList());
setCommandOptions(uid, commandOptions);
}
}
}

View File

@@ -79,10 +79,10 @@ public class NanoleafMDNSDiscoveryParticipant implements MDNSDiscoveryParticipan
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,
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",
logger.debug("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);
}

View File

@@ -33,6 +33,8 @@ 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.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,36 +43,37 @@ import org.slf4j.LoggerFactory;
* panels connected to the controller.
*
* @author Martin Raepple - Initial contribution
* @author Kai Kreuzer - Made it a ThingHandlerService
*/
@NonNullByDefault
public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService implements NanoleafControllerListener {
public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService
implements NanoleafControllerListener, ThingHandlerService {
private static final int SEARCH_TIMEOUT_SECONDS = 60;
private final Logger logger = LoggerFactory.getLogger(NanoleafPanelsDiscoveryService.class);
private final NanoleafControllerHandler bridgeHandler;
private @Nullable NanoleafControllerHandler bridgeHandler;
private @Nullable ControllerInfo controllerInfo;
/**
* Constructs a new {@link NanoleafPanelsDiscoveryService} attached to the given bridge handler.
*
* @param nanoleafControllerHandler The bridge handler this discovery service is attached to
* Constructs a new {@link NanoleafPanelsDiscoveryService}.
*/
public NanoleafPanelsDiscoveryService(NanoleafControllerHandler nanoleafControllerHandler) {
public NanoleafPanelsDiscoveryService() {
super(NanoleafHandlerFactory.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIMEOUT_SECONDS, false);
this.bridgeHandler = nanoleafControllerHandler;
}
@Override
public void deactivate() {
if (bridgeHandler != null) {
bridgeHandler.unregisterControllerListener(this);
}
super.deactivate();
}
@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);
createResultsFromControllerInfo();
}
/**
@@ -81,38 +84,60 @@ public class NanoleafPanelsDiscoveryService extends AbstractDiscoveryService imp
*/
@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");
}
this.controllerInfo = controllerInfo;
}
private void createResultsFromControllerInfo() {
ThingUID bridgeUID;
if (bridgeHandler != null) {
bridgeUID = bridgeHandler.getThing().getUID();
} else {
logger.info("No panels found or connected to controller");
return;
}
if (controllerInfo != null) {
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,
bridgeUID, 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(bridgeUID)
.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.debug("No panels found or connected to controller");
}
}
}
@Override
public void setThingHandler(ThingHandler handler) {
this.bridgeHandler = (NanoleafControllerHandler) handler;
this.bridgeHandler.registerControllerListener(this);
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
}

View File

@@ -17,7 +17,11 @@ import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
@@ -36,10 +40,11 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.nanoleaf.internal.NanoleafControllerListener;
import org.openhab.binding.nanoleaf.internal.NanoleafException;
import org.openhab.binding.nanoleaf.internal.NanoleafInterruptedException;
import org.openhab.binding.nanoleaf.internal.NanoleafUnauthorizedException;
import org.openhab.binding.nanoleaf.internal.OpenAPIUtils;
import org.openhab.binding.nanoleaf.internal.commanddescription.NanoleafCommandDescriptionProvider;
import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
import org.openhab.binding.nanoleaf.internal.discovery.NanoleafPanelsDiscoveryService;
import org.openhab.binding.nanoleaf.internal.model.AuthToken;
import org.openhab.binding.nanoleaf.internal.model.BooleanState;
import org.openhab.binding.nanoleaf.internal.model.Brightness;
@@ -67,6 +72,7 @@ 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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
@@ -81,15 +87,13 @@ import com.google.gson.JsonSyntaxException;
*
* @author Martin Raepple - Initial contribution
* @author Stefan Höhn - Canvas Touch Support
* @author Kai Kreuzer - refactoring, bug fixing and code clean up
*/
@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 static final int PAIRING_INTERVAL = 10;
private final Logger logger = LoggerFactory.getLogger(NanoleafControllerHandler.class);
private HttpClient httpClient;
@@ -98,7 +102,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
// 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
@@ -120,7 +123,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
@Override
public void initialize() {
logger.debug("Initializing the controller (bridge)");
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED);
updateStatus(ThingStatus.UNKNOWN);
NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class);
setAddress(config.address);
setPort(config.port);
@@ -157,13 +160,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
"@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) {
@@ -186,11 +185,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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:
@@ -218,25 +215,28 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
@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;
scheduler.execute(() -> {
// 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());
}
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");
stopAllJobs();
super.handleRemoval();
logger.debug("Nanoleaf controller removed");
});
}
@Override
@@ -246,34 +246,37 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
logger.debug("Disposing handler for Nanoleaf controller {}", getThing().getUID());
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return List.of(NanoleafPanelsDiscoveryService.class, NanoleafCommandDescriptionProvider.class);
}
public boolean registerControllerListener(NanoleafControllerListener controllerListener) {
logger.debug("Register new listener for controller {}", getThing().getUID());
boolean result = controllerListeners.add(controllerListener);
if (result) {
startPanelDiscoveryJob();
}
return result;
return controllerListeners.add(controllerListener);
}
public boolean unregisterControllerListener(NanoleafControllerListener controllerListener) {
logger.debug("Unregister listener for controller {}", getThing().getUID());
boolean result = controllerListeners.remove(controllerListener);
if (result) {
stopPanelDiscoveryJob();
}
return result;
return controllerListeners.remove(controllerListener);
}
public NanoleafControllerConfig getControllerConfig() {
NanoleafControllerConfig config = new NanoleafControllerConfig();
config.address = Objects.requireNonNullElse(getAddress(), "");
config.port = getPort();
config.refreshInterval = getRefreshIntervall();
config.refreshInterval = getRefreshInterval();
config.authToken = getAuthToken();
config.deviceType = Objects.requireNonNullElse(getDeviceType(), "");
return config;
}
public String getLayout() {
Layout layout = controllerInfo.getPanelLayout().getLayout();
String layoutView = (layout != null) ? layout.getLayoutView() : "";
return layoutView;
}
public synchronized void startPairingJob() {
if (pairingJob == null || pairingJob.isCancelled()) {
logger.debug("Start pairing job, interval={} sec", PAIRING_INTERVAL);
@@ -293,8 +296,8 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
String localAuthToken = getAuthToken();
if (localAuthToken != null && !localAuthToken.isEmpty()) {
if (updateJob == null || updateJob.isCancelled()) {
logger.debug("Start controller status job, repeat every {} sec", getRefreshIntervall());
updateJob = scheduler.scheduleWithFixedDelay(this::runUpdate, 0, getRefreshIntervall(),
logger.debug("Start controller status job, repeat every {} sec", getRefreshInterval());
updateJob = scheduler.scheduleWithFixedDelay(this::runUpdate, 0, getRefreshInterval(),
TimeUnit.SECONDS);
}
} else {
@@ -311,24 +314,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
}
}
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_TOUCHSUPPORT)) {
@@ -367,11 +352,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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);
}
updateStatus(ThingStatus.ONLINE);
} catch (NanoleafUnauthorizedException nae) {
logger.warn("Status update unauthorized: {}", nae.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
@@ -403,7 +384,8 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
return;
}
ContentResponse authTokenResponse = OpenAPIUtils
.requestBuilder(httpClient, getControllerConfig(), API_ADD_USER, HttpMethod.POST).send();
.requestBuilder(httpClient, getControllerConfig(), API_ADD_USER, HttpMethod.POST)
.timeout(20, TimeUnit.SECONDS).send();
if (logger.isTraceEnabled()) {
logger.trace("Auth token response: {}", authTokenResponse.getContentAsString());
}
@@ -429,7 +411,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
stopPairingJob();
startUpdateJob();
startPanelDiscoveryJob();
startTouchJob();
} else {
logger.debug("No auth token found in response: {}", authTokenResponse.getContentAsString());
@@ -446,7 +427,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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);
logger.debug("Cannot send authorization request to controller: ", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/error.nanoleaf.controller.authRequest");
} catch (RuntimeException e) {
@@ -459,34 +440,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
}
}
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");
String localAuthToken = getAuthToken();
if (localAuthToken == null || localAuthToken.isEmpty()) {
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
*/
@@ -579,14 +532,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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();
@@ -662,6 +610,10 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
panelHandler.updatePanelColorChannel();
}
});
for (NanoleafControllerListener controllerListener : controllerListeners) {
controllerListener.onControllerInfoFetched(getThing().getUID(), controllerInfo);
}
}
private ControllerInfo receiveControllerInfo() throws NanoleafException, NanoleafUnauthorizedException {
@@ -674,17 +626,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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
@@ -780,13 +721,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
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;
@@ -844,7 +778,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
this.port = port;
}
private int getRefreshIntervall() {
private int getRefreshInterval() {
return refreshIntervall;
}
@@ -871,7 +805,6 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
private void stopAllJobs() {
stopPairingJob();
stopUpdateJob();
stopPanelDiscoveryJob();
stopTouchJob();
}
}

View File

@@ -15,11 +15,11 @@ package org.openhab.binding.nanoleaf.internal.handler;
import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
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;
@@ -28,13 +28,21 @@ 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.NanoleafBadRequestException;
import org.openhab.binding.nanoleaf.internal.NanoleafException;
import org.openhab.binding.nanoleaf.internal.NanoleafNotFoundException;
import org.openhab.binding.nanoleaf.internal.NanoleafUnauthorizedException;
import org.openhab.binding.nanoleaf.internal.OpenAPIUtils;
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.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.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
@@ -165,8 +173,9 @@ public class NanoleafPanelHandler extends BaseThingHandler {
private void sendRenderedEffectCommand(Command command) throws NanoleafException {
logger.debug("Command Type: {}", command.getClass());
HSBType currentPanelColor = getPanelColor();
if (currentPanelColor != null)
if (currentPanelColor != null) {
logger.debug("currentPanelColor: {}", currentPanelColor.toString());
}
HSBType newPanelColor = new HSBType();
if (command instanceof HSBType) {
@@ -205,11 +214,11 @@ public class NanoleafPanelHandler extends BaseThingHandler {
// 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)
int red = rgbPercent[0].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal(255)).intValue();
int green = rgbPercent[1].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
int green = rgbPercent[1].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal(255)).intValue();
int blue = rgbPercent[2].toBigDecimal().divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP)
int blue = rgbPercent[2].toBigDecimal().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
.multiply(new BigDecimal(255)).intValue();
logger.trace("Setting new rgb {} {} {}", red, green, blue);
Bridge bridge = getBridge();
@@ -253,8 +262,9 @@ public class NanoleafPanelHandler extends BaseThingHandler {
@Nullable
HSBType panelColor = getPanelColor();
logger.trace("updatePanelColorChannel: panelColor: {}", panelColor);
if (panelColor != null)
if (panelColor != null) {
updateState(CHANNEL_PANEL_COLOR, panelColor);
}
}
/**
@@ -265,28 +275,14 @@ public class NanoleafPanelHandler extends BaseThingHandler {
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());
triggerChannel(CHANNEL_PANEL_TAP, CommonTriggerEvents.SHORT_PRESSED);
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());
triggerChannel(CHANNEL_PANEL_TAP, CommonTriggerEvents.DOUBLE_PRESSED);
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;
@@ -327,7 +323,7 @@ public class NanoleafPanelHandler extends BaseThingHandler {
} catch (NanoleafException nue) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/error.nanoleaf.panel.communication");
logger.warn("Panel data could not be retrieved: {}", nue.getMessage());
logger.debug("Panel data could not be retrieved: {}", nue.getMessage());
}
return panelInfo.get(panelID);

View File

@@ -60,7 +60,7 @@ public class Layout {
* 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() {
@@ -97,6 +97,11 @@ public class Layout {
int shiftWidth = getSideLength() / 2;
if (shiftWidth == 0) {
// seems we do not have squares here
return "Cannot render layout. Please note that layout views are only supported for square panels.";
}
int lineY = maxy;
Map<Integer, PositionDatum> map;
@@ -108,8 +113,9 @@ public class Layout {
@Nullable
PositionDatum panel = positionData.get(index);
if (panel != null && panel.getPosY() == lineY)
if (panel != null && panel.getPosY() == lineY) {
map.put(panel.getPosX(), panel);
}
}
}
lineY -= shiftWidth;
@@ -118,14 +124,16 @@ public class Layout {
@Nullable
PositionDatum panel = map.get(x);
view += String.format("%5s ", panel.getPanelId());
} else
} else {
view += " ";
}
}
view += "\n";
view += System.lineSeparator();
}
return view;
} else
} else {
return "";
}
}
}

View File

@@ -25,7 +25,7 @@
<description>@text/thing-type.config.nanoleaf.controller.refreshInterval.description</description>
<default>60</default>
</parameter>
<parameter name="deviceType" type="text" readOnly="true">
<parameter name="deviceType" type="text">
<label>@text/thing-type.config.nanoleaf.controller.deviceType.label</label>
<description>@text/thing-type.config.nanoleaf.controller.deviceType.description</description>
<default>lightPanels</default>

View File

@@ -1,5 +1,5 @@
binding.nanoleaf.name = Nanoleaf Binding
binding.nanoleaf.description = Integrates the Nanoleaf Light Panels (v010221)
binding.nanoleaf.description = Integrates the Nanoleaf light panels
# thing types
thing-type.nanoleaf.controller.name = Nanoleaf Controller
@@ -22,14 +22,6 @@ 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
@@ -40,21 +32,17 @@ 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
channel-type.nanoleaf.tap.label = Button
channel-type.nanoleaf.tap.description = Button events of 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.communication = Communication failed. Please check your network and 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.

View File

@@ -22,14 +22,6 @@ 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
@@ -40,21 +32,17 @@ 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
channel-type.nanoleaf.tap.label = Taster
channel-type.nanoleaf.tap.description = Tastevents des Panels
# 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.communication = Kommunikationsfehler. Netzwerk und 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.

View File

@@ -9,16 +9,14 @@
<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="color" typeId="system.color"/>
<channel id="colorTemperature" typeId="system.color-temperature"/>
<channel id="colorTemperatureAbs" typeId="system.color-temperature-abs"/>
<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>
@@ -40,9 +38,8 @@
<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"/>
<channel id="color" typeId="system.color"/>
<channel id="tap" typeId="system.button"/>
</channels>
<representation-property>id</representation-property>
@@ -50,35 +47,13 @@
<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 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="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">
<channel-type id="colorMode" advanced="true">
<item-type>String</item-type>
<label>@text/channel-type.nanoleaf.colorMode.label</label>
<description>@text/channel-type.nanoleaf.colorMode.description</description>
@@ -91,62 +66,30 @@
</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">
<channel-type id="rhythmState" advanced="true">
<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">
<channel-type id="rhythmActive" advanced="true">
<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">
<channel-type id="rhythmMode" advanced="true">
<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>
<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>