mirror of
https://github.com/esphome/esphome.git
synced 2026-10-05 02:21:30 +00:00
Merge remote-tracking branch 'upstream/dev' into 20260218-zigbee-proxy
This commit is contained in:
@@ -14,11 +14,63 @@ include the relevant `.cpp` and `.h` test files there.
|
||||
|
||||
### Override component code generation for testing
|
||||
|
||||
When generating code for testing, ESPHome won't invoke the component's `to_code` function, since most components do not
|
||||
need to generate configuration code for testing.
|
||||
During C++ test builds, `to_code` is suppressed for every component by default — most components do not
|
||||
need to generate configuration code for a unit test binary.
|
||||
|
||||
If you do need to generate code to for example configure compilation flags or add libraries,
|
||||
add the component name to the `CPP_TESTING_CODEGEN_COMPONENTS` allowlist in `script/cpp_unit_test.py`.
|
||||
#### Manifest overrides
|
||||
|
||||
If your component needs to customise code generation behavior for testing — for example to re-enable
|
||||
`to_code`, supply a lightweight stub, add a test-only dependency, or change any other manifest attribute —
|
||||
create an `__init__.py` in your component's test directory and define `override_manifest`:
|
||||
|
||||
**Top-level component** (`tests/components/<component>/__init__.py`):
|
||||
|
||||
```python
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# Re-enable the component's own to_code (needed when the component must
|
||||
# emit C++ setup code that the test binary depends on at link time).
|
||||
manifest.enable_codegen()
|
||||
```
|
||||
|
||||
Or supply a lightweight stub instead of the real `to_code`:
|
||||
|
||||
```python
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
async def to_code_testing(config):
|
||||
# Only emit what the C++ tests actually need
|
||||
pass
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
manifest.dependencies = manifest.dependencies + ["some_test_only_dep"]
|
||||
```
|
||||
|
||||
**Platform component** (`tests/components/<component>/<domain>/__init__.py`,
|
||||
e.g. `tests/components/my_sensor/sensor/__init__.py`):
|
||||
|
||||
```python
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
manifest.enable_codegen()
|
||||
```
|
||||
|
||||
`override_manifest` receives a `ComponentManifestOverride` that wraps the real manifest.
|
||||
Attribute assignments store an override; reads fall back to the real manifest when no
|
||||
override is present.
|
||||
|
||||
Key methods:
|
||||
|
||||
| Method | Effect |
|
||||
|---|---|
|
||||
| `manifest.enable_codegen()` | Remove the `to_code` suppression, re-enabling code generation |
|
||||
| `manifest.restore()` | Clear **all** overrides, reverting every attribute to the original |
|
||||
|
||||
The function is called after `to_code` has already been set to `None`, so calling
|
||||
`enable_codegen()` is a deliberate opt-in.
|
||||
|
||||
## Running component unit tests
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ output:
|
||||
id: ac_dimmer_1
|
||||
gate_pin: ${gate_pin}
|
||||
zero_cross_pin: ${zero_cross_pin}
|
||||
zero_cross_interrupt_type: ANY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: P23
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: A0
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO16
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: GPIO1
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: A5
|
||||
name: ADC Test sensor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
sensor:
|
||||
- id: my_sensor
|
||||
- id: adc_my_sensor
|
||||
platform: adc
|
||||
pin: VCC
|
||||
name: ADC Test sensor
|
||||
@@ -4,3 +4,14 @@ sensor:
|
||||
tvoc:
|
||||
name: AGS10 TVOC
|
||||
update_interval: 60s
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Test AGS10 Actions"
|
||||
on_press:
|
||||
- ags10.set_zero_point:
|
||||
id: ags10_1
|
||||
mode: CURRENT_VALUE
|
||||
- ags10.new_i2c_address:
|
||||
id: ags10_1
|
||||
address: 0x1A
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
id: bin1
|
||||
id: alarm_control_panel_bin1
|
||||
pin: 1
|
||||
|
||||
alarm_control_panel:
|
||||
@@ -18,7 +18,7 @@ alarm_control_panel:
|
||||
pending_time: 15s
|
||||
trigger_time: 30s
|
||||
binary_sensors:
|
||||
- input: bin1
|
||||
- input: alarm_control_panel_bin1
|
||||
bypass_armed_home: true
|
||||
bypass_armed_night: true
|
||||
bypass_auto: true
|
||||
@@ -39,7 +39,7 @@ alarm_control_panel:
|
||||
pending_time: 15s
|
||||
trigger_time: 30s
|
||||
binary_sensors:
|
||||
- input: bin1
|
||||
- input: alarm_control_panel_bin1
|
||||
bypass_armed_home: true
|
||||
bypass_armed_night: true
|
||||
bypass_auto: true
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
animation:
|
||||
- id: rgb565_animation
|
||||
image:
|
||||
- platform: animation
|
||||
id: rgb565_animation
|
||||
file: $component_dir/anim.gif
|
||||
type: RGB565
|
||||
transparency: opaque
|
||||
resize: 50x50
|
||||
- id: rgb_animation
|
||||
- platform: animation
|
||||
id: rgb_animation
|
||||
file: $component_dir/anim.apng
|
||||
type: RGB
|
||||
transparency: chroma_key
|
||||
resize: 50x50
|
||||
- id: grayscale_animation
|
||||
- platform: animation
|
||||
id: grayscale_animation
|
||||
file: $component_dir/anim.apng
|
||||
type: grayscale
|
||||
|
||||
display:
|
||||
lambda: |-
|
||||
id(rgb565_animation).next_frame();
|
||||
id(rgb_animation1).next_frame();
|
||||
id(grayscale_animation2).next_frame();
|
||||
id(rgb_animation).next_frame();
|
||||
id(grayscale_animation).next_frame();
|
||||
it.image(0, 0, rgb565_animation);
|
||||
it.image(120, 0, rgb_animation1);
|
||||
it.image(240, 0, grayscale_animation2);
|
||||
it.image(120, 0, rgb_animation);
|
||||
it.image(240, 0, grayscale_animation);
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 12
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 5
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: main_lcd
|
||||
id: animation_main_lcd
|
||||
spi_id: spi_bus
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Legacy top-level `animation:` form (deprecated; migrates to
|
||||
# `platform: animation`). Config-only test exercising the deprecation path.
|
||||
display:
|
||||
- platform: sdl
|
||||
id: animation_display
|
||||
auto_clear_enabled: false
|
||||
dimensions:
|
||||
width: 480
|
||||
height: 480
|
||||
|
||||
animation:
|
||||
- id: legacy_animation
|
||||
file: $component_dir/anim.gif
|
||||
type: RGB565
|
||||
transparency: opaque
|
||||
resize: 50x50
|
||||
@@ -91,6 +91,52 @@ api:
|
||||
- float_arr.size()
|
||||
- string_arr[0].c_str()
|
||||
- string_arr.size()
|
||||
# Test array + string args used after a non-synchronous action (delay).
|
||||
# The default non-owning types (StringRef, const FixedVector&) would
|
||||
# dangle once rx_buf_ is reused, and FixedVector is non-copyable so
|
||||
# DelayAction's lambda capture would fail to compile. The api codegen
|
||||
# must fall back to owning std::string / std::vector here.
|
||||
- action: array_with_delay
|
||||
variables:
|
||||
name: string
|
||||
int_arr: int[]
|
||||
string_arr: string[]
|
||||
then:
|
||||
- delay: 20ms
|
||||
- logger.log:
|
||||
format: "Delayed: %s (%u ints, %u strings)"
|
||||
args:
|
||||
- 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:
|
||||
@@ -102,12 +148,12 @@ api:
|
||||
lambda: 'return condition;'
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Condition true, value: %d"
|
||||
args: ['value']
|
||||
format: "Condition true, value: %ld"
|
||||
args: ['(long) value']
|
||||
else:
|
||||
- logger.log:
|
||||
format: "Condition false, value: %d"
|
||||
args: ['value']
|
||||
format: "Condition false, value: %ld"
|
||||
args: ['(long) value']
|
||||
- logger.log: "After if/else"
|
||||
# Test nested IfAction (multiple ContinuationAction instances)
|
||||
- action: test_nested_if
|
||||
@@ -153,8 +199,8 @@ api:
|
||||
count: !lambda 'return count;'
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Repeat iteration: %d"
|
||||
args: ['iteration']
|
||||
format: "Repeat iteration: %lu"
|
||||
args: ['(unsigned long) iteration']
|
||||
- logger.log: "After repeat"
|
||||
# Test combined continuations (if + while + repeat)
|
||||
- action: test_combined_continuations
|
||||
@@ -175,8 +221,8 @@ api:
|
||||
lambda: 'return id(api_continuation_test_counter) > 0;'
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Combined: repeat=%d, while=%d"
|
||||
args: ['iteration', 'id(api_continuation_test_counter)']
|
||||
format: "Combined: repeat=%lu, while=%d"
|
||||
args: ['(unsigned long) iteration', 'id(api_continuation_test_counter)']
|
||||
- lambda: 'id(api_continuation_test_counter)--;'
|
||||
else:
|
||||
- logger.log: "Skipped loops"
|
||||
@@ -190,8 +236,8 @@ api:
|
||||
- api.respond:
|
||||
success: true
|
||||
- logger.log:
|
||||
format: "Status response sent (call_id=%d)"
|
||||
args: [call_id]
|
||||
format: "Status response sent (call_id=%lu)"
|
||||
args: ['(unsigned long) call_id']
|
||||
|
||||
- action: test_respond_status_error
|
||||
variables:
|
||||
@@ -211,8 +257,8 @@ api:
|
||||
value: float
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Optional response (call_id=%d, return_response=%d)"
|
||||
args: [call_id, return_response]
|
||||
format: "Optional response (call_id=%lu, return_response=%lu)"
|
||||
args: ['(unsigned long) call_id', '(unsigned long) return_response']
|
||||
- api.respond:
|
||||
data: !lambda |-
|
||||
root["sensor"] = sensor_name;
|
||||
@@ -246,8 +292,8 @@ api:
|
||||
input: string
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Only response (call_id=%d)"
|
||||
args: [call_id]
|
||||
format: "Only response (call_id=%lu)"
|
||||
args: ['(unsigned long) call_id']
|
||||
- api.respond:
|
||||
data: !lambda |-
|
||||
root["input"] = input;
|
||||
@@ -278,7 +324,7 @@ api:
|
||||
event:
|
||||
- platform: template
|
||||
name: Test Event
|
||||
id: test_event
|
||||
id: api_test_event
|
||||
event_types:
|
||||
- single_click
|
||||
- double_click
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<<: !include common-base.yaml
|
||||
packages:
|
||||
common: !include common-base.yaml
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
# Encryption enabled without a key: compiles both frame helpers so the key
|
||||
# can be provisioned at runtime (zero-PSK noise or deprecated plaintext)
|
||||
api:
|
||||
encryption:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
openthread:
|
||||
tlv: 0E080000000000010000
|
||||
@@ -0,0 +1,123 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ios>
|
||||
#include <random>
|
||||
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
#include "esphome/components/api/proto.h"
|
||||
|
||||
namespace esphome::api::testing {
|
||||
|
||||
// Generic varint decoder, used to verify the encoded bytes round-trip back to
|
||||
// the original 48-bit MAC value, independent of the specialized encoder under
|
||||
// test.
|
||||
static uint64_t decode_varint(const uint8_t *buf, size_t len, size_t *consumed) {
|
||||
uint64_t value = 0;
|
||||
int shift = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
value |= static_cast<uint64_t>(buf[i] & 0x7F) << shift;
|
||||
if ((buf[i] & 0x80) == 0) {
|
||||
*consumed = i + 1;
|
||||
return value;
|
||||
}
|
||||
shift += 7;
|
||||
}
|
||||
*consumed = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reference encoder mirroring ProtoEncode::encode_varint_raw_64.
|
||||
static size_t reference_encode(uint64_t value, uint8_t *out) {
|
||||
uint8_t *p = out;
|
||||
if (value < 128) {
|
||||
*p++ = static_cast<uint8_t>(value);
|
||||
return p - out;
|
||||
}
|
||||
do {
|
||||
*p++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
} while (value > 0x7F);
|
||||
*p++ = static_cast<uint8_t>(value);
|
||||
return p - out;
|
||||
}
|
||||
|
||||
// Encode `mac` via the 48-bit fast path and verify:
|
||||
// - byte-identical output to the reference loop
|
||||
// - encoded byte length matches `expected_bytes`
|
||||
// - calc_uint64_48bit_force agrees on the size
|
||||
// - the bytes round-trip through a generic varint decoder
|
||||
static void verify_mac(uint64_t mac, size_t expected_bytes) {
|
||||
ASSERT_LT(mac, 1ULL << 48) << "test fixture mac exceeds 48 bits";
|
||||
|
||||
uint8_t ref_buf[16] = {0};
|
||||
size_t ref_len = reference_encode(mac, ref_buf);
|
||||
|
||||
APIBuffer api_buf;
|
||||
api_buf.resize(16);
|
||||
uint8_t *pos = api_buf.data();
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
uint8_t *proto_debug_end_ = api_buf.data() + api_buf.size();
|
||||
#endif
|
||||
ProtoEncode::encode_varint_raw_48bit(pos PROTO_ENCODE_DEBUG_ARG, mac);
|
||||
size_t new_len = pos - api_buf.data();
|
||||
|
||||
EXPECT_EQ(new_len, expected_bytes) << "mac=0x" << std::hex << mac << std::dec;
|
||||
EXPECT_EQ(ref_len, expected_bytes) << "reference disagrees on length for mac=0x" << std::hex << mac << std::dec;
|
||||
|
||||
for (size_t i = 0; i < new_len; i++) {
|
||||
EXPECT_EQ(api_buf.data()[i], ref_buf[i])
|
||||
<< "byte " << i << " differs for mac=0x" << std::hex << mac << " (got 0x" << static_cast<int>(api_buf.data()[i])
|
||||
<< ", expected 0x" << static_cast<int>(ref_buf[i]) << ")" << std::dec;
|
||||
}
|
||||
|
||||
size_t consumed = 0;
|
||||
uint64_t decoded = decode_varint(api_buf.data(), new_len, &consumed);
|
||||
EXPECT_EQ(consumed, new_len) << "decoder did not consume all bytes for mac=0x" << std::hex << mac << std::dec;
|
||||
EXPECT_EQ(decoded, mac) << "round-trip mismatch for mac=0x" << std::hex << mac << std::dec;
|
||||
|
||||
// Verify the size helper agrees. field_id_size = 1 (typical 1-byte tag).
|
||||
uint32_t calc_size = ProtoSize::calc_uint64_48bit_force(1, mac);
|
||||
EXPECT_EQ(calc_size, 1 + expected_bytes)
|
||||
<< "calc_uint64_48bit_force size mismatch for mac=0x" << std::hex << mac << std::dec;
|
||||
}
|
||||
|
||||
// Compute the canonical varint byte length for a value < 1<<48.
|
||||
static size_t expected_varint_len(uint64_t v) {
|
||||
if (v < (1ULL << 7))
|
||||
return 1;
|
||||
if (v < (1ULL << 14))
|
||||
return 2;
|
||||
if (v < (1ULL << 21))
|
||||
return 3;
|
||||
if (v < (1ULL << 28))
|
||||
return 4;
|
||||
if (v < (1ULL << 35))
|
||||
return 5;
|
||||
if (v < (1ULL << 42))
|
||||
return 6;
|
||||
return 7;
|
||||
}
|
||||
|
||||
// --- Specific MACs requested for verification ---
|
||||
|
||||
TEST(ProtoMacVarint, AllZeros) { verify_mac(0x000000000000ULL, 1); } // 00:00:00:00:00:00
|
||||
TEST(ProtoMacVarint, FirstByteOnly) { verify_mac(0x110000000000ULL, 7); } // 11:00:00:00:00:00
|
||||
TEST(ProtoMacVarint, SecondByteOnly) { verify_mac(0x00AA00000000ULL, 6); } // 00:AA:00:00:00:00
|
||||
TEST(ProtoMacVarint, ThirdByteOnly) { verify_mac(0x0000BB000000ULL, 5); } // 00:00:BB:00:00:00
|
||||
TEST(ProtoMacVarint, FourthByteOnly) { verify_mac(0x000000CC0000ULL, 4); } // 00:00:00:CC:00:00
|
||||
TEST(ProtoMacVarint, FifthByteOnly) { verify_mac(0x00000000DD00ULL, 3); } // 00:00:00:00:DD:00
|
||||
TEST(ProtoMacVarint, SixthByteOnly) { verify_mac(0x0000000000EEULL, 2); } // 00:00:00:00:00:EE
|
||||
TEST(ProtoMacVarint, AllOnes) { verify_mac(0xFFFFFFFFFFFFULL, 7); } // FF:FF:FF:FF:FF:FF
|
||||
|
||||
// 100 deterministic-random 48-bit MACs to catch regressions across the space.
|
||||
TEST(ProtoMacVarint, RandomSample) {
|
||||
// NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) -- fixed seed for reproducibility
|
||||
std::mt19937_64 rng(0xC0FFEE);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
uint64_t mac = rng() & 0xFFFFFFFFFFFFULL;
|
||||
verify_mac(mac, expected_varint_len(mac));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::api::testing
|
||||
@@ -0,0 +1,16 @@
|
||||
# Declares the component graph the C++ unit test build needs so that the aqi
|
||||
# component's sources (which include sensor.h) compile. to_code is suppressed by
|
||||
# the test harness; this only pulls the sensor + aqi source/include paths in.
|
||||
# Loaded with plain yaml.safe_load, so avoid lambdas / ESPHome-tagged values here.
|
||||
sensor:
|
||||
- platform: template
|
||||
id: pm25_sensor
|
||||
name: "PM2.5"
|
||||
- platform: template
|
||||
id: pm10_sensor
|
||||
name: "PM10"
|
||||
- platform: aqi
|
||||
name: "AQI"
|
||||
pm_2_5: pm25_sensor
|
||||
pm_10_0: pm10_sensor
|
||||
calculation_type: AQI
|
||||
@@ -20,3 +20,10 @@ sensor:
|
||||
pm_2_5: pm25_sensor
|
||||
pm_10_0: pm10_sensor
|
||||
calculation_type: CAQI
|
||||
|
||||
- platform: aqi
|
||||
name: "Air Quality Index (AQI, extended)"
|
||||
pm_2_5: pm25_sensor
|
||||
pm_10_0: pm10_sensor
|
||||
calculation_type: AQI
|
||||
extended_range: true
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/aqi/aqi_calculator.h"
|
||||
#include "esphome/components/aqi/caqi_calculator.h"
|
||||
|
||||
namespace esphome::aqi::testing {
|
||||
|
||||
// US AQI (EPA 2024): PM2.5 225.5-500.4 -> 301-500, PM10 425-604 -> 301-500.
|
||||
|
||||
TEST(USAQI, LowRangeUnaffectedByExtendedFlag) {
|
||||
AQICalculator calc;
|
||||
// PM2.5 25 drives over PM10 50; well below the top band, so the flag changes nothing.
|
||||
EXPECT_EQ(calc.get_aqi(25.0f, 50.0f, false), 81);
|
||||
EXPECT_EQ(calc.get_aqi(25.0f, 50.0f, true), 81);
|
||||
}
|
||||
|
||||
TEST(USAQI, HazardousInterpolatesNotPinnedAt301) {
|
||||
AQICalculator calc;
|
||||
// Regression guard: the old FLT_MAX top bucket collapsed every hazardous reading to 301.
|
||||
EXPECT_EQ(calc.get_aqi(225.5f, 0.0f, false), 301); // band start
|
||||
EXPECT_EQ(calc.get_aqi(250.0f, 0.0f, false), 319); // interpolated, not 301
|
||||
EXPECT_EQ(calc.get_aqi(500.4f, 0.0f, false), 500); // band top
|
||||
}
|
||||
|
||||
TEST(USAQI, DefaultClampsAtStandardMaximum) {
|
||||
AQICalculator calc;
|
||||
EXPECT_EQ(calc.get_aqi(600.0f, 0.0f, false), 500);
|
||||
EXPECT_EQ(calc.get_aqi(1000.0f, 0.0f, false), 500);
|
||||
EXPECT_EQ(calc.get_aqi(0.0f, 604.0f, false), 500); // PM10 top breakpoint
|
||||
}
|
||||
|
||||
TEST(USAQI, ExtendedRangeExtrapolatesBeyond500) {
|
||||
AQICalculator calc;
|
||||
EXPECT_EQ(calc.get_aqi(600.0f, 0.0f, true), 572);
|
||||
EXPECT_EQ(calc.get_aqi(1000.0f, 0.0f, true), 862);
|
||||
EXPECT_EQ(calc.get_aqi(0.0f, 700.0f, true), 607); // PM10 extrapolated past 500
|
||||
}
|
||||
|
||||
TEST(USAQI, ExtendedRangeSaturatesUint16NoWraparound) {
|
||||
AQICalculator calc;
|
||||
// An absurd concentration would overflow uint16_t; it must saturate, not wrap to a small value.
|
||||
EXPECT_EQ(calc.get_aqi(100000.0f, 0.0f, true), 65535);
|
||||
}
|
||||
|
||||
TEST(USAQI, WorseOfTwoPollutantsWins) {
|
||||
AQICalculator calc;
|
||||
// PM10 604 -> 500 dominates PM2.5 25 -> 81.
|
||||
EXPECT_EQ(calc.get_aqi(25.0f, 604.0f, false), 500);
|
||||
}
|
||||
|
||||
// CAQI (CITEAIR): no maximum by spec -- the top ">100" class is open, so it is always unbounded
|
||||
// and the extended_range flag does not apply.
|
||||
|
||||
TEST(CAQI, LowRange) {
|
||||
CAQICalculator calc;
|
||||
EXPECT_EQ(calc.get_aqi(25.0f, 50.0f, false), 50);
|
||||
}
|
||||
|
||||
TEST(CAQI, ContinuousAt100NoPinAt101) {
|
||||
CAQICalculator calc;
|
||||
// Old code pinned everything above the top breakpoint to 101; now it reaches exactly 100.
|
||||
EXPECT_EQ(calc.get_aqi(110.1f, 0.0f, false), 100);
|
||||
}
|
||||
|
||||
TEST(CAQI, UnboundedAboveTopBand) {
|
||||
CAQICalculator calc;
|
||||
EXPECT_EQ(calc.get_aqi(200.0f, 0.0f, false), 139);
|
||||
EXPECT_EQ(calc.get_aqi(2000.0f, 0.0f, false), 925);
|
||||
}
|
||||
|
||||
TEST(CAQI, ExtendedRangeFlagIsIgnored) {
|
||||
CAQICalculator calc;
|
||||
// CAQI is always unbounded, so the flag must make no difference either way.
|
||||
EXPECT_EQ(calc.get_aqi(200.0f, 0.0f, true), calc.get_aqi(200.0f, 0.0f, false));
|
||||
EXPECT_EQ(calc.get_aqi(2000.0f, 0.0f, true), calc.get_aqi(2000.0f, 0.0f, false));
|
||||
}
|
||||
|
||||
TEST(CAQI, SaturatesUint16NoWraparound) {
|
||||
CAQICalculator calc;
|
||||
// CAQI is unbounded, so an extreme reading can extrapolate past uint16_t; it must saturate,
|
||||
// not wrap around to a small (falsely "good") value.
|
||||
EXPECT_EQ(calc.get_aqi(200000.0f, 0.0f, false), 65535);
|
||||
}
|
||||
|
||||
} // namespace esphome::aqi::testing
|
||||
@@ -12,6 +12,11 @@ esphome:
|
||||
trigger_keep: 10s
|
||||
stage_gain: 3
|
||||
power_consumption: 70uA
|
||||
- at581x.settings:
|
||||
id: waveradar
|
||||
frequency: !lambda "return 5800;"
|
||||
poweron_selfcheck_time: !lambda "return 2000;"
|
||||
power_consumption: !lambda "return 70;"
|
||||
- at581x.reset:
|
||||
id: waveradar
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
audio:
|
||||
codecs:
|
||||
flac:
|
||||
buffer_memory: internal
|
||||
mp3:
|
||||
buffer_memory: psram
|
||||
opus:
|
||||
floating_point: false
|
||||
state_memory: psram
|
||||
pseudostack:
|
||||
threadsafe: false
|
||||
buffer_memory: internal
|
||||
size: 80000
|
||||
wav:
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
@@ -1,5 +1,5 @@
|
||||
audio_file:
|
||||
- id: test_audio
|
||||
- id: audio_file_test_audio
|
||||
file:
|
||||
type: local
|
||||
path: $component_dir/test.wav
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
audio_file:
|
||||
- id: audio_file_test_audio
|
||||
file:
|
||||
type: local
|
||||
path: $component_dir/test.wav
|
||||
|
||||
media_source:
|
||||
- platform: audio_file
|
||||
id: audio_file_source
|
||||
# task_stack_in_psram: false must validate without a psram: component
|
||||
task_stack_in_psram: false
|
||||
@@ -0,0 +1,7 @@
|
||||
psram:
|
||||
|
||||
media_source:
|
||||
- platform: audio_http
|
||||
id: audio_http_source
|
||||
buffer_size: 100000
|
||||
task_stack_in_psram: true
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
@@ -1,7 +1,7 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_i2c_display
|
||||
id: axs15231_ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 19
|
||||
pages:
|
||||
@@ -13,6 +13,6 @@ touchscreen:
|
||||
- platform: axs15231
|
||||
i2c_id: i2c_bus
|
||||
id: axs15231_touchscreen
|
||||
display: ssd1306_i2c_display
|
||||
display: axs15231_ssd1306_i2c_display
|
||||
interrupt_pin: 20
|
||||
reset_pin: 18
|
||||
|
||||
@@ -4,7 +4,7 @@ packages:
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: ssd1306_display
|
||||
id: axs15231_ssd1306_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: 13
|
||||
pages:
|
||||
@@ -15,5 +15,5 @@ display:
|
||||
touchscreen:
|
||||
- platform: axs15231
|
||||
i2c_id: i2c_bus
|
||||
display: ssd1306_display
|
||||
display: axs15231_ssd1306_display
|
||||
interrupt_pin: 12
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
id: bang_bang_template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
@@ -8,7 +8,7 @@ switch:
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: template_sensor1
|
||||
id: bang_bang_template_sensor1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return 42.0;
|
||||
@@ -20,16 +20,16 @@ sensor:
|
||||
climate:
|
||||
- platform: bang_bang
|
||||
name: Bang Bang Climate
|
||||
sensor: template_sensor1
|
||||
humidity_sensor: template_sensor1
|
||||
sensor: bang_bang_template_sensor1
|
||||
humidity_sensor: bang_bang_template_sensor1
|
||||
default_target_temperature_low: 18°C
|
||||
default_target_temperature_high: 24°C
|
||||
idle_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: bang_bang_template_switch1
|
||||
cool_action:
|
||||
- switch.turn_on: template_switch2
|
||||
heat_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: bang_bang_template_switch1
|
||||
away_config:
|
||||
default_target_temperature_low: 16°C
|
||||
default_target_temperature_high: 20°C
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
trigger_on_initial_state: true
|
||||
id: some_binary_sensor
|
||||
id: binary_sensor_some_binary_sensor
|
||||
name: "Random binary"
|
||||
lambda: return (random_uint32() & 1) == 0;
|
||||
filters:
|
||||
@@ -21,7 +21,7 @@ binary_sensor:
|
||||
time_off: 100ms
|
||||
time_on: 400ms
|
||||
- lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
if (id(binary_sensor_some_binary_sensor).state) {
|
||||
return x;
|
||||
}
|
||||
return {};
|
||||
@@ -36,7 +36,7 @@ binary_sensor:
|
||||
- logger.log:
|
||||
format: "New state is %s"
|
||||
args: ['x.has_value() ? ONOFF(x) : "Unknown"']
|
||||
- binary_sensor.invalidate_state: some_binary_sensor
|
||||
- binary_sensor.invalidate_state: binary_sensor_some_binary_sensor
|
||||
|
||||
# Test autorepeat with default configuration (no timings)
|
||||
- platform: template
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: bin1
|
||||
id: binary_sensor_map_bin1
|
||||
lambda: |-
|
||||
if (millis() > 10000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
- platform: template
|
||||
id: bin2
|
||||
id: binary_sensor_map_bin2
|
||||
lambda: |-
|
||||
if (millis() > 20000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
- platform: template
|
||||
id: bin3
|
||||
id: binary_sensor_map_bin3
|
||||
lambda: |-
|
||||
if (millis() > 30000) {
|
||||
return true;
|
||||
@@ -26,33 +26,33 @@ sensor:
|
||||
name: Binary Sensor Map Group
|
||||
type: group
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
value: 10.0
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
value: 15.0
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map Sum
|
||||
type: sum
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
value: 10.0
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
value: 15.0
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map Bayesian
|
||||
type: bayesian
|
||||
prior: 0.4
|
||||
observations:
|
||||
- binary_sensor: bin1
|
||||
- binary_sensor: binary_sensor_map_bin1
|
||||
prob_given_true: 0.9
|
||||
prob_given_false: 0.4
|
||||
- binary_sensor: bin2
|
||||
- binary_sensor: binary_sensor_map_bin2
|
||||
prob_given_true: 0.7
|
||||
prob_given_false: 0.05
|
||||
- binary_sensor: bin3
|
||||
- binary_sensor: binary_sensor_map_bin3
|
||||
prob_given_true: 0.8
|
||||
prob_given_false: 0.2
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -56,7 +56,7 @@ sensor:
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
id: test_number
|
||||
id: ble_client_test_number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 255
|
||||
@@ -72,5 +72,5 @@ button:
|
||||
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
|
||||
characteristic_uuid: "abcd1235-abcd-1234-abcd-abcd12345678"
|
||||
value: !lambda |-
|
||||
uint8_t val = (uint8_t)id(test_number).state;
|
||||
uint8_t val = (uint8_t)id(ble_client_test_number).state;
|
||||
return std::vector<uint8_t>{0xAA, val, 0xBB};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# resolve_irk() is compiled only when a sensor configures irk:
|
||||
# (request_irk_support() emits USE_BLE_DEVICE_IRK). The unit-test build has
|
||||
# no sensors, so emit the define here to put the real IRK path under test.
|
||||
async def to_code_testing(config):
|
||||
cg.add_define("USE_BLE_DEVICE_IRK")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_device.h"
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
// from_scan_result() ingests BLE controller order (LSB-first); the public
|
||||
// accessors must expose the historical esp32 semantics: address() in printable
|
||||
// (MSB-first) order, address_uint64() with byte 0 in the LSB, address_str()
|
||||
// printed MSB-first.
|
||||
namespace {
|
||||
// Device AA:BB:CC:DD:EE:FF — controller order delivers FF first.
|
||||
const uint8_t MAC_LSB_FIRST[6] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa};
|
||||
} // namespace
|
||||
|
||||
TEST(BleDeviceAddress, AccessorsMatchEsp32Semantics) {
|
||||
ESPBTDevice device;
|
||||
device.from_scan_result(MAC_LSB_FIRST, -50, BLE_ADDR_TYPE_PUBLIC, nullptr, 0);
|
||||
|
||||
const uint8_t *raw = device.address();
|
||||
EXPECT_EQ(raw[0], 0xaa); // MSB first, like ESP-IDF's bda
|
||||
EXPECT_EQ(raw[5], 0xff);
|
||||
|
||||
EXPECT_EQ(device.address_uint64(), 0xAABBCCDDEEFFULL);
|
||||
|
||||
EXPECT_EQ(device.address_str(), "AA:BB:CC:DD:EE:FF");
|
||||
}
|
||||
|
||||
} // namespace esphome::ble_device_base::testing
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_aes_ccm.h"
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
// Reference vector generated with Python `cryptography` AESCCM(tag_length=4),
|
||||
// using the same AES-128-CCM parameters BTHome advertisements use: a 16-byte
|
||||
// key, a 13-byte nonce, a 4-byte authentication tag and no associated data.
|
||||
namespace {
|
||||
const uint8_t KEY[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
|
||||
const uint8_t NONCE[13] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c};
|
||||
const uint8_t CIPHERTEXT[7] = {0x68, 0xb4, 0xf6, 0xc5, 0x2b, 0xf8, 0xaf};
|
||||
const uint8_t TAG[4] = {0x48, 0x4d, 0xaa, 0x56};
|
||||
const uint8_t PLAINTEXT[7] = {0x02, 0x01, 0x64, 0x03, 0x10, 0x8a, 0x01};
|
||||
} // namespace
|
||||
|
||||
TEST(BleAesCcm, DecryptsAndAuthenticatesKnownVector) {
|
||||
uint8_t out[sizeof(PLAINTEXT)] = {};
|
||||
EXPECT_TRUE(aes_ccm_auth_decrypt(KEY, NONCE, sizeof(NONCE), nullptr, 0, CIPHERTEXT, sizeof(CIPHERTEXT), out, TAG,
|
||||
sizeof(TAG)));
|
||||
EXPECT_EQ(0, memcmp(out, PLAINTEXT, sizeof(PLAINTEXT)));
|
||||
}
|
||||
|
||||
TEST(BleAesCcm, RejectsTamperedTag) {
|
||||
uint8_t bad_tag[sizeof(TAG)];
|
||||
memcpy(bad_tag, TAG, sizeof(TAG));
|
||||
bad_tag[0] ^= 0x01;
|
||||
uint8_t out[sizeof(PLAINTEXT)] = {};
|
||||
EXPECT_FALSE(aes_ccm_auth_decrypt(KEY, NONCE, sizeof(NONCE), nullptr, 0, CIPHERTEXT, sizeof(CIPHERTEXT), out, bad_tag,
|
||||
sizeof(bad_tag)));
|
||||
}
|
||||
|
||||
TEST(BleAesCcm, RejectsTamperedCiphertext) {
|
||||
uint8_t bad_ct[sizeof(CIPHERTEXT)];
|
||||
memcpy(bad_ct, CIPHERTEXT, sizeof(CIPHERTEXT));
|
||||
bad_ct[0] ^= 0x01;
|
||||
uint8_t out[sizeof(PLAINTEXT)] = {};
|
||||
EXPECT_FALSE(
|
||||
aes_ccm_auth_decrypt(KEY, NONCE, sizeof(NONCE), nullptr, 0, bad_ct, sizeof(bad_ct), out, TAG, sizeof(TAG)));
|
||||
}
|
||||
|
||||
TEST(BleAesCcm, RejectsWrongKey) {
|
||||
uint8_t bad_key[sizeof(KEY)];
|
||||
memcpy(bad_key, KEY, sizeof(KEY));
|
||||
bad_key[0] ^= 0xFF;
|
||||
uint8_t out[sizeof(PLAINTEXT)] = {};
|
||||
EXPECT_FALSE(aes_ccm_auth_decrypt(bad_key, NONCE, sizeof(NONCE), nullptr, 0, CIPHERTEXT, sizeof(CIPHERTEXT), out, TAG,
|
||||
sizeof(TAG)));
|
||||
}
|
||||
|
||||
} // namespace esphome::ble_device_base::testing
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_device.h"
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
// A 16- or 32-bit UUID must compare equal to its 128-bit Bluetooth Base UUID form, matching
|
||||
// esp32_ble_tracker. The 128-bit raw is the base UUID (LSB-first) with the short value at
|
||||
// bytes 12.. : here 0x1234 -> bytes [12]=0x34, [13]=0x12.
|
||||
TEST(BleDeviceUuid, ShortFormMatchesEquivalentLongForm) {
|
||||
const ESPBTUUID u16 = ESPBTUUID::from_uint16(0x1234);
|
||||
const uint8_t raw128[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
|
||||
0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00};
|
||||
const ESPBTUUID u128 = ESPBTUUID::from_raw(raw128);
|
||||
EXPECT_TRUE(u16 == u128);
|
||||
EXPECT_TRUE(u128 == u16); // symmetric
|
||||
}
|
||||
|
||||
TEST(BleDeviceUuid, ThirtyTwoBitMatchesEquivalentLongForm) {
|
||||
const ESPBTUUID u32 = ESPBTUUID::from_uint32(0x1122AAFF);
|
||||
const uint8_t raw128[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
|
||||
0x00, 0x10, 0x00, 0x00, 0xFF, 0xAA, 0x22, 0x11};
|
||||
const ESPBTUUID u128 = ESPBTUUID::from_raw(raw128);
|
||||
EXPECT_TRUE(u32 == u128);
|
||||
}
|
||||
|
||||
TEST(BleDeviceUuid, DifferentUuidsDoNotMatch) {
|
||||
EXPECT_FALSE(ESPBTUUID::from_uint16(0x1234) == ESPBTUUID::from_uint16(0x1235));
|
||||
const uint8_t raw128[16] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
|
||||
0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00};
|
||||
// Same low bytes but a non-base prefix is a genuinely different 128-bit UUID.
|
||||
uint8_t custom[16];
|
||||
memcpy(custom, raw128, 16);
|
||||
custom[0] ^= 0x01;
|
||||
EXPECT_FALSE(ESPBTUUID::from_uint16(0x1234) == ESPBTUUID::from_raw(custom));
|
||||
}
|
||||
|
||||
} // namespace esphome::ble_device_base::testing
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "esphome/components/ble_device_base/ble_device.h"
|
||||
|
||||
namespace esphome::ble_device_base::testing {
|
||||
|
||||
// Reference vector generated with Python `cryptography` AES-128-ECB following
|
||||
// the RPA resolution procedure (Bluetooth Core, Vol 3 Part H §2.2.2):
|
||||
// hash = e(IRK, prand), where prand is the top 3 address bytes and the hash
|
||||
// must equal the low 3 address bytes.
|
||||
namespace {
|
||||
const uint8_t IRK[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
|
||||
// 4A:2B:7C:FB:7B:21 — prand 4A:2B:7C (two MSBs = 01, an RPA), hash FB:7B:21.
|
||||
const uint8_t RPA_LSB_FIRST[6] = {0x21, 0x7b, 0xfb, 0x7c, 0x2b, 0x4a};
|
||||
|
||||
ESPBTDevice make_device(const uint8_t mac_lsb_first[6]) {
|
||||
ESPBTDevice device;
|
||||
device.from_scan_result(mac_lsb_first, /*rssi=*/-60, /*addr_type=*/BLE_ADDR_TYPE_RPA_RANDOM, nullptr, 0);
|
||||
return device;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(BleIrk, ResolvesMatchingRpa) {
|
||||
ESPBTDevice device = make_device(RPA_LSB_FIRST);
|
||||
EXPECT_TRUE(device.resolve_irk(IRK));
|
||||
}
|
||||
|
||||
TEST(BleIrk, RejectsWrongIrk) {
|
||||
uint8_t wrong_irk[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
wrong_irk[i] = IRK[i] ^ 0xff;
|
||||
ESPBTDevice device = make_device(RPA_LSB_FIRST);
|
||||
EXPECT_FALSE(device.resolve_irk(wrong_irk));
|
||||
}
|
||||
|
||||
TEST(BleIrk, RejectsWrongAddress) {
|
||||
uint8_t other_mac[6];
|
||||
for (int i = 0; i < 6; i++)
|
||||
other_mac[i] = RPA_LSB_FIRST[i];
|
||||
other_mac[0] ^= 0x01; // corrupt one hash byte
|
||||
ESPBTDevice device = make_device(other_mac);
|
||||
EXPECT_FALSE(device.resolve_irk(IRK));
|
||||
}
|
||||
|
||||
} // namespace esphome::ble_device_base::testing
|
||||
@@ -1,6 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
esp32_ble_tracker:
|
||||
|
||||
esp32_ble:
|
||||
max_connections: 9
|
||||
|
||||
bluetooth_proxy:
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
sensor:
|
||||
- platform: bmi270
|
||||
name: "BMI270 Temperature"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: acceleration_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: "BMI270 Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: acceleration_z
|
||||
name: "BMI270 Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_x
|
||||
name: "BMI270 Gyro X"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_y
|
||||
name: "BMI270 Gyro Y"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: gyroscope_z
|
||||
name: "BMI270 Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_x
|
||||
name: "BMI270 Angular Rate X"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_y
|
||||
name: "BMI270 Angular Rate Y"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: angular_rate_z
|
||||
name: "BMI270 Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: pitch
|
||||
name: "BMI270 Pitch"
|
||||
- platform: motion
|
||||
motion_id: bmi270_motion
|
||||
type: roll
|
||||
name: "BMI270 Roll"
|
||||
|
||||
motion:
|
||||
- platform: bmi270
|
||||
id: bmi270_motion
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
# Accelerometer output data rate: 12_5HZ | 25HZ | 50HZ | 100HZ |
|
||||
# 200HZ | 400HZ | 800HZ | 1600HZ
|
||||
accelerometer_odr: 100HZ
|
||||
|
||||
# Gyroscope full-scale range: 125DPS | 250DPS | 500DPS | 1000DPS | 2000DPS
|
||||
gyroscope_range: 2000DPS
|
||||
|
||||
# Gyroscope output data rate: 25HZ | 50HZ | 100HZ | 200HZ |
|
||||
# 400HZ | 800HZ | 1600HZ | 3200HZ
|
||||
gyroscope_odr: 200HZ
|
||||
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,9 @@
|
||||
sensor:
|
||||
- platform: bmp581_spi
|
||||
cs_pin: ${cs_pin}
|
||||
temperature:
|
||||
name: BMP581 Temperature
|
||||
iir_filter: 2x
|
||||
pressure:
|
||||
name: BMP581 Pressure
|
||||
oversampling: 128x
|
||||
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO5
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO15
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO5
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,6 +1,6 @@
|
||||
canbus:
|
||||
- platform: esp32_can
|
||||
id: esp32_internal_can
|
||||
id: canbus_esp32_internal_can
|
||||
rx_pin: 4
|
||||
tx_pin: 5
|
||||
can_id: 4
|
||||
@@ -40,7 +40,7 @@ canbus:
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
id: test_number
|
||||
id: canbus_test_number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 255
|
||||
@@ -50,10 +50,17 @@ button:
|
||||
- platform: template
|
||||
name: Canbus Actions
|
||||
on_press:
|
||||
- canbus.send:
|
||||
can_id: 0x601
|
||||
data: [0, 1, 2]
|
||||
- canbus.send:
|
||||
can_id: 0x1FFFFFFF
|
||||
use_extended_id: true
|
||||
data: [0, 1, 2]
|
||||
- canbus.send: "abc"
|
||||
- canbus.send: [0, 1, 2]
|
||||
- canbus.send: !lambda return {0, 1, 2};
|
||||
# Test canbus.send with lambda that references a component (function pointer)
|
||||
- canbus.send: !lambda |-
|
||||
uint8_t val = (uint8_t)id(test_number).state;
|
||||
uint8_t val = (uint8_t)id(canbus_test_number).state;
|
||||
return std::vector<uint8_t>{0xAA, val, 0xBB};
|
||||
|
||||
@@ -17,6 +17,15 @@ cc1101:
|
||||
sync0: 0x91
|
||||
sync1: 0xD3
|
||||
num_preamble: 2
|
||||
foc_bs_cs_gate: true
|
||||
foc_pre_k: "2K"
|
||||
foc_post_k: "K/2"
|
||||
foc_limit: "BW/4"
|
||||
bs_pre_ki: "3KI"
|
||||
bs_pre_kp: "4KP"
|
||||
bs_post_ki: "KI/2"
|
||||
bs_post_kp: "KP"
|
||||
bs_limit: "12.5%"
|
||||
on_packet:
|
||||
then:
|
||||
- lambda: |-
|
||||
|
||||
@@ -6,13 +6,13 @@ cd74hc4067:
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: esp_adc_sensor
|
||||
id: cd74hc4067_esp_adc_sensor
|
||||
pin: ${pin}
|
||||
- platform: cd74hc4067
|
||||
id: cd74hc4067_adc_0
|
||||
number: 0
|
||||
sensor: esp_adc_sensor
|
||||
sensor: cd74hc4067_esp_adc_sensor
|
||||
- platform: cd74hc4067
|
||||
id: cd74hc4067_adc_1
|
||||
number: 1
|
||||
sensor: esp_adc_sensor
|
||||
sensor: cd74hc4067_esp_adc_sensor
|
||||
|
||||
@@ -29,3 +29,74 @@ climate:
|
||||
heat_action:
|
||||
- switch.turn_on: climate_heater_switch
|
||||
- switch.turn_off: climate_cooler_switch
|
||||
# Thermostat-based climate so climate.control: action variants get build
|
||||
# coverage (bang_bang doesn't support fan modes, presets, etc.). Climate
|
||||
# has no template platform, so thermostat is the right vehicle.
|
||||
- platform: thermostat
|
||||
id: climate_test_thermostat
|
||||
name: Test Thermostat
|
||||
sensor: climate_temperature_sensor
|
||||
min_idle_time: 30s
|
||||
min_heating_off_time: 300s
|
||||
min_heating_run_time: 300s
|
||||
min_cooling_off_time: 300s
|
||||
min_cooling_run_time: 300s
|
||||
heat_action:
|
||||
- logger.log: heating
|
||||
idle_action:
|
||||
- logger.log: idle
|
||||
cool_action:
|
||||
- logger.log: cooling
|
||||
auto_mode:
|
||||
- logger.log: auto
|
||||
heat_cool_mode:
|
||||
- logger.log: heat_cool
|
||||
preset:
|
||||
- name: Default
|
||||
default_target_temperature_low: 18°C
|
||||
default_target_temperature_high: 22°C
|
||||
|
||||
button:
|
||||
# Exercise the climate.control: action so ControlAction templates get
|
||||
# build coverage. Various field combinations are tested.
|
||||
- platform: template
|
||||
name: "Climate Control Mode"
|
||||
on_press:
|
||||
- climate.control:
|
||||
id: climate_test_thermostat
|
||||
mode: HEAT
|
||||
- platform: template
|
||||
name: "Climate Control Mode And Temps"
|
||||
on_press:
|
||||
- climate.control:
|
||||
id: climate_test_thermostat
|
||||
mode: HEAT_COOL
|
||||
target_temperature_low: 19.0°C
|
||||
target_temperature_high: 23.0°C
|
||||
- platform: template
|
||||
name: "Climate Control Lambda Temp"
|
||||
on_press:
|
||||
- climate.control:
|
||||
id: climate_test_thermostat
|
||||
target_temperature_high: !lambda "return 21.5;"
|
||||
- platform: template
|
||||
name: "Climate Control Off"
|
||||
on_press:
|
||||
- climate.control:
|
||||
id: climate_test_thermostat
|
||||
mode: "OFF"
|
||||
|
||||
# Exercise climate.control inside a trigger with non-empty Ts (number on_value
|
||||
# passes float).
|
||||
number:
|
||||
- platform: template
|
||||
id: climate_target_temp_number
|
||||
optimistic: true
|
||||
min_value: 16
|
||||
max_value: 28
|
||||
step: 0.5
|
||||
on_value:
|
||||
then:
|
||||
- climate.control:
|
||||
id: climate_test_thermostat
|
||||
target_temperature_high: !lambda "return x;"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
sensor:
|
||||
- platform: template
|
||||
id: temp_sensor
|
||||
id: climate_ir_lg_temp_sensor
|
||||
lambda: return 22.0;
|
||||
update_interval: 60s
|
||||
- platform: template
|
||||
@@ -12,5 +12,5 @@ climate:
|
||||
- platform: climate_ir_lg
|
||||
name: LG Climate
|
||||
transmitter_id: xmitr
|
||||
sensor: temp_sensor
|
||||
sensor: climate_ir_lg_temp_sensor
|
||||
humidity_sensor: humidity_sensor
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: color_temperature_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: color_temperature_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
light:
|
||||
- platform: color_temperature
|
||||
name: Lights
|
||||
color_temperature: light_output_1
|
||||
brightness: light_output_2
|
||||
color_temperature: color_temperature_light_output_1
|
||||
brightness: color_temperature_light_output_2
|
||||
cold_white_color_temperature: 153 mireds
|
||||
warm_white_color_temperature: 500 mireds
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
display:
|
||||
- platform: qspi_dbi
|
||||
model: RM690B0
|
||||
data_rate: 80MHz
|
||||
spi_mode: mode0
|
||||
dimensions:
|
||||
width: 450
|
||||
height: 600
|
||||
offset_width: 16
|
||||
color_order: rgb
|
||||
invert_colors: false
|
||||
brightness: 255
|
||||
cs_pin: 11
|
||||
reset_pin: 13
|
||||
enable_pin: 9
|
||||
|
||||
- platform: qspi_dbi
|
||||
model: CUSTOM
|
||||
id: main_lcd
|
||||
draw_from_origin: true
|
||||
dimensions:
|
||||
height: 240
|
||||
width: 536
|
||||
transform:
|
||||
mirror_x: true
|
||||
swap_xy: true
|
||||
color_order: rgb
|
||||
brightness: 255
|
||||
cs_pin: 6
|
||||
reset_pin: 17
|
||||
enable_pin: 38
|
||||
init_sequence:
|
||||
- [0x3A, 0x66]
|
||||
- [0x11]
|
||||
- delay 120ms
|
||||
- [0x29]
|
||||
- delay 20ms
|
||||
@@ -1,4 +0,0 @@
|
||||
packages:
|
||||
qspi: !include ../../test_build_components/common/qspi/esp32-s3-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,17 +1,17 @@
|
||||
output:
|
||||
- platform: ${pwm_platform}
|
||||
id: fan_output_1
|
||||
id: copy_fan_output_1
|
||||
pin: ${pin}
|
||||
|
||||
fan:
|
||||
- platform: speed
|
||||
id: fan_speed
|
||||
output: fan_output_1
|
||||
id: copy_fan_speed
|
||||
output: copy_fan_output_1
|
||||
preset_modes:
|
||||
- Eco
|
||||
- Turbo
|
||||
- platform: copy
|
||||
source_id: fan_speed
|
||||
source_id: copy_fan_speed
|
||||
name: Fan Speed Copy
|
||||
|
||||
select:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# core (esphome/core/config.py) must run its to_code during C++ test builds
|
||||
# because it bootstraps the fundamental application infrastructure that all
|
||||
# components depend on (component registration, event loop, etc.).
|
||||
manifest.enable_codegen()
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
TEST(HelpersTest, Ilog10PowersOfTen) {
|
||||
EXPECT_EQ(ilog10(1.0f), 0);
|
||||
EXPECT_EQ(ilog10(10.0f), 1);
|
||||
EXPECT_EQ(ilog10(100.0f), 2);
|
||||
EXPECT_EQ(ilog10(1000.0f), 3);
|
||||
EXPECT_EQ(ilog10(10000.0f), 4);
|
||||
EXPECT_EQ(ilog10(100000.0f), 5);
|
||||
EXPECT_EQ(ilog10(0.1f), -1);
|
||||
EXPECT_EQ(ilog10(0.001f), -3);
|
||||
}
|
||||
|
||||
TEST(HelpersTest, Ilog10General) {
|
||||
EXPECT_EQ(ilog10(5.0f), 0);
|
||||
EXPECT_EQ(ilog10(9.99f), 0);
|
||||
EXPECT_EQ(ilog10(50.0f), 1);
|
||||
EXPECT_EQ(ilog10(99.0f), 1);
|
||||
EXPECT_EQ(ilog10(999.0f), 2);
|
||||
EXPECT_EQ(ilog10(0.5f), -1);
|
||||
EXPECT_EQ(ilog10(0.0072f), -3);
|
||||
EXPECT_EQ(ilog10(120000.0f), 5);
|
||||
EXPECT_EQ(ilog10(123456.789f), 5);
|
||||
}
|
||||
|
||||
TEST(HelpersTest, Ilog10Negative) {
|
||||
EXPECT_EQ(ilog10(-1.0f), 0);
|
||||
EXPECT_EQ(ilog10(-10.0f), 1);
|
||||
EXPECT_EQ(ilog10(-0.1f), -1);
|
||||
EXPECT_EQ(ilog10(-123.456f), 2);
|
||||
}
|
||||
|
||||
// Verify that ilog10 + pow10_int produces the same rounding result as log10/pow.
|
||||
// ilog10 may differ from floor(log10f()) for values not exactly representable in float
|
||||
// (e.g. 0.01f is 0.00999...), but the full round-trip must match.
|
||||
TEST(HelpersTest, Ilog10RoundTripMatchesLog10) {
|
||||
float values[] = {0.0072f, 0.05f, 0.1f, 0.5f, 1.0f, 3.14f, 9.99f, 10.0f, 42.0f, 100.0f,
|
||||
1234.5f, 9999.0f, 10000.0f, 99999.0f, 120000.0f, 999999.0f, -1.0f, -0.1f, -123.456f, -10000.0f};
|
||||
for (uint8_t digits = 1; digits <= 6; digits++) {
|
||||
for (float v : values) {
|
||||
// New implementation using ilog10 + pow10_int
|
||||
float factor_new = pow10_int(digits - 1 - ilog10(v));
|
||||
float result_new = roundf(v * factor_new) / factor_new;
|
||||
|
||||
// Reference using log10/pow
|
||||
double factor_ref = pow(10.0, digits - std::ceil(std::log10(std::fabs(v))));
|
||||
float result_ref = static_cast<float>(round(v * factor_ref) / factor_ref);
|
||||
|
||||
EXPECT_FLOAT_EQ(result_new, result_ref) << "mismatch for value=" << v << " digits=" << (int) digits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,216 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
// --- format_hex_to() ---
|
||||
|
||||
TEST(FormatHexTo, Basic) {
|
||||
const uint8_t data[] = {0xAB, 0xCD, 0xEF};
|
||||
char buffer[7]; // 3 * 2 + 1
|
||||
format_hex_to(buffer, data, 3);
|
||||
EXPECT_STREQ(buffer, "abcdef");
|
||||
}
|
||||
|
||||
TEST(FormatHexTo, SingleByte) {
|
||||
const uint8_t data[] = {0x0F};
|
||||
char buffer[3];
|
||||
format_hex_to(buffer, data, 1);
|
||||
EXPECT_STREQ(buffer, "0f");
|
||||
}
|
||||
|
||||
TEST(FormatHexTo, ZeroLength) {
|
||||
char buffer[4] = "xxx";
|
||||
format_hex_to(buffer, static_cast<size_t>(sizeof(buffer)), static_cast<const uint8_t *>(nullptr), 0);
|
||||
EXPECT_STREQ(buffer, "");
|
||||
}
|
||||
|
||||
TEST(FormatHexTo, ZeroBufferSize) {
|
||||
char buffer[4] = "xxx";
|
||||
const uint8_t data[] = {0xAB};
|
||||
format_hex_to(buffer, static_cast<size_t>(0), data, 1);
|
||||
// Should not crash, buffer unchanged
|
||||
EXPECT_EQ(buffer[0], 'x');
|
||||
}
|
||||
|
||||
TEST(FormatHexTo, BufferTooSmall) {
|
||||
const uint8_t data[] = {0xAB, 0xCD, 0xEF};
|
||||
char buffer[5]; // only room for 2 bytes
|
||||
format_hex_to(buffer, data, 3);
|
||||
EXPECT_STREQ(buffer, "abcd");
|
||||
}
|
||||
|
||||
TEST(FormatHexTo, MacAddress) {
|
||||
const uint8_t mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
|
||||
char buffer[13];
|
||||
format_hex_to(buffer, mac, 6);
|
||||
EXPECT_STREQ(buffer, "aabbccddeeff");
|
||||
}
|
||||
|
||||
// --- format_hex_pretty_to() ---
|
||||
|
||||
TEST(FormatHexPrettyTo, BasicColon) {
|
||||
const uint8_t data[] = {0xAB, 0xCD, 0xEF};
|
||||
char buffer[9]; // 3 * 3
|
||||
format_hex_pretty_to(buffer, data, 3);
|
||||
EXPECT_STREQ(buffer, "AB:CD:EF");
|
||||
}
|
||||
|
||||
TEST(FormatHexPrettyTo, SingleByte) {
|
||||
const uint8_t data[] = {0x0F};
|
||||
char buffer[3];
|
||||
format_hex_pretty_to(buffer, data, 1);
|
||||
EXPECT_STREQ(buffer, "0F");
|
||||
}
|
||||
|
||||
TEST(FormatHexPrettyTo, ZeroLength) {
|
||||
char buffer[4] = "xxx";
|
||||
format_hex_pretty_to(buffer, static_cast<size_t>(sizeof(buffer)), static_cast<const uint8_t *>(nullptr), 0);
|
||||
EXPECT_STREQ(buffer, "");
|
||||
}
|
||||
|
||||
TEST(FormatHexPrettyTo, ZeroBufferSize) {
|
||||
char buffer[4] = "xxx";
|
||||
const uint8_t data[] = {0xAB};
|
||||
format_hex_pretty_to(buffer, static_cast<size_t>(0), data, 1);
|
||||
EXPECT_EQ(buffer[0], 'x');
|
||||
}
|
||||
|
||||
TEST(FormatHexPrettyTo, CustomSeparator) {
|
||||
const uint8_t data[] = {0xAA, 0xBB, 0xCC};
|
||||
char buffer[9];
|
||||
format_hex_pretty_to(buffer, data, 3, '-');
|
||||
EXPECT_STREQ(buffer, "AA-BB-CC");
|
||||
}
|
||||
|
||||
// --- format_mac_addr_upper() ---
|
||||
|
||||
TEST(FormatMacAddrUpper, Basic) {
|
||||
const uint8_t mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
|
||||
char buffer[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(mac, buffer);
|
||||
EXPECT_STREQ(buffer, "AA:BB:CC:DD:EE:FF");
|
||||
}
|
||||
|
||||
TEST(FormatMacAddrUpper, AllZeros) {
|
||||
const uint8_t mac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
char buffer[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
format_mac_addr_upper(mac, buffer);
|
||||
EXPECT_STREQ(buffer, "00:00:00:00:00:00");
|
||||
}
|
||||
|
||||
// --- format_hex_char() ---
|
||||
|
||||
TEST(FormatHexChar, LowercaseDigits) {
|
||||
EXPECT_EQ(format_hex_char(0), '0');
|
||||
EXPECT_EQ(format_hex_char(9), '9');
|
||||
EXPECT_EQ(format_hex_char(10), 'a');
|
||||
EXPECT_EQ(format_hex_char(15), 'f');
|
||||
}
|
||||
|
||||
TEST(FormatHexChar, UppercaseDigits) {
|
||||
EXPECT_EQ(format_hex_pretty_char(0), '0');
|
||||
EXPECT_EQ(format_hex_pretty_char(9), '9');
|
||||
EXPECT_EQ(format_hex_pretty_char(10), 'A');
|
||||
EXPECT_EQ(format_hex_pretty_char(15), 'F');
|
||||
}
|
||||
|
||||
// --- small_pow10() ---
|
||||
|
||||
TEST(SmallPow10, Zero) { EXPECT_EQ(small_pow10(0), 1u); }
|
||||
TEST(SmallPow10, One) { EXPECT_EQ(small_pow10(1), 10u); }
|
||||
TEST(SmallPow10, Two) { EXPECT_EQ(small_pow10(2), 100u); }
|
||||
TEST(SmallPow10, Three) { EXPECT_EQ(small_pow10(3), 1000u); }
|
||||
|
||||
// --- frac_to_str_unchecked() ---
|
||||
|
||||
TEST(FracToStr, OneDigit) {
|
||||
char buf[8];
|
||||
char *end = frac_to_str_unchecked(buf, 5, 1);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "5");
|
||||
EXPECT_EQ(end - buf, 1);
|
||||
}
|
||||
|
||||
TEST(FracToStr, TwoDigits) {
|
||||
char buf[8];
|
||||
char *end = frac_to_str_unchecked(buf, 46, 10);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "46");
|
||||
}
|
||||
|
||||
TEST(FracToStr, ThreeDigits) {
|
||||
char buf[8];
|
||||
char *end = frac_to_str_unchecked(buf, 456, 100);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "456");
|
||||
EXPECT_EQ(end - buf, 3);
|
||||
}
|
||||
|
||||
TEST(FracToStr, LeadingZeros) {
|
||||
char buf[8];
|
||||
char *end = frac_to_str_unchecked(buf, 1, 100);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "001");
|
||||
|
||||
end = frac_to_str_unchecked(buf, 5, 10);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "05");
|
||||
}
|
||||
|
||||
TEST(FracToStr, AllZeros) {
|
||||
char buf[8];
|
||||
char *end = frac_to_str_unchecked(buf, 0, 100);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "000");
|
||||
|
||||
end = frac_to_str_unchecked(buf, 0, 1);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "0");
|
||||
}
|
||||
|
||||
TEST(FracToStr, ZeroDivisor) {
|
||||
char buf[8];
|
||||
buf[0] = 'X';
|
||||
char *end = frac_to_str_unchecked(buf, 0, 0);
|
||||
EXPECT_EQ(end, buf); // writes nothing
|
||||
}
|
||||
|
||||
// --- buf_append_sep_str() ---
|
||||
|
||||
TEST(BufAppendSepStr, Basic) {
|
||||
char buf[32] = "23.46";
|
||||
char *start = buf + 5;
|
||||
char *end = buf_append_sep_str(start, sizeof(buf) - 5, ' ', "°C", 3);
|
||||
EXPECT_STREQ(buf, "23.46 °C");
|
||||
EXPECT_EQ(end - buf, 9); // "°C" is 3 bytes (UTF-8)
|
||||
}
|
||||
|
||||
TEST(BufAppendSepStr, EmptyString) {
|
||||
char buf[32] = "100";
|
||||
char *start = buf + 3;
|
||||
char *end = buf_append_sep_str(start, sizeof(buf) - 3, ' ', "", 0);
|
||||
EXPECT_STREQ(buf, "100 ");
|
||||
EXPECT_EQ(end - start, 1); // just the separator
|
||||
}
|
||||
|
||||
TEST(BufAppendSepStr, NoRoom) {
|
||||
char buf[8] = "1234567";
|
||||
char *start = buf + 7;
|
||||
char *end = buf_append_sep_str(start, 1, ' ', "unit", 4);
|
||||
EXPECT_EQ(end, start); // nothing written
|
||||
}
|
||||
|
||||
TEST(BufAppendSepStr, Truncation) {
|
||||
char buf[8] = "val";
|
||||
char *start = buf + 3;
|
||||
// remaining = 5, separator takes 1, so 3 chars of string fit + null
|
||||
char *end = buf_append_sep_str(start, 5, ' ', "longunit", 8);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "val lon");
|
||||
EXPECT_EQ(end - buf, 7);
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
// --- uint32_to_str_unchecked() (internal, raw pointer) ---
|
||||
|
||||
TEST(Uint32ToStr, InternalZero) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
char *end = uint32_to_str_unchecked(buf, 0);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "0");
|
||||
EXPECT_EQ(end - buf, 1);
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, InternalSingleDigit) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
char *end = uint32_to_str_unchecked(buf, 7);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "7");
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, InternalMultiDigit) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
char *end = uint32_to_str_unchecked(buf, 12345);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "12345");
|
||||
EXPECT_EQ(end - buf, 5);
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, InternalMaxValue) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
char *end = uint32_to_str_unchecked(buf, 4294967295u);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "4294967295");
|
||||
EXPECT_EQ(end - buf, 10);
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, InternalPowersOfTen) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
char *end;
|
||||
|
||||
end = uint32_to_str_unchecked(buf, 10);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "10");
|
||||
|
||||
end = uint32_to_str_unchecked(buf, 100);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "100");
|
||||
|
||||
end = uint32_to_str_unchecked(buf, 1000000);
|
||||
*end = '\0';
|
||||
EXPECT_STREQ(buf, "1000000");
|
||||
}
|
||||
|
||||
// --- uint32_to_str() (public, span API) ---
|
||||
|
||||
TEST(Uint32ToStr, SpanZero) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
EXPECT_EQ(uint32_to_str(buf, 0), 1u);
|
||||
EXPECT_STREQ(buf, "0");
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, SpanMultiDigit) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
EXPECT_EQ(uint32_to_str(buf, 12345), 5u);
|
||||
EXPECT_STREQ(buf, "12345");
|
||||
}
|
||||
|
||||
TEST(Uint32ToStr, SpanMaxValue) {
|
||||
char buf[UINT32_MAX_STR_SIZE];
|
||||
EXPECT_EQ(uint32_to_str(buf, 4294967295u), 10u);
|
||||
EXPECT_STREQ(buf, "4294967295");
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -0,0 +1,237 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
// Helper to call value_accuracy_to_buf and return as string
|
||||
static std::string va_to_string(float value, int8_t accuracy_decimals) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
std::span<char, VALUE_ACCURACY_MAX_LEN> sp(buf);
|
||||
size_t len = value_accuracy_to_buf(sp, value, accuracy_decimals);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
// Helper: reference implementation using snprintf for comparison
|
||||
static std::string va_reference(float value, int8_t accuracy_decimals) {
|
||||
// Replicate normalize_accuracy_decimals logic
|
||||
if (accuracy_decimals < 0) {
|
||||
float divisor;
|
||||
if (accuracy_decimals == -1) {
|
||||
divisor = 10.0f;
|
||||
} else if (accuracy_decimals == -2) {
|
||||
divisor = 100.0f;
|
||||
} else {
|
||||
divisor = pow10_int(-accuracy_decimals);
|
||||
}
|
||||
value = roundf(value / divisor) * divisor;
|
||||
accuracy_decimals = 0;
|
||||
}
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
snprintf(buf, sizeof(buf), "%.*f", accuracy_decimals, value);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// --- Basic formatting ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, ZeroDecimals) {
|
||||
EXPECT_EQ(va_to_string(23.456f, 0), "23");
|
||||
EXPECT_EQ(va_to_string(0.0f, 0), "0");
|
||||
EXPECT_EQ(va_to_string(100.0f, 0), "100");
|
||||
EXPECT_EQ(va_to_string(1.0f, 0), "1");
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, OneDecimal) {
|
||||
EXPECT_EQ(va_to_string(23.456f, 1), "23.5");
|
||||
EXPECT_EQ(va_to_string(0.0f, 1), "0.0");
|
||||
EXPECT_EQ(va_to_string(1.05f, 1), va_reference(1.05f, 1));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, TwoDecimals) {
|
||||
EXPECT_EQ(va_to_string(23.456f, 2), "23.46");
|
||||
EXPECT_EQ(va_to_string(0.0f, 2), "0.00");
|
||||
EXPECT_EQ(va_to_string(1.005f, 2), va_reference(1.005f, 2));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, ThreeDecimals) {
|
||||
EXPECT_EQ(va_to_string(23.456f, 3), "23.456");
|
||||
EXPECT_EQ(va_to_string(0.0f, 3), "0.000");
|
||||
}
|
||||
|
||||
// --- Negative values ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, NegativeValues) {
|
||||
EXPECT_EQ(va_to_string(-23.456f, 2), "-23.46");
|
||||
EXPECT_EQ(va_to_string(-0.5f, 1), "-0.5");
|
||||
EXPECT_EQ(va_to_string(-100.0f, 0), "-100");
|
||||
}
|
||||
|
||||
// --- Negative accuracy_decimals (rounding to tens/hundreds) ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, NegativeAccuracy) {
|
||||
EXPECT_EQ(va_to_string(1234.0f, -1), va_reference(1234.0f, -1));
|
||||
EXPECT_EQ(va_to_string(1234.0f, -2), va_reference(1234.0f, -2));
|
||||
EXPECT_EQ(va_to_string(56.0f, -1), va_reference(56.0f, -1));
|
||||
}
|
||||
|
||||
// --- Special float values ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, NaN) {
|
||||
std::string result = va_to_string(NAN, 2);
|
||||
EXPECT_EQ(result, va_reference(NAN, 2));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, Infinity) {
|
||||
std::string result = va_to_string(INFINITY, 2);
|
||||
EXPECT_EQ(result, va_reference(INFINITY, 2));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, NegativeInfinity) {
|
||||
std::string result = va_to_string(-INFINITY, 2);
|
||||
EXPECT_EQ(result, va_reference(-INFINITY, 2));
|
||||
}
|
||||
|
||||
// --- Edge cases ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, VerySmallValues) {
|
||||
EXPECT_EQ(va_to_string(0.001f, 3), "0.001");
|
||||
EXPECT_EQ(va_to_string(0.001f, 2), "0.00");
|
||||
EXPECT_EQ(va_to_string(0.009f, 2), "0.01");
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, LargeValues) {
|
||||
EXPECT_EQ(va_to_string(999999.0f, 0), va_reference(999999.0f, 0));
|
||||
EXPECT_EQ(va_to_string(1013.25f, 2), "1013.25");
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, Rounding) {
|
||||
// 0.5 rounds up
|
||||
EXPECT_EQ(va_to_string(23.5f, 0), "24");
|
||||
EXPECT_EQ(va_to_string(23.45f, 1), "23.5"); // float: 23.45 -> 23.4 or 23.5
|
||||
EXPECT_EQ(va_to_string(23.45f, 1), va_reference(23.45f, 1));
|
||||
}
|
||||
|
||||
// --- Match snprintf for a range of typical sensor values ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, MatchesSnprintf) {
|
||||
float test_values[] = {0.0f, 1.0f, -1.0f, 23.456f, -23.456f, 100.0f, 0.1f, 0.01f, 99.99f, 1013.25f, -40.0f};
|
||||
int8_t test_accuracies[] = {0, 1, 2, 3};
|
||||
|
||||
for (float value : test_values) {
|
||||
for (int8_t acc : test_accuracies) {
|
||||
EXPECT_EQ(va_to_string(value, acc), va_reference(value, acc))
|
||||
<< "Mismatch for value=" << value << " accuracy=" << static_cast<int>(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Return value (length) ---
|
||||
|
||||
TEST(ValueAccuracyToBuf, ReturnsCorrectLength) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
std::span<char, VALUE_ACCURACY_MAX_LEN> sp(buf);
|
||||
|
||||
size_t len = value_accuracy_to_buf(sp, 23.456f, 2);
|
||||
EXPECT_EQ(len, 5u); // "23.46"
|
||||
EXPECT_EQ(strlen(buf), len);
|
||||
|
||||
len = value_accuracy_to_buf(sp, 0.0f, 0);
|
||||
EXPECT_EQ(len, 1u); // "0"
|
||||
EXPECT_EQ(strlen(buf), len);
|
||||
|
||||
len = value_accuracy_to_buf(sp, -100.0f, 1);
|
||||
EXPECT_EQ(len, 6u); // "-100.0"
|
||||
EXPECT_EQ(strlen(buf), len);
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, NegativeZero) {
|
||||
// Hand-rolled formatter must preserve snprintf's sign-of-zero behavior.
|
||||
EXPECT_EQ(va_to_string(-0.0f, 2), va_reference(-0.0f, 2));
|
||||
EXPECT_EQ(va_to_string(-0.0f, 0), va_reference(-0.0f, 0));
|
||||
// Tiny negative that rounds to zero at this precision must still render as "-0.00".
|
||||
EXPECT_EQ(va_to_string(-0.001f, 2), va_reference(-0.001f, 2));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyToBuf, OverflowFallsBackToSnprintf) {
|
||||
// |value| * 10^acc must exceed UINT32_MAX to exercise the snprintf fallback path.
|
||||
EXPECT_EQ(va_to_string(1.0e7f, 3), va_reference(1.0e7f, 3));
|
||||
EXPECT_EQ(va_to_string(-1.0e7f, 3), va_reference(-1.0e7f, 3));
|
||||
EXPECT_EQ(va_to_string(5.0e9f, 0), va_reference(5.0e9f, 0));
|
||||
}
|
||||
|
||||
// --- value_accuracy_with_uom_to_buf ---
|
||||
|
||||
static std::string va_uom_to_string(float value, int8_t accuracy_decimals, const char *uom) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
std::span<char, VALUE_ACCURACY_MAX_LEN> sp(buf);
|
||||
StringRef ref(uom);
|
||||
size_t len = value_accuracy_with_uom_to_buf(sp, value, accuracy_decimals, ref);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
static std::string va_uom_reference(float value, int8_t accuracy_decimals, const char *uom) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
if (!uom || *uom == '\0') {
|
||||
snprintf(buf, sizeof(buf), "%.*f", accuracy_decimals, value);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%.*f %s", accuracy_decimals, value, uom);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyWithUomToBuf, BasicWithUnit) {
|
||||
EXPECT_EQ(va_uom_to_string(23.456f, 2, "°C"), va_uom_reference(23.456f, 2, "°C"));
|
||||
EXPECT_EQ(va_uom_to_string(1013.25f, 2, "hPa"), va_uom_reference(1013.25f, 2, "hPa"));
|
||||
EXPECT_EQ(va_uom_to_string(-40.0f, 1, "°F"), va_uom_reference(-40.0f, 1, "°F"));
|
||||
EXPECT_EQ(va_uom_to_string(100.0f, 0, "%"), va_uom_reference(100.0f, 0, "%"));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyWithUomToBuf, EmptyUnit) {
|
||||
EXPECT_EQ(va_uom_to_string(23.456f, 2, ""), "23.46");
|
||||
EXPECT_EQ(va_uom_to_string(0.0f, 1, ""), "0.0");
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyWithUomToBuf, ReturnsCorrectLength) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
std::span<char, VALUE_ACCURACY_MAX_LEN> sp(buf);
|
||||
StringRef ref("°C");
|
||||
size_t len = value_accuracy_with_uom_to_buf(sp, 23.46f, 2, ref);
|
||||
EXPECT_EQ(strlen(buf), len);
|
||||
EXPECT_EQ(len, strlen("23.46 °C"));
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyWithUomToBuf, NearBufferLimitTruncates) {
|
||||
// Build a unit long enough that value + " " + unit exceeds VALUE_ACCURACY_MAX_LEN.
|
||||
// "23.46" (5) + " " (1) + unit -> must cap at buf.size()-1 and stay null-terminated.
|
||||
std::string long_unit(VALUE_ACCURACY_MAX_LEN, 'U');
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
std::span<char, VALUE_ACCURACY_MAX_LEN> sp(buf);
|
||||
StringRef ref(long_unit.c_str());
|
||||
size_t len = value_accuracy_with_uom_to_buf(sp, 23.46f, 2, ref);
|
||||
EXPECT_LT(len, VALUE_ACCURACY_MAX_LEN);
|
||||
EXPECT_EQ(strlen(buf), len);
|
||||
// Should begin with the formatted value and a separator.
|
||||
EXPECT_EQ(std::string(buf, 6), "23.46 ");
|
||||
}
|
||||
|
||||
TEST(ValueAccuracyWithUomToBuf, MatchesSnprintf) {
|
||||
const char *units[] = {"°C", "hPa", "%", "W", "kWh", "m/s"};
|
||||
float values[] = {0.0f, 23.456f, -40.0f, 1013.25f, 100.0f};
|
||||
int8_t accs[] = {0, 1, 2, 3};
|
||||
for (const char *u : units) {
|
||||
for (float v : values) {
|
||||
for (int8_t a : accs) {
|
||||
EXPECT_EQ(va_uom_to_string(v, a, u), va_uom_reference(v, a, u))
|
||||
<< "value=" << v << " acc=" << static_cast<int>(a) << " uom=" << u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -0,0 +1,22 @@
|
||||
display:
|
||||
- platform: ssd1306_i2c
|
||||
i2c_id: i2c_bus
|
||||
id: cst328_ssd1306_i2c_display
|
||||
model: SSD1306_128X64
|
||||
reset_pin: ${display_reset_pin}
|
||||
pages:
|
||||
- id: cst328_page1
|
||||
lambda: |-
|
||||
it.rectangle(0, 0, it.get_width(), it.get_height());
|
||||
|
||||
touchscreen:
|
||||
- platform: cst328
|
||||
i2c_id: i2c_bus
|
||||
id: cst328_touchscreen
|
||||
display: cst328_ssd1306_i2c_display
|
||||
interrupt_pin: ${interrupt_pin}
|
||||
reset_pin: ${reset_pin}
|
||||
|
||||
binary_sensor:
|
||||
- platform: cst328
|
||||
id: touch_key_cst328
|
||||
@@ -0,0 +1,8 @@
|
||||
substitutions:
|
||||
display_reset_pin: "4"
|
||||
interrupt_pin: "20"
|
||||
reset_pin: "21"
|
||||
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
cst328: !include common.yaml
|
||||
@@ -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
|
||||
@@ -1,9 +1,9 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
id: esp_adc_sensor
|
||||
id: ct_clamp_esp_adc_sensor
|
||||
pin: ${pin}
|
||||
- platform: ct_clamp
|
||||
sensor: esp_adc_sensor
|
||||
sensor: ct_clamp_esp_adc_sensor
|
||||
name: CT Clamp
|
||||
sample_duration: 500ms
|
||||
update_interval: 5s
|
||||
|
||||
@@ -31,7 +31,7 @@ sensor:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: template_switch1
|
||||
id: current_based_template_switch1
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: template_switch2
|
||||
@@ -46,7 +46,7 @@ cover:
|
||||
open_obstacle_current_threshold: 0.8
|
||||
open_duration: 12s
|
||||
open_action:
|
||||
- switch.turn_on: template_switch1
|
||||
- switch.turn_on: current_based_template_switch1
|
||||
close_sensor: ade7953_current_b
|
||||
close_moving_current_threshold: 0.5
|
||||
close_obstacle_current_threshold: 0.8
|
||||
@@ -54,7 +54,7 @@ cover:
|
||||
close_action:
|
||||
- switch.turn_on: template_switch2
|
||||
stop_action:
|
||||
- switch.turn_off: template_switch1
|
||||
- switch.turn_off: current_based_template_switch1
|
||||
- switch.turn_off: template_switch2
|
||||
obstacle_rollback: 30%
|
||||
start_sensing_delay: 0.8s
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
light:
|
||||
- platform: cwww
|
||||
name: CWWW Light
|
||||
cold_white: light_output_1
|
||||
warm_white: light_output_2
|
||||
cold_white: cwww_light_output_1
|
||||
warm_white: cwww_light_output_2
|
||||
cold_white_color_temperature: 153 mireds
|
||||
warm_white_color_temperature: 500 mireds
|
||||
constant_brightness: true
|
||||
|
||||
@@ -5,11 +5,11 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
channel: 0
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
channel: 1
|
||||
phase_angle: 180°
|
||||
|
||||
@@ -5,10 +5,10 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -5,10 +5,10 @@ substitutions:
|
||||
|
||||
output:
|
||||
- platform: ${light_platform}
|
||||
id: light_output_1
|
||||
id: cwww_light_output_1
|
||||
pin: ${pin_o1}
|
||||
- platform: ${light_platform}
|
||||
id: light_output_2
|
||||
id: cwww_light_output_2
|
||||
pin: ${pin_o2}
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -6,9 +6,15 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
esp32_ext1_wakeup:
|
||||
pins:
|
||||
- number: GPIO2
|
||||
- pin: GPIO2
|
||||
on_wake:
|
||||
- logger.log: Woken by ext1 pin GPIO2
|
||||
- number: GPIO13
|
||||
mode: ANY_HIGH
|
||||
touch_wakeup: true
|
||||
|
||||
@@ -5,8 +5,15 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
esp32_ext1_wakeup:
|
||||
pins:
|
||||
- number: GPIO2
|
||||
- pin:
|
||||
number: GPIO2
|
||||
on_wake:
|
||||
- logger.log: Woken by ext1 pin GPIO2
|
||||
- number: GPIO5
|
||||
mode: ANY_HIGH
|
||||
|
||||
@@ -5,3 +5,7 @@ deep_sleep:
|
||||
sleep_duration: 50s
|
||||
wakeup_pin: ${wakeup_pin}
|
||||
wakeup_pin_mode: INVERT_WAKEUP
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
|
||||
@@ -4,3 +4,9 @@ esphome:
|
||||
- deep_sleep.prevent
|
||||
- delay: 1s
|
||||
- deep_sleep.allow
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return false;'
|
||||
then:
|
||||
- deep_sleep.enter:
|
||||
sleep_duration: 60min
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
deep_sleep:
|
||||
run_duration: 30s
|
||||
sleep_duration: 12h
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
wakeup_pin:
|
||||
- pin:
|
||||
number: P6
|
||||
|
||||
@@ -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
|
||||
@@ -1,5 +1,9 @@
|
||||
deep_sleep:
|
||||
run_duration: 10s
|
||||
sleep_duration: 50s
|
||||
on_wake:
|
||||
- logger.log:
|
||||
format: "Woke up, cause %d"
|
||||
args: ["static_cast<int>(cause)"]
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
deep_sleep:
|
||||
run_duration: 10s
|
||||
sleep_duration: 50s
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
zigbee:
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Temperature"
|
||||
id: temperature_sensor
|
||||
@@ -1,11 +0,0 @@
|
||||
dlms_meter:
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key!
|
||||
|
||||
sensor:
|
||||
- platform: dlms_meter
|
||||
reactive_energy_plus:
|
||||
name: "Reactive energy taken from grid"
|
||||
reactive_energy_minus:
|
||||
name: "Reactive energy put into grid"
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,17 +0,0 @@
|
||||
dlms_meter:
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key!
|
||||
provider: netznoe # (optional) key - only set if using evn
|
||||
|
||||
sensor:
|
||||
- platform: dlms_meter
|
||||
# EVN
|
||||
power_factor:
|
||||
name: "Power Factor"
|
||||
|
||||
text_sensor:
|
||||
- platform: dlms_meter
|
||||
# EVN
|
||||
meternumber:
|
||||
name: "meterNumber"
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -1,4 +1,16 @@
|
||||
dlms_meter:
|
||||
id: dlms_meter_hub
|
||||
receive_timeout: 50ms
|
||||
decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB"
|
||||
auth_key: "11223344556677889900AABBCCDDEEFF"
|
||||
skip_crc: true
|
||||
provider: "netznoe"
|
||||
custom_patterns:
|
||||
- "custom_pattern_1"
|
||||
- "custom_pattern_2"
|
||||
|
||||
sensor:
|
||||
# Old Schema tests
|
||||
- platform: dlms_meter
|
||||
voltage_l1:
|
||||
name: "Voltage L1"
|
||||
@@ -20,8 +32,36 @@ sensor:
|
||||
name: "Active energy taken from grid"
|
||||
active_energy_minus:
|
||||
name: "Active energy put into grid"
|
||||
reactive_energy_plus:
|
||||
name: "Reactive energy taken from grid"
|
||||
reactive_energy_minus:
|
||||
name: "Reactive energy put into grid"
|
||||
power_factor:
|
||||
name: "Power factor"
|
||||
|
||||
# Dynamic Schema tests
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "1-0:99.99.9"
|
||||
name: "Custom Dynamic Sensor"
|
||||
|
||||
text_sensor:
|
||||
# Old Schema tests
|
||||
- platform: dlms_meter
|
||||
timestamp:
|
||||
name: "timestamp"
|
||||
meternumber:
|
||||
name: "Meter Number"
|
||||
|
||||
# Dynamic Schema tests
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "0-0:99.99.9"
|
||||
name: "Custom Dynamic Text Sensor"
|
||||
|
||||
binary_sensor:
|
||||
# Dynamic Schema tests (Binary sensors only use the dynamic schema)
|
||||
- platform: dlms_meter
|
||||
dlms_meter_id: dlms_meter_hub
|
||||
obis_code: "0-1:2.3.4"
|
||||
name: "Custom Binary Sensor"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-ard.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp32-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp32-idf.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common-netznoe.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
packages:
|
||||
uart_2400: !include ../../test_build_components/common/uart_2400/esp8266-ard.yaml
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common-generic.yaml
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,115 @@
|
||||
# Combined DS248x test covering all chip variants and options:
|
||||
# - DS2482-100: active pullup, multiple sensors + index access
|
||||
# - DS2482-101: sleep pin, bus_sleep / hub_sleep
|
||||
# - DS2482-800: all 8 channels
|
||||
# - DS2484: adjustable 1-Wire timing + RWPU pullup resistor selection
|
||||
ds248x:
|
||||
- id: ds2482_100
|
||||
address: 0x18
|
||||
type: ds2482-100
|
||||
active_pullup: true
|
||||
- id: ds2482_101
|
||||
address: 0x19
|
||||
type: ds2482-101
|
||||
active_pullup: true
|
||||
sleep_pin:
|
||||
number: GPIO12
|
||||
inverted: false
|
||||
bus_sleep: true
|
||||
hub_sleep: true
|
||||
- id: ds2482_800
|
||||
address: 0x1a
|
||||
type: ds2482-800
|
||||
active_pullup: true
|
||||
- id: ds2484_hub
|
||||
address: 0x1b
|
||||
type: ds2484
|
||||
active_pullup: true
|
||||
# DS2484-specific 1-Wire timing parameters (optional fine-tuning)
|
||||
reset_low_time: 8 # tRSTL: Reset low time
|
||||
master_sample_time: 8 # tMSP: Master sample point
|
||||
write_0_low_time: 8 # tW0L: Write-0 low time
|
||||
recovery_time: 8 # tREC0: Recovery time
|
||||
active_pullup_resistance: 1000ohm # RWPU: weak pullup resistor selection
|
||||
|
||||
one_wire:
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_100
|
||||
channel: 0
|
||||
id: ow_100
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_101
|
||||
channel: 0
|
||||
id: ow_101
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 0
|
||||
id: ow_800_0
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 1
|
||||
id: ow_800_1
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 2
|
||||
id: ow_800_2
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 3
|
||||
id: ow_800_3
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 4
|
||||
id: ow_800_4
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 5
|
||||
id: ow_800_5
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 6
|
||||
id: ow_800_6
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2482_800
|
||||
channel: 7
|
||||
id: ow_800_7
|
||||
- platform: ds248x
|
||||
ds248x_id: ds2484_hub
|
||||
channel: 0
|
||||
id: ow_2484
|
||||
|
||||
sensor:
|
||||
# DS2482-100: explicit address + index-based access on the same bus
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_100
|
||||
address: 0x1c0000031edd2a28
|
||||
name: Temp 100 by address
|
||||
resolution: 12
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_100
|
||||
index: 0
|
||||
name: Temp 100 by index
|
||||
# DS2482-101 (sleep variant)
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_101
|
||||
address: 0x578295491f64ff28
|
||||
name: Temp 101
|
||||
# DS2482-800: sensors on a few of the eight channels
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_800_0
|
||||
address: 0x1c0000031edd2a28
|
||||
name: Temp 800 CH0
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_800_3
|
||||
index: 0
|
||||
name: Temp 800 CH3 by index
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_800_7
|
||||
address: 0x2800000123456789
|
||||
name: Temp 800 CH7
|
||||
# DS2484 (adjustable timing)
|
||||
- platform: dallas_temp
|
||||
one_wire_id: ow_2484
|
||||
address: 0x1c0000031edd2a28
|
||||
name: Temp 2484
|
||||
resolution: 12
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user