mirror of
https://github.com/esphome/esphome.git
synced 2026-02-21 00:45:35 -07:00
Merge remote-tracking branch 'kbx81/20250915-wifi-info-use-callbacks' into integration
This commit is contained in:
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
|
||||
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
python script/run-in-env.py pre-commit run --all-files
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@openhomefoundation.org>
|
||||
|
||||
@@ -484,6 +484,7 @@ esphome/components/template/datetime/* @rfdarter
|
||||
esphome/components/template/event/* @nohat
|
||||
esphome/components/template/fan/* @ssieb
|
||||
esphome/components/text/* @mauritskorse
|
||||
esphome/components/thermopro_ble/* @sittner
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @esphome/core
|
||||
esphome/components/tinyusb/* @kbx81
|
||||
|
||||
@@ -85,6 +85,7 @@ CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
|
||||
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
|
||||
CONF_LISTEN_BACKLOG = "listen_backlog"
|
||||
CONF_MAX_SEND_QUEUE = "max_send_queue"
|
||||
CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only"
|
||||
|
||||
|
||||
def validate_encryption_key(value):
|
||||
@@ -537,9 +538,24 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||
API_CONNECTED_CONDITION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Optional(CONF_STATE_SUBSCRIPTION_ONLY, default=False): cv.templatable(
|
||||
cv.boolean
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"api.connected", APIConnectedCondition, API_CONNECTED_CONDITION_SCHEMA
|
||||
)
|
||||
async def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
return cg.new_Pvariable(condition_id, template_arg)
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
templ = await cg.templatable(config[CONF_STATE_SUBSCRIPTION_ONLY], args, cg.bool_)
|
||||
cg.add(var.set_state_subscription_only(templ))
|
||||
return var
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
|
||||
@@ -559,7 +559,18 @@ void APIServer::request_time() {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
bool APIServer::is_connected(bool state_subscription_only) const {
|
||||
if (!state_subscription_only) {
|
||||
return !this->clients_.empty();
|
||||
}
|
||||
|
||||
for (const auto &client : this->clients_) {
|
||||
if (client->flags_.state_subscription) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void APIServer::on_shutdown() {
|
||||
this->shutting_down_ = true;
|
||||
|
||||
@@ -150,7 +150,7 @@ class APIServer : public Component, public Controller {
|
||||
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
|
||||
#endif
|
||||
|
||||
bool is_connected() const;
|
||||
bool is_connected(bool state_subscription_only = false) const;
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
struct HomeAssistantStateSubscription {
|
||||
@@ -254,8 +254,11 @@ class APIServer : public Component, public Controller {
|
||||
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||
TEMPLATABLE_VALUE(bool, state_subscription_only)
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_api_server->is_connected(); }
|
||||
bool check(const Ts &...x) override {
|
||||
return global_api_server->is_connected(this->state_subscription_only_.value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -132,7 +132,11 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
|
||||
|
||||
void get_bluetooth_mac_address_pretty(std::span<char, 18> output) {
|
||||
const uint8_t *mac = esp_bt_dev_get_address();
|
||||
format_mac_addr_upper(mac, output.data());
|
||||
if (mac != nullptr) {
|
||||
format_mac_addr_upper(mac, output.data());
|
||||
} else {
|
||||
output[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -47,18 +47,20 @@ MULTI_CONF = True
|
||||
|
||||
|
||||
def _bus_declare_type(value):
|
||||
if CORE.is_esp32:
|
||||
return cv.declare_id(IDFI2CBus)(value)
|
||||
if CORE.using_arduino:
|
||||
return cv.declare_id(ArduinoI2CBus)(value)
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(IDFI2CBus)(value)
|
||||
if CORE.using_zephyr:
|
||||
return cv.declare_id(ZephyrI2CBus)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
if CORE.using_esp_idf:
|
||||
return cv.require_framework_version(esp_idf=cv.Version(5, 4, 2))(config)
|
||||
if CORE.is_esp32:
|
||||
return cv.require_framework_version(
|
||||
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
|
||||
)(config)
|
||||
return config
|
||||
|
||||
|
||||
@@ -67,12 +69,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): _bus_declare_type,
|
||||
cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
cv.only_on_esp32, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number,
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||
cv.only_with_esp_idf, cv.boolean
|
||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
|
||||
cv.only_on_esp32, cv.boolean
|
||||
),
|
||||
cv.SplitDefault(
|
||||
CONF_FREQUENCY,
|
||||
@@ -151,7 +153,7 @@ async def to_code(config):
|
||||
cg.add(var.set_scan(config[CONF_SCAN]))
|
||||
if CONF_TIMEOUT in config:
|
||||
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
||||
if CORE.using_arduino:
|
||||
if CORE.using_arduino and not CORE.is_esp32:
|
||||
cg.add_library("Wire", None)
|
||||
|
||||
|
||||
@@ -248,14 +250,16 @@ def final_validate_device_schema(
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"i2c_bus_arduino.cpp": {
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.RP2040_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||
"i2c_bus_esp_idf.cpp": {
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
|
||||
#include "i2c_bus_arduino.h"
|
||||
#include <Arduino.h>
|
||||
@@ -15,16 +15,7 @@ static const char *const TAG = "i2c.arduino";
|
||||
void ArduinoI2CBus::setup() {
|
||||
recover_();
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
static uint8_t next_bus_num = 0;
|
||||
if (next_bus_num == 0) {
|
||||
wire_ = &Wire;
|
||||
} else {
|
||||
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
this->port_ = next_bus_num;
|
||||
next_bus_num++;
|
||||
#elif defined(USE_ESP8266)
|
||||
#if defined(USE_ESP8266)
|
||||
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
#elif defined(USE_RP2040)
|
||||
static bool first = true;
|
||||
@@ -54,10 +45,7 @@ void ArduinoI2CBus::set_pins_and_clock_() {
|
||||
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
|
||||
#endif
|
||||
if (timeout_ > 0) { // if timeout specified in yaml
|
||||
#if defined(USE_ESP32)
|
||||
// https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.cpp
|
||||
wire_->setTimeOut(timeout_ / 1000); // unit: ms
|
||||
#elif defined(USE_ESP8266)
|
||||
#if defined(USE_ESP8266)
|
||||
// https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
|
||||
wire_->setClockStretchLimit(timeout_); // unit: us
|
||||
#elif defined(USE_RP2040)
|
||||
@@ -76,9 +64,7 @@ void ArduinoI2CBus::dump_config() {
|
||||
" Frequency: %u Hz",
|
||||
this->sda_pin_, this->scl_pin_, this->frequency_);
|
||||
if (timeout_ > 0) {
|
||||
#if defined(USE_ESP32)
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
|
||||
#elif defined(USE_ESP8266)
|
||||
#if defined(USE_ESP8266)
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
|
||||
#elif defined(USE_RP2040)
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
|
||||
@@ -275,4 +261,4 @@ void ArduinoI2CBus::recover_() {
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
|
||||
#include <Wire.h>
|
||||
#include "esphome/core/component.h"
|
||||
@@ -29,7 +29,7 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
|
||||
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
|
||||
void set_timeout(uint32_t timeout) { timeout_ = timeout; }
|
||||
|
||||
int get_port() const override { return this->port_; }
|
||||
int get_port() const override { return 0; }
|
||||
|
||||
private:
|
||||
void recover_();
|
||||
@@ -37,7 +37,6 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
|
||||
RecoveryCode recovery_result_;
|
||||
|
||||
protected:
|
||||
int8_t port_{-1};
|
||||
TwoWire *wire_;
|
||||
uint8_t sda_pin_;
|
||||
uint8_t scl_pin_;
|
||||
@@ -49,4 +48,4 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "i2c_bus_esp_idf.h"
|
||||
|
||||
@@ -299,4 +299,4 @@ void IDFI2CBus::recover_() {
|
||||
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "i2c_bus.h"
|
||||
@@ -53,4 +53,4 @@ class IDFI2CBus : public InternalI2CBus, public Component {
|
||||
} // namespace i2c
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -120,7 +120,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
|
||||
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
|
||||
bool has_psk = api::global_api_server->get_noise_ctx()->has_psk();
|
||||
bool has_psk = api::global_api_server->get_noise_ctx().has_psk();
|
||||
const char *encryption_key = has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
|
||||
txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -38,6 +39,14 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui
|
||||
PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
|
||||
Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
decoder->draw(x, y, w, h, color);
|
||||
|
||||
// Feed watchdog periodically to avoid triggering during long decode operations.
|
||||
// Feed every 1024 pixels to balance efficiency and responsiveness.
|
||||
uint32_t pixels = w * h;
|
||||
decoder->increment_pixels_decoded(pixels);
|
||||
if ((decoder->get_pixels_decoded() % 1024) < pixels) {
|
||||
App.feed_wdt();
|
||||
}
|
||||
}
|
||||
|
||||
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {
|
||||
|
||||
@@ -25,9 +25,13 @@ class PngDecoder : public ImageDecoder {
|
||||
int prepare(size_t download_size) override;
|
||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||
|
||||
void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; }
|
||||
uint32_t get_pixels_decoded() const { return this->pixels_decoded_; }
|
||||
|
||||
protected:
|
||||
RAMAllocator<pngle_t> allocator_;
|
||||
pngle_t *pngle_;
|
||||
uint32_t pixels_decoded_{0};
|
||||
};
|
||||
|
||||
} // namespace online_image
|
||||
|
||||
@@ -53,6 +53,18 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
|
||||
this->lock_row_(stream, obj, area, node, friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
this->event_type_(stream);
|
||||
for (auto *obj : App.get_events())
|
||||
this->event_row_(stream, obj, area, node, friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
this->text_type_(stream);
|
||||
for (auto *obj : App.get_texts())
|
||||
this->text_row_(stream, obj, area, node, friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->text_sensor_type_(stream);
|
||||
for (auto *obj : App.get_text_sensors())
|
||||
@@ -547,6 +559,100 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso
|
||||
}
|
||||
#endif
|
||||
|
||||
// Type-specific implementation
|
||||
#ifdef USE_TEXT
|
||||
void PrometheusHandler::text_type_(AsyncResponseStream *stream) {
|
||||
stream->print(ESPHOME_F("#TYPE esphome_text_value gauge\n"));
|
||||
stream->print(ESPHOME_F("#TYPE esphome_text_failed gauge\n"));
|
||||
}
|
||||
void PrometheusHandler::text_row_(AsyncResponseStream *stream, text::Text *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name) {
|
||||
if (obj->is_internal() && !this->include_internal_)
|
||||
return;
|
||||
if (obj->has_state()) {
|
||||
// We have a valid value, output this value
|
||||
stream->print(ESPHOME_F("esphome_text_failed{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\"} 0\n"));
|
||||
// Data itself
|
||||
stream->print(ESPHOME_F("esphome_text_value{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",value=\""));
|
||||
stream->print(obj->state.c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
} else {
|
||||
// Invalid state
|
||||
stream->print(ESPHOME_F("esphome_text_failed{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\"} 1\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Type-specific implementation
|
||||
#ifdef USE_EVENT
|
||||
void PrometheusHandler::event_type_(AsyncResponseStream *stream) {
|
||||
stream->print(ESPHOME_F("#TYPE esphome_event_value gauge\n"));
|
||||
stream->print(ESPHOME_F("#TYPE esphome_event_failed gauge\n"));
|
||||
}
|
||||
void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name) {
|
||||
if (obj->is_internal() && !this->include_internal_)
|
||||
return;
|
||||
if (obj->get_last_event_type() != nullptr) {
|
||||
// We have a valid event type, output this value
|
||||
stream->print(ESPHOME_F("esphome_event_failed{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\"} 0\n"));
|
||||
// Data itself
|
||||
stream->print(ESPHOME_F("esphome_event_value{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",last_event_type=\""));
|
||||
stream->print(obj->get_last_event_type());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
} else {
|
||||
// No event triggered yet
|
||||
stream->print(ESPHOME_F("esphome_event_failed{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
add_node_label_(stream, node);
|
||||
add_friendly_name_label_(stream, friendly_name);
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\"} 1\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Type-specific implementation
|
||||
#ifdef USE_NUMBER
|
||||
void PrometheusHandler::number_type_(AsyncResponseStream *stream) {
|
||||
@@ -620,7 +726,7 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",value=\""));
|
||||
stream->print(obj->state.c_str());
|
||||
stream->print(obj->current_option());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
|
||||
@@ -123,6 +123,22 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
||||
std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
/// Return the type for prometheus
|
||||
void event_type_(AsyncResponseStream *stream);
|
||||
/// Return the event values state as prometheus data point
|
||||
void event_row_(AsyncResponseStream *stream, event::Event *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
/// Return the type for prometheus
|
||||
void text_type_(AsyncResponseStream *stream);
|
||||
/// Return the text values state as prometheus data point
|
||||
void text_row_(AsyncResponseStream *stream, text::Text *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
/// Return the type for prometheus
|
||||
void text_sensor_type_(AsyncResponseStream *stream);
|
||||
|
||||
0
esphome/components/thermopro_ble/__init__.py
Normal file
0
esphome/components/thermopro_ble/__init__.py
Normal file
97
esphome/components/thermopro_ble/sensor.py
Normal file
97
esphome/components/thermopro_ble/sensor.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble_tracker, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_EXTERNAL_TEMPERATURE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_SIGNAL_STRENGTH,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@sittner"]
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
thermopro_ble_ns = cg.esphome_ns.namespace("thermopro_ble")
|
||||
ThermoProBLE = thermopro_ble_ns.class_(
|
||||
"ThermoProBLE", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ThermoProBLE),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(external_temperature_config)
|
||||
cg.add(var.set_external_temperature(sens))
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(battery_level_config)
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
|
||||
sens = await sensor.new_sensor(signal_strength_config)
|
||||
cg.add(var.set_signal_strength(sens))
|
||||
204
esphome/components/thermopro_ble/thermopro_ble.cpp
Normal file
204
esphome/components/thermopro_ble/thermopro_ble.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
#include "thermopro_ble.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome::thermopro_ble {
|
||||
|
||||
// this size must be large enough to hold the largest data frame
|
||||
// of all supported devices
|
||||
static constexpr std::size_t MAX_DATA_SIZE = 24;
|
||||
|
||||
struct DeviceParserMapping {
|
||||
const char *prefix;
|
||||
DeviceParser parser;
|
||||
};
|
||||
|
||||
static float tp96_battery(uint16_t voltage);
|
||||
|
||||
static optional<ParseResult> parse_tp972(const uint8_t *data, std::size_t data_size);
|
||||
static optional<ParseResult> parse_tp96(const uint8_t *data, std::size_t data_size);
|
||||
static optional<ParseResult> parse_tp3(const uint8_t *data, std::size_t data_size);
|
||||
|
||||
static const char *const TAG = "thermopro_ble";
|
||||
|
||||
static const struct DeviceParserMapping DEVICE_PARSER_MAP[] = {
|
||||
{"TP972", parse_tp972}, {"TP970", parse_tp96}, {"TP96", parse_tp96}, {"TP3", parse_tp3}};
|
||||
|
||||
void ThermoProBLE::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ThermoPro BLE");
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "External temperature", this->external_temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
}
|
||||
|
||||
bool ThermoProBLE::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
// check for matching mac address
|
||||
if (device.address_uint64() != this->address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for valid device type
|
||||
update_device_type_(device.get_name());
|
||||
if (this->device_parser_ == nullptr) {
|
||||
ESP_LOGVV(TAG, "parse_device(): invalid device type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
|
||||
// publish signal strength
|
||||
float signal_strength = float(device.get_rssi());
|
||||
if (this->signal_strength_ != nullptr)
|
||||
this->signal_strength_->publish_state(signal_strength);
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_manufacturer_datas()) {
|
||||
// check maximum data size
|
||||
std::size_t data_size = service_data.data.size() + 2;
|
||||
if (data_size > MAX_DATA_SIZE) {
|
||||
ESP_LOGVV(TAG, "parse_device(): maximum data size exceeded!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// reconstruct whole record from 2 byte uuid and data
|
||||
esp_bt_uuid_t uuid = service_data.uuid.get_uuid();
|
||||
uint8_t data[MAX_DATA_SIZE] = {static_cast<uint8_t>(uuid.uuid.uuid16), static_cast<uint8_t>(uuid.uuid.uuid16 >> 8)};
|
||||
std::copy(service_data.data.begin(), service_data.data.end(), std::begin(data) + 2);
|
||||
|
||||
// dispatch data to parser
|
||||
optional<ParseResult> result = this->device_parser_(data, data_size);
|
||||
if (!result.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// publish sensor values
|
||||
if (result->temperature.has_value() && this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(*result->temperature);
|
||||
if (result->external_temperature.has_value() && this->external_temperature_ != nullptr)
|
||||
this->external_temperature_->publish_state(*result->external_temperature);
|
||||
if (result->humidity.has_value() && this->humidity_ != nullptr)
|
||||
this->humidity_->publish_state(*result->humidity);
|
||||
if (result->battery_level.has_value() && this->battery_level_ != nullptr)
|
||||
this->battery_level_->publish_state(*result->battery_level);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void ThermoProBLE::update_device_type_(const std::string &device_name) {
|
||||
// check for changed device name (should only happen on initial call)
|
||||
if (this->device_name_ == device_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remember device name
|
||||
this->device_name_ = device_name;
|
||||
|
||||
// try to find device parser
|
||||
for (const auto &mapping : DEVICE_PARSER_MAP) {
|
||||
if (device_name.starts_with(mapping.prefix)) {
|
||||
this->device_parser_ = mapping.parser;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// device type unknown
|
||||
this->device_parser_ = nullptr;
|
||||
ESP_LOGVV(TAG, "update_device_type_(): unknown device type %s.", device_name.c_str());
|
||||
}
|
||||
|
||||
static inline uint16_t read_uint16(const uint8_t *data, std::size_t offset) {
|
||||
return static_cast<uint16_t>(data[offset + 0]) | (static_cast<uint16_t>(data[offset + 1]) << 8);
|
||||
}
|
||||
|
||||
static inline int16_t read_int16(const uint8_t *data, std::size_t offset) {
|
||||
return static_cast<int16_t>(read_uint16(data, offset));
|
||||
}
|
||||
|
||||
static inline uint32_t read_uint32(const uint8_t *data, std::size_t offset) {
|
||||
return static_cast<uint32_t>(data[offset + 0]) | (static_cast<uint32_t>(data[offset + 1]) << 8) |
|
||||
(static_cast<uint32_t>(data[offset + 2]) << 16) | (static_cast<uint32_t>(data[offset + 3]) << 24);
|
||||
}
|
||||
|
||||
// Battery calculation used with permission from:
|
||||
// https://github.com/Bluetooth-Devices/thermopro-ble/blob/main/src/thermopro_ble/parser.py
|
||||
//
|
||||
// TP96x battery values appear to be a voltage reading, probably in millivolts.
|
||||
// This means that calculating battery life from it is a non-linear function.
|
||||
// Examining the curve, it looked fairly close to a curve from the tanh function.
|
||||
// So, I created a script to use Tensorflow to optimize an equation in the format
|
||||
// A*tanh(B*x+C)+D
|
||||
// Where A,B,C,D are the variables to optimize for. This yielded the below function
|
||||
static float tp96_battery(uint16_t voltage) {
|
||||
float level = 52.317286f * tanh(static_cast<float>(voltage) / 273.624277936f - 8.76485439394f) + 51.06925f;
|
||||
return std::max(0.0f, std::min(level, 100.0f));
|
||||
}
|
||||
|
||||
static optional<ParseResult> parse_tp972(const uint8_t *data, std::size_t data_size) {
|
||||
if (data_size != 23) {
|
||||
ESP_LOGVV(TAG, "parse_tp972(): payload has wrong size of %d (!= 23)!", data_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
ParseResult result;
|
||||
|
||||
// ambient temperature, 2 bytes, 16-bit unsigned integer, -54 °C offset
|
||||
result.external_temperature = static_cast<float>(read_uint16(data, 1)) - 54.0f;
|
||||
|
||||
// battery level, 2 bytes, 16-bit unsigned integer, voltage (convert to percentage)
|
||||
result.battery_level = tp96_battery(read_uint16(data, 3));
|
||||
|
||||
// internal temperature, 4 bytes, float, -54 °C offset
|
||||
result.temperature = static_cast<float>(read_uint32(data, 9)) - 54.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static optional<ParseResult> parse_tp96(const uint8_t *data, std::size_t data_size) {
|
||||
if (data_size != 7) {
|
||||
ESP_LOGVV(TAG, "parse_tp96(): payload has wrong size of %d (!= 7)!", data_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
ParseResult result;
|
||||
|
||||
// internal temperature, 2 bytes, 16-bit unsigned integer, -30 °C offset
|
||||
result.temperature = static_cast<float>(read_uint16(data, 1)) - 30.0f;
|
||||
|
||||
// battery level, 2 bytes, 16-bit unsigned integer, voltage (convert to percentage)
|
||||
result.battery_level = tp96_battery(read_uint16(data, 3));
|
||||
|
||||
// ambient temperature, 2 bytes, 16-bit unsigned integer, -30 °C offset
|
||||
result.external_temperature = static_cast<float>(read_uint16(data, 5)) - 30.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static optional<ParseResult> parse_tp3(const uint8_t *data, std::size_t data_size) {
|
||||
if (data_size < 6) {
|
||||
ESP_LOGVV(TAG, "parse_tp3(): payload has wrong size of %d (< 6)!", data_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
ParseResult result;
|
||||
|
||||
// temperature, 2 bytes, 16-bit signed integer, 0.1 °C
|
||||
result.temperature = static_cast<float>(read_int16(data, 1)) * 0.1f;
|
||||
|
||||
// humidity, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||
result.humidity = static_cast<float>(data[3]);
|
||||
|
||||
// battery level, 2 bits (0-2)
|
||||
result.battery_level = static_cast<float>(data[4] & 0x3) * 50.0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace esphome::thermopro_ble
|
||||
|
||||
#endif
|
||||
49
esphome/components/thermopro_ble/thermopro_ble.h
Normal file
49
esphome/components/thermopro_ble/thermopro_ble.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome::thermopro_ble {
|
||||
|
||||
struct ParseResult {
|
||||
optional<float> temperature;
|
||||
optional<float> external_temperature;
|
||||
optional<float> humidity;
|
||||
optional<float> battery_level;
|
||||
};
|
||||
|
||||
using DeviceParser = optional<ParseResult> (*)(const uint8_t *data, std::size_t data_size);
|
||||
|
||||
class ThermoProBLE : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { this->address_ = address; };
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; }
|
||||
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
|
||||
void set_external_temperature(sensor::Sensor *external_temperature) {
|
||||
this->external_temperature_ = external_temperature;
|
||||
}
|
||||
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
|
||||
void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; }
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
std::string device_name_;
|
||||
DeviceParser device_parser_{nullptr};
|
||||
sensor::Sensor *signal_strength_{nullptr};
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *external_temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
|
||||
void update_device_type_(const std::string &device_name);
|
||||
};
|
||||
|
||||
} // namespace esphome::thermopro_ble
|
||||
|
||||
#endif
|
||||
@@ -97,6 +97,7 @@ WIFI_MIN_AUTH_MODES = {
|
||||
VALIDATE_WIFI_MIN_AUTH_MODE = cv.enum(WIFI_MIN_AUTH_MODES, upper=True)
|
||||
WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition)
|
||||
WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition)
|
||||
WiFiAPActiveCondition = wifi_ns.class_("WiFiAPActiveCondition", Condition)
|
||||
WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action)
|
||||
WiFiDisableAction = wifi_ns.class_("WiFiDisableAction", automation.Action)
|
||||
WiFiConfigureAction = wifi_ns.class_(
|
||||
@@ -590,6 +591,11 @@ async def wifi_enabled_to_code(config, condition_id, template_arg, args):
|
||||
return cg.new_Pvariable(condition_id, template_arg)
|
||||
|
||||
|
||||
@automation.register_condition("wifi.ap_active", WiFiAPActiveCondition, cv.Schema({}))
|
||||
async def wifi_ap_active_to_code(config, condition_id, template_arg, args):
|
||||
return cg.new_Pvariable(condition_id, template_arg)
|
||||
|
||||
|
||||
@automation.register_action("wifi.enable", WiFiEnableAction, cv.Schema({}))
|
||||
async def wifi_enable_to_code(config, action_id, template_arg, args):
|
||||
return cg.new_Pvariable(action_id, template_arg)
|
||||
@@ -601,6 +607,8 @@ async def wifi_disable_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
|
||||
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
||||
WIFI_CALLBACKS_KEY = "wifi_callbacks"
|
||||
|
||||
|
||||
def request_wifi_scan_results():
|
||||
@@ -613,13 +621,41 @@ def request_wifi_scan_results():
|
||||
CORE.data[KEEP_SCAN_RESULTS_KEY] = True
|
||||
|
||||
|
||||
def enable_runtime_power_save_control():
|
||||
"""Enable runtime WiFi power save control.
|
||||
|
||||
Components that need to dynamically switch WiFi power saving on/off for latency
|
||||
performance (e.g., audio streaming, large data transfers) should call this
|
||||
function during their code generation. This enables the request_high_performance()
|
||||
and release_high_performance() APIs.
|
||||
|
||||
Only supported on ESP32.
|
||||
"""
|
||||
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
|
||||
|
||||
|
||||
def request_wifi_callbacks() -> None:
|
||||
"""Request that WiFi callbacks be compiled in.
|
||||
|
||||
Components that need to be notified about WiFi state changes (IP address changes,
|
||||
scan results, connection state) should call this function during their code generation.
|
||||
This enables the add_on_ip_state_callback(), add_on_wifi_scan_state_callback(),
|
||||
and add_on_wifi_connect_state_callback() APIs.
|
||||
"""
|
||||
CORE.data[WIFI_CALLBACKS_KEY] = True
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def final_step():
|
||||
"""Final code generation step to configure scan result retention."""
|
||||
"""Final code generation step to configure optional WiFi features."""
|
||||
if CORE.data.get(KEEP_SCAN_RESULTS_KEY, False):
|
||||
cg.add(
|
||||
cg.RawExpression("wifi::global_wifi_component->set_keep_scan_results(true)")
|
||||
)
|
||||
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
|
||||
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
|
||||
if CORE.data.get(WIFI_CALLBACKS_KEY, False):
|
||||
cg.add_define("USE_WIFI_CALLBACKS")
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
116
esphome/components/wifi/automation.h
Normal file
116
esphome/components/wifi/automation.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_WIFI
|
||||
#include "wifi_component.h"
|
||||
|
||||
namespace esphome::wifi {
|
||||
|
||||
template<typename... Ts> class WiFiConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_wifi_component->is_connected(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiEnabledCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiAPActiveCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiEnableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_wifi_component->enable(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiDisableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_wifi_component->disable(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, public Component {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, ssid)
|
||||
TEMPLATABLE_VALUE(std::string, password)
|
||||
TEMPLATABLE_VALUE(bool, save)
|
||||
TEMPLATABLE_VALUE(uint32_t, connection_timeout)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto ssid = this->ssid_.value(x...);
|
||||
auto password = this->password_.value(x...);
|
||||
// Avoid multiple calls
|
||||
if (this->connecting_)
|
||||
return;
|
||||
// If already connected to the same AP, do nothing
|
||||
if (global_wifi_component->wifi_ssid() == ssid) {
|
||||
// Callback to notify the user that the connection was successful
|
||||
this->connect_trigger_->trigger();
|
||||
return;
|
||||
}
|
||||
// Create a new WiFiAP object with the new SSID and password
|
||||
this->new_sta_.set_ssid(ssid);
|
||||
this->new_sta_.set_password(password);
|
||||
// Save the current STA
|
||||
this->old_sta_ = global_wifi_component->get_sta();
|
||||
// Disable WiFi
|
||||
global_wifi_component->disable();
|
||||
// Set the state to connecting
|
||||
this->connecting_ = true;
|
||||
// Store the new STA so once the WiFi is enabled, it will connect to it
|
||||
// This is necessary because the WiFiComponent will raise an error and fallback to the saved STA
|
||||
// if trying to connect to a new STA while already connected to another one
|
||||
if (this->save_.value(x...)) {
|
||||
global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password());
|
||||
} else {
|
||||
global_wifi_component->set_sta(new_sta_);
|
||||
}
|
||||
// Enable WiFi
|
||||
global_wifi_component->enable();
|
||||
// Set timeout for the connection
|
||||
this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() {
|
||||
// If the timeout is reached, stop connecting and revert to the old AP
|
||||
global_wifi_component->disable();
|
||||
global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password());
|
||||
global_wifi_component->enable();
|
||||
// Start a timeout for the fallback if the connection to the old AP fails
|
||||
this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() {
|
||||
this->connecting_ = false;
|
||||
this->error_trigger_->trigger();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }
|
||||
Trigger<> *get_error_trigger() const { return this->error_trigger_; }
|
||||
|
||||
void loop() override {
|
||||
if (!this->connecting_)
|
||||
return;
|
||||
if (global_wifi_component->is_connected()) {
|
||||
// The WiFi is connected, stop the timeout and reset the connecting flag
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->cancel_timeout("wifi-fallback-timeout");
|
||||
this->connecting_ = false;
|
||||
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
|
||||
// Callback to notify the user that the connection was successful
|
||||
this->connect_trigger_->trigger();
|
||||
} else {
|
||||
// Callback to notify the user that the connection failed
|
||||
this->error_trigger_->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool connecting_{false};
|
||||
WiFiAP new_sta_;
|
||||
WiFiAP old_sta_;
|
||||
Trigger<> *connect_trigger_{new Trigger<>()};
|
||||
Trigger<> *error_trigger_{new Trigger<>()};
|
||||
};
|
||||
|
||||
} // namespace esphome::wifi
|
||||
#endif
|
||||
@@ -37,8 +37,7 @@
|
||||
#include "esphome/components/esp32_improv/esp32_improv_component.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
namespace esphome::wifi {
|
||||
|
||||
static const char *const TAG = "wifi";
|
||||
|
||||
@@ -330,6 +329,19 @@ float WiFiComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
|
||||
void WiFiComponent::setup() {
|
||||
this->wifi_pre_setup_();
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
// Create semaphore for high-performance mode requests
|
||||
// Start at 0, increment on request, decrement on release
|
||||
this->high_performance_semaphore_ = xSemaphoreCreateCounting(UINT32_MAX, 0);
|
||||
if (this->high_performance_semaphore_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed semaphore");
|
||||
}
|
||||
|
||||
// Store the configured power save mode as baseline
|
||||
this->configured_power_save_ = this->power_save_;
|
||||
#endif
|
||||
|
||||
if (this->enable_on_boot_) {
|
||||
this->start();
|
||||
} else {
|
||||
@@ -371,6 +383,19 @@ void WiFiComponent::start() {
|
||||
ESP_LOGV(TAG, "Setting Output Power Option failed");
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
// Synchronize power_save_ with semaphore state before applying
|
||||
if (this->high_performance_semaphore_ != nullptr) {
|
||||
UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_);
|
||||
if (semaphore_count > 0) {
|
||||
this->power_save_ = WIFI_POWER_SAVE_NONE;
|
||||
this->is_high_performance_mode_ = true;
|
||||
} else {
|
||||
this->power_save_ = this->configured_power_save_;
|
||||
this->is_high_performance_mode_ = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!this->wifi_apply_power_save_()) {
|
||||
ESP_LOGV(TAG, "Setting Power Save Option failed");
|
||||
}
|
||||
@@ -525,11 +550,37 @@ void WiFiComponent::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
// Check if power save mode needs to be updated based on high-performance requests
|
||||
if (this->high_performance_semaphore_ != nullptr) {
|
||||
// Semaphore count directly represents active requests (starts at 0, increments on request)
|
||||
UBaseType_t semaphore_count = uxSemaphoreGetCount(this->high_performance_semaphore_);
|
||||
|
||||
if (semaphore_count > 0 && !this->is_high_performance_mode_) {
|
||||
// Transition to high-performance mode (no power save)
|
||||
ESP_LOGV(TAG, "Switching to high-performance mode (%" PRIu32 " active %s)", (uint32_t) semaphore_count,
|
||||
semaphore_count == 1 ? "request" : "requests");
|
||||
this->power_save_ = WIFI_POWER_SAVE_NONE;
|
||||
if (this->wifi_apply_power_save_()) {
|
||||
this->is_high_performance_mode_ = true;
|
||||
}
|
||||
} else if (semaphore_count == 0 && this->is_high_performance_mode_) {
|
||||
// Restore to configured power save mode
|
||||
ESP_LOGV(TAG, "Restoring power save mode to configured setting");
|
||||
this->power_save_ = this->configured_power_save_;
|
||||
if (this->wifi_apply_power_save_()) {
|
||||
this->is_high_performance_mode_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
WiFiComponent::WiFiComponent() { global_wifi_component = this; }
|
||||
|
||||
bool WiFiComponent::has_ap() const { return this->has_ap_; }
|
||||
bool WiFiComponent::is_ap_active() const { return this->state_ == WIFI_COMPONENT_STATE_AP; }
|
||||
bool WiFiComponent::has_sta() const { return !this->sta_.empty(); }
|
||||
#ifdef USE_WIFI_11KV_SUPPORT
|
||||
void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; }
|
||||
@@ -1567,7 +1618,12 @@ bool WiFiComponent::is_connected() {
|
||||
return this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED &&
|
||||
this->wifi_sta_connect_status_() == WiFiSTAConnectStatus::CONNECTED && !this->error_from_callback_;
|
||||
}
|
||||
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; }
|
||||
void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) {
|
||||
this->power_save_ = power_save;
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
this->configured_power_save_ = power_save;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
|
||||
|
||||
@@ -1586,6 +1642,38 @@ bool WiFiComponent::is_esp32_improv_active_() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
bool WiFiComponent::request_high_performance() {
|
||||
// Already configured for high performance - request satisfied
|
||||
if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Semaphore initialization failed
|
||||
if (this->high_performance_semaphore_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Give the semaphore (non-blocking). This increments the count.
|
||||
return xSemaphoreGive(this->high_performance_semaphore_) == pdTRUE;
|
||||
}
|
||||
|
||||
bool WiFiComponent::release_high_performance() {
|
||||
// Already configured for high performance - nothing to release
|
||||
if (this->configured_power_save_ == WIFI_POWER_SAVE_NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Semaphore initialization failed
|
||||
if (this->high_performance_semaphore_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Take the semaphore (non-blocking). This decrements the count.
|
||||
return xSemaphoreTake(this->high_performance_semaphore_, 0) == pdTRUE;
|
||||
}
|
||||
#endif // USE_ESP32 && USE_WIFI_RUNTIME_POWER_SAVE
|
||||
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
bool WiFiComponent::load_fast_connect_settings_(WiFiAP ¶ms) {
|
||||
SavedWifiFastConnectSettings fast_connect_save{};
|
||||
@@ -1725,6 +1813,5 @@ bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->
|
||||
|
||||
WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
} // namespace esphome::wifi
|
||||
#endif
|
||||
|
||||
@@ -49,8 +49,12 @@ extern "C" {
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::wifi {
|
||||
|
||||
/// Sentinel value for RSSI when WiFi is not connected
|
||||
static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127;
|
||||
@@ -308,6 +312,7 @@ class WiFiComponent : public Component {
|
||||
|
||||
bool has_sta() const;
|
||||
bool has_ap() const;
|
||||
bool is_ap_active() const;
|
||||
|
||||
#ifdef USE_WIFI_11KV_SUPPORT
|
||||
void set_btm(bool btm);
|
||||
@@ -364,6 +369,58 @@ class WiFiComponent : public Component {
|
||||
|
||||
int32_t get_wifi_channel();
|
||||
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
/// Add a callback that will be called on configuration changes (IP change, SSID change, etc.)
|
||||
/// @param callback The callback to be called; template arguments are:
|
||||
/// - IP addresses
|
||||
/// - DNS address 1
|
||||
/// - DNS address 2
|
||||
void add_on_ip_state_callback(
|
||||
std::function<void(network::IPAddresses, network::IPAddress, network::IPAddress)> &&callback) {
|
||||
this->ip_state_callback_.add(std::move(callback));
|
||||
}
|
||||
/// - Wi-Fi scan results
|
||||
void add_on_wifi_scan_state_callback(std::function<void(wifi_scan_vector_t<WiFiScanResult> &)> &&callback) {
|
||||
this->wifi_scan_state_callback_.add(std::move(callback));
|
||||
}
|
||||
/// - Wi-Fi SSID
|
||||
/// - Wi-Fi BSSID
|
||||
void add_on_wifi_connect_state_callback(std::function<void(std::string, wifi::bssid_t)> &&callback) {
|
||||
this->wifi_connect_state_callback_.add(std::move(callback));
|
||||
}
|
||||
#endif // USE_WIFI_CALLBACKS
|
||||
|
||||
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
|
||||
/** Request high-performance mode (no power saving) for improved WiFi latency.
|
||||
*
|
||||
* Components that need maximum WiFi performance (e.g., audio streaming, large data transfers)
|
||||
* can call this method to temporarily disable WiFi power saving. Multiple components can
|
||||
* request high performance simultaneously using a counting semaphore.
|
||||
*
|
||||
* Power saving will be restored to the YAML-configured mode when all components have
|
||||
* called release_high_performance().
|
||||
*
|
||||
* Note: Only supported on ESP32.
|
||||
*
|
||||
* @return true if request was satisfied (high-performance mode active or already configured),
|
||||
* false if operation failed (semaphore error)
|
||||
*/
|
||||
bool request_high_performance();
|
||||
|
||||
/** Release a high-performance mode request.
|
||||
*
|
||||
* Should be called when a component no longer needs maximum WiFi latency.
|
||||
* When all requests are released (semaphore count reaches zero), WiFi power saving
|
||||
* is restored to the YAML-configured mode.
|
||||
*
|
||||
* Note: Only supported on ESP32.
|
||||
*
|
||||
* @return true if release was successful (or already in high-performance config),
|
||||
* false if operation failed (semaphore error)
|
||||
*/
|
||||
bool release_high_performance();
|
||||
#endif // USE_WIFI_RUNTIME_POWER_SAVE
|
||||
|
||||
protected:
|
||||
#ifdef USE_WIFI_AP
|
||||
void setup_ap_config_();
|
||||
@@ -489,6 +546,11 @@ class WiFiComponent : public Component {
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
optional<float> output_power_;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
CallbackManager<void(network::IPAddresses, network::IPAddress, network::IPAddress)> ip_state_callback_;
|
||||
CallbackManager<void(wifi_scan_vector_t<WiFiScanResult> &)> wifi_scan_state_callback_;
|
||||
CallbackManager<void(std::string, wifi::bssid_t)> wifi_connect_state_callback_;
|
||||
#endif // USE_WIFI_CALLBACKS
|
||||
ESPPreferenceObject pref_;
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
ESPPreferenceObject fast_connect_pref_;
|
||||
@@ -534,6 +596,12 @@ class WiFiComponent : public Component {
|
||||
bool keep_scan_results_{false};
|
||||
bool did_scan_this_cycle_{false};
|
||||
bool skip_cooldown_next_cycle_{false};
|
||||
#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_POWER_SAVE)
|
||||
WiFiPowerSaveMode configured_power_save_{WIFI_POWER_SAVE_NONE};
|
||||
bool is_high_performance_mode_{false};
|
||||
|
||||
SemaphoreHandle_t high_performance_semaphore_{nullptr};
|
||||
#endif
|
||||
|
||||
// Pointers at the end (naturally aligned)
|
||||
Trigger<> *connect_trigger_{new Trigger<>()};
|
||||
@@ -547,107 +615,5 @@ class WiFiComponent : public Component {
|
||||
|
||||
extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
template<typename... Ts> class WiFiConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_wifi_component->is_connected(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiEnabledCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiEnableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_wifi_component->enable(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiDisableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_wifi_component->disable(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, public Component {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(std::string, ssid)
|
||||
TEMPLATABLE_VALUE(std::string, password)
|
||||
TEMPLATABLE_VALUE(bool, save)
|
||||
TEMPLATABLE_VALUE(uint32_t, connection_timeout)
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
auto ssid = this->ssid_.value(x...);
|
||||
auto password = this->password_.value(x...);
|
||||
// Avoid multiple calls
|
||||
if (this->connecting_)
|
||||
return;
|
||||
// If already connected to the same AP, do nothing
|
||||
if (global_wifi_component->wifi_ssid() == ssid) {
|
||||
// Callback to notify the user that the connection was successful
|
||||
this->connect_trigger_->trigger();
|
||||
return;
|
||||
}
|
||||
// Create a new WiFiAP object with the new SSID and password
|
||||
this->new_sta_.set_ssid(ssid);
|
||||
this->new_sta_.set_password(password);
|
||||
// Save the current STA
|
||||
this->old_sta_ = global_wifi_component->get_sta();
|
||||
// Disable WiFi
|
||||
global_wifi_component->disable();
|
||||
// Set the state to connecting
|
||||
this->connecting_ = true;
|
||||
// Store the new STA so once the WiFi is enabled, it will connect to it
|
||||
// This is necessary because the WiFiComponent will raise an error and fallback to the saved STA
|
||||
// if trying to connect to a new STA while already connected to another one
|
||||
if (this->save_.value(x...)) {
|
||||
global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password());
|
||||
} else {
|
||||
global_wifi_component->set_sta(new_sta_);
|
||||
}
|
||||
// Enable WiFi
|
||||
global_wifi_component->enable();
|
||||
// Set timeout for the connection
|
||||
this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() {
|
||||
// If the timeout is reached, stop connecting and revert to the old AP
|
||||
global_wifi_component->disable();
|
||||
global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password());
|
||||
global_wifi_component->enable();
|
||||
// Start a timeout for the fallback if the connection to the old AP fails
|
||||
this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() {
|
||||
this->connecting_ = false;
|
||||
this->error_trigger_->trigger();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }
|
||||
Trigger<> *get_error_trigger() const { return this->error_trigger_; }
|
||||
|
||||
void loop() override {
|
||||
if (!this->connecting_)
|
||||
return;
|
||||
if (global_wifi_component->is_connected()) {
|
||||
// The WiFi is connected, stop the timeout and reset the connecting flag
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->cancel_timeout("wifi-fallback-timeout");
|
||||
this->connecting_ = false;
|
||||
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
|
||||
// Callback to notify the user that the connection was successful
|
||||
this->connect_trigger_->trigger();
|
||||
} else {
|
||||
// Callback to notify the user that the connection failed
|
||||
this->error_trigger_->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool connecting_{false};
|
||||
WiFiAP new_sta_;
|
||||
WiFiAP old_sta_;
|
||||
Trigger<> *connect_trigger_{new Trigger<>()};
|
||||
Trigger<> *error_trigger_{new Trigger<>()};
|
||||
};
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
} // namespace esphome::wifi
|
||||
#endif
|
||||
|
||||
@@ -38,8 +38,7 @@ extern "C" {
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
namespace esphome::wifi {
|
||||
|
||||
static const char *const TAG = "wifi_esp8266";
|
||||
|
||||
@@ -514,6 +513,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
|
||||
it.channel);
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(),
|
||||
global_wifi_component->wifi_bssid());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case EVENT_STAMODE_DISCONNECTED: {
|
||||
@@ -533,6 +536,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case EVENT_STAMODE_AUTHMODE_CHANGE: {
|
||||
@@ -555,6 +561,11 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(),
|
||||
format_ip_addr(it.mask).c_str());
|
||||
s_sta_got_ip = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->ip_state_callback_.call(global_wifi_component->wifi_sta_ip_addresses(),
|
||||
global_wifi_component->get_dns_address(0),
|
||||
global_wifi_component->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case EVENT_STAMODE_DHCP_TIMEOUT: {
|
||||
@@ -729,6 +740,9 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
it->is_hidden != 0);
|
||||
}
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
@@ -885,8 +899,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t
|
||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
|
||||
void WiFiComponent::wifi_loop_() {}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
|
||||
} // namespace esphome::wifi
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
namespace esphome::wifi {
|
||||
|
||||
static const char *const TAG = "wifi_esp32";
|
||||
|
||||
@@ -728,6 +727,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
const auto &it = data->data.sta_disconnected;
|
||||
@@ -751,6 +753,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
error_from_callback_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) {
|
||||
const auto &it = data->data.ip_got_ip;
|
||||
@@ -759,12 +764,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw));
|
||||
this->got_ipv4_address_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) {
|
||||
const auto &it = data->data.ip_got_ip6;
|
||||
ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip));
|
||||
this->num_ipv6_addresses_++;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) {
|
||||
@@ -804,6 +815,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN,
|
||||
ssid.empty());
|
||||
}
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) {
|
||||
ESP_LOGV(TAG, "AP start");
|
||||
@@ -1088,8 +1102,6 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
|
||||
return network::IPAddress(dns_ip);
|
||||
}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
|
||||
} // namespace esphome::wifi
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
namespace esphome::wifi {
|
||||
|
||||
static const char *const TAG = "wifi_lt";
|
||||
|
||||
@@ -288,7 +287,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
|
||||
@@ -314,6 +315,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
|
||||
@@ -335,11 +339,17 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(),
|
||||
format_ip4_addr(WiFi.gatewayIP()).c_str());
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
|
||||
// auto it = info.got_ip.ip_info;
|
||||
ESP_LOGV(TAG, "Got IPv6");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
|
||||
@@ -433,6 +443,9 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
@@ -493,8 +506,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}
|
||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
|
||||
void WiFiComponent::wifi_loop_() {}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
|
||||
} // namespace esphome::wifi
|
||||
#endif // USE_LIBRETINY
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "wifi_component.h"
|
||||
|
||||
#ifdef USE_WIFI
|
||||
@@ -15,11 +14,14 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi {
|
||||
namespace esphome::wifi {
|
||||
|
||||
static const char *const TAG = "wifi_pico_w";
|
||||
|
||||
// Track previous state for detecting changes
|
||||
static bool s_sta_was_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool s_sta_had_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
|
||||
if (sta.has_value()) {
|
||||
if (sta.value()) {
|
||||
@@ -51,7 +53,7 @@ bool WiFiComponent::wifi_apply_power_save_() {
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
// TODO: The driver doesnt seem to have an API for this
|
||||
// TODO: The driver doesn't seem to have an API for this
|
||||
bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; }
|
||||
|
||||
bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
@@ -219,16 +221,61 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
|
||||
}
|
||||
|
||||
void WiFiComponent::wifi_loop_() {
|
||||
// Handle scan completion
|
||||
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
|
||||
this->scan_done_ = true;
|
||||
ESP_LOGV(TAG, "Scan done");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_scan_state_callback_.call(this->scan_result_);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Poll for connection state changes
|
||||
// The arduino-pico WiFi library doesn't have event callbacks like ESP8266/ESP32,
|
||||
// so we need to poll the link status to detect state changes
|
||||
auto status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA);
|
||||
bool is_connected = (status == CYW43_LINK_UP);
|
||||
|
||||
// Detect connection state change
|
||||
if (is_connected && !s_sta_was_connected) {
|
||||
// Just connected
|
||||
s_sta_was_connected = true;
|
||||
ESP_LOGV(TAG, "Connected");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
|
||||
#endif
|
||||
} else if (!is_connected && s_sta_was_connected) {
|
||||
// Just disconnected
|
||||
s_sta_was_connected = false;
|
||||
s_sta_had_ip = false;
|
||||
ESP_LOGV(TAG, "Disconnected");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Detect IP address changes (only when connected)
|
||||
if (is_connected) {
|
||||
bool has_ip = false;
|
||||
// Check for any IP address (IPv4 or IPv6)
|
||||
for (auto addr : addrList) {
|
||||
has_ip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_ip && !s_sta_had_ip) {
|
||||
// Just got IP address
|
||||
s_sta_had_ip = true;
|
||||
ESP_LOGV(TAG, "Got IP address");
|
||||
#ifdef USE_WIFI_CALLBACKS
|
||||
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiComponent::wifi_pre_setup_() {}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace esphome
|
||||
|
||||
} // namespace esphome::wifi
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -15,31 +15,27 @@ DEPENDENCIES = ["wifi"]
|
||||
|
||||
wifi_info_ns = cg.esphome_ns.namespace("wifi_info")
|
||||
IPAddressWiFiInfo = wifi_info_ns.class_(
|
||||
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
ScanResultsWiFiInfo = wifi_info_ns.class_(
|
||||
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
SSIDWiFiInfo = wifi_info_ns.class_(
|
||||
"SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component)
|
||||
BSSIDWiFiInfo = wifi_info_ns.class_(
|
||||
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
MacAddressWifiInfo = wifi_info_ns.class_(
|
||||
"MacAddressWifiInfo", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
DNSAddressWifiInfo = wifi_info_ns.class_(
|
||||
"DNSAddressWifiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
"DNSAddressWifiInfo", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
|
||||
IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
.extend(
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -49,22 +45,31 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema(
|
||||
ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
),
|
||||
cv.Optional(CONF_SSID): text_sensor.text_sensor_schema(
|
||||
SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
),
|
||||
cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema(
|
||||
BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
|
||||
MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
|
||||
DNSAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Keys that require WiFi callbacks
|
||||
_NETWORK_INFO_KEYS = {
|
||||
CONF_SSID,
|
||||
CONF_BSSID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_DNS_ADDRESS,
|
||||
CONF_SCAN_RESULTS,
|
||||
}
|
||||
|
||||
|
||||
async def setup_conf(config, key):
|
||||
if key in config:
|
||||
@@ -74,6 +79,10 @@ async def setup_conf(config, key):
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Request WiFi callbacks for any sensor that needs them
|
||||
if _NETWORK_INFO_KEYS.intersection(config):
|
||||
wifi.request_wifi_callbacks()
|
||||
|
||||
await setup_conf(config, CONF_SSID)
|
||||
await setup_conf(config, CONF_BSSID)
|
||||
await setup_conf(config, CONF_MAC_ADDRESS)
|
||||
|
||||
@@ -2,18 +2,121 @@
|
||||
#ifdef USE_WIFI
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi_info {
|
||||
namespace esphome::wifi_info {
|
||||
|
||||
static const char *const TAG = "wifi_info";
|
||||
|
||||
static constexpr size_t MAX_STATE_LENGTH = 255;
|
||||
|
||||
/********************
|
||||
* IPAddressWiFiInfo
|
||||
*******************/
|
||||
|
||||
void IPAddressWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_ip_state_callback(
|
||||
[this](network::IPAddresses ips, network::IPAddress dns1_ip, network::IPAddress dns2_ip) {
|
||||
this->state_callback_(ips);
|
||||
});
|
||||
}
|
||||
|
||||
void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); }
|
||||
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
|
||||
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
|
||||
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
|
||||
void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); }
|
||||
|
||||
void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) {
|
||||
this->publish_state(ips[0].str());
|
||||
uint8_t sensor = 0;
|
||||
for (auto &ip : ips) {
|
||||
if (ip.is_set()) {
|
||||
if (this->ip_sensors_[sensor] != nullptr) {
|
||||
this->ip_sensors_[sensor]->publish_state(ip.str());
|
||||
}
|
||||
sensor++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************
|
||||
* DNSAddressWifiInfo
|
||||
********************/
|
||||
|
||||
void DNSAddressWifiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_ip_state_callback(
|
||||
[this](network::IPAddresses ips, network::IPAddress dns1_ip, network::IPAddress dns2_ip) {
|
||||
this->state_callback_(dns1_ip, dns2_ip);
|
||||
});
|
||||
}
|
||||
|
||||
void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); }
|
||||
|
||||
} // namespace wifi_info
|
||||
} // namespace esphome
|
||||
void DNSAddressWifiInfo::state_callback_(network::IPAddress dns1_ip, network::IPAddress dns2_ip) {
|
||||
std::string dns_results = dns1_ip.str() + " " + dns2_ip.str();
|
||||
this->publish_state(dns_results);
|
||||
}
|
||||
|
||||
/**********************
|
||||
* ScanResultsWiFiInfo
|
||||
*********************/
|
||||
|
||||
void ScanResultsWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_scan_state_callback(
|
||||
[this](const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) { this->state_callback_(results); });
|
||||
}
|
||||
|
||||
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
|
||||
|
||||
void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
|
||||
std::string scan_results;
|
||||
for (const auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
|
||||
scan_results += scan.get_ssid();
|
||||
scan_results += ": ";
|
||||
scan_results += esphome::to_string(scan.get_rssi());
|
||||
scan_results += "dB\n";
|
||||
}
|
||||
// There's a limit of 255 characters per state; longer states just don't get sent so we truncate it
|
||||
if (scan_results.length() > MAX_STATE_LENGTH) {
|
||||
scan_results.resize(MAX_STATE_LENGTH);
|
||||
}
|
||||
this->publish_state(scan_results);
|
||||
}
|
||||
|
||||
/***************
|
||||
* SSIDWiFiInfo
|
||||
**************/
|
||||
|
||||
void SSIDWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
|
||||
[this](const std::string &ssid, wifi::bssid_t bssid) { this->state_callback_(ssid); });
|
||||
}
|
||||
|
||||
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
|
||||
|
||||
void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); }
|
||||
|
||||
/****************
|
||||
* BSSIDWiFiInfo
|
||||
***************/
|
||||
|
||||
void BSSIDWiFiInfo::setup() {
|
||||
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
|
||||
[this](const std::string &ssid, wifi::bssid_t bssid) { this->state_callback_(bssid); });
|
||||
}
|
||||
|
||||
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
|
||||
|
||||
void BSSIDWiFiInfo::state_callback_(wifi::bssid_t bssid) {
|
||||
char buf[18] = "unknown";
|
||||
if (mac_address_is_valid(bssid.data())) {
|
||||
format_mac_addr_upper(bssid.data(), buf);
|
||||
}
|
||||
this->publish_state(buf);
|
||||
}
|
||||
/*********************
|
||||
* MacAddressWifiInfo
|
||||
********************/
|
||||
|
||||
void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); }
|
||||
|
||||
} // namespace esphome::wifi_info
|
||||
#endif
|
||||
|
||||
@@ -7,121 +7,54 @@
|
||||
#ifdef USE_WIFI
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace wifi_info {
|
||||
namespace esphome::wifi_info {
|
||||
|
||||
static constexpr size_t MAX_STATE_LENGTH = 255;
|
||||
|
||||
class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses();
|
||||
if (ips != this->last_ips_) {
|
||||
this->last_ips_ = ips;
|
||||
this->publish_state(ips[0].str());
|
||||
uint8_t sensor = 0;
|
||||
for (auto &ip : ips) {
|
||||
if (ip.is_set()) {
|
||||
if (this->ip_sensors_[sensor] != nullptr) {
|
||||
this->ip_sensors_[sensor]->publish_state(ip.str());
|
||||
}
|
||||
sensor++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
|
||||
|
||||
protected:
|
||||
network::IPAddresses last_ips_;
|
||||
void state_callback_(const network::IPAddresses &ips);
|
||||
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
|
||||
};
|
||||
|
||||
class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
class DNSAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
auto dns_one = wifi::global_wifi_component->get_dns_address(0);
|
||||
auto dns_two = wifi::global_wifi_component->get_dns_address(1);
|
||||
|
||||
std::string dns_results = dns_one.str() + " " + dns_two.str();
|
||||
|
||||
if (dns_results != this->last_results_) {
|
||||
this->last_results_ = dns_results;
|
||||
this->publish_state(dns_results);
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::string last_results_;
|
||||
void state_callback_(network::IPAddress dns1_ip, network::IPAddress dns2_ip);
|
||||
};
|
||||
|
||||
class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
std::string scan_results;
|
||||
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
|
||||
scan_results += scan.get_ssid();
|
||||
scan_results += ": ";
|
||||
scan_results += esphome::to_string(scan.get_rssi());
|
||||
scan_results += "dB\n";
|
||||
}
|
||||
|
||||
// There's a limit of 255 characters per state.
|
||||
// Longer states just don't get sent so we truncate it.
|
||||
if (scan_results.length() > MAX_STATE_LENGTH) {
|
||||
scan_results.resize(MAX_STATE_LENGTH);
|
||||
}
|
||||
if (this->last_scan_results_ != scan_results) {
|
||||
this->last_scan_results_ = scan_results;
|
||||
this->publish_state(scan_results);
|
||||
}
|
||||
}
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::string last_scan_results_;
|
||||
void state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results);
|
||||
};
|
||||
|
||||
class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
std::string ssid = wifi::global_wifi_component->wifi_ssid();
|
||||
if (this->last_ssid_ != ssid) {
|
||||
this->last_ssid_ = ssid;
|
||||
this->publish_state(this->last_ssid_);
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::string last_ssid_;
|
||||
void state_callback_(const std::string &ssid);
|
||||
};
|
||||
|
||||
class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid();
|
||||
if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) {
|
||||
std::copy(bssid.begin(), bssid.end(), last_bssid_.begin());
|
||||
char buf[18];
|
||||
format_mac_addr_upper(bssid.data(), buf);
|
||||
this->publish_state(buf);
|
||||
}
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
wifi::bssid_t last_bssid_;
|
||||
void state_callback_(wifi::bssid_t bssid);
|
||||
};
|
||||
|
||||
class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
@@ -133,6 +66,5 @@ class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
} // namespace wifi_info
|
||||
} // namespace esphome
|
||||
} // namespace esphome::wifi_info
|
||||
#endif
|
||||
|
||||
@@ -211,6 +211,8 @@
|
||||
#define USE_WEBSERVER_SORTING
|
||||
#define USE_WIFI_11KV_SUPPORT
|
||||
#define USE_WIFI_FAST_CONNECT
|
||||
#define USE_WIFI_CALLBACKS
|
||||
#define USE_WIFI_RUNTIME_POWER_SAVE
|
||||
#define USB_HOST_MAX_REQUESTS 16
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
@@ -22,7 +22,7 @@ pillow==11.3.0
|
||||
cairosvg==2.8.2
|
||||
freetype-py==2.5.1
|
||||
jinja2==3.1.6
|
||||
bleak==1.1.1
|
||||
bleak==2.0.0
|
||||
|
||||
# esp-idf >= 5.0 requires this
|
||||
pyparsing >= 3.0
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- wait_until:
|
||||
condition:
|
||||
api.connected:
|
||||
state_subscription_only: true
|
||||
- homeassistant.event:
|
||||
event: esphome.button_pressed
|
||||
data:
|
||||
|
||||
@@ -39,6 +39,15 @@ sensor:
|
||||
return 0.0;
|
||||
update_interval: 60s
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: "Template text"
|
||||
optimistic: true
|
||||
min_length: 0
|
||||
max_length: 100
|
||||
mode: text
|
||||
initial_value: "Hello World"
|
||||
|
||||
text_sensor:
|
||||
- platform: version
|
||||
name: "ESPHome Version"
|
||||
@@ -52,6 +61,25 @@ text_sensor:
|
||||
return {"Goodbye (cruel) World"};
|
||||
update_interval: 60s
|
||||
|
||||
event:
|
||||
- platform: template
|
||||
name: "Template Event"
|
||||
id: template_event1
|
||||
event_types:
|
||||
- "custom_event_1"
|
||||
- "custom_event_2"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Template Event Button"
|
||||
on_press:
|
||||
- logger.log: "Template Event Button pressed"
|
||||
- lambda: |-
|
||||
ESP_LOGD("template_event_button", "Template Event Button pressed");
|
||||
- event.trigger:
|
||||
id: template_event1
|
||||
event_type: custom_event_1
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: template_binary_sensor1
|
||||
|
||||
13
tests/components/thermopro_ble/common.yaml
Normal file
13
tests/components/thermopro_ble/common.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
esp32_ble_tracker:
|
||||
|
||||
sensor:
|
||||
- platform: thermopro_ble
|
||||
mac_address: FE:74:B8:6A:97:B7
|
||||
temperature:
|
||||
name: "ThermoPro Temperature"
|
||||
humidity:
|
||||
name: "ThermoPro Humidity"
|
||||
battery_level:
|
||||
name: "ThermoPro Battery Level"
|
||||
signal_strength:
|
||||
name: "ThermoPro Signal Strength"
|
||||
4
tests/components/thermopro_ble/test.esp32-idf.yaml
Normal file
4
tests/components/thermopro_ble/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
ble: !include ../../test_build_components/common/ble/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -10,6 +10,10 @@ esphome:
|
||||
- logger.log: "Connected to WiFi!"
|
||||
on_error:
|
||||
- logger.log: "Failed to connect to WiFi!"
|
||||
- if:
|
||||
condition: wifi.ap_active
|
||||
then:
|
||||
- logger.log: "WiFi AP is active!"
|
||||
|
||||
wifi:
|
||||
networks:
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
psram:
|
||||
|
||||
# Tests the high performance request and release; requires the USE_WIFI_RUNTIME_POWER_SAVE define
|
||||
esphome:
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
|
||||
on_boot:
|
||||
- then:
|
||||
- lambda: |-
|
||||
esphome::wifi::global_wifi_component->request_high_performance();
|
||||
esphome::wifi::global_wifi_component->release_high_performance();
|
||||
|
||||
wifi:
|
||||
use_psram: true
|
||||
min_auth_mode: WPA
|
||||
|
||||
Reference in New Issue
Block a user