Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
325de23
[shelly] Add Shelly Pro RGBWW PM device type with profile dispatch
markus7017 Aug 16, 2026
34f61eb
[shelly] Propagate CCT color-temperature end-to-end for Pro RGBWW PM
markus7017 Aug 16, 2026
0952c67
[shelly] Wire component-level power/energy into meter channels for Pr…
markus7017 Aug 20, 2026
2cdd822
[shelly] README: document cctx2 color-temp channel and per-channel me…
markus7017 Aug 16, 2026
dc73d59
[shelly] Tag Pro RGBWW PM settings.lights entries with their API comp…
markus7017 Aug 17, 2026
5a9ffe4
[shelly] Dispatch Pro RGBWW PM light RPCs by apiComponent tag
markus7017 Aug 17, 2026
0ea2d3a
[shelly] Fix white/temp channel group for hybrid Pro RGBWW PM seconda…
markus7017 Aug 17, 2026
2679637
[shelly] Meter every Pro RGBWW PM light-mode component, not just rgb0
markus7017 Aug 17, 2026
aae8dcc
[shelly] Add meter3-5 channel groups for Pro RGBWW PM light profile
markus7017 Aug 17, 2026
c85c811
[shelly] Fix null-safety build error in lightRpcMethods() fallback
markus7017 Aug 17, 2026
52eed26
[shelly] Add test coverage for hybrid Pro RGBWW PM light profiles
markus7017 Aug 17, 2026
d1180cf
[shelly] README: document per-component metering for hybrid Pro RGBWW…
markus7017 Aug 17, 2026
45c4d90
[shelly] Fix hybrid Pro RGBWW PM command handling to use per-componen…
markus7017 Aug 17, 2026
5e935e7
[shelly] Fix WebSocket status push skipping hybrid profile secondary …
markus7017 Aug 17, 2026
2ec7616
[shelly] Consolidate light API component handling into ShellyApiLight…
markus7017 Aug 20, 2026
558366d
[shelly] Add device-level accumulated meter channels for Gen2 Pro RGB…
markus7017 Aug 20, 2026
e717b29
[shelly] Fix wrong RPC component id for hybrid Gen2 light profiles
markus7017 Aug 19, 2026
2a3b20f
[shelly] Fix phantom power channel for hybrid Gen2 light profiles
markus7017 Aug 19, 2026
91f951d
[shelly] Remove unused imports in ShellyLightHandler and ShellyUtils
markus7017 Aug 19, 2026
9a20bb2
[shelly] Add missing i18n group labels for Pro RGBWW PM light channels
markus7017 Aug 19, 2026
71ade77
[shelly] Fix cctx2 README description: two CCT components, not warm/c…
markus7017 Aug 20, 2026
875df96
[shelly] Retain per-component CCT ct_range for Pro RGBWW PM
markus7017 Aug 20, 2026
b89bba4
[shelly] Process the 5th Pro RGBWW PM input (input:4)
markus7017 Aug 20, 2026
9243f03
[shelly] Fix getLightIdFromGroup on hybrid Pro RGBWW PM profiles
markus7017 Aug 21, 2026
9703d68
[shelly] Drop wrong config.name from Shelly2ApiRpc.setAutoTimer
markus7017 Aug 21, 2026
d33981e
[shelly] Use per-component ct_range in updateLightMode WS-push path
markus7017 Aug 21, 2026
14ee34e
[shelly] Assert actual CCT percentage in hybrid ct_range test
markus7017 Aug 21, 2026
7ef3347
[shelly] Fix excess white channel on Pro RGBWW PM color group
markus7017 Aug 22, 2026
a928c4a
[shelly] Fix Pro RGBWW PM colorFull White option turning the light off
markus7017 Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions bundles/org.openhab.binding.shelly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ See section [Discovery](#discovery) for details.
| shellyplusdimmer | Shelly Plus Dimmer Gen 3 | S3DM-0A101WWL |
| shellyprodm2pm | Shelly Pro Dimmer 2PM | SPDM-002PE01EU |
| shellyplusrgbwpm | Shelly Plus RGBW PM | SNDC-0D4P10WW |
| shellyprorgbwwpm | Shelly Pro RGBWW PM | SPDC-0D5PE16EU |
| shellywalldisplay | Shelly Plus Wall Display | SAWD-0A1XX10EU1 |
| shellyblugw | Shelly BLU Gateway | SNGW-BT01 |
| shellyblugw3 | Shelly BLU Gateway 3 | S3GW-1DBT001 |
Expand Down Expand Up @@ -1740,6 +1741,71 @@ In `light` profile (white mode), each of the 4 LED output channels is exposed as
`Note`:
totalEnergy might reset on restart depending on device type and firmware version

### Shelly Pro RGBWW PM (thing-type: shellyprorgbwwpm)

The active device profile (`light`, `rgbcct`, `cctx2` or `rgbx2light`) is selected in the Shelly App/device settings and determines which channel groups below are populated.
Changing the profile requires deleting and re-discovering the Thing.

In `rgbcct` or `rgbx2light` profile, the RGB component is exposed as the color component (color mode):

| Group | Channel | Type | read-only | Description |
| ------- | ------------- | -------- | --------- | ----------------------------------------------------------------------- |
| control | power | Switch | r/w | Switch light ON/OFF |
| | autoOn | Number | r/w | Sets a timer to turn the device ON after every OFF command; in seconds |
| | autoOff | Number | r/w | Sets a timer to turn the device OFF after every ON command; in seconds |
| | timerActive | Switch | yes | ON: An auto-on/off timer is active |
| color | hsb | HSB | r/w | Represents the color picker (HSBType); control r/g/b, not white |
| | full | String | r/w | Set Red / Green / Blue / Yellow / White mode and switch mode |
| | | | r/w | Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" |
| | red | Dimmer | r/w | Red brightness: 0..100% (control only the red channel) |
| | green | Dimmer | r/w | Green brightness: 0..100% (control only the green channel) |
| | blue | Dimmer | r/w | Blue brightness: 0..100% (control only the blue channel) |
| | white | Dimmer | r/w | White brightness: 0..100% (control only the white channel) |

@andrewfg andrewfg Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As described here https://shelly-api-docs.shelly.cloud/gen2/Devices/Gen2/ShellyProRGBWWPM the device has 5 outputs which can be used in various combinations:

  • 5x individual light -- LIGHT profile
  • 1x RGB group, plus 2x individual light -- RGBX2LIGHT profile
  • 1x RGB group, plus 1x cool-warm pair -- RGBXCCT profile
  • 2x cool-warm pair -- CCTX2 profile

Note that the first group seems always to be RGB and not RGBW.

Therefore I would assume that the RGB mode will NOT expose a White component. i.e. RGB -only..

See my comments here #19227 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still a concrete RGB-vs-RGBW issue here. colorSettingsRGB reuses the colorFull channel type, which still offers white as an option.

The Pro RGBWW PM exposes an RGB component, not an RGBW component. Selecting white therefore makes handleFullColor() produce RGBW = 0,0,0,255, but the RGB RPC path only sends the RGB values and ignores the white value. The device consequently receives RGB = 0,0,0.

An RGB-only component should therefore not expose the White option, unless White is explicitly translated to a valid RGB value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the RGB RPC path only sends the RGB values and ignores the white value. The device consequently receives RGB = 0,0,0.

This is fixed by my PR

| meter1 | currentPower | Number | yes | Current power consumption in Watts |
| | energyAvg1Min | Number | yes | Energy consumed in the previous minute (Wh) |
| | totalEnergy | Number | yes | Total energy consumption in kWh |
| | lastUpdate | DateTime | yes | Timestamp of the last measurement |

`Note`:
`rgbcct` and `rgbx2light` combine the RGB component above with additional CCT (`rgbcct`) or Light
(`rgbx2light`) components. Each additional component is exposed as its own `light1`/`light2` group

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each additional component is exposed as its own light1/light2 group

This applies only to LIGHT or RGBX2LIGHT and not to RGBCCT or CCTX2

.. also (nit) in OH markdown convention every sentence must be on a new line.

(same layout as the `light` profile below) with its own independent meter (`meter2`/`meter3`).
Since every profile has more than one meter, the device also gets the aggregated `device#accumulatedPower`/`device#totalEnergy` channels described in the general notes on channels above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably you should add a whole new table of channels for RGBCCT with its own comments etc.

In `light` profile (white mode), each of the 5 LED output channels is exposed as its own group:

| Group | Channel | Type | read-only | Description |
| ------ | ----------- | ------ | --------- | ----------------------------------------------------------------------- |
| light1 | brightness | Dimmer | r/w | Channel 1: Brightness: 0..100, control power state with ON/OFF |
| | autoOn | Number | r/w | Sets a timer to turn the device ON after every OFF command; in seconds |
| | autoOff | Number | r/w | Sets a timer to turn the device OFF after every ON command; in seconds |
| | timerActive | Switch | yes | ON: An auto-on/off timer is active |
| light2 | | | | Same for LED channel 2 |
| light3 | | | | Same for LED channel 3 |
| light4 | | | | Same for LED channel 4 |
| light5 | | | | Same for LED channel 5 |
| meter1 | | | | Meter for LED channel 1, see meter group in color mode above |
| meter2 | | | | Meter for LED channel 2 (if configured) |
| meter3 | | | | Meter for LED channel 3 (if configured) |
| meter4 | | | | Meter for LED channel 4 (if configured) |
| meter5 | | | | Meter for LED channel 5 (if configured) |

In `cctx2` profile (dual color-temperature mode), the device exposes two independent CCT components (`CCT:0` and `CCT:1`), each controlling its own warm/cold white pair; they are each exposed as their own channel group, with its own independent meter:

| Group | Channel | Type | read-only | Description |
| ------ | ----------- | ------ | --------- | ----------------------------------------------------------------------- |
| light1 | brightness | Dimmer | r/w | CCT channel 1: Brightness: 0..100, control power state with ON/OFF |
| | colorTemp | Dimmer | r/w | CCT channel 1: Color temperature: 0..100% (2700K..6500K) |
| | autoOn | Number | r/w | Sets a timer to turn the device ON after every OFF command; in seconds |
| | autoOff | Number | r/w | Sets a timer to turn the device OFF after every ON command; in seconds |
| | timerActive | Switch | yes | ON: An auto-on/off timer is active |
| light2 | | | | Same for CCT channel 2 |
| meter1 | | | | Meter for CCT channel 1 (light1), see meter group above |
| meter2 | | | | Meter for CCT channel 2 (light2), see meter group above |

`Note`:
totalEnergy might reset on restart depending on device type and firmware version

## Shelly Plus Mini Series

### Shelly Plus 1 Mini (thing-type: shelly1mini)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public class ShellyDevices {
public static final String SHELLYDT_PRODM2PM = "SPDM-002PE01EU";
public static final String SHELLYDT_PLUSWALLDISPLAY = "SAWD-0A1XX10EU1";
public static final String SHELLYDT_PLUSRGBWPM = "SNDC-0D4P10WW";
public static final String SHELLYDT_PRORGBWWPM = "SPDC-0D5PE16EU";
public static final String SHELLYDT_PLUSSTRIP = "S4PL-00416EU";
public static final String SHELLYDT_PLUSPLUGCPM = "S4PL-00116EU"; // Shelly Plus Plug CPM (Gen4)
public static final String SHELLYDT_PLUSEM = "S3EM-002CXCEU";
Expand Down Expand Up @@ -262,6 +263,7 @@ public class ShellyDevices {
public static final ThingTypeUID THING_TYPE_SHELLYPLUSDIMMER = new ThingTypeUID(BINDING_ID, "shellyplusdimmer");
public static final ThingTypeUID THING_TYPE_SHELLYPRODM2PM = new ThingTypeUID(BINDING_ID, "shellyprodm2pm");
public static final ThingTypeUID THING_TYPE_SHELLYPLUSRGBWPM = new ThingTypeUID(BINDING_ID, "shellyplusrgbwpm");
public static final ThingTypeUID THING_TYPE_SHELLYPRORGBWWPM = new ThingTypeUID(BINDING_ID, "shellyprorgbwwpm");
public static final ThingTypeUID THING_TYPE_SHELLYPLUSSTRIP = new ThingTypeUID(BINDING_ID, "shellyplusstrip");
public static final ThingTypeUID THING_TYPE_SHELLYPLUSEM = new ThingTypeUID(BINDING_ID, "shellyplusem");
public static final ThingTypeUID THING_TYPE_SHELLYPLUS3EM63 = new ThingTypeUID(BINDING_ID, "shellyplus3em63");
Expand Down Expand Up @@ -321,7 +323,8 @@ public class ShellyDevices {

// RGBW2 devices
public static final Set<ThingTypeUID> GROUP_RGBW2_THING_TYPES = Set.of( //
THING_TYPE_SHELLYRGBW2_COLOR, THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYPLUSRGBWPM);
THING_TYPE_SHELLYRGBW2_COLOR, THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYPLUSRGBWPM,

@andrewfg andrewfg Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think RGBWWPM is not strictly an RGBW thing type since it has no W component.

See other comments above and below

THING_TYPE_SHELLYPRORGBWWPM);

// All Light Bulbs / RGBW devices
public static final Set<ThingTypeUID> GROUP_LIGHT_THING_TYPES = Stream
Expand Down Expand Up @@ -467,6 +470,7 @@ public class ShellyDevices {
Map.entry(SHELLYDT_PLUSDIMMERG3, THING_TYPE_SHELLYPLUSDIMMER),
Map.entry(SHELLYDT_PRODM2PM, THING_TYPE_SHELLYPRODM2PM),
Map.entry(SHELLYDT_PLUSRGBWPM, THING_TYPE_SHELLYPLUSRGBWPM),
Map.entry(SHELLYDT_PRORGBWWPM, THING_TYPE_SHELLYPRORGBWWPM),
Map.entry(SHELLYDT_PLUSEM, THING_TYPE_SHELLYPLUSEM),
Map.entry(SHELLYDT_PLUS3EM63, THING_TYPE_SHELLYPLUS3EM63),
Map.entry(SHELLYDT_PLUSBLUGW, THING_TYPE_SHELLYPLUSBLUGW), //
Expand Down Expand Up @@ -656,6 +660,7 @@ public class ShellyDevices {
Map.entry("shellydimmerg3", THING_TYPE_SHELLYPLUSDIMMER),
Map.entry("shellyprodm2pm", THING_TYPE_SHELLYPRODM2PM),
Map.entry("shellyplusrgbwpm", THING_TYPE_SHELLYPLUSRGBWPM),
Map.entry("shellyprorgbwwpm", THING_TYPE_SHELLYPRORGBWWPM),
Map.entry("shellyplusstrip", THING_TYPE_SHELLYPLUSSTRIP),
Map.entry("shellyblugw", THING_TYPE_SHELLYPLUSBLUGW), //
Map.entry("shellyblugwg3", THING_TYPE_SHELLYPLUSBLUGW), //
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (c) 2010-2026 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.shelly.internal.api;

import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.substringAfter;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRgbwLight;

/**
* The {@link ShellyApiLightUtil} provides light-specific helpers shared by Gen1 (api1) and Gen2+ (api2) code:
* the {@code settings.lights[i].apiComponent} tag values used to identify which light API component family (RGB,
* RGBW, CCT or plain Light) a {@code ShellySettingsRgbwLight} entry belongs to, and the light channel group/id
* resolution shared across handlers.
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
public class ShellyApiLightUtil {
private ShellyApiLightUtil() {
}

/**
* The light API component family a {@code settings.lights[i]} entry belongs to. {@code NONE} marks an
* untagged entry (a Gen1 RGBW2/Bulb entry never sets apiComponent) or an out-of-range index.
* <p>
* Each index is an independently switchable/dimmable/metered physical component - not a sub-channel of one
* combined light. A hybrid Pro RGBWW PM profile (e.g. {@code rgbcct}, {@code rgbx2light}) reports its color
* component (RGB/RGBW) and its secondary CCT/Light component(s) as separate {@code settings.lights} indices,
* each with its own RPC methods and meter - never merged into a single multi-channel model.
*/
public enum ShellyLightApiComponent {
RGB,
RGBW,
CCT,
LIGHT,
NONE
}

/**
* True when tag identifies an RGB/RGBW color component, as opposed to a CCT/Light one.
*/
public static boolean isColorComponent(ShellyLightApiComponent apiComponent) {
return apiComponent == ShellyLightApiComponent.RGB || apiComponent == ShellyLightApiComponent.RGBW;
}

public static boolean isRgbComponent(ShellyLightApiComponent apiComponent) {
return apiComponent == ShellyLightApiComponent.RGB;
}

public static boolean isRgbwComponent(ShellyLightApiComponent apiComponent) {
return apiComponent == ShellyLightApiComponent.RGBW;
}

public static boolean isCctComponent(ShellyLightApiComponent apiComponent) {
return apiComponent == ShellyLightApiComponent.CCT;
}

public static boolean isLightComponent(ShellyLightApiComponent apiComponent) {
return apiComponent == ShellyLightApiComponent.LIGHT;
}

/**
* The apiComponent tag of {@code lights.get(idx)}, or {@code NONE} when lights is null, idx is out of range,
* or the entry is untagged (a Gen1 RGBW2/Bulb entry never sets apiComponent).
*/
public static ShellyLightApiComponent tagAt(@Nullable List<ShellySettingsRgbwLight> lights, int idx) {
return lights != null && idx >= 0 && idx < lights.size() ? lights.get(idx).apiComponent
: ShellyLightApiComponent.NONE;
}

/**
* True when any entry in lights is an RGB/RGBW color component.
*/
public static boolean hasColorComponent(List<ShellySettingsRgbwLight> lights) {
return lights.stream().map(l -> l.apiComponent).anyMatch(ShellyApiLightUtil::isColorComponent);
}

/**
* Reverses {@link ShellyDeviceProfile#getControlGroup(int)}: converts a channel group name back into its
* {@code settings.lights} index. On a hybrid profile (e.g. {@code rgbcct}, {@code rgbx2light}) the indexed
* groups (light1, light2, ...) start after the leading color component slot(s), so the group number alone
* is not the flat index - {@code profile.getColorComponentCount()} must be added back in.
*/
public static Integer getLightIdFromGroup(String groupName, ShellyDeviceProfile profile) {
if (groupName.startsWith(CHANNEL_GROUP_LIGHT_INDEX)) {
return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_INDEX)) - 1
+ profile.getColorComponentCount();
}
if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1
+ profile.getColorComponentCount();
}
return 0; // only 1 light, e.g. bulb or rgbw2 in color mode
}

// Gen2 RGBW PM ships on light1..n natively. A Gen1 RGBW2 Thing that already carries the
// deprecated channel1..n group (from before this Thing was migrated) keeps publishing there,
// dual-written to light1..n by ShellyBaseHandler.updateChannel(); a freshly discovered Gen1
// RGBW2 Thing goes straight to light1..n and never gets a channel1..n group.
public static String lightChannelGroupPrefix(ShellyDeviceProfile profile) {
return profile.isGen2 || !profile.hasLegacyLightChannels ? CHANNEL_GROUP_LIGHT_INDEX
: CHANNEL_GROUP_LIGHT_CHANNEL;
}

// Bulb/Duo report white/temp under a dedicated group, distinct from their color group; everything else
// (including a hybrid Pro RGBWW PM profile's secondary CCT/Light component) shares the same per-component
// group resolution as the color channels, so this just delegates to getControlGroup().
public static String buildWhiteGroupName(ShellyDeviceProfile profile, int lightId) {
if (profile.isBulb || profile.isDuo) {
return CHANNEL_GROUP_WHITE_CONTROL;
}
return profile.getControlGroup(lightId);
}
}
Loading