mirror of
https://github.com/esphome/esphome.git
synced 2025-10-27 04:28:42 +00:00
Compare commits
2 Commits
dev
...
fix_clang_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
326405ae8f | ||
|
|
c9e166905f |
@@ -1 +1 @@
|
||||
3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c
|
||||
d7693a1e996cacd4a3d1c9a16336799c2a8cc3db02e4e74084151ce964581248
|
||||
|
||||
7
.github/workflows/auto-label-pr.yml
vendored
7
.github/workflows/auto-label-pr.yml
vendored
@@ -53,7 +53,6 @@ jobs:
|
||||
'new-target-platform',
|
||||
'merging-to-release',
|
||||
'merging-to-beta',
|
||||
'chained-pr',
|
||||
'core',
|
||||
'small-pr',
|
||||
'dashboard',
|
||||
@@ -141,8 +140,6 @@ jobs:
|
||||
labels.add('merging-to-release');
|
||||
} else if (baseRef === 'beta') {
|
||||
labels.add('merging-to-beta');
|
||||
} else if (baseRef !== 'dev') {
|
||||
labels.add('chained-pr');
|
||||
}
|
||||
|
||||
return labels;
|
||||
@@ -531,8 +528,8 @@ jobs:
|
||||
const apiData = await fetchApiData();
|
||||
const baseRef = context.payload.pull_request.base.ref;
|
||||
|
||||
// Early exit for release and beta branches only
|
||||
if (baseRef === 'release' || baseRef === 'beta') {
|
||||
// Early exit for non-dev branches
|
||||
if (baseRef !== 'dev') {
|
||||
const branchLabels = await detectMergeBranch();
|
||||
const finalLabels = Array.from(branchLabels);
|
||||
|
||||
|
||||
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
@@ -178,8 +178,6 @@ jobs:
|
||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||
changed-cpp-file-count: ${{ steps.determine.outputs.changed-cpp-file-count }}
|
||||
memory_impact: ${{ steps.determine.outputs.memory-impact }}
|
||||
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
||||
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -212,8 +210,6 @@ jobs:
|
||||
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||
echo "changed-cpp-file-count=$(echo "$output" | jq -r '.changed_cpp_file_count')" >> $GITHUB_OUTPUT
|
||||
echo "memory-impact=$(echo "$output" | jq -c '.memory_impact')" >> $GITHUB_OUTPUT
|
||||
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
||||
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
||||
|
||||
integration-tests:
|
||||
name: Run integration tests
|
||||
@@ -251,33 +247,6 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||
|
||||
cpp-unit-tests:
|
||||
name: Run C++ unit tests
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Run cpp_unit_test.py
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
if [ "${{ needs.determine-jobs.outputs.cpp-unit-tests-run-all }}" = "true" ]; then
|
||||
script/cpp_unit_test.py --all
|
||||
else
|
||||
ARGS=$(echo '${{ needs.determine-jobs.outputs.cpp-unit-tests-components }}' | jq -r '.[] | @sh' | xargs)
|
||||
script/cpp_unit_test.py $ARGS
|
||||
fi
|
||||
|
||||
clang-tidy-single:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
1
.github/workflows/status-check-labels.yml
vendored
1
.github/workflows/status-check-labels.yml
vendored
@@ -14,7 +14,6 @@ jobs:
|
||||
label:
|
||||
- needs-docs
|
||||
- merge-after-release
|
||||
- chained-pr
|
||||
steps:
|
||||
- name: Check for ${{ matrix.label }} label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
|
||||
@@ -201,7 +201,6 @@ esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/hbridge/switch/* @dwmw2
|
||||
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||
esphome/components/he60r/* @clydebarrow
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
|
||||
@@ -231,22 +231,9 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
api_component = (name, mem)
|
||||
break
|
||||
|
||||
# Also include wifi_stack and other important system components if they exist
|
||||
system_components_to_include = [
|
||||
# Empty list - we've finished debugging symbol categorization
|
||||
# Add component names here if you need to debug their symbols
|
||||
]
|
||||
system_components = [
|
||||
(name, mem)
|
||||
for name, mem in components
|
||||
if name in system_components_to_include
|
||||
]
|
||||
|
||||
# Combine all components to analyze: top ESPHome + all external + API if not already included + system components
|
||||
components_to_analyze = (
|
||||
list(top_esphome_components)
|
||||
+ list(top_external_components)
|
||||
+ system_components
|
||||
# Combine all components to analyze: top ESPHome + all external + API if not already included
|
||||
components_to_analyze = list(top_esphome_components) + list(
|
||||
top_external_components
|
||||
)
|
||||
if api_component and api_component not in components_to_analyze:
|
||||
components_to_analyze.append(api_component)
|
||||
|
||||
@@ -127,39 +127,40 @@ SYMBOL_PATTERNS = {
|
||||
"tryget_socket_unconn",
|
||||
"cs_create_ctrl_sock",
|
||||
"netbuf_alloc",
|
||||
"tcp_", # TCP protocol functions
|
||||
"udp_", # UDP protocol functions
|
||||
"lwip_", # LwIP stack functions
|
||||
"eagle_lwip", # ESP-specific LwIP functions
|
||||
"new_linkoutput", # Link output function
|
||||
"acd_", # Address Conflict Detection (ACD)
|
||||
"eth_", # Ethernet functions
|
||||
"mac_enable_bb", # MAC baseband enable
|
||||
"reassemble_and_dispatch", # Packet reassembly
|
||||
],
|
||||
# dhcp must come before libc to avoid "dhcp_select" matching "select" pattern
|
||||
"dhcp": ["dhcp", "handle_dhcp"],
|
||||
"ipv6_stack": ["nd6_", "ip6_", "mld6_", "icmp6_", "icmp6_input"],
|
||||
# Order matters! More specific categories must come before general ones.
|
||||
# mdns must come before bluetooth to avoid "_mdns_disable_pcb" matching "ble_" pattern
|
||||
"mdns_lib": ["mdns"],
|
||||
# memory_mgmt must come before wifi_stack to catch mmu_hal_* symbols
|
||||
"memory_mgmt": [
|
||||
"mem_",
|
||||
"memory_",
|
||||
"tlsf_",
|
||||
"memp_",
|
||||
"pbuf_",
|
||||
"pbuf_alloc",
|
||||
"pbuf_copy_partial_pbuf",
|
||||
"esp_mmu_map",
|
||||
"mmu_hal_",
|
||||
"s_do_mapping", # Memory mapping function, not WiFi
|
||||
"hash_map_", # Hash map data structure
|
||||
"umm_assimilate", # UMM malloc assimilation
|
||||
"wifi_stack": [
|
||||
"ieee80211",
|
||||
"hostap",
|
||||
"sta_",
|
||||
"ap_",
|
||||
"scan_",
|
||||
"wifi_",
|
||||
"wpa_",
|
||||
"wps_",
|
||||
"esp_wifi",
|
||||
"cnx_",
|
||||
"wpa3_",
|
||||
"sae_",
|
||||
"wDev_",
|
||||
"ic_",
|
||||
"mac_",
|
||||
"esf_buf",
|
||||
"gWpaSm",
|
||||
"sm_WPA",
|
||||
"eapol_",
|
||||
"owe_",
|
||||
"wifiLowLevelInit",
|
||||
"s_do_mapping",
|
||||
"gScanStruct",
|
||||
"ppSearchTxframe",
|
||||
"ppMapWaitTxq",
|
||||
"ppFillAMPDUBar",
|
||||
"ppCheckTxConnTrafficIdle",
|
||||
"ppCalTkipMic",
|
||||
],
|
||||
# Bluetooth categories must come BEFORE wifi_stack to avoid misclassification
|
||||
# Many BLE symbols contain patterns like "ble_" that would otherwise match wifi patterns
|
||||
"bluetooth": ["bt_", "ble_", "l2c_", "gatt_", "gap_", "hci_", "BT_init"],
|
||||
"wifi_bt_coex": ["coex"],
|
||||
"bluetooth_rom": ["r_ble", "r_lld", "r_llc", "r_llm"],
|
||||
"bluedroid_bt": [
|
||||
"bluedroid",
|
||||
@@ -206,61 +207,6 @@ SYMBOL_PATTERNS = {
|
||||
"copy_extra_byte_in_db",
|
||||
"parse_read_local_supported_commands_response",
|
||||
],
|
||||
"bluetooth": [
|
||||
"bt_",
|
||||
"_ble_", # More specific than "ble_" to avoid matching "able_", "enable_", "disable_"
|
||||
"l2c_",
|
||||
"l2ble_", # L2CAP for BLE
|
||||
"gatt_",
|
||||
"gap_",
|
||||
"hci_",
|
||||
"btsnd_hcic_", # Bluetooth HCI command send functions
|
||||
"BT_init",
|
||||
"BT_tx_", # Bluetooth transmit functions
|
||||
"esp_ble_", # Catch esp_ble_* functions
|
||||
],
|
||||
"bluetooth_ll": [
|
||||
"llm_", # Link layer manager
|
||||
"llc_", # Link layer control
|
||||
"lld_", # Link layer driver
|
||||
"ld_acl_", # Link layer ACL (Asynchronous Connection-Oriented)
|
||||
"llcp_", # Link layer control protocol
|
||||
"lmp_", # Link manager protocol
|
||||
],
|
||||
"wifi_bt_coex": ["coex"],
|
||||
"wifi_stack": [
|
||||
"ieee80211",
|
||||
"hostap",
|
||||
"sta_",
|
||||
"wifi_ap_", # More specific than "ap_" to avoid matching "cap_", "map_"
|
||||
"wifi_scan_", # More specific than "scan_" to avoid matching "_scan_" in other contexts
|
||||
"wifi_",
|
||||
"wpa_",
|
||||
"wps_",
|
||||
"esp_wifi",
|
||||
"cnx_",
|
||||
"wpa3_",
|
||||
"sae_",
|
||||
"wDev_",
|
||||
"ic_mac_", # More specific than "mac_" to avoid matching emac_
|
||||
"esf_buf",
|
||||
"gWpaSm",
|
||||
"sm_WPA",
|
||||
"eapol_",
|
||||
"owe_",
|
||||
"wifiLowLevelInit",
|
||||
# Removed "s_do_mapping" - this is memory management, not WiFi
|
||||
"gScanStruct",
|
||||
"ppSearchTxframe",
|
||||
"ppMapWaitTxq",
|
||||
"ppFillAMPDUBar",
|
||||
"ppCheckTxConnTrafficIdle",
|
||||
"ppCalTkipMic",
|
||||
"phy_force_wifi",
|
||||
"phy_unforce_wifi",
|
||||
"write_wifi_chan",
|
||||
"wifi_track_pll",
|
||||
],
|
||||
"crypto_math": [
|
||||
"ecp_",
|
||||
"bignum_",
|
||||
@@ -285,36 +231,13 @@ SYMBOL_PATTERNS = {
|
||||
"p_256_init_curve",
|
||||
"shift_sub_rows",
|
||||
"rshift",
|
||||
"rijndaelEncrypt", # AES Rijndael encryption
|
||||
],
|
||||
# System and Arduino core functions must come before libc
|
||||
"esp_system": [
|
||||
"system_", # ESP system functions
|
||||
"postmortem_", # Postmortem reporting
|
||||
],
|
||||
"arduino_core": [
|
||||
"pinMode",
|
||||
"resetPins",
|
||||
"millis",
|
||||
"micros",
|
||||
"delay(", # More specific - Arduino delay function with parenthesis
|
||||
"delayMicroseconds",
|
||||
"digitalWrite",
|
||||
"digitalRead",
|
||||
],
|
||||
"sntp": ["sntp_", "sntp_recv"],
|
||||
"scheduler": [
|
||||
"run_scheduled_",
|
||||
"compute_scheduled_",
|
||||
"event_TaskQueue",
|
||||
],
|
||||
"hw_crypto": ["esp_aes", "esp_sha", "esp_rsa", "esp_bignum", "esp_mpi"],
|
||||
"libc": [
|
||||
"printf",
|
||||
"scanf",
|
||||
"malloc",
|
||||
"_free", # More specific than "free" to match _free, __free_r, etc. but not arbitrary "free" substring
|
||||
"umm_free", # UMM malloc free function
|
||||
"free",
|
||||
"memcpy",
|
||||
"memset",
|
||||
"strcpy",
|
||||
@@ -336,7 +259,7 @@ SYMBOL_PATTERNS = {
|
||||
"_setenv_r",
|
||||
"_tzset_unlocked_r",
|
||||
"__tzcalc_limits",
|
||||
"_select", # More specific than "select" to avoid matching "dhcp_select", etc.
|
||||
"select",
|
||||
"scalbnf",
|
||||
"strtof",
|
||||
"strtof_l",
|
||||
@@ -393,24 +316,8 @@ SYMBOL_PATTERNS = {
|
||||
"CSWTCH$",
|
||||
"dst$",
|
||||
"sulp",
|
||||
"_strtol_l", # String to long with locale
|
||||
"__cvt", # Convert
|
||||
"__utoa", # Unsigned to ASCII
|
||||
"__global_locale", # Global locale
|
||||
"_ctype_", # Character type
|
||||
"impure_data", # Impure data
|
||||
],
|
||||
"string_ops": [
|
||||
"strcmp",
|
||||
"strncmp",
|
||||
"strchr",
|
||||
"strstr",
|
||||
"strtok",
|
||||
"strdup",
|
||||
"strncasecmp_P", # String compare (case insensitive, from program memory)
|
||||
"strnlen_P", # String length (from program memory)
|
||||
"strncat_P", # String concatenate (from program memory)
|
||||
],
|
||||
"string_ops": ["strcmp", "strncmp", "strchr", "strstr", "strtok", "strdup"],
|
||||
"memory_alloc": ["malloc", "calloc", "realloc", "free", "_sbrk"],
|
||||
"file_io": [
|
||||
"fread",
|
||||
@@ -431,26 +338,10 @@ SYMBOL_PATTERNS = {
|
||||
"vsscanf",
|
||||
],
|
||||
"cpp_anonymous": ["_GLOBAL__N_", "n$"],
|
||||
# Plain C patterns only - C++ symbols will be categorized via DEMANGLED_PATTERNS
|
||||
"nvs": ["nvs_"], # Plain C NVS functions
|
||||
"ota": ["ota_", "OTA", "esp_ota", "app_desc"],
|
||||
# cpp_runtime: Removed _ZN, _ZL to let DEMANGLED_PATTERNS categorize C++ symbols properly
|
||||
# Only keep patterns that are truly runtime-specific and not categorizable by namespace
|
||||
"cpp_runtime": ["__cxx", "_ZSt", "__gxx_personality", "_Z16"],
|
||||
"exception_handling": [
|
||||
"__cxa_",
|
||||
"_Unwind_",
|
||||
"__gcc_personality",
|
||||
"uw_frame_state",
|
||||
"search_object", # Search for exception handling object
|
||||
"get_cie_encoding", # Get CIE encoding
|
||||
"add_fdes", # Add frame description entries
|
||||
"fde_unencoded_compare", # Compare FDEs
|
||||
"fde_mixed_encoding_compare", # Compare mixed encoding FDEs
|
||||
"frame_downheap", # Frame heap operations
|
||||
"frame_heapsort", # Frame heap sorting
|
||||
],
|
||||
"cpp_runtime": ["__cxx", "_ZN", "_ZL", "_ZSt", "__gxx_personality", "_Z16"],
|
||||
"exception_handling": ["__cxa_", "_Unwind_", "__gcc_personality", "uw_frame_state"],
|
||||
"static_init": ["_GLOBAL__sub_I_"],
|
||||
"mdns_lib": ["mdns"],
|
||||
"phy_radio": [
|
||||
"phy_",
|
||||
"rf_",
|
||||
@@ -503,47 +394,10 @@ SYMBOL_PATTERNS = {
|
||||
"txcal_debuge_mode",
|
||||
"ant_wifitx_cfg",
|
||||
"reg_init_begin",
|
||||
"tx_cap_init", # TX capacitance init
|
||||
"ram_set_txcap", # RAM TX capacitance setting
|
||||
"tx_atten_", # TX attenuation
|
||||
"txiq_", # TX I/Q calibration
|
||||
"ram_cal_", # RAM calibration
|
||||
"ram_rxiq_", # RAM RX I/Q
|
||||
"readvdd33", # Read VDD33
|
||||
"test_tout", # Test timeout
|
||||
"tsen_meas", # Temperature sensor measurement
|
||||
"bbpll_cal", # Baseband PLL calibration
|
||||
"set_cal_", # Set calibration
|
||||
"set_rfanagain_", # Set RF analog gain
|
||||
"set_txdc_", # Set TX DC
|
||||
"get_vdd33_", # Get VDD33
|
||||
"gen_rx_gain_table", # Generate RX gain table
|
||||
"ram_ana_inf_gating_en", # RAM analog interface gating enable
|
||||
"tx_cont_en", # TX continuous enable
|
||||
"tx_delay_cfg", # TX delay configuration
|
||||
"tx_gain_table_set", # TX gain table set
|
||||
"check_and_reset_hw_deadlock", # Hardware deadlock check
|
||||
"s_config", # System/hardware config
|
||||
"chan14_mic_cfg", # Channel 14 MIC config
|
||||
],
|
||||
"wifi_phy_pp": [
|
||||
"pp_",
|
||||
"ppT",
|
||||
"ppR",
|
||||
"ppP",
|
||||
"ppInstall",
|
||||
"ppCalTxAMPDULength",
|
||||
"ppCheckTx", # Packet processor TX check
|
||||
"ppCal", # Packet processor calibration
|
||||
"HdlAllBuffedEb", # Handle buffered EB
|
||||
],
|
||||
"wifi_phy_pp": ["pp_", "ppT", "ppR", "ppP", "ppInstall", "ppCalTxAMPDULength"],
|
||||
"wifi_lmac": ["lmac"],
|
||||
"wifi_device": [
|
||||
"wdev",
|
||||
"wDev_",
|
||||
"ic_set_sta", # Set station mode
|
||||
"ic_set_vif", # Set virtual interface
|
||||
],
|
||||
"wifi_device": ["wdev", "wDev_"],
|
||||
"power_mgmt": [
|
||||
"pm_",
|
||||
"sleep",
|
||||
@@ -552,7 +406,15 @@ SYMBOL_PATTERNS = {
|
||||
"deep_sleep",
|
||||
"power_down",
|
||||
"g_pm",
|
||||
"pmc", # Power Management Controller
|
||||
],
|
||||
"memory_mgmt": [
|
||||
"mem_",
|
||||
"memory_",
|
||||
"tlsf_",
|
||||
"memp_",
|
||||
"pbuf_",
|
||||
"pbuf_alloc",
|
||||
"pbuf_copy_partial_pbuf",
|
||||
],
|
||||
"hal_layer": ["hal_"],
|
||||
"clock_mgmt": [
|
||||
@@ -577,6 +439,7 @@ SYMBOL_PATTERNS = {
|
||||
"error_handling": ["panic", "abort", "assert", "error_", "fault"],
|
||||
"authentication": ["auth"],
|
||||
"ppp_protocol": ["ppp", "ipcp_", "lcp_", "chap_", "LcpEchoCheck"],
|
||||
"dhcp": ["dhcp", "handle_dhcp"],
|
||||
"ethernet_phy": [
|
||||
"emac_",
|
||||
"eth_phy_",
|
||||
@@ -755,15 +618,7 @@ SYMBOL_PATTERNS = {
|
||||
"ampdu_dispatch_upto",
|
||||
],
|
||||
"ieee802_11": ["ieee802_11_", "ieee802_11_parse_elems"],
|
||||
"rate_control": [
|
||||
"rssi_margin",
|
||||
"rcGetSched",
|
||||
"get_rate_fcc_index",
|
||||
"rcGetRate", # Get rate
|
||||
"rc_get_", # Rate control getters
|
||||
"rc_set_", # Rate control setters
|
||||
"rc_enable_", # Rate control enable functions
|
||||
],
|
||||
"rate_control": ["rssi_margin", "rcGetSched", "get_rate_fcc_index"],
|
||||
"nan": ["nan_dp_", "nan_dp_post_tx", "nan_dp_delete_peer"],
|
||||
"channel_mgmt": ["chm_init", "chm_set_current_channel"],
|
||||
"trace": ["trc_init", "trc_onAmpduOp"],
|
||||
@@ -944,18 +799,31 @@ SYMBOL_PATTERNS = {
|
||||
"supports_interlaced_inquiry_scan",
|
||||
"supports_reading_remote_extended_features",
|
||||
],
|
||||
"bluetooth_ll": [
|
||||
"lld_pdu_",
|
||||
"ld_acl_",
|
||||
"lld_stop_ind_handler",
|
||||
"lld_evt_winsize_change",
|
||||
"config_lld_evt_funcs_reset",
|
||||
"config_lld_funcs_reset",
|
||||
"config_llm_funcs_reset",
|
||||
"llm_set_long_adv_data",
|
||||
"lld_retry_tx_prog",
|
||||
"llc_link_sup_to_ind_handler",
|
||||
"config_llc_funcs_reset",
|
||||
"lld_evt_rxwin_compute",
|
||||
"config_btdm_funcs_reset",
|
||||
"config_ea_funcs_reset",
|
||||
"llc_defalut_state_tab_reset",
|
||||
"config_rwip_funcs_reset",
|
||||
"ke_lmp_rx_flooding_detect",
|
||||
],
|
||||
}
|
||||
|
||||
# Demangled patterns: patterns found in demangled C++ names
|
||||
DEMANGLED_PATTERNS = {
|
||||
"gpio_driver": ["GPIO"],
|
||||
"uart_driver": ["UART"],
|
||||
# mdns_lib must come before network_stack to avoid "udp" matching "_udpReadBuffer" in MDNSResponder
|
||||
"mdns_lib": [
|
||||
"MDNSResponder",
|
||||
"MDNSImplementation",
|
||||
"MDNS",
|
||||
],
|
||||
"network_stack": [
|
||||
"lwip",
|
||||
"tcp",
|
||||
@@ -968,24 +836,6 @@ DEMANGLED_PATTERNS = {
|
||||
"ethernet",
|
||||
"ppp",
|
||||
"slip",
|
||||
"UdpContext", # UDP context class
|
||||
"DhcpServer", # DHCP server class
|
||||
],
|
||||
"arduino_core": [
|
||||
"String::", # Arduino String class
|
||||
"Print::", # Arduino Print class
|
||||
"HardwareSerial::", # Serial class
|
||||
"IPAddress::", # IP address class
|
||||
"EspClass::", # ESP class
|
||||
"experimental::_SPI", # Experimental SPI
|
||||
],
|
||||
"ota": [
|
||||
"UpdaterClass",
|
||||
"Updater::",
|
||||
],
|
||||
"wifi": [
|
||||
"ESP8266WiFi",
|
||||
"WiFi::",
|
||||
],
|
||||
"wifi_stack": ["NetworkInterface"],
|
||||
"nimble_bt": [
|
||||
@@ -1004,6 +854,7 @@ DEMANGLED_PATTERNS = {
|
||||
"rtti": ["__type_info", "__class_type_info"],
|
||||
"web_server_lib": ["AsyncWebServer", "AsyncWebHandler", "WebServer"],
|
||||
"async_tcp": ["AsyncClient", "AsyncServer"],
|
||||
"mdns_lib": ["mdns"],
|
||||
"json_lib": [
|
||||
"ArduinoJson",
|
||||
"JsonDocument",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "absolute_humidity.h"
|
||||
|
||||
// test
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ enum SaturationVaporPressureEquation {
|
||||
};
|
||||
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
// Test change for clang-tidy split logic
|
||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
public:
|
||||
AbsoluteHumidityComponent() = default;
|
||||
|
||||
@@ -9,7 +9,7 @@ static const char *const TAG = "adalight_light_effect";
|
||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
AdalightLightEffect::AdalightLightEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
|
||||
void AdalightLightEffect::start() {
|
||||
AddressableLightEffect::start();
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace adalight {
|
||||
|
||||
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
||||
public:
|
||||
AdalightLightEffect(const char *name);
|
||||
AdalightLightEffect(const std::string &name);
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
@@ -486,7 +486,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
if (light->supports_effects()) {
|
||||
msg.effects.emplace_back("None");
|
||||
for (auto *effect : light->get_effects()) {
|
||||
msg.effects.emplace_back(effect->get_name());
|
||||
msg.effects.push_back(effect->get_name());
|
||||
}
|
||||
}
|
||||
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
@@ -1572,13 +1572,7 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
||||
resp.success = false;
|
||||
|
||||
psk_t psk{};
|
||||
if (msg.key.empty()) {
|
||||
if (this->parent_->clear_noise_psk(true)) {
|
||||
resp.success = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||
}
|
||||
} else if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
|
||||
@@ -468,31 +468,6 @@ uint16_t APIServer::get_port() const { return this->port_; }
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
|
||||
const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
|
||||
if (!this->noise_pref_.save(&new_psk)) {
|
||||
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
|
||||
return false;
|
||||
}
|
||||
// ensure it's written immediately
|
||||
if (!global_preferences->sync()) {
|
||||
ESP_LOGW(TAG, "Failed to sync preferences");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
|
||||
if (make_active) {
|
||||
this->set_timeout(100, [this, active_psk]() {
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||
this->set_noise_psk(active_psk);
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||
// When PSK is set from YAML, this function should never be called
|
||||
@@ -507,21 +482,27 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
}
|
||||
|
||||
SavedNoisePsk new_saved_psk{psk};
|
||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
|
||||
make_active);
|
||||
#endif
|
||||
}
|
||||
bool APIServer::clear_noise_psk(bool make_active) {
|
||||
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||
// When PSK is set from YAML, this function should never be called
|
||||
// but if it is, reject the change
|
||||
ESP_LOGW(TAG, "Key set in YAML");
|
||||
return false;
|
||||
#else
|
||||
SavedNoisePsk empty_psk{};
|
||||
psk_t empty{};
|
||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
|
||||
make_active);
|
||||
if (!this->noise_pref_.save(&new_saved_psk)) {
|
||||
ESP_LOGW(TAG, "Failed to save Noise PSK");
|
||||
return false;
|
||||
}
|
||||
// ensure it's written immediately
|
||||
if (!global_preferences->sync()) {
|
||||
ESP_LOGW(TAG, "Failed to sync preferences");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Noise PSK saved");
|
||||
if (make_active) {
|
||||
this->set_timeout(100, [this, psk]() {
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||
this->set_noise_psk(psk);
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
c->send_message(req, DisconnectRequest::MESSAGE_TYPE);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -53,7 +53,6 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool save_noise_psk(psk_t psk, bool make_active = true);
|
||||
bool clear_noise_psk(bool make_active = true);
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||
#endif // USE_API_NOISE
|
||||
@@ -175,10 +174,6 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
protected:
|
||||
void schedule_reboot_timeout_();
|
||||
#ifdef USE_API_NOISE
|
||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||
const psk_t &active_psk, bool make_active);
|
||||
#endif // USE_API_NOISE
|
||||
// Pointers and pointer-like types first (4 bytes each)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
|
||||
@@ -264,31 +264,20 @@ async def delayed_off_filter_to_code(config, filter_id):
|
||||
),
|
||||
)
|
||||
async def autorepeat_filter_to_code(config, filter_id):
|
||||
timings = []
|
||||
if len(config) > 0:
|
||||
timings = [
|
||||
cg.StructInitializer(
|
||||
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
|
||||
("delay", conf[CONF_DELAY]),
|
||||
("time_off", conf[CONF_TIME_OFF]),
|
||||
("time_on", conf[CONF_TIME_ON]),
|
||||
)
|
||||
timings.extend(
|
||||
(conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])
|
||||
for conf in config
|
||||
]
|
||||
)
|
||||
else:
|
||||
timings = [
|
||||
cg.StructInitializer(
|
||||
cg.MockObj("AutorepeatFilterTiming", "esphome::binary_sensor::"),
|
||||
("delay", cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds),
|
||||
(
|
||||
"time_off",
|
||||
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
|
||||
),
|
||||
(
|
||||
"time_on",
|
||||
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
|
||||
),
|
||||
timings.append(
|
||||
(
|
||||
cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds,
|
||||
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
|
||||
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
|
||||
)
|
||||
]
|
||||
)
|
||||
var = cg.new_Pvariable(filter_id, timings)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -92,8 +92,8 @@ class DoubleClickTrigger : public Trigger<> {
|
||||
|
||||
class MultiClickTrigger : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(timing) {}
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::vector<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(std::move(timing)) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
@@ -115,7 +115,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void trigger_();
|
||||
|
||||
BinarySensor *parent_;
|
||||
FixedVector<MultiClickTriggerEvent> timing_;
|
||||
std::vector<MultiClickTriggerEvent> timing_;
|
||||
uint32_t invalid_cooldown_{1000};
|
||||
optional<size_t> at_index_{};
|
||||
bool last_state_{false};
|
||||
|
||||
@@ -51,7 +51,7 @@ void BinarySensor::add_filter(Filter *filter) {
|
||||
last_filter->next_ = filter;
|
||||
}
|
||||
}
|
||||
void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
|
||||
void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
|
||||
for (Filter *filter : filters) {
|
||||
this->add_filter(filter);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -48,7 +48,7 @@ class BinarySensor : public StatefulEntityBase<bool>, public EntityBase_DeviceCl
|
||||
void publish_initial_state(bool new_state);
|
||||
|
||||
void add_filter(Filter *filter);
|
||||
void add_filters(std::initializer_list<Filter *> filters);
|
||||
void add_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "filter.h"
|
||||
|
||||
#include "binary_sensor.h"
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -67,7 +68,7 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) : timings_(timings) {}
|
||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
@@ -80,6 +82,11 @@ class InvertFilter : public Filter {
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) {
|
||||
this->delay = delay;
|
||||
this->time_off = off;
|
||||
this->time_on = on;
|
||||
}
|
||||
uint32_t delay;
|
||||
uint32_t time_off;
|
||||
uint32_t time_on;
|
||||
@@ -87,7 +94,7 @@ struct AutorepeatFilterTiming {
|
||||
|
||||
class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings);
|
||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
@@ -97,7 +104,7 @@ class AutorepeatFilter : public Filter, public Component {
|
||||
void next_timing_();
|
||||
void next_value_(bool val);
|
||||
|
||||
FixedVector<AutorepeatFilterTiming> timings_;
|
||||
std::vector<AutorepeatFilterTiming> timings_;
|
||||
uint8_t active_timing_{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -385,14 +385,12 @@ void Climate::save_state_() {
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = true;
|
||||
const auto &supported = traits.get_supported_custom_fan_modes();
|
||||
// std::set has consistent order (lexicographic for strings)
|
||||
size_t i = 0;
|
||||
for (const auto &mode : supported) {
|
||||
if (mode == custom_fan_mode) {
|
||||
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
if (vec[i] == custom_fan_mode) {
|
||||
state.custom_fan_mode = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_presets() && preset.has_value()) {
|
||||
@@ -402,14 +400,12 @@ void Climate::save_state_() {
|
||||
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
|
||||
state.uses_custom_preset = true;
|
||||
const auto &supported = traits.get_supported_custom_presets();
|
||||
// std::set has consistent order (lexicographic for strings)
|
||||
size_t i = 0;
|
||||
for (const auto &preset : supported) {
|
||||
if (preset == custom_preset) {
|
||||
std::vector<std::string> vec{supported.begin(), supported.end()};
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
if (vec[i] == custom_preset) {
|
||||
state.custom_preset = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
@@ -524,23 +520,13 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||
call.set_target_humidity(this->target_humidity);
|
||||
}
|
||||
if (this->uses_custom_fan_mode) {
|
||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
||||
call.fan_mode_.reset();
|
||||
call.custom_fan_mode_ = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
|
||||
}
|
||||
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
call.set_fan_mode(this->fan_mode);
|
||||
}
|
||||
if (this->uses_custom_preset) {
|
||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
||||
call.preset_.reset();
|
||||
call.custom_preset_ = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
|
||||
}
|
||||
} else if (traits.supports_preset(this->preset)) {
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
call.set_preset(this->preset);
|
||||
}
|
||||
if (traits.supports_swing_mode(this->swing_mode)) {
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
call.set_swing_mode(this->swing_mode);
|
||||
}
|
||||
return call;
|
||||
@@ -559,25 +545,29 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||
climate->target_humidity = this->target_humidity;
|
||||
}
|
||||
if (this->uses_custom_fan_mode) {
|
||||
if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
|
||||
climate->fan_mode.reset();
|
||||
climate->custom_fan_mode = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode);
|
||||
}
|
||||
} else if (traits.supports_fan_mode(this->fan_mode)) {
|
||||
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||
climate->fan_mode = this->fan_mode;
|
||||
climate->custom_fan_mode.reset();
|
||||
}
|
||||
if (this->uses_custom_preset) {
|
||||
if (this->custom_preset < traits.get_supported_custom_presets().size()) {
|
||||
climate->preset.reset();
|
||||
climate->custom_preset = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset);
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
|
||||
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||
const auto &modes = traits.get_supported_custom_fan_modes();
|
||||
std::vector<std::string> modes_vec{modes.begin(), modes.end()};
|
||||
if (custom_fan_mode < modes_vec.size()) {
|
||||
climate->custom_fan_mode = modes_vec[this->custom_fan_mode];
|
||||
}
|
||||
} else if (traits.supports_preset(this->preset)) {
|
||||
climate->preset = this->preset;
|
||||
climate->custom_preset.reset();
|
||||
}
|
||||
if (traits.supports_swing_mode(this->swing_mode)) {
|
||||
if (traits.get_supports_presets() && !this->uses_custom_preset) {
|
||||
climate->preset = this->preset;
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
|
||||
// std::set has consistent order (lexicographic for strings), so this is ok
|
||||
const auto &presets = traits.get_supported_custom_presets();
|
||||
std::vector<std::string> presets_vec{presets.begin(), presets.end()};
|
||||
if (custom_preset < presets_vec.size()) {
|
||||
climate->custom_preset = presets_vec[this->custom_preset];
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
climate->swing_mode = this->swing_mode;
|
||||
}
|
||||
climate->publish_state();
|
||||
|
||||
@@ -33,7 +33,6 @@ class Climate;
|
||||
class ClimateCall {
|
||||
public:
|
||||
explicit ClimateCall(Climate *parent) : parent_(parent) {}
|
||||
friend struct ClimateDeviceRestoreState;
|
||||
|
||||
/// Set the mode of the climate device.
|
||||
ClimateCall &set_mode(ClimateMode mode);
|
||||
|
||||
@@ -80,8 +80,8 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
light_effects_.insert(light_effect);
|
||||
|
||||
@@ -95,8 +95,8 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
light_effects_.erase(light_effect);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace e131 {
|
||||
static const char *const TAG = "e131_addressable_light_effect";
|
||||
static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1);
|
||||
|
||||
E131AddressableLightEffect::E131AddressableLightEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
E131AddressableLightEffect::E131AddressableLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
|
||||
int E131AddressableLightEffect::get_data_per_universe() const { return get_lights_per_universe() * channels_; }
|
||||
|
||||
@@ -58,8 +58,8 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
|
||||
auto *input_data = packet.values + 1;
|
||||
|
||||
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
|
||||
output_end);
|
||||
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name().c_str(), universe,
|
||||
output_offset, output_end);
|
||||
|
||||
switch (channels_) {
|
||||
case E131_MONO:
|
||||
|
||||
@@ -13,7 +13,7 @@ enum E131LightChannels { E131_MONO = 1, E131_RGB = 3, E131_RGBW = 4 };
|
||||
|
||||
class E131AddressableLightEffect : public light::AddressableLightEffect {
|
||||
public:
|
||||
E131AddressableLightEffect(const char *name);
|
||||
E131AddressableLightEffect(const std::string &name);
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
@@ -304,13 +304,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
|
||||
ext = "tar.xz"
|
||||
else:
|
||||
ext = "zip"
|
||||
if release:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
|
||||
|
||||
|
||||
def _is_framework_url(source: str) -> str:
|
||||
@@ -359,7 +355,6 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
||||
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"),
|
||||
@@ -555,32 +550,6 @@ CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
|
||||
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING = "enable_lwip_tcpip_core_locking"
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY = "enable_lwip_check_thread_safety"
|
||||
CONF_DISABLE_LIBC_LOCKS_IN_IRAM = "disable_libc_locks_in_iram"
|
||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS = "disable_vfs_support_termios"
|
||||
CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
|
||||
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
|
||||
|
||||
# VFS requirement tracking
|
||||
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
|
||||
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
|
||||
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
|
||||
|
||||
|
||||
def require_vfs_select() -> None:
|
||||
"""Mark that VFS select support is required by a component.
|
||||
|
||||
Call this from components that use esp_vfs_eventfd or other VFS select features.
|
||||
This prevents CONFIG_VFS_SUPPORT_SELECT from being disabled.
|
||||
"""
|
||||
CORE.data[KEY_VFS_SELECT_REQUIRED] = True
|
||||
|
||||
|
||||
def require_vfs_dir() -> None:
|
||||
"""Mark that VFS directory support is required by a component.
|
||||
|
||||
Call this from components that use directory functions (opendir, readdir, mkdir, etc.).
|
||||
This prevents CONFIG_VFS_SUPPORT_DIR from being disabled.
|
||||
"""
|
||||
CORE.data[KEY_VFS_DIR_REQUIRED] = True
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
@@ -646,13 +615,6 @@ FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_SELECT, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||
}
|
||||
),
|
||||
@@ -882,11 +844,6 @@ async def to_code(config):
|
||||
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
|
||||
os.environ.pop(clean_var, None)
|
||||
|
||||
# Set the location of the IDF component manager cache
|
||||
os.environ["IDF_COMPONENT_CACHE_PATH"] = str(
|
||||
CORE.relative_internal_path(".espressif")
|
||||
)
|
||||
|
||||
add_extra_script(
|
||||
"post",
|
||||
"post_build.py",
|
||||
@@ -1005,43 +962,6 @@ async def to_code(config):
|
||||
if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True):
|
||||
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
|
||||
|
||||
# Disable VFS support for termios (terminal I/O functions)
|
||||
# ESPHome doesn't use termios functions on ESP32 (only used in host UART driver).
|
||||
# Saves approximately 1.8KB of flash when disabled (default).
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True),
|
||||
)
|
||||
|
||||
# Disable VFS support for select() with file descriptors
|
||||
# ESPHome only uses select() with sockets via lwip_select(), which still works.
|
||||
# VFS select is only needed for UART/eventfd file descriptors.
|
||||
# Components that need it (e.g., openthread) call require_vfs_select().
|
||||
# Saves approximately 2.7KB of flash when disabled (default).
|
||||
if CORE.data.get(KEY_VFS_SELECT_REQUIRED, False):
|
||||
# Component requires VFS select - force enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_SELECT", True)
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_SELECT",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True),
|
||||
)
|
||||
|
||||
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
|
||||
# ESPHome doesn't use directory functions on ESP32.
|
||||
# Components that need it (e.g., storage components) call require_vfs_dir().
|
||||
# Saves approximately 0.5KB+ of flash when disabled (default).
|
||||
if CORE.data.get(KEY_VFS_DIR_REQUIRED, False):
|
||||
# Component requires VFS directory support - force enable regardless of user setting
|
||||
add_idf_sdkconfig_option("CONFIG_VFS_SUPPORT_DIR", True)
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_DIR",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True),
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
if CONF_PARTITIONS in config:
|
||||
add_extra_build_file(
|
||||
|
||||
@@ -40,13 +40,13 @@ class ESP32InternalGPIOPin : public InternalGPIOPin {
|
||||
// - 3 bytes for members below
|
||||
// - 1 byte padding for alignment
|
||||
// - 4 bytes for vtable pointer
|
||||
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
|
||||
gpio::Flags flags_{}; // GPIO flags (1 byte)
|
||||
uint8_t pin_; // GPIO pin number (0-255, actual max ~54 on ESP32)
|
||||
gpio::Flags flags_; // GPIO flags (1 byte)
|
||||
struct PinFlags {
|
||||
uint8_t inverted : 1; // Invert pin logic (1 bit)
|
||||
uint8_t drive_strength : 2; // Drive strength 0-3 (2 bits)
|
||||
uint8_t reserved : 5; // Reserved for future use (5 bits)
|
||||
} pin_flags_{}; // Total: 1 byte
|
||||
} pin_flags_; // Total: 1 byte
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool isr_service_installed;
|
||||
};
|
||||
|
||||
@@ -223,10 +223,7 @@ async def esp32_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_DRIVE_STRENGTH in config:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
|
||||
@@ -112,7 +112,7 @@ async def to_code(config):
|
||||
|
||||
cg.add_define("USE_IMPROV")
|
||||
|
||||
await improv_base.setup_improv_core(var, config, "esp32_improv")
|
||||
await improv_base.setup_improv_core(var, config)
|
||||
|
||||
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
|
||||
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
||||
|
||||
@@ -389,13 +389,11 @@ void ESP32ImprovComponent::check_wifi_connection_() {
|
||||
std::string url_strings[3];
|
||||
size_t url_count = 0;
|
||||
|
||||
#ifdef USE_ESP32_IMPROV_NEXT_URL
|
||||
// Add next_url if configured (should be first per Improv BLE spec)
|
||||
std::string next_url = this->get_formatted_next_url_();
|
||||
if (!next_url.empty()) {
|
||||
url_strings[url_count++] = std::move(next_url);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add default URLs for backward compatibility
|
||||
url_strings[url_count++] = ESPHOME_MY_LINK;
|
||||
|
||||
@@ -190,9 +190,7 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option(
|
||||
"extra_scripts", ["pre:testing_mode.py", "post:post_build.py"]
|
||||
)
|
||||
cg.add_platformio_option("extra_scripts", ["pre:iram_fix.py", "post:post_build.py"])
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
@@ -232,9 +230,9 @@ async def to_code(config):
|
||||
# For cases where nullptrs can be handled, use nothrow: `new (std::nothrow) T;`
|
||||
cg.add_build_flag("-DNEW_OOM_ABORT")
|
||||
|
||||
# In testing mode, fake larger memory to allow linking grouped component tests
|
||||
# Real ESP8266 hardware only has 32KB IRAM and ~80KB RAM, but for CI testing
|
||||
# we pretend it has much larger memory to test that components compile together
|
||||
# In testing mode, fake a larger IRAM to allow linking grouped component tests
|
||||
# Real ESP8266 hardware only has 32KB IRAM, but for CI testing we pretend it has 2MB
|
||||
# This is done via a pre-build script that generates a custom linker script
|
||||
if CORE.testing_mode:
|
||||
cg.add_build_flag("-DESPHOME_TESTING_MODE")
|
||||
|
||||
@@ -273,8 +271,8 @@ def copy_files():
|
||||
post_build_file,
|
||||
CORE.relative_build_path("post_build.py"),
|
||||
)
|
||||
testing_mode_file = dir / "testing_mode.py.script"
|
||||
iram_fix_file = dir / "iram_fix.py.script"
|
||||
copy_file_if_changed(
|
||||
testing_mode_file,
|
||||
CORE.relative_build_path("testing_mode.py"),
|
||||
iram_fix_file,
|
||||
CORE.relative_build_path("iram_fix.py"),
|
||||
)
|
||||
|
||||
@@ -29,8 +29,8 @@ class ESP8266GPIOPin : public InternalGPIOPin {
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_{};
|
||||
gpio::Flags flags_{};
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace esp8266
|
||||
|
||||
@@ -165,10 +165,7 @@ async def esp8266_pin_to_code(config):
|
||||
num = config[CONF_NUMBER]
|
||||
mode = config[CONF_MODE]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(mode)))
|
||||
if num < 16:
|
||||
initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][
|
||||
|
||||
44
esphome/components/esp8266/iram_fix.py.script
Normal file
44
esphome/components/esp8266/iram_fix.py.script
Normal file
@@ -0,0 +1,44 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def patch_linker_script_after_preprocess(source, target, env):
|
||||
"""Patch the local linker script after PlatformIO preprocesses it."""
|
||||
# Check if we're in testing mode by looking for the define
|
||||
build_flags = env.get("BUILD_FLAGS", [])
|
||||
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
|
||||
|
||||
if not testing_mode:
|
||||
return
|
||||
|
||||
# Get the local linker script path
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
local_ld = os.path.join(build_dir, "ld", "local.eagle.app.v6.common.ld")
|
||||
|
||||
if not os.path.exists(local_ld):
|
||||
return
|
||||
|
||||
# Read the linker script
|
||||
with open(local_ld, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace IRAM size from 0x8000 (32KB) to 0x200000 (2MB)
|
||||
# The line looks like: iram1_0_seg : org = 0x40100000, len = 0x8000
|
||||
updated = re.sub(
|
||||
r"(iram1_0_seg\s*:\s*org\s*=\s*0x40100000\s*,\s*len\s*=\s*)0x8000",
|
||||
r"\g<1>0x200000",
|
||||
content,
|
||||
)
|
||||
|
||||
if updated != content:
|
||||
with open(local_ld, "w") as f:
|
||||
f.write(updated)
|
||||
print("ESPHome: Patched IRAM size to 2MB for testing mode")
|
||||
|
||||
|
||||
# Hook into the build process right before linking
|
||||
# This runs after PlatformIO has already preprocessed the linker scripts
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_linker_script_after_preprocess)
|
||||
@@ -1,166 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
# Memory sizes for testing mode (allow larger builds for CI component grouping)
|
||||
TESTING_IRAM_SIZE = "0x200000" # 2MB
|
||||
TESTING_DRAM_SIZE = "0x200000" # 2MB
|
||||
TESTING_FLASH_SIZE = "0x2000000" # 32MB
|
||||
|
||||
|
||||
def patch_segment_size(content, segment_name, new_size, label):
|
||||
"""Patch a memory segment's length in linker script.
|
||||
|
||||
Args:
|
||||
content: Linker script content
|
||||
segment_name: Name of the segment (e.g., 'iram1_0_seg')
|
||||
new_size: New size as hex string (e.g., '0x200000')
|
||||
label: Human-readable label for logging (e.g., 'IRAM')
|
||||
|
||||
Returns:
|
||||
Tuple of (patched_content, was_patched)
|
||||
"""
|
||||
# Match: segment_name : org = 0x..., len = 0x...
|
||||
pattern = rf"({segment_name}\s*:\s*org\s*=\s*0x[0-9a-fA-F]+\s*,\s*len\s*=\s*)0x[0-9a-fA-F]+"
|
||||
new_content = re.sub(pattern, rf"\g<1>{new_size}", content)
|
||||
return new_content, new_content != content
|
||||
|
||||
|
||||
def apply_memory_patches(content):
|
||||
"""Apply IRAM, DRAM, and Flash patches to linker script content.
|
||||
|
||||
Args:
|
||||
content: Linker script content as string
|
||||
|
||||
Returns:
|
||||
Patched content as string
|
||||
"""
|
||||
patches_applied = []
|
||||
|
||||
# Patch IRAM (for larger code in IRAM)
|
||||
content, patched = patch_segment_size(content, "iram1_0_seg", TESTING_IRAM_SIZE, "IRAM")
|
||||
if patched:
|
||||
patches_applied.append("IRAM")
|
||||
|
||||
# Patch DRAM (for larger BSS/data sections)
|
||||
content, patched = patch_segment_size(content, "dram0_0_seg", TESTING_DRAM_SIZE, "DRAM")
|
||||
if patched:
|
||||
patches_applied.append("DRAM")
|
||||
|
||||
# Patch Flash (for larger code sections)
|
||||
content, patched = patch_segment_size(content, "irom0_0_seg", TESTING_FLASH_SIZE, "Flash")
|
||||
if patched:
|
||||
patches_applied.append("Flash")
|
||||
|
||||
if patches_applied:
|
||||
iram_mb = int(TESTING_IRAM_SIZE, 16) // (1024 * 1024)
|
||||
dram_mb = int(TESTING_DRAM_SIZE, 16) // (1024 * 1024)
|
||||
flash_mb = int(TESTING_FLASH_SIZE, 16) // (1024 * 1024)
|
||||
print(f" Patched memory segments: {', '.join(patches_applied)} (IRAM/DRAM: {iram_mb}MB, Flash: {flash_mb}MB)")
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def patch_linker_script_file(filepath, description):
|
||||
"""Patch a linker script file in the build directory with enlarged memory segments.
|
||||
|
||||
This function modifies linker scripts in the build directory only (never SDK files).
|
||||
It patches IRAM, DRAM, and Flash segments to allow larger builds in testing mode.
|
||||
|
||||
Args:
|
||||
filepath: Path to the linker script file in the build directory
|
||||
description: Human-readable description for logging
|
||||
|
||||
Returns:
|
||||
True if the file was patched, False if already patched or not found
|
||||
"""
|
||||
if not os.path.exists(filepath):
|
||||
print(f"ESPHome: {description} not found at {filepath}")
|
||||
return False
|
||||
|
||||
print(f"ESPHome: Patching {description}...")
|
||||
with open(filepath, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
patched_content = apply_memory_patches(content)
|
||||
|
||||
if patched_content != content:
|
||||
with open(filepath, "w") as f:
|
||||
f.write(patched_content)
|
||||
print(f"ESPHome: Successfully patched {description}")
|
||||
return True
|
||||
else:
|
||||
print(f"ESPHome: {description} already patched or no changes needed")
|
||||
return False
|
||||
|
||||
|
||||
def patch_local_linker_script(source, target, env):
|
||||
"""Patch the local.eagle.app.v6.common.ld in build directory.
|
||||
|
||||
This patches the preprocessed linker script that PlatformIO creates in the build
|
||||
directory, enlarging IRAM, DRAM, and Flash segments for testing mode.
|
||||
|
||||
Args:
|
||||
source: SCons source nodes
|
||||
target: SCons target nodes
|
||||
env: SCons environment
|
||||
"""
|
||||
# Check if we're in testing mode
|
||||
build_flags = env.get("BUILD_FLAGS", [])
|
||||
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
|
||||
|
||||
if not testing_mode:
|
||||
return
|
||||
|
||||
# Patch the local linker script if it exists
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
ld_dir = os.path.join(build_dir, "ld")
|
||||
if os.path.exists(ld_dir):
|
||||
local_ld = os.path.join(ld_dir, "local.eagle.app.v6.common.ld")
|
||||
if os.path.exists(local_ld):
|
||||
patch_linker_script_file(local_ld, "local.eagle.app.v6.common.ld")
|
||||
|
||||
|
||||
# Check if we're in testing mode
|
||||
build_flags = env.get("BUILD_FLAGS", [])
|
||||
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
|
||||
|
||||
if testing_mode:
|
||||
# Create a custom linker script in the build directory with patched memory limits
|
||||
# This allows larger IRAM/DRAM/Flash for CI component grouping tests
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
ldscript = env.GetProjectOption("board_build.ldscript", "")
|
||||
assert ldscript, "No linker script configured in board_build.ldscript"
|
||||
|
||||
framework_dir = env.PioPlatform().get_package_dir("framework-arduinoespressif8266")
|
||||
assert framework_dir is not None, "Could not find framework-arduinoespressif8266 package"
|
||||
|
||||
# Read the original SDK linker script (read-only, SDK is never modified)
|
||||
sdk_ld = os.path.join(framework_dir, "tools", "sdk", "ld", ldscript)
|
||||
# Create a custom version in the build directory (isolated, temporary)
|
||||
custom_ld = os.path.join(build_dir, f"testing_{ldscript}")
|
||||
|
||||
if os.path.exists(sdk_ld) and not os.path.exists(custom_ld):
|
||||
# Read the SDK linker script
|
||||
with open(sdk_ld, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Apply memory patches (IRAM: 2MB, DRAM: 2MB, Flash: 32MB)
|
||||
patched_content = apply_memory_patches(content)
|
||||
|
||||
# Write the patched linker script to the build directory
|
||||
with open(custom_ld, "w") as f:
|
||||
f.write(patched_content)
|
||||
|
||||
print(f"ESPHome: Created custom linker script: {custom_ld}")
|
||||
|
||||
# Tell the linker to use our custom script from the build directory
|
||||
assert os.path.exists(custom_ld), f"Custom linker script not found: {custom_ld}"
|
||||
env.Replace(LDSCRIPT_PATH=custom_ld)
|
||||
print(f"ESPHome: Using custom linker script with patched memory limits")
|
||||
|
||||
# Also patch local.eagle.app.v6.common.ld after PlatformIO creates it
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", patch_local_linker_script)
|
||||
@@ -14,13 +14,13 @@ template<typename... Ts> class SendAction : public Action<Ts...>, public Parente
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, data);
|
||||
|
||||
public:
|
||||
void add_on_sent(const std::initializer_list<Action<Ts...> *> &actions) {
|
||||
void add_on_sent(const std::vector<Action<Ts...> *> &actions) {
|
||||
this->sent_.add_actions(actions);
|
||||
if (this->flags_.wait_for_sent) {
|
||||
this->sent_.add_action(new LambdaAction<Ts...>([this](Ts... x) { this->play_next_(x...); }));
|
||||
}
|
||||
}
|
||||
void add_on_error(const std::initializer_list<Action<Ts...> *> &actions) {
|
||||
void add_on_error(const std::vector<Action<Ts...> *> &actions) {
|
||||
this->error_.add_actions(actions);
|
||||
if (this->flags_.wait_for_sent) {
|
||||
this->error_.add_action(new LambdaAction<Ts...>([this](Ts... x) {
|
||||
|
||||
@@ -32,7 +32,6 @@ from esphome.const import (
|
||||
CONF_MISO_PIN,
|
||||
CONF_MODE,
|
||||
CONF_MOSI_PIN,
|
||||
CONF_NUMBER,
|
||||
CONF_PAGE_ID,
|
||||
CONF_PIN,
|
||||
CONF_POLLING_INTERVAL,
|
||||
@@ -53,36 +52,12 @@ from esphome.core import (
|
||||
coroutine_with_priority,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CONFLICTS_WITH = ["wifi"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
AUTO_LOAD = ["network"]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# RMII pins that are hardcoded on ESP32 classic and cannot be changed
|
||||
# These pins are used by the internal Ethernet MAC when using RMII PHYs
|
||||
ESP32_RMII_FIXED_PINS = {
|
||||
19: "EMAC_TXD0",
|
||||
21: "EMAC_TX_EN",
|
||||
22: "EMAC_TXD1",
|
||||
25: "EMAC_RXD0",
|
||||
26: "EMAC_RXD1",
|
||||
27: "EMAC_RX_CRS_DV",
|
||||
}
|
||||
|
||||
# RMII default pins for ESP32-P4
|
||||
# These are the default pins used by ESP-IDF and are configurable in principle,
|
||||
# but ESPHome's ethernet component currently has no way to change them
|
||||
ESP32P4_RMII_DEFAULT_PINS = {
|
||||
34: "EMAC_TXD0",
|
||||
35: "EMAC_TXD1",
|
||||
28: "EMAC_RX_CRS_DV",
|
||||
29: "EMAC_RXD0",
|
||||
30: "EMAC_RXD1",
|
||||
49: "EMAC_TX_EN",
|
||||
}
|
||||
|
||||
ethernet_ns = cg.esphome_ns.namespace("ethernet")
|
||||
PHYRegister = ethernet_ns.struct("PHYRegister")
|
||||
CONF_PHY_ADDR = "phy_addr"
|
||||
@@ -298,7 +273,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate_spi(config):
|
||||
def _final_validate(config):
|
||||
if config[CONF_TYPE] not in SPI_ETHERNET_TYPES:
|
||||
return
|
||||
if spi_configs := fv.full_config.get().get(CONF_SPI):
|
||||
@@ -317,6 +292,9 @@ def _final_validate_spi(config):
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
def manual_ip(config):
|
||||
return cg.StructInitializer(
|
||||
ManualIP,
|
||||
@@ -405,57 +383,3 @@ async def to_code(config):
|
||||
|
||||
if CORE.using_arduino:
|
||||
cg.add_library("WiFi", None)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
"""Validate that RMII pins are not used by other components."""
|
||||
# Only validate for RMII-based PHYs on ESP32/ESP32P4
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH":
|
||||
return # SPI and OPENETH don't use RMII
|
||||
|
||||
variant = get_esp32_variant()
|
||||
if variant == VARIANT_ESP32:
|
||||
rmii_pins = ESP32_RMII_FIXED_PINS
|
||||
is_configurable = False
|
||||
elif variant == VARIANT_ESP32P4:
|
||||
rmii_pins = ESP32P4_RMII_DEFAULT_PINS
|
||||
is_configurable = True
|
||||
else:
|
||||
return # No RMII validation needed for other variants
|
||||
|
||||
# Check all used pins against RMII reserved pins
|
||||
for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values():
|
||||
for pin_path, _, pin_config in pin_list:
|
||||
pin_num = pin_config.get(CONF_NUMBER)
|
||||
if pin_num not in rmii_pins:
|
||||
continue
|
||||
# Found a conflict - show helpful error message
|
||||
pin_function = rmii_pins[pin_num]
|
||||
component_path = ".".join(str(p) for p in pin_path)
|
||||
if is_configurable:
|
||||
error_msg = (
|
||||
f"GPIO{pin_num} is used by Ethernet RMII "
|
||||
f"({pin_function}) with the current default "
|
||||
f"configuration. This conflicts with '{component_path}'. "
|
||||
f"Please choose a different GPIO pin for "
|
||||
f"'{component_path}'."
|
||||
)
|
||||
else:
|
||||
error_msg = (
|
||||
f"GPIO{pin_num} is reserved for Ethernet RMII "
|
||||
f"({pin_function}) and cannot be used. This pin is "
|
||||
f"hardcoded by ESP-IDF and cannot be changed when using "
|
||||
f"RMII Ethernet PHYs. Please choose a different GPIO pin "
|
||||
f"for '{component_path}'."
|
||||
)
|
||||
raise cv.Invalid(error_msg, path=pin_path)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
"""Final validation for Ethernet component."""
|
||||
_final_validate_spi(config)
|
||||
_final_validate_rmii_pins(config)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -8,19 +8,12 @@ namespace event {
|
||||
static const char *const TAG = "event";
|
||||
|
||||
void Event::trigger(const std::string &event_type) {
|
||||
// Linear search - faster than std::set for small datasets (1-5 items typical)
|
||||
const std::string *found = nullptr;
|
||||
for (const auto &type : this->types_) {
|
||||
if (type == event_type) {
|
||||
found = &type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == nullptr) {
|
||||
auto found = types_.find(event_type);
|
||||
if (found == types_.end()) {
|
||||
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
|
||||
return;
|
||||
}
|
||||
last_event_type = found;
|
||||
last_event_type = &(*found);
|
||||
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
|
||||
this->event_callback_.call(event_type);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
@@ -25,13 +26,13 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
const std::string *last_event_type;
|
||||
|
||||
void trigger(const std::string &event_type);
|
||||
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
|
||||
const FixedVector<std::string> &get_event_types() const { return this->types_; }
|
||||
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
|
||||
std::set<std::string> get_event_types() const { return this->types_; }
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<std::string> types_;
|
||||
std::set<std::string> types_;
|
||||
};
|
||||
|
||||
} // namespace event
|
||||
|
||||
@@ -67,7 +67,7 @@ void GPIOSwitch::write_state(bool state) {
|
||||
this->pin_->digital_write(state);
|
||||
this->publish_state(state);
|
||||
}
|
||||
void GPIOSwitch::set_interlock(const std::initializer_list<Switch *> &interlock) { this->interlock_ = interlock; }
|
||||
void GPIOSwitch::set_interlock(const std::vector<Switch *> &interlock) { this->interlock_ = interlock; }
|
||||
|
||||
} // namespace gpio
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
@@ -18,14 +19,14 @@ class GPIOSwitch : public switch_::Switch, public Component {
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_interlock(const std::initializer_list<Switch *> &interlock);
|
||||
void set_interlock(const std::vector<Switch *> &interlock);
|
||||
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
|
||||
GPIOPin *pin_;
|
||||
FixedVector<Switch *> interlock_;
|
||||
std::vector<Switch *> interlock_;
|
||||
uint32_t interlock_wait_time_{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@optimusprimespace", "@ssieb"]
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "hdc2010.h"
|
||||
// https://github.com/vigsterkr/homebridge-hdc2010/blob/main/src/hdc2010.js
|
||||
// https://github.com/lime-labs/HDC2080-Arduino/blob/master/src/HDC2080.cpp
|
||||
namespace esphome {
|
||||
namespace hdc2010 {
|
||||
|
||||
static const char *const TAG = "hdc2010";
|
||||
|
||||
static const uint8_t HDC2010_ADDRESS = 0x40; // 0b1000000 or 0b1000001 from datasheet
|
||||
static const uint8_t HDC2010_CMD_CONFIGURATION_MEASUREMENT = 0x8F;
|
||||
static const uint8_t HDC2010_CMD_START_MEASUREMENT = 0xF9;
|
||||
static const uint8_t HDC2010_CMD_TEMPERATURE_LOW = 0x00;
|
||||
static const uint8_t HDC2010_CMD_TEMPERATURE_HIGH = 0x01;
|
||||
static const uint8_t HDC2010_CMD_HUMIDITY_LOW = 0x02;
|
||||
static const uint8_t HDC2010_CMD_HUMIDITY_HIGH = 0x03;
|
||||
static const uint8_t CONFIG = 0x0E;
|
||||
static const uint8_t MEASUREMENT_CONFIG = 0x0F;
|
||||
|
||||
void HDC2010Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
|
||||
const uint8_t data[2] = {
|
||||
0b00000000, // resolution 14bit for both humidity and temperature
|
||||
0b00000000 // reserved
|
||||
};
|
||||
|
||||
if (!this->write_bytes(HDC2010_CMD_CONFIGURATION_MEASUREMENT, data, 2)) {
|
||||
ESP_LOGW(TAG, "Initial config instruction error");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set measurement mode to temperature and humidity
|
||||
uint8_t config_contents;
|
||||
this->read_register(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||
config_contents = (config_contents & 0xF9); // Always set to TEMP_AND_HUMID mode
|
||||
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||
|
||||
// Set rate to manual
|
||||
this->read_register(CONFIG, &config_contents, 1);
|
||||
config_contents &= 0x8F;
|
||||
this->write_bytes(CONFIG, &config_contents, 1);
|
||||
|
||||
// Set temperature resolution to 14bit
|
||||
this->read_register(CONFIG, &config_contents, 1);
|
||||
config_contents &= 0x3F;
|
||||
this->write_bytes(CONFIG, &config_contents, 1);
|
||||
|
||||
// Set humidity resolution to 14bit
|
||||
this->read_register(CONFIG, &config_contents, 1);
|
||||
config_contents &= 0xCF;
|
||||
this->write_bytes(CONFIG, &config_contents, 1);
|
||||
}
|
||||
|
||||
void HDC2010Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HDC2010:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
void HDC2010Component::update() {
|
||||
// Trigger measurement
|
||||
uint8_t config_contents;
|
||||
this->read_register(CONFIG, &config_contents, 1);
|
||||
config_contents |= 0x01;
|
||||
this->write_bytes(MEASUREMENT_CONFIG, &config_contents, 1);
|
||||
|
||||
// 1ms delay after triggering the sample
|
||||
set_timeout(1, [this]() {
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
float temp = this->read_temp();
|
||||
this->temperature_sensor_->publish_state(temp);
|
||||
ESP_LOGD(TAG, "Temp=%.1f°C", temp);
|
||||
}
|
||||
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
float humidity = this->read_humidity();
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
ESP_LOGD(TAG, "Humidity=%.1f%%", humidity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
float HDC2010Component::read_temp() {
|
||||
uint8_t byte[2];
|
||||
|
||||
this->read_register(HDC2010_CMD_TEMPERATURE_LOW, &byte[0], 1);
|
||||
this->read_register(HDC2010_CMD_TEMPERATURE_HIGH, &byte[1], 1);
|
||||
|
||||
uint16_t temp = encode_uint16(byte[1], byte[0]);
|
||||
return (float) temp * 0.0025177f - 40.0f;
|
||||
}
|
||||
|
||||
float HDC2010Component::read_humidity() {
|
||||
uint8_t byte[2];
|
||||
|
||||
this->read_register(HDC2010_CMD_HUMIDITY_LOW, &byte[0], 1);
|
||||
this->read_register(HDC2010_CMD_HUMIDITY_HIGH, &byte[1], 1);
|
||||
|
||||
uint16_t humidity = encode_uint16(byte[1], byte[0]);
|
||||
return (float) humidity * 0.001525879f;
|
||||
}
|
||||
|
||||
} // namespace hdc2010
|
||||
} // namespace esphome
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hdc2010 {
|
||||
|
||||
class HDC2010Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
|
||||
|
||||
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
|
||||
|
||||
/// Setup the sensor and check for connection.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// Retrieve the latest sensor values. This operation takes approximately 16ms.
|
||||
void update() override;
|
||||
|
||||
float read_temp();
|
||||
|
||||
float read_humidity();
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace hdc2010
|
||||
} // namespace esphome
|
||||
@@ -1,56 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
hdc2010_ns = cg.esphome_ns.namespace("hdc2010")
|
||||
HDC2010Component = hdc2010_ns.class_(
|
||||
"HDC2010Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HDC2010Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
@@ -28,8 +28,8 @@ class HostGPIOPin : public InternalGPIOPin {
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_{};
|
||||
gpio::Flags flags_{};
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace host
|
||||
|
||||
@@ -57,9 +57,6 @@ async def host_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -169,7 +169,7 @@ class HttpRequestComponent : public Component {
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
|
||||
const std::string &body, const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) = 0;
|
||||
std::set<std::string> collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
|
||||
@@ -17,7 +17,7 @@ static const char *const TAG = "http_request.arduino";
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -33,7 +33,7 @@ class HttpRequestArduino : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
std::set<std::string> collect_headers) override;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
@@ -20,7 +20,7 @@ static const char *const TAG = "http_request.host";
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) {
|
||||
std::set<std::string> response_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) override;
|
||||
std::set<std::string> response_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
|
||||
@@ -39,7 +39,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
std::set<std::string> collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
uint16_t buffer_size_tx_{};
|
||||
|
||||
@@ -3,8 +3,6 @@ import re
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import __version__
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
@@ -37,9 +35,7 @@ def _process_next_url(url: str):
|
||||
return url
|
||||
|
||||
|
||||
async def setup_improv_core(var: MockObj, config: ConfigType, component: str):
|
||||
if next_url := config.get(CONF_NEXT_URL):
|
||||
cg.add(var.set_next_url(_process_next_url(next_url)))
|
||||
cg.add_define(f"USE_{component.upper()}_NEXT_URL")
|
||||
|
||||
async def setup_improv_core(var, config):
|
||||
if CONF_NEXT_URL in config:
|
||||
cg.add(var.set_next_url(_process_next_url(config[CONF_NEXT_URL])))
|
||||
cg.add_library("improv/Improv", "1.2.4")
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_base {
|
||||
|
||||
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
|
||||
static constexpr const char DEVICE_NAME_PLACEHOLDER[] = "{{device_name}}";
|
||||
static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHOLDER) - 1;
|
||||
static constexpr const char IP_ADDRESS_PLACEHOLDER[] = "{{ip_address}}";
|
||||
@@ -45,7 +43,6 @@ std::string ImprovBase::get_formatted_next_url_() {
|
||||
|
||||
return formatted_url;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace improv_base
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_base {
|
||||
|
||||
class ImprovBase {
|
||||
public:
|
||||
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
|
||||
void set_next_url(const std::string &next_url) { this->next_url_ = next_url; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#if defined(USE_ESP32_IMPROV_NEXT_URL) || defined(USE_IMPROV_SERIAL_NEXT_URL)
|
||||
std::string get_formatted_next_url_();
|
||||
std::string next_url_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace improv_base
|
||||
|
||||
@@ -43,4 +43,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await improv_base.setup_improv_core(var, config, "improv_serial")
|
||||
await improv_base.setup_improv_core(var, config)
|
||||
|
||||
@@ -28,38 +28,6 @@ void ImprovSerialComponent::setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::loop() {
|
||||
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
|
||||
this->last_read_byte_ = 0;
|
||||
this->rx_buffer_.clear();
|
||||
ESP_LOGV(TAG, "Timeout");
|
||||
}
|
||||
|
||||
auto byte = this->read_byte_();
|
||||
while (byte.has_value()) {
|
||||
if (this->parse_improv_serial_byte_(byte.value())) {
|
||||
this->last_read_byte_ = millis();
|
||||
} else {
|
||||
this->last_read_byte_ = 0;
|
||||
this->rx_buffer_.clear();
|
||||
}
|
||||
byte = this->read_byte_();
|
||||
}
|
||||
|
||||
if (this->state_ == improv::STATE_PROVISIONING) {
|
||||
if (wifi::global_wifi_component->is_connected()) {
|
||||
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
|
||||
this->connecting_sta_.get_password());
|
||||
this->connecting_sta_ = {};
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->set_state_(improv::STATE_PROVISIONED);
|
||||
|
||||
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
|
||||
this->send_response_(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
|
||||
|
||||
optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||
@@ -110,28 +78,8 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||
return byte;
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) {
|
||||
// First, set length field
|
||||
this->tx_header_[TX_LENGTH_IDX] = this->tx_header_[TX_TYPE_IDX] == TYPE_RPC_RESPONSE ? size : 1;
|
||||
|
||||
const bool there_is_data = data != nullptr && size > 0;
|
||||
// If there_is_data, checksum must not include our optional data byte
|
||||
const uint8_t header_checksum_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE - 2;
|
||||
// Only transmit the full buffer length if there is no data (only state/error byte is provided in this case)
|
||||
const uint8_t header_tx_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE;
|
||||
// Calculate checksum for message
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 0; i < header_checksum_len; i++) {
|
||||
checksum += this->tx_header_[i];
|
||||
}
|
||||
if (there_is_data) {
|
||||
// Include data in checksum
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
}
|
||||
this->tx_header_[TX_CHECKSUM_IDX] = checksum;
|
||||
|
||||
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
|
||||
data.push_back('\n');
|
||||
#ifdef USE_ESP32
|
||||
switch (logger::global_logger->get_uart()) {
|
||||
case logger::UART_SELECTION_UART0:
|
||||
@@ -139,52 +87,68 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case logger::UART_SELECTION_UART2:
|
||||
#endif
|
||||
uart_write_bytes(this->uart_num_, this->tx_header_, header_tx_len);
|
||||
if (there_is_data) {
|
||||
uart_write_bytes(this->uart_num_, data, size);
|
||||
uart_write_bytes(this->uart_num_, &this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
|
||||
}
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
|
||||
uart_write_bytes(this->uart_num_, data.data(), data.size());
|
||||
break;
|
||||
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||
case logger::UART_SELECTION_USB_CDC:
|
||||
esp_usb_console_write_buf((const char *) this->tx_header_, header_tx_len);
|
||||
if (there_is_data) {
|
||||
esp_usb_console_write_buf((const char *) data, size);
|
||||
esp_usb_console_write_buf((const char *) &this->tx_header_[TX_CHECKSUM_IDX],
|
||||
2); // Footer: checksum and newline
|
||||
}
|
||||
case logger::UART_SELECTION_USB_CDC: {
|
||||
const char *msg = (char *) data.data();
|
||||
esp_usb_console_write_buf(msg, data.size());
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
#endif // USE_LOGGER_USB_CDC
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case logger::UART_SELECTION_USB_SERIAL_JTAG:
|
||||
usb_serial_jtag_write_bytes((const char *) this->tx_header_, header_tx_len, 20 / portTICK_PERIOD_MS);
|
||||
if (there_is_data) {
|
||||
usb_serial_jtag_write_bytes((const char *) data, size, 20 / portTICK_PERIOD_MS);
|
||||
usb_serial_jtag_write_bytes((const char *) &this->tx_header_[TX_CHECKSUM_IDX], 2,
|
||||
20 / portTICK_PERIOD_MS); // Footer: checksum and newline
|
||||
}
|
||||
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
|
||||
delay(10);
|
||||
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
|
||||
break;
|
||||
#endif
|
||||
#endif // USE_LOGGER_USB_SERIAL_JTAG
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#elif defined(USE_ARDUINO)
|
||||
this->hw_serial_->write(this->tx_header_, header_tx_len);
|
||||
if (there_is_data) {
|
||||
this->hw_serial_->write(data, size);
|
||||
this->hw_serial_->write(&this->tx_header_[TX_CHECKSUM_IDX], 2); // Footer: checksum and newline
|
||||
}
|
||||
this->hw_serial_->write(data.data(), data.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::loop() {
|
||||
if (this->last_read_byte_ && (millis() - this->last_read_byte_ > IMPROV_SERIAL_TIMEOUT)) {
|
||||
this->last_read_byte_ = 0;
|
||||
this->rx_buffer_.clear();
|
||||
ESP_LOGV(TAG, "Improv Serial timeout");
|
||||
}
|
||||
|
||||
auto byte = this->read_byte_();
|
||||
while (byte.has_value()) {
|
||||
if (this->parse_improv_serial_byte_(byte.value())) {
|
||||
this->last_read_byte_ = millis();
|
||||
} else {
|
||||
this->last_read_byte_ = 0;
|
||||
this->rx_buffer_.clear();
|
||||
}
|
||||
byte = this->read_byte_();
|
||||
}
|
||||
|
||||
if (this->state_ == improv::STATE_PROVISIONING) {
|
||||
if (wifi::global_wifi_component->is_connected()) {
|
||||
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
|
||||
this->connecting_sta_.get_password());
|
||||
this->connecting_sta_ = {};
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->set_state_(improv::STATE_PROVISIONED);
|
||||
|
||||
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
|
||||
this->send_response_(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
|
||||
std::vector<std::string> urls;
|
||||
#ifdef USE_IMPROV_SERIAL_NEXT_URL
|
||||
if (!this->next_url_.empty()) {
|
||||
urls.push_back(this->get_formatted_next_url_());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
||||
if (ip.is_ip4()) {
|
||||
@@ -211,13 +175,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
|
||||
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
|
||||
size_t at = this->rx_buffer_.size();
|
||||
this->rx_buffer_.push_back(byte);
|
||||
ESP_LOGV(TAG, "Byte: 0x%02X", byte);
|
||||
ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
|
||||
const uint8_t *raw = &this->rx_buffer_[0];
|
||||
|
||||
return improv::parse_improv_serial_byte(
|
||||
at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
|
||||
[this](improv::Error error) -> void {
|
||||
ESP_LOGW(TAG, "Error decoding payload");
|
||||
ESP_LOGW(TAG, "Error decoding Improv payload");
|
||||
this->set_error_(error);
|
||||
});
|
||||
}
|
||||
@@ -233,7 +197,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
|
||||
@@ -274,7 +238,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGW(TAG, "Unknown payload");
|
||||
ESP_LOGW(TAG, "Unknown Improv payload");
|
||||
this->set_error_(improv::ERROR_UNKNOWN_RPC);
|
||||
return false;
|
||||
}
|
||||
@@ -283,26 +247,57 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
|
||||
void ImprovSerialComponent::set_state_(improv::State state) {
|
||||
this->state_ = state;
|
||||
this->tx_header_[TX_TYPE_IDX] = TYPE_CURRENT_STATE;
|
||||
this->tx_header_[TX_DATA_IDX] = state;
|
||||
this->write_data_();
|
||||
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_CURRENT_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = state;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::set_error_(improv::Error error) {
|
||||
this->tx_header_[TX_TYPE_IDX] = TYPE_ERROR_STATE;
|
||||
this->tx_header_[TX_DATA_IDX] = error;
|
||||
this->write_data_();
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_ERROR_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = error;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
|
||||
this->tx_header_[TX_TYPE_IDX] = TYPE_RPC_RESPONSE;
|
||||
this->write_data_(response.data(), response.size());
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(9);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_RPC_RESPONSE;
|
||||
data[8] = response.size();
|
||||
data.insert(data.end(), response.begin(), response.end());
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data.push_back(checksum);
|
||||
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::on_wifi_connect_timeout_() {
|
||||
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
|
||||
this->set_state_(improv::STATE_AUTHORIZED);
|
||||
ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
|
||||
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
|
||||
wifi::global_wifi_component->clear_sta();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,16 +26,6 @@
|
||||
namespace esphome {
|
||||
namespace improv_serial {
|
||||
|
||||
// TX buffer layout constants
|
||||
static constexpr uint8_t TX_HEADER_SIZE = 6; // Bytes 0-5 = "IMPROV"
|
||||
static constexpr uint8_t TX_VERSION_IDX = 6;
|
||||
static constexpr uint8_t TX_TYPE_IDX = 7;
|
||||
static constexpr uint8_t TX_LENGTH_IDX = 8;
|
||||
static constexpr uint8_t TX_DATA_IDX = 9; // For state/error messages only
|
||||
static constexpr uint8_t TX_CHECKSUM_IDX = 10;
|
||||
static constexpr uint8_t TX_NEWLINE_IDX = 11;
|
||||
static constexpr uint8_t TX_BUFFER_SIZE = 12;
|
||||
|
||||
enum ImprovSerialType : uint8_t {
|
||||
TYPE_CURRENT_STATE = 0x01,
|
||||
TYPE_ERROR_STATE = 0x02,
|
||||
@@ -67,22 +57,7 @@ class ImprovSerialComponent : public Component, public improv_base::ImprovBase {
|
||||
std::vector<uint8_t> build_version_info_();
|
||||
|
||||
optional<uint8_t> read_byte_();
|
||||
void write_data_(const uint8_t *data = nullptr, size_t size = 0);
|
||||
|
||||
uint8_t tx_header_[TX_BUFFER_SIZE] = {
|
||||
'I', // 0: Header
|
||||
'M', // 1: Header
|
||||
'P', // 2: Header
|
||||
'R', // 3: Header
|
||||
'O', // 4: Header
|
||||
'V', // 5: Header
|
||||
IMPROV_SERIAL_VERSION, // 6: Version
|
||||
0, // 7: ImprovSerialType
|
||||
0, // 8: Length
|
||||
0, // 9...X: Data (here, one byte reserved for state/error)
|
||||
0, // X + 10: Checksum
|
||||
'\n',
|
||||
};
|
||||
void write_data_(std::vector<uint8_t> &data);
|
||||
|
||||
#ifdef USE_ESP32
|
||||
uart_port_t uart_num_;
|
||||
|
||||
@@ -199,9 +199,6 @@ async def component_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -27,8 +27,8 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_{};
|
||||
gpio::Flags flags_{};
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace libretiny
|
||||
|
||||
@@ -61,12 +61,8 @@ void AddressableLightTransformer::start() {
|
||||
this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
|
||||
}
|
||||
|
||||
inline constexpr uint8_t subtract_scaled_difference(uint8_t a, uint8_t b, int32_t scale) {
|
||||
return uint8_t(int32_t(a) - (((int32_t(a) - int32_t(b)) * scale) / 256));
|
||||
}
|
||||
|
||||
optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||
float smoothed_progress = LightTransformer::smoothed_progress(this->get_progress_());
|
||||
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
|
||||
|
||||
// When running an output-buffer modifying effect, don't try to transition individual LEDs, but instead just fade the
|
||||
// LightColorValues. write_state() then picks up the change in brightness, and the color change is picked up by the
|
||||
@@ -78,37 +74,38 @@ optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||
// all LEDs, we use the current state of each LED as the start.
|
||||
|
||||
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
|
||||
// state of each LED at the start of the transition. Instead, we "fake" the look of lerp by calculating
|
||||
// the delta between the current state and the target state, assuming that the delta represents the rest
|
||||
// of the transition that was to be applied as of the previous transition step, and scaling the delta for
|
||||
// what should be left after the current transition step. In this manner, the delta decays to zero as the
|
||||
// transition progresses.
|
||||
//
|
||||
// Here's an example of how the algorithm progresses in discrete steps:
|
||||
//
|
||||
// At time = 0.00, 0% complete, 100% remaining, 100% will remain after this step, so the scale is 100% / 100% = 100%.
|
||||
// At time = 0.10, 0% complete, 100% remaining, 90% will remain after this step, so the scale is 90% / 100% = 90%.
|
||||
// At time = 0.20, 10% complete, 90% remaining, 80% will remain after this step, so the scale is 80% / 90% = 88.9%.
|
||||
// At time = 0.50, 20% complete, 80% remaining, 50% will remain after this step, so the scale is 50% / 80% = 62.5%.
|
||||
// At time = 0.90, 50% complete, 50% remaining, 10% will remain after this step, so the scale is 10% / 50% = 20%.
|
||||
// At time = 0.91, 90% complete, 10% remaining, 9% will remain after this step, so the scale is 9% / 10% = 90%.
|
||||
// At time = 1.00, 91% complete, 9% remaining, 0% will remain after this step, so the scale is 0% / 9% = 0%.
|
||||
//
|
||||
// Because the color values are quantized to 8 bit resolution after each step, the transition may appear
|
||||
// non-linear when applying small deltas.
|
||||
// state of each LED at the start of the transition.
|
||||
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
|
||||
// dynamically-calculated alpha values to match the look.
|
||||
|
||||
if (smoothed_progress > this->last_transition_progress_ && this->last_transition_progress_ < 1.f) {
|
||||
int32_t scale = int32_t(256.f * std::max((1.f - smoothed_progress) / (1.f - this->last_transition_progress_), 0.f));
|
||||
for (auto led : this->light_) {
|
||||
led.set_rgbw(subtract_scaled_difference(this->target_color_.red, led.get_red(), scale),
|
||||
subtract_scaled_difference(this->target_color_.green, led.get_green(), scale),
|
||||
subtract_scaled_difference(this->target_color_.blue, led.get_blue(), scale),
|
||||
subtract_scaled_difference(this->target_color_.white, led.get_white(), scale));
|
||||
}
|
||||
this->last_transition_progress_ = smoothed_progress;
|
||||
this->light_.schedule_show();
|
||||
float denom = (1.0f - smoothed_progress);
|
||||
float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||
|
||||
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
||||
// We solve this by accumulating the fractional part of the alpha over time.
|
||||
float alpha255 = alpha * 255.0f;
|
||||
float alpha255int = floorf(alpha255);
|
||||
float alpha255remainder = alpha255 - alpha255int;
|
||||
|
||||
this->accumulated_alpha_ += alpha255remainder;
|
||||
float alpha_add = floorf(this->accumulated_alpha_);
|
||||
this->accumulated_alpha_ -= alpha_add;
|
||||
|
||||
alpha255 += alpha_add;
|
||||
alpha255 = clamp(alpha255, 0.0f, 255.0f);
|
||||
auto alpha8 = static_cast<uint8_t>(alpha255);
|
||||
|
||||
if (alpha8 != 0) {
|
||||
uint8_t inv_alpha8 = 255 - alpha8;
|
||||
Color add = this->target_color_ * alpha8;
|
||||
|
||||
for (auto led : this->light_)
|
||||
led.set(add + led.get() * inv_alpha8);
|
||||
}
|
||||
|
||||
this->last_transition_progress_ = smoothed_progress;
|
||||
this->light_.schedule_show();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "light_output.h"
|
||||
#include "light_state.h"
|
||||
#include "light_transformer.h"
|
||||
#include "transformers.h"
|
||||
|
||||
#ifdef USE_POWER_SUPPLY
|
||||
#include "esphome/components/power_supply/power_supply.h"
|
||||
@@ -103,7 +103,7 @@ class AddressableLight : public LightOutput, public Component {
|
||||
bool effect_active_{false};
|
||||
};
|
||||
|
||||
class AddressableLightTransformer : public LightTransformer {
|
||||
class AddressableLightTransformer : public LightTransitionTransformer {
|
||||
public:
|
||||
AddressableLightTransformer(AddressableLight &light) : light_(light) {}
|
||||
|
||||
@@ -113,6 +113,7 @@ class AddressableLightTransformer : public LightTransformer {
|
||||
protected:
|
||||
AddressableLight &light_;
|
||||
float last_transition_progress_{0.0f};
|
||||
float accumulated_alpha_{0.0f};
|
||||
Color target_color_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/light/light_state.h"
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
|
||||
@@ -30,7 +30,7 @@ inline static uint8_t half_sin8(uint8_t v) { return sin16_c(uint16_t(v) * 128u)
|
||||
|
||||
class AddressableLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit AddressableLightEffect(const char *name) : LightEffect(name) {}
|
||||
explicit AddressableLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
void start_internal() override {
|
||||
this->get_addressable_()->set_effect_active(true);
|
||||
this->get_addressable_()->clear_effect_data();
|
||||
@@ -57,7 +57,8 @@ class AddressableLightEffect : public LightEffect {
|
||||
|
||||
class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
public:
|
||||
AddressableLambdaLightEffect(const char *name, std::function<void(AddressableLight &, Color, bool initial_run)> f,
|
||||
AddressableLambdaLightEffect(const std::string &name,
|
||||
std::function<void(AddressableLight &, Color, bool initial_run)> f,
|
||||
uint32_t update_interval)
|
||||
: AddressableLightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
|
||||
void start() override { this->initial_run_ = true; }
|
||||
@@ -80,7 +81,7 @@ class AddressableLambdaLightEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableRainbowLightEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableRainbowLightEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableRainbowLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
ESPHSVColor hsv;
|
||||
hsv.value = 255;
|
||||
@@ -111,8 +112,8 @@ struct AddressableColorWipeEffectColor {
|
||||
|
||||
class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableColorWipeEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
void set_colors(const std::initializer_list<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
|
||||
explicit AddressableColorWipeEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void set_colors(const std::vector<AddressableColorWipeEffectColor> &colors) { this->colors_ = colors; }
|
||||
void set_add_led_interval(uint32_t add_led_interval) { this->add_led_interval_ = add_led_interval; }
|
||||
void set_reverse(bool reverse) { this->reverse_ = reverse; }
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
@@ -154,7 +155,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||
}
|
||||
|
||||
protected:
|
||||
FixedVector<AddressableColorWipeEffectColor> colors_;
|
||||
std::vector<AddressableColorWipeEffectColor> colors_;
|
||||
size_t at_color_{0};
|
||||
uint32_t last_add_{0};
|
||||
uint32_t add_led_interval_{};
|
||||
@@ -164,7 +165,7 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableScanEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableScanEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
|
||||
void set_scan_width(uint32_t scan_width) { this->scan_width_ = scan_width; }
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
@@ -201,7 +202,7 @@ class AddressableScanEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableTwinkleEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &addressable, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
uint8_t pos_add = 0;
|
||||
@@ -243,7 +244,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableRandomTwinkleEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableRandomTwinkleEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableRandomTwinkleEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
uint8_t pos_add = 0;
|
||||
@@ -292,7 +293,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableFireworksEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableFireworksEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void start() override {
|
||||
auto &it = *this->get_addressable_();
|
||||
it.all() = Color::BLACK;
|
||||
@@ -341,7 +342,7 @@ class AddressableFireworksEffect : public AddressableLightEffect {
|
||||
|
||||
class AddressableFlickerEffect : public AddressableLightEffect {
|
||||
public:
|
||||
explicit AddressableFlickerEffect(const char *name) : AddressableLightEffect(name) {}
|
||||
explicit AddressableFlickerEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
void apply(AddressableLight &it, const Color ¤t_color) override {
|
||||
const uint32_t now = millis();
|
||||
const uint8_t intensity = this->intensity_;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "light_effect.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +17,7 @@ inline static float random_cubic_float() {
|
||||
/// Pulse effect.
|
||||
class PulseLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit PulseLightEffect(const char *name) : LightEffect(name) {}
|
||||
explicit PulseLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
@@ -60,7 +60,7 @@ class PulseLightEffect : public LightEffect {
|
||||
/// Random effect. Sets random colors every 10 seconds and slowly transitions between them.
|
||||
class RandomLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit RandomLightEffect(const char *name) : LightEffect(name) {}
|
||||
explicit RandomLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
@@ -112,7 +112,7 @@ class RandomLightEffect : public LightEffect {
|
||||
|
||||
class LambdaLightEffect : public LightEffect {
|
||||
public:
|
||||
LambdaLightEffect(const char *name, std::function<void(bool initial_run)> f, uint32_t update_interval)
|
||||
LambdaLightEffect(const std::string &name, std::function<void(bool initial_run)> f, uint32_t update_interval)
|
||||
: LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {}
|
||||
|
||||
void start() override { this->initial_run_ = true; }
|
||||
@@ -138,7 +138,7 @@ class LambdaLightEffect : public LightEffect {
|
||||
|
||||
class AutomationLightEffect : public LightEffect {
|
||||
public:
|
||||
AutomationLightEffect(const char *name) : LightEffect(name) {}
|
||||
AutomationLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
void stop() override { this->trig_->stop_action(); }
|
||||
void apply() override {
|
||||
if (!this->trig_->is_action_running()) {
|
||||
@@ -163,7 +163,7 @@ struct StrobeLightEffectColor {
|
||||
|
||||
class StrobeLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit StrobeLightEffect(const char *name) : LightEffect(name) {}
|
||||
explicit StrobeLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
void apply() override {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_switch_ < this->colors_[this->at_color_].duration)
|
||||
@@ -188,17 +188,17 @@ class StrobeLightEffect : public LightEffect {
|
||||
this->last_switch_ = now;
|
||||
}
|
||||
|
||||
void set_colors(const std::initializer_list<StrobeLightEffectColor> &colors) { this->colors_ = colors; }
|
||||
void set_colors(const std::vector<StrobeLightEffectColor> &colors) { this->colors_ = colors; }
|
||||
|
||||
protected:
|
||||
FixedVector<StrobeLightEffectColor> colors_;
|
||||
std::vector<StrobeLightEffectColor> colors_;
|
||||
uint32_t last_switch_{0};
|
||||
size_t at_color_{0};
|
||||
};
|
||||
|
||||
class FlickerLightEffect : public LightEffect {
|
||||
public:
|
||||
explicit FlickerLightEffect(const char *name) : LightEffect(name) {}
|
||||
explicit FlickerLightEffect(const std::string &name) : LightEffect(name) {}
|
||||
|
||||
void apply() override {
|
||||
LightColorValues remote = this->state_->remote_values;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "esphome/core/finite_set_mask.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
@@ -108,9 +107,13 @@ constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) {
|
||||
// Type alias for raw color mode bitmask values
|
||||
using color_mode_bitmask_t = uint16_t;
|
||||
|
||||
// Lookup table for ColorMode bit mapping
|
||||
// This array defines the canonical order of color modes (bit 0-9)
|
||||
constexpr ColorMode COLOR_MODE_LOOKUP[] = {
|
||||
// Constants for ColorMode count and bit range
|
||||
static constexpr int COLOR_MODE_COUNT = 10; // UNKNOWN through RGB_COLD_WARM_WHITE
|
||||
static constexpr int MAX_BIT_INDEX = sizeof(color_mode_bitmask_t) * 8; // Number of bits in bitmask type
|
||||
|
||||
// Compile-time array of all ColorMode values in declaration order
|
||||
// Bit positions (0-9) map directly to enum declaration order
|
||||
static constexpr ColorMode COLOR_MODES[COLOR_MODE_COUNT] = {
|
||||
ColorMode::UNKNOWN, // bit 0
|
||||
ColorMode::ON_OFF, // bit 1
|
||||
ColorMode::BRIGHTNESS, // bit 2
|
||||
@@ -123,42 +126,33 @@ constexpr ColorMode COLOR_MODE_LOOKUP[] = {
|
||||
ColorMode::RGB_COLD_WARM_WHITE, // bit 9
|
||||
};
|
||||
|
||||
/// Bit mapping policy for ColorMode
|
||||
/// Uses lookup table for non-contiguous enum values
|
||||
struct ColorModeBitPolicy {
|
||||
using mask_t = uint16_t; // 10 bits requires uint16_t
|
||||
static constexpr int MAX_BITS = sizeof(COLOR_MODE_LOOKUP) / sizeof(COLOR_MODE_LOOKUP[0]);
|
||||
|
||||
static constexpr unsigned to_bit(ColorMode mode) {
|
||||
// Linear search through lookup table
|
||||
// Compiler optimizes this to efficient code since array is constexpr
|
||||
for (int i = 0; i < MAX_BITS; ++i) {
|
||||
if (COLOR_MODE_LOOKUP[i] == mode)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
/// Map ColorMode enum values to bit positions (0-9)
|
||||
/// Bit positions follow the enum declaration order
|
||||
static constexpr int mode_to_bit(ColorMode mode) {
|
||||
// Linear search through COLOR_MODES array
|
||||
// Compiler optimizes this to efficient code since array is constexpr
|
||||
for (int i = 0; i < COLOR_MODE_COUNT; ++i) {
|
||||
if (COLOR_MODES[i] == mode)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr ColorMode from_bit(unsigned bit) {
|
||||
return (bit < MAX_BITS) ? COLOR_MODE_LOOKUP[bit] : ColorMode::UNKNOWN;
|
||||
}
|
||||
};
|
||||
|
||||
// Type alias for ColorMode bitmask using policy-based design
|
||||
using ColorModeMask = FiniteSetMask<ColorMode, ColorModeBitPolicy>;
|
||||
|
||||
// Number of ColorCapability enum values
|
||||
constexpr int COLOR_CAPABILITY_COUNT = 6;
|
||||
/// Map bit positions (0-9) to ColorMode enum values
|
||||
/// Bit positions follow the enum declaration order
|
||||
static constexpr ColorMode bit_to_mode(int bit) {
|
||||
// Direct lookup in COLOR_MODES array
|
||||
return (bit >= 0 && bit < COLOR_MODE_COUNT) ? COLOR_MODES[bit] : ColorMode::UNKNOWN;
|
||||
}
|
||||
|
||||
/// Helper to compute capability bitmask at compile time
|
||||
constexpr uint16_t compute_capability_bitmask(ColorCapability capability) {
|
||||
uint16_t mask = 0;
|
||||
static constexpr color_mode_bitmask_t compute_capability_bitmask(ColorCapability capability) {
|
||||
color_mode_bitmask_t mask = 0;
|
||||
uint8_t cap_bit = static_cast<uint8_t>(capability);
|
||||
|
||||
// Check each ColorMode to see if it has this capability
|
||||
constexpr int color_mode_count = sizeof(COLOR_MODE_LOOKUP) / sizeof(COLOR_MODE_LOOKUP[0]);
|
||||
for (int bit = 0; bit < color_mode_count; ++bit) {
|
||||
uint8_t mode_val = static_cast<uint8_t>(COLOR_MODE_LOOKUP[bit]);
|
||||
for (int bit = 0; bit < COLOR_MODE_COUNT; ++bit) {
|
||||
uint8_t mode_val = static_cast<uint8_t>(bit_to_mode(bit));
|
||||
if ((mode_val & cap_bit) != 0) {
|
||||
mask |= (1 << bit);
|
||||
}
|
||||
@@ -166,9 +160,12 @@ constexpr uint16_t compute_capability_bitmask(ColorCapability capability) {
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Number of ColorCapability enum values
|
||||
static constexpr int COLOR_CAPABILITY_COUNT = 6;
|
||||
|
||||
/// Compile-time lookup table mapping ColorCapability to bitmask
|
||||
/// This array is computed at compile time using constexpr
|
||||
constexpr uint16_t CAPABILITY_BITMASKS[] = {
|
||||
static constexpr color_mode_bitmask_t CAPABILITY_BITMASKS[] = {
|
||||
compute_capability_bitmask(ColorCapability::ON_OFF), // 1 << 0
|
||||
compute_capability_bitmask(ColorCapability::BRIGHTNESS), // 1 << 1
|
||||
compute_capability_bitmask(ColorCapability::WHITE), // 1 << 2
|
||||
@@ -177,38 +174,130 @@ constexpr uint16_t CAPABILITY_BITMASKS[] = {
|
||||
compute_capability_bitmask(ColorCapability::RGB), // 1 << 5
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper function to convert a power-of-2 ColorCapability value to an array index for CAPABILITY_BITMASKS
|
||||
* lookup.
|
||||
*
|
||||
* This function maps ColorCapability values (1, 2, 4, 8, 16, 32) to array indices (0, 1, 2, 3, 4, 5).
|
||||
* Used to index into the CAPABILITY_BITMASKS lookup table.
|
||||
*
|
||||
* @param capability A ColorCapability enum value (must be a power of 2).
|
||||
* @return The corresponding array index (0-based).
|
||||
*/
|
||||
inline int capability_to_index(ColorCapability capability) {
|
||||
uint8_t cap_val = static_cast<uint8_t>(capability);
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n))
|
||||
return __builtin_ctz(cap_val);
|
||||
#else
|
||||
// Fallback for compilers without __builtin_ctz
|
||||
int index = 0;
|
||||
while (cap_val > 1) {
|
||||
cap_val >>= 1;
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
#endif
|
||||
}
|
||||
/// Bitmask for storing a set of ColorMode values efficiently.
|
||||
/// Replaces std::set<ColorMode> to eliminate red-black tree overhead (~586 bytes).
|
||||
class ColorModeMask {
|
||||
public:
|
||||
constexpr ColorModeMask() = default;
|
||||
|
||||
/// Check if any mode in the bitmask has a specific capability
|
||||
/// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
|
||||
inline bool has_capability(const ColorModeMask &mask, ColorCapability capability) {
|
||||
// Lookup the pre-computed bitmask for this capability and check intersection with our mask
|
||||
return (mask.get_mask() & CAPABILITY_BITMASKS[capability_to_index(capability)]) != 0;
|
||||
}
|
||||
/// Support initializer list syntax: {ColorMode::RGB, ColorMode::WHITE}
|
||||
constexpr ColorModeMask(std::initializer_list<ColorMode> modes) {
|
||||
for (auto mode : modes) {
|
||||
this->add(mode);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void add(ColorMode mode) { this->mask_ |= (1 << mode_to_bit(mode)); }
|
||||
|
||||
/// Add multiple modes at once using initializer list
|
||||
constexpr void add(std::initializer_list<ColorMode> modes) {
|
||||
for (auto mode : modes) {
|
||||
this->add(mode);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool contains(ColorMode mode) const { return (this->mask_ & (1 << mode_to_bit(mode))) != 0; }
|
||||
|
||||
constexpr size_t size() const {
|
||||
// Count set bits using Brian Kernighan's algorithm
|
||||
// More efficient for sparse bitmasks (typical case: 2-4 modes out of 10)
|
||||
uint16_t n = this->mask_;
|
||||
size_t count = 0;
|
||||
while (n) {
|
||||
n &= n - 1; // Clear the least significant set bit
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
constexpr bool empty() const { return this->mask_ == 0; }
|
||||
|
||||
/// Iterator support for API encoding
|
||||
class Iterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = ColorMode;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const ColorMode *;
|
||||
using reference = ColorMode;
|
||||
|
||||
constexpr Iterator(color_mode_bitmask_t mask, int bit) : mask_(mask), bit_(bit) { advance_to_next_set_bit_(); }
|
||||
|
||||
constexpr ColorMode operator*() const { return bit_to_mode(bit_); }
|
||||
|
||||
constexpr Iterator &operator++() {
|
||||
++bit_;
|
||||
advance_to_next_set_bit_();
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const Iterator &other) const { return bit_ == other.bit_; }
|
||||
|
||||
constexpr bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||
|
||||
private:
|
||||
constexpr void advance_to_next_set_bit_() { bit_ = ColorModeMask::find_next_set_bit(mask_, bit_); }
|
||||
|
||||
color_mode_bitmask_t mask_;
|
||||
int bit_;
|
||||
};
|
||||
|
||||
constexpr Iterator begin() const { return Iterator(mask_, 0); }
|
||||
constexpr Iterator end() const { return Iterator(mask_, MAX_BIT_INDEX); }
|
||||
|
||||
/// Get the raw bitmask value for API encoding
|
||||
constexpr color_mode_bitmask_t get_mask() const { return this->mask_; }
|
||||
|
||||
/// Find the next set bit in a bitmask starting from a given position
|
||||
/// Returns the bit position, or MAX_BIT_INDEX if no more bits are set
|
||||
static constexpr int find_next_set_bit(color_mode_bitmask_t mask, int start_bit) {
|
||||
int bit = start_bit;
|
||||
while (bit < MAX_BIT_INDEX && !(mask & (1 << bit))) {
|
||||
++bit;
|
||||
}
|
||||
return bit;
|
||||
}
|
||||
|
||||
/// Find the first set bit in a bitmask and return the corresponding ColorMode
|
||||
/// Used for optimizing compute_color_mode_() intersection logic
|
||||
static constexpr ColorMode first_mode_from_mask(color_mode_bitmask_t mask) {
|
||||
return bit_to_mode(find_next_set_bit(mask, 0));
|
||||
}
|
||||
|
||||
/// Check if a ColorMode is present in a raw bitmask value
|
||||
/// Useful for checking intersection results without creating a temporary ColorModeMask
|
||||
static constexpr bool mask_contains(color_mode_bitmask_t mask, ColorMode mode) {
|
||||
return (mask & (1 << mode_to_bit(mode))) != 0;
|
||||
}
|
||||
|
||||
/// Check if any mode in the bitmask has a specific capability
|
||||
/// Used for checking if a light supports a capability (e.g., BRIGHTNESS, RGB)
|
||||
bool has_capability(ColorCapability capability) const {
|
||||
// Lookup the pre-computed bitmask for this capability and check intersection with our mask
|
||||
// ColorCapability values: 1, 2, 4, 8, 16, 32 -> array indices: 0, 1, 2, 3, 4, 5
|
||||
// We need to convert the power-of-2 value to an index
|
||||
uint8_t cap_val = static_cast<uint8_t>(capability);
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
// Use compiler intrinsic for efficient bit position lookup (O(1) vs O(log n))
|
||||
int index = __builtin_ctz(cap_val);
|
||||
#else
|
||||
// Fallback for compilers without __builtin_ctz
|
||||
int index = 0;
|
||||
while (cap_val > 1) {
|
||||
cap_val >>= 1;
|
||||
++index;
|
||||
}
|
||||
#endif
|
||||
return (this->mask_ & CAPABILITY_BITMASKS[index]) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Using uint16_t instead of uint32_t for more efficient iteration (fewer bits to scan).
|
||||
// Currently only 10 ColorMode values exist, so 16 bits is sufficient.
|
||||
// Can be changed to uint32_t if more than 16 color modes are needed in the future.
|
||||
// Note: Due to struct padding, uint16_t and uint32_t result in same LightTraits size (12 bytes).
|
||||
color_mode_bitmask_t mask_{0};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
|
||||
@@ -17,19 +17,19 @@ class ESPColorCorrection {
|
||||
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
|
||||
}
|
||||
inline uint8_t color_correct_red(uint8_t red) const ESPHOME_ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8_twice(red, this->max_brightness_.red, this->local_brightness_);
|
||||
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_green(uint8_t green) const ESPHOME_ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8_twice(green, this->max_brightness_.green, this->local_brightness_);
|
||||
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_blue(uint8_t blue) const ESPHOME_ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8_twice(blue, this->max_brightness_.blue, this->local_brightness_);
|
||||
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_white(uint8_t white) const ESPHOME_ALWAYS_INLINE {
|
||||
uint8_t res = esp_scale8_twice(white, this->max_brightness_.white, this->local_brightness_);
|
||||
uint8_t res = esp_scale8(esp_scale8(white, this->max_brightness_.white), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline Color color_uncorrect(Color color) const ESPHOME_ALWAYS_INLINE {
|
||||
|
||||
@@ -156,7 +156,7 @@ void LightCall::perform() {
|
||||
if (this->effect_ == 0u) {
|
||||
effect_s = "None";
|
||||
} else {
|
||||
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name();
|
||||
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str();
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
@@ -437,7 +437,7 @@ ColorMode LightCall::compute_color_mode_() {
|
||||
|
||||
// Use the preferred suitable mode.
|
||||
if (intersection != 0) {
|
||||
ColorMode mode = ColorModeMask::first_value_from_mask(intersection);
|
||||
ColorMode mode = ColorModeMask::first_mode_from_mask(intersection);
|
||||
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
|
||||
LOG_STR_ARG(color_mode_to_human(mode)));
|
||||
return mode;
|
||||
@@ -511,7 +511,7 @@ LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
||||
LightEffect *e = this->parent_->effects_[i];
|
||||
|
||||
if (strcasecmp(effect.c_str(), e->get_name()) == 0) {
|
||||
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
|
||||
this->set_effect(i + 1);
|
||||
found = true;
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -9,7 +11,7 @@ class LightState;
|
||||
|
||||
class LightEffect {
|
||||
public:
|
||||
explicit LightEffect(const char *name) : name_(name) {}
|
||||
explicit LightEffect(std::string name) : name_(std::move(name)) {}
|
||||
|
||||
/// Initialize this LightEffect. Will be called once after creation.
|
||||
virtual void start() {}
|
||||
@@ -22,11 +24,7 @@ class LightEffect {
|
||||
/// Apply this effect. Use the provided state for starting transitions, ...
|
||||
virtual void apply() = 0;
|
||||
|
||||
/**
|
||||
* Returns the name of this effect.
|
||||
* The returned pointer is valid for the lifetime of the program and must not be freed.
|
||||
*/
|
||||
const char *get_name() const { return this->name_; }
|
||||
const std::string &get_name() { return this->name_; }
|
||||
|
||||
/// Internal method called by the LightState when this light effect is registered in it.
|
||||
virtual void init() {}
|
||||
@@ -49,7 +47,7 @@ class LightEffect {
|
||||
|
||||
protected:
|
||||
LightState *state_{nullptr};
|
||||
const char *name_;
|
||||
std::string name_;
|
||||
|
||||
/// Internal method to find this effect's index in the parent light's effect list.
|
||||
uint32_t get_index_in_parent_() const;
|
||||
|
||||
@@ -178,9 +178,12 @@ void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore
|
||||
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
|
||||
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
||||
const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
||||
void LightState::add_effects(const std::initializer_list<LightEffect *> &effects) {
|
||||
void LightState::add_effects(const std::vector<LightEffect *> &effects) {
|
||||
// Called once from Python codegen during setup with all effects from YAML config
|
||||
this->effects_ = effects;
|
||||
this->effects_.init(effects.size());
|
||||
for (auto *effect : effects) {
|
||||
this->effects_.push_back(effect);
|
||||
}
|
||||
}
|
||||
|
||||
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
||||
|
||||
@@ -163,7 +163,7 @@ class LightState : public EntityBase, public Component {
|
||||
const FixedVector<LightEffect *> &get_effects() const;
|
||||
|
||||
/// Add effects for this light state.
|
||||
void add_effects(const std::initializer_list<LightEffect *> &effects);
|
||||
void add_effects(const std::vector<LightEffect *> &effects);
|
||||
|
||||
/// Get the total number of effects available for this light.
|
||||
size_t get_effect_count() const { return this->effects_.size(); }
|
||||
@@ -177,7 +177,7 @@ class LightState : public EntityBase, public Component {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < this->effects_.size(); i++) {
|
||||
if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name()) == 0) {
|
||||
if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name().c_str()) == 0) {
|
||||
return i + 1; // Effects are 1-indexed in active_effect_index_
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ class LightTraits {
|
||||
this->supported_color_modes_ = ColorModeMask(modes);
|
||||
}
|
||||
|
||||
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode) > 0; }
|
||||
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.contains(color_mode); }
|
||||
bool supports_color_capability(ColorCapability color_capability) const {
|
||||
return has_capability(this->supported_color_modes_, color_capability);
|
||||
return this->supported_color_modes_.has_capability(color_capability);
|
||||
}
|
||||
|
||||
float get_min_mireds() const { return this->min_mireds_; }
|
||||
|
||||
@@ -38,10 +38,6 @@ class LightTransformer {
|
||||
const LightColorValues &get_target_values() const { return this->target_values_; }
|
||||
|
||||
protected:
|
||||
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
|
||||
// transition from 0 to 1 on x = [0, 1]
|
||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||
|
||||
/// The progress of this transition, on a scale of 0 to 1.
|
||||
float get_progress_() {
|
||||
uint32_t now = esphome::millis();
|
||||
|
||||
@@ -50,11 +50,15 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
if (this->changing_color_mode_)
|
||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||
|
||||
float v = LightTransformer::smoothed_progress(p);
|
||||
float v = LightTransitionTransformer::smoothed_progress(p);
|
||||
return LightColorValues::lerp(start, end, v);
|
||||
}
|
||||
|
||||
protected:
|
||||
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
|
||||
// transition from 0 to 1 on x = [0, 1]
|
||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||
|
||||
LightColorValues end_values_{};
|
||||
LightColorValues intermediate_values_{};
|
||||
bool changing_color_mode_{false};
|
||||
|
||||
@@ -99,11 +99,7 @@ const std::string &get_use_address() {
|
||||
return wifi::global_wifi_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#ifdef USE_OPENTHREAD
|
||||
return openthread::global_openthread_component->get_use_address();
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD)
|
||||
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI)
|
||||
// Fallback when no network component is defined (e.g., host platform)
|
||||
static const std::string empty;
|
||||
return empty;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
@@ -278,19 +277,3 @@ def upload_program(config: ConfigType, args, host: str) -> bool:
|
||||
raise EsphomeError(f"Upload failed with result: {result}")
|
||||
|
||||
return handled
|
||||
|
||||
|
||||
def show_logs(config: ConfigType, args, devices: list[str]) -> bool:
|
||||
address = devices[0]
|
||||
from .ble_logger import is_mac_address, logger_connect, logger_scan
|
||||
|
||||
if devices[0] == "BLE":
|
||||
ble_device = asyncio.run(logger_scan(CORE.config["esphome"]["name"]))
|
||||
if ble_device:
|
||||
address = ble_device.address
|
||||
else:
|
||||
return True
|
||||
if is_mac_address(address):
|
||||
asyncio.run(logger_connect(address))
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from typing import Final
|
||||
|
||||
from bleak import BleakClient, BleakScanner, BLEDevice
|
||||
from bleak.exc import (
|
||||
BleakCharacteristicNotFoundError,
|
||||
BleakDBusError,
|
||||
BleakDeviceNotFoundError,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NUS_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||
NUS_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||
|
||||
MAC_ADDRESS_PATTERN: Final = re.compile(
|
||||
r"([0-9A-F]{2}[:]){5}[0-9A-F]{2}$", flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
def is_mac_address(value: str) -> bool:
|
||||
return MAC_ADDRESS_PATTERN.match(value)
|
||||
|
||||
|
||||
async def logger_scan(name: str) -> BLEDevice | None:
|
||||
_LOGGER.info("Scanning bluetooth for %s...", name)
|
||||
device = await BleakScanner.find_device_by_name(name)
|
||||
if not device:
|
||||
_LOGGER.error("%s Bluetooth LE device was not found!", name)
|
||||
return device
|
||||
|
||||
|
||||
async def logger_connect(host: str) -> int | None:
|
||||
disconnected_event = asyncio.Event()
|
||||
|
||||
def handle_disconnect(client):
|
||||
disconnected_event.set()
|
||||
|
||||
def handle_rx(_, data: bytearray):
|
||||
print(data.decode("utf-8"), end="")
|
||||
|
||||
_LOGGER.info("Connecting %s...", host)
|
||||
try:
|
||||
async with BleakClient(host, disconnected_callback=handle_disconnect) as client:
|
||||
_LOGGER.info("Connected %s...", host)
|
||||
try:
|
||||
await client.start_notify(NUS_TX_CHAR_UUID, handle_rx)
|
||||
except BleakDBusError as e:
|
||||
_LOGGER.error("Bluetooth LE logger: %s", e)
|
||||
disconnected_event.set()
|
||||
await disconnected_event.wait()
|
||||
except BleakDeviceNotFoundError:
|
||||
_LOGGER.error("Device %s not found", host)
|
||||
return 1
|
||||
except BleakCharacteristicNotFoundError:
|
||||
_LOGGER.error("Device %s has no NUS characteristic", host)
|
||||
return 1
|
||||
@@ -74,9 +74,6 @@ async def nrf52_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -4,14 +4,11 @@ from esphome.components.esp32 import (
|
||||
VARIANT_ESP32H2,
|
||||
add_idf_sdkconfig_option,
|
||||
only_on_variant,
|
||||
require_vfs_select,
|
||||
)
|
||||
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID, CONF_USE_ADDRESS
|
||||
from esphome.core import CORE
|
||||
from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_DEVICE_TYPE,
|
||||
@@ -109,20 +106,6 @@ _CONNECTION_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate(config: ConfigType) -> ConfigType:
|
||||
if CONF_USE_ADDRESS not in config:
|
||||
config[CONF_USE_ADDRESS] = f"{CORE.name}.local"
|
||||
return config
|
||||
|
||||
|
||||
def _require_vfs_select(config):
|
||||
"""Register VFS select requirement during config validation."""
|
||||
# OpenThread uses esp_vfs_eventfd which requires VFS select support
|
||||
require_vfs_select()
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -134,14 +117,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
||||
cv.Optional(CONF_TLV): cv.string_strict,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
}
|
||||
).extend(_CONNECTION_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||
cv.only_with_esp_idf,
|
||||
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
||||
_validate,
|
||||
_require_vfs_select,
|
||||
)
|
||||
|
||||
|
||||
@@ -165,7 +145,6 @@ async def to_code(config):
|
||||
enable_mdns_storage()
|
||||
|
||||
ot = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(ot.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
await cg.register_component(ot, config)
|
||||
|
||||
srp = cg.new_Pvariable(config[CONF_SRP_ID])
|
||||
|
||||
@@ -252,12 +252,6 @@ void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
|
||||
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
|
||||
}
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
const std::string &OpenThreadComponent::get_use_address() const { return this->use_address_; }
|
||||
|
||||
void OpenThreadComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
|
||||
|
||||
} // namespace openthread
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -33,15 +33,11 @@ class OpenThreadComponent : public Component {
|
||||
void on_factory_reset(std::function<void()> callback);
|
||||
void defer_factory_reset_external_callback();
|
||||
|
||||
const std::string &get_use_address() const;
|
||||
void set_use_address(const std::string &use_address);
|
||||
|
||||
protected:
|
||||
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
||||
bool teardown_started_{false};
|
||||
bool teardown_complete_{false};
|
||||
std::function<void()> factory_reset_external_callback_;
|
||||
std::string use_address_;
|
||||
};
|
||||
|
||||
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -38,6 +38,7 @@ void Pipsolar::loop() {
|
||||
}
|
||||
if (this->state_ == STATE_COMMAND_COMPLETE) {
|
||||
if (this->check_incoming_length_(4)) {
|
||||
ESP_LOGD(TAG, "response length for command OK");
|
||||
if (this->check_incoming_crc_()) {
|
||||
// crc ok
|
||||
if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
|
||||
@@ -48,15 +49,15 @@ void Pipsolar::loop() {
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
|
||||
} else {
|
||||
// crc failed
|
||||
// no log message necessary, check_incoming_crc_() logs
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "command %s response length not OK: with length %zu",
|
||||
ESP_LOGD(TAG, "response length for command %s not OK: with length %zu",
|
||||
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
@@ -65,10 +66,46 @@ void Pipsolar::loop() {
|
||||
}
|
||||
|
||||
if (this->state_ == STATE_POLL_CHECKED) {
|
||||
ESP_LOGD(TAG, "poll %s decode", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_response_(this->enabled_polling_commands_[this->last_polling_command_].identifier,
|
||||
(const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
switch (this->enabled_polling_commands_[this->last_polling_command_].identifier) {
|
||||
case POLLING_QPIRI:
|
||||
ESP_LOGD(TAG, "Decode QPIRI");
|
||||
handle_qpiri_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QPIGS:
|
||||
ESP_LOGD(TAG, "Decode QPIGS");
|
||||
handle_qpigs_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QMOD:
|
||||
ESP_LOGD(TAG, "Decode QMOD");
|
||||
handle_qmod_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QFLAG:
|
||||
ESP_LOGD(TAG, "Decode QFLAG");
|
||||
handle_qflag_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QPIWS:
|
||||
ESP_LOGD(TAG, "Decode QPIWS");
|
||||
handle_qpiws_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QT:
|
||||
ESP_LOGD(TAG, "Decode QT");
|
||||
handle_qt_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
case POLLING_QMN:
|
||||
ESP_LOGD(TAG, "Decode QMN");
|
||||
handle_qmn_((const char *) this->read_buffer_);
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
default:
|
||||
this->state_ = STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,8 +113,6 @@ void Pipsolar::loop() {
|
||||
if (this->check_incoming_crc_()) {
|
||||
if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
|
||||
this->read_buffer_[3] == 'K') {
|
||||
ESP_LOGD(TAG, "poll %s NACK", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
this->state_ = STATE_IDLE;
|
||||
return;
|
||||
}
|
||||
@@ -86,9 +121,6 @@ void Pipsolar::loop() {
|
||||
this->state_ = STATE_POLL_CHECKED;
|
||||
return;
|
||||
} else {
|
||||
// crc failed
|
||||
// no log message necessary, check_incoming_crc_() logs
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
this->state_ = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
@@ -126,19 +158,21 @@ void Pipsolar::loop() {
|
||||
// command timeout
|
||||
const char *command = this->command_queue_[this->command_queue_position_].c_str();
|
||||
this->command_start_millis_ = millis();
|
||||
ESP_LOGD(TAG, "command %s timeout", command);
|
||||
ESP_LOGD(TAG, "timeout command from queue: %s", command);
|
||||
this->command_queue_[this->command_queue_position_] = std::string("");
|
||||
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
|
||||
this->state_ = STATE_IDLE;
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (this->state_ == STATE_POLL) {
|
||||
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
|
||||
// command timeout
|
||||
ESP_LOGD(TAG, "poll %s timeout", this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->handle_poll_error_(this->enabled_polling_commands_[this->last_polling_command_].identifier);
|
||||
ESP_LOGD(TAG, "timeout command to poll: %s",
|
||||
this->enabled_polling_commands_[this->last_polling_command_].command);
|
||||
this->state_ = STATE_IDLE;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +187,7 @@ uint8_t Pipsolar::check_incoming_length_(uint8_t length) {
|
||||
uint8_t Pipsolar::check_incoming_crc_() {
|
||||
uint16_t crc16;
|
||||
crc16 = this->pipsolar_crc_(read_buffer_, read_pos_ - 3);
|
||||
ESP_LOGD(TAG, "checking crc on incoming message");
|
||||
if (((uint8_t) ((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
|
||||
((uint8_t) ((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
|
||||
ESP_LOGD(TAG, "CRC OK");
|
||||
@@ -218,7 +253,7 @@ bool Pipsolar::send_next_poll_() {
|
||||
this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
|
||||
// end Byte
|
||||
this->write(0x0D);
|
||||
ESP_LOGD(TAG, "Sending polling command: %s with length %d",
|
||||
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
|
||||
this->enabled_polling_commands_[this->last_polling_command_].command,
|
||||
this->enabled_polling_commands_[this->last_polling_command_].length);
|
||||
return true;
|
||||
@@ -239,38 +274,6 @@ void Pipsolar::queue_command(const std::string &command) {
|
||||
ESP_LOGD(TAG, "Command queue full dropping command: %s", command.c_str());
|
||||
}
|
||||
|
||||
void Pipsolar::handle_poll_response_(ENUMPollingCommand polling_command, const char *message) {
|
||||
switch (polling_command) {
|
||||
case POLLING_QPIRI:
|
||||
handle_qpiri_(message);
|
||||
break;
|
||||
case POLLING_QPIGS:
|
||||
handle_qpigs_(message);
|
||||
break;
|
||||
case POLLING_QMOD:
|
||||
handle_qmod_(message);
|
||||
break;
|
||||
case POLLING_QFLAG:
|
||||
handle_qflag_(message);
|
||||
break;
|
||||
case POLLING_QPIWS:
|
||||
handle_qpiws_(message);
|
||||
break;
|
||||
case POLLING_QT:
|
||||
handle_qt_(message);
|
||||
break;
|
||||
case POLLING_QMN:
|
||||
handle_qmn_(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Pipsolar::handle_poll_error_(ENUMPollingCommand polling_command) {
|
||||
// handlers are designed in a way that an empty message sets all sensors to unknown
|
||||
this->handle_poll_response_(polling_command, "");
|
||||
}
|
||||
|
||||
void Pipsolar::handle_qpiri_(const char *message) {
|
||||
if (this->last_qpiri_) {
|
||||
this->last_qpiri_->publish_state(message);
|
||||
|
||||
@@ -204,9 +204,6 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
|
||||
bool send_next_command_();
|
||||
bool send_next_poll_();
|
||||
|
||||
void handle_poll_response_(ENUMPollingCommand polling_command, const char *message);
|
||||
void handle_poll_error_(ENUMPollingCommand polling_command);
|
||||
// these handlers are designed in a way that an empty message sets all sensors to unknown
|
||||
void handle_qpiri_(const char *message);
|
||||
void handle_qpigs_(const char *message);
|
||||
void handle_qmod_(const char *message);
|
||||
|
||||
@@ -4,18 +4,11 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_BUS_VOLTAGE,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_BATTERY,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_FLASH,
|
||||
ICON_GAUGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
@@ -29,10 +22,6 @@ from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
ICON_SOLAR_POWER = "mdi:solar-power"
|
||||
ICON_SOLAR_PANEL = "mdi:solar-panel"
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
|
||||
# QPIRI sensors
|
||||
CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage"
|
||||
CONF_GRID_RATING_CURRENT = "grid_rating_current"
|
||||
@@ -86,19 +75,16 @@ TYPES = {
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_GRID_RATING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
@@ -112,12 +98,11 @@ TYPES = {
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
),
|
||||
CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema(
|
||||
@@ -146,151 +131,124 @@ TYPES = {
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_BATTERY_TYPE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
),
|
||||
CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
),
|
||||
CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PARALLEL_MAX_NUM: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_MACHINE_TYPE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_TOPOLOGY: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_OUTPUT_MODE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_POWER_BALANCE: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_GRID_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_GRID_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_BUS_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_BATTERY,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_SOLAR_PANEL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_SOLAR_PANEL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
@@ -298,14 +256,12 @@ TYPES = {
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_EEPROM_VERSION: sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
),
|
||||
CONF_PV_CHARGING_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_SOLAR_POWER,
|
||||
accuracy_decimals=0,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -12,25 +12,6 @@
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
// IDF version 5.5.1 and above is required because of a bug in
|
||||
// the RMT encoder: https://github.com/espressif/esp-idf/issues/17244
|
||||
typedef union { // NOLINT(modernize-use-using)
|
||||
struct {
|
||||
uint16_t duration : 15;
|
||||
uint16_t level : 1;
|
||||
};
|
||||
uint16_t val;
|
||||
} rmt_symbol_half_t;
|
||||
|
||||
struct RemoteTransmitterComponentStore {
|
||||
uint32_t times{0};
|
||||
uint32_t index{0};
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
public Component
|
||||
#ifdef USE_ESP32
|
||||
@@ -75,14 +56,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
#ifdef USE_ESP32
|
||||
void configure_rmt_();
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
RemoteTransmitterComponentStore store_{};
|
||||
std::vector<rmt_symbol_half_t> rmt_temp_;
|
||||
#else
|
||||
std::vector<rmt_symbol_word_t> rmt_temp_;
|
||||
#endif
|
||||
uint32_t current_carrier_frequency_{38000};
|
||||
bool initialized_{false};
|
||||
std::vector<rmt_symbol_word_t> rmt_temp_;
|
||||
bool with_dma_{false};
|
||||
bool eot_level_{false};
|
||||
rmt_channel_handle_t channel_{NULL};
|
||||
|
||||
@@ -10,46 +10,6 @@ namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
// Maximum RMT symbol duration (15-bit field)
|
||||
static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
|
||||
rmt_symbol_word_t *symbols, bool *done, void *arg) {
|
||||
auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
|
||||
const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
|
||||
size_t length = size / sizeof(rmt_symbol_half_t);
|
||||
size_t count = 0;
|
||||
|
||||
// copy symbols
|
||||
for (size_t i = 0; i < free; i++) {
|
||||
uint16_t sym_0 = encoded[store->index++].val;
|
||||
if (store->index >= length) {
|
||||
store->index = 0;
|
||||
store->times--;
|
||||
if (store->times == 0) {
|
||||
*done = true;
|
||||
symbols[count++].val = sym_0;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
uint16_t sym_1 = encoded[store->index++].val;
|
||||
if (store->index >= length) {
|
||||
store->index = 0;
|
||||
store->times--;
|
||||
if (store->times == 0) {
|
||||
*done = true;
|
||||
symbols[count++].val = sym_0 | (sym_1 << 16);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
symbols[count++].val = sym_0 | (sym_1 << 16);
|
||||
}
|
||||
*done = false;
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->inverted_ = this->pin_->is_inverted();
|
||||
this->configure_rmt_();
|
||||
@@ -74,17 +34,6 @@ void RemoteTransmitterComponent::dump_config() {
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
rmt_symbol_half_t symbol = {
|
||||
.duration = 1,
|
||||
.level = value,
|
||||
};
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = value;
|
||||
this->store_.times = 1;
|
||||
this->store_.index = 0;
|
||||
#else
|
||||
rmt_symbol_word_t symbol = {
|
||||
.duration0 = 1,
|
||||
.level0 = value,
|
||||
@@ -93,8 +42,8 @@ void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
};
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.loop_count = 0;
|
||||
config.flags.eot_level = value;
|
||||
#endif
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
@@ -141,20 +90,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
rmt_simple_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
encoder.callback = encoder_callback;
|
||||
encoder.arg = &this->store_;
|
||||
encoder.min_chunk_size = 1;
|
||||
error = rmt_new_simple_encoder(&encoder, &this->encoder_);
|
||||
if (error != ESP_OK) {
|
||||
this->error_code_ = error;
|
||||
this->error_string_ = "in rmt_new_simple_encoder";
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#else
|
||||
rmt_copy_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
error = rmt_new_copy_encoder(&encoder, &this->encoder_);
|
||||
@@ -164,7 +99,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
error = rmt_enable(this->channel_);
|
||||
if (error != ESP_OK) {
|
||||
@@ -196,79 +130,6 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
}
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
|
||||
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
|
||||
this->configure_rmt_();
|
||||
}
|
||||
|
||||
this->rmt_temp_.clear();
|
||||
this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
|
||||
|
||||
// encode any delay at the start of the buffer to simplify the encoder callback
|
||||
// this will be skipped the first time around
|
||||
send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
|
||||
while (send_wait > 0) {
|
||||
int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(this->eot_level_),
|
||||
});
|
||||
send_wait -= duration;
|
||||
}
|
||||
|
||||
// encode data
|
||||
size_t offset = this->rmt_temp_.size();
|
||||
for (int32_t value : this->temp_.get_data()) {
|
||||
bool level = value >= 0;
|
||||
if (!level) {
|
||||
value = -value;
|
||||
}
|
||||
value = this->from_microseconds_(static_cast<uint32_t>(value));
|
||||
while (value > 0) {
|
||||
int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(level ^ this->inverted_),
|
||||
});
|
||||
value -= duration;
|
||||
}
|
||||
}
|
||||
|
||||
if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
|
||||
ESP_LOGE(TAG, "Empty data");
|
||||
return;
|
||||
}
|
||||
|
||||
this->transmit_trigger_->trigger();
|
||||
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
this->store_.times = send_times;
|
||||
this->store_.index = offset;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
error = rmt_tx_wait_all_done(this->channel_, -1);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
#else
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
@@ -290,7 +151,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
val = this->from_microseconds_(static_cast<uint32_t>(val));
|
||||
|
||||
do {
|
||||
int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
int32_t item = std::min(val, int32_t(32767));
|
||||
val -= item;
|
||||
|
||||
if (rmt_i % 2 == 0) {
|
||||
@@ -319,6 +180,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.loop_count = 0;
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
|
||||
@@ -338,7 +200,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
}
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
@@ -29,8 +29,8 @@ class RP2040GPIOPin : public InternalGPIOPin {
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_{};
|
||||
gpio::Flags flags_{};
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace rp2040
|
||||
|
||||
@@ -94,9 +94,6 @@ async def rp2040_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
# Only set if true to avoid bloating setup() function
|
||||
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
|
||||
if config[CONF_INVERTED]:
|
||||
cg.add(var.set_inverted(True))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
@@ -28,8 +28,6 @@ from esphome.const import (
|
||||
CONF_ON_RAW_VALUE,
|
||||
CONF_ON_VALUE,
|
||||
CONF_ON_VALUE_RANGE,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_PERIOD,
|
||||
CONF_QUANTILE,
|
||||
CONF_SEND_EVERY,
|
||||
CONF_SEND_FIRST_AT,
|
||||
@@ -646,29 +644,10 @@ async def throttle_with_priority_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
|
||||
|
||||
|
||||
HEARTBEAT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PERIOD): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"heartbeat",
|
||||
HeartbeatFilter,
|
||||
cv.Any(
|
||||
cv.positive_time_period_milliseconds,
|
||||
HEARTBEAT_SCHEMA,
|
||||
),
|
||||
"heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
async def heartbeat_filter_to_code(config, filter_id):
|
||||
if isinstance(config, dict):
|
||||
var = cg.new_Pvariable(filter_id, config[CONF_PERIOD])
|
||||
await cg.register_component(var, {})
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
return var
|
||||
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
@@ -878,9 +857,7 @@ async def setup_sensor_core_(var, config):
|
||||
cg.add(var.set_unit_of_measurement(unit_of_measurement))
|
||||
if (accuracy_decimals := config.get(CONF_ACCURACY_DECIMALS)) is not None:
|
||||
cg.add(var.set_accuracy_decimals(accuracy_decimals))
|
||||
# Only set force_update if True (default is False)
|
||||
if config[CONF_FORCE_UPDATE]:
|
||||
cg.add(var.set_force_update(True))
|
||||
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||
filters = await build_filters(config[CONF_FILTERS])
|
||||
cg.add(var.set_filters(filters))
|
||||
|
||||
@@ -313,7 +313,7 @@ optional<float> DeltaFilter::new_value(float value) {
|
||||
}
|
||||
|
||||
// OrFilter
|
||||
OrFilter::OrFilter(std::initializer_list<Filter *> filters) : filters_(filters), phi_(this) {}
|
||||
OrFilter::OrFilter(std::vector<Filter *> filters) : filters_(std::move(filters)), phi_(this) {}
|
||||
OrFilter::PhiNode::PhiNode(OrFilter *or_parent) : or_parent_(or_parent) {}
|
||||
|
||||
optional<float> OrFilter::PhiNode::new_value(float value) {
|
||||
@@ -326,14 +326,14 @@ optional<float> OrFilter::PhiNode::new_value(float value) {
|
||||
}
|
||||
optional<float> OrFilter::new_value(float value) {
|
||||
this->has_value_ = false;
|
||||
for (auto *filter : this->filters_)
|
||||
for (Filter *filter : this->filters_)
|
||||
filter->input(value);
|
||||
|
||||
return {};
|
||||
}
|
||||
void OrFilter::initialize(Sensor *parent, Filter *next) {
|
||||
Filter::initialize(parent, next);
|
||||
for (auto *filter : this->filters_) {
|
||||
for (Filter *filter : this->filters_) {
|
||||
filter->initialize(parent, &this->phi_);
|
||||
}
|
||||
this->phi_.initialize(parent, nullptr);
|
||||
@@ -372,12 +372,8 @@ optional<float> HeartbeatFilter::new_value(float value) {
|
||||
this->last_input_ = value;
|
||||
this->has_value_ = true;
|
||||
|
||||
if (this->optimistic_) {
|
||||
return value;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HeartbeatFilter::setup() {
|
||||
this->set_interval("heartbeat", this->time_period_, [this]() {
|
||||
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
|
||||
@@ -388,27 +384,20 @@ void HeartbeatFilter::setup() {
|
||||
this->output(this->last_input_);
|
||||
});
|
||||
}
|
||||
|
||||
float HeartbeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
CalibrateLinearFilter::CalibrateLinearFilter(std::initializer_list<std::array<float, 3>> linear_functions)
|
||||
: linear_functions_(linear_functions) {}
|
||||
|
||||
optional<float> CalibrateLinearFilter::new_value(float value) {
|
||||
for (const auto &f : this->linear_functions_) {
|
||||
for (std::array<float, 3> f : this->linear_functions_) {
|
||||
if (!std::isfinite(f[2]) || value < f[2])
|
||||
return (value * f[0]) + f[1];
|
||||
}
|
||||
return NAN;
|
||||
}
|
||||
|
||||
CalibratePolynomialFilter::CalibratePolynomialFilter(std::initializer_list<float> coefficients)
|
||||
: coefficients_(coefficients) {}
|
||||
|
||||
optional<float> CalibratePolynomialFilter::new_value(float value) {
|
||||
float res = 0.0f;
|
||||
float x = 1.0f;
|
||||
for (const auto &coefficient : this->coefficients_) {
|
||||
for (float coefficient : this->coefficients_) {
|
||||
res += x * coefficient;
|
||||
x *= value;
|
||||
}
|
||||
|
||||
@@ -396,16 +396,15 @@ class HeartbeatFilter : public Filter, public Component {
|
||||
explicit HeartbeatFilter(uint32_t time_period);
|
||||
|
||||
void setup() override;
|
||||
optional<float> new_value(float value) override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint32_t time_period_;
|
||||
float last_input_;
|
||||
bool has_value_{false};
|
||||
bool optimistic_{false};
|
||||
};
|
||||
|
||||
class DeltaFilter : public Filter {
|
||||
@@ -423,7 +422,7 @@ class DeltaFilter : public Filter {
|
||||
|
||||
class OrFilter : public Filter {
|
||||
public:
|
||||
explicit OrFilter(std::initializer_list<Filter *> filters);
|
||||
explicit OrFilter(std::vector<Filter *> filters);
|
||||
|
||||
void initialize(Sensor *parent, Filter *next) override;
|
||||
|
||||
@@ -439,27 +438,28 @@ class OrFilter : public Filter {
|
||||
OrFilter *or_parent_;
|
||||
};
|
||||
|
||||
FixedVector<Filter *> filters_;
|
||||
std::vector<Filter *> filters_;
|
||||
PhiNode phi_;
|
||||
bool has_value_{false};
|
||||
};
|
||||
|
||||
class CalibrateLinearFilter : public Filter {
|
||||
public:
|
||||
explicit CalibrateLinearFilter(std::initializer_list<std::array<float, 3>> linear_functions);
|
||||
CalibrateLinearFilter(std::vector<std::array<float, 3>> linear_functions)
|
||||
: linear_functions_(std::move(linear_functions)) {}
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
FixedVector<std::array<float, 3>> linear_functions_;
|
||||
std::vector<std::array<float, 3>> linear_functions_;
|
||||
};
|
||||
|
||||
class CalibratePolynomialFilter : public Filter {
|
||||
public:
|
||||
explicit CalibratePolynomialFilter(std::initializer_list<float> coefficients);
|
||||
CalibratePolynomialFilter(std::vector<float> coefficients) : coefficients_(std::move(coefficients)) {}
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
protected:
|
||||
FixedVector<float> coefficients_;
|
||||
std::vector<float> coefficients_;
|
||||
};
|
||||
|
||||
class ClampFilter : public Filter {
|
||||
|
||||
@@ -107,12 +107,12 @@ void Sensor::add_filter(Filter *filter) {
|
||||
}
|
||||
filter->initialize(this, nullptr);
|
||||
}
|
||||
void Sensor::add_filters(std::initializer_list<Filter *> filters) {
|
||||
void Sensor::add_filters(const std::vector<Filter *> &filters) {
|
||||
for (Filter *filter : filters) {
|
||||
this->add_filter(filter);
|
||||
}
|
||||
}
|
||||
void Sensor::set_filters(std::initializer_list<Filter *> filters) {
|
||||
void Sensor::set_filters(const std::vector<Filter *> &filters) {
|
||||
this->clear_filters();
|
||||
this->add_filters(filters);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
@@ -77,10 +77,10 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
* SlidingWindowMovingAverageFilter(15, 15), // average over last 15 values
|
||||
* });
|
||||
*/
|
||||
void add_filters(std::initializer_list<Filter *> filters);
|
||||
void add_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
/// Clear the filters and replace them by filters.
|
||||
void set_filters(std::initializer_list<Filter *> filters);
|
||||
void set_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
/// Clear the entire filter chain.
|
||||
void clear_filters();
|
||||
|
||||
@@ -27,7 +27,7 @@ void SNTPComponent::setup() {
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
esp_sntp_setservername(i++, server);
|
||||
esp_sntp_setservername(i++, server.c_str());
|
||||
}
|
||||
esp_sntp_set_sync_interval(this->get_update_interval());
|
||||
esp_sntp_set_time_sync_notification_cb([](struct timeval *tv) {
|
||||
@@ -42,7 +42,7 @@ void SNTPComponent::setup() {
|
||||
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
sntp_setservername(i++, server);
|
||||
sntp_setservername(i++, server.c_str());
|
||||
}
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
@@ -59,7 +59,7 @@ void SNTPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SNTP Time:");
|
||||
size_t i = 0;
|
||||
for (auto &server : this->servers_) {
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str());
|
||||
}
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace sntp {
|
||||
|
||||
// Server count is calculated at compile time by Python codegen
|
||||
// SNTP_SERVER_COUNT will always be defined
|
||||
|
||||
/// The SNTP component allows you to configure local timekeeping via Simple Network Time Protocol.
|
||||
///
|
||||
/// \note
|
||||
@@ -18,7 +14,10 @@ namespace sntp {
|
||||
/// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
||||
class SNTPComponent : public time::RealTimeClock {
|
||||
public:
|
||||
SNTPComponent(const std::array<const char *, SNTP_SERVER_COUNT> &servers) : servers_(servers) {}
|
||||
SNTPComponent(const std::vector<std::string> &servers) : servers_(servers) {}
|
||||
|
||||
// Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would
|
||||
// have had no effect anyway, and making the strings immutable avoids the need to strdup their contents.
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -30,10 +29,7 @@ class SNTPComponent : public time::RealTimeClock {
|
||||
void time_synced();
|
||||
|
||||
protected:
|
||||
// Store const char pointers to string literals
|
||||
// ESP8266: strings in rodata (RAM), but avoids std::string overhead (~24 bytes each)
|
||||
// Other platforms: strings in flash
|
||||
std::array<const char *, SNTP_SERVER_COUNT> servers_;
|
||||
std::vector<std::string> servers_;
|
||||
bool has_time_{false};
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
|
||||
@@ -43,11 +43,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
servers = config[CONF_SERVERS]
|
||||
|
||||
# Define server count at compile time
|
||||
cg.add_define("SNTP_SERVER_COUNT", len(servers))
|
||||
|
||||
# Pass string literals to constructor - stored in flash/rodata by compiler
|
||||
var = cg.new_Pvariable(config[CONF_ID], servers)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -110,28 +110,17 @@ def validate_mapping(value):
|
||||
"substitute", SubstituteFilter, cv.ensure_list(validate_mapping)
|
||||
)
|
||||
async def substitute_filter_to_code(config, filter_id):
|
||||
substitutions = [
|
||||
cg.StructInitializer(
|
||||
cg.MockObj("Substitution", "esphome::text_sensor::"),
|
||||
("from", conf[CONF_FROM]),
|
||||
("to", conf[CONF_TO]),
|
||||
)
|
||||
for conf in config
|
||||
]
|
||||
return cg.new_Pvariable(filter_id, substitutions)
|
||||
from_strings = [conf[CONF_FROM] for conf in config]
|
||||
to_strings = [conf[CONF_TO] for conf in config]
|
||||
return cg.new_Pvariable(filter_id, from_strings, to_strings)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("map", MapFilter, cv.ensure_list(validate_mapping))
|
||||
async def map_filter_to_code(config, filter_id):
|
||||
mappings = [
|
||||
cg.StructInitializer(
|
||||
cg.MockObj("Substitution", "esphome::text_sensor::"),
|
||||
("from", conf[CONF_FROM]),
|
||||
("to", conf[CONF_TO]),
|
||||
)
|
||||
for conf in config
|
||||
]
|
||||
return cg.new_Pvariable(filter_id, mappings)
|
||||
map_ = cg.std_ns.class_("map").template(cg.std_string, cg.std_string)
|
||||
return cg.new_Pvariable(
|
||||
filter_id, map_([(item[CONF_FROM], item[CONF_TO]) for item in config])
|
||||
)
|
||||
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
@@ -62,27 +62,19 @@ optional<std::string> AppendFilter::new_value(std::string value) { return value
|
||||
optional<std::string> PrependFilter::new_value(std::string value) { return this->prefix_ + value; }
|
||||
|
||||
// Substitute
|
||||
SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &substitutions)
|
||||
: substitutions_(substitutions) {}
|
||||
|
||||
optional<std::string> SubstituteFilter::new_value(std::string value) {
|
||||
std::size_t pos;
|
||||
for (const auto &sub : this->substitutions_) {
|
||||
while ((pos = value.find(sub.from)) != std::string::npos)
|
||||
value.replace(pos, sub.from.size(), sub.to);
|
||||
for (size_t i = 0; i < this->from_strings_.size(); i++) {
|
||||
while ((pos = value.find(this->from_strings_[i])) != std::string::npos)
|
||||
value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Map
|
||||
MapFilter::MapFilter(const std::initializer_list<Substitution> &mappings) : mappings_(mappings) {}
|
||||
|
||||
optional<std::string> MapFilter::new_value(std::string value) {
|
||||
for (const auto &mapping : this->mappings_) {
|
||||
if (mapping.from == value)
|
||||
return mapping.to;
|
||||
}
|
||||
return value; // Pass through if no match
|
||||
auto item = mappings_.find(value);
|
||||
return item == mappings_.end() ? value : item->second;
|
||||
}
|
||||
|
||||
} // namespace text_sensor
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace text_sensor {
|
||||
@@ -94,52 +98,26 @@ class PrependFilter : public Filter {
|
||||
std::string prefix_;
|
||||
};
|
||||
|
||||
struct Substitution {
|
||||
std::string from;
|
||||
std::string to;
|
||||
};
|
||||
|
||||
/// A simple filter that replaces a substring with another substring
|
||||
class SubstituteFilter : public Filter {
|
||||
public:
|
||||
explicit SubstituteFilter(const std::initializer_list<Substitution> &substitutions);
|
||||
SubstituteFilter(std::vector<std::string> from_strings, std::vector<std::string> to_strings)
|
||||
: from_strings_(std::move(from_strings)), to_strings_(std::move(to_strings)) {}
|
||||
optional<std::string> new_value(std::string value) override;
|
||||
|
||||
protected:
|
||||
FixedVector<Substitution> substitutions_;
|
||||
std::vector<std::string> from_strings_;
|
||||
std::vector<std::string> to_strings_;
|
||||
};
|
||||
|
||||
/** A filter that maps values from one set to another
|
||||
*
|
||||
* Uses linear search instead of std::map for typical small datasets (2-20 mappings).
|
||||
* Linear search on contiguous memory is faster than red-black tree lookups when:
|
||||
* - Dataset is small (< ~30 items)
|
||||
* - Memory is contiguous (cache-friendly, better CPU cache utilization)
|
||||
* - No pointer chasing overhead (tree node traversal)
|
||||
* - String comparison cost dominates lookup time
|
||||
*
|
||||
* Benchmark results (see benchmark_map_filter.cpp):
|
||||
* - 2 mappings: Linear 1.26x faster than std::map
|
||||
* - 5 mappings: Linear 2.25x faster than std::map
|
||||
* - 10 mappings: Linear 1.83x faster than std::map
|
||||
* - 20 mappings: Linear 1.59x faster than std::map
|
||||
* - 30 mappings: Linear 1.09x faster than std::map
|
||||
* - 40 mappings: std::map 1.27x faster than Linear (break-even)
|
||||
*
|
||||
* Benefits over std::map:
|
||||
* - ~2KB smaller flash (no red-black tree code)
|
||||
* - ~24-32 bytes less RAM per mapping (no tree node overhead)
|
||||
* - Faster for typical ESPHome usage (2-10 mappings common, 20+ rare)
|
||||
*
|
||||
* Break-even point: ~35-40 mappings, but ESPHome configs rarely exceed 20
|
||||
*/
|
||||
/// A filter that maps values from one set to another
|
||||
class MapFilter : public Filter {
|
||||
public:
|
||||
explicit MapFilter(const std::initializer_list<Substitution> &mappings);
|
||||
MapFilter(std::map<std::string, std::string> mappings) : mappings_(std::move(mappings)) {}
|
||||
optional<std::string> new_value(std::string value) override;
|
||||
|
||||
protected:
|
||||
FixedVector<Substitution> mappings_;
|
||||
std::map<std::string, std::string> mappings_;
|
||||
};
|
||||
|
||||
} // namespace text_sensor
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user