From 7f63aa4f06e57b3e1ac16afefd9042084f31b0f6 Mon Sep 17 00:00:00 2001 From: Kostiantyn Synytsia <20212876+martaisty@users.noreply.github.com> Date: Sat, 27 May 2023 12:42:24 +0300 Subject: [PATCH 1/3] add effects for HALight --- src/device-types/HALight.cpp | 114 +++++++++++- src/device-types/HALight.h | 94 +++++++++- src/utils/HADictionary.cpp | 3 + src/utils/HADictionary.h | 3 + tests/LightTest/LightTest.ino | 324 +++++++++++++++++++++++++++++++++- 5 files changed, 532 insertions(+), 6 deletions(-) diff --git a/src/device-types/HALight.cpp b/src/device-types/HALight.cpp index 03f6dfe2..d889390c 100644 --- a/src/device-types/HALight.cpp +++ b/src/device-types/HALight.cpp @@ -58,14 +58,31 @@ HALight::HALight(const char* uniqueId, const uint8_t features) : _maxMireds(), _currentColorTemperature(0), _currentRGBColor(), + _effects(nullptr), + _currentEffect(0), _stateCallback(nullptr), _brightnessCallback(nullptr), _colorTemperatureCallback(nullptr), - _rgbColorCallback(nullptr) + _rgbColorCallback(nullptr), + _effectCallback(nullptr) { } +HALight::~HALight() +{ + if (_effects) { + const uint8_t effectsNb = _effects->getItemsNb(); + const HASerializerArray::ItemType* effects = _effects->getItems(); + + for (uint8_t i = 0; i < effectsNb; i++) { + delete[] effects[i]; + } + + delete _effects; + } +} + bool HALight::setState(const bool state, const bool force) { if (!force && state == _currentState) { @@ -122,13 +139,49 @@ bool HALight::setRGBColor(const RGBColor& color, const bool force) return false; } +void HALight::setEffects(const char* const effects[], const uint8_t size) +{ + if (!(_features & EffectsFeature) || !effects || size == 0 || _effects) { // effects can be set only once + return; + } + + _effects = new HASerializerArray(size, false); + + uint8_t effectLen = 0; + for (uint8_t i = 0; i < size; i++) { + effectLen = strlen(effects[i]); + + char* const effect = new char[effectLen + 1]; // include null terminator + effect[effectLen] = '\0'; + memcpy(effect, effects[i], effectLen); + + _effects->add(effect); + } +} + +bool HALight::setEffect(const uint8_t effect, const bool force) +{ + if (!force && effect == _currentEffect) { + return true; + } + + if (publishEffect(effect)) { + _currentEffect = effect; + return true; + } + + return false; +} + void HALight::buildSerializer() { - if (_serializer || !uniqueId()) { + // EffectsFeature enabled and no _effects set => unlogical + const bool effectsEnabledButNotSet = (_features & EffectsFeature) && !_effects; + if (_serializer || !uniqueId() || effectsEnabledButNotSet) { return; } - _serializer = new HASerializer(this, 19); // 19 - max properties nb + _serializer = new HASerializer(this, 22); // 22 - max properties nb _serializer->set(AHATOFSTR(HANameProperty), _name); _serializer->set(AHATOFSTR(HAObjectIdProperty), _objectId); _serializer->set(HASerializer::WithUniqueId); @@ -189,6 +242,17 @@ void HALight::buildSerializer() _serializer->topic(AHATOFSTR(HARGBStateTopic)); } + if (_features & EffectsFeature) { + _serializer->topic(AHATOFSTR(HAEffectStateTopic)); + _serializer->topic(AHATOFSTR(HAEffectCommandTopic)); + + _serializer->set( + AHATOFSTR(HAEffectsProperty), + _effects, + HASerializer::ArrayPropertyType + ); + } + _serializer->set(HASerializer::WithDevice); _serializer->set(HASerializer::WithAvailability); _serializer->topic(AHATOFSTR(HAStateTopic)); @@ -209,6 +273,7 @@ void HALight::onMqttConnected() publishBrightness(_currentBrightness); publishColorTemperature(_currentColorTemperature); publishRGBColor(_currentRGBColor); + publishEffect(_currentEffect); } subscribeTopic(uniqueId(), AHATOFSTR(HACommandTopic)); @@ -224,6 +289,10 @@ void HALight::onMqttConnected() if (_features & RGBFeature) { subscribeTopic(uniqueId(), AHATOFSTR(HARGBCommandTopic)); } + + if (_features & EffectsFeature) { + subscribeTopic(uniqueId(), AHATOFSTR(HAEffectCommandTopic)); + } } void HALight::onMqttMessage( @@ -258,6 +327,14 @@ void HALight::onMqttMessage( ) ) { handleRGBCommand(payload, length); + } else if ( + HASerializer::compareDataTopics( + topic, + uniqueId(), + AHATOFSTR(HAEffectCommandTopic) + ) + ) { + handleEffectCommand(payload, length); } } @@ -317,6 +394,20 @@ bool HALight::publishRGBColor(const RGBColor& color) return publishOnDataTopic(AHATOFSTR(HARGBStateTopic), str, true); } +bool HALight::publishEffect(const uint8_t effect) +{ + if (!(_features & EffectsFeature) || !_effects || effect >= _effects->getItemsNb()) { + return false; + } + + const char* effectName = _effects->getItems()[effect]; + if (!effectName) { + return false; + } + + return publishOnDataTopic(AHATOFSTR(HAEffectStateTopic), effectName, true); +} + void HALight::handleStateCommand(const uint8_t* cmd, const uint16_t length) { (void)cmd; @@ -370,4 +461,21 @@ void HALight::handleRGBCommand(const uint8_t* cmd, const uint16_t length) } } +void HALight::handleEffectCommand(const uint8_t* cmd, const uint16_t length) +{ + if (!_effectCallback) { + return; + } + + const uint8_t effectsNb = _effects->getItemsNb(); + const HASerializerArray::ItemType* effects = _effects->getItems(); + + for (uint8_t i = 0; i < effectsNb; i++) { + if (strlen(effects[i]) == length && memcmp(cmd, effects[i], length) == 0) { + _effectCallback(i, this); + return; + } + } +} + #endif diff --git a/src/device-types/HALight.h b/src/device-types/HALight.h index 021d32f5..07ba9d3d 100644 --- a/src/device-types/HALight.h +++ b/src/device-types/HALight.h @@ -6,14 +6,17 @@ #ifndef EX_ARDUINOHA_LIGHT +class HASerializerArray; + #define HALIGHT_STATE_CALLBACK(name) void (*name)(bool state, HALight* sender) #define HALIGHT_BRIGHTNESS_CALLBACK(name) void (*name)(uint8_t brightness, HALight* sender) #define HALIGHT_COLOR_TEMP_CALLBACK(name) void (*name)(uint16_t temperature, HALight* sender) #define HALIGHT_RGB_COLOR_CALLBACK(name) void (*name)(HALight::RGBColor color, HALight* sender) +#define HALIGHT_EFFECT_CALLBACK(name) void (*name)(uint8_t index, HALight* sender) /** * HALight allows adding a controllable light in the Home Assistant panel. - * The library supports only the state, brightness, color temperature and RGB color. + * The library supports only the state, brightness, color temperature, RGB color and effects. * If you need more features please open a new GitHub issue. * * @note @@ -29,7 +32,8 @@ class HALight : public HABaseDeviceType DefaultFeatures = 0, BrightnessFeature = 1, ColorTemperatureFeature = 2, - RGBFeature = 4 + RGBFeature = 4, + EffectsFeature = 8 }; struct RGBColor { @@ -77,6 +81,7 @@ class HALight : public HABaseDeviceType * `HALight::BrightnessFeature | HALight::ColorTemperatureFeature` */ HALight(const char* uniqueId, const uint8_t features = DefaultFeatures); + ~HALight(); /** * Changes state of the light and publishes MQTT message. @@ -122,6 +127,33 @@ class HALight : public HABaseDeviceType */ bool setRGBColor(const RGBColor& color, const bool force = false); + /** + * Sets the list of available effects that will be listed. + * For example: + * ` + * const char* const lightEffects[] = {"Fire","Rainbow","Snowflales","Rain","Smoke"}; + * light.setEffects(lightEffects, 5); + * ` + * + * + * @param effects The list of effects i.e. array of strings. + * @param size The size of the effects list i.e. total number of effects. + * @note The effects list can be set only once. + */ + void setEffects(const char* const effects[], const uint8_t size); + + /** + * Changes the effect of the light and publishes MQTT message. + * Effect represents the index of the effect that was set using setEffects method. + * Please note that if a new value is the same as previous one, + * the MQTT message won't be published. + * + * @param effect The new effect index of the light. + * @param force Forces to update the value without comparing it to a previous known value. + * @return Returns `true` if the effect is set successfully. + */ + bool setEffect(const uint8_t effect, const bool force = false); + /** * Alias for `setState(true)`. */ @@ -202,6 +234,24 @@ class HALight : public HABaseDeviceType inline const RGBColor& getCurrentRGBColor() const { return _currentRGBColor; } + /** + * Sets the current effect of the light without pushing the value to Home Assistant. + * This method may be useful if you want to change the effect before the connection + * with the MQTT broker is acquired. + * + * @param effect The new effect. + */ + inline void setCurrentEffect(const uint8_t effect) + { _currentEffect = effect; } + + /** + * Returns the last known effect of the light. + * Effect represents the index of the effect that was set using setEffects method. + * By default the effect is set to `0`. + */ + inline uint8_t getCurrentEffect() const + { return _currentEffect; } + /** * Sets icon of the light. * Any icon from MaterialDesignIcons.com (for example: `mdi:home`). @@ -297,6 +347,21 @@ class HALight : public HABaseDeviceType inline void onRGBColorCommand(HALIGHT_RGB_COLOR_CALLBACK(callback)) { _rgbColorCallback = callback; } + /** + * Registers callback that will be called each time the effect command from HA is received. + * Please note that it's not possible to register multiple callbacks for the same light. + * + * @param callback + * @note In non-optimistic mode, the effect must be reported back to HA using the HALight::setEffect method. + */ + inline void onEffectCommand(HALIGHT_EFFECT_CALLBACK(callback)) + { _effectCallback = callback; } + +#ifdef ARDUINOHA_TEST + inline HASerializerArray* getEffects() const + { return _effects; } +#endif + protected: virtual void buildSerializer() override; virtual void onMqttConnected() override; @@ -339,6 +404,14 @@ class HALight : public HABaseDeviceType */ bool publishRGBColor(const RGBColor& color); + /** + * Publishes the MQTT message with the given effect. + * + * @param effect The effect to publish. + * @returns Returns `true` if the MQTT message has been published successfully. + */ + bool publishEffect(const uint8_t effect); + /** * Parses the given state command and executes the callback with proper value. * @@ -371,6 +444,14 @@ class HALight : public HABaseDeviceType */ void handleRGBCommand(const uint8_t* cmd, const uint16_t length); + /** + * Parses the given effect command and executes the callback with proper value. + * + * @param cmd The data of the command. + * @param length Length of the command. + */ + void handleEffectCommand(const uint8_t* cmd, const uint16_t length); + /// Features enabled for the light. const uint8_t _features; @@ -404,6 +485,12 @@ class HALight : public HABaseDeviceType /// The current RBB color. By default the value is not set. RGBColor _currentRGBColor; + /// Array of effects for the serializer. + HASerializerArray* _effects; + + /// The current effect (the current effect's index). By default it's `0`. + uint8_t _currentEffect; + /// The callback that will be called when the state command is received from the HA. HALIGHT_STATE_CALLBACK(_stateCallback); @@ -415,6 +502,9 @@ class HALight : public HABaseDeviceType /// The callback that will be called when the RGB command is received from the HA. HALIGHT_RGB_COLOR_CALLBACK(_rgbColorCallback); + + /// The callback that will be called when the effect is received from the HA. + HALIGHT_EFFECT_CALLBACK(_effectCallback); }; #endif diff --git a/src/utils/HADictionary.cpp b/src/utils/HADictionary.cpp index 266d21dd..ba14aa9c 100644 --- a/src/utils/HADictionary.cpp +++ b/src/utils/HADictionary.cpp @@ -65,6 +65,7 @@ const char HASpeedRangeMinProperty[] PROGMEM = {"spd_rng_min"}; const char HABrightnessScaleProperty[] PROGMEM = {"bri_scl"}; const char HAMinMiredsProperty[] PROGMEM = {"min_mirs"}; const char HAMaxMiredsProperty[] PROGMEM = {"max_mirs"}; +const char HAEffectsProperty[] PROGMEM = {"fx_list"}; const char HATemperatureUnitProperty[] PROGMEM = {"temp_unit"}; const char HAMinTempProperty[] PROGMEM = {"min_temp"}; const char HAMaxTempProperty[] PROGMEM = {"max_temp"}; @@ -105,6 +106,8 @@ const char HATemperatureStateTopic[] PROGMEM = {"temp_stat_t"}; const char HARGBCommandTopic[] PROGMEM = {"rgb_cmd_t"}; const char HARGBStateTopic[] PROGMEM = {"rgb_stat_t"}; const char HAJsonAttributesTopic[] PROGMEM = {"json_attr_t"}; +const char HAEffectCommandTopic[] PROGMEM = {"fx_cmd_t"}; +const char HAEffectStateTopic[] PROGMEM = {"fx_stat_t"}; // misc const char HAOnline[] PROGMEM = {"online"}; diff --git a/src/utils/HADictionary.h b/src/utils/HADictionary.h index 609f38df..27527e06 100644 --- a/src/utils/HADictionary.h +++ b/src/utils/HADictionary.h @@ -65,6 +65,7 @@ extern const char HASpeedRangeMinProperty[]; extern const char HABrightnessScaleProperty[]; extern const char HAMinMiredsProperty[]; extern const char HAMaxMiredsProperty[]; +extern const char HAEffectsProperty[]; extern const char HATemperatureUnitProperty[]; extern const char HAMinTempProperty[]; extern const char HAMaxTempProperty[]; @@ -105,6 +106,8 @@ extern const char HATemperatureStateTopic[]; extern const char HARGBCommandTopic[]; extern const char HARGBStateTopic[]; extern const char HAJsonAttributesTopic[]; +extern const char HAEffectCommandTopic[]; +extern const char HAEffectStateTopic[]; // misc extern const char HAOnline[]; diff --git a/tests/LightTest/LightTest.ino b/tests/LightTest/LightTest.ino index cd2ce1a5..d1f4d49b 100644 --- a/tests/LightTest/LightTest.ino +++ b/tests/LightTest/LightTest.ino @@ -6,7 +6,8 @@ lastStateCallbackCall.reset(); \ lastBrightnessCallbackCall.reset(); \ lastColorTempCallbackCall.reset(); \ - lastRGBColorCallbackCall.reset(); + lastRGBColorCallbackCall.reset(); \ + lastEffectCallbackCall.reset(); #define assertStateCallbackCalled(expectedState, callerPtr) \ assertTrue(lastStateCallbackCall.called); \ @@ -41,6 +42,14 @@ #define assertRGBColorCallbackNotCalled() \ assertFalse(lastRGBColorCallbackCall.called); +#define assertEffectCallbackCalled(expectedIndex, callerPtr) \ + assertTrue(lastEffectCallbackCall.called); \ + assertEqual(expectedIndex, lastEffectCallbackCall.index); \ + assertEqual(callerPtr, lastEffectCallbackCall.caller); + +#define assertEffectCallbackNotCalled() \ + assertFalse(lastEffectCallbackCall.called); + using aunit::TestRunner; struct StateCallback { @@ -91,12 +100,25 @@ struct RGBCommandCallback { } }; +struct EffectCallback { + bool called = false; + uint8_t index = 0; + HALight* caller = nullptr; + + void reset() { + called = false; + index = 0; + caller = nullptr; + } +}; + static const char* testDeviceId = "testDevice"; static const char* testUniqueId = "uniqueLight"; static StateCallback lastStateCallbackCall; static BrightnessCallback lastBrightnessCallbackCall; static ColorTemperatureCallback lastColorTempCallbackCall; static RGBCommandCallback lastRGBColorCallbackCall; +static EffectCallback lastEffectCallbackCall; const char ConfigTopic[] PROGMEM = {"homeassistant/light/testDevice/uniqueLight/config"}; const char StateTopic[] PROGMEM = {"testData/testDevice/uniqueLight/stat_t"}; @@ -107,6 +129,8 @@ const char BrightnessCommandTopic[] PROGMEM = {"testData/testDevice/uniqueLight/ const char ColorTemperatureCommandTopic[] PROGMEM = {"testData/testDevice/uniqueLight/clr_temp_cmd_t"}; const char RGBCommandTopic[] PROGMEM = {"testData/testDevice/uniqueLight/rgb_cmd_t"}; const char RGBStateTopic[] PROGMEM = {"testData/testDevice/uniqueLight/rgb_stat_t"}; +const char EffectCommandTopic[] PROGMEM = {"testData/testDevice/uniqueLight/fx_cmd_t"}; +const char EffectStateTopic[] PROGMEM = {"testData/testDevice/uniqueLight/fx_stat_t"}; void onStateCommandReceived(bool state, HALight* caller) { @@ -136,6 +160,13 @@ void onRGBColorCommand(HALight::RGBColor color, HALight* caller) lastRGBColorCallbackCall.caller = caller; } +void onEffectCommandReceived(uint8_t index, HALight* caller) +{ + lastEffectCallbackCall.called = true; + lastEffectCallbackCall.index = index; + lastEffectCallbackCall.caller = caller; +} + AHA_TEST(LightTest, invalid_unique_id) { prepareTest @@ -278,6 +309,106 @@ AHA_TEST(LightTest, default_params_with_brightness_and_color_temp) { assertEqual(4, mock->getFlushedMessagesNb()); } +AHA_TEST(LightTest, default_params_with_no_effects_set) { + prepareTest + + HALight light(testUniqueId, HALight::EffectsFeature); + light.buildSerializerTest(); + HASerializer* serializer = light.getSerializer(); + + assertTrue(serializer == nullptr); + assertTrue(light.getEffects() == nullptr); +} + +AHA_TEST(LightTest, default_params_with_invalid_effects_disabled_feature) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + HALight light(testUniqueId); + light.setEffects(effects, 2); + light.buildSerializerTest(); + HASerializer* serializer = light.getSerializer(); + + assertTrue(serializer != nullptr); + assertTrue(light.getEffects() == nullptr); +} + +AHA_TEST(LightTest, default_params_with_invalid_effects_nullptr) { + prepareTest + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(nullptr, 1); + light.buildSerializerTest(); + HASerializer* serializer = light.getSerializer(); + + assertTrue(serializer == nullptr); + assertTrue(light.getEffects() == nullptr); +} + +AHA_TEST(LightTest, default_params_with_invalid_effects_zero_size) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 0); + light.buildSerializerTest(); + HASerializer* serializer = light.getSerializer(); + + assertTrue(serializer == nullptr); + assertTrue(light.getEffects() == nullptr); +} + +AHA_TEST(LightTest, default_params_with_effects_single_init) { + prepareTest + + const char* const effects[] = { "Effect 1" }; + const char* const notAbleToSetEffects[] = { "Effect 2" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 1); + light.setEffects(notAbleToSetEffects, 1); // effects already initialized at previous line + light.buildSerializerTest(); + HASerializer* serializer = light.getSerializer(); + + assertTrue(serializer != nullptr); + + HASerializerArray* lightEffects = light.getEffects(); + + assertTrue(lightEffects != nullptr); + assertEqual(1, lightEffects->getItemsNb()); + assertEqual(0, strcmp(effects[0], lightEffects->getItems()[0])); +} + +AHA_TEST(LightTest, default_params_with_effects) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 2); + + assertEntityConfig( + mock, + light, + ( + "{" + "\"uniq_id\":\"uniqueLight\"," + "\"fx_stat_t\":\"testData/testDevice/uniqueLight/fx_stat_t\"," + "\"fx_cmd_t\":\"testData/testDevice/uniqueLight/fx_cmd_t\"," + "\"fx_list\":[\"Effect 1\",\"Effect 2\"]," + "\"dev\":{\"ids\":\"testDevice\"}," + "\"stat_t\":\"testData/testDevice/uniqueLight/stat_t\"," + "\"cmd_t\":\"testData/testDevice/uniqueLight/cmd_t\"" + "}" + ) + ) + + // config + default state + default effect + assertEqual(3, mock->getFlushedMessagesNb()); +} + AHA_TEST(LightTest, state_command_subscription) { prepareTest @@ -330,6 +461,22 @@ AHA_TEST(LightTest, rgb_command_subscription) { ); } +AHA_TEST(LightTest, effect_command_subscription) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 2); + mqtt.loop(); + + assertEqual(2, mock->getSubscriptionsNb()); + assertEqual( + AHATOFSTR(EffectCommandTopic), + mock->getSubscriptions()[1]->topic + ); +} + AHA_TEST(LightTest, availability) { prepareTest @@ -390,6 +537,20 @@ AHA_TEST(LightTest, publish_last_known_rgb_color) { assertMqttMessage(2, AHATOFSTR(RGBStateTopic), "255,123,15", true) } +AHA_TEST(LightTest, publish_last_known_effect) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 2); + light.setCurrentEffect(1); + mqtt.loop(); + + assertEqual(3, mock->getFlushedMessagesNb()); + assertMqttMessage(2, AHATOFSTR(EffectStateTopic), "Effect 2", true) +} + AHA_TEST(LightTest, publish_nothing_if_retained) { prepareTest @@ -636,6 +797,16 @@ AHA_TEST(LightTest, current_rgb_color_setter) { assertTrue(HALight::RGBColor(255,123,111) == light.getCurrentRGBColor()); } +AHA_TEST(LightTest, current_effect_setter) { + prepareTest + + HALight light(testUniqueId); + light.setCurrentEffect(3); + + assertEqual(0, mock->getFlushedMessagesNb()); + assertEqual((uint8_t)3, light.getCurrentEffect()); +} + AHA_TEST(LightTest, publish_state) { prepareTest @@ -788,6 +959,89 @@ AHA_TEST(LightTest, publish_rgb_color_debounce_skip) { assertSingleMqttMessage(AHATOFSTR(RGBStateTopic), "255,123,111", true) } +AHA_TEST(LightTest, publish_nothing_if_effects_feature_is_disabled) { + prepareTest + + mock->connectDummy(); + HALight light(testUniqueId); + + assertFalse(light.setEffect(5)); + assertEqual(mock->getFlushedMessagesNb(), 0); +} + +AHA_TEST(LightTest, publish_effect_first) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2", "Effect 3" }; + + mock->connectDummy(); + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + + assertTrue(light.setEffect(0)); + // First effect is a default one, shouldn't publish + assertEqual(mock->getFlushedMessagesNb(), 0); +} + +AHA_TEST(LightTest, publish_effect_last) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2", "Effect 3" }; + + mock->connectDummy(); + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + + assertTrue(light.setEffect(2)); + assertSingleMqttMessage(AHATOFSTR(EffectStateTopic), "Effect 3", true) +} + +AHA_TEST(LightTest, publish_effect_only) { + prepareTest + + const char* const effects[] = { "Effect 1" }; + + mock->connectDummy(); + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 1); + + assertTrue(light.setEffect(0)); + // First effect is a default one, shouldn't publish + assertEqual(mock->getFlushedMessagesNb(), 0); +} + +AHA_TEST(LightTest, publish_effect_debounce) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + mock->connectDummy(); + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 2); + light.setCurrentEffect(1); + + // it shouldn't publish data if value doesn't change + assertTrue(light.setEffect(1)); + assertEqual(mock->getFlushedMessagesNb(), 0); +} + +AHA_TEST(LightTest, publish_effect_debounce_skip) { + prepareTest + + const char* const effects[] = { "Effect 1", "Effect 2" }; + + mock->connectDummy(); + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 2); + light.setCurrentEffect(1); + + assertTrue(light.setEffect(1, true)); + assertSingleMqttMessage(AHATOFSTR(EffectStateTopic), "Effect 2", true) +} + AHA_TEST(LightTest, state_command_on) { prepareTest @@ -1040,6 +1294,74 @@ AHA_TEST(LightTest, rgb_color_command_different_light) { assertRGBColorCallbackNotCalled() } +AHA_TEST(LightTest, effect_command) { + prepareTest + + const char* const effects[] = { "Fire", "Rainbow", "Snowflakes" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + light.onEffectCommand(onEffectCommandReceived); + mock->fakeMessage(AHATOFSTR(EffectCommandTopic), F("Rainbow")); + + assertEffectCallbackCalled(1, &light) +} + +AHA_TEST(LightTest, effect_command_first) { + prepareTest + + const char* const effects[] = { "Fire", "Rainbow", "Snowflakes" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + light.onEffectCommand(onEffectCommandReceived); + mock->fakeMessage(AHATOFSTR(EffectCommandTopic), F("Fire")); + + assertEffectCallbackCalled(0, &light) +} + +AHA_TEST(LightTest, effect_command_last) { + prepareTest + + const char* const effects[] = { "Fire", "Rainbow", "Snowflakes" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + light.onEffectCommand(onEffectCommandReceived); + mock->fakeMessage(AHATOFSTR(EffectCommandTopic), F("Snowflakes")); + + assertEffectCallbackCalled(2, &light) +} + +AHA_TEST(LightTest, effect_command_non_existing) { + prepareTest + + const char* const effects[] = { "Fire", "Rainbow", "Snowflakes" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + light.onEffectCommand(onEffectCommandReceived); + mock->fakeMessage(AHATOFSTR(EffectCommandTopic), F("NON_EXISTING")); + + assertEffectCallbackNotCalled() +} + +AHA_TEST(LightTest, effect_command_different_light) { + prepareTest + + const char* const effects[] = { "Fire", "Rainbow", "Snowflakes" }; + + HALight light(testUniqueId, HALight::EffectsFeature); + light.setEffects(effects, 3); + light.onEffectCommand(onEffectCommandReceived); + mock->fakeMessage( + F("testData/testDevice/uniqueLightDifferent/fx_cmd_t"), + F("Fire") + ); + + assertEffectCallbackNotCalled() +} + void setup() { delay(1000); From dcdc4373311d55c034e3126343622aeb2066d0de Mon Sep 17 00:00:00 2001 From: Kostiantyn Synytsia <20212876+martaisty@users.noreply.github.com> Date: Mon, 29 May 2023 00:31:30 +0300 Subject: [PATCH 2/3] added light effects example --- examples/light/light.ino | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/light/light.ino b/examples/light/light.ino index c0d7c611..4117e920 100644 --- a/examples/light/light.ino +++ b/examples/light/light.ino @@ -11,9 +11,21 @@ HAMqtt mqtt(client, device); // HALight::BrightnessFeature enables support for setting brightness of the light. // HALight::ColorTemperatureFeature enables support for setting color temperature of the light. -// Both features are optional and you can remove them if they're not needed. +// HALight::EffectsFeature enables support for setting effect of the light. +// All features are optional, and you can remove them if they're not needed. // "prettyLight" is unique ID of the light. You should define your own ID. -HALight light("prettyLight", HALight::BrightnessFeature | HALight::ColorTemperatureFeature | HALight::RGBFeature); +HALight light("prettyLight", HALight::BrightnessFeature | + HALight::ColorTemperatureFeature | + HALight::RGBFeature | + HALight::EffectsFeature); + +const char* const lightEffects[] = { + "Fire", + "Rainbow", + "Polar light", + "Rain", + "Smoke" +}; void onStateCommand(bool state, HALight* sender) { Serial.print("State: "); @@ -47,6 +59,15 @@ void onRGBColorCommand(HALight::RGBColor color, HALight* sender) { sender->setRGBColor(color); // report color back to the Home Assistant } +void onEffectCommand(uint8_t index, HALight* sender) { + Serial.print("Effect index: "); + Serial.println(index); + Serial.print("Effect name: "); + Serial.println(lightEffects[index]); + + sender->setEffect(index); // report effect back to the Home Assistant +} + void setup() { Serial.begin(9600); @@ -74,11 +95,15 @@ void setup() { // light.setMinMireds(50); // light.setMaxMireds(200); + // Light effects (mandatory if HALight::EffectsFeature is set, optional otherwise) + light.setEffects(lightEffects, 5); + // handle light states light.onStateCommand(onStateCommand); light.onBrightnessCommand(onBrightnessCommand); // optional light.onColorTemperatureCommand(onColorTemperatureCommand); // optional light.onRGBColorCommand(onRGBColorCommand); // optional + light.onEffectCommand(onEffectCommand); // optional mqtt.begin(BROKER_ADDR); } From 2b08ebe63d65ad8be773289c302aefb31769d518 Mon Sep 17 00:00:00 2001 From: Kostiantyn Synytsia <20212876+martaisty@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:09:40 +0300 Subject: [PATCH 3/3] add documentation for light effects --- docs/documents/api/device-types/ha-light.html | 125 +++++++++++++++++- docs/genindex.html | 28 +++- docs/objects.inv | Bin 33483 -> 33920 bytes docs/searchindex.js | 2 +- 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/docs/documents/api/device-types/ha-light.html b/docs/documents/api/device-types/ha-light.html index 9369dc5c..a9f4f5de 100644 --- a/docs/documents/api/device-types/ha-light.html +++ b/docs/documents/api/device-types/ha-light.html @@ -154,7 +154,7 @@

HALight class
class HALight : public HABaseDeviceType
-

HALight allows adding a controllable light in the Home Assistant panel. The library supports only the state, brightness, color temperature and RGB color. If you need more features please open a new GitHub issue.

+

HALight allows adding a controllable light in the Home Assistant panel. The library supports only the state, brightness, color temperature, RGB color and effects. If you need more features please open a new GitHub issue.

Note

You can find more information about this entity in the Home Assistant documentation: https://www.home-assistant.io/integrations/light.mqtt/

@@ -185,6 +185,11 @@

HALight classenumerator RGBFeature
+
+
+enumerator EffectsFeature
+
+

@@ -203,6 +208,11 @@

HALight class +
+~HALight()
+
+
bool setState(const bool state, const bool force = false)
@@ -271,6 +281,41 @@

HALight class +
+void setEffects(const char *const effects[], const uint8_t size)
+

Sets the list of available effects that will be listed. For example: const char* const lightEffects[] = {"Fire","Rainbow","Snowflales","Rain","Smoke"}; light.setEffects(lightEffects, 5);

+
+

Note

+

The effects list can be set only once.

+
+
+
Parameters
+
    +
  • effects – The list of effects i.e. array of strings.

  • +
  • size – The size of the effects list i.e. total number of effects.

  • +
+
+
+

+ +
+
+bool setEffect(const uint8_t effect, const bool force = false)
+

Changes the effect of the light and publishes MQTT message. Effect represents the index of the effect that was set using setEffects method. Please note that if a new value is the same as previous one, the MQTT message won’t be published.

+
+
Parameters
+
    +
  • effect – The new effect index of the light.

  • +
  • force – Forces to update the value without comparing it to a previous known value.

  • +
+
+
Returns
+

Returns true if the effect is set successfully.

+
+
+
+
inline bool turnOn()
@@ -351,6 +396,23 @@

HALight class0,0,0.

+
+
+inline void setCurrentEffect(const uint8_t effect)
+

Sets the current effect of the light without pushing the value to Home Assistant. This method may be useful if you want to change the effect before the connection with the MQTT broker is acquired.

+
+
Parameters
+

effect – The new effect.

+
+
+
+ +
+
+inline uint8_t getCurrentEffect() const
+

Returns the last known effect of the light. Effect represents the index of the effect that was set using setEffects method. By default the effect is set to 0.

+
+
inline void setIcon(const char *icon)
@@ -477,6 +539,21 @@

HALight class +
+inline void onEffectCommand(void (*callback)(uint8_t index, HALight *sender))
+

Registers callback that will be called each time the effect command from HA is received. Please note that it’s not possible to register multiple callbacks for the same light.

+
+

Note

+

In non-optimistic mode, the effect must be reported back to HA using the HALight::setEffect method.

+
+
+
Parameters
+

callback

+
+
+

+

Public Static Attributes

@@ -562,6 +639,20 @@

HALight class +
+bool publishEffect(const uint8_t effect)
+

Publishes the MQTT message with the given effect.

+
+
Parameters
+

effect – The effect to publish.

+
+
Returns
+

Returns true if the MQTT message has been published successfully.

+
+
+
+
void handleStateCommand(const uint8_t *cmd, const uint16_t length)
@@ -618,6 +709,20 @@

HALight class +
+void handleEffectCommand(const uint8_t *cmd, const uint16_t length)
+

Parses the given effect command and executes the callback with proper value.

+
+
Parameters
+
    +
  • cmd – The data of the command.

  • +
  • length – Length of the command.

  • +
+
+
+

+

Private Members

@@ -687,6 +792,18 @@

HALight class +
+HASerializerArray *_effects
+

Array of effects for the serializer.

+
+ +
+
+uint8_t _currentEffect
+

The current effect (the current effect’s index). By default it’s 0.

+
+
void (*_stateCallback)(bool state, HALight *sender)
@@ -711,6 +828,12 @@

HALight class +
+void (*_effectCallback)(uint8_t index, HALight *sender)
+

The callback that will be called when the effect is received from the HA.

+

+

diff --git a/docs/genindex.html b/docs/genindex.html index 2b8fd4c3..9b957ad8 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -892,23 +892,29 @@

H

  • HALight::_currentBrightness (C++ member)
  • HALight::_currentColorTemperature (C++ member) +
  • +
  • HALight::_currentEffect (C++ member)
  • HALight::_currentRGBColor (C++ member)
  • HALight::_currentState (C++ member) +
  • +
  • HALight::_effectCallback (C++ member) +
  • +
  • HALight::_effects (C++ member)
  • HALight::_features (C++ member)
  • HALight::_icon (C++ member)
  • - - +
    • HALight::_retain (C++ member)
    • HALight::_rgbColorCallback (C++ member) @@ -924,12 +930,16 @@

      H

    • HALight::Features::ColorTemperatureFeature (C++ enumerator)
    • HALight::Features::DefaultFeatures (C++ enumerator) +
    • +
    • HALight::Features::EffectsFeature (C++ enumerator)
    • HALight::Features::RGBFeature (C++ enumerator)
    • HALight::getCurrentBrightness (C++ function)
    • HALight::getCurrentColorTemperature (C++ function) +
    • +
    • HALight::getCurrentEffect (C++ function)
    • HALight::getCurrentRGBColor (C++ function)
    • @@ -940,6 +950,8 @@

      H

    • HALight::handleBrightnessCommand (C++ function)
    • HALight::handleColorTemperatureCommand (C++ function) +
    • +
    • HALight::handleEffectCommand (C++ function)
    • HALight::handleRGBCommand (C++ function)
    • @@ -948,6 +960,8 @@

      H

    • HALight::onBrightnessCommand (C++ function)
    • HALight::onColorTemperatureCommand (C++ function) +
    • +
    • HALight::onEffectCommand (C++ function)
    • HALight::onMqttConnected (C++ function)
    • @@ -960,6 +974,8 @@

      H

    • HALight::publishBrightness (C++ function)
    • HALight::publishColorTemperature (C++ function) +
    • +
    • HALight::publishEffect (C++ function)
    • HALight::publishRGBColor (C++ function)
    • @@ -996,10 +1012,16 @@

      H

    • HALight::setCurrentBrightness (C++ function)
    • HALight::setCurrentColorTemperature (C++ function) +
    • +
    • HALight::setCurrentEffect (C++ function)
    • HALight::setCurrentRGBColor (C++ function)
    • HALight::setCurrentState (C++ function) +
    • +
    • HALight::setEffect (C++ function) +
    • +
    • HALight::setEffects (C++ function)
    • HALight::setIcon (C++ function)
    • @@ -1018,6 +1040,8 @@

      H

    • HALight::turnOff (C++ function)
    • HALight::turnOn (C++ function) +
    • +
    • HALight::~HALight (C++ function)
    • HALock (C++ class)
    • diff --git a/docs/objects.inv b/docs/objects.inv index 3f54edf74ac0495438e5a61062788c481877267c..0f26bc83701ac7e576f6733aec24017e556fa44e 100644 GIT binary patch delta 27348 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 L0`&g}@R+|l%8Lib delta 26908 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