mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 22:06:12 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d06f58f972 | ||
|
|
657a854431 | ||
|
|
a28cd24dcd |
@@ -5,12 +5,21 @@
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::homeassistant {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "homeassistant.button");
|
||||
|
||||
// In flash on the ESP8266; copied to the stack only while a request is built
|
||||
PROGMEM_STRING_TABLE(ButtonActionStrings, "button.press", "input_button.press", "entity_id");
|
||||
enum ButtonActionString : uint8_t {
|
||||
BUTTON_ACTION_STRING_SERVICE,
|
||||
BUTTON_ACTION_STRING_INPUT_SERVICE,
|
||||
BUTTON_ACTION_STRING_ENTITY_ID,
|
||||
};
|
||||
|
||||
void HomeassistantButton::dump_config() {
|
||||
LOG_BUTTON("", "Homeassistant Button", this);
|
||||
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_);
|
||||
@@ -31,21 +40,17 @@ void HomeassistantButton::press_action() {
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr auto SERVICE_BUTTON = StringRef::from_lit("button.press");
|
||||
static constexpr auto SERVICE_INPUT_BUTTON = StringRef::from_lit("input_button.press");
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
static constexpr char INPUT_PREFIX[] = "input_";
|
||||
|
||||
ProgmemStringTableCopy<ButtonActionStrings> strings;
|
||||
// An input helper entity id starts with the "input_" its action name starts with
|
||||
const bool input_helper =
|
||||
strncmp(this->entity_id_, strings.get(BUTTON_ACTION_STRING_INPUT_SERVICE), sizeof("input_") - 1) == 0;
|
||||
api::HomeassistantActionRequest resp;
|
||||
if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) {
|
||||
resp.service = SERVICE_INPUT_BUTTON;
|
||||
} else {
|
||||
resp.service = SERVICE_BUTTON;
|
||||
}
|
||||
resp.service =
|
||||
StringRef(strings.get(input_helper ? BUTTON_ACTION_STRING_INPUT_SERVICE : BUTTON_ACTION_STRING_SERVICE));
|
||||
|
||||
resp.data.init(1);
|
||||
auto &entity_id_kv = resp.data.emplace_back();
|
||||
entity_id_kv.key = ENTITY_ID_KEY;
|
||||
entity_id_kv.key = StringRef(strings.get(BUTTON_ACTION_STRING_ENTITY_ID));
|
||||
entity_id_kv.value = StringRef(this->entity_id_);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
|
||||
@@ -5,12 +5,22 @@
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::homeassistant {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "homeassistant.number");
|
||||
|
||||
// In flash on the ESP8266; copied to the stack only while a request is built
|
||||
PROGMEM_STRING_TABLE(NumberActionStrings, "number.set_value", "input_number.set_value", "entity_id", "value");
|
||||
enum NumberActionString : uint8_t {
|
||||
NUMBER_ACTION_STRING_SERVICE,
|
||||
NUMBER_ACTION_STRING_INPUT_SERVICE,
|
||||
NUMBER_ACTION_STRING_ENTITY_ID,
|
||||
NUMBER_ACTION_STRING_VALUE,
|
||||
};
|
||||
|
||||
void HomeassistantNumber::state_changed_(StringRef state) {
|
||||
auto number_value = parse_number<float>(state.c_str());
|
||||
if (!number_value.has_value()) {
|
||||
@@ -89,27 +99,22 @@ void HomeassistantNumber::control(float value) {
|
||||
|
||||
this->publish_state(value);
|
||||
|
||||
static constexpr auto NUMBER_SERVICE_NAME = StringRef::from_lit("number.set_value");
|
||||
static constexpr auto INPUT_NUMBER_SERVICE_NAME = StringRef::from_lit("input_number.set_value");
|
||||
static constexpr char INPUT_PREFIX[] = "input_";
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
static constexpr auto VALUE_KEY = StringRef::from_lit("value");
|
||||
|
||||
ProgmemStringTableCopy<NumberActionStrings> strings;
|
||||
// An input helper entity id starts with the "input_" its action name starts with
|
||||
const bool input_helper =
|
||||
strncmp(this->entity_id_, strings.get(NUMBER_ACTION_STRING_INPUT_SERVICE), sizeof("input_") - 1) == 0;
|
||||
api::HomeassistantActionRequest resp;
|
||||
// input_number entities have their own set_value action
|
||||
if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) {
|
||||
resp.service = INPUT_NUMBER_SERVICE_NAME;
|
||||
} else {
|
||||
resp.service = NUMBER_SERVICE_NAME;
|
||||
}
|
||||
resp.service =
|
||||
StringRef(strings.get(input_helper ? NUMBER_ACTION_STRING_INPUT_SERVICE : NUMBER_ACTION_STRING_SERVICE));
|
||||
|
||||
resp.data.init(2);
|
||||
auto &entity_id = resp.data.emplace_back();
|
||||
entity_id.key = ENTITY_ID_KEY;
|
||||
entity_id.key = StringRef(strings.get(NUMBER_ACTION_STRING_ENTITY_ID));
|
||||
entity_id.value = StringRef(this->entity_id_);
|
||||
|
||||
auto &entity_value = resp.data.emplace_back();
|
||||
entity_value.key = VALUE_KEY;
|
||||
entity_value.key = StringRef(strings.get(NUMBER_ACTION_STRING_VALUE));
|
||||
// Stack buffer - no heap allocation; %g produces shortest representation
|
||||
char value_buf[16];
|
||||
buf_append_printf(value_buf, sizeof(value_buf), 0, "%g", value);
|
||||
|
||||
@@ -6,12 +6,22 @@
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::homeassistant {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "homeassistant.select");
|
||||
|
||||
// In flash on the ESP8266; copied to the stack only while a request is built
|
||||
PROGMEM_STRING_TABLE(SelectActionStrings, "select.select_option", "input_select.select_option", "entity_id", "option");
|
||||
enum SelectActionString : uint8_t {
|
||||
SELECT_ACTION_STRING_SERVICE,
|
||||
SELECT_ACTION_STRING_INPUT_SERVICE,
|
||||
SELECT_ACTION_STRING_ENTITY_ID,
|
||||
SELECT_ACTION_STRING_OPTION,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
/// Emit the UTF-8 encoding of a code point. Rejects NUL, which would end the option early.
|
||||
@@ -237,26 +247,21 @@ void HomeassistantSelect::control(size_t index) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr auto SERVICE_SELECT = StringRef::from_lit("select.select_option");
|
||||
static constexpr auto SERVICE_INPUT_SELECT = StringRef::from_lit("input_select.select_option");
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
static constexpr auto OPTION_KEY = StringRef::from_lit("option");
|
||||
static constexpr char INPUT_PREFIX[] = "input_";
|
||||
|
||||
ProgmemStringTableCopy<SelectActionStrings> strings;
|
||||
// An input helper entity id starts with the "input_" its action name starts with
|
||||
const bool input_helper =
|
||||
strncmp(this->entity_id_, strings.get(SELECT_ACTION_STRING_INPUT_SERVICE), sizeof("input_") - 1) == 0;
|
||||
api::HomeassistantActionRequest resp;
|
||||
if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) {
|
||||
resp.service = SERVICE_INPUT_SELECT;
|
||||
} else {
|
||||
resp.service = SERVICE_SELECT;
|
||||
}
|
||||
resp.service =
|
||||
StringRef(strings.get(input_helper ? SELECT_ACTION_STRING_INPUT_SERVICE : SELECT_ACTION_STRING_SERVICE));
|
||||
|
||||
resp.data.init(2);
|
||||
auto &entity_id = resp.data.emplace_back();
|
||||
entity_id.key = ENTITY_ID_KEY;
|
||||
entity_id.key = StringRef(strings.get(SELECT_ACTION_STRING_ENTITY_ID));
|
||||
entity_id.value = StringRef(this->entity_id_);
|
||||
|
||||
auto &option = resp.data.emplace_back();
|
||||
option.key = OPTION_KEY;
|
||||
option.key = StringRef(strings.get(SELECT_ACTION_STRING_OPTION));
|
||||
option.value = StringRef(this->option_at(index));
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
#include "homeassistant_switch.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::homeassistant {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "homeassistant.switch");
|
||||
|
||||
// In flash on the ESP8266; copied to the stack only while a request is built
|
||||
PROGMEM_STRING_TABLE(SwitchActionStrings, "homeassistant.turn_on", "homeassistant.turn_off", "entity_id");
|
||||
enum SwitchActionString : uint8_t {
|
||||
SWITCH_ACTION_STRING_TURN_ON,
|
||||
SWITCH_ACTION_STRING_TURN_OFF,
|
||||
SWITCH_ACTION_STRING_ENTITY_ID,
|
||||
};
|
||||
|
||||
using namespace esphome::switch_;
|
||||
|
||||
void HomeassistantSwitch::setup() {
|
||||
@@ -47,20 +56,13 @@ void HomeassistantSwitch::write_state(bool state) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr auto SERVICE_ON = StringRef::from_lit("homeassistant.turn_on");
|
||||
static constexpr auto SERVICE_OFF = StringRef::from_lit("homeassistant.turn_off");
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
|
||||
ProgmemStringTableCopy<SwitchActionStrings> strings;
|
||||
api::HomeassistantActionRequest resp;
|
||||
if (state) {
|
||||
resp.service = SERVICE_ON;
|
||||
} else {
|
||||
resp.service = SERVICE_OFF;
|
||||
}
|
||||
resp.service = StringRef(strings.get(state ? SWITCH_ACTION_STRING_TURN_ON : SWITCH_ACTION_STRING_TURN_OFF));
|
||||
|
||||
resp.data.init(1);
|
||||
auto &entity_id_kv = resp.data.emplace_back();
|
||||
entity_id_kv.key = ENTITY_ID_KEY;
|
||||
entity_id_kv.key = StringRef(strings.get(SWITCH_ACTION_STRING_ENTITY_ID));
|
||||
entity_id_kv.value = StringRef(this->entity_id_);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
|
||||
@@ -6,12 +6,22 @@
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::homeassistant {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "homeassistant.text");
|
||||
|
||||
// In flash on the ESP8266; copied to the stack only while a request is built
|
||||
PROGMEM_STRING_TABLE(TextActionStrings, "text.set_value", "input_text.set_value", "entity_id", "value");
|
||||
enum TextActionString : uint8_t {
|
||||
TEXT_ACTION_STRING_SERVICE,
|
||||
TEXT_ACTION_STRING_INPUT_SERVICE,
|
||||
TEXT_ACTION_STRING_ENTITY_ID,
|
||||
TEXT_ACTION_STRING_VALUE,
|
||||
};
|
||||
|
||||
void HomeassistantText::state_changed_(StringRef state) {
|
||||
if (state == this->state) {
|
||||
return;
|
||||
@@ -86,26 +96,20 @@ void HomeassistantText::control(const std::string &value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr auto SERVICE_TEXT = StringRef::from_lit("text.set_value");
|
||||
static constexpr auto SERVICE_INPUT_TEXT = StringRef::from_lit("input_text.set_value");
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
static constexpr auto VALUE_KEY = StringRef::from_lit("value");
|
||||
static constexpr char INPUT_PREFIX[] = "input_";
|
||||
|
||||
ProgmemStringTableCopy<TextActionStrings> strings;
|
||||
// An input helper entity id starts with the "input_" its action name starts with
|
||||
const bool input_helper =
|
||||
strncmp(this->entity_id_, strings.get(TEXT_ACTION_STRING_INPUT_SERVICE), sizeof("input_") - 1) == 0;
|
||||
api::HomeassistantActionRequest resp;
|
||||
if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) {
|
||||
resp.service = SERVICE_INPUT_TEXT;
|
||||
} else {
|
||||
resp.service = SERVICE_TEXT;
|
||||
}
|
||||
resp.service = StringRef(strings.get(input_helper ? TEXT_ACTION_STRING_INPUT_SERVICE : TEXT_ACTION_STRING_SERVICE));
|
||||
|
||||
resp.data.init(2);
|
||||
auto &entity_id_kv = resp.data.emplace_back();
|
||||
entity_id_kv.key = ENTITY_ID_KEY;
|
||||
entity_id_kv.key = StringRef(strings.get(TEXT_ACTION_STRING_ENTITY_ID));
|
||||
entity_id_kv.value = StringRef(this->entity_id_);
|
||||
|
||||
auto &value_kv = resp.data.emplace_back();
|
||||
value_kv.key = VALUE_KEY;
|
||||
value_kv.key = StringRef(strings.get(TEXT_ACTION_STRING_VALUE));
|
||||
value_kv.value = StringRef(value.data(), value.size());
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
|
||||
@@ -153,4 +153,38 @@ struct LogString;
|
||||
} \
|
||||
}
|
||||
|
||||
/// The index of the string value (len bytes, in RAM) in a PROGMEM_STRING_TABLE, or -1 when it is
|
||||
/// not one of its strings
|
||||
template<typename Table> int8_t progmem_string_table_find(const char *value, size_t len) {
|
||||
for (uint8_t i = 0; i < Table::COUNT; i++) {
|
||||
const char *entry = Table::get_(i, i);
|
||||
if (ESPHOME_strlen_P(entry) == len && ESPHOME_strncmp_P(value, entry, len) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// The strings of a PROGMEM_STRING_TABLE addressable as plain pointers, for code that hands them on
|
||||
/// without reading them itself, such as an API request. On the ESP8266 this is a copy of the table on
|
||||
/// the stack that lives as long as the object; elsewhere the table is read in place.
|
||||
///
|
||||
/// Example, with an enum that numbers the table's strings:
|
||||
/// ProgmemStringTableCopy<MyStrings> strings;
|
||||
/// request.service = StringRef(strings.get(MY_STRING_TURN_ON));
|
||||
template<typename Name> class ProgmemStringTableCopy {
|
||||
static_assert(Name::Table::INDEXED, "the table needs offsets");
|
||||
|
||||
public:
|
||||
#ifdef USE_ESP8266
|
||||
ProgmemStringTableCopy() { progmem_memcpy(this->blob_, Name::BLOB.data(), Name::BLOB_SIZE); }
|
||||
const char *get(uint8_t idx) const { return this->blob_ + progmem_read_byte(&Name::OFFSETS[idx]); }
|
||||
|
||||
private:
|
||||
char blob_[Name::BLOB_SIZE];
|
||||
#else
|
||||
const char *get(uint8_t idx) const { return Name::get_(idx, idx); }
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::testing {
|
||||
|
||||
PROGMEM_STRING_TABLE(Names, "light.turn_on", "entity_id", "rgb_color");
|
||||
|
||||
TEST(ProgmemStringTableCopy, HandsOutEveryString) {
|
||||
ProgmemStringTableCopy<Names> strings;
|
||||
EXPECT_STREQ(strings.get(0), "light.turn_on");
|
||||
EXPECT_STREQ(strings.get(1), "entity_id");
|
||||
EXPECT_STREQ(strings.get(2), "rgb_color");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTableCopy, StringsStayValidWhileTheCopyLives) {
|
||||
ProgmemStringTableCopy<Names> strings;
|
||||
const char *key = strings.get(1);
|
||||
const char *other = strings.get(2);
|
||||
EXPECT_EQ(std::strlen(key), 9U);
|
||||
EXPECT_STREQ(key, "entity_id");
|
||||
EXPECT_STREQ(other, "rgb_color");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTableFind, FindsWholeStringsOnly) {
|
||||
EXPECT_EQ(progmem_string_table_find<Names>("entity_id", 9), 1);
|
||||
EXPECT_EQ(progmem_string_table_find<Names>("rgb_color", 9), 2);
|
||||
EXPECT_EQ(progmem_string_table_find<Names>("entity", 6), -1);
|
||||
EXPECT_EQ(progmem_string_table_find<Names>("entity_id_x", 11), -1);
|
||||
EXPECT_EQ(progmem_string_table_find<Names>("", 0), -1);
|
||||
}
|
||||
|
||||
} // namespace esphome::testing
|
||||
Reference in New Issue
Block a user