mirror of
https://github.com/alexhopeoconnor/arduino-home-assistant.git
synced 2026-10-04 02:48:13 +10:00
HASelect improvements (#221)
* added getCurrentOption method to the HASelect * final fixes, tests * added support for publishing None state in the HASelect * update select.ino * update docs
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
* Added support for the `state_class` property in the `HASensor` ([#179](https://github.com/dawidchyrzynski/arduino-home-assistant/pull/179) by [@https://github.com/Starzu])
|
||||
* Implemented extended unique ID support for all device types. This allows you to prefix each device type's unique ID with the device ID, ensuring smooth deployment of identical code on multiple devices without encountering unique ID conflicts [#212](https://github.com/dawidchyrzynski/arduino-home-assistant/issues/212#issuecomment-1919832684)
|
||||
* Added support for the `configuration_url` parameter in the `HADevice` [#182](https://github.com/dawidchyrzynski/arduino-home-assistant/issues/182)
|
||||
* Added `getCurrentOption` method to the `HASelect` device type [#163](https://github.com/dawidchyrzynski/arduino-home-assistant/issues/163)
|
||||
* Added support for publishing `None` state in the `HASelect` device type [#146](https://github.com/dawidchyrzynski/arduino-home-assistant/issues/146)
|
||||
|
||||
**Fixes:**
|
||||
* Fixed a bug with the maximum number of device types ([#190](https://github.com/dawidchyrzynski/arduino-home-assistant/issues/190) by [@martaisty](https://github.com/martaisty))
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>state</strong> – New state of the select. </p></li>
|
||||
<li><p><strong>state</strong> – New state of the select. You can set <code class="docutils literal notranslate"><span class="pre">-1</span></code> to reset the select. </p></li>
|
||||
<li><p><strong>force</strong> – Forces to update state without comparing it to previous known state. </p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
@@ -208,6 +208,12 @@
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="cpp function">
|
||||
<dt class="sig sig-object cpp" id="_CPPv4NK8HASelect16getCurrentOptionEv">
|
||||
<span id="_CPPv3NK8HASelect16getCurrentOptionEv"></span><span id="_CPPv2NK8HASelect16getCurrentOptionEv"></span><span id="HASelect::getCurrentOptionC"></span><span class="target" id="class_h_a_select_1adadff59b86c066136d88188618fbf485"></span><span class="k"><span class="pre">const</span></span><span class="w"> </span><span class="kt"><span class="pre">char</span></span><span class="w"> </span><span class="p"><span class="pre">*</span></span><span class="sig-name descname"><span class="n"><span class="pre">getCurrentOption</span></span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><span class="w"> </span><span class="k"><span class="pre">const</span></span><a class="headerlink" href="#_CPPv4NK8HASelect16getCurrentOptionEv" title="Permalink to this definition">¶</a><br /></dt>
|
||||
<dd><p>Returns the selected option based on the most recent state of the select. You can utilize this method to get the string representation of the option (e.g. for printing). If no option is selected, null is returned. </p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="cpp function">
|
||||
<dt class="sig sig-object cpp" id="_CPPv4N8HASelect15setCurrentStateEK6int8_t">
|
||||
<span id="_CPPv3N8HASelect15setCurrentStateEK6int8_t"></span><span id="_CPPv2N8HASelect15setCurrentStateEK6int8_t"></span><span id="HASelect::setCurrentState__int8_tC"></span><span class="target" id="class_h_a_select_1a3ce2a5372c12323e5bd1a29aada88aee"></span><span class="k"><span class="pre">inline</span></span><span class="w"> </span><span class="kt"><span class="pre">void</span></span><span class="w"> </span><span class="sig-name descname"><span class="n"><span class="pre">setCurrentState</span></span></span><span class="sig-paren">(</span><span class="k"><span class="pre">const</span></span><span class="w"> </span><span class="n"><span class="pre">int8_t</span></span><span class="w"> </span><span class="n sig-param"><span class="pre">state</span></span><span class="sig-paren">)</span><a class="headerlink" href="#_CPPv4N8HASelect15setCurrentStateEK6int8_t" title="Permalink to this definition">¶</a><br /></dt>
|
||||
|
||||
@@ -210,6 +210,12 @@
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="cpp function">
|
||||
<dt class="sig sig-object cpp" id="_CPPv4NK17HASerializerArray7getItemEK7uint8_t">
|
||||
<span id="_CPPv3NK17HASerializerArray7getItemEK7uint8_t"></span><span id="_CPPv2NK17HASerializerArray7getItemEK7uint8_t"></span><span id="HASerializerArray::getItem__uint8_tCC"></span><span class="target" id="class_h_a_serializer_array_1ad5f99f2ffc0ee2e08ada23e1487f89c4"></span><span class="k"><span class="pre">const</span></span><span class="w"> </span><span class="kt"><span class="pre">char</span></span><span class="w"> </span><span class="p"><span class="pre">*</span></span><span class="sig-name descname"><span class="n"><span class="pre">getItem</span></span></span><span class="sig-paren">(</span><span class="k"><span class="pre">const</span></span><span class="w"> </span><span class="n"><span class="pre">uint8_t</span></span><span class="w"> </span><span class="n sig-param"><span class="pre">index</span></span><span class="sig-paren">)</span><span class="w"> </span><span class="k"><span class="pre">const</span></span><a class="headerlink" href="#_CPPv4NK17HASerializerArray7getItemEK7uint8_t" title="Permalink to this definition">¶</a><br /></dt>
|
||||
<dd><p>Returns a pointer to the item at the given index. If the element doesn’t exist, null is returned. </p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="cpp function">
|
||||
<dt class="sig sig-object cpp" id="_CPPv4NK17HASerializerArray13calculateSizeEv">
|
||||
<span id="_CPPv3NK17HASerializerArray13calculateSizeEv"></span><span id="_CPPv2NK17HASerializerArray13calculateSizeEv"></span><span id="HASerializerArray::calculateSizeC"></span><span class="target" id="class_h_a_serializer_array_1acd7c86423ae198910ae141002771c847"></span><span class="n"><span class="pre">uint16_t</span></span><span class="w"> </span><span class="sig-name descname"><span class="n"><span class="pre">calculateSize</span></span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><span class="w"> </span><span class="k"><span class="pre">const</span></span><a class="headerlink" href="#_CPPv4NK17HASerializerArray13calculateSizeEv" title="Permalink to this definition">¶</a><br /></dt>
|
||||
|
||||
+6
-2
@@ -867,10 +867,10 @@
|
||||
</li>
|
||||
<li><a href="documents/api/device-types/ha-hvac.html#_CPPv4N6HAHVAC15TemperatureUnit11DefaultUnitE">HAHVAC::TemperatureUnit::DefaultUnit (C++ enumerator)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="documents/api/device-types/ha-hvac.html#_CPPv4N6HAHVAC15TemperatureUnit14FahrenheitUnitE">HAHVAC::TemperatureUnit::FahrenheitUnit (C++ enumerator)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="documents/api/device-types/ha-hvac.html#_CPPv4N6HAHVACD0Ev">HAHVAC::~HAHVAC (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/device-types/ha-light.html#_CPPv47HALight">HALight (C++ class)</a>
|
||||
@@ -1330,6 +1330,8 @@
|
||||
<li><a href="documents/api/device-types/ha-select.html#_CPPv4N8HASelect15buildSerializerEv">HASelect::buildSerializer (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/device-types/ha-select.html#_CPPv4NK8HASelect20countOptionsInStringEPKc">HASelect::countOptionsInString (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/device-types/ha-select.html#_CPPv4NK8HASelect16getCurrentOptionEv">HASelect::getCurrentOption (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/device-types/ha-select.html#_CPPv4NK8HASelect15getCurrentStateEv">HASelect::getCurrentState (C++ function)</a>
|
||||
</li>
|
||||
@@ -1520,6 +1522,8 @@
|
||||
<li><a href="documents/api/utils/ha-serializer-array.html#_CPPv4NK17HASerializerArray13calculateSizeEv">HASerializerArray::calculateSize (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/utils/ha-serializer-array.html#_CPPv4N17HASerializerArray5clearEv">HASerializerArray::clear (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/utils/ha-serializer-array.html#_CPPv4NK17HASerializerArray7getItemEK7uint8_t">HASerializerArray::getItem (C++ function)</a>
|
||||
</li>
|
||||
<li><a href="documents/api/utils/ha-serializer-array.html#_CPPv4NK17HASerializerArray8getItemsEv">HASerializerArray::getItems (C++ function)</a>
|
||||
</li>
|
||||
|
||||
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ byte mac[] = {0x00, 0x10, 0xFA, 0x6E, 0x38, 0x4A};
|
||||
EthernetClient client;
|
||||
HADevice device(mac, sizeof(mac));
|
||||
HAMqtt mqtt(client, device);
|
||||
HASelect select("mySelect");
|
||||
HASelect mySelect("mySelect");
|
||||
|
||||
void onSelectCommand(int8_t index, HASelect* sender)
|
||||
{
|
||||
@@ -31,6 +31,12 @@ void onSelectCommand(int8_t index, HASelect* sender)
|
||||
}
|
||||
|
||||
sender->setState(index); // report the selected option back to the HA panel
|
||||
|
||||
// it may return null
|
||||
if (sender->getCurrentOption()) {
|
||||
Serial.print("Current option: ");
|
||||
Serial.println(sender->getCurrentOption());
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
@@ -42,18 +48,18 @@ void setup() {
|
||||
device.setSoftwareVersion("1.0.0");
|
||||
|
||||
// set available options
|
||||
select.setOptions("Low;Medium;High"); // use semicolons as separator of options
|
||||
select.onCommand(onSelectCommand);
|
||||
mySelect.setOptions("Low;Medium;High"); // use semicolons as separator of options
|
||||
mySelect.onCommand(onSelectCommand);
|
||||
|
||||
select.setIcon("mdi:home"); // optional
|
||||
select.setName("My dropdown"); // optional
|
||||
mySelect.setIcon("mdi:home"); // optional
|
||||
mySelect.setName("My dropdown"); // optional
|
||||
|
||||
// Optionally you can set retain flag for the HA commands
|
||||
// select.setRetain(true);
|
||||
// mySelect.setRetain(true);
|
||||
|
||||
// Optionally you can enable optimistic mode for the HASelect.
|
||||
// In this mode you won't need to report state back to the HA when commands are executed.
|
||||
// select.setOptimistic(true);
|
||||
// mySelect.setOptimistic(true);
|
||||
|
||||
mqtt.begin(BROKER_ADDR);
|
||||
}
|
||||
@@ -64,5 +70,8 @@ void loop() {
|
||||
|
||||
// You can also report the state to the HA panel at runtime as shown below.
|
||||
// The integer corresponds to the option's index.
|
||||
// select.setState(1);
|
||||
// mySelect.setState(1);
|
||||
|
||||
// You can also reset the select as follows:
|
||||
// mySelect.setState(-1);
|
||||
}
|
||||
@@ -85,6 +85,11 @@ bool HASelect::setState(const int8_t state, const bool force)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* HASelect::getCurrentOption() const
|
||||
{
|
||||
return _options->getItem(getCurrentState());
|
||||
}
|
||||
|
||||
void HASelect::buildSerializer()
|
||||
{
|
||||
if (_serializer || !uniqueId() || !_options) {
|
||||
@@ -164,10 +169,14 @@ void HASelect::onMqttMessage(
|
||||
|
||||
bool HASelect::publishState(const int8_t state)
|
||||
{
|
||||
if (state == -1 || !_options || state >= _options->getItemsNb()) {
|
||||
if (!_options || state >= _options->getItemsNb()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state == -1) {
|
||||
return publishOnDataTopic(AHATOFSTR(HAStateTopic), AHATOFSTR(HAStateNone), true);
|
||||
}
|
||||
|
||||
const char* item = _options->getItems()[state];
|
||||
if (!item) {
|
||||
return false;
|
||||
|
||||
@@ -41,12 +41,19 @@ public:
|
||||
* Please note that if a new value is the same as previous one,
|
||||
* the MQTT message won't be published.
|
||||
*
|
||||
* @param state New state of the select.
|
||||
* @param state New state of the select. You can set `-1` to reset the select.
|
||||
* @param force Forces to update state without comparing it to previous known state.
|
||||
* @returns Returns true if MQTT message has been published successfully.
|
||||
*/
|
||||
bool setState(const int8_t state, const bool force = false);
|
||||
|
||||
/**
|
||||
* Returns the selected option based on the most recent state of the select.
|
||||
* You can utilize this method to get the string representation of the option (e.g. for printing).
|
||||
* If no option is selected, null is returned.
|
||||
*/
|
||||
const char* getCurrentOption() const;
|
||||
|
||||
/**
|
||||
* Sets the current state of the select without publishing it to Home Assistant.
|
||||
* State represents the index of the option that was set using the setOptions method.
|
||||
|
||||
@@ -28,6 +28,15 @@ bool HASerializerArray::add(ItemType item)
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* HASerializerArray::getItem(const uint8_t index) const
|
||||
{
|
||||
if (index < 0 || index >= _itemsNb) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return _items[index];
|
||||
}
|
||||
|
||||
uint16_t HASerializerArray::calculateSize() const
|
||||
{
|
||||
uint16_t size =
|
||||
|
||||
@@ -37,12 +37,18 @@ public:
|
||||
|
||||
/**
|
||||
* Adds a new element to the array.
|
||||
*
|
||||
*
|
||||
* @param itemP Item to add (string).
|
||||
* @returns Returns `true` if item has been added to the array successfully.
|
||||
*/
|
||||
bool add(ItemType item);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the item at the given index.
|
||||
* If the element doesn't exist, null is returned.
|
||||
*/
|
||||
const char* getItem(const uint8_t index) const;
|
||||
|
||||
/**
|
||||
* Calculates the size of the serialized array (JSON representation).
|
||||
*/
|
||||
|
||||
@@ -108,7 +108,7 @@ AHA_TEST(SelectTest, extended_unique_id) {
|
||||
"}"
|
||||
)
|
||||
)
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); // only config should be pushed
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, single_option) {
|
||||
@@ -131,7 +131,7 @@ AHA_TEST(SelectTest, single_option) {
|
||||
"}"
|
||||
)
|
||||
)
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); // only config should be pushed
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, multiple_options) {
|
||||
@@ -154,7 +154,7 @@ AHA_TEST(SelectTest, multiple_options) {
|
||||
"}"
|
||||
)
|
||||
)
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); // only config should be pushed
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, command_subscription) {
|
||||
@@ -194,6 +194,7 @@ AHA_TEST(SelectTest, publish_last_known_state) {
|
||||
mqtt.loop();
|
||||
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
assertEqual("B", select.getCurrentOption());
|
||||
assertMqttMessage(1, AHATOFSTR(StateTopic), "B", true)
|
||||
}
|
||||
|
||||
@@ -209,6 +210,17 @@ AHA_TEST(SelectTest, publish_nothing_if_retained) {
|
||||
assertEqual(1, mock->getFlushedMessagesNb()); // only config should be pushed
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, publish_state_none) {
|
||||
prepareTest
|
||||
|
||||
HASelect select(testUniqueId);
|
||||
select.setOptions("Option A;B;C");
|
||||
mqtt.loop();
|
||||
|
||||
assertEqual(2, mock->getFlushedMessagesNb());
|
||||
assertMqttMessage(1, AHATOFSTR(StateTopic), "None", true)
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, name_setter) {
|
||||
prepareTest
|
||||
|
||||
@@ -310,6 +322,7 @@ AHA_TEST(SelectTest, current_state_getter) {
|
||||
|
||||
assertEqual(0, mock->getFlushedMessagesNb());
|
||||
assertEqual(1, select.getCurrentState());
|
||||
assertEqual("B", select.getCurrentOption());
|
||||
}
|
||||
|
||||
AHA_TEST(SelectTest, publish_state_first) {
|
||||
@@ -321,6 +334,7 @@ AHA_TEST(SelectTest, publish_state_first) {
|
||||
|
||||
assertTrue(select.setState(0));
|
||||
assertTrue(select.getOptions() != nullptr);
|
||||
assertEqual("Option A", select.getCurrentOption());
|
||||
assertEqual(3, select.getOptions()->getItemsNb());
|
||||
assertSingleMqttMessage(AHATOFSTR(StateTopic), "Option A", true)
|
||||
}
|
||||
@@ -334,6 +348,7 @@ AHA_TEST(SelectTest, publish_state_last) {
|
||||
|
||||
assertTrue(select.setState(2));
|
||||
assertTrue(select.getOptions() != nullptr);
|
||||
assertEqual("C", select.getCurrentOption());
|
||||
assertEqual(3, select.getOptions()->getItemsNb());
|
||||
assertSingleMqttMessage(AHATOFSTR(StateTopic), "C", true)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ static const char* testDeviceId = "testDevice";
|
||||
static const char* testUniqueId = "uniqueSensor";
|
||||
const char ConfigTopic[] PROGMEM = {"homeassistant/sensor/testDevice/uniqueSensor/config"};
|
||||
const char StateTopic[] PROGMEM = {"testData/testDevice/uniqueSensor/stat_t"};
|
||||
const char DummyTemplateStr[] PROGMEM = {"dummyTemplate"};
|
||||
|
||||
AHA_TEST(SensorTest, invalid_unique_id) {
|
||||
initMqttTest(testDeviceId)
|
||||
|
||||
@@ -50,6 +50,7 @@ AHA_TEST(SerializerArrayTest, size_overflow_progmem) {
|
||||
assertTrue(array.add(HANameProperty));
|
||||
assertFalse(array.add(HAUniqueIdProperty));
|
||||
assertEqual(1, array.getItemsNb());
|
||||
|
||||
assertEqual((uintptr_t)HANameProperty, (uintptr_t)&(*array.getItems()[0]));
|
||||
assertJson("[\"name\"]", array);
|
||||
}
|
||||
@@ -61,6 +62,8 @@ AHA_TEST(SerializerArrayTest, single_element_ram) {
|
||||
|
||||
assertTrue(result);
|
||||
assertEqual(1, array.getItemsNb());
|
||||
|
||||
assertEqual((uintptr_t)item, (uintptr_t)&(*array.getItem(0)));
|
||||
assertEqual((uintptr_t)item, (uintptr_t)&(*array.getItems()[0]));
|
||||
assertJson("[\"test\"]", array);
|
||||
}
|
||||
@@ -77,8 +80,17 @@ AHA_TEST(SerializerArrayTest, multiple_elements_ram) {
|
||||
assertEqual(3, array.getItemsNb());
|
||||
|
||||
HASerializerArray::ItemType* items = array.getItems();
|
||||
|
||||
assertEqual((uintptr_t)nullptr, (uintptr_t)&(*array.getItem(-1)));
|
||||
assertEqual((uintptr_t)nullptr, (uintptr_t)&(*array.getItem(3)));
|
||||
|
||||
assertEqual((uintptr_t)item0, (uintptr_t)&(*array.getItem(0)));
|
||||
assertEqual((uintptr_t)item0, (uintptr_t)&(*items[0]));
|
||||
|
||||
assertEqual((uintptr_t)item1, (uintptr_t)&(*array.getItem(1)));
|
||||
assertEqual((uintptr_t)item1, (uintptr_t)&(*items[1]));
|
||||
|
||||
assertEqual((uintptr_t)item2, (uintptr_t)&(*array.getItem(2)));
|
||||
assertEqual((uintptr_t)item2, (uintptr_t)&(*items[2]));
|
||||
assertJson("[\"item0\",\"item1\",\"item2\"]", array);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user