mirror of
https://github.com/alexhopeoconnor/arduino-home-assistant.git
synced 2026-10-04 02:48:13 +10:00
Fix HASelect long-options parsing and add dynamic HANumber config updates.
This prevents overflow-related crashes when select options exceed 255 characters, adds runtime min/max/step updates that republish discovery config, and includes related HAText compilation fixes with test coverage.
This commit is contained in:
@@ -37,6 +37,14 @@ bool HANumber::setState(const HANumeric& state, const bool force)
|
||||
return false;
|
||||
}
|
||||
|
||||
void HANumber::updateMinMaxStep(const float min, const float max, const float step)
|
||||
{
|
||||
_minValue = HANumeric(min, _precision);
|
||||
_maxValue = HANumeric(max, _precision);
|
||||
_step = HANumeric(step, _precision);
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
void HANumber::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId()) {
|
||||
|
||||
@@ -189,6 +189,16 @@ public:
|
||||
inline void setStep(const float step)
|
||||
{ _step = HANumeric(step, _precision); }
|
||||
|
||||
/**
|
||||
* Updates min/max/step and republishes discovery config.
|
||||
* This can be used when limits need to change at runtime.
|
||||
*
|
||||
* @param min The minimal value.
|
||||
* @param max The maximal value.
|
||||
* @param step The slider step value.
|
||||
*/
|
||||
void updateMinMaxStep(const float min, const float max, const float step);
|
||||
|
||||
/**
|
||||
* Registers callback that will be called each time the number is changed in the HA panel.
|
||||
* Please note that it's not possible to register multiple callbacks for the same number.
|
||||
|
||||
@@ -54,7 +54,7 @@ void HASelect::setOptions(const char* options)
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t optionLen = 0;
|
||||
uint16_t optionLen = 0;
|
||||
for (uint16_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';' || options[i] == 0) {
|
||||
if (optionLen == 0) {
|
||||
@@ -65,7 +65,10 @@ void HASelect::setOptions(const char* options)
|
||||
option[optionLen] = 0;
|
||||
memcpy(option, &options[i - optionLen], optionLen);
|
||||
|
||||
_options->add(option);
|
||||
if (!_options->add(option)) {
|
||||
delete[] option;
|
||||
break;
|
||||
}
|
||||
optionLen = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -213,8 +216,12 @@ uint8_t HASelect::countOptionsInString(const char* options) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < optionsLen; i++) {
|
||||
for (uint16_t i = 0; i < optionsLen; i++) {
|
||||
if (options[i] == ';') {
|
||||
if (optionsNb == 255) {
|
||||
break;
|
||||
}
|
||||
|
||||
optionsNb++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ HAText::HAText(const char* uniqueId) :
|
||||
_retain(false),
|
||||
_optimistic(false),
|
||||
_mode(ModeText),
|
||||
_min(),
|
||||
_max(),
|
||||
_minValue(),
|
||||
_maxValue(),
|
||||
_pattern(nullptr),
|
||||
_currentState(nullptr),
|
||||
_commandCallback(nullptr)
|
||||
@@ -62,18 +62,18 @@ void HAText::buildSerializer()
|
||||
);
|
||||
_serializer->set(AHATOFSTR(HAPatternProperty), _pattern);
|
||||
|
||||
if (_min.isSet()) {
|
||||
if (_minValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMinProperty),
|
||||
&_min,
|
||||
&_minValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
if (_max.isSet()) {
|
||||
if (_maxValue.isSet()) {
|
||||
_serializer->set(
|
||||
AHATOFSTR(HAMaxProperty),
|
||||
&_max,
|
||||
&_maxValue,
|
||||
HASerializer::NumberPropertyType
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
* @param min Minimum number of characters.
|
||||
*/
|
||||
inline void setMin(const uint16_t min)
|
||||
{ _min = HANumeric(min, PrecisionP0); }
|
||||
{ _minValue = HANumeric(min, PrecisionP0); }
|
||||
|
||||
/**
|
||||
* Sets maximum accepted length of the command payload.
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
* @param max Maximum number of characters.
|
||||
*/
|
||||
inline void setMax(const uint16_t max)
|
||||
{ _max = HANumeric(max, PrecisionP0); }
|
||||
{ _maxValue = HANumeric(max, PrecisionP0); }
|
||||
|
||||
/**
|
||||
* Sets regex pattern for command payload validation in Home Assistant.
|
||||
@@ -186,10 +186,10 @@ private:
|
||||
Mode _mode;
|
||||
|
||||
/// The minimal number of characters accepted by Home Assistant.
|
||||
HANumeric _min;
|
||||
HANumeric _minValue;
|
||||
|
||||
/// The maximum number of characters accepted by Home Assistant.
|
||||
HANumeric _max;
|
||||
HANumeric _maxValue;
|
||||
|
||||
/// Regular expression pattern accepted by Home Assistant. It can be nullptr.
|
||||
const char* _pattern;
|
||||
|
||||
@@ -785,6 +785,57 @@ AHA_TEST(NumberTest, step_setter_p3) {
|
||||
)
|
||||
}
|
||||
|
||||
AHA_TEST(NumberTest, update_min_max_step_before_connect) {
|
||||
prepareTest
|
||||
|
||||
HANumber number(testUniqueId);
|
||||
number.updateMinMaxStep(1, 99, 5);
|
||||
|
||||
assertEntityConfig(
|
||||
mock,
|
||||
number,
|
||||
(
|
||||
"{"
|
||||
"\"uniq_id\":\"uniqueNumber\","
|
||||
"\"min\":1,"
|
||||
"\"max\":99,"
|
||||
"\"step\":5,"
|
||||
"\"dev\":{\"ids\":\"testDevice\"},"
|
||||
"\"stat_t\":\"testData/testDevice/uniqueNumber/stat_t\","
|
||||
"\"cmd_t\":\"testData/testDevice/uniqueNumber/cmd_t\""
|
||||
"}"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
AHA_TEST(NumberTest, update_min_max_step_republishes_config) {
|
||||
prepareTest
|
||||
|
||||
HANumber number(testUniqueId);
|
||||
mqtt.loop();
|
||||
assertEqual(2, mock->getFlushedMessagesNb()); // config + default state
|
||||
|
||||
number.updateMinMaxStep(1, 99, 5);
|
||||
|
||||
assertEqual(3, mock->getFlushedMessagesNb());
|
||||
assertMqttMessage(
|
||||
2,
|
||||
AHATOFSTR(ConfigTopic),
|
||||
(
|
||||
"{"
|
||||
"\"uniq_id\":\"uniqueNumber\","
|
||||
"\"min\":1,"
|
||||
"\"max\":99,"
|
||||
"\"step\":5,"
|
||||
"\"dev\":{\"ids\":\"testDevice\"},"
|
||||
"\"stat_t\":\"testData/testDevice/uniqueNumber/stat_t\","
|
||||
"\"cmd_t\":\"testData/testDevice/uniqueNumber/cmd_t\""
|
||||
"}"
|
||||
),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
AHA_TEST(NumberTest, command_none) {
|
||||
prepareTest
|
||||
|
||||
|
||||
@@ -157,6 +157,26 @@ AHA_TEST(SelectTest, multiple_options) {
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, long_options_string_over_255_chars) {
|
||||
prepareTest
|
||||
|
||||
char options[273];
|
||||
for (uint16_t i = 0; i < 270; i++) {
|
||||
options[i] = 'A';
|
||||
}
|
||||
|
||||
options[270] = ';';
|
||||
options[271] = 'B';
|
||||
options[272] = 0;
|
||||
|
||||
HASelect select(testUniqueId);
|
||||
select.setOptions(options);
|
||||
|
||||
assertTrue(select.getOptions() != nullptr);
|
||||
assertEqual((uint8_t)2, select.getOptions()->getItemsNb());
|
||||
assertEqual("B", select.getOptions()->getItem(1));
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, command_subscription) {
|
||||
prepareTest
|
||||
|
||||
|
||||
Reference in New Issue
Block a user