1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-29 00:22:21 +01:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-09-25 10:56:43 -05:00
36 changed files with 392 additions and 166 deletions

View File

@@ -738,11 +738,11 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
return clean_mqtt(config, args)
def command_clean_platform(args: ArgsProtocol, config: ConfigType) -> int | None:
def command_clean_all(args: ArgsProtocol) -> int | None:
try:
writer.clean_platform()
writer.clean_all(args.configuration)
except OSError as err:
_LOGGER.error("Error deleting platform files: %s", err)
_LOGGER.error("Error cleaning all files: %s", err)
return 1
_LOGGER.info("Done!")
return 0
@@ -938,6 +938,7 @@ PRE_CONFIG_ACTIONS = {
"dashboard": command_dashboard,
"vscode": command_vscode,
"update-all": command_update_all,
"clean-all": command_clean_all,
}
POST_CONFIG_ACTIONS = {
@@ -948,7 +949,6 @@ POST_CONFIG_ACTIONS = {
"run": command_run,
"clean": command_clean,
"clean-mqtt": command_clean_mqtt,
"clean-platform": command_clean_platform,
"mqtt-fingerprint": command_mqtt_fingerprint,
"idedata": command_idedata,
"rename": command_rename,
@@ -958,7 +958,6 @@ POST_CONFIG_ACTIONS = {
SIMPLE_CONFIG_ACTIONS = [
"clean",
"clean-mqtt",
"clean-platform",
"config",
]
@@ -1174,11 +1173,9 @@ def parse_args(argv):
"configuration", help="Your YAML configuration file(s).", nargs="+"
)
parser_clean = subparsers.add_parser(
"clean-platform", help="Delete all platform files."
)
parser_clean.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs="+"
parser_clean_all = subparsers.add_parser("clean-all", help="Clean all files.")
parser_clean_all.add_argument(
"configuration", help="Your YAML configuration directory.", nargs="*"
)
parser_dashboard = subparsers.add_parser(
@@ -1227,7 +1224,7 @@ def parse_args(argv):
parser_update = subparsers.add_parser("update-all")
parser_update.add_argument(
"configuration", help="Your YAML configuration file directories.", nargs="+"
"configuration", help="Your YAML configuration file or directory.", nargs="+"
)
parser_idedata = subparsers.add_parser("idedata")

View File

@@ -19,6 +19,15 @@ std::string build_json(const json_build_t &f) {
bool parse_json(const std::string &data, const json_parse_t &f) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonDocument doc = parse_json(data);
if (doc.overflowed() || doc.isNull())
return false;
return f(doc.as<JsonObject>());
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
}
JsonDocument parse_json(const std::string &data) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
#ifdef USE_PSRAM
auto doc_allocator = SpiRamAllocator();
JsonDocument json_document(&doc_allocator);
@@ -27,20 +36,18 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
#endif
if (json_document.overflowed()) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
return false;
return JsonObject(); // return unbound object
}
DeserializationError err = deserializeJson(json_document, data);
JsonObject root = json_document.as<JsonObject>();
if (err == DeserializationError::Ok) {
return f(root);
return json_document;
} else if (err == DeserializationError::NoMemory) {
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
return false;
return JsonObject(); // return unbound object
}
ESP_LOGE(TAG, "Parse error: %s", err.c_str());
return false;
return JsonObject(); // return unbound object
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
}

View File

@@ -49,6 +49,8 @@ std::string build_json(const json_build_t &f);
/// Parse a JSON string and run the provided json parse function if it's valid.
bool parse_json(const std::string &data, const json_parse_t &f);
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
JsonDocument parse_json(const std::string &data);
/// Builder class for creating JSON documents without lambdas
class JsonBuilder {

View File

@@ -66,7 +66,7 @@ CONFIG_SCHEMA = (
),
cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure,
cv.Optional(CONF_TEMPERATURE_OFFSET): cv.All(
cv.temperature,
cv.temperature_delta,
cv.float_range(min=0, max=655.35),
),
cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All(

View File

View File

@@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
CONF_WTS01_ID = "wts01_id"
CODEOWNERS = ["@alepee"]
DEPENDENCIES = ["uart"]
wts01_ns = cg.esphome_ns.namespace("wts01")
WTS01Sensor = wts01_ns.class_(
"WTS01Sensor", cg.Component, uart.UARTDevice, sensor.Sensor
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
WTS01Sensor,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"wts01",
baud_rate=9600,
require_rx=True,
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

View File

@@ -0,0 +1,91 @@
#include "wts01.h"
#include "esphome/core/log.h"
#include <cmath>
namespace esphome {
namespace wts01 {
constexpr uint8_t HEADER_1 = 0x55;
constexpr uint8_t HEADER_2 = 0x01;
constexpr uint8_t HEADER_3 = 0x01;
constexpr uint8_t HEADER_4 = 0x04;
static const char *const TAG = "wts01";
void WTS01Sensor::loop() {
// Process all available data at once
while (this->available()) {
uint8_t c;
if (this->read_byte(&c)) {
this->handle_char_(c);
}
}
}
void WTS01Sensor::dump_config() { LOG_SENSOR("", "WTS01 Sensor", this); }
void WTS01Sensor::handle_char_(uint8_t c) {
// State machine for processing the header. Reset if something doesn't match.
if (this->buffer_pos_ == 0 && c != HEADER_1) {
return;
}
if (this->buffer_pos_ == 1 && c != HEADER_2) {
this->buffer_pos_ = 0;
return;
}
if (this->buffer_pos_ == 2 && c != HEADER_3) {
this->buffer_pos_ = 0;
return;
}
if (this->buffer_pos_ == 3 && c != HEADER_4) {
this->buffer_pos_ = 0;
return;
}
// Add byte to buffer
this->buffer_[this->buffer_pos_++] = c;
// Process complete packet
if (this->buffer_pos_ >= PACKET_SIZE) {
this->process_packet_();
this->buffer_pos_ = 0;
}
}
void WTS01Sensor::process_packet_() {
// Based on Tasmota implementation
// Format: 55 01 01 04 01 11 16 12 95
// header T Td Ck - T = Temperature, Td = Temperature decimal, Ck = Checksum
uint8_t calculated_checksum = 0;
for (uint8_t i = 0; i < PACKET_SIZE - 1; i++) {
calculated_checksum += this->buffer_[i];
}
uint8_t received_checksum = this->buffer_[PACKET_SIZE - 1];
if (calculated_checksum != received_checksum) {
ESP_LOGW(TAG, "WTS01 Checksum doesn't match: 0x%02X != 0x%02X", received_checksum, calculated_checksum);
return;
}
// Extract temperature value
int8_t temp = this->buffer_[6];
int32_t sign = 1;
// Handle negative temperatures
if (temp < 0) {
sign = -1;
}
// Calculate temperature (temp + decimal/100)
float temperature = static_cast<float>(temp) + (sign * static_cast<float>(this->buffer_[7]) / 100.0f);
ESP_LOGV(TAG, "Received new temperature: %.2f°C", temperature);
this->publish_state(temperature);
}
} // namespace wts01
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace wts01 {
constexpr uint8_t PACKET_SIZE = 9;
class WTS01Sensor : public sensor::Sensor, public uart::UARTDevice, public Component {
public:
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
uint8_t buffer_[PACKET_SIZE];
uint8_t buffer_pos_{0};
void handle_char_(uint8_t c);
void process_packet_();
};
} // namespace wts01
} // namespace esphome

View File

@@ -479,10 +479,12 @@ class EsphomeCleanMqttHandler(EsphomeCommandWebSocket):
return [*DASHBOARD_COMMAND, "clean-mqtt", config_file]
class EsphomeCleanPlatformHandler(EsphomeCommandWebSocket):
class EsphomeCleanAllHandler(EsphomeCommandWebSocket):
async def build_command(self, json_message: dict[str, Any]) -> list[str]:
config_file = settings.rel_path(json_message["configuration"])
return [*DASHBOARD_COMMAND, "clean-platform", config_file]
clean_build_dir = json_message.get("clean_build_dir", True)
if clean_build_dir:
return [*DASHBOARD_COMMAND, "clean-all", settings.config_dir]
return [*DASHBOARD_COMMAND, "clean-all"]
class EsphomeCleanHandler(EsphomeCommandWebSocket):
@@ -1319,7 +1321,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
(f"{rel}compile", EsphomeCompileHandler),
(f"{rel}validate", EsphomeValidateHandler),
(f"{rel}clean-mqtt", EsphomeCleanMqttHandler),
(f"{rel}clean-platform", EsphomeCleanPlatformHandler),
(f"{rel}clean-all", EsphomeCleanAllHandler),
(f"{rel}clean", EsphomeCleanHandler),
(f"{rel}vscode", EsphomeVscodeHandler),
(f"{rel}ace", EsphomeAceEditorHandler),

View File

@@ -335,13 +335,15 @@ def clean_build():
shutil.rmtree(cache_dir)
def clean_platform():
def clean_all(configuration: list[str]):
import shutil
# Clean entire build dir
if CORE.build_path.is_dir():
_LOGGER.info("Deleting %s", CORE.build_path)
shutil.rmtree(CORE.build_path)
for dir in configuration:
buid_dir = Path(dir) / ".esphome"
if buid_dir.is_dir():
_LOGGER.info("Deleting %s", buid_dir)
shutil.rmtree(buid_dir)
# Clean PlatformIO project files
try: