Skip to content

Commit 612971a

Browse files
committed
add effects for HALight
1 parent 1d333ab commit 612971a

File tree

5 files changed

+532
-6
lines changed

5 files changed

+532
-6
lines changed

src/device-types/HALight.cpp

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,31 @@ HALight::HALight(const char* uniqueId, const uint8_t features) :
5858
_maxMireds(),
5959
_currentColorTemperature(0),
6060
_currentRGBColor(),
61+
_effects(nullptr),
62+
_currentEffect(0),
6163
_stateCallback(nullptr),
6264
_brightnessCallback(nullptr),
6365
_colorTemperatureCallback(nullptr),
64-
_rgbColorCallback(nullptr)
66+
_rgbColorCallback(nullptr),
67+
_effectCallback(nullptr)
6568
{
6669

6770
}
6871

72+
HALight::~HALight()
73+
{
74+
if (_effects) {
75+
const uint8_t effectsNb = _effects->getItemsNb();
76+
const HASerializerArray::ItemType* effects = _effects->getItems();
77+
78+
for (uint8_t i = 0; i < effectsNb; i++) {
79+
delete[] effects[i];
80+
}
81+
82+
delete _effects;
83+
}
84+
}
85+
6986
bool HALight::setState(const bool state, const bool force)
7087
{
7188
if (!force && state == _currentState) {
@@ -122,13 +139,49 @@ bool HALight::setRGBColor(const RGBColor& color, const bool force)
122139
return false;
123140
}
124141

142+
void HALight::setEffects(const char* const effects[], const uint8_t size)
143+
{
144+
if (!(_features & EffectsFeature) || !effects || size == 0 || _effects) { // effects can be set only once
145+
return;
146+
}
147+
148+
_effects = new HASerializerArray(size, false);
149+
150+
uint8_t effectLen = 0;
151+
for (uint8_t i = 0; i < size; i++) {
152+
effectLen = strlen(effects[i]);
153+
154+
char* const effect = new char[effectLen + 1]; // include null terminator
155+
effect[effectLen] = '\0';
156+
memcpy(effect, effects[i], effectLen);
157+
158+
_effects->add(effect);
159+
}
160+
}
161+
162+
bool HALight::setEffect(const uint8_t effect, const bool force)
163+
{
164+
if (!force && effect == _currentEffect) {
165+
return true;
166+
}
167+
168+
if (publishEffect(effect)) {
169+
_currentEffect = effect;
170+
return true;
171+
}
172+
173+
return false;
174+
}
175+
125176
void HALight::buildSerializer()
126177
{
127-
if (_serializer || !uniqueId()) {
178+
// EffectsFeature enabled and no _effects set => unlogical
179+
const bool effectsEnabledButNotSet = (_features & EffectsFeature) && !_effects;
180+
if (_serializer || !uniqueId() || effectsEnabledButNotSet) {
128181
return;
129182
}
130183

131-
_serializer = new HASerializer(this, 19); // 19 - max properties nb
184+
_serializer = new HASerializer(this, 22); // 22 - max properties nb
132185
_serializer->set(AHATOFSTR(HANameProperty), _name);
133186
_serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId);
134187
_serializer->set(HASerializer::WithUniqueId);
@@ -189,6 +242,17 @@ void HALight::buildSerializer()
189242
_serializer->topic(AHATOFSTR(HARGBStateTopic));
190243
}
191244

245+
if (_features & EffectsFeature) {
246+
_serializer->topic(AHATOFSTR(HAEffectStateTopic));
247+
_serializer->topic(AHATOFSTR(HAEffectCommandTopic));
248+
249+
_serializer->set(
250+
AHATOFSTR(HAEffectsProperty),
251+
_effects,
252+
HASerializer::ArrayPropertyType
253+
);
254+
}
255+
192256
_serializer->set(HASerializer::WithDevice);
193257
_serializer->set(HASerializer::WithAvailability);
194258
_serializer->topic(AHATOFSTR(HAStateTopic));
@@ -209,6 +273,7 @@ void HALight::onMqttConnected()
209273
publishBrightness(_currentBrightness);
210274
publishColorTemperature(_currentColorTemperature);
211275
publishRGBColor(_currentRGBColor);
276+
publishEffect(_currentEffect);
212277
}
213278

214279
subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic));
@@ -224,6 +289,10 @@ void HALight::onMqttConnected()
224289
if (_features & RGBFeature) {
225290
subscribeTopic(uniqueId(), AHATOFSTR(HARGBCommandTopic));
226291
}
292+
293+
if (_features & EffectsFeature) {
294+
subscribeTopic(uniqueId(), AHATOFSTR(HAEffectCommandTopic));
295+
}
227296
}
228297

229298
void HALight::onMqttMessage(
@@ -258,6 +327,14 @@ void HALight::onMqttMessage(
258327
)
259328
) {
260329
handleRGBCommand(payload, length);
330+
} else if (
331+
HASerializer::compareDataTopics(
332+
topic,
333+
uniqueId(),
334+
AHATOFSTR(HAEffectCommandTopic)
335+
)
336+
) {
337+
handleEffectCommand(payload, length);
261338
}
262339
}
263340

@@ -317,6 +394,20 @@ bool HALight::publishRGBColor(const RGBColor& color)
317394
return publishOnDataTopic(AHATOFSTR(HARGBStateTopic), str, true);
318395
}
319396

397+
bool HALight::publishEffect(const uint8_t effect)
398+
{
399+
if (!(_features & EffectsFeature) || !_effects || effect >= _effects->getItemsNb()) {
400+
return false;
401+
}
402+
403+
const char* effectName = _effects->getItems()[effect];
404+
if (!effectName) {
405+
return false;
406+
}
407+
408+
return publishOnDataTopic(AHATOFSTR(HAEffectStateTopic), effectName, true);
409+
}
410+
320411
void HALight::handleStateCommand(const uint8_t* cmd, const uint16_t length)
321412
{
322413
(void)cmd;
@@ -370,4 +461,21 @@ void HALight::handleRGBCommand(const uint8_t* cmd, const uint16_t length)
370461
}
371462
}
372463

464+
void HALight::handleEffectCommand(const uint8_t* cmd, const uint16_t length)
465+
{
466+
if (!_effectCallback) {
467+
return;
468+
}
469+
470+
const uint8_t effectsNb = _effects->getItemsNb();
471+
const HASerializerArray::ItemType* effects = _effects->getItems();
472+
473+
for (uint8_t i = 0; i < effectsNb; i++) {
474+
if (strlen(effects[i]) == length && memcmp(cmd, effects[i], length) == 0) {
475+
_effectCallback(i, this);
476+
return;
477+
}
478+
}
479+
}
480+
373481
#endif

src/device-types/HALight.h

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66

77
#ifndef EX_ARDUINOHA_LIGHT
88

9+
class HASerializerArray;
10+
911
#define HALIGHT_STATE_CALLBACK(name) void (*name)(bool state, HALight* sender)
1012
#define HALIGHT_BRIGHTNESS_CALLBACK(name) void (*name)(uint8_t brightness, HALight* sender)
1113
#define HALIGHT_COLOR_TEMP_CALLBACK(name) void (*name)(uint16_t temperature, HALight* sender)
1214
#define HALIGHT_RGB_COLOR_CALLBACK(name) void (*name)(HALight::RGBColor color, HALight* sender)
15+
#define HALIGHT_EFFECT_CALLBACK(name) void (*name)(uint8_t index, HALight* sender)
1316

1417
/**
1518
* HALight allows adding a controllable light in the Home Assistant panel.
16-
* The library supports only the state, brightness, color temperature and RGB color.
19+
* The library supports only the state, brightness, color temperature, RGB color and effects.
1720
* If you need more features please open a new GitHub issue.
1821
*
1922
* @note
@@ -29,7 +32,8 @@ class HALight : public HABaseDeviceType
2932
DefaultFeatures = 0,
3033
BrightnessFeature = 1,
3134
ColorTemperatureFeature = 2,
32-
RGBFeature = 4
35+
RGBFeature = 4,
36+
EffectsFeature = 8
3337
};
3438

3539
struct RGBColor {
@@ -77,6 +81,7 @@ class HALight : public HABaseDeviceType
7781
* `HALight::BrightnessFeature | HALight::ColorTemperatureFeature`
7882
*/
7983
HALight(const char* uniqueId, const uint8_t features = DefaultFeatures);
84+
~HALight();
8085

8186
/**
8287
* Changes state of the light and publishes MQTT message.
@@ -122,6 +127,33 @@ class HALight : public HABaseDeviceType
122127
*/
123128
bool setRGBColor(const RGBColor& color, const bool force = false);
124129

130+
/**
131+
* Sets the list of available effects that will be listed.
132+
* For example:
133+
* `
134+
* const char* const lightEffects[] = {"Fire","Rainbow","Snowflales","Rain","Smoke"};
135+
* light.setEffects(lightEffects, 5);
136+
* `
137+
*
138+
*
139+
* @param effects The list of effects i.e. array of strings.
140+
* @param size The size of the effects list i.e. total number of effects.
141+
* @note The effects list can be set only once.
142+
*/
143+
void setEffects(const char* const effects[], const uint8_t size);
144+
145+
/**
146+
* Changes the effect of the light and publishes MQTT message.
147+
* Effect represents the index of the effect that was set using setEffects method.
148+
* Please note that if a new value is the same as previous one,
149+
* the MQTT message won't be published.
150+
*
151+
* @param effect The new effect index of the light.
152+
* @param force Forces to update the value without comparing it to a previous known value.
153+
* @return Returns `true` if the effect is set successfully.
154+
*/
155+
bool setEffect(const uint8_t effect, const bool force = false);
156+
125157
/**
126158
* Alias for `setState(true)`.
127159
*/
@@ -202,6 +234,24 @@ class HALight : public HABaseDeviceType
202234
inline const RGBColor& getCurrentRGBColor() const
203235
{ return _currentRGBColor; }
204236

237+
/**
238+
* Sets the current effect of the light without pushing the value to Home Assistant.
239+
* This method may be useful if you want to change the effect before the connection
240+
* with the MQTT broker is acquired.
241+
*
242+
* @param effect The new effect.
243+
*/
244+
inline void setCurrentEffect(const uint8_t effect)
245+
{ _currentEffect = effect; }
246+
247+
/**
248+
* Returns the last known effect of the light.
249+
* Effect represents the index of the effect that was set using setEffects method.
250+
* By default the effect is set to `0`.
251+
*/
252+
inline uint8_t getCurrentEffect() const
253+
{ return _currentEffect; }
254+
205255
/**
206256
* Sets icon of the light.
207257
* Any icon from MaterialDesignIcons.com (for example: `mdi:home`).
@@ -297,6 +347,21 @@ class HALight : public HABaseDeviceType
297347
inline void onRGBColorCommand(HALIGHT_RGB_COLOR_CALLBACK(callback))
298348
{ _rgbColorCallback = callback; }
299349

350+
/**
351+
* Registers callback that will be called each time the effect command from HA is received.
352+
* Please note that it's not possible to register multiple callbacks for the same light.
353+
*
354+
* @param callback
355+
* @note In non-optimistic mode, the effect must be reported back to HA using the HALight::setEffect method.
356+
*/
357+
inline void onEffectCommand(HALIGHT_EFFECT_CALLBACK(callback))
358+
{ _effectCallback = callback; }
359+
360+
#ifdef ARDUINOHA_TEST
361+
inline HASerializerArray* getEffects() const
362+
{ return _effects; }
363+
#endif
364+
300365
protected:
301366
virtual void buildSerializer() override;
302367
virtual void onMqttConnected() override;
@@ -339,6 +404,14 @@ class HALight : public HABaseDeviceType
339404
*/
340405
bool publishRGBColor(const RGBColor& color);
341406

407+
/**
408+
* Publishes the MQTT message with the given effect.
409+
*
410+
* @param effect The effect to publish.
411+
* @returns Returns `true` if the MQTT message has been published successfully.
412+
*/
413+
bool publishEffect(const uint8_t effect);
414+
342415
/**
343416
* Parses the given state command and executes the callback with proper value.
344417
*
@@ -371,6 +444,14 @@ class HALight : public HABaseDeviceType
371444
*/
372445
void handleRGBCommand(const uint8_t* cmd, const uint16_t length);
373446

447+
/**
448+
* Parses the given effect command and executes the callback with proper value.
449+
*
450+
* @param cmd The data of the command.
451+
* @param length Length of the command.
452+
*/
453+
void handleEffectCommand(const uint8_t* cmd, const uint16_t length);
454+
374455
/// Features enabled for the light.
375456
const uint8_t _features;
376457

@@ -404,6 +485,12 @@ class HALight : public HABaseDeviceType
404485
/// The current RBB color. By default the value is not set.
405486
RGBColor _currentRGBColor;
406487

488+
/// Array of effects for the serializer.
489+
HASerializerArray* _effects;
490+
491+
/// The current effect (the current effect's index). By default it's `0`.
492+
uint8_t _currentEffect;
493+
407494
/// The callback that will be called when the state command is received from the HA.
408495
HALIGHT_STATE_CALLBACK(_stateCallback);
409496

@@ -415,6 +502,9 @@ class HALight : public HABaseDeviceType
415502

416503
/// The callback that will be called when the RGB command is received from the HA.
417504
HALIGHT_RGB_COLOR_CALLBACK(_rgbColorCallback);
505+
506+
/// The callback that will be called when the effect is received from the HA.
507+
HALIGHT_EFFECT_CALLBACK(_effectCallback);
418508
};
419509

420510
#endif

src/utils/HADictionary.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const char HASpeedRangeMinProperty[] PROGMEM = {"spd_rng_min"};
6565
const char HABrightnessScaleProperty[] PROGMEM = {"bri_scl"};
6666
const char HAMinMiredsProperty[] PROGMEM = {"min_mirs"};
6767
const char HAMaxMiredsProperty[] PROGMEM = {"max_mirs"};
68+
const char HAEffectsProperty[] PROGMEM = {"fx_list"};
6869
const char HATemperatureUnitProperty[] PROGMEM = {"temp_unit"};
6970
const char HAMinTempProperty[] PROGMEM = {"min_temp"};
7071
const char HAMaxTempProperty[] PROGMEM = {"max_temp"};
@@ -105,6 +106,8 @@ const char HATemperatureStateTopic[] PROGMEM = {"temp_stat_t"};
105106
const char HARGBCommandTopic[] PROGMEM = {"rgb_cmd_t"};
106107
const char HARGBStateTopic[] PROGMEM = {"rgb_stat_t"};
107108
const char HAJsonAttributesTopic[] PROGMEM = {"json_attr_t"};
109+
const char HAEffectCommandTopic[] PROGMEM = {"fx_cmd_t"};
110+
const char HAEffectStateTopic[] PROGMEM = {"fx_stat_t"};
108111

109112
// misc
110113
const char HAOnline[] PROGMEM = {"online"};

src/utils/HADictionary.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ extern const char HASpeedRangeMinProperty[];
6565
extern const char HABrightnessScaleProperty[];
6666
extern const char HAMinMiredsProperty[];
6767
extern const char HAMaxMiredsProperty[];
68+
extern const char HAEffectsProperty[];
6869
extern const char HATemperatureUnitProperty[];
6970
extern const char HAMinTempProperty[];
7071
extern const char HAMaxTempProperty[];
@@ -105,6 +106,8 @@ extern const char HATemperatureStateTopic[];
105106
extern const char HARGBCommandTopic[];
106107
extern const char HARGBStateTopic[];
107108
extern const char HAJsonAttributesTopic[];
109+
extern const char HAEffectCommandTopic[];
110+
extern const char HAEffectStateTopic[];
108111

109112
// misc
110113
extern const char HAOnline[];

0 commit comments

Comments
 (0)