]> git.basschouten.com Git - openhab-addons.git/commitdiff
[nanoleaf] More color for less network calls (#13893)
authorJørgen Austvik <jaustvik@acm.org>
Sun, 11 Dec 2022 15:09:53 +0000 (16:09 +0100)
committerGitHub <noreply@github.com>
Sun, 11 Dec 2022 15:09:53 +0000 (16:09 +0100)
* [nanoleaf] More color for less network calls

This is a refactoring that moves the "get panel color" out of the
panel handler and into a separate class, with callbacks.

This makes us do only one REST call to get colors instead of one per
panel that is a thing. It also lets us retrieve colors for all panels -
 also those that doesn't have a thing in OpenHAB,

While testing this out, I found a bug where solid colors set in the app
wasn't reflected in neither the controller nor panel channels, and that
should also be fixed now.

Signed-off-by: Jørgen Austvik <jaustvik@acm.org>
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/NanoleafBindingConstants.java
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafControllerColorChangeListener.java [new file with mode: 0644]
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColorChangeListener.java [new file with mode: 0644]
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColors.java [new file with mode: 0644]
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafControllerHandler.java
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/handler/NanoleafPanelHandler.java
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/ConstantPanelState.java [new file with mode: 0644]
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/LivePanelState.java [new file with mode: 0644]
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/PanelState.java
bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/shape/PanelFactory.java
bundles/org.openhab.binding.nanoleaf/src/test/java/org/openhab/binding/nanoleaf/internal/layout/NanoleafLayoutTest.java

index 5109d8abce3201cd9babb6cafc233a6a12d2b3af..96a1e6ceea67ba7cd5f45686c449f5509d412d83 100644 (file)
@@ -91,7 +91,8 @@ public class NanoleafBindingConstants {
     public static final String SERVICE_TYPE = "_nanoleafapi._tcp.local.";
 
     // Effect/scene name for static color
-    public static final String EFFECT_NAME_STATIC_COLOR = "*Dynamic*";
+    public static final String EFFECT_NAME_STATIC_COLOR = "*Static*";
+    public static final String EFFECT_NAME_SOLID_COLOR = "*Solid*";
 
     // Color channels increase/decrease brightness step size
     public static final int BRIGHTNESS_STEP_SIZE = 5;
diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafControllerColorChangeListener.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafControllerColorChangeListener.java
new file mode 100644 (file)
index 0000000..346e111
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 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.colors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A listener used to notify panels when they change color.
+ *
+ * @author Jørgen Austvik - Initial contribution
+ */
+
+@NonNullByDefault
+public interface NanoleafControllerColorChangeListener {
+
+    /**
+     * This method is called after any panel changes its color.
+     */
+    void onPanelChangedColor();
+}
diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColorChangeListener.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColorChangeListener.java
new file mode 100644 (file)
index 0000000..e01d0c3
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2022 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.colors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.HSBType;
+
+/**
+ * A listener used to notify panels when they change color.
+ *
+ * @author Jørgen Austvik - Initial contribution
+ */
+
+@NonNullByDefault
+public interface NanoleafPanelColorChangeListener {
+
+    /**
+     * This method is called after a panel changes its color
+     *
+     * @param newColor the new color of the panel
+     */
+    void onPanelChangedColor(HSBType newColor);
+}
diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColors.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/colors/NanoleafPanelColors.java
new file mode 100644 (file)
index 0000000..bb6d2cc
--- /dev/null
@@ -0,0 +1,155 @@
+/**
+ * Copyright (c) 2010-2022 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.colors;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.nanoleaf.internal.handler.NanoleafPanelHandler;
+import org.openhab.core.library.types.HSBType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stores information about panels and their colors, while sending notifications to panels and controllers
+ * about updated states.
+ *
+ * @author Jørgen Austvik - Initial contribution
+ */
+@NonNullByDefault
+public class NanoleafPanelColors {
+
+    private final Logger logger = LoggerFactory.getLogger(NanoleafPanelColors.class);
+
+    // holds current color data per panel
+    private final Map<Integer, HSBType> panelColors = new ConcurrentHashMap<>();
+    private final Map<Integer, NanoleafPanelColorChangeListener> panelChangeListeners = new ConcurrentHashMap<>();
+    private @Nullable NanoleafControllerColorChangeListener controllerListener;
+
+    private boolean updatePanelColorNoController(Integer panelId, HSBType color) {
+        boolean updatePanel = false;
+        if (panelColors.containsKey(panelId)) {
+            HSBType existingColor = panelColors.get(panelId);
+            if (existingColor != null && !existingColor.equals(color)) {
+                // Color change - update the panel thing
+                updatePanel = true;
+            }
+        } else {
+            // First time we see this panels color - update the panel thing
+            updatePanel = true;
+        }
+
+        panelColors.put(panelId, color);
+
+        if (updatePanel) {
+            @Nullable
+            NanoleafPanelColorChangeListener panelHandler = panelChangeListeners.get(panelId);
+            if (panelHandler != null) {
+                panelHandler.onPanelChangedColor(color);
+            }
+        }
+
+        return updatePanel;
+    }
+
+    private void updatePanelColor(Integer panelId, HSBType color) {
+        boolean updatePanel = updatePanelColorNoController(panelId, color);
+        if (updatePanel) {
+            notifyControllerListener();
+        }
+    }
+
+    private void notifyControllerListener() {
+        NanoleafControllerColorChangeListener privateControllerListener = controllerListener;
+        if (privateControllerListener != null) {
+            privateControllerListener.onPanelChangedColor();
+        }
+    }
+
+    /**
+     * Retrieves the color of the panel. Used by the panels to read their state.
+     *
+     * @param panelId The id of the panel
+     * @return The color of the panel
+     */
+    public @Nullable HSBType getPanelColor(Integer panelId) {
+        return panelColors.get(panelId);
+    }
+
+    /**
+     * Called from panels to update the state.
+     *
+     * @param panelId The panel that received the update
+     * @param color The new color of the panel
+     */
+    public void setPanelColor(Integer panelId, HSBType color) {
+        updatePanelColor(panelId, color);
+    }
+
+    public void registerChangeListener(Integer panelId, NanoleafPanelHandler panelListener) {
+        logger.trace("Adding color change listener for panel {}", panelId);
+        panelChangeListeners.put(panelId, panelListener);
+    }
+
+    public void unregisterChangeListener(Integer panelId) {
+        logger.trace("Removing color change listener for panel {}", panelId);
+        panelChangeListeners.remove(panelId);
+    }
+
+    public void registerChangeListener(NanoleafControllerColorChangeListener controllerListener) {
+        logger.trace("Setting color change listener for controller");
+        this.controllerListener = controllerListener;
+    }
+
+    /**
+     * Returns the color of a panel.
+     * 
+     * @param panelId The panel
+     * @param defaultColor Default color if panel is missing color information
+     * @return Color of the panel
+     */
+    public HSBType getColor(Integer panelId, HSBType defaultColor) {
+        return panelColors.getOrDefault(panelId, defaultColor);
+    }
+
+    /**
+     * Returns true if we have color information for the given panel.
+     *
+     * @param panelId The panel to check if has color
+     * @return true if we have color information about the panel
+     */
+    public boolean hasColor(Integer panelId) {
+        return panelColors.containsKey(panelId);
+    }
+
+    /**
+     * Sets all panels to the same color. This will make controller repaint only once.
+     * 
+     * @param panelIds Panels to update
+     * @param color The color for all panels
+     */
+    public void setMultiple(List<Integer> panelIds, HSBType color) {
+        logger.debug("Setting all panels to color {}", color);
+        boolean updatePanel = false;
+        for (Integer panelId : panelIds) {
+            updatePanel |= updatePanelColorNoController(panelId, color);
+        }
+
+        if (updatePanel) {
+            notifyControllerListener();
+        }
+    }
+}
index cd34d655d755d3308ff5bf6f68f9486787557380..21fbfb325ff117b19fbc5dcc87b981e09e7e44a0 100644 (file)
@@ -17,6 +17,8 @@ import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.*;
 import java.io.IOException;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -36,15 +38,21 @@ import org.eclipse.jetty.client.api.Request;
 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.NanoleafBadRequestException;
 import org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants;
 import org.openhab.binding.nanoleaf.internal.NanoleafControllerListener;
 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.colors.NanoleafControllerColorChangeListener;
+import org.openhab.binding.nanoleaf.internal.colors.NanoleafPanelColors;
 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.layout.ConstantPanelState;
 import org.openhab.binding.nanoleaf.internal.layout.LayoutSettings;
+import org.openhab.binding.nanoleaf.internal.layout.LivePanelState;
 import org.openhab.binding.nanoleaf.internal.layout.NanoleafLayout;
 import org.openhab.binding.nanoleaf.internal.layout.PanelState;
 import org.openhab.binding.nanoleaf.internal.model.AuthToken;
@@ -58,10 +66,12 @@ import org.openhab.binding.nanoleaf.internal.model.IntegerState;
 import org.openhab.binding.nanoleaf.internal.model.Layout;
 import org.openhab.binding.nanoleaf.internal.model.On;
 import org.openhab.binding.nanoleaf.internal.model.PanelLayout;
+import org.openhab.binding.nanoleaf.internal.model.PositionDatum;
 import org.openhab.binding.nanoleaf.internal.model.Rhythm;
 import org.openhab.binding.nanoleaf.internal.model.Sat;
 import org.openhab.binding.nanoleaf.internal.model.State;
 import org.openhab.binding.nanoleaf.internal.model.TouchEvents;
+import org.openhab.binding.nanoleaf.internal.model.Write;
 import org.openhab.core.config.core.Configuration;
 import org.openhab.core.io.net.http.HttpClientFactory;
 import org.openhab.core.library.types.DecimalType;
@@ -96,7 +106,7 @@ import com.google.gson.JsonSyntaxException;
  * @author Kai Kreuzer - refactoring, bug fixing and code clean up
  */
 @NonNullByDefault
-public class NanoleafControllerHandler extends BaseBridgeHandler {
+public class NanoleafControllerHandler extends BaseBridgeHandler implements NanoleafControllerColorChangeListener {
 
     // Pairing interval in seconds
     private static final int PAIRING_INTERVAL = 10;
@@ -110,6 +120,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
     private @Nullable Request sseTouchjobRequest;
     private final List<NanoleafControllerListener> controllerListeners = new CopyOnWriteArrayList<NanoleafControllerListener>();
     private PanelLayout previousPanelLayout = new PanelLayout();
+    private final NanoleafPanelColors panelColors = new NanoleafPanelColors();
 
     private @NonNullByDefault({}) ScheduledFuture<?> pairingJob;
     private @NonNullByDefault({}) ScheduledFuture<?> updateJob;
@@ -154,6 +165,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
     @Override
     public void initialize() {
         logger.debug("Initializing the controller (bridge)");
+        this.panelColors.registerChangeListener(this);
         updateStatus(ThingStatus.UNKNOWN);
         NanoleafControllerConfig config = getConfigAs(NanoleafControllerConfig.class);
         setAddress(config.address);
@@ -582,7 +594,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
                     if (panelHandler != null) {
                         logger.trace("Checking available panel -{}- versus event panel -{}-", panelHandler.getPanelID(),
                                 event.getPanelId());
-                        if (panelHandler.getPanelID().equals(event.getPanelId())) {
+                        if (panelHandler.getPanelID().equals(Integer.valueOf(event.getPanelId()))) {
                             logger.debug("Panel {} found. Triggering item with gesture {}.", panelHandler.getPanelID(),
                                     event.getGesture());
                             panelHandler.updatePanelGesture(event.getGesture());
@@ -648,34 +660,52 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
 
         Brightness stateBrightness = state.getBrightness();
         int brightness = stateBrightness != null ? stateBrightness.getValue() : 0;
+        HSBType stateColor = new HSBType(new DecimalType(hue), new PercentType(saturation),
+                new PercentType(powerState == OnOffType.ON ? brightness : 0));
 
-        updateState(CHANNEL_COLOR, new HSBType(new DecimalType(hue), new PercentType(saturation),
-                new PercentType(powerState == OnOffType.ON ? brightness : 0)));
+        updateState(CHANNEL_COLOR, stateColor);
         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 the color channels of each panel
-        getThing().getThings().forEach(child -> {
-            NanoleafPanelHandler panelHandler = (NanoleafPanelHandler) child.getHandler();
-            if (panelHandler != null) {
-                logger.debug("Update color channel for panel {}", panelHandler.getThing().getUID());
-                panelHandler.updatePanelColorChannel();
-            }
-        });
-
+        updatePanelColors();
+        if (EFFECT_NAME_SOLID_COLOR.equals(controllerInfo.getEffects().getSelect())) {
+            setSolidColor(stateColor);
+        }
         updateProperties();
         updateConfiguration();
         updateLayout(controllerInfo.getPanelLayout());
-        updateVisualState(controllerInfo.getPanelLayout());
+        updateVisualState(controllerInfo.getPanelLayout(), powerState);
 
         for (NanoleafControllerListener controllerListener : controllerListeners) {
             controllerListener.onControllerInfoFetched(getThing().getUID(), controllerInfo);
         }
     }
 
+    private void setSolidColor(HSBType color) {
+        // If the panels are set to solid color, they are read from the state
+        PanelLayout panelLayout = controllerInfo.getPanelLayout();
+        Layout layout = panelLayout.getLayout();
+
+        if (layout != null) {
+            List<PositionDatum> positionData = layout.getPositionData();
+            if (positionData != null) {
+                List<Integer> allPanelIds = new ArrayList<>(positionData.size());
+                for (PositionDatum pd : positionData) {
+                    allPanelIds.add(pd.getPanelId());
+                }
+
+                panelColors.setMultiple(allPanelIds, color);
+            } else {
+                logger.debug("Missing position datum when setting solid color for {}", getThing().getUID());
+            }
+        } else {
+            logger.debug("Missing layout when setting solid color for {}", getThing().getUID());
+        }
+    }
+
     private void updateConfiguration() {
         // only update the Thing config if value isn't set yet
         if (getConfig().get(NanoleafControllerConfig.DEVICE_TYPE) == null) {
@@ -711,20 +741,20 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
         }
     }
 
-    private void updateVisualState(PanelLayout panelLayout) {
+    private void updateVisualState(PanelLayout panelLayout, OnOffType powerState) {
         ChannelUID stateChannel = new ChannelUID(getThing().getUID(), CHANNEL_VISUAL_STATE);
 
-        Bridge bridge = getThing();
-        List<Thing> things = bridge.getThings();
-        if (things == null) {
-            logger.trace("No things to get state from!");
-            return;
-        }
-
         try {
+            PanelState panelState;
+            if (OnOffType.OFF.equals(powerState)) {
+                // If powered off: show all panels as black
+                panelState = new ConstantPanelState(HSBType.BLACK);
+            } else {
+                // Static color for panels, use it
+                panelState = new LivePanelState(panelColors);
+            }
+
             LayoutSettings settings = new LayoutSettings(false, true, true, true);
-            logger.trace("Getting panel state for {} things", things.size());
-            PanelState panelState = new PanelState(things);
             byte[] bytes = NanoleafLayout.render(panelLayout, panelState, settings);
             if (bytes.length > 0) {
                 updateState(stateChannel, new RawType(bytes, "image/png"));
@@ -756,11 +786,9 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
             return;
         }
 
-        Bridge bridge = getThing();
-        List<Thing> things = bridge.getThings();
         try {
             LayoutSettings settings = new LayoutSettings(true, false, true, false);
-            byte[] bytes = NanoleafLayout.render(panelLayout, new PanelState(things), settings);
+            byte[] bytes = NanoleafLayout.render(panelLayout, new LivePanelState(panelColors), settings);
             if (bytes.length > 0) {
                 updateState(layoutChannel, new RawType(bytes, "image/png"));
                 logger.trace("Rendered layout of panel {} in updateState has {} bytes", getThing().getUID(),
@@ -799,6 +827,7 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
                     h.setValue(((HSBType) command).getHue().intValue());
                     s.setValue(((HSBType) command).getSaturation().intValue());
                     b.setValue(((HSBType) command).getBrightness().intValue());
+                    setSolidColor((HSBType) command);
                     stateObject.setState(h);
                     stateObject.setState(s);
                     stateObject.setState(b);
@@ -919,6 +948,126 @@ public class NanoleafControllerHandler extends BaseBridgeHandler {
         }
     }
 
+    private boolean hasStaticEffect() {
+        return EFFECT_NAME_STATIC_COLOR.equals(controllerInfo.getEffects().getSelect())
+                || EFFECT_NAME_SOLID_COLOR.equals(controllerInfo.getEffects().getSelect());
+    }
+
+    /**
+     * Checks if we are in a mode where color changes should be rendered.
+     *
+     * @return True if a color change on a panel should be rendered
+     */
+    private boolean showsUpdatedColors() {
+        if (!hasStaticEffect()) {
+            return false;
+        }
+
+        State state = controllerInfo.getState();
+        OnOffType powerState = state.getOnOff();
+        return OnOffType.ON.equals(powerState);
+    }
+
+    @Override
+    public void onPanelChangedColor() {
+        if (showsUpdatedColors()) {
+            // Update the visual state if a panel has changed color
+            updateVisualState(controllerInfo.getPanelLayout(), controllerInfo.getState().getOnOff());
+        }
+    }
+
+    /**
+     * For individual panels to get access to the panel colors.
+     *
+     * @return Information about colors of panels.
+     */
+    public NanoleafPanelColors getColorInformation() {
+        return panelColors;
+    }
+
+    private void updatePanelColors() {
+        // get panel color data from controller
+        try {
+            Effects effects = new Effects();
+            Write write = new Write();
+            write.setCommand("request");
+            write.setAnimName(EFFECT_NAME_STATIC_COLOR);
+            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(config, panelData);
+                }
+            }
+        } catch (NanoleafNotFoundException nfe) {
+            logger.debug("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.debug("Panel data could not be retrieved: {}", nue.getMessage());
+        }
+    }
+
+    void parsePanelData(NanoleafControllerConfig config, ContentResponse panelData) {
+        // panelData is in format (numPanels, (PanelId, 1, R, G, B, W, TransitionTime) * numPanel)
+        @Nullable
+        Write response = null;
+
+        String panelDataContent = panelData.getContentAsString();
+        try {
+            response = gson.fromJson(panelDataContent, Write.class);
+        } catch (JsonSyntaxException jse) {
+            logger.warn("Unable to parse panel data information from Nanoleaf", jse);
+            logger.trace("Panel Data which couldn't be parsed: {}", panelDataContent);
+        }
+
+        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) {
+                        // found panel data - store it
+                        panelColors.setPanelColor(Integer.valueOf(panelDataPoints[i]),
+                                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) {
+                        Integer idQuotient = Integer.valueOf(panelDataPoints[i]);
+                        Integer idRemainder = Integer.valueOf(panelDataPoints[i + 1]);
+                        Integer idNum = idQuotient * 256 + idRemainder;
+                        // found panel data - store it
+                        panelColors.setPanelColor(idNum, HSBType.fromRGB(Integer.parseInt(panelDataPoints[i + 3]),
+                                Integer.parseInt(panelDataPoints[i + 4]), Integer.parseInt(panelDataPoints[i + 5])));
+                    }
+                }
+            }
+        }
+    }
+
     private @Nullable String getAddress() {
         return address;
     }
index 60b66196c6c9e369dfc89b149e17c98c1350b314..e80545dd901e18694de5f09bc790e3be8fb6b01a 100644 (file)
@@ -16,23 +16,18 @@ 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 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.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.colors.NanoleafPanelColorChangeListener;
 import org.openhab.binding.nanoleaf.internal.config.NanoleafControllerConfig;
 import org.openhab.binding.nanoleaf.internal.model.Effects;
 import org.openhab.binding.nanoleaf.internal.model.Write;
@@ -50,6 +45,7 @@ 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.thing.binding.ThingHandler;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
 import org.slf4j.Logger;
@@ -65,7 +61,7 @@ import com.google.gson.Gson;
  * @author Stefan Höhn - Canvas Touch Support
  */
 @NonNullByDefault
-public class NanoleafPanelHandler extends BaseThingHandler {
+public class NanoleafPanelHandler extends BaseThingHandler implements NanoleafPanelColorChangeListener {
 
     private static final PercentType MIN_PANEL_BRIGHTNESS = PercentType.ZERO;
     private static final PercentType MAX_PANEL_BRIGHTNESS = PercentType.HUNDRED;
@@ -75,9 +71,7 @@ public class NanoleafPanelHandler extends BaseThingHandler {
     private final HttpClient httpClient;
     // JSON parser for API responses
     private final Gson gson = new Gson();
-
-    // holds current color data per panel
-    private final Map<String, HSBType> panelInfo = new HashMap<>();
+    private HSBType currentPanelColor = HSBType.BLACK;
 
     private @NonNullByDefault({}) ScheduledFuture<?> singleTapJob;
     private @NonNullByDefault({}) ScheduledFuture<?> doubleTapJob;
@@ -141,6 +135,14 @@ public class NanoleafPanelHandler extends BaseThingHandler {
     @Override
     public void handleRemoval() {
         logger.debug("Nanoleaf panel {} removed", getThing().getUID());
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            ThingHandler handler = bridge.getHandler();
+            if (handler instanceof NanoleafControllerHandler) {
+                ((NanoleafControllerHandler) handler).getColorInformation().unregisterChangeListener(getPanelID());
+            }
+        }
+
         super.handleRemoval();
     }
 
@@ -166,21 +168,27 @@ public class NanoleafPanelHandler extends BaseThingHandler {
 
     private void initializePanel(ThingStatusInfo panelStatus) {
         updateStatus(panelStatus.getStatus(), panelStatus.getStatusDetail());
+        updateState(CHANNEL_PANEL_COLOR, currentPanelColor);
         logger.debug("Panel {} status changed to {}-{}", this.getThing().getUID(), panelStatus.getStatus(),
                 panelStatus.getStatusDetail());
+
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            ThingHandler handler = bridge.getHandler();
+            if (handler instanceof NanoleafControllerHandler) {
+                ((NanoleafControllerHandler) handler).getColorInformation().registerChangeListener(getPanelID(), this);
+            }
+        }
     }
 
     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();
+        logger.debug("currentPanelColor: {}", currentPanelColor);
 
+        HSBType newPanelColor = new HSBType();
         if (command instanceof HSBType) {
             newPanelColor = (HSBType) command;
-        } else if (command instanceof OnOffType && (currentPanelColor != null)) {
+        } else if (command instanceof OnOffType) {
             if (OnOffType.ON.equals(command)) {
                 newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(),
                         MAX_PANEL_BRIGHTNESS);
@@ -188,11 +196,11 @@ public class NanoleafPanelHandler extends BaseThingHandler {
                 newPanelColor = new HSBType(currentPanelColor.getHue(), currentPanelColor.getSaturation(),
                         MIN_PANEL_BRIGHTNESS);
             }
-        } else if (command instanceof PercentType && (currentPanelColor != null)) {
+        } else if (command instanceof PercentType) {
             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)) {
+        } else if (command instanceof IncreaseDecreaseType) {
             int brightness = currentPanelColor.getBrightness().intValue();
             if (command.equals(IncreaseDecreaseType.INCREASE)) {
                 brightness = Math.min(MAX_PANEL_BRIGHTNESS.intValue(), brightness + BRIGHTNESS_STEP_SIZE);
@@ -209,8 +217,8 @@ public class NanoleafPanelHandler extends BaseThingHandler {
             return;
         }
         // store panel's new HSB value
-        logger.trace("Setting new color {}", newPanelColor);
-        panelInfo.put(getThing().getConfiguration().get(CONFIG_PANEL_ID).toString(), newPanelColor);
+        logger.trace("Setting new color {} to panel {}", newPanelColor, getPanelID());
+        setPanelColor(newPanelColor);
         // transform to RGB
         PercentType[] rgbPercent = newPanelColor.toRGB();
         logger.trace("Setting new rgbpercent {} {} {}", rgbPercent[0], rgbPercent[1], rgbPercent[2]);
@@ -258,15 +266,6 @@ public class NanoleafPanelHandler extends BaseThingHandler {
         }
     }
 
-    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
      *
@@ -286,98 +285,32 @@ public class NanoleafPanelHandler extends BaseThingHandler {
         }
     }
 
-    public String getPanelID() {
-        String panelID = getThing().getConfiguration().get(CONFIG_PANEL_ID).toString();
-        return panelID;
-    }
-
-    public @Nullable HSBType getColor() {
-        String panelID = getPanelID();
-        return panelInfo.get(panelID);
+    public Integer getPanelID() {
+        return (Integer) getThing().getConfiguration().get(CONFIG_PANEL_ID);
     }
 
-    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);
-                }
+    private void setPanelColor(HSBType color) {
+        Integer panelId = getPanelID();
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            ThingHandler handler = bridge.getHandler();
+            if (handler instanceof NanoleafControllerHandler) {
+                ((NanoleafControllerHandler) handler).getColorInformation().setPanelColor(panelId, color);
+            } else {
+                logger.debug("Couldn't find handler for panel {}", panelId);
             }
-        } catch (NanoleafNotFoundException nfe) {
-            logger.debug("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.debug("Panel data could not be retrieved: {}", nue.getMessage());
+        } else {
+            logger.debug("Couldn't find bridge for panel {}", panelId);
         }
-
-        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) {
-                        Integer idQuotient = Integer.valueOf(panelDataPoints[i]);
-                        Integer idRemainder = Integer.valueOf(panelDataPoints[i + 1]);
-                        Integer idNum = idQuotient * 256 + 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])));
-                        }
-                    }
-                }
-            }
+    @Override
+    public void onPanelChangedColor(HSBType newColor) {
+        if (logger.isTraceEnabled()) {
+            logger.trace("updatePanelColorChannel: panelColor: {} for panel {}", newColor, getPanelID());
         }
+
+        currentPanelColor = newColor;
+        updateState(CHANNEL_PANEL_COLOR, newColor);
     }
 }
diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/ConstantPanelState.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/ConstantPanelState.java
new file mode 100644 (file)
index 0000000..7f6c0f4
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 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.layout;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.HSBType;
+
+/**
+ * Always returns the same color for all panels.
+ *
+ * @author Jørgen Austvik - Initial contribution
+ */
+@NonNullByDefault
+public class ConstantPanelState implements PanelState {
+    private final HSBType color;
+
+    public ConstantPanelState(HSBType color) {
+        this.color = color;
+    }
+
+    @Override
+    public HSBType getHSBForPanel(Integer panelId) {
+        return color;
+    }
+}
diff --git a/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/LivePanelState.java b/bundles/org.openhab.binding.nanoleaf/src/main/java/org/openhab/binding/nanoleaf/internal/layout/LivePanelState.java
new file mode 100644 (file)
index 0000000..05b2f31
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2022 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.layout;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.nanoleaf.internal.colors.NanoleafPanelColors;
+import org.openhab.core.library.types.HSBType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stores the state of the panels.
+ *
+ * @author Jørgen Austvik - Initial contribution
+ */
+@NonNullByDefault
+public class LivePanelState implements PanelState {
+
+    private static final Logger logger = LoggerFactory.getLogger(LivePanelState.class);
+    private final NanoleafPanelColors panelColors;
+
+    public LivePanelState(NanoleafPanelColors panelColors) {
+        this.panelColors = panelColors;
+    }
+
+    @Override
+    public HSBType getHSBForPanel(Integer panelId) {
+        if (logger.isTraceEnabled()) {
+            if (!panelColors.hasColor(panelId)) {
+                logger.trace("Failed to get color for panel {}, falling back to black", panelId);
+            }
+        }
+
+        return panelColors.getColor(panelId, HSBType.BLACK);
+    }
+}
index 02c6a7a947d616ea744b02187a2e519b25b75606..1fb251ff6bf3cd63d8e655945885fb38ffc5bbb4 100644 (file)
  *
  * SPDX-License-Identifier: EPL-2.0
  */
-
 package org.openhab.binding.nanoleaf.internal.layout;
 
-import static org.openhab.binding.nanoleaf.internal.NanoleafBindingConstants.CONFIG_PANEL_ID;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.nanoleaf.internal.handler.NanoleafPanelHandler;
 import org.openhab.core.library.types.HSBType;
-import org.openhab.core.thing.Thing;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Stores the state of the panels.
@@ -32,37 +21,7 @@ import org.slf4j.LoggerFactory;
  * @author Jørgen Austvik - Initial contribution
  */
 @NonNullByDefault
-public class PanelState {
-
-    private static final Logger logger = LoggerFactory.getLogger(PanelState.class);
-    private final Map<Integer, HSBType> panelStates = new HashMap<>();
-
-    public PanelState(List<Thing> panels) {
-        for (Thing panel : panels) {
-            Integer panelId = Integer.valueOf(panel.getConfiguration().get(CONFIG_PANEL_ID).toString());
-            NanoleafPanelHandler panelHandler = (NanoleafPanelHandler) panel.getHandler();
-            if (panelHandler != null) {
-                HSBType c = panelHandler.getColor();
-
-                if (c == null) {
-                    logger.trace("Panel {}: Failed to get color", panelId);
-                }
-
-                HSBType color = (c == null) ? HSBType.BLACK : c;
-                panelStates.put(panelId, color);
-            } else {
-                logger.trace("Panel {}: Couldn't find handler", panelId);
-            }
-        }
-    }
-
-    public HSBType getHSBForPanel(Integer panelId) {
-        if (logger.isTraceEnabled()) {
-            if (!panelStates.containsKey(panelId)) {
-                logger.trace("Failed to get color for panel {}, falling back to black", panelId);
-            }
-        }
+public interface PanelState {
 
-        return panelStates.getOrDefault(panelId, HSBType.BLACK);
-    }
+    HSBType getHSBForPanel(Integer panelId);
 }
index f0425f45b5b3f9cbd7b165bfc2675e75ecccf679..2fa76a0d2c9d62949bf7e0a4a7caef5b4496e7c2 100644 (file)
@@ -16,6 +16,7 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.List;
+import java.util.Objects;
 import java.util.Queue;
 
 import org.eclipse.jdt.annotation.NonNull;
@@ -36,7 +37,7 @@ public class PanelFactory {
         List<Panel> result = new ArrayList<>(panels.size());
         Deque<PositionDatum> panelStack = new ArrayDeque<>(panels);
         while (!panelStack.isEmpty()) {
-            PositionDatum panel = panelStack.peek();
+            PositionDatum panel = Objects.requireNonNull(panelStack.peek());
             final ShapeType shapeType = ShapeType.valueOf(panel.getShapeType());
             Panel shape = createPanel(shapeType, takeFirst(shapeType.getNumLightsPerShape(), panelStack));
             result.add(shape);
index 2400808d35e675f44089d295b4e32244045f7844..e459d2c27f48bd49a1c2eac7f4f70d1810d6a665 100644 (file)
@@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -70,14 +69,10 @@ public class NanoleafLayoutTest {
         // Files.write(permanentOutFile, result);
     }
 
-    private class TestPanelState extends PanelState {
+    private class TestPanelState implements PanelState {
         private final HSBType testColors[] = { HSBType.fromRGB(160, 120, 40), HSBType.fromRGB(80, 60, 20),
                 HSBType.fromRGB(120, 90, 30), HSBType.fromRGB(200, 150, 60) };
 
-        public TestPanelState() {
-            super(Collections.emptyList());
-        }
-
         @Override
         public HSBType getHSBForPanel(Integer panelId) {
             return testColors[panelId % testColors.length];