Skip to content

Commit

Permalink
Refactored code to use core features
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
kaikreuzer committed Feb 7, 2021
1 parent 9f0ec10 commit b285116
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 396 deletions.
93 changes: 36 additions & 57 deletions bundles/org.openhab.binding.nanoleaf/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@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");
Expand All @@ -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";
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,22 @@
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;
Expand All @@ -49,6 +42,7 @@
* 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)
Expand All @@ -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
Expand All @@ -77,7 +70,6 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {

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)) {
Expand All @@ -87,30 +79,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
}
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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -57,7 +58,7 @@ public static Request requestBuilder(HttpClient httpClient, NanoleafControllerCo
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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() + "':\n");
console.println(layout);
console.println("\n");
}
}
});
} 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."));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ public String getServiceType() {

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);
}

Expand Down
Loading

0 comments on commit b285116

Please sign in to comment.