mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 17:00:21 +00:00
Merge branch 'dev' into store-yaml-firmware
This commit is contained in:
@@ -109,6 +109,34 @@ api:
|
||||
- name.c_str()
|
||||
- int_arr.size()
|
||||
- string_arr.size()
|
||||
# Test string + array args used by homeassistant.action's deferred
|
||||
# on_success/on_error response callback. homeassistant.action registers
|
||||
# synchronous=False, so the api codegen must fall back to owning
|
||||
# std::string / std::vector args here: the non-owning defaults would
|
||||
# dangle once rx_buf_ is reused before the response arrives, and the
|
||||
# non-copyable FixedVector would fail to compile when captured into
|
||||
# the response callback.
|
||||
- action: action_response_args
|
||||
variables:
|
||||
name: string
|
||||
int_arr: int[]
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: notify.notify
|
||||
data:
|
||||
message: !lambda 'return name;'
|
||||
on_success:
|
||||
- logger.log:
|
||||
format: "Notified %s (%u ints)"
|
||||
args:
|
||||
- name.c_str()
|
||||
- int_arr.size()
|
||||
on_error:
|
||||
- logger.log:
|
||||
format: "Notify failed (%s): %s"
|
||||
args:
|
||||
- error.c_str()
|
||||
- name.c_str()
|
||||
# Test ContinuationAction (IfAction with then/else branches)
|
||||
- action: test_if_action
|
||||
variables:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO14
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
uart_4800: !include ../../test_build_components/common/uart_4800/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO3
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
uart_4800: !include ../../test_build_components/common/uart_4800/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,6 +3,6 @@ substitutions:
|
||||
rx_pin: GPIO5
|
||||
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
uart_4800: !include ../../test_build_components/common/uart_4800/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -3,52 +3,64 @@ sensor:
|
||||
name: "BMI270 Temperature"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
name: "BMI270 Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
name: "BMI270 Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
name: "BMI270 Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
name: "BMI270 Gyro X"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
name: "BMI270 Gyro Y"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
name: "BMI270 Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
name: "BMI270 Angular Rate X"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
name: "BMI270 Angular Rate Y"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
name: "BMI270 Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
name: "BMI270 Pitch"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
name: "BMI270 Roll"
|
||||
|
||||
motion:
|
||||
- platform: bmi270
|
||||
id: bmi270_motion
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
display:
|
||||
- id: cst9220_display
|
||||
platform: ili9xxx
|
||||
model: ili9342
|
||||
cs_pin: ${cs_pin}
|
||||
dc_pin: ${dc_pin}
|
||||
reset_pin: ${disp_reset_pin}
|
||||
invert_colors: false
|
||||
|
||||
touchscreen:
|
||||
- id: ts_cst9220
|
||||
i2c_id: i2c_bus
|
||||
platform: cst9220
|
||||
display: cst9220_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
@@ -0,0 +1,12 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO4
|
||||
dc_pin: GPIO5
|
||||
disp_reset_pin: GPIO12
|
||||
interrupt_pin: GPIO15
|
||||
reset_pin: GPIO25
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
wakeup_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
<<: !include common-esp32-ext1.yaml
|
||||
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
wakeup_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
<<: !include common-esp32-ext1.yaml
|
||||
@@ -161,3 +161,66 @@ display:
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
|
||||
# Waveshare 2.13" V4 B series 3-color e-paper (122x250, BWR, SSD1680)
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
model: waveshare-2.13in-bv4
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
dc_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO17
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 15, Color(255, 0, 0));
|
||||
|
||||
# Soldered Inkplate 2 3-color e-paper (104x212, BWR)
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
model: inkplate2
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
dc_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO17
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 15, Color(255, 0, 0));
|
||||
|
||||
# Waveshare 7.5" V2 BWR (800x480, UC8179 controller, EDP_7in5b_V2)
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
model: waveshare-7.5in-bv2-bwr
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
dc_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO17
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 100, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 60, Color(255, 0, 0));
|
||||
|
||||
@@ -77,3 +77,34 @@ esp32_ble_server:
|
||||
id: test_change_descriptor
|
||||
value:
|
||||
data: [0x01, 0x02, 0x03]
|
||||
|
||||
# Regression test for #17142: the set_value action used from a trigger that passes
|
||||
# its argument by reference (climate on_control supplies ClimateCall&) previously
|
||||
# failed to compile.
|
||||
sensor:
|
||||
- platform: template
|
||||
id: ble_test_temp
|
||||
lambda: "return 20.0;"
|
||||
|
||||
output:
|
||||
- platform: template
|
||||
id: ble_test_output
|
||||
type: float
|
||||
write_action:
|
||||
- logger.log: "out"
|
||||
|
||||
climate:
|
||||
- platform: pid
|
||||
name: "BLE Test Climate"
|
||||
id: ble_test_climate
|
||||
sensor: ble_test_temp
|
||||
default_target_temperature: 20
|
||||
heat_output: ble_test_output
|
||||
control_parameters:
|
||||
kp: 0.1
|
||||
ki: 0.001
|
||||
kd: 0.1
|
||||
on_control:
|
||||
- ble_server.characteristic.set_value:
|
||||
id: test_notify_characteristic
|
||||
value: !lambda "return std::vector<uint8_t>{0, 1, 2};"
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
# P0.2, P0.4 and P0.5 all live on the same Zephyr port device (gpio0) and each
|
||||
# attaches its own interrupt. This locks in shared-port behavior: every pin owns
|
||||
# a separate gpio_callback initialized with its own BIT(pin) mask, so Zephyr
|
||||
# dispatches to each pin independently even though the port device is shared.
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: 2
|
||||
id: gpio_binary_sensor
|
||||
use_interrupt: true
|
||||
interrupt_type: ANY
|
||||
|
||||
# Inverted pin with an edge-specific interrupt: exercises the inversion-aware
|
||||
# interrupt-arming path (logical RISING must arm on the physical falling edge).
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: P0.4
|
||||
inverted: true
|
||||
id: gpio_binary_sensor_inverted
|
||||
use_interrupt: true
|
||||
interrupt_type: RISING
|
||||
|
||||
# Second non-inverted interrupt on the same port (gpio0) as P0.2 above: verifies
|
||||
# multiple pins sharing one port device each get their own callback/pin_mask.
|
||||
- platform: gpio
|
||||
pin: P0.5
|
||||
id: gpio_binary_sensor_shared_port
|
||||
use_interrupt: true
|
||||
interrupt_type: FALLING
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
#ifdef USE_HOST
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include "esphome/components/host/preferences.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome::host::testing {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/// RAII helper to save and restore an environment variable.
|
||||
class ScopedEnvVar {
|
||||
public:
|
||||
explicit ScopedEnvVar(const char *name) : name_(name) {
|
||||
const char *val = getenv(name);
|
||||
if (val != nullptr) {
|
||||
saved_value_ = val;
|
||||
was_set_ = true;
|
||||
}
|
||||
}
|
||||
~ScopedEnvVar() {
|
||||
if (this->was_set_) {
|
||||
setenv(this->name_.c_str(), this->saved_value_.c_str(), 1);
|
||||
} else {
|
||||
unsetenv(this->name_.c_str());
|
||||
}
|
||||
}
|
||||
ScopedEnvVar(const ScopedEnvVar &) = delete;
|
||||
ScopedEnvVar &operator=(const ScopedEnvVar &) = delete;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string saved_value_;
|
||||
bool was_set_{false};
|
||||
};
|
||||
|
||||
class HostPreferencesTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create a unique temp directory for this test
|
||||
this->temp_dir_ = fs::temp_directory_path() / "esphome_prefs_test";
|
||||
fs::create_directories(this->temp_dir_);
|
||||
|
||||
// Set up App name — string literal has static storage so StringRef is safe
|
||||
App.pre_setup("test_prefs", 10, "", 0);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
std::error_code ec;
|
||||
fs::remove_all(this->temp_dir_, ec);
|
||||
}
|
||||
|
||||
fs::path temp_dir_;
|
||||
};
|
||||
|
||||
TEST_F(HostPreferencesTest, BothVarsUnset_SyncReturnsFalse) {
|
||||
ScopedEnvVar home_guard("HOME");
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
unsetenv("HOME");
|
||||
unsetenv("ESPHOME_PREFDIR");
|
||||
|
||||
HostPreferences prefs;
|
||||
EXPECT_FALSE(prefs.sync());
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, BothVarsUnset_SaveSucceedsInMemory) {
|
||||
ScopedEnvVar home_guard("HOME");
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
unsetenv("HOME");
|
||||
unsetenv("ESPHOME_PREFDIR");
|
||||
|
||||
HostPreferences prefs;
|
||||
uint32_t value = 42;
|
||||
// save() stores in memory even without a valid file path
|
||||
EXPECT_TRUE(prefs.save(0x1234, reinterpret_cast<const uint8_t *>(&value), sizeof(value)));
|
||||
|
||||
// But sync to disk should fail
|
||||
EXPECT_FALSE(prefs.sync());
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, PrefDirSet_SaveAndSync) {
|
||||
ScopedEnvVar home_guard("HOME");
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
|
||||
auto prefdir = this->temp_dir_ / "prefdir";
|
||||
setenv("ESPHOME_PREFDIR", prefdir.c_str(), 1);
|
||||
unsetenv("HOME");
|
||||
|
||||
HostPreferences prefs;
|
||||
uint32_t value = 42;
|
||||
EXPECT_TRUE(prefs.save(0x1234, reinterpret_cast<const uint8_t *>(&value), sizeof(value)));
|
||||
EXPECT_TRUE(prefs.sync());
|
||||
|
||||
// Verify file was created in ESPHOME_PREFDIR
|
||||
auto expected_file = prefdir / "test_prefs.prefs";
|
||||
EXPECT_TRUE(fs::exists(expected_file));
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, HomeSet_SaveAndSync) {
|
||||
ScopedEnvVar home_guard("HOME");
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
|
||||
auto home = this->temp_dir_ / "home";
|
||||
setenv("HOME", home.c_str(), 1);
|
||||
unsetenv("ESPHOME_PREFDIR");
|
||||
|
||||
HostPreferences prefs;
|
||||
uint32_t value = 42;
|
||||
EXPECT_TRUE(prefs.save(0x1234, reinterpret_cast<const uint8_t *>(&value), sizeof(value)));
|
||||
EXPECT_TRUE(prefs.sync());
|
||||
|
||||
// Verify file was created in HOME/.esphome/prefs
|
||||
auto expected_file = home / ".esphome" / "prefs" / "test_prefs.prefs";
|
||||
EXPECT_TRUE(fs::exists(expected_file));
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, PrefDirTakesPrecedenceOverHome) {
|
||||
ScopedEnvVar home_guard("HOME");
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
|
||||
auto prefdir = this->temp_dir_ / "prefdir";
|
||||
auto home = this->temp_dir_ / "home";
|
||||
setenv("ESPHOME_PREFDIR", prefdir.c_str(), 1);
|
||||
setenv("HOME", home.c_str(), 1);
|
||||
|
||||
HostPreferences prefs;
|
||||
uint32_t value = 42;
|
||||
EXPECT_TRUE(prefs.save(0x1234, reinterpret_cast<const uint8_t *>(&value), sizeof(value)));
|
||||
EXPECT_TRUE(prefs.sync());
|
||||
|
||||
// File should be in ESPHOME_PREFDIR, not HOME
|
||||
auto prefdir_file = prefdir / "test_prefs.prefs";
|
||||
auto home_file = home / ".esphome" / "prefs" / "test_prefs.prefs";
|
||||
EXPECT_TRUE(fs::exists(prefdir_file));
|
||||
EXPECT_FALSE(fs::exists(home_file));
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, SaveAndLoadRoundTrip) {
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
|
||||
auto prefdir = this->temp_dir_ / "roundtrip";
|
||||
setenv("ESPHOME_PREFDIR", prefdir.c_str(), 1);
|
||||
|
||||
// Save data with one instance
|
||||
{
|
||||
HostPreferences prefs;
|
||||
uint32_t value = 0xDEADBEEF;
|
||||
EXPECT_TRUE(prefs.save(0xABCD, reinterpret_cast<const uint8_t *>(&value), sizeof(value)));
|
||||
EXPECT_TRUE(prefs.sync());
|
||||
}
|
||||
|
||||
// Load with a fresh instance (reads from file)
|
||||
{
|
||||
HostPreferences prefs;
|
||||
uint32_t loaded = 0;
|
||||
EXPECT_TRUE(prefs.load(0xABCD, reinterpret_cast<uint8_t *>(&loaded), sizeof(loaded)));
|
||||
EXPECT_EQ(loaded, 0xDEADBEEFu);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HostPreferencesTest, LoadNonExistentKeyReturnsFalse) {
|
||||
ScopedEnvVar prefdir_guard("ESPHOME_PREFDIR");
|
||||
|
||||
auto prefdir = this->temp_dir_ / "nokey";
|
||||
setenv("ESPHOME_PREFDIR", prefdir.c_str(), 1);
|
||||
|
||||
HostPreferences prefs;
|
||||
uint32_t loaded = 0;
|
||||
EXPECT_FALSE(prefs.load(0x9999, reinterpret_cast<uint8_t *>(&loaded), sizeof(loaded)));
|
||||
}
|
||||
|
||||
} // namespace esphome::host::testing
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,109 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
|
||||
display:
|
||||
# Generic IT8951 with explicit dimensions
|
||||
- platform: it8951
|
||||
spi_id: spi_bus
|
||||
model: it8951
|
||||
dimensions:
|
||||
width: 1872
|
||||
height: 1404
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
enable_pin:
|
||||
- GPIO17
|
||||
- GPIO18
|
||||
vcom: 1500
|
||||
update_interval: 60s
|
||||
# Exercise an alias for the update_mode config option.
|
||||
update_mode: fast
|
||||
lambda: |-
|
||||
it.circle(64, 64, 50, Color::BLACK);
|
||||
|
||||
# m5stack-m5paper (960x540) — model supplies pin defaults
|
||||
- platform: it8951
|
||||
id: m5epd_display
|
||||
spi_id: spi_bus
|
||||
model: m5stack-m5paper
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
full_update_every: 30
|
||||
invert_colors: false
|
||||
sleep_when_done: true
|
||||
grayscale: true
|
||||
update_mode: GC16
|
||||
rotation: 270
|
||||
transform:
|
||||
mirror_x: false
|
||||
mirror_y: false
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 30, Color::BLACK);
|
||||
|
||||
# seeed-reterminal-e1003 (1872x1404)
|
||||
- platform: it8951
|
||||
spi_id: spi_bus
|
||||
model: seeed-reterminal-e1003
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
vcom: 1400
|
||||
sleep_when_done: false
|
||||
lambda: |-
|
||||
it.filled_rectangle(0, 0, 128, 128, Color::BLACK);
|
||||
|
||||
# seeed-ee03 (1872x1404), monochrome fast path
|
||||
- platform: it8951
|
||||
spi_id: spi_bus
|
||||
model: seeed-ee03
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
grayscale: false
|
||||
dithering: false
|
||||
update_mode: DU
|
||||
lambda: |-
|
||||
it.circle(128, 128, 64, Color::BLACK);
|
||||
|
||||
# Exercise the it8951.update automation: alias modes, a direct enum-name mode,
|
||||
# and the bare (default-mode) form.
|
||||
interval:
|
||||
- interval: 30s
|
||||
then:
|
||||
- it8951.update:
|
||||
id: m5epd_display
|
||||
mode: fast
|
||||
- it8951.update:
|
||||
id: m5epd_display
|
||||
mode: full
|
||||
- it8951.update:
|
||||
id: m5epd_display
|
||||
mode: A2
|
||||
- it8951.update: m5epd_display
|
||||
@@ -18,6 +18,9 @@ class MockUARTComponent : public uart::UARTComponent {
|
||||
MOCK_METHOD(size_t, available, (), (override));
|
||||
MOCK_METHOD(uart::UARTFlushResult, flush, (), (override));
|
||||
MOCK_METHOD(void, check_logger_conflict, (), (override));
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
void load_settings(bool dump_config) override {}
|
||||
#endif // USE_ESP8266 || USE_ESP32
|
||||
};
|
||||
|
||||
// Expose protected members for testing.
|
||||
|
||||
@@ -1,54 +1,66 @@
|
||||
sensor:
|
||||
- platform: lsm6ds
|
||||
name: "lsm6ds Temperature"
|
||||
name: "LSM6DS Temperature"
|
||||
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
name: "LSM6DS Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
name: "LSM6DS Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
name: "LSM6DS Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
name: "LSM6DS Gyro X"
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
name: "LSM6DS Gyro Y"
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
name: "LSM6DS Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
name: "LSM6DS Angular Rate X"
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
name: "LSM6DS Angular Rate Y"
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
name: "LSM6DS Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
name: "LSM6DS Pitch"
|
||||
- platform: motion
|
||||
motion_id: lsm6ds_motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
name: "LSM6DS Roll"
|
||||
|
||||
motion:
|
||||
- platform: lsm6ds
|
||||
id: lsm6ds_motion
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
|
||||
mdns:
|
||||
@@ -1,7 +1,11 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
- !include ../../test_build_components/common/i2c/esp32-s3-idf.yaml
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
<<: !include common.yaml
|
||||
ch422g:
|
||||
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
model: WAVESHARE-5-1024X600
|
||||
|
||||
@@ -4,6 +4,181 @@
|
||||
|
||||
namespace esphome::modbus::helpers {
|
||||
|
||||
using FC = ModbusFunctionCode;
|
||||
|
||||
// --- server_frame_length ---------------------------------------------------
|
||||
// Frame layout: address(1) + function(1) + ... + CRC(2). Fixtures borrowed from
|
||||
// tests/integration/fixtures/uart_mock_modbus.yaml.
|
||||
|
||||
TEST(ModbusServerFrameLength, TooShortReturnsMinimum) {
|
||||
const uint8_t frame[] = {0x01};
|
||||
EXPECT_EQ(server_frame_length(frame, 1), MIN_FRAME_SIZE);
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, ReadHoldingUsesByteCount) {
|
||||
// inject_rx for basic_register: 2 data bytes -> 5 + 2 = 7
|
||||
const uint8_t frame[] = {0x01, 0x03, 0x02, 0x01, 0x03, 0xF9, 0xD5};
|
||||
EXPECT_EQ(server_frame_length(frame, sizeof(frame)), 7);
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, ReadByteCountCappedAtMax) {
|
||||
const uint8_t frame[] = {0x01, 0x03, 0xFF}; // claim 255 bytes
|
||||
EXPECT_EQ(server_frame_length(frame, sizeof(frame)), 5 + MAX_NUM_OF_REGISTERS_TO_READ * 2);
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, ReadMissingByteCountReturnsHeaderOnly) {
|
||||
const uint8_t frame[] = {0x01, 0x03};
|
||||
EXPECT_EQ(server_frame_length(frame, sizeof(frame)), 5);
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, ExceptionResponse) {
|
||||
// exception_response fixture: function code 0x83 has the exception bit set
|
||||
const uint8_t frame[] = {0x01, 0x83, 0x02, 0xC0, 0xF1};
|
||||
EXPECT_EQ(server_frame_length(frame, sizeof(frame)), 5);
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, WriteResponsesAreFixed) {
|
||||
for (FC fc :
|
||||
{FC::WRITE_SINGLE_COIL, FC::WRITE_SINGLE_REGISTER, FC::WRITE_MULTIPLE_COILS, FC::WRITE_MULTIPLE_REGISTERS}) {
|
||||
const uint8_t frame[] = {0x01, static_cast<uint8_t>(fc)};
|
||||
EXPECT_EQ(server_frame_length(frame, sizeof(frame)), 8) << "fc=" << static_cast<int>(fc);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ModbusServerFrameLength, MiscFixedAndUnknown) {
|
||||
const uint8_t mask[] = {0x01, static_cast<uint8_t>(FC::MASK_WRITE_REGISTER)};
|
||||
const uint8_t fifo[] = {0x01, static_cast<uint8_t>(FC::READ_FIFO_QUEUE)};
|
||||
const uint8_t unknown[] = {0x01, 0x42};
|
||||
EXPECT_EQ(server_frame_length(mask, sizeof(mask)), 10);
|
||||
EXPECT_EQ(server_frame_length(fifo, sizeof(fifo)), 6);
|
||||
EXPECT_EQ(server_frame_length(unknown, sizeof(unknown)), MIN_FRAME_SIZE);
|
||||
}
|
||||
|
||||
// --- client_frame_length ---------------------------------------------------
|
||||
|
||||
TEST(ModbusClientFrameLength, TooShortReturnsMinimum) {
|
||||
const uint8_t frame[] = {0x01};
|
||||
EXPECT_EQ(client_frame_length(frame, 1), MIN_FRAME_SIZE);
|
||||
}
|
||||
|
||||
TEST(ModbusClientFrameLength, ReadAndWriteSingleAreFixed) {
|
||||
// basic_register request fixture is a read-holding request -> 8 bytes
|
||||
const uint8_t read[] = {0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x74, 0x0A};
|
||||
EXPECT_EQ(client_frame_length(read, sizeof(read)), 8);
|
||||
for (FC fc : {FC::READ_COILS, FC::READ_DISCRETE_INPUTS, FC::READ_INPUT_REGISTERS, FC::WRITE_SINGLE_COIL,
|
||||
FC::WRITE_SINGLE_REGISTER}) {
|
||||
const uint8_t frame[] = {0x01, static_cast<uint8_t>(fc)};
|
||||
EXPECT_EQ(client_frame_length(frame, sizeof(frame)), 8) << "fc=" << static_cast<int>(fc);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ModbusClientFrameLength, WriteMultipleUsesByteCount) {
|
||||
// write 2 registers (4 data bytes): addr(2)+qty(2)+count(1) then data; count is frame[6]
|
||||
const uint8_t frame[] = {0x01, 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x0B, 0x00, 0x16};
|
||||
EXPECT_EQ(client_frame_length(frame, sizeof(frame)), 9 + 4);
|
||||
}
|
||||
|
||||
TEST(ModbusClientFrameLength, WriteMultipleByteCountCapped) {
|
||||
const uint8_t frame[] = {0x01, 0x0F, 0x00, 0x00, 0x00, 0x02, 0xFF};
|
||||
EXPECT_EQ(client_frame_length(frame, sizeof(frame)), 9 + MAX_NUM_OF_REGISTERS_TO_WRITE * 2);
|
||||
}
|
||||
|
||||
TEST(ModbusClientFrameLength, WriteMultipleMissingByteCount) {
|
||||
const uint8_t frame[] = {0x01, 0x10, 0x00, 0x00, 0x00, 0x02};
|
||||
EXPECT_EQ(client_frame_length(frame, sizeof(frame)), 9);
|
||||
}
|
||||
|
||||
TEST(ModbusClientFrameLength, MiscFixedAndUnknown) {
|
||||
const uint8_t mask[] = {0x01, static_cast<uint8_t>(FC::MASK_WRITE_REGISTER)};
|
||||
const uint8_t fifo[] = {0x01, static_cast<uint8_t>(FC::READ_FIFO_QUEUE)};
|
||||
const uint8_t unknown[] = {0x01, 0x42};
|
||||
EXPECT_EQ(client_frame_length(mask, sizeof(mask)), 10);
|
||||
EXPECT_EQ(client_frame_length(fifo, sizeof(fifo)), 6);
|
||||
EXPECT_EQ(client_frame_length(unknown, sizeof(unknown)), MIN_FRAME_SIZE);
|
||||
}
|
||||
|
||||
// --- create_client_pdu -----------------------------------------------------
|
||||
// PDU = function code + data (no address, no CRC).
|
||||
|
||||
TEST(ModbusCreateClientPdu, ReadHolding) {
|
||||
auto pdu = create_client_pdu(FC::READ_HOLDING_REGISTERS, 0x0003, 1);
|
||||
const std::vector<uint8_t> expected{0x03, 0x00, 0x03, 0x00, 0x01};
|
||||
EXPECT_EQ(std::vector<uint8_t>(pdu.begin(), pdu.end()), expected);
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteSingleOmitsQuantity) {
|
||||
const uint8_t values[] = {0x00, 0x0B};
|
||||
auto pdu = create_client_pdu(FC::WRITE_SINGLE_REGISTER, 0x0003, 1, values, sizeof(values));
|
||||
const std::vector<uint8_t> expected{0x06, 0x00, 0x03, 0x00, 0x0B};
|
||||
EXPECT_EQ(std::vector<uint8_t>(pdu.begin(), pdu.end()), expected);
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteSingleTooFewValuesReturnsEmpty) {
|
||||
const uint8_t values[] = {0x00};
|
||||
auto pdu = create_client_pdu(FC::WRITE_SINGLE_COIL, 0x0003, 1, values, sizeof(values));
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteMultipleIncludesByteCount) {
|
||||
const uint8_t values[] = {0x00, 0x0B, 0x00, 0x16};
|
||||
auto pdu = create_client_pdu(FC::WRITE_MULTIPLE_REGISTERS, 0x0000, 2, values, sizeof(values));
|
||||
const std::vector<uint8_t> expected{0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x0B, 0x00, 0x16};
|
||||
EXPECT_EQ(std::vector<uint8_t>(pdu.begin(), pdu.end()), expected);
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteMultipleOverCapacityReturnsEmpty) {
|
||||
std::vector<uint8_t> values(MAX_PDU_SIZE - 6 + 1, 0xAA);
|
||||
auto pdu = create_client_pdu(FC::WRITE_MULTIPLE_REGISTERS, 0x0000, 1, values.data(), values.size());
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, UnsupportedFunctionCodeReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::READ_FIFO_QUEUE, 0x0000, 1);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, ZeroEntitiesReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::READ_HOLDING_REGISTERS, 0x0000, 0);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteWithoutValuesReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::WRITE_MULTIPLE_REGISTERS, 0x0000, 1, nullptr, 0);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, ReadHoldingOverMaxReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::READ_HOLDING_REGISTERS, 0x0000, MAX_NUM_OF_REGISTERS_TO_READ + 1);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
// Regression: coils allow up to 2000 entities, well above the 125 register limit.
|
||||
// A switch fall-through previously subjected coil/discrete reads to the register limit.
|
||||
TEST(ModbusCreateClientPdu, ReadCoilsAboveRegisterLimitIsValid) {
|
||||
const uint16_t quantity = MAX_NUM_OF_REGISTERS_TO_READ + 1; // 126: valid for coils, too many for registers
|
||||
auto pdu = create_client_pdu(FC::READ_COILS, 0x0000, quantity);
|
||||
const std::vector<uint8_t> expected{0x01, 0x00, 0x00, static_cast<uint8_t>(quantity >> 8),
|
||||
static_cast<uint8_t>(quantity & 0xFF)};
|
||||
EXPECT_EQ(std::vector<uint8_t>(pdu.begin(), pdu.end()), expected);
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, ReadCoilsOverMaxReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::READ_COILS, 0x0000, MAX_NUM_OF_COILS_TO_READ + 1);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, ReadDiscreteInputsOverMaxReturnsEmpty) {
|
||||
auto pdu = create_client_pdu(FC::READ_DISCRETE_INPUTS, 0x0000, MAX_NUM_OF_DISCRETE_INPUTS_TO_READ + 1);
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusCreateClientPdu, WriteMultipleOverEntityLimitReturnsEmpty) {
|
||||
const uint8_t values[] = {0x00, 0x0B};
|
||||
auto pdu = create_client_pdu(FC::WRITE_MULTIPLE_REGISTERS, 0x0000, MAX_NUM_OF_REGISTERS_TO_WRITE + 1, values,
|
||||
sizeof(values));
|
||||
EXPECT_TRUE(pdu.empty());
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, PayloadToNumberRejectsOffsetAtEndOfBuffer) {
|
||||
const std::vector<uint8_t> data{0x12, 0x34};
|
||||
EXPECT_EQ(payload_to_number(data, SensorValueType::U_WORD, 2, 0xFFFFFFFF), 0);
|
||||
@@ -19,4 +194,40 @@ TEST(ModbusHelpersTest, PayloadToNumberDecodesValidWord) {
|
||||
EXPECT_EQ(payload_to_number(data, SensorValueType::U_WORD, 0, 0xFFFFFFFF), 0x1234);
|
||||
}
|
||||
|
||||
// --- registers_to_number ---------------------------------------------------
|
||||
// Register words are host byte order; results must match the byte-based payload_to_number.
|
||||
|
||||
TEST(ModbusHelpersTest, RegistersToNumberDecodesWord) {
|
||||
const uint16_t registers[] = {0x1234};
|
||||
EXPECT_EQ(registers_to_number(registers, 1, SensorValueType::U_WORD), 0x1234);
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, RegistersToNumberDecodesDwordHighWordFirst) {
|
||||
const uint16_t registers[] = {0x1234, 0x5678};
|
||||
EXPECT_EQ(registers_to_number(registers, 2, SensorValueType::U_DWORD), 0x12345678);
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, RegistersToNumberDecodesAtSpanStart) {
|
||||
// The function decodes the value at the start of the span; the caller advances the pointer.
|
||||
const uint16_t registers[] = {0xAAAA, 0x1234};
|
||||
EXPECT_EQ(registers_to_number(registers + 1, 1, SensorValueType::U_WORD), 0x1234);
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, RegistersToNumberMatchesPayloadToNumber) {
|
||||
// Same value via both decoders: registers (host order) vs big-endian bytes.
|
||||
const uint16_t registers[] = {0x8001, 0x0002};
|
||||
const std::vector<uint8_t> bytes{0x80, 0x01, 0x00, 0x02};
|
||||
for (auto value_type : {SensorValueType::S_DWORD, SensorValueType::U_DWORD, SensorValueType::S_DWORD_R}) {
|
||||
EXPECT_EQ(registers_to_number(registers, 2, value_type), payload_to_number(bytes, value_type, 0, 0xFFFFFFFF))
|
||||
<< "value_type=" << static_cast<int>(value_type);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, RegistersToNumberRejectsTruncatedMultiRegisterValue) {
|
||||
const uint16_t registers[] = {0x1234};
|
||||
bool error = false;
|
||||
EXPECT_EQ(registers_to_number(registers, 1, SensorValueType::U_DWORD, &error), 0);
|
||||
EXPECT_TRUE(error);
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus::helpers
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::modbus {
|
||||
|
||||
// Exposes protected methods for testing.
|
||||
class TestModbus : public Modbus {
|
||||
public:
|
||||
bool test_parse_modbus_byte(uint8_t byte) { return this->parse_modbus_byte_(byte); }
|
||||
void test_clear_rx_buffer() { this->rx_buffer_.clear(); }
|
||||
void set_waiting(uint8_t addr) { this->waiting_for_response_ = addr; }
|
||||
};
|
||||
|
||||
class MockDevice : public ModbusDevice {
|
||||
public:
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override { this->data_received = true; }
|
||||
bool data_received{false};
|
||||
};
|
||||
|
||||
TEST(ModbusTest, TwoByteRegressionTest) {
|
||||
TestModbus modbus;
|
||||
modbus.set_role(ModbusRole::CLIENT);
|
||||
// First byte (at=0)
|
||||
EXPECT_TRUE(modbus.test_parse_modbus_byte(0x01));
|
||||
// Second byte (at=1)
|
||||
// This used to reach raw[2] because it skipped the if(at==2) check, causing a
|
||||
// buffer overflow.
|
||||
EXPECT_TRUE(modbus.test_parse_modbus_byte(0x03));
|
||||
}
|
||||
|
||||
TEST(ModbusTest, TestValidFrame) {
|
||||
TestModbus modbus;
|
||||
modbus.set_role(ModbusRole::CLIENT);
|
||||
|
||||
MockDevice device;
|
||||
device.set_parent(&modbus);
|
||||
device.set_address(0x01);
|
||||
modbus.register_device(&device);
|
||||
modbus.set_waiting(0x01);
|
||||
|
||||
// Address 1, Function 3, Length 2, Data 0x1234
|
||||
uint8_t frame_data[] = {0x01, 0x03, 0x02, 0x12, 0x34};
|
||||
uint16_t crc = esphome::crc16(frame_data, sizeof(frame_data));
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
for (uint8_t b : frame_data)
|
||||
frame.push_back(b);
|
||||
frame.push_back(crc & 0xFF);
|
||||
frame.push_back((crc >> 8) & 0xFF);
|
||||
|
||||
for (size_t i = 0; i < frame.size(); i++) {
|
||||
bool result = modbus.test_parse_modbus_byte(frame[i]);
|
||||
EXPECT_TRUE(result) << "Failed at byte " << i << " (0x" << std::hex << (int) frame[i] << ")";
|
||||
}
|
||||
EXPECT_TRUE(device.data_received);
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus
|
||||
@@ -18,6 +18,7 @@ modbus_server:
|
||||
registers:
|
||||
- address: 0x9
|
||||
value_type: S_DWORD
|
||||
allow_partial_read: true
|
||||
read_lambda: |-
|
||||
return 31;
|
||||
write_lambda: |-
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/modbus_server/modbus_server.h"
|
||||
|
||||
namespace esphome::modbus_server {
|
||||
|
||||
using modbus::ModbusExceptionCode;
|
||||
using modbus::RegisterValues;
|
||||
|
||||
namespace {
|
||||
|
||||
RegisterValues make_registers(std::initializer_list<uint16_t> values) {
|
||||
RegisterValues registers;
|
||||
for (uint16_t value : values)
|
||||
registers.push_back(value);
|
||||
return registers;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A single writable WORD register is applied and the handler reports success (nullopt).
|
||||
TEST(ModbusServerWrite, SingleWordSucceeds) {
|
||||
ModbusServer server;
|
||||
int64_t written = -1;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_WORD, 1);
|
||||
reg.write_lambda = [&written](int64_t value) {
|
||||
written = value;
|
||||
return true;
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234}));
|
||||
EXPECT_FALSE(status.has_value()); // nullopt == success
|
||||
EXPECT_EQ(written, 0x1234);
|
||||
}
|
||||
|
||||
// A multi-register value is decoded high word first and applied as a single number.
|
||||
TEST(ModbusServerWrite, DwordSucceeds) {
|
||||
ModbusServer server;
|
||||
int64_t written = -1;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_DWORD, 2);
|
||||
reg.write_lambda = [&written](int64_t value) {
|
||||
written = value;
|
||||
return true;
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234, 0x5678}));
|
||||
EXPECT_FALSE(status.has_value());
|
||||
EXPECT_EQ(written, 0x12345678);
|
||||
}
|
||||
|
||||
// Regression: a request that under-supplies a multi-register value is rejected before any
|
||||
// write_lambda runs, so no register is partially written.
|
||||
TEST(ModbusServerWrite, UnderSuppliedValueAppliesNothing) {
|
||||
ModbusServer server;
|
||||
bool word_written = false;
|
||||
ServerRegister word_reg(0x0000, SensorValueType::U_WORD, 1);
|
||||
word_reg.write_lambda = [&word_written](int64_t) {
|
||||
word_written = true;
|
||||
return true;
|
||||
};
|
||||
bool dword_written = false;
|
||||
ServerRegister dword_reg(0x0001, SensorValueType::U_DWORD, 2); // needs two registers
|
||||
dword_reg.write_lambda = [&dword_written](int64_t) {
|
||||
dword_written = true;
|
||||
return true;
|
||||
};
|
||||
server.add_server_register(&word_reg);
|
||||
server.add_server_register(&dword_reg);
|
||||
|
||||
// Two words supplied: one for the WORD at 0x0000, but only one of the two the DWORD at 0x0001 needs.
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1111, 0x2222}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
EXPECT_FALSE(word_written); // the writable WORD must NOT have been applied
|
||||
EXPECT_FALSE(dword_written);
|
||||
}
|
||||
|
||||
// A read-only register (no write_lambda) yields ILLEGAL_DATA_ADDRESS and applies nothing.
|
||||
TEST(ModbusServerWrite, UnwritableRegisterRejected) {
|
||||
ModbusServer server;
|
||||
ServerRegister read_only(0x0000, SensorValueType::U_WORD, 1); // no write_lambda set
|
||||
server.add_server_register(&read_only);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// An address with no registered register yields ILLEGAL_DATA_ADDRESS.
|
||||
TEST(ModbusServerWrite, UnmatchedAddressRejected) {
|
||||
ModbusServer server;
|
||||
auto status = server.on_modbus_write_registers(0x0005, make_registers({0x1234}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// A write_lambda failing at runtime is the one non-atomic case: the earlier register is already
|
||||
// applied, and the handler reports SERVICE_DEVICE_FAILURE.
|
||||
TEST(ModbusServerWrite, CallbackFailureIsServiceDeviceFailure) {
|
||||
ModbusServer server;
|
||||
bool first_written = false;
|
||||
ServerRegister first(0x0000, SensorValueType::U_WORD, 1);
|
||||
first.write_lambda = [&first_written](int64_t) {
|
||||
first_written = true;
|
||||
return true;
|
||||
};
|
||||
ServerRegister second(0x0001, SensorValueType::U_WORD, 1);
|
||||
second.write_lambda = [](int64_t) { return false; }; // rejects at runtime
|
||||
server.add_server_register(&first);
|
||||
server.add_server_register(&second);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0xAAAA, 0xBBBB}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||
EXPECT_TRUE(first_written); // pre-validation passed, so the first write applied before the failure
|
||||
}
|
||||
|
||||
// --- on_modbus_read_registers --------------------------------------------------
|
||||
|
||||
TEST(ModbusServerRead, SingleWordSucceeds) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_WORD, 1);
|
||||
reg.read_lambda = []() -> int64_t { return 0x1234; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
}
|
||||
|
||||
TEST(ModbusServerRead, DwordReturnsTwoWordsHighFirst) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_DWORD, 2);
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
EXPECT_EQ(out[1], 0x5678);
|
||||
}
|
||||
|
||||
// Starting inside a multi-register value is rejected with ILLEGAL_DATA_ADDRESS -- not masked by the courtesy
|
||||
// default -- and the read_lambda is never invoked.
|
||||
TEST(ModbusServerRead, StartInsideValueRejected) {
|
||||
ModbusServer server;
|
||||
bool read_called = false;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2); // occupies 0x0010 and 0x0011
|
||||
reg.read_lambda = [&read_called]() -> int64_t {
|
||||
read_called = true;
|
||||
return 0;
|
||||
};
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
EXPECT_FALSE(read_called);
|
||||
}
|
||||
|
||||
// A read that stops short of a value's end clips it -> ILLEGAL_DATA_ADDRESS, and the read_lambda is not invoked.
|
||||
TEST(ModbusServerRead, ClippedTailRejected) {
|
||||
ModbusServer server;
|
||||
bool read_called = false;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_DWORD, 2);
|
||||
reg.read_lambda = [&read_called]() -> int64_t {
|
||||
read_called = true;
|
||||
return 0;
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
EXPECT_FALSE(read_called);
|
||||
}
|
||||
|
||||
// A write-only register (no read_lambda) is not readable -> ILLEGAL_DATA_ADDRESS, not a courtesy default.
|
||||
TEST(ModbusServerRead, WriteOnlyRegisterRejected) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0000, SensorValueType::U_WORD, 1); // no read_lambda set
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// An unregistered address with courtesy enabled returns the default value for each cell.
|
||||
TEST(ModbusServerRead, CourtesyDefaultForUnregistered) {
|
||||
ModbusServer server;
|
||||
server.set_server_courtesy_response(
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0xABCD);
|
||||
EXPECT_EQ(out[1], 0xABCD);
|
||||
}
|
||||
|
||||
// An unregistered address with courtesy disabled is rejected.
|
||||
TEST(ModbusServerRead, UnregisteredRejectedWithoutCourtesy) {
|
||||
ModbusServer server;
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
}
|
||||
|
||||
// --- partial reads (opt-in) ----------------------------------------------------
|
||||
|
||||
// With allow_partial_read, reading only the first register of a DWORD returns its high word.
|
||||
TEST(ModbusServerRead, PartialReadHighWord) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0010, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
}
|
||||
|
||||
// With allow_partial_read, starting at the interior cell returns the low word.
|
||||
TEST(ModbusServerRead, PartialReadLowWordFromInterior) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x5678);
|
||||
}
|
||||
|
||||
// Slicing is in wire order, so a reversed value type partials correctly: U_DWORD_R emits the low word
|
||||
// first, so 0x0010 holds 0x5678 and 0x0011 holds 0x1234.
|
||||
TEST(ModbusServerRead, PartialReadReversedType) {
|
||||
ModbusServer server;
|
||||
ServerRegister reg(0x0010, SensorValueType::U_DWORD_R, 2);
|
||||
reg.allow_partial_read = true;
|
||||
reg.read_lambda = []() -> int64_t { return 0x12345678; };
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues first;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0010, 1, first).has_value());
|
||||
ASSERT_EQ(first.size(), 1u);
|
||||
EXPECT_EQ(first[0], 0x5678);
|
||||
|
||||
RegisterValues second;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0011, 1, second).has_value());
|
||||
ASSERT_EQ(second.size(), 1u);
|
||||
EXPECT_EQ(second[0], 0x1234);
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus_server
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
@@ -1 +1,5 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
|
||||
@@ -19,5 +19,3 @@ nrf52:
|
||||
reg0:
|
||||
voltage: 2.1V
|
||||
uicr_erase: true
|
||||
framework:
|
||||
version: "2.6.1-b"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -0,0 +1,20 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
openthread:
|
||||
device_type: MTD
|
||||
force_dataset: false
|
||||
use_address: open-thread-test.local
|
||||
tlv: 0e080000000000010000000300001035060004001fffe00208e227ac6a7f24052f0708fdb753eb517cb4d3051062b2442a928d9ea3b947a1618fc4085a030f4f70656e5468726561642d393837330102987304105330d857354330133c05e1fd7ae81a910c0402a0f7f8
|
||||
poll_period: 5s
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Radio Always On"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
turn_on_action:
|
||||
then:
|
||||
- openthread.set_poll_period: 0s
|
||||
turn_off_action:
|
||||
then:
|
||||
- openthread.set_poll_period: 5s
|
||||
@@ -1,14 +1,6 @@
|
||||
esp32:
|
||||
board: esp32-c6-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
log_level: DEBUG
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
<<: !include common.yaml
|
||||
|
||||
openthread:
|
||||
device_type: MTD
|
||||
channel: 13
|
||||
network_name: OpenThread-8f28
|
||||
network_key: 0xdfd34f0f05cad978ec4e32b0413038ff
|
||||
@@ -16,7 +8,4 @@ openthread:
|
||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||
force_dataset: true
|
||||
use_address: open-thread-test.local
|
||||
poll_period: 20sec
|
||||
output_power: 1dBm
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
@@ -0,0 +1,26 @@
|
||||
display:
|
||||
- platform: pixoo
|
||||
id: pixoo_display
|
||||
model: 64x64
|
||||
cs_pin: GPIO5
|
||||
data_rate: 10MHz
|
||||
update_interval: 1s
|
||||
lambda: |-
|
||||
it.fill(Color(0, 0, 0));
|
||||
it.filled_rectangle(0, 0, 16, 16, Color(255, 0, 0));
|
||||
it.line(0, 0, 63, 63, Color(0, 255, 0));
|
||||
|
||||
- platform: pixoo
|
||||
id: pixoo_display_pages
|
||||
model: 64x64
|
||||
cs_pin: GPIO21
|
||||
rotation: 90
|
||||
pages:
|
||||
- id: pixoo_page
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height(), Color(0, 0, 255));
|
||||
|
||||
light:
|
||||
- platform: pixoo
|
||||
pixoo_id: pixoo_display
|
||||
name: Pixoo Brightness
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,47 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/power_supply/power_supply.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::power_supply::testing {
|
||||
|
||||
// Minimal dummy internal GPIO pin implementation for testing
|
||||
class DummyInternalPin : public InternalGPIOPin {
|
||||
public:
|
||||
DummyInternalPin() = default;
|
||||
void setup() override {}
|
||||
void pin_mode(esphome::gpio::Flags) override {}
|
||||
esphome::gpio::Flags get_flags() const override { return esphome::gpio::FLAG_NONE; }
|
||||
bool digital_read() override { return false; }
|
||||
void digital_write(bool) override {}
|
||||
void detach_interrupt() const override {}
|
||||
ISRInternalGPIOPin to_isr() const override { return ISRInternalGPIOPin(); }
|
||||
uint8_t get_pin() const override { return 0; }
|
||||
bool is_inverted() const override { return false; }
|
||||
|
||||
protected:
|
||||
// Implement protected attach_interrupt required by InternalGPIOPin
|
||||
void attach_interrupt(void (*func)(void *), void *arg, esphome::gpio::InterruptType type) const override {}
|
||||
};
|
||||
|
||||
TEST(PowerSupply, HasHigherPriorityThanBusWhenInternalAndEnableOnBoot) {
|
||||
power_supply::PowerSupply ps;
|
||||
DummyInternalPin pin;
|
||||
ps.set_pin(&pin);
|
||||
ps.set_enable_on_boot(true);
|
||||
|
||||
// POWER priority should be greater than BUS priority
|
||||
EXPECT_GT(ps.get_setup_priority(), setup_priority::BUS);
|
||||
}
|
||||
|
||||
TEST(PowerSupply, FallsBackToIOWhenNotEnableOnBoot) {
|
||||
power_supply::PowerSupply ps;
|
||||
DummyInternalPin pin;
|
||||
ps.set_pin(&pin);
|
||||
ps.set_enable_on_boot(false);
|
||||
|
||||
EXPECT_EQ(ps.get_setup_priority(), setup_priority::IO);
|
||||
}
|
||||
|
||||
} // namespace esphome::power_supply::testing
|
||||
@@ -0,0 +1,5 @@
|
||||
# Config-only: the ESP32-S3 supports both quad and octal. The compile test uses
|
||||
# octal; this exercises the other branch of the per-variant mode enum (quad) and
|
||||
# lets speed fall back to its 40MHz default.
|
||||
psram:
|
||||
mode: quad
|
||||
@@ -0,0 +1,4 @@
|
||||
# Config-only: with no options the single-mode ESP32 resolves mode -> quad and
|
||||
# speed -> 40MHz from the per-variant defaults. Compiling adds no signal here,
|
||||
# so this only runs through `esphome config`.
|
||||
psram:
|
||||
@@ -0,0 +1,4 @@
|
||||
# Config-only: the ESP32-P4 has a distinct value set (hex mode, 20/100/200MHz).
|
||||
# With no options it resolves mode -> hex and speed -> 20MHz, exercising the
|
||||
# P4-specific default branch of the per-variant enums.
|
||||
psram:
|
||||
@@ -0,0 +1,70 @@
|
||||
sensor:
|
||||
- platform: qmi8658
|
||||
name: "QMI8658 Temperature"
|
||||
|
||||
- platform: motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
- platform: motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
- platform: motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
- platform: motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
- platform: motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
- platform: motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
|
||||
motion:
|
||||
- platform: qmi8658
|
||||
i2c_id: i2c_bus
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
# Accelerometer output data rate: 31_25HZ | 62_5HZ | 125HZ | 250HZ |
|
||||
# 500HZ | 1000HZ | 2000HZ | 4000HZ | 8000HZ
|
||||
accelerometer_odr: 1000HZ
|
||||
|
||||
# Gyroscope full-scale range: 16DPS | 32DPS | 64DPS | 128DPS |
|
||||
# 256DPS | 512DPS | 1024DPS | 2048DPS
|
||||
gyroscope_range: 2048DPS
|
||||
|
||||
# Gyroscope output data rate: 31_25HZ | 62_5HZ | 125HZ | 250HZ |
|
||||
# 500HZ | 1000HZ | 2000HZ | 4000HZ | 8000HZ
|
||||
gyroscope_odr: 1000HZ
|
||||
axis_map:
|
||||
x: y
|
||||
y: x
|
||||
z: -z
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -7,3 +7,8 @@ speaker:
|
||||
- platform: resampler
|
||||
id: resampler_speaker_id
|
||||
output_speaker: resampler_i2s_speaker_id
|
||||
bits_per_sample: 16
|
||||
- platform: resampler
|
||||
id: resampler_speaker_2_id
|
||||
output_speaker: resampler_speaker_id
|
||||
bits_per_sample: passthrough
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
uart_19200: !include ../../test_build_components/common/uart_19200/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
uart_19200: !include ../../test_build_components/common/uart_19200/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
uart_19200: !include ../../test_build_components/common/uart_19200/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
@@ -0,0 +1,18 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: st7123_ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
pages:
|
||||
- id: st7123_page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: st7123
|
||||
i2c_id: i2c_bus
|
||||
id: st7123_touchscreen
|
||||
display: st7123_ssd1306_i2c_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
@@ -0,0 +1,9 @@
|
||||
substitutions:
|
||||
display_reset_pin: "10"
|
||||
interrupt_pin: "20"
|
||||
reset_pin: "21"
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -21,6 +21,8 @@ sx126x:
|
||||
coding_rate: CR_4_6
|
||||
tcxo_voltage: 1_8V
|
||||
tcxo_delay: 5ms
|
||||
whitening_enable: false
|
||||
whitening_initial: 0x1FF
|
||||
on_packet:
|
||||
then:
|
||||
- lambda: |-
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
ufm01:
|
||||
id: ufm01_component
|
||||
uart_id: uart_bus
|
||||
|
||||
sensor:
|
||||
- platform: ufm01
|
||||
accumulated_flow:
|
||||
id: accumulated_flow
|
||||
name: "Accumulated flow"
|
||||
flow:
|
||||
id: flow
|
||||
name: "Flow"
|
||||
temperature:
|
||||
id: temperature
|
||||
name: "Temperature"
|
||||
|
||||
binary_sensor:
|
||||
- platform: ufm01
|
||||
ufc_chip_error:
|
||||
id: ufc_chip_error
|
||||
name: "UFC chip error"
|
||||
flow_direction_wrong:
|
||||
id: flow_direction_wrong
|
||||
name: "Flow direction wrong"
|
||||
empty_tube:
|
||||
id: empty_tube
|
||||
name: "Empty tube"
|
||||
flow_rate_out_of_range:
|
||||
id: flow_rate_out_of_range
|
||||
name: "Flow rate out of range"
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart_2400_even: !include ../../test_build_components/common/uart_2400_even/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart_2400_even: !include ../../test_build_components/common/uart_2400_even/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart_2400_even: !include ../../test_build_components/common/uart_2400_even/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,34 @@
|
||||
waveshare_io_ch32v003:
|
||||
- id: wave_io
|
||||
i2c_id: i2c_bus
|
||||
address: 0x24
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: wave_io_binary_sensor
|
||||
pin:
|
||||
waveshare_io_ch32v003: wave_io
|
||||
number: 3
|
||||
mode: INPUT
|
||||
inverted: false
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: wave_io_output
|
||||
pin:
|
||||
waveshare_io_ch32v003: wave_io
|
||||
number: 0
|
||||
mode: OUTPUT
|
||||
inverted: false
|
||||
|
||||
- platform: waveshare_io_ch32v003
|
||||
id: wave_io_pwm_output
|
||||
inverted: true
|
||||
zero_means_zero: true
|
||||
safe_pwm_levels:
|
||||
min_value: 0
|
||||
max_value: 247
|
||||
|
||||
sensor:
|
||||
- platform: waveshare_io_ch32v003
|
||||
id: wave_io_adc
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,15 +1,19 @@
|
||||
psram:
|
||||
|
||||
# Tests the high performance request and release; requires the USE_WIFI_RUNTIME_POWER_SAVE define
|
||||
# Tests the high performance and roaming suppression request/release APIs;
|
||||
# requires the USE_WIFI_RUNTIME_POWER_SAVE and USE_WIFI_RUNTIME_ROAMING_SUPPRESSION defines
|
||||
esphome:
|
||||
platformio_options:
|
||||
build_flags:
|
||||
- "-DUSE_WIFI_RUNTIME_POWER_SAVE"
|
||||
- "-DUSE_WIFI_RUNTIME_ROAMING_SUPPRESSION"
|
||||
on_boot:
|
||||
- then:
|
||||
- lambda: |-
|
||||
esphome::wifi::global_wifi_component->request_high_performance();
|
||||
esphome::wifi::global_wifi_component->release_high_performance();
|
||||
esphome::wifi::global_wifi_component->request_roaming_suppression();
|
||||
esphome::wifi::global_wifi_component->release_roaming_suppression();
|
||||
|
||||
wifi:
|
||||
use_psram: true
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Garage Door Open 10"
|
||||
report: "enable"
|
||||
report: "default"
|
||||
- platform: template
|
||||
name: "Garage Door Open 12"
|
||||
report: "force"
|
||||
|
||||
Reference in New Issue
Block a user