Skip to content

Commit a51e7f9

Browse files
andrewfgCopilot
andauthored
State machine to model lights in Thing handlers (#4995)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8f22f9b commit a51e7f9

File tree

3 files changed

+2681
-0
lines changed

3 files changed

+2681
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.core.thing.binding;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.openhab.core.thing.ChannelUID;
17+
import org.openhab.core.thing.Thing;
18+
import org.openhab.core.types.Command;
19+
import org.openhab.core.util.LightModel;
20+
21+
/**
22+
* {@link BaseLightThingHandler} provides an abstract base implementation for a {@link ThingHandler} for a light.
23+
*
24+
* @author Andrew Fiddian-Green - Initial contribution
25+
*/
26+
@NonNullByDefault
27+
public abstract class BaseLightThingHandler extends BaseThingHandler {
28+
29+
/**
30+
* Light state machine model to manage light capabilities, configuration, and runtime state
31+
*/
32+
protected final LightModel model = new LightModel();
33+
34+
public BaseLightThingHandler(Thing thing) {
35+
super(thing);
36+
}
37+
38+
/**
39+
* Override this method to handle commands from OH core.
40+
* <p>
41+
* Example: (implementation will depend on the specific binding and device).
42+
*
43+
* <pre>
44+
* {@code
45+
*
46+
* // update the model state based on the command from OpenHAB
47+
* model.handleCommand(command);
48+
*
49+
* // or if it is a color temperature command
50+
* model.handleColorTemperatureCommand(command);
51+
*
52+
* // and transmit the appropriate command to the remote light device based on the model state
53+
* doTransmitBindingSpecificRemoteLightData(model);
54+
*
55+
* }
56+
* </pre>
57+
*/
58+
@Override
59+
public abstract void handleCommand(ChannelUID channelUID, Command command);
60+
61+
/**
62+
* Override this method to provide initialization of the light state machine capabilities and configuration
63+
* parameters.
64+
* <p>
65+
* Example: (implementation will depend on the specific binding and device).
66+
*
67+
* <pre>
68+
* {@code
69+
*
70+
* // STEP 1: Set up the light state machine capabilities.
71+
* model.configSetLightCapabilities(LightCapabilities.COLOR_WITH_COLOR_TEMPERATURE);
72+
*
73+
* // STEP 2: optionally set up the light state machine configuration parameters.
74+
* // These would typically be read from the thing configuration or read from the remote device.
75+
* model.configSetRgbDataType(RgbDataType.RGB_NO_BRIGHTNESS); // RGB data type
76+
* model.configSetMinimumOnBrightness(2); // minimum brightness level in % when on
77+
* model.configSetIncreaseDecreaseStep(10); // step size for increase/decrease commands
78+
* model.configSetMirekControlCoolest(153); // color temperature control range coolest
79+
* model.configSetMirekControlWarmest(500); // color temperature control range warmest
80+
*
81+
* // STEP 3: optionally if the light has warm and cool white LEDS then set up their LED color temperatures.
82+
* // These would typically be read from the thing configuration or read from the remote device.
83+
* model.configSetMirekCoolWhiteLED(153);
84+
* model.configSetMirekWarmWhiteLED(500);
85+
*
86+
* // STEP 4: now set the status to UNKNOWN to indicate that we are initialized
87+
* updateStatus(ThingStatus.UNKNOWN);
88+
*
89+
* // STEP 5: finally provide further initialization, e.g. connecting to the remote device
90+
* ...
91+
*
92+
* }
93+
* </pre>
94+
*/
95+
@Override
96+
public abstract void initialize();
97+
98+
/**
99+
* Transmit the appropriate command to the remote light device based on the model state.
100+
* This method must be overridden in the concrete implementation to transmit the appropriate command(s)
101+
* to the remote light device based on the model state.
102+
* <p>
103+
* Example: (implementation will depend on the specific binding and device).
104+
*
105+
* <pre>
106+
* {@code
107+
*
108+
* if (model.getOnOff() == OnOffType.ON) {
109+
* transmit command to turn on the light
110+
* } else {
111+
* transmit command to turn off the light
112+
* }
113+
*
114+
* if (model.getBrightness() != null) {
115+
* transmit command to set brightness to model.getBrightness()
116+
* }
117+
*
118+
* if (model.getColor() != null) {
119+
* transmit command to set color to model.getColor()
120+
* }
121+
*
122+
* if (model.getColorTemperature() != null) {
123+
* transmit command to set color temperature to model.getColorTemperature()
124+
* }
125+
*
126+
* if (model.getColorTemperaturePercent() != null) {
127+
* transmit command to set color temperature percent to model.getColorTemperaturePercent()
128+
* }
129+
*
130+
* if (model.getRGBx().length == 3) {
131+
* transmit command to set RGB value to model.getRGBx()
132+
* }
133+
*
134+
* if the light supports XY color coordinates:
135+
* transmit command to set XY value to model.getXY()
136+
* }
137+
* }
138+
* </pre>
139+
*
140+
* @param model the light model containing the current state
141+
*/
142+
protected abstract void doTransmitBindingSpecificRemoteLightData(LightModel model);
143+
144+
/**
145+
* Receive data from the remote light device and update the model state accordingly.
146+
* This method must be overridden in the concrete implementation to 1) receive data from the remote light device
147+
* 2) update the model state accordingly, and 3) update the openHAB channels.
148+
* <P>
149+
* Example: (implementation will depend on the specific binding and device).
150+
*
151+
* <pre>
152+
* {@code
153+
*
154+
* STEP 1: Parse the remoteData to extract the relevant information. Depends on specific binding / device
155+
*
156+
* OnOffType onOff = ...; // extract on/off state from remoteData
157+
* Integer brightness = ...; // extract brightness from remoteData
158+
* HSBType color = ...; // extract color from remoteData
159+
* Integer colorTemperature = ...; // extract color temperature from remoteData
160+
* Integer colorTemperaturePercent = ...; // extract color temperature percent from remoteData
161+
* RGBType rgb = ...; // extract RGB value from remoteData
162+
* XYType xy = ...; // extract XY value from remoteData
163+
*
164+
* STEP 2: Update the model state based on the received data
165+
*
166+
* if (onOff != null) {
167+
* model.setOnOff(onOff.booleanValue());
168+
* }
169+
*
170+
* if (brightness != null) {
171+
* model.setBrightness(brightness);
172+
* }
173+
*
174+
* if (color != null) {
175+
* model.setColor(color);
176+
* }
177+
*
178+
* if (rgb != null) {
179+
* model.setRGBx(rgb);
180+
* }
181+
*
182+
* if (xy != null) {
183+
* model.setXY(xy);
184+
* }
185+
*
186+
* Handle color temperature reports from the device
187+
* Option A: device reports color temperature directly in Mirek (micro reciprocal Kelvin)
188+
* if (colorTemperatureMirek != null) {
189+
* model.setMirek(colorTemperatureMirek);
190+
* }
191+
*
192+
* Option B: device reports color temperature in Kelvin
193+
* if (colorTemperatureKelvin != null) {
194+
* // Convert Kelvin to Mirek: mirek = 1_000_000 / kelvin
195+
* double mirek = 1_000_000d / colorTemperatureKelvin;
196+
* model.setMirek(mirek);
197+
* }
198+
*
199+
* Option C: device reports color temperature as a 0–100% value
200+
* if (colorTemperaturePercent != null) {
201+
* // Map the percent value to your device's supported Mirek range (mirekMin..mirekMax)
202+
* // before updating the model. Example:
203+
* double mirek = mirekMin + (mirekMax - mirekMin) * colorTemperaturePercent / 100.0;
204+
* model.setMirek(mirek);
205+
* }
206+
*
207+
* STEP 3: After updating the model, update the channel states in OpenHAB
208+
* Note: Ensure that the channel IDs used in updateState() match those defined in the thing type.
209+
*
210+
* if (model.configGetLightCapabilities().supportsColor()) {
211+
* updateState(CHANNEL_COLOR, model.getColor());
212+
* } else if (model.configGetLightCapabilities().supportsBrightness()) {
213+
* updateState(CHANNEL_BRIGHTNESS, model.getBrightness());
214+
* } else {
215+
* updateState(CHANNEL_ON_OFF, model.getOnOff());
216+
* }
217+
*
218+
* if (model.configGetLightCapabilities().supportsColorTemperature()) {
219+
* updateState(CHANNEL_COLOR_TEMPERATURE_ABS, model.getColorTemperature());
220+
* updateState(CHANNEL_COLOR_TEMPERATURE_PERCENT, model.getColorTemperaturePercent());
221+
* }
222+
* }
223+
* </pre>
224+
*
225+
* @param remoteData the data received from the remote light device
226+
*/
227+
protected abstract void onReceiveBindingSpecificRemoteLightData(Object... remoteData);
228+
}

0 commit comments

Comments
 (0)