mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 03:12:20 +01:00
[helper] Make crc8 function more flexible to avoid reimplementation in individual components (#10201)
This commit is contained in:
17
tests/integration/fixtures/crc8_helper.yaml
Normal file
17
tests/integration/fixtures/crc8_helper.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
esphome:
|
||||
name: crc8-helper-test
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
level: INFO
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
components: [crc8_test_component]
|
||||
|
||||
crc8_test_component:
|
@@ -0,0 +1,17 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
crc8_test_component_ns = cg.esphome_ns.namespace("crc8_test_component")
|
||||
CRC8TestComponent = crc8_test_component_ns.class_("CRC8TestComponent", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CRC8TestComponent),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
@@ -0,0 +1,170 @@
|
||||
#include "crc8_test_component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace crc8_test_component {
|
||||
|
||||
static const char *const TAG = "crc8_test";
|
||||
|
||||
void CRC8TestComponent::setup() {
|
||||
ESP_LOGI(TAG, "CRC8 Helper Function Integration Test Starting");
|
||||
|
||||
// Run all test suites
|
||||
test_crc8_dallas_maxim();
|
||||
test_crc8_sensirion_style();
|
||||
test_crc8_pec_style();
|
||||
test_crc8_parameter_equivalence();
|
||||
test_crc8_edge_cases();
|
||||
test_component_compatibility();
|
||||
|
||||
ESP_LOGI(TAG, "CRC8 Integration Test Complete");
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_dallas_maxim() {
|
||||
ESP_LOGI(TAG, "Testing Dallas/Maxim CRC8 (default parameters)");
|
||||
|
||||
// Test vectors for Dallas/Maxim CRC8 (polynomial 0x8C, LSB-first, init 0x00)
|
||||
const uint8_t test1[] = {0x01};
|
||||
const uint8_t test2[] = {0xFF};
|
||||
const uint8_t test3[] = {0x12, 0x34};
|
||||
const uint8_t test4[] = {0xAA, 0xBB, 0xCC};
|
||||
const uint8_t test5[] = {0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("Dallas [0x01]", test1, sizeof(test1), 0x5E);
|
||||
all_passed &= verify_crc8("Dallas [0xFF]", test2, sizeof(test2), 0x35);
|
||||
all_passed &= verify_crc8("Dallas [0x12, 0x34]", test3, sizeof(test3), 0xA2);
|
||||
all_passed &= verify_crc8("Dallas [0xAA, 0xBB, 0xCC]", test4, sizeof(test4), 0xD4);
|
||||
all_passed &= verify_crc8("Dallas [0x01...0x05]", test5, sizeof(test5), 0x2A);
|
||||
|
||||
log_test_result("Dallas/Maxim CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_sensirion_style() {
|
||||
ESP_LOGI(TAG, "Testing Sensirion CRC8 (0x31 poly, MSB-first, init 0xFF)");
|
||||
|
||||
const uint8_t test1[] = {0x00};
|
||||
const uint8_t test2[] = {0x01};
|
||||
const uint8_t test3[] = {0xFF};
|
||||
const uint8_t test4[] = {0x12, 0x34};
|
||||
const uint8_t test5[] = {0xBE, 0xEF};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("Sensirion [0x00]", test1, sizeof(test1), 0xAC, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0x01]", test2, sizeof(test2), 0x9D, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0xFF]", test3, sizeof(test3), 0x00, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0x12, 0x34]", test4, sizeof(test4), 0x37, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0xBE, 0xEF]", test5, sizeof(test5), 0x92, 0xFF, 0x31, true);
|
||||
|
||||
log_test_result("Sensirion CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_pec_style() {
|
||||
ESP_LOGI(TAG, "Testing PEC CRC8 (0x07 poly, MSB-first, init 0x00)");
|
||||
|
||||
const uint8_t test1[] = {0x00};
|
||||
const uint8_t test2[] = {0x01};
|
||||
const uint8_t test3[] = {0xFF};
|
||||
const uint8_t test4[] = {0x12, 0x34};
|
||||
const uint8_t test5[] = {0xAA, 0xBB};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("PEC [0x00]", test1, sizeof(test1), 0x00, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0x01]", test2, sizeof(test2), 0x07, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0xFF]", test3, sizeof(test3), 0xF3, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0x12, 0x34]", test4, sizeof(test4), 0xF1, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0xAA, 0xBB]", test5, sizeof(test5), 0xB2, 0x00, 0x07, true);
|
||||
|
||||
log_test_result("PEC CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_parameter_equivalence() {
|
||||
ESP_LOGI(TAG, "Testing parameter equivalence");
|
||||
|
||||
const uint8_t test_data[] = {0x12, 0x34, 0x56, 0x78};
|
||||
|
||||
// Test that default parameters work as expected
|
||||
uint8_t default_result = crc8(test_data, sizeof(test_data));
|
||||
uint8_t explicit_result = crc8(test_data, sizeof(test_data), 0x00, 0x8C, false);
|
||||
|
||||
bool passed = (default_result == explicit_result);
|
||||
if (!passed) {
|
||||
ESP_LOGE(TAG, "Parameter equivalence FAILED: default=0x%02X, explicit=0x%02X", default_result, explicit_result);
|
||||
}
|
||||
|
||||
log_test_result("Parameter equivalence", passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_edge_cases() {
|
||||
ESP_LOGI(TAG, "Testing edge cases");
|
||||
|
||||
bool all_passed = true;
|
||||
|
||||
// Empty array test
|
||||
const uint8_t empty[] = {};
|
||||
uint8_t empty_result = crc8(empty, 0);
|
||||
bool empty_passed = (empty_result == 0x00); // Should return init value
|
||||
if (!empty_passed) {
|
||||
ESP_LOGE(TAG, "Empty array test FAILED: expected 0x00, got 0x%02X", empty_result);
|
||||
}
|
||||
all_passed &= empty_passed;
|
||||
|
||||
// Single byte tests
|
||||
const uint8_t single_zero[] = {0x00};
|
||||
const uint8_t single_ff[] = {0xFF};
|
||||
all_passed &= verify_crc8("Single [0x00]", single_zero, 1, 0x00);
|
||||
all_passed &= verify_crc8("Single [0xFF]", single_ff, 1, 0x35);
|
||||
|
||||
log_test_result("Edge cases", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_component_compatibility() {
|
||||
ESP_LOGI(TAG, "Testing component compatibility");
|
||||
|
||||
// Test specific component use cases
|
||||
bool all_passed = true;
|
||||
|
||||
// AGS10-style data (Sensirion CRC8)
|
||||
const uint8_t ags10_data[] = {0x12, 0x34, 0x56};
|
||||
uint8_t ags10_result = crc8(ags10_data, sizeof(ags10_data), 0xFF, 0x31, true);
|
||||
ESP_LOGI(TAG, "AGS10-style CRC8: 0x%02X", ags10_result);
|
||||
|
||||
// LC709203F-style data (PEC CRC8)
|
||||
const uint8_t lc_data[] = {0xAA, 0xBB};
|
||||
uint8_t lc_result = crc8(lc_data, sizeof(lc_data), 0x00, 0x07, true);
|
||||
ESP_LOGI(TAG, "LC709203F-style CRC8: 0x%02X", lc_result);
|
||||
|
||||
// DallasTemperature-style data (Dallas CRC8)
|
||||
const uint8_t dallas_data[] = {0x28, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};
|
||||
uint8_t dallas_result = crc8(dallas_data, sizeof(dallas_data));
|
||||
ESP_LOGI(TAG, "Dallas-style CRC8: 0x%02X", dallas_result);
|
||||
|
||||
all_passed = true; // These are just demonstration tests
|
||||
log_test_result("Component compatibility", all_passed);
|
||||
}
|
||||
|
||||
bool CRC8TestComponent::verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected,
|
||||
uint8_t crc, uint8_t poly, bool msb_first) {
|
||||
uint8_t result = esphome::crc8(data, len, crc, poly, msb_first);
|
||||
bool passed = (result == expected);
|
||||
|
||||
if (passed) {
|
||||
ESP_LOGI(TAG, "%s: PASS (0x%02X)", test_name, result);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: FAIL - expected 0x%02X, got 0x%02X", test_name, expected, result);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
void CRC8TestComponent::log_test_result(const char *test_name, bool passed) {
|
||||
if (passed) {
|
||||
ESP_LOGI(TAG, "%s: ALL TESTS PASSED", test_name);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: SOME TESTS FAILED", test_name);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crc8_test_component
|
||||
} // namespace esphome
|
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace crc8_test_component {
|
||||
|
||||
class CRC8TestComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
private:
|
||||
void test_crc8_dallas_maxim();
|
||||
void test_crc8_sensirion_style();
|
||||
void test_crc8_pec_style();
|
||||
void test_crc8_parameter_equivalence();
|
||||
void test_crc8_edge_cases();
|
||||
void test_component_compatibility();
|
||||
void test_old_vs_new_implementations();
|
||||
|
||||
void log_test_result(const char *test_name, bool passed);
|
||||
bool verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected, uint8_t crc = 0x00,
|
||||
uint8_t poly = 0x8C, bool msb_first = false);
|
||||
};
|
||||
|
||||
} // namespace crc8_test_component
|
||||
} // namespace esphome
|
100
tests/integration/test_crc8_helper.py
Normal file
100
tests/integration/test_crc8_helper.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Integration test for CRC8 helper function."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crc8_helper(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test the CRC8 helper function through integration testing."""
|
||||
# Get the path to the external components directory
|
||||
external_components_path = str(
|
||||
Path(__file__).parent / "fixtures" / "external_components"
|
||||
)
|
||||
|
||||
# Replace the placeholder in the YAML config with the actual path
|
||||
yaml_config = yaml_config.replace(
|
||||
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||
)
|
||||
|
||||
# Track test completion with asyncio.Event
|
||||
test_complete = asyncio.Event()
|
||||
|
||||
# Track test results
|
||||
test_results = {
|
||||
"dallas_maxim": False,
|
||||
"sensirion": False,
|
||||
"pec": False,
|
||||
"parameter_equivalence": False,
|
||||
"edge_cases": False,
|
||||
"component_compatibility": False,
|
||||
"setup_started": False,
|
||||
}
|
||||
|
||||
def on_log_line(line):
|
||||
"""Process log lines to track test progress and results."""
|
||||
# Track test start
|
||||
if "CRC8 Helper Function Integration Test Starting" in line:
|
||||
test_results["setup_started"] = True
|
||||
|
||||
# Track test completion
|
||||
elif "CRC8 Integration Test Complete" in line:
|
||||
test_complete.set()
|
||||
|
||||
# Track individual test results
|
||||
elif "ALL TESTS PASSED" in line:
|
||||
if "Dallas/Maxim CRC8" in line:
|
||||
test_results["dallas_maxim"] = True
|
||||
elif "Sensirion CRC8" in line:
|
||||
test_results["sensirion"] = True
|
||||
elif "PEC CRC8" in line:
|
||||
test_results["pec"] = True
|
||||
elif "Parameter equivalence" in line:
|
||||
test_results["parameter_equivalence"] = True
|
||||
elif "Edge cases" in line:
|
||||
test_results["edge_cases"] = True
|
||||
elif "Component compatibility" in line:
|
||||
test_results["component_compatibility"] = True
|
||||
|
||||
# Log failures for debugging
|
||||
elif "TEST FAILED:" in line or "SUBTEST FAILED:" in line:
|
||||
print(f"CRC8 Test Failure: {line}")
|
||||
|
||||
# Compile and run the test
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=on_log_line),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "crc8-helper-test"
|
||||
|
||||
# Wait for tests to complete with timeout
|
||||
try:
|
||||
await asyncio.wait_for(test_complete.wait(), timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("CRC8 integration test timed out after 5 seconds")
|
||||
|
||||
# Verify all tests passed
|
||||
assert test_results["setup_started"], "CRC8 test setup never started"
|
||||
assert test_results["dallas_maxim"], "Dallas/Maxim CRC8 test failed"
|
||||
assert test_results["sensirion"], "Sensirion CRC8 test failed"
|
||||
assert test_results["pec"], "PEC CRC8 test failed"
|
||||
assert test_results["parameter_equivalence"], (
|
||||
"Parameter equivalence test failed"
|
||||
)
|
||||
assert test_results["edge_cases"], "Edge cases test failed"
|
||||
assert test_results["component_compatibility"], (
|
||||
"Component compatibility test failed"
|
||||
)
|
Reference in New Issue
Block a user