mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			73 Commits
		
	
	
		
			add-heap-t
			...
			2025.5.0b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1e20440c8e | ||
| 
						 | 
					0630244195 | ||
| 
						 | 
					183659f527 | ||
| 
						 | 
					4ea63af796 | ||
| 
						 | 
					0aa7911b1b | ||
| 
						 | 
					032949bc77 | ||
| 
						 | 
					6f8ee65919 | ||
| 
						 | 
					c5654b4cb2 | ||
| 
						 | 
					410b6353fe | ||
| 
						 | 
					a36e1aab8e | ||
| 
						 | 
					864ae7a56c | ||
| 
						 | 
					2560d2b9d0 | ||
| 
						 | 
					0cf9b05afd | ||
| 
						 | 
					8b65d1673a | ||
| 
						 | 
					5e164b107a | ||
| 
						 | 
					a83959d738 | ||
| 
						 | 
					0ccc5bf714 | ||
| 
						 | 
					bc0956019b | ||
| 
						 | 
					49f631d6c5 | ||
| 
						 | 
					a9d5eb8470 | ||
| 
						 | 
					7c0546c9f0 | ||
| 
						 | 
					f4eb75e4e0 | ||
| 
						 | 
					5b2c19bc86 | ||
| 
						 | 
					185b84b8b2 | ||
| 
						 | 
					facf94699e | ||
| 
						 | 
					58104229e2 | ||
| 
						 | 
					50c88b7aa7 | ||
| 
						 | 
					81bae96109 | ||
| 
						 | 
					a3ed090594 | ||
| 
						 | 
					cff1820772 | ||
| 
						 | 
					bdd2774544 | ||
| 
						 | 
					38790793dd | ||
| 
						 | 
					dcd786d21c | ||
| 
						 | 
					71e88fe9b2 | ||
| 
						 | 
					11dcaf7383 | ||
| 
						 | 
					dded81d622 | ||
| 
						 | 
					8324b3244c | ||
| 
						 | 
					401c090edd | ||
| 
						 | 
					8757957e17 | ||
| 
						 | 
					e2c8a5b638 | ||
| 
						 | 
					7bb899bfa1 | ||
| 
						 | 
					3e2359ddff | ||
| 
						 | 
					04147a7f27 | ||
| 
						 | 
					cae3c030d2 | ||
| 
						 | 
					d7c615ec43 | ||
| 
						 | 
					1294e8ccd5 | ||
| 
						 | 
					37a2cb07d1 | ||
| 
						 | 
					2af3994f79 | ||
| 
						 | 
					0c0fe81814 | ||
| 
						 | 
					82c8614315 | ||
| 
						 | 
					a85dc65038 | ||
| 
						 | 
					290b8bdca0 | ||
| 
						 | 
					a96ed0b70a | ||
| 
						 | 
					cdc1a7c646 | ||
| 
						 | 
					7f59aff157 | ||
| 
						 | 
					cdce59f7f9 | ||
| 
						 | 
					ff1c3cb52e | ||
| 
						 | 
					bec9d91419 | ||
| 
						 | 
					8399d894c1 | ||
| 
						 | 
					e1732c4945 | ||
| 
						 | 
					ca221d6cb2 | ||
| 
						 | 
					8a90ce882a | ||
| 
						 | 
					b3400a1308 | ||
| 
						 | 
					23fb1bed61 | ||
| 
						 | 
					2b3757dff8 | ||
| 
						 | 
					1da8e99d27 | ||
| 
						 | 
					e94e71ded8 | ||
| 
						 | 
					00f20c1e55 | ||
| 
						 | 
					45d019a7e4 | ||
| 
						 | 
					8465017db9 | ||
| 
						 | 
					782d748210 | ||
| 
						 | 
					b01d85a974 | ||
| 
						 | 
					797a4c61f2 | 
							
								
								
									
										11
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -57,6 +57,17 @@ jobs:
 | 
			
		||||
              event: 'REQUEST_CHANGES',
 | 
			
		||||
              body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
 | 
			
		||||
            })
 | 
			
		||||
      - if: failure()
 | 
			
		||||
        name: Show changes
 | 
			
		||||
        run: git diff
 | 
			
		||||
      - if: failure()
 | 
			
		||||
        name: Archive artifacts
 | 
			
		||||
        uses: actions/upload-artifact@v4.6.2
 | 
			
		||||
        with:
 | 
			
		||||
          name: generated-proto-files
 | 
			
		||||
          path: |
 | 
			
		||||
            esphome/components/api/api_pb2.*
 | 
			
		||||
            esphome/components/api/api_pb2_service.*
 | 
			
		||||
      - if: success()
 | 
			
		||||
        name: Dismiss review
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -292,6 +292,11 @@ jobs:
 | 
			
		||||
            name: Run script/clang-tidy for ESP32 IDF
 | 
			
		||||
            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
			
		||||
            pio_cache_key: tidyesp32-idf
 | 
			
		||||
          - id: clang-tidy
 | 
			
		||||
            name: Run script/clang-tidy for ZEPHYR
 | 
			
		||||
            options: --environment nrf52-tidy --grep USE_ZEPHYR
 | 
			
		||||
            pio_cache_key: tidy-zephyr
 | 
			
		||||
            ignore_errors: true
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
@@ -331,13 +336,13 @@ jobs:
 | 
			
		||||
      - name: Run clang-tidy
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
			
		||||
          script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
			
		||||
        env:
 | 
			
		||||
          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
			
		||||
          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
        if: always()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -231,3 +231,25 @@ jobs:
 | 
			
		||||
                content: description
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
  deploy-esphome-schema:
 | 
			
		||||
    if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - init
 | 
			
		||||
      - deploy-manifest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Trigger Workflow
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            github.rest.actions.createWorkflowDispatch({
 | 
			
		||||
              owner: "esphome",
 | 
			
		||||
              repo: "esphome-schema",
 | 
			
		||||
              workflow_id: "generate-schemas.yml",
 | 
			
		||||
              ref: "main",
 | 
			
		||||
              inputs: {
 | 
			
		||||
                version: "${{ needs.init.outputs.tag }}",
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
			
		||||
    # Ruff version.
 | 
			
		||||
    rev: v0.11.0
 | 
			
		||||
    rev: v0.11.9
 | 
			
		||||
    hooks:
 | 
			
		||||
      # Run the linter.
 | 
			
		||||
      - id: ruff
 | 
			
		||||
 
 | 
			
		||||
@@ -282,6 +282,7 @@ esphome/components/microphone/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/mics_4514/* @jesserockz
 | 
			
		||||
esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
esphome/components/mipi_spi/* @clydebarrow
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/mixer/speaker/* @kahrendt
 | 
			
		||||
esphome/components/mlx90393/* @functionpointer
 | 
			
		||||
@@ -398,6 +399,7 @@ esphome/components/smt100/* @piechade
 | 
			
		||||
esphome/components/sn74hc165/* @jesserockz
 | 
			
		||||
esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/sound_level/* @kahrendt
 | 
			
		||||
esphome/components/speaker/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
 | 
			
		||||
esphome/components/spi/* @clydebarrow @esphome/core
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ AirthingsWaveBase = airthings_wave_base_ns.class_(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BASE_SCHEMA = (
 | 
			
		||||
    sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CODE,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
@@ -12,6 +14,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_WEB_SERVER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
 | 
			
		||||
@@ -78,12 +81,11 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
 | 
			
		||||
    "AlarmControlPanelCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
_ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AlarmControlPanel),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
			
		||||
                mqtt.MQTTAlarmControlPanelComponent
 | 
			
		||||
            ),
 | 
			
		||||
@@ -146,6 +148,33 @@ ALARM_CONTROL_PANEL_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def alarm_control_panel_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
 | 
			
		||||
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
 | 
			
		||||
    cv.deprecated_schema_constant("alarm_control_panel")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(AlarmControlPanel),
 | 
			
		||||
@@ -209,6 +238,12 @@ async def register_alarm_control_panel(var, config):
 | 
			
		||||
    await setup_alarm_control_panel_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_alarm_control_panel(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_alarm_control_panel(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, cover
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
from esphome.const import CONF_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
@@ -15,9 +15,9 @@ Am43Component = am43_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.COVER_SCHEMA.extend(
 | 
			
		||||
    cover.cover_schema(Am43Component)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Am43Component),
 | 
			
		||||
            cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
 | 
			
		||||
            cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
@@ -28,9 +28,8 @@ CONFIG_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,8 @@
 | 
			
		||||
import base64
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import Condition
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ACTION,
 | 
			
		||||
@@ -25,14 +23,12 @@ from esphome.const import (
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_VARIABLES,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
AUTO_LOAD = ["socket"]
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
api_ns = cg.esphome_ns.namespace("api")
 | 
			
		||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
 | 
			
		||||
HomeAssistantServiceCallAction = api_ns.class_(
 | 
			
		||||
@@ -53,11 +49,6 @@ SERVICE_ARG_NATIVE_TYPES = {
 | 
			
		||||
    "string[]": cg.std_vector.template(cg.std_string),
 | 
			
		||||
}
 | 
			
		||||
CONF_ENCRYPTION = "encryption"
 | 
			
		||||
CONF_HEAP_TRACING = "heap_tracing"
 | 
			
		||||
CONF_HEAP_TRACING_STANDALONE = "standalone"  # vs SYSTEM
 | 
			
		||||
CONF_HEAP_TRACING_RECORDS = "num_records"
 | 
			
		||||
CONF_HEAP_TASK_TRACKING = "task_tracking"
 | 
			
		||||
CONF_HEAP_TASK_MAX = "max_tasks"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_encryption_key(value):
 | 
			
		||||
@@ -104,22 +95,6 @@ def _encryption_schema(config):
 | 
			
		||||
    return ENCRYPTION_SCHEMA(config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HEAP_TRACING_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TASK_TRACKING, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TASK_MAX, default=10): cv.positive_int,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _heap_tracing_schema(config):
 | 
			
		||||
    if config is None:
 | 
			
		||||
        config = {}
 | 
			
		||||
    return HEAP_TRACING_SCHEMA(config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -134,7 +109,6 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ): ACTIONS_SCHEMA,
 | 
			
		||||
            cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_ENCRYPTION): _encryption_schema,
 | 
			
		||||
            cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
 | 
			
		||||
            cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
 | 
			
		||||
                single=True
 | 
			
		||||
            ),
 | 
			
		||||
@@ -202,74 +176,6 @@ async def to_code(config):
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add_define("USE_API_PLAINTEXT")
 | 
			
		||||
 | 
			
		||||
    # Handle heap tracing configuration if ESP32 platform and using ESP-IDF
 | 
			
		||||
    if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None:
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            # Enable heap tracing in sdkconfig
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True)
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_HEAP_TRACE_STACK_DEPTH", "30")
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_ESP32_APPTRACE_ENABLE", True)
 | 
			
		||||
 | 
			
		||||
            # Set tracing mode (standalone or system)
 | 
			
		||||
            if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]:
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True)
 | 
			
		||||
            else:
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True)
 | 
			
		||||
 | 
			
		||||
            # Enable runtime stats gathering for task info
 | 
			
		||||
            if heap_tracing_config[CONF_HEAP_TASK_TRACKING]:
 | 
			
		||||
                add_idf_sdkconfig_option(
 | 
			
		||||
                    "CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS", True
 | 
			
		||||
                )
 | 
			
		||||
                add_idf_sdkconfig_option(
 | 
			
		||||
                    "CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS", True
 | 
			
		||||
                )
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_FREERTOS_USE_TRACE_FACILITY", True)
 | 
			
		||||
 | 
			
		||||
            # Generate code to implement heap tracing
 | 
			
		||||
            cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"'))
 | 
			
		||||
 | 
			
		||||
            # Define the trace record buffer
 | 
			
		||||
            num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS]
 | 
			
		||||
            cg.add_global(
 | 
			
		||||
                cg.RawStatement(
 | 
			
		||||
                    f"static heap_trace_record_t trace_record[{num_records}];"
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # No additional setup needed for task tracking
 | 
			
		||||
 | 
			
		||||
            # Add helper functions for heap tracing with extern "C" to make them globally accessible
 | 
			
		||||
            cg.add_global(
 | 
			
		||||
                cg.RawStatement(
 | 
			
		||||
                    """
 | 
			
		||||
// Global heap tracing functions that can be called from any context
 | 
			
		||||
extern "C" void start_heap_trace() {
 | 
			
		||||
    heap_trace_init_standalone(trace_record, """
 | 
			
		||||
                    + str(num_records)
 | 
			
		||||
                    + """);
 | 
			
		||||
    heap_trace_start(HEAP_TRACE_LEAKS);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" void stop_and_dump_heap_trace() {
 | 
			
		||||
    heap_trace_stop();
 | 
			
		||||
    heap_trace_dump();
 | 
			
		||||
}
 | 
			
		||||
"""
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Add periodic heap trace dumping to the api_server.cpp file
 | 
			
		||||
            # This will be added in C++ code
 | 
			
		||||
            cg.add_define("USE_API_HEAP_TRACE")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # Not using ESP-IDF, so we can't use heap tracing
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
                "Heap tracing is only available when using ESP-IDF. "
 | 
			
		||||
                "Disabling heap tracing configuration."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_API")
 | 
			
		||||
    cg.add_global(api_ns.using)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,23 +33,24 @@ service APIConnection {
 | 
			
		||||
  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
			
		||||
  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
			
		||||
 | 
			
		||||
  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
			
		||||
  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
			
		||||
  rpc light_command (LightCommandRequest) returns (void) {}
 | 
			
		||||
  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
  rpc camera_image (CameraImageRequest) returns (void) {}
 | 
			
		||||
  rpc climate_command (ClimateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
  rpc text_command (TextCommandRequest) returns (void) {}
 | 
			
		||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
			
		||||
  rpc button_command (ButtonCommandRequest) returns (void) {}
 | 
			
		||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
			
		||||
  rpc valve_command (ValveCommandRequest) returns (void) {}
 | 
			
		||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
			
		||||
  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
			
		||||
  rpc date_command (DateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc datetime_command (DateTimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
			
		||||
  rpc light_command (LightCommandRequest) returns (void) {}
 | 
			
		||||
  rpc lock_command (LockCommandRequest) returns (void) {}
 | 
			
		||||
  rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
 | 
			
		||||
  rpc number_command (NumberCommandRequest) returns (void) {}
 | 
			
		||||
  rpc select_command (SelectCommandRequest) returns (void) {}
 | 
			
		||||
  rpc siren_command (SirenCommandRequest) returns (void) {}
 | 
			
		||||
  rpc switch_command (SwitchCommandRequest) returns (void) {}
 | 
			
		||||
  rpc text_command (TextCommandRequest) returns (void) {}
 | 
			
		||||
  rpc time_command (TimeCommandRequest) returns (void) {}
 | 
			
		||||
  rpc update_command (UpdateCommandRequest) returns (void) {}
 | 
			
		||||
  rpc valve_command (ValveCommandRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
 | 
			
		||||
@@ -655,7 +656,7 @@ message SubscribeLogsResponse {
 | 
			
		||||
  option (no_delay) = false;
 | 
			
		||||
 | 
			
		||||
  LogLevel level = 1;
 | 
			
		||||
  string message = 3;
 | 
			
		||||
  bytes message = 3;
 | 
			
		||||
  bool send_failed = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -911,6 +912,7 @@ message ClimateStateResponse {
 | 
			
		||||
  float target_temperature = 4;
 | 
			
		||||
  float target_temperature_low = 5;
 | 
			
		||||
  float target_temperature_high = 6;
 | 
			
		||||
  // For older peers, equal to preset == CLIMATE_PRESET_AWAY
 | 
			
		||||
  bool unused_legacy_away = 7;
 | 
			
		||||
  ClimateAction action = 8;
 | 
			
		||||
  ClimateFanMode fan_mode = 9;
 | 
			
		||||
@@ -936,6 +938,7 @@ message ClimateCommandRequest {
 | 
			
		||||
  float target_temperature_low = 7;
 | 
			
		||||
  bool has_target_temperature_high = 8;
 | 
			
		||||
  float target_temperature_high = 9;
 | 
			
		||||
  // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
 | 
			
		||||
  bool unused_has_legacy_away = 10;
 | 
			
		||||
  bool unused_legacy_away = 11;
 | 
			
		||||
  bool has_fan_mode = 12;
 | 
			
		||||
@@ -1038,6 +1041,49 @@ message SelectCommandRequest {
 | 
			
		||||
  string state = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== SIREN ====================
 | 
			
		||||
message ListEntitiesSirenResponse {
 | 
			
		||||
  option (id) = 55;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
 | 
			
		||||
  string object_id = 1;
 | 
			
		||||
  fixed32 key = 2;
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  repeated string tones = 7;
 | 
			
		||||
  bool supports_duration = 8;
 | 
			
		||||
  bool supports_volume = 9;
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
}
 | 
			
		||||
message SirenStateResponse {
 | 
			
		||||
  option (id) = 56;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool state = 2;
 | 
			
		||||
}
 | 
			
		||||
message SirenCommandRequest {
 | 
			
		||||
  option (id) = 57;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_SIREN";
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool has_state = 2;
 | 
			
		||||
  bool state = 3;
 | 
			
		||||
  bool has_tone = 4;
 | 
			
		||||
  string tone = 5;
 | 
			
		||||
  bool has_duration = 6;
 | 
			
		||||
  uint32 duration = 7;
 | 
			
		||||
  bool has_volume = 8;
 | 
			
		||||
  float volume = 9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== LOCK ====================
 | 
			
		||||
enum LockState {
 | 
			
		||||
@@ -1207,8 +1253,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
 | 
			
		||||
 | 
			
		||||
message BluetoothServiceData {
 | 
			
		||||
  string uuid = 1;
 | 
			
		||||
  repeated uint32 legacy_data = 2 [deprecated = true];
 | 
			
		||||
  bytes data = 3; // Changed in proto version 1.7
 | 
			
		||||
  repeated uint32 legacy_data = 2 [deprecated = true];  // Removed in api version 1.7
 | 
			
		||||
  bytes data = 3;  // Added in api version 1.7
 | 
			
		||||
}
 | 
			
		||||
message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  option (id) = 67;
 | 
			
		||||
@@ -1217,7 +1263,7 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  option (no_delay) = true;
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  string name = 2;
 | 
			
		||||
  bytes name = 2;
 | 
			
		||||
  sint32 rssi = 3;
 | 
			
		||||
 | 
			
		||||
  repeated string service_uuids = 4;
 | 
			
		||||
@@ -1504,7 +1550,7 @@ message BluetoothScannerSetModeRequest {
 | 
			
		||||
  BluetoothScannerMode mode = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== PUSH TO TALK ====================
 | 
			
		||||
// ==================== VOICE ASSISTANT ====================
 | 
			
		||||
enum VoiceAssistantSubscribeFlag {
 | 
			
		||||
  VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
 | 
			
		||||
  VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -73,6 +73,91 @@ const char *api_error_to_str(APIError err) {
 | 
			
		||||
  return "UNKNOWN";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Common implementation for writing raw data to socket
 | 
			
		||||
template<typename StateEnum>
 | 
			
		||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
 | 
			
		||||
                                    std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
 | 
			
		||||
                                    StateEnum failed_state) {
 | 
			
		||||
  // This method writes data to socket or buffers it
 | 
			
		||||
  // Returns APIError::OK if successful (or would block, but data has been buffered)
 | 
			
		||||
  // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
 | 
			
		||||
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;  // Nothing to do, success
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf.empty()) {
 | 
			
		||||
    // try to empty tx_buf first
 | 
			
		||||
    while (!tx_buf.empty()) {
 | 
			
		||||
      ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
 | 
			
		||||
      if (is_would_block(sent)) {
 | 
			
		||||
        break;
 | 
			
		||||
      } else if (sent == -1) {
 | 
			
		||||
        ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
			
		||||
        state = failed_state;
 | 
			
		||||
        return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
			
		||||
      }
 | 
			
		||||
      // TODO: inefficient if multiple packets in txbuf
 | 
			
		||||
      // replace with deque of buffers
 | 
			
		||||
      tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                    reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
 | 
			
		||||
    state = failed_state;
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;  // Socket write failed
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf.reserve(tx_buf.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                      reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;  // Success, data buffered
 | 
			
		||||
  }
 | 
			
		||||
  return APIError::OK;  // Success, all data sent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
 | 
			
		||||
// uncomment to log raw packets
 | 
			
		||||
//#define HELPER_LOG_PACKETS
 | 
			
		||||
@@ -547,79 +632,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
/** Write the data to the socket, or buffer it a write would block
 | 
			
		||||
 *
 | 
			
		||||
 * @param data The data to write
 | 
			
		||||
 * @param len The length of data
 | 
			
		||||
 */
 | 
			
		||||
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // try to empty tx_buf_ first
 | 
			
		||||
    aerr = try_send_tx_buf_();
 | 
			
		||||
    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
  // fully sent
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
 | 
			
		||||
  uint8_t header[3];
 | 
			
		||||
  header[0] = 0x01;  // indicator
 | 
			
		||||
@@ -753,6 +765,11 @@ void noise_rand_bytes(void *output, size_t len) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Explicit template instantiation for Noise
 | 
			
		||||
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
 | 
			
		||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
			
		||||
    APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
 | 
			
		||||
#endif  // USE_API_NOISE
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_PLAINTEXT
 | 
			
		||||
@@ -977,79 +994,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
 | 
			
		||||
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
/** Write the data to the socket, or buffer it a write would block
 | 
			
		||||
 *
 | 
			
		||||
 * @param data The data to write
 | 
			
		||||
 * @param len The length of data
 | 
			
		||||
 */
 | 
			
		||||
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  if (iovcnt == 0)
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  APIError aerr;
 | 
			
		||||
 | 
			
		||||
  size_t total_write_len = 0;
 | 
			
		||||
  for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
#ifdef HELPER_LOG_PACKETS
 | 
			
		||||
    ESP_LOGVV(TAG, "Sending raw: %s",
 | 
			
		||||
              format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    total_write_len += iov[i].iov_len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // try to empty tx_buf_ first
 | 
			
		||||
    aerr = try_send_tx_buf_();
 | 
			
		||||
    if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
 | 
			
		||||
      return aerr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  } else if (sent == -1) {
 | 
			
		||||
    // an error occurred
 | 
			
		||||
    state_ = State::FAILED;
 | 
			
		||||
    HELPER_LOG("Socket write failed with errno %d", errno);
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
        to_consume -= iov[i].iov_len;
 | 
			
		||||
      } else {
 | 
			
		||||
        tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
 | 
			
		||||
                       reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
        to_consume = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return APIError::OK;
 | 
			
		||||
  }
 | 
			
		||||
  // fully sent
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APIError APIPlaintextFrameHelper::close() {
 | 
			
		||||
  state_ = State::CLOSED;
 | 
			
		||||
@@ -1067,6 +1011,11 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
 | 
			
		||||
  }
 | 
			
		||||
  return APIError::OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Explicit template instantiation for Plaintext
 | 
			
		||||
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
 | 
			
		||||
    const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
 | 
			
		||||
    APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
 | 
			
		||||
#endif  // USE_API_PLAINTEXT
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,12 @@ class APIFrameHelper {
 | 
			
		||||
  virtual APIError shutdown(int how) = 0;
 | 
			
		||||
  // Give this helper a name for logging
 | 
			
		||||
  virtual void set_log_info(std::string info) = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // Common implementation for writing raw data to socket
 | 
			
		||||
  template<typename StateEnum>
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
 | 
			
		||||
                      const std::string &info, StateEnum &state, StateEnum failed_state);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
@@ -103,7 +109,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
			
		||||
  APIError try_send_tx_buf_();
 | 
			
		||||
  APIError write_frame_(const uint8_t *data, size_t len);
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
			
		||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
			
		||||
  }
 | 
			
		||||
  APIError init_handshake_();
 | 
			
		||||
  APIError check_handshake_finished_();
 | 
			
		||||
  void send_explicit_handshake_reject_(const std::string &reason);
 | 
			
		||||
@@ -164,7 +172,9 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
 | 
			
		||||
  APIError try_read_frame_(ParsedFrame *frame);
 | 
			
		||||
  APIError try_send_tx_buf_();
 | 
			
		||||
  APIError write_raw_(const struct iovec *iov, int iovcnt);
 | 
			
		||||
  inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr<socket::Socket> socket_;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5377,6 +5377,307 @@ void SelectCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->supports_duration = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->supports_volume = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesSirenResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->object_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->name = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->tones.push_back(value.as_string());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesSirenResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(1, this->object_id);
 | 
			
		||||
  buffer.encode_fixed32(2, this->key);
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  for (auto &it : this->tones) {
 | 
			
		||||
    buffer.encode_string(7, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_bool(8, this->supports_duration);
 | 
			
		||||
  buffer.encode_bool(9, this->supports_volume);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
 | 
			
		||||
  ProtoSize::add_string_field(total_size, 1, this->object_id, false);
 | 
			
		||||
  ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
 | 
			
		||||
  ProtoSize::add_string_field(total_size, 1, this->name, false);
 | 
			
		||||
  ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
 | 
			
		||||
  ProtoSize::add_string_field(total_size, 1, this->icon, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
 | 
			
		||||
  if (!this->tones.empty()) {
 | 
			
		||||
    for (const auto &it : this->tones) {
 | 
			
		||||
      ProtoSize::add_string_field(total_size, 1, it, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false);
 | 
			
		||||
  ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("ListEntitiesSirenResponse {\n");
 | 
			
		||||
  out.append("  object_id: ");
 | 
			
		||||
  out.append("'").append(this->object_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  name: ");
 | 
			
		||||
  out.append("'").append(this->name).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  unique_id: ");
 | 
			
		||||
  out.append("'").append(this->unique_id).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  for (const auto &it : this->tones) {
 | 
			
		||||
    out.append("  tones: ");
 | 
			
		||||
    out.append("'").append(it).append("'");
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  supports_duration: ");
 | 
			
		||||
  out.append(YESNO(this->supports_duration));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supports_volume: ");
 | 
			
		||||
  out.append(YESNO(this->supports_volume));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool SirenStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool SirenStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void SirenStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->state);
 | 
			
		||||
}
 | 
			
		||||
void SirenStateResponse::calculate_size(uint32_t &total_size) const {
 | 
			
		||||
  ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->state, false);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SirenStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SirenStateResponse {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  state: ");
 | 
			
		||||
  out.append(YESNO(this->state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->has_state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->state = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->has_tone = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->has_duration = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->duration = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->has_volume = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 5: {
 | 
			
		||||
      this->tone = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->key = value.as_fixed32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->volume = value.as_float();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void SirenCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_fixed32(1, this->key);
 | 
			
		||||
  buffer.encode_bool(2, this->has_state);
 | 
			
		||||
  buffer.encode_bool(3, this->state);
 | 
			
		||||
  buffer.encode_bool(4, this->has_tone);
 | 
			
		||||
  buffer.encode_string(5, this->tone);
 | 
			
		||||
  buffer.encode_bool(6, this->has_duration);
 | 
			
		||||
  buffer.encode_uint32(7, this->duration);
 | 
			
		||||
  buffer.encode_bool(8, this->has_volume);
 | 
			
		||||
  buffer.encode_float(9, this->volume);
 | 
			
		||||
}
 | 
			
		||||
void SirenCommandRequest::calculate_size(uint32_t &total_size) const {
 | 
			
		||||
  ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->has_state, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->state, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->has_tone, false);
 | 
			
		||||
  ProtoSize::add_string_field(total_size, 1, this->tone, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->has_duration, false);
 | 
			
		||||
  ProtoSize::add_uint32_field(total_size, 1, this->duration, false);
 | 
			
		||||
  ProtoSize::add_bool_field(total_size, 1, this->has_volume, false);
 | 
			
		||||
  ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void SirenCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("SirenCommandRequest {\n");
 | 
			
		||||
  out.append("  key: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->key);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_state: ");
 | 
			
		||||
  out.append(YESNO(this->has_state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  state: ");
 | 
			
		||||
  out.append(YESNO(this->state));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_tone: ");
 | 
			
		||||
  out.append(YESNO(this->has_tone));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  tone: ");
 | 
			
		||||
  out.append("'").append(this->tone).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_duration: ");
 | 
			
		||||
  out.append(YESNO(this->has_duration));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  duration: ");
 | 
			
		||||
  sprintf(buffer, "%" PRIu32, this->duration);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_volume: ");
 | 
			
		||||
  out.append(YESNO(this->has_volume));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  volume: ");
 | 
			
		||||
  sprintf(buffer, "%g", this->volume);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 6: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1284,6 +1284,65 @@ class SelectCommandRequest : public ProtoMessage {
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesSirenResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::vector<std::string> tones{};
 | 
			
		||||
  bool supports_duration{false};
 | 
			
		||||
  bool supports_volume{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void calculate_size(uint32_t &total_size) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class SirenStateResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool state{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void calculate_size(uint32_t &total_size) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class SirenCommandRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint32_t key{0};
 | 
			
		||||
  bool has_state{false};
 | 
			
		||||
  bool state{false};
 | 
			
		||||
  bool has_tone{false};
 | 
			
		||||
  std::string tone{};
 | 
			
		||||
  bool has_duration{false};
 | 
			
		||||
  uint32_t duration{0};
 | 
			
		||||
  bool has_volume{false};
 | 
			
		||||
  float volume{0.0f};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void calculate_size(uint32_t &total_size) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
 | 
			
		||||
  bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class ListEntitiesLockResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  std::string object_id{};
 | 
			
		||||
 
 | 
			
		||||
@@ -292,6 +292,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_siren_response(const ListEntitiesSirenResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_list_entities_siren_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<ListEntitiesSirenResponse>(msg, 55);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
bool APIServerConnectionBase::send_siren_state_response(const SirenStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_siren_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<SirenStateResponse>(msg, 56);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
@@ -903,6 +921,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_select_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 57: {
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
      SirenCommandRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_siren_command_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1369,8 +1398,8 @@ void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncrypt
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1379,46 +1408,7 @@ void APIServerConnection::on_cover_command_request(const CoverCommandRequest &ms
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->cover_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->fan_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->light_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->switch_command(msg);
 | 
			
		||||
  this->button_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
@@ -1447,8 +1437,8 @@ void APIServerConnection::on_climate_command_request(const ClimateCommandRequest
 | 
			
		||||
  this->climate_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1457,85 +1447,7 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->number_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->text_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->select_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->button_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->lock_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->valve_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
  this->cover_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
@@ -1551,19 +1463,6 @@ void APIServerConnection::on_date_command_request(const DateCommandRequest &msg)
 | 
			
		||||
  this->date_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->time_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1577,6 +1476,136 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
 | 
			
		||||
  this->datetime_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->fan_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->light_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->lock_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->media_player_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->number_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->select_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->siren_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->switch_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->text_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->time_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1590,6 +1619,19 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
 | 
			
		||||
  this->update_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->valve_command(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
			
		||||
 
 | 
			
		||||
@@ -136,6 +136,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void on_select_command_request(const SelectCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  bool send_list_entities_siren_response(const ListEntitiesSirenResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  bool send_siren_state_response(const SirenStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  virtual void on_siren_command_request(const SirenCommandRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
@@ -364,17 +373,8 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  virtual void cover_command(const CoverCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  virtual void fan_command(const FanCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  virtual void light_command(const LightCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  virtual void switch_command(const SwitchCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void button_command(const ButtonCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  virtual void camera_image(const CameraImageRequest &msg) = 0;
 | 
			
		||||
@@ -382,39 +382,51 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  virtual void climate_command(const ClimateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  virtual void number_command(const NumberCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  virtual void text_command(const TextCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void select_command(const SelectCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  virtual void button_command(const ButtonCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  virtual void lock_command(const LockCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  virtual void valve_command(const ValveCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  virtual void cover_command(const CoverCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  virtual void date_command(const DateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  virtual void time_command(const TimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  virtual void fan_command(const FanCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  virtual void light_command(const LightCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  virtual void lock_command(const LockCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  virtual void number_command(const NumberCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  virtual void select_command(const SelectCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  virtual void siren_command(const SirenCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  virtual void switch_command(const SwitchCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  virtual void text_command(const TextCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  virtual void time_command(const TimeCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  virtual void update_command(const UpdateCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  virtual void valve_command(const ValveCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -478,17 +490,8 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  void on_cover_command_request(const CoverCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  void on_fan_command_request(const FanCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  void on_light_command_request(const LightCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  void on_switch_command_request(const SwitchCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void on_button_command_request(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  void on_camera_image_request(const CameraImageRequest &msg) override;
 | 
			
		||||
@@ -496,39 +499,51 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  void on_climate_command_request(const ClimateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void on_number_command_request(const NumberCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  void on_text_command_request(const TextCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  void on_select_command_request(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  void on_button_command_request(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  void on_lock_command_request(const LockCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  void on_valve_command_request(const ValveCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  void on_cover_command_request(const CoverCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  void on_date_command_request(const DateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  void on_time_command_request(const TimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  void on_fan_command_request(const FanCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  void on_light_command_request(const LightCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  void on_lock_command_request(const LockCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void on_number_command_request(const NumberCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  void on_select_command_request(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SIREN
 | 
			
		||||
  void on_siren_command_request(const SirenCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  void on_switch_command_request(const SwitchCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  void on_text_command_request(const TextCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  void on_time_command_request(const TimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  void on_update_command_request(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  void on_valve_command_request(const ValveCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -14,96 +14,6 @@
 | 
			
		||||
#include "esphome/components/logger/logger.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
#include "esp_heap_trace.h"
 | 
			
		||||
#include "esp_heap_caps.h"
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#include "freertos/task.h"
 | 
			
		||||
 | 
			
		||||
// Forward declare heap tracing functions that will be used in the API class
 | 
			
		||||
extern "C" void start_heap_trace();
 | 
			
		||||
extern "C" void stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
// Task heap information tracking
 | 
			
		||||
extern "C" void dump_task_heap_info() {
 | 
			
		||||
  // Get basic heap statistics
 | 
			
		||||
  multi_heap_info_t info;
 | 
			
		||||
  heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "=== Task Heap Information ===");
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
  ESP_LOGI("HEAP", "Total free bytes: %u", info.total_free_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Total allocated bytes: %u", info.total_allocated_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Minimum free bytes: %u", info.minimum_free_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Largest free block: %u", info.largest_free_block);
 | 
			
		||||
  ESP_LOGI("HEAP", "Free blocks: %u", info.free_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "Allocated blocks: %u", info.allocated_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "Total blocks: %u", info.total_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
  // Get information about running tasks with a much larger buffer to prevent overflow
 | 
			
		||||
  // The FreeRTOS functions don't provide a way to check buffer size requirements in advance
 | 
			
		||||
  static char buffer[2048];
 | 
			
		||||
 | 
			
		||||
  // Zero out the buffer for safety
 | 
			
		||||
  memset(buffer, 0, sizeof(buffer));
 | 
			
		||||
 | 
			
		||||
  // Get task list
 | 
			
		||||
  vTaskList(buffer);
 | 
			
		||||
 | 
			
		||||
  // Check if buffer has valid content
 | 
			
		||||
  if (buffer[0] != '\0') {
 | 
			
		||||
    ESP_LOGI("HEAP", "Task Information:");
 | 
			
		||||
    ESP_LOGI("HEAP", "Name          State  Priority  Stack   Num");
 | 
			
		||||
    ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
    // Process the buffer line by line to add the log prefix to each line
 | 
			
		||||
    char *line = strtok(buffer, "\n\r");
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    while (line != nullptr && strlen(line) > 0 && count < 20) {
 | 
			
		||||
      ESP_LOGI("HEAP", "%s", line);
 | 
			
		||||
      line = strtok(nullptr, "\n\r");
 | 
			
		||||
      count++;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE("HEAP", "Could not get task information");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
  // Runtime statistics - use a separate section with a different buffer to avoid corruption
 | 
			
		||||
  static char stats_buffer[2048];
 | 
			
		||||
  memset(stats_buffer, 0, sizeof(stats_buffer));
 | 
			
		||||
 | 
			
		||||
  // Get runtime stats
 | 
			
		||||
  vTaskGetRunTimeStats(stats_buffer);
 | 
			
		||||
 | 
			
		||||
  // Check if buffer has valid content
 | 
			
		||||
  if (stats_buffer[0] != '\0') {
 | 
			
		||||
    ESP_LOGI("HEAP", "Task Runtime Statistics:");
 | 
			
		||||
    ESP_LOGI("HEAP", "Name          Time      Percentage");
 | 
			
		||||
    ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
    // Process the runtime stats buffer line by line safely
 | 
			
		||||
    char *line = strtok(stats_buffer, "\n\r");
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    // Limit to 20 lines to prevent buffer overruns
 | 
			
		||||
    while (line != nullptr && count < 20) {
 | 
			
		||||
      // Skip empty lines
 | 
			
		||||
      if (strlen(line) > 0) {
 | 
			
		||||
        ESP_LOGI("HEAP", "%s", line);
 | 
			
		||||
      }
 | 
			
		||||
      line = strtok(nullptr, "\n\r");
 | 
			
		||||
      count++;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE("HEAP", "Could not get task runtime statistics");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -120,11 +30,6 @@ void APIServer::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
 | 
			
		||||
  this->setup_controller();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  ESP_LOGI(TAG, "Initializing heap tracing");
 | 
			
		||||
  start_heap_trace();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  uint32_t hash = 88491486UL;
 | 
			
		||||
 | 
			
		||||
@@ -221,19 +126,29 @@ void APIServer::loop() {
 | 
			
		||||
    conn->start();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Partition clients into remove and active
 | 
			
		||||
  auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
 | 
			
		||||
                                [](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
 | 
			
		||||
  // print disconnection messages
 | 
			
		||||
  for (auto it = new_end; it != this->clients_.end(); ++it) {
 | 
			
		||||
    this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_);
 | 
			
		||||
    ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
 | 
			
		||||
  }
 | 
			
		||||
  // resize vector
 | 
			
		||||
  this->clients_.erase(new_end, this->clients_.end());
 | 
			
		||||
  // Process clients and remove disconnected ones in a single pass
 | 
			
		||||
  if (!this->clients_.empty()) {
 | 
			
		||||
    size_t client_index = 0;
 | 
			
		||||
    while (client_index < this->clients_.size()) {
 | 
			
		||||
      auto &client = this->clients_[client_index];
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
      if (client->remove_) {
 | 
			
		||||
        // Handle disconnection
 | 
			
		||||
        this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
 | 
			
		||||
        ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
 | 
			
		||||
 | 
			
		||||
        // Swap with the last element and pop (avoids expensive vector shifts)
 | 
			
		||||
        if (client_index < this->clients_.size() - 1) {
 | 
			
		||||
          std::swap(this->clients_[client_index], this->clients_.back());
 | 
			
		||||
        }
 | 
			
		||||
        this->clients_.pop_back();
 | 
			
		||||
        // Don't increment client_index since we need to process the swapped element
 | 
			
		||||
      } else {
 | 
			
		||||
        // Process active client
 | 
			
		||||
        client->loop();
 | 
			
		||||
        client_index++;  // Move to next client
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->reboot_timeout_ != 0) {
 | 
			
		||||
@@ -249,24 +164,6 @@ void APIServer::loop() {
 | 
			
		||||
      this->status_clear_warning();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  // Periodically dump heap trace information (every 30 seconds)
 | 
			
		||||
  static uint32_t last_heap_trace_dump = 0;
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (now - last_heap_trace_dump > 30000) {  // 30 seconds
 | 
			
		||||
    ESP_LOGI(TAG, "Dumping heap trace information");
 | 
			
		||||
    stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
    // Also dump task-specific heap information
 | 
			
		||||
    dump_task_heap_info();
 | 
			
		||||
 | 
			
		||||
    // Start a new trace for the next period
 | 
			
		||||
    start_heap_trace();
 | 
			
		||||
 | 
			
		||||
    last_heap_trace_dump = now;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::dump_config() {
 | 
			
		||||
@@ -579,16 +476,6 @@ void APIServer::on_shutdown() {
 | 
			
		||||
    c->send_disconnect_request(DisconnectRequest());
 | 
			
		||||
  }
 | 
			
		||||
  delay(10);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  // Make sure to stop tracing on shutdown to get final results
 | 
			
		||||
  ESP_LOGI(TAG, "Final heap trace dump on shutdown");
 | 
			
		||||
  stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
  // Dump final task heap information
 | 
			
		||||
  ESP_LOGI(TAG, "Final task heap information dump on shutdown");
 | 
			
		||||
  dump_task_heap_info();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -3,5 +3,6 @@ import esphome.codegen as cg
 | 
			
		||||
CODEOWNERS = ["@circuitsetup", "@descipher"]
 | 
			
		||||
 | 
			
		||||
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
 | 
			
		||||
ATM90E32Component = atm90e32_ns.class_("ATM90E32Component", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_ATM90E32_ID = "atm90e32_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
#include "atm90e32.h"
 | 
			
		||||
#include "atm90e32_reg.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
@@ -11,117 +11,86 @@ void ATM90E32Component::loop() {
 | 
			
		||||
  if (this->get_publish_interval_flag_()) {
 | 
			
		||||
    this->set_publish_interval_flag_(false);
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr) {
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].voltage_ = this->get_phase_voltage_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].current_ = this->get_phase_current_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].apparent_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].apparent_power_ = this->get_phase_apparent_power_(phase);
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // After the local store in collected we can publish them trusting they are withing +-1 haardware sampling
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      // After the local store is collected we can publish them trusting they are within +-1 hardware sampling
 | 
			
		||||
      if (this->phase_[phase].voltage_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].voltage_sensor_->publish_state(this->get_local_phase_voltage_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].current_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].current_sensor_->publish_state(this->get_local_phase_current_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].power_sensor_->publish_state(this->get_local_phase_active_power_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].power_factor_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].power_factor_sensor_->publish_state(this->get_local_phase_power_factor_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reactive_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].reactive_power_sensor_->publish_state(this->get_local_phase_reactive_power_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].apparent_power_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].apparent_power_sensor_->publish_state(this->get_local_phase_apparent_power_(phase));
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].forward_active_energy_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_forward_active_energy_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].reverse_active_energy_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_reverse_active_energy_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].phase_angle_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].phase_angle_sensor_->publish_state(this->get_local_phase_angle_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
 | 
			
		||||
        this->phase_[phase].harmonic_active_power_sensor_->publish_state(
 | 
			
		||||
            this->get_local_phase_harmonic_active_power_(phase));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
      if (this->phase_[phase].peak_current_sensor_ != nullptr)
 | 
			
		||||
        this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase));
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
    if (this->freq_sensor_ != nullptr) {
 | 
			
		||||
    if (this->freq_sensor_ != nullptr)
 | 
			
		||||
      this->freq_sensor_->publish_state(this->get_frequency_());
 | 
			
		||||
    }
 | 
			
		||||
    if (this->chip_temperature_sensor_ != nullptr) {
 | 
			
		||||
 | 
			
		||||
    if (this->chip_temperature_sensor_ != nullptr)
 | 
			
		||||
      this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::update() {
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
 | 
			
		||||
@@ -130,82 +99,30 @@ void ATM90E32Component::update() {
 | 
			
		||||
  }
 | 
			
		||||
  this->set_publish_interval_flag_(true);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_calibrations_() {
 | 
			
		||||
  if (enable_offset_calibration_) {
 | 
			
		||||
    this->pref_.load(&this->offset_phase_);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_offset_calibrations() {
 | 
			
		||||
  // Run the calibrations and
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE A
 | 
			
		||||
  this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
 | 
			
		||||
  this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
 | 
			
		||||
  this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE B
 | 
			
		||||
  this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
 | 
			
		||||
  this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
 | 
			
		||||
  this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset
 | 
			
		||||
  // Setup voltage and current calibration offsets for PHASE C
 | 
			
		||||
  this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
 | 
			
		||||
  this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
 | 
			
		||||
  this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset
 | 
			
		||||
  this->pref_.save(&this->offset_phase_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_offset_calibrations() {
 | 
			
		||||
  // Clear the calibrations and
 | 
			
		||||
  this->offset_phase_[PHASEA].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEA].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_);  // C Current offset
 | 
			
		||||
  this->offset_phase_[PHASEB].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEB].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_);  // C Current offset
 | 
			
		||||
  this->offset_phase_[PHASEC].voltage_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_);  // C Voltage offset
 | 
			
		||||
  this->offset_phase_[PHASEC].current_offset_ = 0;
 | 
			
		||||
  this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_);  // C Current offset
 | 
			
		||||
  this->pref_.save(&this->offset_phase_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
 | 
			
		||||
  ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
 | 
			
		||||
           this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  this->check_phase_status();
 | 
			
		||||
  this->check_over_current();
 | 
			
		||||
  this->check_freq_status();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
  if (this->enable_offset_calibration_) {
 | 
			
		||||
    uint32_t hash = fnv1_hash(App.get_friendly_name());
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
 | 
			
		||||
    this->restore_calibrations_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint16_t mmode0 = 0x87;  // 3P4W 50Hz
 | 
			
		||||
  uint16_t high_thresh = 0;
 | 
			
		||||
  uint16_t low_thresh = 0;
 | 
			
		||||
 | 
			
		||||
  if (line_freq_ == 60) {
 | 
			
		||||
    mmode0 |= 1 << 12;  // sets 12th bit to 1, 60Hz
 | 
			
		||||
    // for freq threshold registers
 | 
			
		||||
    high_thresh = 6300;  // 63.00 Hz
 | 
			
		||||
    low_thresh = 5700;   // 57.00 Hz
 | 
			
		||||
  } else {
 | 
			
		||||
    high_thresh = 5300;  // 53.00 Hz
 | 
			
		||||
    low_thresh = 4700;   // 47.00 Hz
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (current_phases_ == 2) {
 | 
			
		||||
@@ -216,33 +133,83 @@ void ATM90E32Component::setup() {
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A);    // Perform soft reset
 | 
			
		||||
  delay(6);                                               // Wait for the minimum 5ms + 1ms
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);  // enable register config access
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
 | 
			
		||||
  if (!this->validate_spi_read_(0x55AA, "setup()")) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_METEREN, 0x0001);        // Enable Metering
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F);  // Peak Detector time ms (15:8), Sag Period ms (7:0)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F);  // Peak Detector time (15:8) 255ms, Sag Period (7:0) 63ms
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861);       // PL Constant MSB (default) = 140625000
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468);       // PL Constant LSB (default)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654);       // ZX2, ZX1, ZX0 pin config
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654);       // Zero crossing (ZX2, ZX1, ZX0) pin config
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_MMODE0, mmode0);         // Mode Config (frequency set in main program)
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_);      // PGA Gain Configuration for Current Channels
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_FREQHITH, high_thresh);  // Frequency high threshold
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_FREQLOTH, low_thresh);   // Frequency low threshold
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C);       // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C);       // All Reactive Startup Power Threshold - 50%
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C);       // All Reactive Startup Power Threshold - 50%
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE);       // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE);       // Each phase Reactive Phase Threshold - 10%
 | 
			
		||||
  // Setup voltage and current gain for PHASE A
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_);  // A Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_);       // A line current gain
 | 
			
		||||
  // Setup voltage and current gain for PHASE B
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_);  // B Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_);       // B line current gain
 | 
			
		||||
  // Setup voltage and current gain for PHASE C
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_);  // C Voltage rms gain
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_);       // C line current gain
 | 
			
		||||
 | 
			
		||||
  if (this->enable_offset_calibration_) {
 | 
			
		||||
    // Initialize flash storage for offset calibrations
 | 
			
		||||
    uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
 | 
			
		||||
    this->restore_offset_calibrations_();
 | 
			
		||||
 | 
			
		||||
    // Initialize flash storage for power offset calibrations
 | 
			
		||||
    uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
 | 
			
		||||
    this->restore_power_offset_calibrations_();
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      this->write16_(this->voltage_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
 | 
			
		||||
      this->write16_(this->current_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->offset_phase_[phase].current_offset_));
 | 
			
		||||
      this->write16_(this->power_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->power_offset_phase_[phase].active_power_offset));
 | 
			
		||||
      this->write16_(this->reactive_power_offset_registers[phase],
 | 
			
		||||
                     static_cast<uint16_t>(this->power_offset_phase_[phase].reactive_power_offset));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->enable_gain_calibration_) {
 | 
			
		||||
    // Initialize flash storage for gain calibration
 | 
			
		||||
    uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
 | 
			
		||||
    this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
 | 
			
		||||
    this->restore_gain_calibrations_();
 | 
			
		||||
 | 
			
		||||
    if (this->using_saved_calibrations_) {
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
 | 
			
		||||
    } else {
 | 
			
		||||
      for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
        this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
 | 
			
		||||
        this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
 | 
			
		||||
      this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Sag threshold (78%)
 | 
			
		||||
  uint16_t sagth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 0.78f);
 | 
			
		||||
  // Overvoltage threshold (122%)
 | 
			
		||||
  uint16_t ovth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 1.22f);
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_OVTH, ovth);
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);  // end configuration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -257,6 +224,7 @@ void ATM90E32Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Current A", this->phase_[PHASEA].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power A", this->phase_[PHASEA].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power A", this->phase_[PHASEA].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
 | 
			
		||||
@@ -267,22 +235,24 @@ void ATM90E32Component::dump_config() {
 | 
			
		||||
  LOG_SENSOR("  ", "Current B", this->phase_[PHASEB].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power B", this->phase_[PHASEB].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power B", this->phase_[PHASEB].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power B", this->phase_[PHASEB].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle B", this->phase_[PHASEB].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current B", this->phase_[PHASEB].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Current C", this->phase_[PHASEC].current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power C", this->phase_[PHASEC].power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Apparent Power C", this->phase_[PHASEC].apparent_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power A", this->phase_[PHASEC].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle A", this->phase_[PHASEC].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current A", this->phase_[PHASEC].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Harmonic Power C", this->phase_[PHASEC].harmonic_active_power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Phase Angle C", this->phase_[PHASEC].phase_angle_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Frequency", this->freq_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Chip Temp", this->chip_temperature_sensor_);
 | 
			
		||||
}
 | 
			
		||||
@@ -298,7 +268,7 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
 | 
			
		||||
  uint8_t data[2];
 | 
			
		||||
  uint16_t output;
 | 
			
		||||
  this->enable();
 | 
			
		||||
  delay_microseconds_safe(10);
 | 
			
		||||
  delay_microseconds_safe(1);  // min delay between CS low and first SCK is 200ns - 1ms is plenty
 | 
			
		||||
  this->write_byte(addrh);
 | 
			
		||||
  this->write_byte(addrl);
 | 
			
		||||
  this->read_array(data, 2);
 | 
			
		||||
@@ -328,8 +298,7 @@ void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
 | 
			
		||||
  this->write_byte16(a_register);
 | 
			
		||||
  this->write_byte16(val);
 | 
			
		||||
  this->disable();
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val);
 | 
			
		||||
  this->validate_spi_read_(val, "write16()");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
 | 
			
		||||
@@ -340,6 +309,8 @@ float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return t
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_apparent_power_(uint8_t phase) { return this->phase_[phase].apparent_power_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_local_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
@@ -360,8 +331,7 @@ float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return t
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_voltage_(uint8_t phase) {
 | 
			
		||||
  const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI URMS voltage register read error.");
 | 
			
		||||
  this->validate_spi_read_(voltage, "get_phase_voltage()");
 | 
			
		||||
  return (float) voltage / 100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -371,8 +341,7 @@ float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
 | 
			
		||||
  uint16_t voltage = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < reads; i++) {
 | 
			
		||||
    voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
 | 
			
		||||
    if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
 | 
			
		||||
      ESP_LOGW(TAG, "SPI URMS voltage register read error.");
 | 
			
		||||
    this->validate_spi_read_(voltage, "get_phase_voltage_avg_()");
 | 
			
		||||
    accumulation += voltage;
 | 
			
		||||
  }
 | 
			
		||||
  voltage = accumulation / reads;
 | 
			
		||||
@@ -386,8 +355,7 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
 | 
			
		||||
  uint16_t current = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < reads; i++) {
 | 
			
		||||
    current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
 | 
			
		||||
    if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
 | 
			
		||||
      ESP_LOGW(TAG, "SPI IRMS current register read error.");
 | 
			
		||||
    this->validate_spi_read_(current, "get_phase_current_avg_()");
 | 
			
		||||
    accumulation += current;
 | 
			
		||||
  }
 | 
			
		||||
  current = accumulation / reads;
 | 
			
		||||
@@ -397,8 +365,7 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_current_(uint8_t phase) {
 | 
			
		||||
  const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI IRMS current register read error.");
 | 
			
		||||
  this->validate_spi_read_(current, "get_phase_current_()");
 | 
			
		||||
  return (float) current / 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -412,11 +379,15 @@ float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) {
 | 
			
		||||
  return val * 0.00032f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_apparent_power_(uint8_t phase) {
 | 
			
		||||
  const int val = this->read32_(ATM90E32_REGISTER_SMEAN + phase, ATM90E32_REGISTER_SMEANLSB + phase);
 | 
			
		||||
  return val * 0.00032f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_power_factor_(uint8_t phase) {
 | 
			
		||||
  const int16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);
 | 
			
		||||
  if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != powerfactor)
 | 
			
		||||
    ESP_LOGW(TAG, "SPI power factor read error.");
 | 
			
		||||
  return (float) powerfactor / 1000;
 | 
			
		||||
  uint16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);  // unsigned to compare to lastspidata
 | 
			
		||||
  this->validate_spi_read_(powerfactor, "get_phase_power_factor_()");
 | 
			
		||||
  return (float) ((int16_t) powerfactor) / 1000;  // make it signed again
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
@@ -426,17 +397,19 @@ float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
 | 
			
		||||
  } else {
 | 
			
		||||
    this->phase_[phase].cumulative_forward_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_forward_active_energy_ * 10 / 3200);
 | 
			
		||||
  // 0.01CF resolution = 0.003125 Wh per count
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_forward_active_energy_ * (10.0f / 3200.0f));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_reverse_active_energy_(uint8_t phase) {
 | 
			
		||||
  const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY);
 | 
			
		||||
  const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY + phase);
 | 
			
		||||
  if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
 | 
			
		||||
    this->phase_[phase].cumulative_reverse_active_energy_ += val;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->phase_[phase].cumulative_reverse_active_energy_ = val;
 | 
			
		||||
  }
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200);
 | 
			
		||||
  // 0.01CF resolution = 0.003125 Wh per count
 | 
			
		||||
  return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * (10.0f / 3200.0f));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
 | 
			
		||||
@@ -446,15 +419,15 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
 | 
			
		||||
  uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
 | 
			
		||||
  return (float) (val > 180) ? val - 360.0 : val;
 | 
			
		||||
  return (val > 180) ? (float) (val - 360.0f) : (float) val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
 | 
			
		||||
  int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
 | 
			
		||||
  if (!this->peak_current_signed_)
 | 
			
		||||
    val = abs(val);
 | 
			
		||||
    val = std::abs(val);
 | 
			
		||||
  // phase register * phase current gain value  / 1000 * 2^13
 | 
			
		||||
  return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
 | 
			
		||||
  return (val * this->phase_[phase].ct_gain_ / 8192000.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ATM90E32Component::get_frequency_() {
 | 
			
		||||
@@ -467,29 +440,433 @@ float ATM90E32Component::get_chip_temperature_() {
 | 
			
		||||
  return (float) ctemp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) {
 | 
			
		||||
  const uint8_t num_reads = 5;
 | 
			
		||||
  uint64_t total_value = 0;
 | 
			
		||||
  for (int i = 0; i < num_reads; ++i) {
 | 
			
		||||
    const uint32_t measurement_value = read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase);
 | 
			
		||||
    total_value += measurement_value;
 | 
			
		||||
  }
 | 
			
		||||
  const uint32_t average_value = total_value / num_reads;
 | 
			
		||||
  const uint32_t shifted_value = average_value >> 7;
 | 
			
		||||
  const uint32_t voltage_offset = ~shifted_value + 1;
 | 
			
		||||
  return voltage_offset & 0xFFFF;  // Take the lower 16 bits
 | 
			
		||||
void ATM90E32Component::run_gain_calibrations() {
 | 
			
		||||
  if (!this->enable_gain_calibration_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) {
 | 
			
		||||
  float ref_voltages[3] = {
 | 
			
		||||
      this->get_reference_voltage(0),
 | 
			
		||||
      this->get_reference_voltage(1),
 | 
			
		||||
      this->get_reference_voltage(2),
 | 
			
		||||
  };
 | 
			
		||||
  float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
 | 
			
		||||
                           this->get_reference_current(2)};
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration  =========================");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
 | 
			
		||||
  ESP_LOGI(TAG,
 | 
			
		||||
           "[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref  | V_gain (old→new) | I_gain (old→new) |");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    float measured_voltage = this->get_phase_voltage_avg_(phase);
 | 
			
		||||
    float measured_current = this->get_phase_current_avg_(phase);
 | 
			
		||||
 | 
			
		||||
    float ref_voltage = ref_voltages[phase];
 | 
			
		||||
    float ref_current = ref_currents[phase];
 | 
			
		||||
 | 
			
		||||
    uint16_t current_voltage_gain = this->read16_(voltage_gain_registers[phase]);
 | 
			
		||||
    uint16_t current_current_gain = this->read16_(current_gain_registers[phase]);
 | 
			
		||||
 | 
			
		||||
    bool did_voltage = false;
 | 
			
		||||
    bool did_current = false;
 | 
			
		||||
 | 
			
		||||
    // Voltage calibration
 | 
			
		||||
    if (ref_voltage <= 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else if (measured_voltage == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
 | 
			
		||||
      if (new_voltage_gain == 0) {
 | 
			
		||||
        ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
 | 
			
		||||
                 phase_labels[phase]);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (new_voltage_gain >= 65535) {
 | 
			
		||||
          ESP_LOGW(
 | 
			
		||||
              TAG,
 | 
			
		||||
              "[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
 | 
			
		||||
              phase_labels[phase]);
 | 
			
		||||
          new_voltage_gain = 65535;
 | 
			
		||||
        }
 | 
			
		||||
        this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
 | 
			
		||||
        did_voltage = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Current calibration
 | 
			
		||||
    if (ref_current == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else if (measured_current == 0.0f) {
 | 
			
		||||
      ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
 | 
			
		||||
               phase_labels[phase]);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
 | 
			
		||||
      if (new_current_gain == 0) {
 | 
			
		||||
        ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
 | 
			
		||||
                 phase_labels[phase]);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (new_current_gain >= 65535) {
 | 
			
		||||
          ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
 | 
			
		||||
                   phase_labels[phase]);
 | 
			
		||||
          new_current_gain = 65535;
 | 
			
		||||
        }
 | 
			
		||||
        this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
 | 
			
		||||
        did_current = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Final row output
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] |   %c   |  %9.2f |  %9.4f | %5.2f | %6.4f |  %5u → %-5u  |  %5u → %-5u  |",
 | 
			
		||||
             'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
 | 
			
		||||
             did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
 | 
			
		||||
             did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
 | 
			
		||||
 | 
			
		||||
  this->save_gain_calibration_to_memory_();
 | 
			
		||||
  this->write_gains_to_registers_();
 | 
			
		||||
  this->verify_gain_writes_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
 | 
			
		||||
  bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
 | 
			
		||||
  if (success) {
 | 
			
		||||
    this->using_saved_calibrations_ = true;
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
 | 
			
		||||
  } else {
 | 
			
		||||
    this->using_saved_calibrations_ = false;
 | 
			
		||||
    ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_offset_calibrations() {
 | 
			
		||||
  if (!this->enable_offset_calibration_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    int16_t voltage_offset = calibrate_offset(phase, true);
 | 
			
		||||
    int16_t current_offset = calibrate_offset(phase, false);
 | 
			
		||||
 | 
			
		||||
    this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
 | 
			
		||||
             current_offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->offset_pref_.save(&this->offset_phase_);  // Save to flash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::run_power_offset_calibrations() {
 | 
			
		||||
  if (!this->enable_offset_calibration_) {
 | 
			
		||||
    ESP_LOGW(
 | 
			
		||||
        TAG,
 | 
			
		||||
        "[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
    int16_t active_offset = calibrate_power_offset(phase, false);
 | 
			
		||||
    int16_t reactive_offset = calibrate_power_offset(phase, true);
 | 
			
		||||
 | 
			
		||||
    this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
 | 
			
		||||
             active_offset, reactive_offset);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->power_offset_pref_.save(&this->power_offset_phase_);  // Save to flash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_gains_to_registers_() {
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write16_(voltage_gain_registers[phase], this->gain_phase_[phase].voltage_gain);
 | 
			
		||||
    this->write16_(current_gain_registers[phase], this->gain_phase_[phase].current_gain);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset) {
 | 
			
		||||
  // Save to runtime
 | 
			
		||||
  this->offset_phase_[phase].voltage_offset_ = voltage_offset;
 | 
			
		||||
  this->phase_[phase].voltage_offset_ = voltage_offset;
 | 
			
		||||
 | 
			
		||||
  // Save to flash-storable struct
 | 
			
		||||
  this->offset_phase_[phase].current_offset_ = current_offset;
 | 
			
		||||
  this->phase_[phase].current_offset_ = current_offset;
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
  this->write16_(voltage_offset_registers[phase], static_cast<uint16_t>(voltage_offset));
 | 
			
		||||
  this->write16_(current_offset_registers[phase], static_cast<uint16_t>(current_offset));
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset) {
 | 
			
		||||
  // Save to runtime
 | 
			
		||||
  this->phase_[phase].active_power_offset_ = p_offset;
 | 
			
		||||
  this->phase_[phase].reactive_power_offset_ = q_offset;
 | 
			
		||||
 | 
			
		||||
  // Save to flash-storable struct
 | 
			
		||||
  this->power_offset_phase_[phase].active_power_offset = p_offset;
 | 
			
		||||
  this->power_offset_phase_[phase].reactive_power_offset = q_offset;
 | 
			
		||||
 | 
			
		||||
  // Write to registers
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
 | 
			
		||||
  this->write16_(this->power_offset_registers[phase], static_cast<uint16_t>(p_offset));
 | 
			
		||||
  this->write16_(this->reactive_power_offset_registers[phase], static_cast<uint16_t>(q_offset));
 | 
			
		||||
  this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_gain_calibrations_() {
 | 
			
		||||
  if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
 | 
			
		||||
      uint16_t i_gain = this->gain_phase_[phase].current_gain;
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION]   Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->write_gains_to_registers_();
 | 
			
		||||
 | 
			
		||||
    if (this->verify_gain_writes_()) {
 | 
			
		||||
      this->using_saved_calibrations_ = true;
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
 | 
			
		||||
    } else {
 | 
			
		||||
      this->using_saved_calibrations_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->using_saved_calibrations_ = false;
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_offset_calibrations_() {
 | 
			
		||||
  if (this->offset_pref_.load(&this->offset_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
      auto &offset = this->offset_phase_[phase];
 | 
			
		||||
      write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
 | 
			
		||||
               offset.voltage_offset_, offset.current_offset_);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::restore_power_offset_calibrations_() {
 | 
			
		||||
  if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
 | 
			
		||||
 | 
			
		||||
    for (uint8_t phase = 0; phase < 3; ++phase) {
 | 
			
		||||
      auto &offset = this->power_offset_phase_[phase];
 | 
			
		||||
      write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
 | 
			
		||||
               offset.active_power_offset, offset.reactive_power_offset);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_gain_calibrations() {
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values...");
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
 | 
			
		||||
    gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
 | 
			
		||||
  this->using_saved_calibrations_ = false;
 | 
			
		||||
 | 
			
		||||
  if (success) {
 | 
			
		||||
    ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
 | 
			
		||||
    for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
      ESP_LOGI(TAG, "[CALIBRATION]   Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
 | 
			
		||||
               gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_gains_to_registers_();  // Apply them to the chip immediately
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_offset_calibrations() {
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write_offsets_to_registers_(phase, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->offset_pref_.save(&this->offset_phase_);  // Save cleared values to flash memory
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::clear_power_offset_calibrations() {
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    this->write_power_offsets_to_registers_(phase, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->power_offset_pref_.save(&this->power_offset_phase_);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
 | 
			
		||||
  const uint8_t num_reads = 5;
 | 
			
		||||
  uint64_t total_value = 0;
 | 
			
		||||
  for (int i = 0; i < num_reads; ++i) {
 | 
			
		||||
    const uint32_t measurement_value = read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
 | 
			
		||||
    total_value += measurement_value;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < num_reads; ++i) {
 | 
			
		||||
    uint32_t reading = voltage ? this->read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase)
 | 
			
		||||
                               : this->read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
 | 
			
		||||
    total_value += reading;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t average_value = total_value / num_reads;
 | 
			
		||||
  const uint32_t current_offset = ~average_value + 1;
 | 
			
		||||
  return current_offset & 0xFFFF;  // Take the lower 16 bits
 | 
			
		||||
  const uint32_t shifted = average_value >> 7;
 | 
			
		||||
  const uint32_t offset = ~shifted + 1;
 | 
			
		||||
  return static_cast<int16_t>(offset);  // Takes lower 16 bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
 | 
			
		||||
  const uint8_t num_reads = 5;
 | 
			
		||||
  uint64_t total_value = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < num_reads; ++i) {
 | 
			
		||||
    uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
 | 
			
		||||
                                : this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
 | 
			
		||||
    total_value += reading;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint32_t average_value = total_value / num_reads;
 | 
			
		||||
  const uint32_t power_offset = ~average_value + 1;
 | 
			
		||||
  return static_cast<int16_t>(power_offset);  // Takes the lower 16 bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATM90E32Component::verify_gain_writes_() {
 | 
			
		||||
  bool success = true;
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
 | 
			
		||||
    uint16_t read_current = this->read16_(current_gain_registers[phase]);
 | 
			
		||||
 | 
			
		||||
    if (read_voltage != this->gain_phase_[phase].voltage_gain ||
 | 
			
		||||
        read_current != this->gain_phase_[phase].current_gain) {
 | 
			
		||||
      ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
 | 
			
		||||
      success = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return success;  // Return true if all writes were successful, false otherwise
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
void ATM90E32Component::check_phase_status() {
 | 
			
		||||
  uint16_t state0 = this->read16_(ATM90E32_REGISTER_EMMSTATE0);
 | 
			
		||||
  uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
 | 
			
		||||
 | 
			
		||||
  for (int phase = 0; phase < 3; phase++) {
 | 
			
		||||
    std::string status;
 | 
			
		||||
 | 
			
		||||
    if (state0 & over_voltage_flags[phase])
 | 
			
		||||
      status += "Over Voltage; ";
 | 
			
		||||
    if (state1 & voltage_sag_flags[phase])
 | 
			
		||||
      status += "Voltage Sag; ";
 | 
			
		||||
    if (state1 & phase_loss_flags[phase])
 | 
			
		||||
      status += "Phase Loss; ";
 | 
			
		||||
 | 
			
		||||
    auto *sensor = this->phase_status_text_sensor_[phase];
 | 
			
		||||
    const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
 | 
			
		||||
    if (!status.empty()) {
 | 
			
		||||
      status.pop_back();  // remove space
 | 
			
		||||
      status.pop_back();  // remove semicolon
 | 
			
		||||
      ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
 | 
			
		||||
      if (sensor != nullptr)
 | 
			
		||||
        sensor->publish_state(status);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (sensor != nullptr)
 | 
			
		||||
        sensor->publish_state("Okay");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::check_freq_status() {
 | 
			
		||||
  uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
 | 
			
		||||
 | 
			
		||||
  std::string freq_status;
 | 
			
		||||
 | 
			
		||||
  if (state1 & ATM90E32_STATUS_S1_FREQHIST) {
 | 
			
		||||
    freq_status = "HIGH";
 | 
			
		||||
  } else if (state1 & ATM90E32_STATUS_S1_FREQLOST) {
 | 
			
		||||
    freq_status = "LOW";
 | 
			
		||||
  } else {
 | 
			
		||||
    freq_status = "Normal";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
 | 
			
		||||
 | 
			
		||||
  if (this->freq_status_text_sensor_ != nullptr) {
 | 
			
		||||
    this->freq_status_text_sensor_->publish_state(freq_status);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32Component::check_over_current() {
 | 
			
		||||
  constexpr float max_current_threshold = 65.53f;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t phase = 0; phase < 3; phase++) {
 | 
			
		||||
    float current_val =
 | 
			
		||||
        this->phase_[phase].current_sensor_ != nullptr ? this->phase_[phase].current_sensor_->state : 0.0f;
 | 
			
		||||
 | 
			
		||||
    if (current_val > max_current_threshold) {
 | 
			
		||||
      ESP_LOGW(TAG, "Over current detected on Phase %c: %.2f A", 'A' + phase, current_val);
 | 
			
		||||
      ESP_LOGW(TAG, "You may need to half your gain_ct: value & multiply the current and power values by 2");
 | 
			
		||||
      if (this->phase_status_text_sensor_[phase] != nullptr) {
 | 
			
		||||
        this->phase_status_text_sensor_[phase]->publish_state("Over Current; ");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) {
 | 
			
		||||
  // this assumes that 60Hz electrical systems use 120V mains,
 | 
			
		||||
  // which is usually, but not always the case
 | 
			
		||||
  float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
 | 
			
		||||
  float target_voltage = nominal_voltage * multiplier;
 | 
			
		||||
 | 
			
		||||
  float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f);  // convert RMS → peak, scale to 0.01V
 | 
			
		||||
  float divider = (2.0f * ugain) / 32768.0f;
 | 
			
		||||
 | 
			
		||||
  float threshold = peak_01v / divider;
 | 
			
		||||
 | 
			
		||||
  return static_cast<uint16_t>(threshold);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATM90E32Component::validate_spi_read_(uint16_t expected, const char *context) {
 | 
			
		||||
  uint16_t last = this->read16_(ATM90E32_REGISTER_LASTSPIDATA);
 | 
			
		||||
  if (last != expected) {
 | 
			
		||||
    if (context != nullptr) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] SPI read mismatch: expected 0x%04X, got 0x%04X", context, expected, last);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "SPI read mismatch: expected 0x%04X, got 0x%04X", expected, last);
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include "atm90e32_reg.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
@@ -18,6 +19,26 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  static const uint8_t PHASEA = 0;
 | 
			
		||||
  static const uint8_t PHASEB = 1;
 | 
			
		||||
  static const uint8_t PHASEC = 2;
 | 
			
		||||
  const char *phase_labels[3] = {"A", "B", "C"};
 | 
			
		||||
  // these registers are not sucessive, so we can't just do 'base + phase'
 | 
			
		||||
  const uint16_t voltage_gain_registers[3] = {ATM90E32_REGISTER_UGAINA, ATM90E32_REGISTER_UGAINB,
 | 
			
		||||
                                              ATM90E32_REGISTER_UGAINC};
 | 
			
		||||
  const uint16_t current_gain_registers[3] = {ATM90E32_REGISTER_IGAINA, ATM90E32_REGISTER_IGAINB,
 | 
			
		||||
                                              ATM90E32_REGISTER_IGAINC};
 | 
			
		||||
  const uint16_t voltage_offset_registers[3] = {ATM90E32_REGISTER_UOFFSETA, ATM90E32_REGISTER_UOFFSETB,
 | 
			
		||||
                                                ATM90E32_REGISTER_UOFFSETC};
 | 
			
		||||
  const uint16_t current_offset_registers[3] = {ATM90E32_REGISTER_IOFFSETA, ATM90E32_REGISTER_IOFFSETB,
 | 
			
		||||
                                                ATM90E32_REGISTER_IOFFSETC};
 | 
			
		||||
  const uint16_t power_offset_registers[3] = {ATM90E32_REGISTER_POFFSETA, ATM90E32_REGISTER_POFFSETB,
 | 
			
		||||
                                              ATM90E32_REGISTER_POFFSETC};
 | 
			
		||||
  const uint16_t reactive_power_offset_registers[3] = {ATM90E32_REGISTER_QOFFSETA, ATM90E32_REGISTER_QOFFSETB,
 | 
			
		||||
                                                       ATM90E32_REGISTER_QOFFSETC};
 | 
			
		||||
  const uint16_t over_voltage_flags[3] = {ATM90E32_STATUS_S0_OVPHASEAST, ATM90E32_STATUS_S0_OVPHASEBST,
 | 
			
		||||
                                          ATM90E32_STATUS_S0_OVPHASECST};
 | 
			
		||||
  const uint16_t voltage_sag_flags[3] = {ATM90E32_STATUS_S1_SAGPHASEAST, ATM90E32_STATUS_S1_SAGPHASEBST,
 | 
			
		||||
                                         ATM90E32_STATUS_S1_SAGPHASECST};
 | 
			
		||||
  const uint16_t phase_loss_flags[3] = {ATM90E32_STATUS_S1_PHASELOSSAST, ATM90E32_STATUS_S1_PHASELOSSBST,
 | 
			
		||||
                                        ATM90E32_STATUS_S1_PHASELOSSCST};
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -42,6 +63,14 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
 | 
			
		||||
  void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
 | 
			
		||||
  void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
 | 
			
		||||
  void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
 | 
			
		||||
  void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
 | 
			
		||||
  void set_active_power_offset(uint8_t phase, int16_t offset) {
 | 
			
		||||
    this->power_offset_phase_[phase].active_power_offset = offset;
 | 
			
		||||
  }
 | 
			
		||||
  void set_reactive_power_offset(uint8_t phase, int16_t offset) {
 | 
			
		||||
    this->power_offset_phase_[phase].reactive_power_offset = offset;
 | 
			
		||||
  }
 | 
			
		||||
  void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
 | 
			
		||||
  void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
 | 
			
		||||
  void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
 | 
			
		||||
@@ -51,53 +80,104 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  void set_current_phases(int phases) { current_phases_ = phases; }
 | 
			
		||||
  void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
 | 
			
		||||
  void run_offset_calibrations();
 | 
			
		||||
  void run_power_offset_calibrations();
 | 
			
		||||
  void clear_offset_calibrations();
 | 
			
		||||
  void clear_power_offset_calibrations();
 | 
			
		||||
  void clear_gain_calibrations();
 | 
			
		||||
  void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
 | 
			
		||||
  uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
 | 
			
		||||
  uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
 | 
			
		||||
  void set_enable_gain_calibration(bool flag) { enable_gain_calibration_ = flag; }
 | 
			
		||||
  int16_t calibrate_offset(uint8_t phase, bool voltage);
 | 
			
		||||
  int16_t calibrate_power_offset(uint8_t phase, bool reactive);
 | 
			
		||||
  void run_gain_calibrations();
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  void set_reference_voltage(uint8_t phase, number::Number *ref_voltage) { ref_voltages_[phase] = ref_voltage; }
 | 
			
		||||
  void set_reference_current(uint8_t phase, number::Number *ref_current) { ref_currents_[phase] = ref_current; }
 | 
			
		||||
#endif
 | 
			
		||||
  float get_reference_voltage(uint8_t phase) {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    return (phase >= 0 && phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0;  // Default voltage
 | 
			
		||||
#else
 | 
			
		||||
    return 120.0;  // Default voltage
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  float get_reference_current(uint8_t phase) {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
    return (phase >= 0 && phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f;  // Default current
 | 
			
		||||
#else
 | 
			
		||||
    return 5.0f;   // Default current
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  bool using_saved_calibrations_ = false;  // Track if stored calibrations are being used
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  void check_phase_status();
 | 
			
		||||
  void check_freq_status();
 | 
			
		||||
  void check_over_current();
 | 
			
		||||
  void set_phase_status_text_sensor(uint8_t phase, text_sensor::TextSensor *sensor) {
 | 
			
		||||
    this->phase_status_text_sensor_[phase] = sensor;
 | 
			
		||||
  }
 | 
			
		||||
  void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
 | 
			
		||||
#endif
 | 
			
		||||
  uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
 | 
			
		||||
  int32_t last_periodic_millis = millis();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  number::Number *ref_voltages_[3]{nullptr, nullptr, nullptr};
 | 
			
		||||
  number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
  uint16_t read16_(uint16_t a_register);
 | 
			
		||||
  int read32_(uint16_t addr_h, uint16_t addr_l);
 | 
			
		||||
  void write16_(uint16_t a_register, uint16_t val);
 | 
			
		||||
  float get_local_phase_voltage_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_reactive_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_power_factor_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_angle_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_peak_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_voltage_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_voltage_avg_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_current_avg_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_reactive_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_power_factor_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_forward_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_reverse_active_energy_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_angle_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_harmonic_active_power_(uint8_t /*phase*/);
 | 
			
		||||
  float get_phase_peak_current_(uint8_t /*phase*/);
 | 
			
		||||
  float get_local_phase_voltage_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_current_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_active_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_reactive_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_apparent_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_power_factor_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_forward_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_reverse_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_angle_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_harmonic_active_power_(uint8_t phase);
 | 
			
		||||
  float get_local_phase_peak_current_(uint8_t phase);
 | 
			
		||||
  float get_phase_voltage_(uint8_t phase);
 | 
			
		||||
  float get_phase_voltage_avg_(uint8_t phase);
 | 
			
		||||
  float get_phase_current_(uint8_t phase);
 | 
			
		||||
  float get_phase_current_avg_(uint8_t phase);
 | 
			
		||||
  float get_phase_active_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_reactive_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_apparent_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_power_factor_(uint8_t phase);
 | 
			
		||||
  float get_phase_forward_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_phase_reverse_active_energy_(uint8_t phase);
 | 
			
		||||
  float get_phase_angle_(uint8_t phase);
 | 
			
		||||
  float get_phase_harmonic_active_power_(uint8_t phase);
 | 
			
		||||
  float get_phase_peak_current_(uint8_t phase);
 | 
			
		||||
  float get_frequency_();
 | 
			
		||||
  float get_chip_temperature_();
 | 
			
		||||
  bool get_publish_interval_flag_() { return publish_interval_flag_; };
 | 
			
		||||
  void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
 | 
			
		||||
  void restore_calibrations_();
 | 
			
		||||
  void restore_offset_calibrations_();
 | 
			
		||||
  void restore_power_offset_calibrations_();
 | 
			
		||||
  void restore_gain_calibrations_();
 | 
			
		||||
  void save_gain_calibration_to_memory_();
 | 
			
		||||
  void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
 | 
			
		||||
  void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
 | 
			
		||||
  void write_gains_to_registers_();
 | 
			
		||||
  bool verify_gain_writes_();
 | 
			
		||||
  bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
 | 
			
		||||
 | 
			
		||||
  struct ATM90E32Phase {
 | 
			
		||||
    uint16_t voltage_gain_{0};
 | 
			
		||||
    uint16_t ct_gain_{0};
 | 
			
		||||
    uint16_t voltage_offset_{0};
 | 
			
		||||
    uint16_t current_offset_{0};
 | 
			
		||||
    int16_t voltage_offset_{0};
 | 
			
		||||
    int16_t current_offset_{0};
 | 
			
		||||
    int16_t active_power_offset_{0};
 | 
			
		||||
    int16_t reactive_power_offset_{0};
 | 
			
		||||
    float voltage_{0};
 | 
			
		||||
    float current_{0};
 | 
			
		||||
    float active_power_{0};
 | 
			
		||||
    float reactive_power_{0};
 | 
			
		||||
    float apparent_power_{0};
 | 
			
		||||
    float power_factor_{0};
 | 
			
		||||
    float forward_active_energy_{0};
 | 
			
		||||
    float reverse_active_energy_{0};
 | 
			
		||||
@@ -119,14 +199,30 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
    uint32_t cumulative_reverse_active_energy_{0};
 | 
			
		||||
  } phase_[3];
 | 
			
		||||
 | 
			
		||||
  struct Calibration {
 | 
			
		||||
    uint16_t voltage_offset_{0};
 | 
			
		||||
    uint16_t current_offset_{0};
 | 
			
		||||
  struct OffsetCalibration {
 | 
			
		||||
    int16_t voltage_offset_{0};
 | 
			
		||||
    int16_t current_offset_{0};
 | 
			
		||||
  } offset_phase_[3];
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject pref_;
 | 
			
		||||
  struct PowerOffsetCalibration {
 | 
			
		||||
    int16_t active_power_offset{0};
 | 
			
		||||
    int16_t reactive_power_offset{0};
 | 
			
		||||
  } power_offset_phase_[3];
 | 
			
		||||
 | 
			
		||||
  struct GainCalibration {
 | 
			
		||||
    uint16_t voltage_gain{1};
 | 
			
		||||
    uint16_t current_gain{1};
 | 
			
		||||
  } gain_phase_[3];
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject offset_pref_;
 | 
			
		||||
  ESPPreferenceObject power_offset_pref_;
 | 
			
		||||
  ESPPreferenceObject gain_calibration_pref_;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *freq_sensor_{nullptr};
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  text_sensor::TextSensor *phase_status_text_sensor_[3]{nullptr};
 | 
			
		||||
  text_sensor::TextSensor *freq_status_text_sensor_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
  sensor::Sensor *chip_temperature_sensor_{nullptr};
 | 
			
		||||
  uint16_t pga_gain_{0x15};
 | 
			
		||||
  int line_freq_{60};
 | 
			
		||||
@@ -134,6 +230,7 @@ class ATM90E32Component : public PollingComponent,
 | 
			
		||||
  bool publish_interval_flag_{false};
 | 
			
		||||
  bool peak_current_signed_{false};
 | 
			
		||||
  bool enable_offset_calibration_{false};
 | 
			
		||||
  bool enable_gain_calibration_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
 
 | 
			
		||||
@@ -176,16 +176,17 @@ static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF;  // C Reverse Harm. E
 | 
			
		||||
 | 
			
		||||
/* POWER & P.F. REGISTERS */
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0;   // Total Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1;    // Mean Power Reg Base (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1;    // Active Power Reg Base (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1;   // A Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2;   // B Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3;   // C Mean Power (P)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4;   // Total Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5;    // Mean Power Reg Base (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5;    // Reactive Power Reg Base (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5;   // A Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6;   // B Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7;   // C Mean Power (Q)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANT = 0xB8;   // Total Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEAN = 0xB9;    // Apparent Mean Power Base (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9;   // A Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA;   // B Mean Power (S)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB;   // C Mean Power (S)
 | 
			
		||||
@@ -206,6 +207,7 @@ static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5;   // Lower Word (A Rea
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6;   // Lower Word (B React. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7;   // Lower Word (C React. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SAMEANTLSB = 0xC8;  // Lower Word (Tot. App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANLSB = 0xC9;    // Lower Word Reg Base (Apparent Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANALSB = 0xC9;   // Lower Word (A App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANBLSB = 0xCA;   // Lower Word (B App. Power)
 | 
			
		||||
static const uint16_t ATM90E32_REGISTER_SMEANCLSB = 0xCB;   // Lower Word (C App. Power)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,95 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import button
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
 | 
			
		||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
 | 
			
		||||
 | 
			
		||||
from .. import atm90e32_ns
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
CONF_RUN_GAIN_CALIBRATION = "run_gain_calibration"
 | 
			
		||||
CONF_CLEAR_GAIN_CALIBRATION = "clear_gain_calibration"
 | 
			
		||||
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
 | 
			
		||||
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
 | 
			
		||||
CONF_RUN_POWER_OFFSET_CALIBRATION = "run_power_offset_calibration"
 | 
			
		||||
CONF_CLEAR_POWER_OFFSET_CALIBRATION = "clear_power_offset_calibration"
 | 
			
		||||
 | 
			
		||||
ATM90E32CalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32CalibrationButton",
 | 
			
		||||
    button.Button,
 | 
			
		||||
ATM90E32GainCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32GainCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearCalibrationButton",
 | 
			
		||||
    button.Button,
 | 
			
		||||
ATM90E32ClearGainCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearGainCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32OffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32OffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearOffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32PowerOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32PowerOffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
ATM90E32ClearPowerOffsetCalibrationButton = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32ClearPowerOffsetCalibrationButton", button.Button
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = {
 | 
			
		||||
    cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
 | 
			
		||||
    cv.Optional(CONF_RUN_GAIN_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32GainCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:scale-balance",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_GAIN_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearGainCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32CalibrationButton,
 | 
			
		||||
        ATM90E32OffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon=ICON_SCALE,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearCalibrationButton,
 | 
			
		||||
        ATM90E32ClearOffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon=ICON_CHIP,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_RUN_POWER_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32PowerOffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon=ICON_SCALE,
 | 
			
		||||
    ),
 | 
			
		||||
    cv.Optional(CONF_CLEAR_POWER_OFFSET_CALIBRATION): button.button_schema(
 | 
			
		||||
        ATM90E32ClearPowerOffsetCalibrationButton,
 | 
			
		||||
        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        icon="mdi:delete",
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if run_gain := config.get(CONF_RUN_GAIN_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_gain)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_gain := config.get(CONF_CLEAR_GAIN_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_gain)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_offset)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_offset)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if run_power := config.get(CONF_RUN_POWER_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(run_power)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 | 
			
		||||
    if clear_power := config.get(CONF_CLEAR_POWER_OFFSET_CALIBRATION):
 | 
			
		||||
        b = await button.new_button(clear_power)
 | 
			
		||||
        await cg.register_parented(b, parent)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#include "atm90e32_button.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -6,15 +7,73 @@ namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "atm90e32.button";
 | 
			
		||||
 | 
			
		||||
void ATM90E32CalibrationButton::press_action() {
 | 
			
		||||
  ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
 | 
			
		||||
void ATM90E32GainCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Gain Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG,
 | 
			
		||||
           "[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values");
 | 
			
		||||
  this->parent_->run_gain_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearGainCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Gain button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  this->parent_->clear_gain_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32OffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Offset Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
 | 
			
		||||
                "these values");
 | 
			
		||||
  this->parent_->run_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearCalibrationButton::press_action() {
 | 
			
		||||
  ESP_LOGI(TAG, "Offset calibrations cleared.");
 | 
			
		||||
void ATM90E32ClearOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Offset button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  this->parent_->clear_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32PowerOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Power Calibration button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**");
 | 
			
		||||
  ESP_LOGI(TAG, "[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
 | 
			
		||||
                "file to save these values");
 | 
			
		||||
  this->parent_->run_power_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ATM90E32ClearPowerOffsetCalibrationButton::press_action() {
 | 
			
		||||
  if (this->parent_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Power button [%s]", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "%s", this->get_name().c_str());
 | 
			
		||||
  this->parent_->clear_power_offset_calibrations();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -7,17 +7,49 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
class ATM90E32GainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32CalibrationButton() = default;
 | 
			
		||||
  ATM90E32GainCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
class ATM90E32ClearGainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearCalibrationButton() = default;
 | 
			
		||||
  ATM90E32ClearGainCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32OffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32OffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearOffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32PowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32PowerOffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATM90E32ClearPowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  ATM90E32ClearPowerOffsetCalibrationButton() = default;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void press_action() override;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										130
									
								
								esphome/components/atm90e32/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								esphome/components/atm90e32/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import number
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_VALUE,
 | 
			
		||||
    CONF_MIN_VALUE,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_PHASE_A,
 | 
			
		||||
    CONF_PHASE_B,
 | 
			
		||||
    CONF_PHASE_C,
 | 
			
		||||
    CONF_REFERENCE_VOLTAGE,
 | 
			
		||||
    CONF_STEP,
 | 
			
		||||
    ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from .. import atm90e32_ns
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
ATM90E32Number = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32Number", number.Number, cg.Parented.template(ATM90E32Component)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_REFERENCE_CURRENT = "reference_current"
 | 
			
		||||
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_VOLTAGE_PHASE_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_MODE, default="box"): cv.string,
 | 
			
		||||
            cv.Optional(CONF_MIN_VALUE, default=100.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_MAX_VALUE, default=260.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_STEP, default=0.1): cv.float_,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(
 | 
			
		||||
        number.number_schema(
 | 
			
		||||
            class_=ATM90E32Number,
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
            icon="mdi:power-plug",
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_CURRENT_PHASE_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_MODE, default="box"): cv.string,
 | 
			
		||||
            cv.Optional(CONF_MIN_VALUE, default=1.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_MAX_VALUE, default=200.0): cv.float_,
 | 
			
		||||
            cv.Optional(CONF_STEP, default=0.1): cv.float_,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(
 | 
			
		||||
        number.number_schema(
 | 
			
		||||
            class_=ATM90E32Number,
 | 
			
		||||
            unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
            icon="mdi:home-lightning-bolt",
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
REFERENCE_VOLTAGE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): REFERENCE_VOLTAGE_PHASE_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
REFERENCE_CURRENT_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): REFERENCE_CURRENT_PHASE_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
 | 
			
		||||
        cv.Optional(CONF_REFERENCE_VOLTAGE): REFERENCE_VOLTAGE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_REFERENCE_CURRENT): REFERENCE_CURRENT_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if voltage_cfg := config.get(CONF_REFERENCE_VOLTAGE):
 | 
			
		||||
        voltage_objs = [None, None, None]
 | 
			
		||||
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if validated := voltage_cfg.get(key):
 | 
			
		||||
                obj = await number.new_number(
 | 
			
		||||
                    validated,
 | 
			
		||||
                    min_value=validated["min_value"],
 | 
			
		||||
                    max_value=validated["max_value"],
 | 
			
		||||
                    step=validated["step"],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_parented(obj, parent)
 | 
			
		||||
                voltage_objs[i] = obj
 | 
			
		||||
 | 
			
		||||
        # Inherit from A → B/C if only A defined
 | 
			
		||||
        if voltage_objs[0] is not None:
 | 
			
		||||
            for i in range(3):
 | 
			
		||||
                if voltage_objs[i] is None:
 | 
			
		||||
                    voltage_objs[i] = voltage_objs[0]
 | 
			
		||||
 | 
			
		||||
        for i, obj in enumerate(voltage_objs):
 | 
			
		||||
            if obj is not None:
 | 
			
		||||
                cg.add(parent.set_reference_voltage(i, obj))
 | 
			
		||||
 | 
			
		||||
    if current_cfg := config.get(CONF_REFERENCE_CURRENT):
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if validated := current_cfg.get(key):
 | 
			
		||||
                obj = await number.new_number(
 | 
			
		||||
                    validated,
 | 
			
		||||
                    min_value=validated["min_value"],
 | 
			
		||||
                    max_value=validated["max_value"],
 | 
			
		||||
                    step=validated["step"],
 | 
			
		||||
                )
 | 
			
		||||
                await cg.register_parented(obj, parent)
 | 
			
		||||
                cg.add(parent.set_reference_current(i, obj))
 | 
			
		||||
							
								
								
									
										16
									
								
								esphome/components/atm90e32/number/atm90e32_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/atm90e32/number/atm90e32_number.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/atm90e32/atm90e32.h"
 | 
			
		||||
#include "esphome/components/number/number.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atm90e32 {
 | 
			
		||||
 | 
			
		||||
class ATM90E32Number : public number::Number, public Parented<ATM90E32Component> {
 | 
			
		||||
 public:
 | 
			
		||||
  void control(float value) override { this->publish_state(value); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atm90e32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -33,6 +33,7 @@ from esphome.const import (
 | 
			
		||||
    UNIT_DEGREES,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_VOLT_AMPS,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_WATT_HOURS,
 | 
			
		||||
@@ -45,10 +46,17 @@ CONF_GAIN_PGA = "gain_pga"
 | 
			
		||||
CONF_CURRENT_PHASES = "current_phases"
 | 
			
		||||
CONF_GAIN_VOLTAGE = "gain_voltage"
 | 
			
		||||
CONF_GAIN_CT = "gain_ct"
 | 
			
		||||
CONF_OFFSET_VOLTAGE = "offset_voltage"
 | 
			
		||||
CONF_OFFSET_CURRENT = "offset_current"
 | 
			
		||||
CONF_OFFSET_ACTIVE_POWER = "offset_active_power"
 | 
			
		||||
CONF_OFFSET_REACTIVE_POWER = "offset_reactive_power"
 | 
			
		||||
CONF_HARMONIC_POWER = "harmonic_power"
 | 
			
		||||
CONF_PEAK_CURRENT = "peak_current"
 | 
			
		||||
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
 | 
			
		||||
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
 | 
			
		||||
CONF_ENABLE_GAIN_CALIBRATION = "enable_gain_calibration"
 | 
			
		||||
CONF_PHASE_STATUS = "phase_status"
 | 
			
		||||
CONF_FREQUENCY_STATUS = "frequency_status"
 | 
			
		||||
UNIT_DEG = "degrees"
 | 
			
		||||
LINE_FREQS = {
 | 
			
		||||
    "50HZ": 50,
 | 
			
		||||
@@ -92,10 +100,11 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
            icon=ICON_LIGHTBULB,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_WATT,
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT_AMPS,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
@@ -137,6 +146,10 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_VOLTAGE, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_CURRENT, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_ACTIVE_POWER, default=0): cv.int_,
 | 
			
		||||
        cv.Optional(CONF_OFFSET_REACTIVE_POWER, default=0): cv.int_,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -164,9 +177,10 @@ CONFIG_SCHEMA = (
 | 
			
		||||
            cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
 | 
			
		||||
                CURRENT_PHASES, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ENABLE_GAIN_CALIBRATION, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
@@ -185,6 +199,10 @@ async def to_code(config):
 | 
			
		||||
        conf = config[phase]
 | 
			
		||||
        cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
 | 
			
		||||
        cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
 | 
			
		||||
        cg.add(var.set_voltage_offset(i, conf[CONF_OFFSET_VOLTAGE]))
 | 
			
		||||
        cg.add(var.set_current_offset(i, conf[CONF_OFFSET_CURRENT]))
 | 
			
		||||
        cg.add(var.set_active_power_offset(i, conf[CONF_OFFSET_ACTIVE_POWER]))
 | 
			
		||||
        cg.add(var.set_reactive_power_offset(i, conf[CONF_OFFSET_REACTIVE_POWER]))
 | 
			
		||||
        if voltage_config := conf.get(CONF_VOLTAGE):
 | 
			
		||||
            sens = await sensor.new_sensor(voltage_config)
 | 
			
		||||
            cg.add(var.set_voltage_sensor(i, sens))
 | 
			
		||||
@@ -218,16 +236,15 @@ async def to_code(config):
 | 
			
		||||
        if peak_current_config := conf.get(CONF_PEAK_CURRENT):
 | 
			
		||||
            sens = await sensor.new_sensor(peak_current_config)
 | 
			
		||||
            cg.add(var.set_peak_current_sensor(i, sens))
 | 
			
		||||
 | 
			
		||||
    if frequency_config := config.get(CONF_FREQUENCY):
 | 
			
		||||
        sens = await sensor.new_sensor(frequency_config)
 | 
			
		||||
        cg.add(var.set_freq_sensor(sens))
 | 
			
		||||
    if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
 | 
			
		||||
        sens = await sensor.new_sensor(chip_temperature_config)
 | 
			
		||||
        cg.add(var.set_chip_temperature_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
 | 
			
		||||
    cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
 | 
			
		||||
    cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
 | 
			
		||||
    cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
 | 
			
		||||
    cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))
 | 
			
		||||
    cg.add(var.set_enable_gain_calibration(config[CONF_ENABLE_GAIN_CALIBRATION]))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								esphome/components/atm90e32/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/atm90e32/text_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
 | 
			
		||||
 | 
			
		||||
from ..sensor import ATM90E32Component
 | 
			
		||||
 | 
			
		||||
CONF_PHASE_STATUS = "phase_status"
 | 
			
		||||
CONF_FREQUENCY_STATUS = "frequency_status"
 | 
			
		||||
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
 | 
			
		||||
 | 
			
		||||
PHASE_STATUS_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_PHASE_A): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PHASE_B): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PHASE_C): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:flash-alert"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(ATM90E32Component),
 | 
			
		||||
        cv.Optional(CONF_PHASE_STATUS): PHASE_STATUS_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_FREQUENCY_STATUS): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon="mdi:lightbulb-alert"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if phase_cfg := config.get(CONF_PHASE_STATUS):
 | 
			
		||||
        for i, key in enumerate(PHASE_KEYS):
 | 
			
		||||
            if sub_phase_cfg := phase_cfg.get(key):
 | 
			
		||||
                sens = await text_sensor.new_text_sensor(sub_phase_cfg)
 | 
			
		||||
                cg.add(parent.set_phase_status_text_sensor(i, sens))
 | 
			
		||||
 | 
			
		||||
    if freq_status_config := config.get(CONF_FREQUENCY_STATUS):
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(freq_status_config)
 | 
			
		||||
        cg.add(parent.set_freq_status_text_sensor(sens))
 | 
			
		||||
@@ -37,16 +37,13 @@ AUDIO_COMPONENT_SCHEMA = cv.Schema(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_stream_limits(
 | 
			
		||||
    min_bits_per_sample: int = _UNDEF,
 | 
			
		||||
    max_bits_per_sample: int = _UNDEF,
 | 
			
		||||
    min_channels: int = _UNDEF,
 | 
			
		||||
    max_channels: int = _UNDEF,
 | 
			
		||||
    min_sample_rate: int = _UNDEF,
 | 
			
		||||
    max_sample_rate: int = _UNDEF,
 | 
			
		||||
    min_bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    max_bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    min_channels: int = cv.UNDEFINED,
 | 
			
		||||
    max_channels: int = cv.UNDEFINED,
 | 
			
		||||
    min_sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
    max_sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
):
 | 
			
		||||
    """Sets the limits for the audio stream that audio component can handle
 | 
			
		||||
 | 
			
		||||
@@ -55,17 +52,17 @@ def set_stream_limits(
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def set_limits_in_config(config):
 | 
			
		||||
        if min_bits_per_sample is not _UNDEF:
 | 
			
		||||
        if min_bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
 | 
			
		||||
        if max_bits_per_sample is not _UNDEF:
 | 
			
		||||
        if max_bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MAX_BITS_PER_SAMPLE] = max_bits_per_sample
 | 
			
		||||
        if min_channels is not _UNDEF:
 | 
			
		||||
        if min_channels is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MIN_CHANNELS] = min_channels
 | 
			
		||||
        if max_channels is not _UNDEF:
 | 
			
		||||
        if max_channels is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MAX_CHANNELS] = max_channels
 | 
			
		||||
        if min_sample_rate is not _UNDEF:
 | 
			
		||||
        if min_sample_rate is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MIN_SAMPLE_RATE] = min_sample_rate
 | 
			
		||||
        if max_sample_rate is not _UNDEF:
 | 
			
		||||
        if max_sample_rate is not cv.UNDEFINED:
 | 
			
		||||
            config[CONF_MAX_SAMPLE_RATE] = max_sample_rate
 | 
			
		||||
 | 
			
		||||
    return set_limits_in_config
 | 
			
		||||
@@ -75,10 +72,10 @@ def final_validate_audio_schema(
 | 
			
		||||
    name: str,
 | 
			
		||||
    *,
 | 
			
		||||
    audio_device: str,
 | 
			
		||||
    bits_per_sample: int = _UNDEF,
 | 
			
		||||
    channels: int = _UNDEF,
 | 
			
		||||
    sample_rate: int = _UNDEF,
 | 
			
		||||
    enabled_channels: list[int] = _UNDEF,
 | 
			
		||||
    bits_per_sample: int = cv.UNDEFINED,
 | 
			
		||||
    channels: int = cv.UNDEFINED,
 | 
			
		||||
    sample_rate: int = cv.UNDEFINED,
 | 
			
		||||
    enabled_channels: list[int] = cv.UNDEFINED,
 | 
			
		||||
    audio_device_issue: bool = False,
 | 
			
		||||
):
 | 
			
		||||
    """Validates audio compatibility when passed between different components.
 | 
			
		||||
@@ -101,7 +98,7 @@ def final_validate_audio_schema(
 | 
			
		||||
    def validate_audio_compatiblity(audio_config):
 | 
			
		||||
        audio_schema = {}
 | 
			
		||||
 | 
			
		||||
        if bits_per_sample is not _UNDEF:
 | 
			
		||||
        if bits_per_sample is not cv.UNDEFINED:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
 | 
			
		||||
@@ -114,7 +111,7 @@ def final_validate_audio_schema(
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        if channels is not _UNDEF:
 | 
			
		||||
        if channels is not cv.UNDEFINED:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_CHANNELS),
 | 
			
		||||
@@ -127,7 +124,7 @@ def final_validate_audio_schema(
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        if sample_rate is not _UNDEF:
 | 
			
		||||
        if sample_rate is not cv.UNDEFINED:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_SAMPLE_RATE),
 | 
			
		||||
@@ -140,7 +137,7 @@ def final_validate_audio_schema(
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        if enabled_channels is not _UNDEF:
 | 
			
		||||
        if enabled_channels is not cv.UNDEFINED:
 | 
			
		||||
            for channel in enabled_channels:
 | 
			
		||||
                try:
 | 
			
		||||
                    # Channels are 0-indexed
 | 
			
		||||
@@ -168,4 +165,4 @@ def final_validate_audio_schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.3")
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.4")
 | 
			
		||||
 
 | 
			
		||||
@@ -135,7 +135,7 @@ const char *audio_file_type_to_string(AudioFileType file_type);
 | 
			
		||||
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
 | 
			
		||||
                         size_t samples_to_scale);
 | 
			
		||||
 | 
			
		||||
/// @brief Unpacks a quantized audio sample into a Q31 fixed point number.
 | 
			
		||||
/// @brief Unpacks a quantized audio sample into a Q31 fixed-point number.
 | 
			
		||||
/// @param data Pointer to uint8_t array containing the audio sample
 | 
			
		||||
/// @param bytes_per_sample The number of bytes per sample
 | 
			
		||||
/// @return Q31 sample
 | 
			
		||||
@@ -160,5 +160,28 @@ inline int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_
 | 
			
		||||
  return sample;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// @brief Packs a Q31 fixed-point number as an audio sample with the specified number of bytes per sample.
 | 
			
		||||
/// Packs the most significant bits - no dithering is applied.
 | 
			
		||||
/// @param sample Q31 fixed-point number to pack
 | 
			
		||||
/// @param data Pointer to data array to store
 | 
			
		||||
/// @param bytes_per_sample The audio data's bytes per sample
 | 
			
		||||
inline void pack_q31_as_audio_sample(int32_t sample, uint8_t *data, size_t bytes_per_sample) {
 | 
			
		||||
  if (bytes_per_sample == 1) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 2) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 3) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample >> 8);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[2] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  } else if (bytes_per_sample == 4) {
 | 
			
		||||
    data[0] = static_cast<uint8_t>(sample);
 | 
			
		||||
    data[1] = static_cast<uint8_t>(sample >> 8);
 | 
			
		||||
    data[2] = static_cast<uint8_t>(sample >> 16);
 | 
			
		||||
    data[3] = static_cast<uint8_t>(sample >> 24);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace audio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -171,7 +171,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
 | 
			
		||||
 | 
			
		||||
    bytes_available_before_processing = this->input_transfer_buffer_->available();
 | 
			
		||||
 | 
			
		||||
    if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
 | 
			
		||||
    if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
 | 
			
		||||
      // Failed to decode in last attempt and there is no new data
 | 
			
		||||
 | 
			
		||||
      if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
 | 
			
		||||
 
 | 
			
		||||
@@ -386,7 +386,7 @@ def validate_click_timing(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
_BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -458,19 +458,17 @@ BINARY_SENSOR_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def binary_sensor_schema(
 | 
			
		||||
    class_: MockObjClass = _UNDEF,
 | 
			
		||||
    class_: MockObjClass = cv.UNDEFINED,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {}
 | 
			
		||||
 | 
			
		||||
    if class_ is not _UNDEF:
 | 
			
		||||
    if class_ is not cv.UNDEFINED:
 | 
			
		||||
        # Not cv.optional
 | 
			
		||||
        schema[cv.GenerateID()] = cv.declare_id(class_)
 | 
			
		||||
 | 
			
		||||
@@ -479,10 +477,15 @@ def binary_sensor_schema(
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return BINARY_SENSOR_SCHEMA.extend(schema)
 | 
			
		||||
    return _BINARY_SENSOR_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
 | 
			
		||||
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_binary_sensor_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ from esphome.components import ble_client, esp32_ble_tracker, text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CHARACTERISTIC_UUID,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NOTIFY,
 | 
			
		||||
    CONF_SERVICE_UUID,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
@@ -32,9 +31,9 @@ BLETextSensorNotifyTrigger = ble_client_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    text_sensor.TEXT_SENSOR_SCHEMA.extend(
 | 
			
		||||
    text_sensor.text_sensor_schema(BLETextSensor)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BLETextSensor),
 | 
			
		||||
            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
@@ -54,7 +53,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await text_sensor.new_text_sensor(config)
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
@@ -101,7 +100,6 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
    cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
 | 
			
		||||
    await text_sensor.register_text_sensor(var, config)
 | 
			
		||||
    for conf in config.get(CONF_ON_NOTIFY, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await ble_client.register_ble_node(trigger, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -73,9 +73,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->read.handle;
 | 
			
		||||
      resp.data.reserve(param->read.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      // Use bulk insert instead of individual push_backs
 | 
			
		||||
      resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -127,9 +126,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->notify.handle;
 | 
			
		||||
      resp.data.reserve(param->notify.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      // Use bulk insert instead of individual push_backs
 | 
			
		||||
      resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
 | 
			
		||||
      this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  api::BluetoothLERawAdvertisementsResponse resp;
 | 
			
		||||
  // Pre-allocate the advertisements vector to avoid reallocations
 | 
			
		||||
  resp.advertisements.reserve(count);
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < count; i++) {
 | 
			
		||||
    auto &result = advertisements[i];
 | 
			
		||||
    api::BluetoothLERawAdvertisement adv;
 | 
			
		||||
@@ -65,9 +68,8 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
 | 
			
		||||
 | 
			
		||||
    uint8_t length = result.adv_data_len + result.scan_rsp_len;
 | 
			
		||||
    adv.data.reserve(length);
 | 
			
		||||
    for (uint16_t i = 0; i < length; i++) {
 | 
			
		||||
      adv.data.push_back(result.ble_adv[i]);
 | 
			
		||||
    }
 | 
			
		||||
    // Use a bulk insert instead of individual push_backs
 | 
			
		||||
    adv.data.insert(adv.data.end(), &result.ble_adv[0], &result.ble_adv[length]);
 | 
			
		||||
 | 
			
		||||
    resp.advertisements.push_back(std::move(adv));
 | 
			
		||||
 | 
			
		||||
@@ -85,21 +87,34 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
			
		||||
  if (!device.get_name().empty())
 | 
			
		||||
    resp.name = device.get_name();
 | 
			
		||||
  resp.rssi = device.get_rssi();
 | 
			
		||||
  for (auto uuid : device.get_service_uuids()) {
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate vectors based on known sizes
 | 
			
		||||
  auto service_uuids = device.get_service_uuids();
 | 
			
		||||
  resp.service_uuids.reserve(service_uuids.size());
 | 
			
		||||
  for (auto uuid : service_uuids) {
 | 
			
		||||
    resp.service_uuids.push_back(uuid.to_string());
 | 
			
		||||
  }
 | 
			
		||||
  for (auto &data : device.get_service_datas()) {
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate service data vector
 | 
			
		||||
  auto service_datas = device.get_service_datas();
 | 
			
		||||
  resp.service_data.reserve(service_datas.size());
 | 
			
		||||
  for (auto &data : service_datas) {
 | 
			
		||||
    api::BluetoothServiceData service_data;
 | 
			
		||||
    service_data.uuid = data.uuid.to_string();
 | 
			
		||||
    service_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.service_data.push_back(std::move(service_data));
 | 
			
		||||
  }
 | 
			
		||||
  for (auto &data : device.get_manufacturer_datas()) {
 | 
			
		||||
 | 
			
		||||
  // Pre-allocate manufacturer data vector
 | 
			
		||||
  auto manufacturer_datas = device.get_manufacturer_datas();
 | 
			
		||||
  resp.manufacturer_data.reserve(manufacturer_datas.size());
 | 
			
		||||
  for (auto &data : manufacturer_datas) {
 | 
			
		||||
    api::BluetoothServiceData manufacturer_data;
 | 
			
		||||
    manufacturer_data.uuid = data.uuid.to_string();
 | 
			
		||||
    manufacturer_data.data.assign(data.data.begin(), data.data.end());
 | 
			
		||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->api_connection_->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -161,11 +176,27 @@ void BluetoothProxy::loop() {
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
      resp.address = connection->get_address();
 | 
			
		||||
      resp.services.reserve(1);  // Always one service per response in this implementation
 | 
			
		||||
      api::BluetoothGATTService service_resp;
 | 
			
		||||
      service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
 | 
			
		||||
      service_resp.handle = service_result.start_handle;
 | 
			
		||||
      uint16_t char_offset = 0;
 | 
			
		||||
      esp_gattc_char_elem_t char_result;
 | 
			
		||||
      // Get the number of characteristics directly with one call
 | 
			
		||||
      uint16_t total_char_count = 0;
 | 
			
		||||
      esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
 | 
			
		||||
          connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
 | 
			
		||||
          service_result.start_handle, service_result.end_handle, 0, &total_char_count);
 | 
			
		||||
 | 
			
		||||
      if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
 | 
			
		||||
        // Only reserve if we successfully got a count
 | 
			
		||||
        service_resp.characteristics.reserve(total_char_count);
 | 
			
		||||
      } else if (char_count_status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str(), char_count_status);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Now process characteristics
 | 
			
		||||
      while (true) {  // characteristics
 | 
			
		||||
        uint16_t char_count = 1;
 | 
			
		||||
        esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
 | 
			
		||||
@@ -187,6 +218,23 @@ void BluetoothProxy::loop() {
 | 
			
		||||
        characteristic_resp.handle = char_result.char_handle;
 | 
			
		||||
        characteristic_resp.properties = char_result.properties;
 | 
			
		||||
        char_offset++;
 | 
			
		||||
 | 
			
		||||
        // Get the number of descriptors directly with one call
 | 
			
		||||
        uint16_t total_desc_count = 0;
 | 
			
		||||
        esp_gatt_status_t desc_count_status =
 | 
			
		||||
            esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
 | 
			
		||||
                                         char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
 | 
			
		||||
 | 
			
		||||
        if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
 | 
			
		||||
          // Only reserve if we successfully got a count
 | 
			
		||||
          characteristic_resp.descriptors.reserve(total_desc_count);
 | 
			
		||||
        } else if (desc_count_status != ESP_GATT_OK) {
 | 
			
		||||
          ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
 | 
			
		||||
                   connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
 | 
			
		||||
                   desc_count_status);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now process descriptors
 | 
			
		||||
        uint16_t desc_offset = 0;
 | 
			
		||||
        esp_gattc_descr_elem_t desc_result;
 | 
			
		||||
        while (true) {  // descriptors
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ ButtonPressTrigger = button_ns.class_(
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUTTON_SCHEMA = (
 | 
			
		||||
_BUTTON_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -60,15 +60,13 @@ BUTTON_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def button_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {cv.GenerateID(): cv.declare_id(class_)}
 | 
			
		||||
 | 
			
		||||
@@ -77,10 +75,15 @@ def button_schema(
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return BUTTON_SCHEMA.extend(schema)
 | 
			
		||||
    return _BUTTON_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
BUTTON_SCHEMA = button_schema(Button)
 | 
			
		||||
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_button_core_(var, config):
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,11 @@ from esphome.const import (
 | 
			
		||||
    CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
 | 
			
		||||
    CONF_CUSTOM_FAN_MODE,
 | 
			
		||||
    CONF_CUSTOM_PRESET,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_FAN_MODE,
 | 
			
		||||
    CONF_FAN_MODE_COMMAND_TOPIC,
 | 
			
		||||
    CONF_FAN_MODE_STATE_TOPIC,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_TEMPERATURE,
 | 
			
		||||
    CONF_MIN_TEMPERATURE,
 | 
			
		||||
@@ -46,6 +48,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_WEB_SERVER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
@@ -151,12 +154,11 @@ ControlTrigger = climate_ns.class_(
 | 
			
		||||
    "ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CLIMATE_SCHEMA = (
 | 
			
		||||
_CLIMATE_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
            cv.Optional(CONF_VISUAL, default={}): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
@@ -245,6 +247,31 @@ CLIMATE_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def climate_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _CLIMATE_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
CLIMATE_SCHEMA = climate_schema(Climate)
 | 
			
		||||
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_climate_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -419,6 +446,12 @@ async def register_climate(var, config):
 | 
			
		||||
    await setup_climate_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_climate(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_climate(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Climate),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_SOURCE_ID,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
@@ -15,12 +14,15 @@ from .. import copy_ns
 | 
			
		||||
CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(CopyCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyCover),
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover),
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
@@ -30,8 +32,7 @@ FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import lock
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID
 | 
			
		||||
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
 | 
			
		||||
from esphome.core.entity_helpers import inherit_property_from
 | 
			
		||||
 | 
			
		||||
from .. import copy_ns
 | 
			
		||||
@@ -9,12 +9,15 @@ from .. import copy_ns
 | 
			
		||||
CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    lock.lock_schema(CopyLock)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyLock),
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock),
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
@@ -23,8 +26,7 @@ FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await lock.register_lock(var, config)
 | 
			
		||||
    var = await lock.new_lock(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    source = await cg.get_variable(config[CONF_SOURCE_ID])
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,15 @@ from .. import copy_ns
 | 
			
		||||
CopyText = copy_ns.class_("CopyText", text.Text, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    text.text_schema(CopyText)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CopyText),
 | 
			
		||||
            cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
    inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_OPEN,
 | 
			
		||||
@@ -31,6 +33,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
@@ -89,12 +92,11 @@ CoverClosedTrigger = cover_ns.class_(
 | 
			
		||||
 | 
			
		||||
CONF_ON_CLOSED = "on_closed"
 | 
			
		||||
 | 
			
		||||
COVER_SCHEMA = (
 | 
			
		||||
_COVER_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
            cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
@@ -124,6 +126,33 @@ COVER_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cover_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    *,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, cv.one_of(*DEVICE_CLASSES, lower=True)),
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _COVER_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
COVER_SCHEMA = cover_schema(Cover)
 | 
			
		||||
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_cover_core_(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
@@ -163,6 +192,12 @@ async def register_cover(var, config):
 | 
			
		||||
    await setup_cover_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_cover(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_cover(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COVER_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CLOSE_ACTION,
 | 
			
		||||
    CONF_CLOSE_DURATION,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_DURATION,
 | 
			
		||||
    CONF_OPEN_ACTION,
 | 
			
		||||
    CONF_OPEN_DURATION,
 | 
			
		||||
@@ -30,9 +29,10 @@ CurrentBasedCover = current_based_ns.class_(
 | 
			
		||||
    "CurrentBasedCover", cover.Cover, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(CurrentBasedCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CurrentBasedCover),
 | 
			
		||||
            cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
 | 
			
		||||
@@ -62,13 +62,14 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
                CONF_START_SENSING_DELAY, default="500ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
 | 
			
		||||
    await automation.build_automation(
 | 
			
		||||
        var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
 | 
			
		||||
 
 | 
			
		||||
@@ -56,21 +56,13 @@ void DallasTemperatureSensor::update() {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
  if (success) {
 | 
			
		||||
    for (uint8_t &i : this->scratch_pad_) {
 | 
			
		||||
      i = this->bus_->read8();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::read_scratch_pad_() {
 | 
			
		||||
  bool success;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
 | 
			
		||||
    if (success)
 | 
			
		||||
      this->read_scratch_pad_int_();
 | 
			
		||||
  }
 | 
			
		||||
  if (!success) {
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("bus reset failed");
 | 
			
		||||
  }
 | 
			
		||||
@@ -113,8 +105,6 @@ void DallasTemperatureSensor::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  this->scratch_pad_[4] = res;
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
  if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
 | 
			
		||||
    this->bus_->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
    this->bus_->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
@@ -124,7 +114,6 @@ void DallasTemperatureSensor::setup() {
 | 
			
		||||
  // write value to EEPROM
 | 
			
		||||
  this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
 | 
			
		||||
@@ -138,6 +127,10 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
 | 
			
		||||
  if (!chksum_validity) {
 | 
			
		||||
    ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
 | 
			
		||||
    this->status_set_warning("scratch pad checksum invalid");
 | 
			
		||||
    ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
 | 
			
		||||
             this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
 | 
			
		||||
             this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
 | 
			
		||||
             crc8(this->scratch_pad_, 8));
 | 
			
		||||
  }
 | 
			
		||||
  return chksum_validity;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor,
 | 
			
		||||
  /// Get the number of milliseconds we have to wait for the conversion phase.
 | 
			
		||||
  uint16_t millis_to_wait_for_conversion_() const;
 | 
			
		||||
  bool read_scratch_pad_();
 | 
			
		||||
  void read_scratch_pad_int_();
 | 
			
		||||
  bool check_scratch_pad_();
 | 
			
		||||
  float get_temp_c_();
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_FORCE_UPDATE,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_MAX_VALUE,
 | 
			
		||||
    CONF_MIN_VALUE,
 | 
			
		||||
@@ -153,9 +152,10 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            climate.CLIMATE_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
            climate.climate_schema(DemoClimate)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(DemoClimate),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(CLIMATE_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -183,9 +183,10 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            cover.COVER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
            cover.cover_schema(DemoCover)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(): cv.declare_id(DemoCover),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(COVER_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -211,9 +212,10 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            fan.FAN_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
            fan.fan_schema(DemoFan)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoFan),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(FAN_TYPES, int=True),
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
@@ -251,7 +253,9 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        ): [
 | 
			
		||||
            light.RGB_LIGHT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
 | 
			
		||||
            light.light_schema(DemoLight, light.LightType.RGB)
 | 
			
		||||
            .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoLight),
 | 
			
		||||
                    cv.Required(CONF_TYPE): cv.enum(LIGHT_TYPES, int=True),
 | 
			
		||||
@@ -377,39 +381,33 @@ async def to_code(config):
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_CLIMATES]:
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        var = await climate.new_climate(conf)
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await climate.register_climate(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_COVERS]:
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        var = await cover.new_cover(conf)
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await cover.register_cover(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_FANS]:
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
 | 
			
		||||
        var = await fan.new_fan(conf)
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await fan.register_fan(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_LIGHTS]:
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
 | 
			
		||||
        var = await light.new_light(conf)
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await light.register_light(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_NUMBERS]:
 | 
			
		||||
        var = cg.new_Pvariable(conf[CONF_ID])
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        await number.register_number(
 | 
			
		||||
            var,
 | 
			
		||||
        var = await number.new_number(
 | 
			
		||||
            conf,
 | 
			
		||||
            min_value=conf[CONF_MIN_VALUE],
 | 
			
		||||
            max_value=conf[CONF_MAX_VALUE],
 | 
			
		||||
            step=conf[CONF_STEP],
 | 
			
		||||
        )
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
        cg.add(var.set_type(conf[CONF_TYPE]))
 | 
			
		||||
 | 
			
		||||
    for conf in config[CONF_SENSORS]:
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_TYPE, ENTITY_CATEGORY_CONFIG
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
 | 
			
		||||
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
 | 
			
		||||
 | 
			
		||||
@@ -26,32 +27,30 @@ Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
 | 
			
		||||
    "Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_SWITCH_SCHEMA = (
 | 
			
		||||
 | 
			
		||||
def _switch_schema(class_: MockObjClass) -> cv.Schema:
 | 
			
		||||
    return (
 | 
			
		||||
        switch.switch_schema(
 | 
			
		||||
            class_,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
        )
 | 
			
		||||
        .extend(
 | 
			
		||||
            {
 | 
			
		||||
            cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
 | 
			
		||||
                cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(
 | 
			
		||||
                    DfrobotSen0395Component
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        "sensor_active": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395PowerSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "turn_on_led": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395LedSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "presence_via_uart": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395UartPresenceSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "start_after_boot": _SWITCH_SCHEMA.extend(
 | 
			
		||||
            {cv.GenerateID(): cv.declare_id(Sen0395StartAfterBootSwitch)}
 | 
			
		||||
        ),
 | 
			
		||||
        "sensor_active": _switch_schema(Sen0395PowerSwitch),
 | 
			
		||||
        "turn_on_led": _switch_schema(Sen0395LedSwitch),
 | 
			
		||||
        "presence_via_uart": _switch_schema(Sen0395UartPresenceSwitch),
 | 
			
		||||
        "start_after_boot": _switch_schema(Sen0395StartAfterBootSwitch),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_CLOSE_ACTION,
 | 
			
		||||
    CONF_CLOSE_DURATION,
 | 
			
		||||
    CONF_CLOSE_ENDSTOP,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_DURATION,
 | 
			
		||||
    CONF_OPEN_ACTION,
 | 
			
		||||
    CONF_OPEN_DURATION,
 | 
			
		||||
@@ -17,9 +16,10 @@ from esphome.const import (
 | 
			
		||||
endstop_ns = cg.esphome_ns.namespace("endstop")
 | 
			
		||||
EndstopCover = endstop_ns.class_("EndstopCover", cover.Cover, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(EndstopCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(EndstopCover),
 | 
			
		||||
            cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Required(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
            cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
@@ -29,13 +29,14 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
            cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
 | 
			
		||||
    await automation.build_automation(
 | 
			
		||||
        var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
 | 
			
		||||
 
 | 
			
		||||
@@ -2,42 +2,66 @@
 | 
			
		||||
 | 
			
		||||
#include "gpio.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "driver/gpio.h"
 | 
			
		||||
#include "driver/rtc_io.h"
 | 
			
		||||
#include "hal/gpio_hal.h"
 | 
			
		||||
#include "soc/soc_caps.h"
 | 
			
		||||
#include "soc/gpio_periph.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
#if (SOC_RTCIO_PIN_COUNT > 0)
 | 
			
		||||
#include "hal/rtc_io_hal.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT
 | 
			
		||||
#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0  // NOLINT
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32";
 | 
			
		||||
 | 
			
		||||
static const gpio_hal_context_t GPIO_HAL = {.dev = GPIO_HAL_GET_HW(GPIO_PORT_0)};
 | 
			
		||||
 | 
			
		||||
bool ESP32InternalGPIOPin::isr_service_installed = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
static gpio_mode_t flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT)
 | 
			
		||||
    return GPIO_MODE_INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
  if (flags == gpio::FLAG_OUTPUT)
 | 
			
		||||
    return GPIO_MODE_OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
  if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN))
 | 
			
		||||
    return GPIO_MODE_OUTPUT_OD;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
  if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN))
 | 
			
		||||
    return GPIO_MODE_INPUT_OUTPUT_OD;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
 | 
			
		||||
  if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT))
 | 
			
		||||
    return GPIO_MODE_INPUT_OUTPUT;
 | 
			
		||||
  } else {
 | 
			
		||||
  // unsupported or gpio::FLAG_NONE
 | 
			
		||||
  return GPIO_MODE_DISABLE;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  gpio_num_t pin;
 | 
			
		||||
  gpio::Flags flags;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
  bool use_rtc;
 | 
			
		||||
  int rtc_pin;
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->pin = this->pin_;
 | 
			
		||||
  arg->flags = gpio::FLAG_NONE;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
  arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_);
 | 
			
		||||
  if (arg->use_rtc)
 | 
			
		||||
    arg->rtc_pin = rtc_io_number_get(this->pin_);
 | 
			
		||||
#endif
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -90,6 +114,7 @@ void ESP32InternalGPIOPin::setup() {
 | 
			
		||||
  if (flags_ & gpio::FLAG_OUTPUT) {
 | 
			
		||||
    gpio_set_drive_capability(pin_, drive_strength_);
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "rtc: %d", SOC_GPIO_SUPPORT_RTC_INDEPENDENT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
@@ -115,28 +140,65 @@ void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
 | 
			
		||||
using namespace esp32;
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  return bool(gpio_get_level(arg->pin)) != arg->inverted;
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
 | 
			
		||||
  return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0);
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
 | 
			
		||||
  gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  // not supported
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  gpio_set_direction(arg->pin, flags_to_mode(flags));
 | 
			
		||||
  gpio_pull_mode_t pull_mode = GPIO_FLOATING;
 | 
			
		||||
  if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_PULLDOWN;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_ONLY;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
    pull_mode = GPIO_PULLDOWN_ONLY;
 | 
			
		||||
  gpio::Flags diff = (gpio::Flags)(flags ^ arg->flags);
 | 
			
		||||
  if (diff & gpio::FLAG_OUTPUT) {
 | 
			
		||||
    if (flags & gpio::FLAG_OUTPUT) {
 | 
			
		||||
      gpio_hal_output_enable(&GPIO_HAL, arg->pin);
 | 
			
		||||
      if (flags & gpio::FLAG_OPEN_DRAIN)
 | 
			
		||||
        gpio_hal_od_enable(&GPIO_HAL, arg->pin);
 | 
			
		||||
    } else {
 | 
			
		||||
      gpio_hal_output_disable(&GPIO_HAL, arg->pin);
 | 
			
		||||
    }
 | 
			
		||||
  gpio_set_pull_mode(arg->pin, pull_mode);
 | 
			
		||||
  }
 | 
			
		||||
  if (diff & gpio::FLAG_INPUT) {
 | 
			
		||||
    if (flags & gpio::FLAG_INPUT) {
 | 
			
		||||
      gpio_hal_input_enable(&GPIO_HAL, arg->pin);
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
      if (arg->use_rtc) {
 | 
			
		||||
        if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
          rtcio_hal_pullup_enable(arg->rtc_pin);
 | 
			
		||||
        } else {
 | 
			
		||||
          rtcio_hal_pullup_disable(arg->rtc_pin);
 | 
			
		||||
        }
 | 
			
		||||
        if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
          rtcio_hal_pulldown_enable(arg->rtc_pin);
 | 
			
		||||
        } else {
 | 
			
		||||
          rtcio_hal_pulldown_disable(arg->rtc_pin);
 | 
			
		||||
        }
 | 
			
		||||
      } else
 | 
			
		||||
#endif
 | 
			
		||||
      {
 | 
			
		||||
        if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
          gpio_hal_pullup_en(&GPIO_HAL, arg->pin);
 | 
			
		||||
        } else {
 | 
			
		||||
          gpio_hal_pullup_dis(&GPIO_HAL, arg->pin);
 | 
			
		||||
        }
 | 
			
		||||
        if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
          gpio_hal_pulldown_en(&GPIO_HAL, arg->pin);
 | 
			
		||||
        } else {
 | 
			
		||||
          gpio_hal_pulldown_dis(&GPIO_HAL, arg->pin);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      gpio_hal_input_disable(&GPIO_HAL, arg->pin);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  arg->flags = flags;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
import logging
 | 
			
		||||
from typing import Any
 | 
			
		||||
from typing import Any, Callable
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
@@ -64,8 +64,7 @@ def _lookup_pin(value):
 | 
			
		||||
def _translate_pin(value):
 | 
			
		||||
    if isinstance(value, dict) or value is None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "This variable only supports pin numbers, not full pin schemas "
 | 
			
		||||
            "(with inverted and mode)."
 | 
			
		||||
            "This variable only supports pin numbers, not full pin schemas (with inverted and mode)."
 | 
			
		||||
        )
 | 
			
		||||
    if isinstance(value, int) and not isinstance(value, bool):
 | 
			
		||||
        return value
 | 
			
		||||
@@ -82,30 +81,22 @@ def _translate_pin(value):
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class ESP32ValidationFunctions:
 | 
			
		||||
    pin_validation: Any
 | 
			
		||||
    usage_validation: Any
 | 
			
		||||
    pin_validation: Callable[[Any], Any]
 | 
			
		||||
    usage_validation: Callable[[Any], Any]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_esp32_validations = {
 | 
			
		||||
    VARIANT_ESP32: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s2_validate_supports,
 | 
			
		||||
    VARIANT_ESP32C2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_c2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_c2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32C3: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_c3_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_c3_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S3: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s3_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s3_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32C2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_c2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_c2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32C6: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_c6_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_c6_validate_supports,
 | 
			
		||||
@@ -114,6 +105,14 @@ _esp32_validations = {
 | 
			
		||||
        pin_validation=esp32_h2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_h2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S3: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s3_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s3_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,7 @@ def esp32_validate_gpio_pin(value):
 | 
			
		||||
        )
 | 
			
		||||
    if 9 <= value <= 10:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "Pin %s (9-10) might already be used by the "
 | 
			
		||||
            "flash interface in QUAD IO flash mode.",
 | 
			
		||||
            "Pin %s (9-10) might already be used by the flash interface in QUAD IO flash mode.",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
    if value in (24, 28, 29, 30, 31):
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ def esp32_c2_validate_supports(value):
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 20:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-20)")
 | 
			
		||||
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ def esp32_c3_validate_supports(value):
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 21:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-21)")
 | 
			
		||||
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ def esp32_c6_validate_supports(value):
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 23:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)")
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-23)")
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ def esp32_h2_validate_supports(value):
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 27:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-27)")
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ CONF_ESP32_BLE_ID = "esp32_ble_id"
 | 
			
		||||
CONF_SCAN_PARAMETERS = "scan_parameters"
 | 
			
		||||
CONF_WINDOW = "window"
 | 
			
		||||
CONF_ON_SCAN_END = "on_scan_end"
 | 
			
		||||
CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
 | 
			
		||||
 | 
			
		||||
DEFAULT_MAX_CONNECTIONS = 3
 | 
			
		||||
IDF_MAX_CONNECTIONS = 9
 | 
			
		||||
@@ -203,6 +204,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
 | 
			
		||||
                {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
 | 
			
		||||
            ),
 | 
			
		||||
            cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
)
 | 
			
		||||
@@ -310,6 +312,8 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
 | 
			
		||||
        if config.get(CONF_SOFTWARE_COEXISTENCE):
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
 | 
			
		||||
        # https://github.com/espressif/esp-idf/issues/4101
 | 
			
		||||
        # https://github.com/espressif/esp-idf/issues/2503
 | 
			
		||||
        # Match arduino CONFIG_BTU_TASK_STACK_SIZE
 | 
			
		||||
@@ -331,6 +335,8 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_OTA_STATE_CALLBACK")  # To be notified when an OTA update starts
 | 
			
		||||
    cg.add_define("USE_ESP32_BLE_CLIENT")
 | 
			
		||||
    if config.get(CONF_SOFTWARE_COEXISTENCE):
 | 
			
		||||
        cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,10 @@
 | 
			
		||||
#include "esphome/components/ota/ota_backend.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
 | 
			
		||||
#include <esp_coexist.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <esp32-hal-bt.h>
 | 
			
		||||
#endif
 | 
			
		||||
@@ -194,10 +198,18 @@ void ESP32BLETracker::loop() {
 | 
			
		||||
    https://github.com/espressif/esp-idf/issues/6688
 | 
			
		||||
 | 
			
		||||
  */
 | 
			
		||||
  if (this->scanner_state_ == ScannerState::IDLE && this->scan_continuous_ && !connecting && !disconnecting &&
 | 
			
		||||
      !promote_to_connecting) {
 | 
			
		||||
  if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) {
 | 
			
		||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
 | 
			
		||||
    if (this->coex_prefer_ble_) {
 | 
			
		||||
      this->coex_prefer_ble_ = false;
 | 
			
		||||
      ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
 | 
			
		||||
      esp_coex_preference_set(ESP_COEX_PREFER_BALANCE);  // Reset to default
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    if (this->scan_continuous_) {
 | 
			
		||||
      this->start_scan_(false);  // first = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // If there is a discovered client and no connecting
 | 
			
		||||
  // clients and no clients using the scanner to search for
 | 
			
		||||
  // devices, then stop scanning and promote the discovered
 | 
			
		||||
@@ -213,6 +225,13 @@ void ESP32BLETracker::loop() {
 | 
			
		||||
          ESP_LOGD(TAG, "Promoting client to connect...");
 | 
			
		||||
          // We only want to promote one client at a time.
 | 
			
		||||
          // once the scanner is fully stopped.
 | 
			
		||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
 | 
			
		||||
          ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
 | 
			
		||||
          if (!this->coex_prefer_ble_) {
 | 
			
		||||
            this->coex_prefer_ble_ = true;
 | 
			
		||||
            esp_coex_preference_set(ESP_COEX_PREFER_BT);  // Prioritize Bluetooth
 | 
			
		||||
          }
 | 
			
		||||
#endif
 | 
			
		||||
          client->set_state(ClientState::READY_TO_CONNECT);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 
 | 
			
		||||
@@ -299,6 +299,9 @@ class ESP32BLETracker : public Component,
 | 
			
		||||
  int discovered_{0};
 | 
			
		||||
  int searching_{0};
 | 
			
		||||
  int disconnecting_{0};
 | 
			
		||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
 | 
			
		||||
  bool coex_prefer_ble_{false};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ namespace esp8266 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp8266";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {  // NOLINT(bugprone-branch-clone)
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
@@ -34,12 +34,36 @@ static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
  volatile uint32_t *in_reg;
 | 
			
		||||
  volatile uint32_t *out_set_reg;
 | 
			
		||||
  volatile uint32_t *out_clr_reg;
 | 
			
		||||
  volatile uint32_t *mode_set_reg;
 | 
			
		||||
  volatile uint32_t *mode_clr_reg;
 | 
			
		||||
  volatile uint32_t *func_reg;
 | 
			
		||||
  uint32_t mask;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  arg->pin = this->pin_;
 | 
			
		||||
  arg->inverted = this->inverted_;
 | 
			
		||||
  if (this->pin_ < 16) {
 | 
			
		||||
    arg->in_reg = &GPI;
 | 
			
		||||
    arg->out_set_reg = &GPOS;
 | 
			
		||||
    arg->out_clr_reg = &GPOC;
 | 
			
		||||
    arg->mode_set_reg = &GPES;
 | 
			
		||||
    arg->mode_clr_reg = &GPEC;
 | 
			
		||||
    arg->func_reg = &GPF(this->pin_);
 | 
			
		||||
    arg->mask = 1 << this->pin_;
 | 
			
		||||
  } else {
 | 
			
		||||
    arg->in_reg = &GP16I;
 | 
			
		||||
    arg->out_set_reg = &GP16O;
 | 
			
		||||
    arg->out_clr_reg = nullptr;
 | 
			
		||||
    arg->mode_set_reg = &GP16E;
 | 
			
		||||
    arg->mode_clr_reg = nullptr;
 | 
			
		||||
    arg->func_reg = &GPF16;
 | 
			
		||||
    arg->mask = 1;
 | 
			
		||||
  }
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -88,20 +112,57 @@ void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
 | 
			
		||||
using namespace esp8266;
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  return bool(digitalRead(arg->pin)) != arg->inverted;  // NOLINT
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
 | 
			
		||||
  return bool(*arg->in_reg & arg->mask) != arg->inverted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  digitalWrite(arg->pin, value != arg->inverted ? 1 : 0);  // NOLINT
 | 
			
		||||
  if (arg->pin < 16) {
 | 
			
		||||
    if (value != arg->inverted) {
 | 
			
		||||
      *arg->out_set_reg = arg->mask;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->out_clr_reg = arg->mask;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (value != arg->inverted) {
 | 
			
		||||
      *arg->out_set_reg |= 1;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->out_set_reg &= ~1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  pinMode(arg->pin, flags_to_mode(flags, arg->pin));  // NOLINT
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
 | 
			
		||||
  if (arg->pin < 16) {
 | 
			
		||||
    if (flags & gpio::FLAG_OUTPUT) {
 | 
			
		||||
      *arg->mode_set_reg = arg->mask;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->mode_clr_reg = arg->mask;
 | 
			
		||||
    }
 | 
			
		||||
    if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
      *arg->func_reg |= 1 << GPFPU;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->func_reg &= ~(1 << GPFPU);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (flags & gpio::FLAG_OUTPUT) {
 | 
			
		||||
      *arg->mode_set_reg |= 1;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->mode_set_reg &= ~1;
 | 
			
		||||
    }
 | 
			
		||||
    if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
      *arg->func_reg |= 1 << GP16FPD;
 | 
			
		||||
    } else {
 | 
			
		||||
      *arg->func_reg &= ~(1 << GP16FPD);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
 | 
			
		||||
 | 
			
		||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
EVENT_SCHEMA = (
 | 
			
		||||
_EVENT_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -58,19 +58,17 @@ EVENT_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def event_schema(
 | 
			
		||||
    class_: MockObjClass = _UNDEF,
 | 
			
		||||
    class_: MockObjClass = cv.UNDEFINED,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = _UNDEF,
 | 
			
		||||
    entity_category: str = _UNDEF,
 | 
			
		||||
    device_class: str = _UNDEF,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    device_class: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {}
 | 
			
		||||
 | 
			
		||||
    if class_ is not _UNDEF:
 | 
			
		||||
    if class_ is not cv.UNDEFINED:
 | 
			
		||||
        schema[cv.GenerateID()] = cv.declare_id(class_)
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
@@ -78,10 +76,15 @@ def event_schema(
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_DEVICE_CLASS, device_class, validate_device_class),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not _UNDEF:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return EVENT_SCHEMA.extend(schema)
 | 
			
		||||
    return _EVENT_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
EVENT_SCHEMA = event_schema()
 | 
			
		||||
EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_event_core_(var, config, *, event_types: list[str]):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
    ICON_RESTART_ALERT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT
 | 
			
		||||
 | 
			
		||||
from .. import factory_reset_ns
 | 
			
		||||
 | 
			
		||||
@@ -16,21 +9,14 @@ FactoryResetSwitch = factory_reset_ns.class_(
 | 
			
		||||
    "FactoryResetSwitch", switch.Switch, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(FactoryResetSwitch),
 | 
			
		||||
        cv.Optional(CONF_INVERTED): cv.invalid(
 | 
			
		||||
            "Factory Reset switches do not support inverted mode!"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): cv.icon,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
        ): cv.entity_category,
 | 
			
		||||
    }
 | 
			
		||||
CONFIG_SCHEMA = switch.switch_schema(
 | 
			
		||||
    FactoryResetSwitch,
 | 
			
		||||
    block_inverted=True,
 | 
			
		||||
    icon=ICON_RESTART_ALERT,
 | 
			
		||||
    entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await switch.new_switch(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await switch.register_switch(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,10 @@ from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DIRECTION,
 | 
			
		||||
    CONF_DIRECTION_COMMAND_TOPIC,
 | 
			
		||||
    CONF_DIRECTION_STATE_TOPIC,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_OFF_SPEED_CYCLE,
 | 
			
		||||
@@ -80,16 +84,21 @@ FanPresetSetTrigger = fan_ns.class_(
 | 
			
		||||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
 | 
			
		||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
 | 
			
		||||
 | 
			
		||||
FAN_SCHEMA = (
 | 
			
		||||
_FAN_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Fan),
 | 
			
		||||
            cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
 | 
			
		||||
                RESTORE_MODES, upper=True, space="_"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
 | 
			
		||||
            cv.Optional(CONF_DIRECTION_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_DIRECTION_COMMAND_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.subscribe_topic
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
 | 
			
		||||
                cv.requires_component("mqtt"), cv.publish_topic
 | 
			
		||||
            ),
 | 
			
		||||
@@ -151,6 +160,37 @@ FAN_SCHEMA = (
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fan_schema(
 | 
			
		||||
    class_: cg.Pvariable,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    default_restore_mode: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
        (
 | 
			
		||||
            CONF_RESTORE_MODE,
 | 
			
		||||
            default_restore_mode,
 | 
			
		||||
            cv.enum(RESTORE_MODES, upper=True, space="_"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _FAN_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
FAN_SCHEMA = fan_schema(Fan)
 | 
			
		||||
FAN_SCHEMA.add_extra(cv.deprecated_schema_constant("fan"))
 | 
			
		||||
 | 
			
		||||
_PRESET_MODES_SCHEMA = cv.All(
 | 
			
		||||
    cv.ensure_list(cv.string_strict),
 | 
			
		||||
    cv.Length(min=1),
 | 
			
		||||
@@ -193,6 +233,14 @@ async def setup_fan_core_(var, config):
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(mqtt_id, var)
 | 
			
		||||
        await mqtt.register_mqtt_component(mqtt_, config)
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            direction_state_topic := config.get(CONF_DIRECTION_STATE_TOPIC)
 | 
			
		||||
        ) is not None:
 | 
			
		||||
            cg.add(mqtt_.set_custom_direction_state_topic(direction_state_topic))
 | 
			
		||||
        if (
 | 
			
		||||
            direction_command_topic := config.get(CONF_DIRECTION_COMMAND_TOPIC)
 | 
			
		||||
        ) is not None:
 | 
			
		||||
            cg.add(mqtt_.set_custom_direction_command_topic(direction_command_topic))
 | 
			
		||||
        if (
 | 
			
		||||
            oscillation_state_topic := config.get(CONF_OSCILLATION_STATE_TOPIC)
 | 
			
		||||
        ) is not None:
 | 
			
		||||
@@ -251,10 +299,9 @@ async def register_fan(var, config):
 | 
			
		||||
    await setup_fan_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def create_fan_state(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
async def new_fan(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_fan(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_CLOSE_ACTION,
 | 
			
		||||
    CONF_CLOSE_DURATION,
 | 
			
		||||
    CONF_CLOSE_ENDSTOP,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAX_DURATION,
 | 
			
		||||
    CONF_OPEN_ACTION,
 | 
			
		||||
    CONF_OPEN_DURATION,
 | 
			
		||||
@@ -50,20 +49,25 @@ def validate_infer_endstop(config):
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_FEEDBACK_COVER_BASE_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
CONFIG_FEEDBACK_COVER_BASE_SCHEMA = (
 | 
			
		||||
    cover.cover_schema(FeedbackCover)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(FeedbackCover),
 | 
			
		||||
            cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
            cv.Optional(CONF_OPEN_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
        cv.Optional(CONF_OPEN_OBSTACLE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
            cv.Optional(CONF_OPEN_OBSTACLE_SENSOR): cv.use_id(
 | 
			
		||||
                binary_sensor.BinarySensor
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
            cv.Optional(CONF_CLOSE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
        cv.Optional(CONF_CLOSE_OBSTACLE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
            cv.Optional(CONF_CLOSE_OBSTACLE_SENSOR): cv.use_id(
 | 
			
		||||
                binary_sensor.BinarySensor
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ASSUMED_STATE): cv.boolean,
 | 
			
		||||
@@ -79,7 +83,9 @@ CONFIG_FEEDBACK_COVER_BASE_SCHEMA = cover.COVER_SCHEMA.extend(
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
 | 
			
		||||
        },
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
@@ -90,9 +96,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
 | 
			
		||||
    # STOP
 | 
			
		||||
    await automation.build_automation(
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,10 @@ static const char *const TAG = "gpio.one_wire";
 | 
			
		||||
void GPIOOneWireBus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
 | 
			
		||||
  this->t_pin_->setup();
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search might fail
 | 
			
		||||
  this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  // clear bus with 480µs high, otherwise initial reset in search might fail
 | 
			
		||||
  this->pin_.digital_write(true);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
  this->search();
 | 
			
		||||
}
 | 
			
		||||
@@ -22,40 +24,49 @@ void GPIOOneWireBus::dump_config() {
 | 
			
		||||
  this->dump_devices_(TAG);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
 | 
			
		||||
int HOT IRAM_ATTR GPIOOneWireBus::reset_int() {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
      return -1;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
  } while (!this->pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  bool r;
 | 
			
		||||
  bool r = false;
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  this->pin_.digital_write(false);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  delayMicroseconds(30);
 | 
			
		||||
 | 
			
		||||
  while (micros() - start < 300) {
 | 
			
		||||
    // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  r = !pin_.digital_read();
 | 
			
		||||
    r = !this->pin_.digital_read();
 | 
			
		||||
    if (r)
 | 
			
		||||
      break;
 | 
			
		||||
    delayMicroseconds(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
  delayMicroseconds(start + 480 - micros());
 | 
			
		||||
  this->pin_.digital_write(true);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  return r ? 1 : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  this->pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // from datasheet:
 | 
			
		||||
  // write 0 low time: t_low0: min=60µs, max=120µs
 | 
			
		||||
@@ -64,72 +75,62 @@ void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
 | 
			
		||||
  // recovery time: t_rec: min=1µs
 | 
			
		||||
  // ds18b20 appears to read the bus after roughly 14µs
 | 
			
		||||
  uint32_t delay0 = bit ? 6 : 60;
 | 
			
		||||
  uint32_t delay1 = bit ? 59 : 5;
 | 
			
		||||
  uint32_t delay1 = bit ? 64 : 10;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  this->pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  this->pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // note: for reading we'll need very accurate timing, as the
 | 
			
		||||
  // timing for the digital_read() is tight; according to the datasheet,
 | 
			
		||||
  // we should read at the end of 16µs starting from the bus low
 | 
			
		||||
  // typically, the ds18b20 pulls the line high after 11µs for a logical 1
 | 
			
		||||
  // and 29µs for a logical 0
 | 
			
		||||
 | 
			
		||||
  uint32_t start = micros();
 | 
			
		||||
  // datasheet says >1µs
 | 
			
		||||
  delayMicroseconds(2);
 | 
			
		||||
  // datasheet says >= 1µs
 | 
			
		||||
  delayMicroseconds(5);
 | 
			
		||||
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  // measure from start value directly, to get best accurate timing no matter
 | 
			
		||||
  // how long pin_mode/delayMicroseconds took
 | 
			
		||||
  uint32_t now = micros();
 | 
			
		||||
  if (now - start < 12)
 | 
			
		||||
    delayMicroseconds(12 - (now - start));
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
 | 
			
		||||
  delayMicroseconds(8);
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
  bool r = this->pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
 | 
			
		||||
  now = micros();
 | 
			
		||||
  if (now - start < 60)
 | 
			
		||||
    delayMicroseconds(60 - (now - start));
 | 
			
		||||
  // read slot is at least 60µs
 | 
			
		||||
  delayMicroseconds(50);
 | 
			
		||||
 | 
			
		||||
  this->pin_.digital_write(true);
 | 
			
		||||
  this->pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit_(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit_(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++)
 | 
			
		||||
    ret |= (uint8_t(this->read_bit_()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit_()) << i);
 | 
			
		||||
@@ -144,6 +145,7 @@ void GPIOOneWireBus::reset_search() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
  if (this->last_device_flag_)
 | 
			
		||||
    return 0u;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
 | 
			
		||||
    this->pin_ = pin->to_isr();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reset() override;
 | 
			
		||||
  void write8(uint8_t val) override;
 | 
			
		||||
  void write64(uint64_t val) override;
 | 
			
		||||
  uint8_t read8() override;
 | 
			
		||||
@@ -31,10 +30,12 @@ class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
 | 
			
		||||
  int reset_int() override;
 | 
			
		||||
  void reset_search() override;
 | 
			
		||||
  uint64_t search_int() override;
 | 
			
		||||
  void write_bit_(bool bit);
 | 
			
		||||
  bool read_bit_();
 | 
			
		||||
  bool read_bit_(uint32_t *t);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace gpio
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
 | 
			
		||||
GPSListener = gps_ns.class_("GPSListener")
 | 
			
		||||
 | 
			
		||||
CONF_GPS_ID = "gps_id"
 | 
			
		||||
CONF_HDOP = "hdop"
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
@@ -40,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SPEED): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
 | 
			
		||||
                accuracy_decimals=6,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_COURSE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_DEGREES,
 | 
			
		||||
@@ -48,12 +49,16 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_METER,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HDOP): sensor.sensor_schema(
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("20s"))
 | 
			
		||||
@@ -92,5 +97,9 @@ async def to_code(config):
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_SATELLITES])
 | 
			
		||||
        cg.add(var.set_satellites_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if hdop_config := config.get(CONF_HDOP):
 | 
			
		||||
        sens = await sensor.new_sensor(hdop_config)
 | 
			
		||||
        cg.add(var.set_hdop_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    # https://platformio.org/lib/show/1655/TinyGPSPlus
 | 
			
		||||
    cg.add_library("mikalhart/TinyGPSPlus", "1.0.2")
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,9 @@ void GPS::update() {
 | 
			
		||||
 | 
			
		||||
  if (this->satellites_sensor_ != nullptr)
 | 
			
		||||
    this->satellites_sensor_->publish_state(this->satellites_);
 | 
			
		||||
 | 
			
		||||
  if (this->hdop_sensor_ != nullptr)
 | 
			
		||||
    this->hdop_sensor_->publish_state(this->hdop_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GPS::loop() {
 | 
			
		||||
@@ -44,23 +47,23 @@ void GPS::loop() {
 | 
			
		||||
 | 
			
		||||
      if (tiny_gps_.speed.isUpdated()) {
 | 
			
		||||
        this->speed_ = tiny_gps_.speed.kmph();
 | 
			
		||||
        ESP_LOGD(TAG, "Speed:");
 | 
			
		||||
        ESP_LOGD(TAG, "  %f km/h", this->speed_);
 | 
			
		||||
        ESP_LOGD(TAG, "Speed: %.3f km/h", this->speed_);
 | 
			
		||||
      }
 | 
			
		||||
      if (tiny_gps_.course.isUpdated()) {
 | 
			
		||||
        this->course_ = tiny_gps_.course.deg();
 | 
			
		||||
        ESP_LOGD(TAG, "Course:");
 | 
			
		||||
        ESP_LOGD(TAG, "  %f °", this->course_);
 | 
			
		||||
        ESP_LOGD(TAG, "Course: %.2f °", this->course_);
 | 
			
		||||
      }
 | 
			
		||||
      if (tiny_gps_.altitude.isUpdated()) {
 | 
			
		||||
        this->altitude_ = tiny_gps_.altitude.meters();
 | 
			
		||||
        ESP_LOGD(TAG, "Altitude:");
 | 
			
		||||
        ESP_LOGD(TAG, "  %f m", this->altitude_);
 | 
			
		||||
        ESP_LOGD(TAG, "Altitude: %.2f m", this->altitude_);
 | 
			
		||||
      }
 | 
			
		||||
      if (tiny_gps_.satellites.isUpdated()) {
 | 
			
		||||
        this->satellites_ = tiny_gps_.satellites.value();
 | 
			
		||||
        ESP_LOGD(TAG, "Satellites:");
 | 
			
		||||
        ESP_LOGD(TAG, "  %d", this->satellites_);
 | 
			
		||||
        ESP_LOGD(TAG, "Satellites: %d", this->satellites_);
 | 
			
		||||
      }
 | 
			
		||||
      if (tiny_gps_.hdop.isUpdated()) {
 | 
			
		||||
        this->hdop_ = tiny_gps_.hdop.hdop();
 | 
			
		||||
        ESP_LOGD(TAG, "HDOP: %.3f", this->hdop_);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (auto *listener : this->listeners_)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ class GPS : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  void set_course_sensor(sensor::Sensor *course_sensor) { course_sensor_ = course_sensor; }
 | 
			
		||||
  void set_altitude_sensor(sensor::Sensor *altitude_sensor) { altitude_sensor_ = altitude_sensor; }
 | 
			
		||||
  void set_satellites_sensor(sensor::Sensor *satellites_sensor) { satellites_sensor_ = satellites_sensor; }
 | 
			
		||||
  void set_hdop_sensor(sensor::Sensor *hdop_sensor) { hdop_sensor_ = hdop_sensor; }
 | 
			
		||||
 | 
			
		||||
  void register_listener(GPSListener *listener) {
 | 
			
		||||
    listener->parent_ = this;
 | 
			
		||||
@@ -46,12 +47,13 @@ class GPS : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  float latitude_ = -1;
 | 
			
		||||
  float longitude_ = -1;
 | 
			
		||||
  float speed_ = -1;
 | 
			
		||||
  float course_ = -1;
 | 
			
		||||
  float altitude_ = -1;
 | 
			
		||||
  int satellites_ = -1;
 | 
			
		||||
  float latitude_ = NAN;
 | 
			
		||||
  float longitude_ = NAN;
 | 
			
		||||
  float speed_ = NAN;
 | 
			
		||||
  float course_ = NAN;
 | 
			
		||||
  float altitude_ = NAN;
 | 
			
		||||
  int satellites_ = 0;
 | 
			
		||||
  double hdop_ = NAN;
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *latitude_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *longitude_sensor_{nullptr};
 | 
			
		||||
@@ -59,6 +61,7 @@ class GPS : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
  sensor::Sensor *course_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *altitude_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *satellites_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *hdop_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool has_time_{false};
 | 
			
		||||
  TinyGPSPlus tiny_gps_;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import cover, uart
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_CLOSE_DURATION, CONF_ID, CONF_OPEN_DURATION
 | 
			
		||||
from esphome.const import CONF_CLOSE_DURATION, CONF_OPEN_DURATION
 | 
			
		||||
 | 
			
		||||
he60r_ns = cg.esphome_ns.namespace("he60r")
 | 
			
		||||
HE60rCover = he60r_ns.class_("HE60rCover", cover.Cover, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cover.COVER_SCHEMA.extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
    cover.cover_schema(HE60rCover)
 | 
			
		||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(HE60rCover),
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_OPEN_DURATION, default="15s"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
@@ -34,9 +34,8 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    var = await cover.new_cover(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await uart.register_uart_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
 | 
			
		||||
 
 | 
			
		||||
@@ -16,14 +16,17 @@ HttpRequestUpdate = http_request_ns.class_(
 | 
			
		||||
 | 
			
		||||
CONF_OTA_ID = "ota_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    update.update_schema(HttpRequestUpdate)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
 | 
			
		||||
            cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
 | 
			
		||||
            cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
 | 
			
		||||
            cv.Required(CONF_SOURCE): cv.url,
 | 
			
		||||
        }
 | 
			
		||||
).extend(cv.polling_component_schema("6h"))
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("6h"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ DEPENDENCIES = ["i2s_audio"]
 | 
			
		||||
 | 
			
		||||
CONF_ADC_PIN = "adc_pin"
 | 
			
		||||
CONF_ADC_TYPE = "adc_type"
 | 
			
		||||
CONF_CORRECT_DC_OFFSET = "correct_dc_offset"
 | 
			
		||||
CONF_PDM = "pdm"
 | 
			
		||||
 | 
			
		||||
I2SAudioMicrophone = i2s_audio_ns.class_(
 | 
			
		||||
@@ -88,10 +89,13 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
			
		||||
        default_sample_rate=16000,
 | 
			
		||||
        default_channel=CONF_RIGHT,
 | 
			
		||||
        default_bits_per_sample="32bit",
 | 
			
		||||
    ).extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_CORRECT_DC_OFFSET, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.typed_schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -140,3 +144,5 @@ async def to_code(config):
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
			
		||||
        cg.add(var.set_pdm(config[CONF_PDM]))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_correct_dc_offset(config[CONF_CORRECT_DC_OFFSET]))
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,8 @@
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/audio/audio.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2s_audio {
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +24,9 @@ static const uint32_t READ_DURATION_MS = 16;
 | 
			
		||||
static const size_t TASK_STACK_SIZE = 4096;
 | 
			
		||||
static const ssize_t TASK_PRIORITY = 23;
 | 
			
		||||
 | 
			
		||||
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
 | 
			
		||||
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "i2s_audio.microphone";
 | 
			
		||||
 | 
			
		||||
enum MicrophoneEventGroupBits : uint32_t {
 | 
			
		||||
@@ -70,21 +75,11 @@ void I2SAudioMicrophone::setup() {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->configure_stream_settings_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::start() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  xSemaphoreTake(this->active_listeners_semaphore_, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return false;  // Waiting for another i2s to return lock
 | 
			
		||||
  }
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::configure_stream_settings_() {
 | 
			
		||||
  uint8_t channel_count = 1;
 | 
			
		||||
#ifdef USE_I2S_LEGACY
 | 
			
		||||
  uint8_t bits_per_sample = this->bits_per_sample_;
 | 
			
		||||
@@ -93,10 +88,10 @@ bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
    channel_count = 2;
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_AUTO) {
 | 
			
		||||
    this->slot_bit_width_ = I2S_SLOT_BIT_WIDTH_16BIT;
 | 
			
		||||
  uint8_t bits_per_sample = 16;
 | 
			
		||||
  if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
 | 
			
		||||
    bits_per_sample = this->slot_bit_width_;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t bits_per_sample = this->slot_bit_width_;
 | 
			
		||||
 | 
			
		||||
  if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
 | 
			
		||||
    channel_count = 2;
 | 
			
		||||
@@ -114,6 +109,26 @@ bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->pdm_) {
 | 
			
		||||
    bits_per_sample = 16;  // PDM mics are always 16 bits per sample
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::start() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  xSemaphoreTake(this->active_listeners_semaphore_, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
  if (!this->parent_->try_lock()) {
 | 
			
		||||
    return false;  // Waiting for another i2s to return lock
 | 
			
		||||
  }
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_I2S_LEGACY
 | 
			
		||||
  i2s_driver_config_t config = {
 | 
			
		||||
      .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
 | 
			
		||||
@@ -202,8 +217,6 @@ bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
  i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
 | 
			
		||||
#if SOC_I2S_SUPPORTS_PDM_RX
 | 
			
		||||
  if (this->pdm_) {
 | 
			
		||||
    bits_per_sample = 16;  // PDM mics are always 16 bits per sample with the IDF 5 driver
 | 
			
		||||
 | 
			
		||||
    i2s_pdm_rx_clk_config_t clk_cfg = {
 | 
			
		||||
        .sample_rate_hz = this->sample_rate_,
 | 
			
		||||
        .clk_src = clk_src,
 | 
			
		||||
@@ -277,10 +290,8 @@ bool I2SAudioMicrophone::start_driver_() {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
 | 
			
		||||
 | 
			
		||||
  this->status_clear_error();
 | 
			
		||||
 | 
			
		||||
  this->configure_stream_settings_();  // redetermine the settings in case some settings were changed after compilation
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -361,9 +372,12 @@ void I2SAudioMicrophone::mic_task(void *params) {
 | 
			
		||||
        samples.resize(bytes_to_read);
 | 
			
		||||
        size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
 | 
			
		||||
        samples.resize(bytes_read);
 | 
			
		||||
        if (this_microphone->correct_dc_offset_) {
 | 
			
		||||
          this_microphone->fix_dc_offset_(samples);
 | 
			
		||||
        }
 | 
			
		||||
        this_microphone->data_callbacks_.call(samples);
 | 
			
		||||
      } else {
 | 
			
		||||
        delay(READ_DURATION_MS);
 | 
			
		||||
        vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -373,11 +387,34 @@ void I2SAudioMicrophone::mic_task(void *params) {
 | 
			
		||||
 | 
			
		||||
  xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
 | 
			
		||||
  while (true) {
 | 
			
		||||
    // Continuously delay until the loop method delete the task
 | 
			
		||||
    delay(10);
 | 
			
		||||
    // Continuously delay until the loop method deletes the task
 | 
			
		||||
    vTaskDelay(pdMS_TO_TICKS(10));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
 | 
			
		||||
  const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
 | 
			
		||||
  const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
 | 
			
		||||
 | 
			
		||||
  if (total_samples == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int64_t offset_accumulator = 0;
 | 
			
		||||
  for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
 | 
			
		||||
    const uint32_t byte_index = sample_index * bytes_per_sample;
 | 
			
		||||
    int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
 | 
			
		||||
    offset_accumulator += sample;
 | 
			
		||||
    sample -= this->dc_offset_;
 | 
			
		||||
    audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const int32_t new_offset = offset_accumulator / total_samples;
 | 
			
		||||
  this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
 | 
			
		||||
                     (DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
 | 
			
		||||
                         DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
 | 
			
		||||
  size_t bytes_read = 0;
 | 
			
		||||
#ifdef USE_I2S_LEGACY
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,10 @@
 | 
			
		||||
#include "esphome/components/microphone/microphone.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/event_groups.h>
 | 
			
		||||
#include <freertos/semphr.h>
 | 
			
		||||
#include <freertos/task.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2s_audio {
 | 
			
		||||
@@ -20,6 +22,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
  void stop() override;
 | 
			
		||||
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void set_correct_dc_offset(bool correct_dc_offset) { this->correct_dc_offset_ = correct_dc_offset; }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_I2S_LEGACY
 | 
			
		||||
  void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
 | 
			
		||||
#else
 | 
			
		||||
@@ -41,8 +46,16 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
  bool start_driver_();
 | 
			
		||||
  void stop_driver_();
 | 
			
		||||
 | 
			
		||||
  /// @brief Attempts to correct a microphone DC offset; e.g., a microphones silent level is offset from 0. Applies a
 | 
			
		||||
  /// correction offset that is updated using an exponential moving average for all samples away from 0.
 | 
			
		||||
  /// @param data
 | 
			
		||||
  void fix_dc_offset_(std::vector<uint8_t> &data);
 | 
			
		||||
 | 
			
		||||
  size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait);
 | 
			
		||||
 | 
			
		||||
  /// @brief Sets the Microphone ``audio_stream_info_`` member variable to the configured I2S settings.
 | 
			
		||||
  void configure_stream_settings_();
 | 
			
		||||
 | 
			
		||||
  static void mic_task(void *params);
 | 
			
		||||
 | 
			
		||||
  SemaphoreHandle_t active_listeners_semaphore_{nullptr};
 | 
			
		||||
@@ -61,6 +74,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
			
		||||
  i2s_chan_handle_t rx_handle_;
 | 
			
		||||
#endif
 | 
			
		||||
  bool pdm_{false};
 | 
			
		||||
 | 
			
		||||
  bool correct_dc_offset_;
 | 
			
		||||
  int32_t dc_offset_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace i2s_audio
 | 
			
		||||
 
 | 
			
		||||
@@ -629,7 +629,16 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
 | 
			
		||||
    std_slot_cfg =
 | 
			
		||||
        I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) audio_stream_info.get_bits_per_sample(), slot_mode);
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_ESP32_VARIANT_ESP32
 | 
			
		||||
  // There seems to be a bug on the ESP32 (non-variant) platform where setting the slot bit width higher then the bits
 | 
			
		||||
  // per sample causes the audio to play too fast. Setting the ws_width to the configured slot bit width seems to
 | 
			
		||||
  // make it play at the correct speed while sending more bits per slot.
 | 
			
		||||
  if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
 | 
			
		||||
    std_slot_cfg.ws_width = static_cast<uint32_t>(this->slot_bit_width_);
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  std_slot_cfg.slot_bit_width = this->slot_bit_width_;
 | 
			
		||||
#endif
 | 
			
		||||
  std_slot_cfg.slot_mask = slot_mask;
 | 
			
		||||
 | 
			
		||||
  pin_config.dout = this->dout_pin_;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import enum
 | 
			
		||||
 | 
			
		||||
import esphome.automation as auto
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import mqtt, power_supply, web_server
 | 
			
		||||
@@ -13,15 +15,18 @@ from esphome.const import (
 | 
			
		||||
    CONF_COLOR_TEMPERATURE,
 | 
			
		||||
    CONF_DEFAULT_TRANSITION_LENGTH,
 | 
			
		||||
    CONF_EFFECTS,
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_FLASH_TRANSITION_LENGTH,
 | 
			
		||||
    CONF_GAMMA_CORRECT,
 | 
			
		||||
    CONF_GREEN,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INITIAL_STATE,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_ON_TURN_OFF,
 | 
			
		||||
    CONF_ON_TURN_ON,
 | 
			
		||||
    CONF_OUTPUT_ID,
 | 
			
		||||
    CONF_POWER_SUPPLY,
 | 
			
		||||
    CONF_RED,
 | 
			
		||||
    CONF_RESTORE_MODE,
 | 
			
		||||
@@ -33,6 +38,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_WHITE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
from .automation import LIGHT_STATE_SCHEMA
 | 
			
		||||
@@ -141,6 +147,51 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LightType(enum.IntEnum):
 | 
			
		||||
    """Light type enum."""
 | 
			
		||||
 | 
			
		||||
    BINARY = 0
 | 
			
		||||
    BRIGHTNESS_ONLY = 1
 | 
			
		||||
    RGB = 2
 | 
			
		||||
    ADDRESSABLE = 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def light_schema(
 | 
			
		||||
    class_: MockObjClass,
 | 
			
		||||
    type_: LightType,
 | 
			
		||||
    *,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    default_restore_mode: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {
 | 
			
		||||
        cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(class_),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
        (
 | 
			
		||||
            CONF_RESTORE_MODE,
 | 
			
		||||
            default_restore_mode,
 | 
			
		||||
            cv.enum(RESTORE_MODES, upper=True, space="_"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    if type_ == LightType.BINARY:
 | 
			
		||||
        return BINARY_LIGHT_SCHEMA.extend(schema)
 | 
			
		||||
    if type_ == LightType.BRIGHTNESS_ONLY:
 | 
			
		||||
        return BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(schema)
 | 
			
		||||
    if type_ == LightType.RGB:
 | 
			
		||||
        return RGB_LIGHT_SCHEMA.extend(schema)
 | 
			
		||||
    if type_ == LightType.ADDRESSABLE:
 | 
			
		||||
        return ADDRESSABLE_LIGHT_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
    raise ValueError(f"Invalid light type: {type_}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_color_temperature_channels(value):
 | 
			
		||||
    if (
 | 
			
		||||
        CONF_COLD_WHITE_COLOR_TEMPERATURE in value
 | 
			
		||||
@@ -223,6 +274,12 @@ async def register_light(output_var, config):
 | 
			
		||||
    await setup_light_core_(light_var, output_var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_light(config, *args):
 | 
			
		||||
    output_var = cg.new_Pvariable(config[CONF_OUTPUT_ID], *args)
 | 
			
		||||
    await register_light(output_var, config)
 | 
			
		||||
    return output_var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_LIGHT")
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import esphome.codegen as cg
 | 
			
		||||
from esphome.components import mqtt, web_server
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ENTITY_CATEGORY,
 | 
			
		||||
    CONF_ICON,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_ON_LOCK,
 | 
			
		||||
@@ -12,6 +14,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_WEB_SERVER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.cpp_generator import MockObjClass
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
@@ -31,7 +34,19 @@ LockCondition = lock_ns.class_("LockCondition", Condition)
 | 
			
		||||
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
 | 
			
		||||
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
 | 
			
		||||
 | 
			
		||||
LOCK_SCHEMA = (
 | 
			
		||||
LockState = lock_ns.enum("LockState")
 | 
			
		||||
 | 
			
		||||
LOCK_STATES = {
 | 
			
		||||
    "LOCKED": LockState.LOCK_STATE_LOCKED,
 | 
			
		||||
    "UNLOCKED": LockState.LOCK_STATE_UNLOCKED,
 | 
			
		||||
    "JAMMED": LockState.LOCK_STATE_JAMMED,
 | 
			
		||||
    "LOCKING": LockState.LOCK_STATE_LOCKING,
 | 
			
		||||
    "UNLOCKING": LockState.LOCK_STATE_UNLOCKING,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
validate_lock_state = cv.enum(LOCK_STATES, upper=True)
 | 
			
		||||
 | 
			
		||||
_LOCK_SCHEMA = (
 | 
			
		||||
    cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
 | 
			
		||||
    .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(
 | 
			
		||||
@@ -52,7 +67,33 @@ LOCK_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_lock_core_(var, config):
 | 
			
		||||
def lock_schema(
 | 
			
		||||
    class_: MockObjClass = cv.UNDEFINED,
 | 
			
		||||
    *,
 | 
			
		||||
    icon: str = cv.UNDEFINED,
 | 
			
		||||
    entity_category: str = cv.UNDEFINED,
 | 
			
		||||
) -> cv.Schema:
 | 
			
		||||
    schema = {}
 | 
			
		||||
 | 
			
		||||
    if class_ is not cv.UNDEFINED:
 | 
			
		||||
        schema[cv.GenerateID()] = cv.declare_id(class_)
 | 
			
		||||
 | 
			
		||||
    for key, default, validator in [
 | 
			
		||||
        (CONF_ICON, icon, cv.icon),
 | 
			
		||||
        (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
 | 
			
		||||
    ]:
 | 
			
		||||
        if default is not cv.UNDEFINED:
 | 
			
		||||
            schema[cv.Optional(key, default=default)] = validator
 | 
			
		||||
 | 
			
		||||
    return _LOCK_SCHEMA.extend(schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Remove before 2025.11.0
 | 
			
		||||
LOCK_SCHEMA = lock_schema()
 | 
			
		||||
LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def _setup_lock_core(var, config):
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_LOCK, []):
 | 
			
		||||
@@ -74,12 +115,18 @@ async def register_lock(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.Pvariable(config[CONF_ID], var)
 | 
			
		||||
    cg.add(cg.App.register_lock(var))
 | 
			
		||||
    await setup_lock_core_(var, config)
 | 
			
		||||
    await _setup_lock_core(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def new_lock(config, *args):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], *args)
 | 
			
		||||
    await register_lock(var, config)
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOCK_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Lock),
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(Lock),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/lock/lock.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace lock {
 | 
			
		||||
@@ -72,16 +72,5 @@ class LockUnlockTrigger : public Trigger<> {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
 | 
			
		||||
  TEMPLATABLE_VALUE(LockState, state)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Lock *lock_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace lock
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,7 @@ DEFAULT = "DEFAULT"
 | 
			
		||||
 | 
			
		||||
CONF_INITIAL_LEVEL = "initial_level"
 | 
			
		||||
CONF_LOGGER_ID = "logger_id"
 | 
			
		||||
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
 | 
			
		||||
 | 
			
		||||
UART_SELECTION_ESP32 = {
 | 
			
		||||
    VARIANT_ESP32: [UART0, UART1, UART2],
 | 
			
		||||
@@ -180,6 +181,20 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
 | 
			
		||||
            cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
 | 
			
		||||
            cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
 | 
			
		||||
            cv.SplitDefault(
 | 
			
		||||
                CONF_TASK_LOG_BUFFER_SIZE,
 | 
			
		||||
                esp32=768,  # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names)
 | 
			
		||||
            ): cv.All(
 | 
			
		||||
                cv.only_on_esp32,
 | 
			
		||||
                cv.validate_bytes,
 | 
			
		||||
                cv.Any(
 | 
			
		||||
                    cv.int_(0),  # Disabled
 | 
			
		||||
                    cv.int_range(
 | 
			
		||||
                        min=640,  # Min: ~4-5 messages with 70-byte text plus thread names
 | 
			
		||||
                        max=32768,  # Max: Depends on message sizes, typically ~300 messages with default size
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            cv.SplitDefault(
 | 
			
		||||
                CONF_HARDWARE_UART,
 | 
			
		||||
                esp8266=UART0,
 | 
			
		||||
@@ -238,6 +253,12 @@ async def to_code(config):
 | 
			
		||||
        baud_rate,
 | 
			
		||||
        config[CONF_TX_BUFFER_SIZE],
 | 
			
		||||
    )
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
 | 
			
		||||
        if task_log_buffer_size > 0:
 | 
			
		||||
            cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
 | 
			
		||||
            cg.add(log.init_log_buffer(task_log_buffer_size))
 | 
			
		||||
 | 
			
		||||
    cg.add(log.set_log_level(initial_level))
 | 
			
		||||
    if CONF_HARDWARE_UART in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
#include <memory>  // For unique_ptr
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
@@ -10,127 +13,121 @@ namespace logger {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "logger";
 | 
			
		||||
 | 
			
		||||
static const char *const LOG_LEVEL_COLORS[] = {
 | 
			
		||||
    "",                                            // NONE
 | 
			
		||||
    ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED),       // ERROR
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW),   // WARNING
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN),    // INFO
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA),  // CONFIG
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN),     // DEBUG
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY),     // VERBOSE
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE),    // VERY_VERBOSE
 | 
			
		||||
};
 | 
			
		||||
static const char *const LOG_LEVEL_LETTERS[] = {
 | 
			
		||||
    "",    // NONE
 | 
			
		||||
    "E",   // ERROR
 | 
			
		||||
    "W",   // WARNING
 | 
			
		||||
    "I",   // INFO
 | 
			
		||||
    "C",   // CONFIG
 | 
			
		||||
    "D",   // DEBUG
 | 
			
		||||
    "V",   // VERBOSE
 | 
			
		||||
    "VV",  // VERY_VERBOSE
 | 
			
		||||
};
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
// Implementation for ESP32 (multi-core with atomic support)
 | 
			
		||||
// Main thread: synchronous logging with direct buffer access
 | 
			
		||||
// Other threads: console output with stack buffer, callbacks via async buffer
 | 
			
		||||
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) {  // NOLINT
 | 
			
		||||
  if (level > this->level_for(tag) || recursion_guard_.load(std::memory_order_relaxed))
 | 
			
		||||
    return;
 | 
			
		||||
  recursion_guard_.store(true, std::memory_order_relaxed);
 | 
			
		||||
 | 
			
		||||
void Logger::write_header_(int level, const char *tag, int line) {
 | 
			
		||||
  if (level < 0)
 | 
			
		||||
    level = 0;
 | 
			
		||||
  if (level > 7)
 | 
			
		||||
    level = 7;
 | 
			
		||||
 | 
			
		||||
  const char *color = LOG_LEVEL_COLORS[level];
 | 
			
		||||
  const char *letter = LOG_LEVEL_LETTERS[level];
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
 | 
			
		||||
  TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
 | 
			
		||||
#else
 | 
			
		||||
  void *current_task = nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // For main task: call log_message_to_buffer_and_send_ which does console and callback logging
 | 
			
		||||
  if (current_task == main_task_) {
 | 
			
		||||
    this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line);
 | 
			
		||||
  } else {
 | 
			
		||||
    const char *thread_name = "";  // NOLINT(clang-analyzer-deadcode.DeadStores)
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
    thread_name = pcTaskGetName(current_task);
 | 
			
		||||
#elif defined(USE_LIBRETINY)
 | 
			
		||||
    thread_name = pcTaskGetTaskName(current_task);
 | 
			
		||||
#endif
 | 
			
		||||
    this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line,
 | 
			
		||||
                            ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color);
 | 
			
		||||
  }
 | 
			
		||||
    this->log_message_to_buffer_and_send_(level, tag, line, format, args);
 | 
			
		||||
    recursion_guard_.store(false, std::memory_order_release);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For non-main tasks: use stack-allocated buffer only for console output
 | 
			
		||||
  if (this->baud_rate_ > 0) {  // If logging is enabled, write to console
 | 
			
		||||
    // Maximum size for console log messages (includes null terminator)
 | 
			
		||||
    static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
 | 
			
		||||
    char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE];  // MUST be stack allocated for thread safety
 | 
			
		||||
    int buffer_at = 0;                              // Initialize buffer position
 | 
			
		||||
    this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
 | 
			
		||||
                                                MAX_CONSOLE_LOG_MSG_SIZE);
 | 
			
		||||
    this->write_msg_(console_buffer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
  // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
 | 
			
		||||
  if (this->log_callback_.size() > 0) {
 | 
			
		||||
    // This will be processed in the main loop
 | 
			
		||||
    this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag, static_cast<uint16_t>(line),
 | 
			
		||||
                                                current_task, format, args);
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
 | 
			
		||||
  recursion_guard_.store(false, std::memory_order_release);
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifndef USE_ESP32
 | 
			
		||||
// Implementation for platforms that do not support atomic operations
 | 
			
		||||
// or have to consider logging in other tasks
 | 
			
		||||
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) {  // NOLINT
 | 
			
		||||
  if (level > this->level_for(tag) || recursion_guard_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  recursion_guard_ = true;
 | 
			
		||||
  this->reset_buffer_();
 | 
			
		||||
  this->write_header_(level, tag, line);
 | 
			
		||||
  this->vprintf_to_buffer_(format, args);
 | 
			
		||||
  this->write_footer_();
 | 
			
		||||
  this->log_message_(level, tag);
 | 
			
		||||
 | 
			
		||||
  // Format and send to both console and callbacks
 | 
			
		||||
  this->log_message_to_buffer_and_send_(level, tag, line, format, args);
 | 
			
		||||
 | 
			
		||||
  recursion_guard_ = false;
 | 
			
		||||
}
 | 
			
		||||
#endif  // !USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
 | 
			
		||||
// Implementation for ESP8266 with flash string support.
 | 
			
		||||
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
 | 
			
		||||
void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
 | 
			
		||||
                          va_list args) {  // NOLINT
 | 
			
		||||
  if (level > this->level_for(tag) || recursion_guard_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  recursion_guard_ = true;
 | 
			
		||||
  this->reset_buffer_();
 | 
			
		||||
  // copy format string
 | 
			
		||||
  this->tx_buffer_at_ = 0;
 | 
			
		||||
 | 
			
		||||
  // Copy format string from progmem
 | 
			
		||||
  auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
 | 
			
		||||
  size_t len = 0;
 | 
			
		||||
  char ch = '.';
 | 
			
		||||
  while (!this->is_buffer_full_() && ch != '\0') {
 | 
			
		||||
  while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
 | 
			
		||||
    this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
 | 
			
		||||
  }
 | 
			
		||||
  // Buffer full form copying format
 | 
			
		||||
  if (this->is_buffer_full_())
 | 
			
		||||
 | 
			
		||||
  // Buffer full from copying format
 | 
			
		||||
  if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
 | 
			
		||||
    recursion_guard_ = false;  // Make sure to reset the recursion guard before returning
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // length of format string, includes null terminator
 | 
			
		||||
  uint32_t offset = this->tx_buffer_at_;
 | 
			
		||||
  // Save the offset before calling format_log_to_buffer_with_terminator_
 | 
			
		||||
  // since it will increment tx_buffer_at_ to the end of the formatted string
 | 
			
		||||
  uint32_t msg_start = this->tx_buffer_at_;
 | 
			
		||||
  this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
 | 
			
		||||
                                              &this->tx_buffer_at_, this->tx_buffer_size_);
 | 
			
		||||
 | 
			
		||||
  // Write to console and send callback starting at the msg_start
 | 
			
		||||
  if (this->baud_rate_ > 0) {
 | 
			
		||||
    this->write_msg_(this->tx_buffer_ + msg_start);
 | 
			
		||||
  }
 | 
			
		||||
  this->call_log_callbacks_(level, tag, this->tx_buffer_ + msg_start);
 | 
			
		||||
 | 
			
		||||
  // now apply vsnprintf
 | 
			
		||||
  this->write_header_(level, tag, line);
 | 
			
		||||
  this->vprintf_to_buffer_(this->tx_buffer_, args);
 | 
			
		||||
  this->write_footer_();
 | 
			
		||||
  this->log_message_(level, tag, offset);
 | 
			
		||||
  recursion_guard_ = false;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_STORE_LOG_STR_IN_FLASH
 | 
			
		||||
 | 
			
		||||
int HOT Logger::level_for(const char *tag) {
 | 
			
		||||
  if (this->log_levels_.count(tag) != 0)
 | 
			
		||||
    return this->log_levels_[tag];
 | 
			
		||||
inline int Logger::level_for(const char *tag) {
 | 
			
		||||
  auto it = this->log_levels_.find(tag);
 | 
			
		||||
  if (it != this->log_levels_.end())
 | 
			
		||||
    return it->second;
 | 
			
		||||
  return this->current_level_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT Logger::log_message_(int level, const char *tag, int offset) {
 | 
			
		||||
  // remove trailing newline
 | 
			
		||||
  if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') {
 | 
			
		||||
    this->tx_buffer_at_--;
 | 
			
		||||
  }
 | 
			
		||||
  // make sure null terminator is present
 | 
			
		||||
  this->set_null_terminator_();
 | 
			
		||||
 | 
			
		||||
  const char *msg = this->tx_buffer_ + offset;
 | 
			
		||||
 | 
			
		||||
  if (this->baud_rate_ > 0) {
 | 
			
		||||
    this->write_msg_(msg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
void HOT Logger::call_log_callbacks_(int level, const char *tag, const char *msg) {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  // Suppress network-logging if memory constrained, but still log to serial
 | 
			
		||||
  // ports. In some configurations (eg BLE enabled) there may be some transient
 | 
			
		||||
  // Suppress network-logging if memory constrained
 | 
			
		||||
  // In some configurations (eg BLE enabled) there may be some transient
 | 
			
		||||
  // memory exhaustion, and trying to log when OOM can lead to a crash. Skipping
 | 
			
		||||
  // here usually allows the stack to recover instead.
 | 
			
		||||
  // See issue #1234 for analysis.
 | 
			
		||||
  if (xPortGetFreeHeapSize() < 2048)
 | 
			
		||||
    return;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->log_callback_.call(level, tag, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -141,13 +138,16 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
 | 
			
		||||
  this->main_task_ = xTaskGetCurrentTaskHandle();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_LOGGER_USB_CDC
 | 
			
		||||
void Logger::loop() {
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  if (this->uart_ != UART_SELECTION_USB_CDC) {
 | 
			
		||||
    return;
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
void Logger::init_log_buffer(size_t total_buffer_size) {
 | 
			
		||||
  this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
 | 
			
		||||
void Logger::loop() {
 | 
			
		||||
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
 | 
			
		||||
  if (this->uart_ == UART_SELECTION_USB_CDC) {
 | 
			
		||||
    static bool opened = false;
 | 
			
		||||
    if (opened == Serial) {
 | 
			
		||||
      return;
 | 
			
		||||
@@ -156,6 +156,32 @@ void Logger::loop() {
 | 
			
		||||
      App.schedule_dump_config();
 | 
			
		||||
    }
 | 
			
		||||
    opened = !opened;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
  // Process any buffered messages when available
 | 
			
		||||
  if (this->log_buffer_->has_messages()) {
 | 
			
		||||
    logger::TaskLogBuffer::LogMessage *message;
 | 
			
		||||
    const char *text;
 | 
			
		||||
    void *received_token;
 | 
			
		||||
 | 
			
		||||
    // Process messages from the buffer
 | 
			
		||||
    while (this->log_buffer_->borrow_message_main_loop(&message, &text, &received_token)) {
 | 
			
		||||
      this->tx_buffer_at_ = 0;
 | 
			
		||||
      // Use the thread name that was stored when the message was created
 | 
			
		||||
      // This avoids potential crashes if the task no longer exists
 | 
			
		||||
      const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
 | 
			
		||||
      this->write_header_to_buffer_(message->level, message->tag, message->line, thread_name, this->tx_buffer_,
 | 
			
		||||
                                    &this->tx_buffer_at_, this->tx_buffer_size_);
 | 
			
		||||
      this->write_body_to_buffer_(text, message->text_length, this->tx_buffer_, &this->tx_buffer_at_,
 | 
			
		||||
                                  this->tx_buffer_size_);
 | 
			
		||||
      this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
 | 
			
		||||
      this->tx_buffer_[this->tx_buffer_at_] = '\0';
 | 
			
		||||
      this->call_log_callbacks_(message->level, message->tag, this->tx_buffer_);
 | 
			
		||||
      this->log_buffer_->release_message_main_loop(received_token);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -171,7 +197,7 @@ void Logger::add_on_log_callback(std::function<void(int, const char *, const cha
 | 
			
		||||
  this->log_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 | 
			
		||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 | 
			
		||||
static const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 | 
			
		||||
 | 
			
		||||
void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
			
		||||
@@ -181,12 +207,16 @@ void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Log Baud Rate: %" PRIu32, this->baud_rate_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Hardware UART: %s", get_uart_selection_());
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
  if (this->log_buffer_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Task Log Buffer Size: %u", this->log_buffer_->size());
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  for (auto &it : this->log_levels_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.first.c_str(), LOG_LEVELS[it.second]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void Logger::write_footer_() { this->write_to_buffer_(ESPHOME_LOG_RESET_COLOR, strlen(ESPHOME_LOG_RESET_COLOR)); }
 | 
			
		||||
 | 
			
		||||
void Logger::set_log_level(int level) {
 | 
			
		||||
  if (level > ESPHOME_LOG_LEVEL) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,19 @@
 | 
			
		||||
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <map>
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#endif
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
#include "task_log_buffer.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
@@ -26,6 +33,29 @@ namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace logger {
 | 
			
		||||
 | 
			
		||||
// Color and letter constants for log levels
 | 
			
		||||
static const char *const LOG_LEVEL_COLORS[] = {
 | 
			
		||||
    "",                                            // NONE
 | 
			
		||||
    ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED),       // ERROR
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW),   // WARNING
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN),    // INFO
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA),  // CONFIG
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN),     // DEBUG
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY),     // VERBOSE
 | 
			
		||||
    ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE),    // VERY_VERBOSE
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const LOG_LEVEL_LETTERS[] = {
 | 
			
		||||
    "",    // NONE
 | 
			
		||||
    "E",   // ERROR
 | 
			
		||||
    "W",   // WARNING
 | 
			
		||||
    "I",   // INFO
 | 
			
		||||
    "C",   // CONFIG
 | 
			
		||||
    "D",   // DEBUG
 | 
			
		||||
    "V",   // VERBOSE
 | 
			
		||||
    "VV",  // VERY_VERBOSE
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
 | 
			
		||||
/** Enum for logging UART selection
 | 
			
		||||
 *
 | 
			
		||||
@@ -57,7 +87,10 @@ enum UARTSelection {
 | 
			
		||||
class Logger : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
 | 
			
		||||
#ifdef USE_LOGGER_USB_CDC
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
  void init_log_buffer(size_t total_buffer_size);
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
 | 
			
		||||
  void loop() override;
 | 
			
		||||
#endif
 | 
			
		||||
  /// Manually set the baud rate for serial, set to 0 to disable.
 | 
			
		||||
@@ -87,7 +120,7 @@ class Logger : public Component {
 | 
			
		||||
  void pre_setup();
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  int level_for(const char *tag);
 | 
			
		||||
  inline int level_for(const char *tag);
 | 
			
		||||
 | 
			
		||||
  /// Register a callback that will be called for every log message sent
 | 
			
		||||
  void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback);
 | 
			
		||||
@@ -103,46 +136,66 @@ class Logger : public Component {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_header_(int level, const char *tag, int line);
 | 
			
		||||
  void write_footer_();
 | 
			
		||||
  void log_message_(int level, const char *tag, int offset = 0);
 | 
			
		||||
  void call_log_callbacks_(int level, const char *tag, const char *msg);
 | 
			
		||||
  void write_msg_(const char *msg);
 | 
			
		||||
 | 
			
		||||
  inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; }
 | 
			
		||||
  inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; }
 | 
			
		||||
  inline void reset_buffer_() { this->tx_buffer_at_ = 0; }
 | 
			
		||||
  inline void set_null_terminator_() {
 | 
			
		||||
    // does not increment buffer_at
 | 
			
		||||
    this->tx_buffer_[this->tx_buffer_at_] = '\0';
 | 
			
		||||
  }
 | 
			
		||||
  inline void write_to_buffer_(char value) {
 | 
			
		||||
    if (!this->is_buffer_full_())
 | 
			
		||||
      this->tx_buffer_[this->tx_buffer_at_++] = value;
 | 
			
		||||
  }
 | 
			
		||||
  inline void write_to_buffer_(const char *value, int length) {
 | 
			
		||||
    for (int i = 0; i < length && !this->is_buffer_full_(); i++) {
 | 
			
		||||
      this->tx_buffer_[this->tx_buffer_at_++] = value[i];
 | 
			
		||||
  // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
 | 
			
		||||
  // It's the caller's responsibility to initialize buffer_at (typically to 0)
 | 
			
		||||
  inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format,
 | 
			
		||||
                                                        va_list args, char *buffer, int *buffer_at, int buffer_size) {
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
 | 
			
		||||
    this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
 | 
			
		||||
#else
 | 
			
		||||
    this->write_header_to_buffer_(level, tag, line, nullptr, buffer, buffer_at, buffer_size);
 | 
			
		||||
#endif
 | 
			
		||||
    this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, args);
 | 
			
		||||
    this->write_footer_to_buffer_(buffer, buffer_at, buffer_size);
 | 
			
		||||
 | 
			
		||||
    // Always ensure the buffer has a null terminator, even if we need to
 | 
			
		||||
    // overwrite the last character of the actual content
 | 
			
		||||
    if (*buffer_at >= buffer_size) {
 | 
			
		||||
      buffer[buffer_size - 1] = '\0';  // Truncate and ensure null termination
 | 
			
		||||
    } else {
 | 
			
		||||
      buffer[*buffer_at] = '\0';  // Normal case, append null terminator
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  inline void vprintf_to_buffer_(const char *format, va_list args) {
 | 
			
		||||
    if (this->is_buffer_full_())
 | 
			
		||||
      return;
 | 
			
		||||
    int remaining = this->buffer_remaining_capacity_();
 | 
			
		||||
    int ret = vsnprintf(this->tx_buffer_ + this->tx_buffer_at_, remaining, format, args);
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
      // Encoding error, do not increment buffer_at
 | 
			
		||||
 | 
			
		||||
  // Helper to format and send a log message to both console and callbacks
 | 
			
		||||
  inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format,
 | 
			
		||||
                                                  va_list args) {
 | 
			
		||||
    // Format to tx_buffer and prepare for output
 | 
			
		||||
    this->tx_buffer_at_ = 0;  // Initialize buffer position
 | 
			
		||||
    this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
 | 
			
		||||
                                                this->tx_buffer_size_);
 | 
			
		||||
 | 
			
		||||
    if (this->baud_rate_ > 0) {
 | 
			
		||||
      this->write_msg_(this->tx_buffer_);  // If logging is enabled, write to console
 | 
			
		||||
    }
 | 
			
		||||
    this->call_log_callbacks_(level, tag, this->tx_buffer_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Write the body of the log message to the buffer
 | 
			
		||||
  inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) {
 | 
			
		||||
    // Calculate available space
 | 
			
		||||
    const int available = buffer_size - *buffer_at;
 | 
			
		||||
    if (available <= 0)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    // Determine copy length (minimum of remaining capacity and string length)
 | 
			
		||||
    const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available;
 | 
			
		||||
 | 
			
		||||
    // Copy the data
 | 
			
		||||
    if (copy_len > 0) {
 | 
			
		||||
      memcpy(buffer + *buffer_at, value, copy_len);
 | 
			
		||||
      *buffer_at += copy_len;
 | 
			
		||||
    }
 | 
			
		||||
    if (ret >= remaining) {
 | 
			
		||||
      // output was too long, truncated
 | 
			
		||||
      ret = remaining;
 | 
			
		||||
  }
 | 
			
		||||
    this->tx_buffer_at_ += ret;
 | 
			
		||||
  }
 | 
			
		||||
  inline void printf_to_buffer_(const char *format, ...) {
 | 
			
		||||
 | 
			
		||||
  // Format string to explicit buffer with varargs
 | 
			
		||||
  inline void printf_to_buffer_(const char *format, char *buffer, int *buffer_at, int buffer_size, ...) {
 | 
			
		||||
    va_list arg;
 | 
			
		||||
    va_start(arg, format);
 | 
			
		||||
    this->vprintf_to_buffer_(format, arg);
 | 
			
		||||
    va_start(arg, buffer_size);
 | 
			
		||||
    this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
 | 
			
		||||
    va_end(arg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -169,10 +222,82 @@ class Logger : public Component {
 | 
			
		||||
  std::map<std::string, int> log_levels_{};
 | 
			
		||||
  CallbackManager<void(int, const char *, const char *)> log_callback_{};
 | 
			
		||||
  int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
 | 
			
		||||
  /// Prevents recursive log calls, if true a log message is already being processed.
 | 
			
		||||
  bool recursion_guard_ = false;
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  std::atomic<bool> recursion_guard_{false};
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
  std::unique_ptr<logger::TaskLogBuffer> log_buffer_;  // Will be initialized with init_log_buffer
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
  bool recursion_guard_{false};
 | 
			
		||||
#endif
 | 
			
		||||
  void *main_task_ = nullptr;
 | 
			
		||||
  CallbackManager<void(int)> level_callback_{};
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
 | 
			
		||||
  const char *HOT get_thread_name_() {
 | 
			
		||||
    TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
 | 
			
		||||
    if (current_task == main_task_) {
 | 
			
		||||
      return nullptr;  // Main task
 | 
			
		||||
    } else {
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
      return pcTaskGetName(current_task);
 | 
			
		||||
#elif defined(USE_LIBRETINY)
 | 
			
		||||
      return pcTaskGetTaskName(current_task);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
 | 
			
		||||
                                          int *buffer_at, int buffer_size) {
 | 
			
		||||
    // Format header
 | 
			
		||||
    if (level < 0)
 | 
			
		||||
      level = 0;
 | 
			
		||||
    if (level > 7)
 | 
			
		||||
      level = 7;
 | 
			
		||||
 | 
			
		||||
    const char *color = esphome::logger::LOG_LEVEL_COLORS[level];
 | 
			
		||||
    const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level];
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
 | 
			
		||||
    if (thread_name != nullptr) {
 | 
			
		||||
      // Non-main task with thread name
 | 
			
		||||
      this->printf_to_buffer_("%s[%s][%s:%03u]%s[%s]%s: ", buffer, buffer_at, buffer_size, color, letter, tag, line,
 | 
			
		||||
                              ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    // Main task or non ESP32/LibreTiny platform
 | 
			
		||||
    this->printf_to_buffer_("%s[%s][%s:%03u]: ", buffer, buffer_at, buffer_size, color, letter, tag, line);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format,
 | 
			
		||||
                                         va_list args) {
 | 
			
		||||
    // Get remaining capacity in the buffer
 | 
			
		||||
    const int remaining = buffer_size - *buffer_at;
 | 
			
		||||
    if (remaining <= 0)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
 | 
			
		||||
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
      return;  // Encoding error, do not increment buffer_at
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update buffer_at with the formatted length (handle truncation)
 | 
			
		||||
    int formatted_len = (ret >= remaining) ? remaining : ret;
 | 
			
		||||
    *buffer_at += formatted_len;
 | 
			
		||||
 | 
			
		||||
    // Remove all trailing newlines right after formatting
 | 
			
		||||
    while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
 | 
			
		||||
      (*buffer_at)--;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) {
 | 
			
		||||
    static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
 | 
			
		||||
    this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
extern Logger *global_logger;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										138
									
								
								esphome/components/logger/task_log_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								esphome/components/logger/task_log_buffer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
 | 
			
		||||
#include "task_log_buffer.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace logger {
 | 
			
		||||
 | 
			
		||||
TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) {
 | 
			
		||||
  // Store the buffer size
 | 
			
		||||
  this->size_ = total_buffer_size;
 | 
			
		||||
  // Allocate memory for the ring buffer using ESPHome's RAM allocator
 | 
			
		||||
  RAMAllocator<uint8_t> allocator;
 | 
			
		||||
  this->storage_ = allocator.allocate(this->size_);
 | 
			
		||||
  // Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity
 | 
			
		||||
  this->ring_buffer_ = xRingbufferCreateStatic(this->size_, RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TaskLogBuffer::~TaskLogBuffer() {
 | 
			
		||||
  if (this->ring_buffer_ != nullptr) {
 | 
			
		||||
    // Delete the ring buffer
 | 
			
		||||
    vRingbufferDelete(this->ring_buffer_);
 | 
			
		||||
    this->ring_buffer_ = nullptr;
 | 
			
		||||
 | 
			
		||||
    // Free the allocated memory
 | 
			
		||||
    RAMAllocator<uint8_t> allocator;
 | 
			
		||||
    allocator.deallocate(this->storage_, this->size_);
 | 
			
		||||
    this->storage_ = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TaskLogBuffer::borrow_message_main_loop(LogMessage **message, const char **text, void **received_token) {
 | 
			
		||||
  if (message == nullptr || text == nullptr || received_token == nullptr) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t item_size = 0;
 | 
			
		||||
  void *received_item = xRingbufferReceive(ring_buffer_, &item_size, 0);
 | 
			
		||||
  if (received_item == nullptr) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  LogMessage *msg = static_cast<LogMessage *>(received_item);
 | 
			
		||||
  *message = msg;
 | 
			
		||||
  *text = msg->text_data();
 | 
			
		||||
  *received_token = received_item;
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TaskLogBuffer::release_message_main_loop(void *token) {
 | 
			
		||||
  if (token == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  vRingbufferReturnItem(ring_buffer_, token);
 | 
			
		||||
  // Update counter to mark all messages as processed
 | 
			
		||||
  last_processed_counter_ = message_counter_.load(std::memory_order_relaxed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
 | 
			
		||||
                                             const char *format, va_list args) {
 | 
			
		||||
  // First, calculate the exact length needed using a null buffer (no actual writing)
 | 
			
		||||
  va_list args_copy;
 | 
			
		||||
  va_copy(args_copy, args);
 | 
			
		||||
  int ret = vsnprintf(nullptr, 0, format, args_copy);
 | 
			
		||||
  va_end(args_copy);
 | 
			
		||||
 | 
			
		||||
  if (ret <= 0) {
 | 
			
		||||
    return false;  // Formatting error or empty message
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Calculate actual text length (capped to maximum size)
 | 
			
		||||
  static constexpr size_t MAX_TEXT_SIZE = 255;
 | 
			
		||||
  size_t text_length = (static_cast<size_t>(ret) > MAX_TEXT_SIZE) ? MAX_TEXT_SIZE : ret;
 | 
			
		||||
 | 
			
		||||
  // Calculate total size needed (header + text length + null terminator)
 | 
			
		||||
  size_t total_size = sizeof(LogMessage) + text_length + 1;
 | 
			
		||||
 | 
			
		||||
  // Acquire memory directly from the ring buffer
 | 
			
		||||
  void *acquired_memory = nullptr;
 | 
			
		||||
  BaseType_t result = xRingbufferSendAcquire(ring_buffer_, &acquired_memory, total_size, 0);
 | 
			
		||||
 | 
			
		||||
  if (result != pdTRUE || acquired_memory == nullptr) {
 | 
			
		||||
    return false;  // Failed to acquire memory
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set up the message header in the acquired memory
 | 
			
		||||
  LogMessage *msg = static_cast<LogMessage *>(acquired_memory);
 | 
			
		||||
  msg->level = level;
 | 
			
		||||
  msg->tag = tag;
 | 
			
		||||
  msg->line = line;
 | 
			
		||||
 | 
			
		||||
  // Store the thread name now instead of waiting until main loop processing
 | 
			
		||||
  // This avoids crashes if the task completes or is deleted between when this message
 | 
			
		||||
  // is enqueued and when it's processed by the main loop
 | 
			
		||||
  const char *thread_name = pcTaskGetName(task_handle);
 | 
			
		||||
  if (thread_name != nullptr) {
 | 
			
		||||
    strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
 | 
			
		||||
    msg->thread_name[sizeof(msg->thread_name) - 1] = '\0';  // Ensure null termination
 | 
			
		||||
  } else {
 | 
			
		||||
    msg->thread_name[0] = '\0';  // Empty string if no thread name
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Format the message text directly into the acquired memory
 | 
			
		||||
  // We add 1 to text_length to ensure space for null terminator during formatting
 | 
			
		||||
  char *text_area = msg->text_data();
 | 
			
		||||
  ret = vsnprintf(text_area, text_length + 1, format, args);
 | 
			
		||||
 | 
			
		||||
  // Handle unexpected formatting error
 | 
			
		||||
  if (ret <= 0) {
 | 
			
		||||
    vRingbufferReturnItem(ring_buffer_, acquired_memory);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Remove trailing newlines
 | 
			
		||||
  while (text_length > 0 && text_area[text_length - 1] == '\n') {
 | 
			
		||||
    text_length--;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  msg->text_length = text_length;
 | 
			
		||||
  // Complete the send operation with the acquired memory
 | 
			
		||||
  result = xRingbufferSendComplete(ring_buffer_, acquired_memory);
 | 
			
		||||
 | 
			
		||||
  if (result != pdTRUE) {
 | 
			
		||||
    return false;  // Failed to complete the message send
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Message sent successfully, increment the counter
 | 
			
		||||
  message_counter_.fetch_add(1, std::memory_order_relaxed);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace logger
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
							
								
								
									
										69
									
								
								esphome/components/logger/task_log_buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/logger/task_log_buffer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/ringbuf.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace logger {
 | 
			
		||||
 | 
			
		||||
class TaskLogBuffer {
 | 
			
		||||
 public:
 | 
			
		||||
  // Structure for a log message header (text data follows immediately after)
 | 
			
		||||
  struct LogMessage {
 | 
			
		||||
    const char *tag;       // We store the pointer, assuming tags are static
 | 
			
		||||
    char thread_name[16];  // Store thread name directly (only used for non-main threads)
 | 
			
		||||
    uint16_t text_length;  // Length of the message text (up to ~64KB)
 | 
			
		||||
    uint16_t line;         // Source code line number
 | 
			
		||||
    uint8_t level;         // Log level (0-7)
 | 
			
		||||
 | 
			
		||||
    // Methods for accessing message contents
 | 
			
		||||
    inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
 | 
			
		||||
 | 
			
		||||
    inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Constructor that takes a total buffer size
 | 
			
		||||
  explicit TaskLogBuffer(size_t total_buffer_size);
 | 
			
		||||
  ~TaskLogBuffer();
 | 
			
		||||
 | 
			
		||||
  // NOT thread-safe - borrow a message from the ring buffer, only call from main loop
 | 
			
		||||
  bool borrow_message_main_loop(LogMessage **message, const char **text, void **received_token);
 | 
			
		||||
 | 
			
		||||
  // NOT thread-safe - release a message buffer and update the counter, only call from main loop
 | 
			
		||||
  void release_message_main_loop(void *token);
 | 
			
		||||
 | 
			
		||||
  // Thread-safe - send a message to the ring buffer from any thread
 | 
			
		||||
  bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
 | 
			
		||||
                                const char *format, va_list args);
 | 
			
		||||
 | 
			
		||||
  // Check if there are messages ready to be processed using an atomic counter for performance
 | 
			
		||||
  inline bool HOT has_messages() const {
 | 
			
		||||
    return message_counter_.load(std::memory_order_relaxed) != last_processed_counter_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get the total buffer size in bytes
 | 
			
		||||
  inline size_t size() const { return size_; }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  RingbufHandle_t ring_buffer_{nullptr};  // FreeRTOS ring buffer handle
 | 
			
		||||
  StaticRingbuffer_t structure_;          // Static structure for the ring buffer
 | 
			
		||||
  uint8_t *storage_{nullptr};             // Pointer to allocated memory
 | 
			
		||||
  size_t size_{0};                        // Size of allocated memory
 | 
			
		||||
 | 
			
		||||
  // Atomic counter for message tracking (only differences matter)
 | 
			
		||||
  std::atomic<uint16_t> message_counter_{0};    // Incremented when messages are committed
 | 
			
		||||
  mutable uint16_t last_processed_counter_{0};  // Tracks last processed message
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace logger
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESPHOME_TASK_LOG_BUFFER
 | 
			
		||||
@@ -19,9 +19,8 @@ from ..widgets import get_widgets, wait_for_widgets
 | 
			
		||||
 | 
			
		||||
LVGLText = lvgl_ns.class_("LVGLText", text.Text)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
 | 
			
		||||
CONFIG_SCHEMA = text.text_schema(LVGLText).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(LVGLText),
 | 
			
		||||
        cv.Required(CONF_WIDGET): cv.use_id(LvText),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -123,11 +123,8 @@ def microphone_source_schema(
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_UNDEF = object()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def final_validate_microphone_source_schema(
 | 
			
		||||
    component_name: str, sample_rate: int = _UNDEF
 | 
			
		||||
    component_name: str, sample_rate: int = cv.UNDEFINED
 | 
			
		||||
):
 | 
			
		||||
    """Validates that the microphone source can provide audio in the correct format. In particular it validates the sample rate and the enabled channels.
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +138,7 @@ def final_validate_microphone_source_schema(
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def _validate_audio_compatability(config):
 | 
			
		||||
        if sample_rate is not _UNDEF:
 | 
			
		||||
        if sample_rate is not cv.UNDEFINED:
 | 
			
		||||
            # Issues require changing the microphone configuration
 | 
			
		||||
            #  - Verifies sample rates match
 | 
			
		||||
            audio.final_validate_audio_schema(
 | 
			
		||||
@@ -165,13 +162,22 @@ def final_validate_microphone_source_schema(
 | 
			
		||||
    return _validate_audio_compatability
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def microphone_source_to_code(config):
 | 
			
		||||
async def microphone_source_to_code(config, passive=False):
 | 
			
		||||
    """Creates a MicrophoneSource variable for codegen.
 | 
			
		||||
 | 
			
		||||
    Setting passive to true makes the MicrophoneSource never start/stop the microphone, but only receives audio when another component has actively started the Microphone. If false, then the microphone needs to be explicitly started/stopped.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        config (Schema): Created with `microphone_source_schema` specifying bits per sample, channels, and gain factor
 | 
			
		||||
        passive (bool): Enable passive mode for the MicrophoneSource
 | 
			
		||||
    """
 | 
			
		||||
    mic = await cg.get_variable(config[CONF_MICROPHONE])
 | 
			
		||||
    mic_source = cg.new_Pvariable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        mic,
 | 
			
		||||
        config[CONF_BITS_PER_SAMPLE],
 | 
			
		||||
        config[CONF_GAIN_FACTOR],
 | 
			
		||||
        passive,
 | 
			
		||||
    )
 | 
			
		||||
    for channel in config[CONF_CHANNELS]:
 | 
			
		||||
        cg.add(mic_source.add_channel(channel))
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,10 @@ namespace microphone {
 | 
			
		||||
static const int32_t Q25_MAX_VALUE = (1 << 25) - 1;
 | 
			
		||||
static const int32_t Q25_MIN_VALUE = ~Q25_MAX_VALUE;
 | 
			
		||||
 | 
			
		||||
static const uint32_t HISTORY_VALUES = 32;
 | 
			
		||||
 | 
			
		||||
void MicrophoneSource::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) {
 | 
			
		||||
  std::function<void(const std::vector<uint8_t> &)> filtered_callback =
 | 
			
		||||
      [this, data_callback](const std::vector<uint8_t> &data) {
 | 
			
		||||
        if (this->enabled_) {
 | 
			
		||||
        if (this->enabled_ || this->passive_) {
 | 
			
		||||
          if (this->processed_samples_.use_count() == 0) {
 | 
			
		||||
            // Create vector if its unused
 | 
			
		||||
            this->processed_samples_ = std::make_shared<std::vector<uint8_t>>();
 | 
			
		||||
@@ -32,13 +30,14 @@ audio::AudioStreamInfo MicrophoneSource::get_audio_stream_info() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicrophoneSource::start() {
 | 
			
		||||
  if (!this->enabled_) {
 | 
			
		||||
  if (!this->enabled_ && !this->passive_) {
 | 
			
		||||
    this->enabled_ = true;
 | 
			
		||||
    this->mic_->start();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MicrophoneSource::stop() {
 | 
			
		||||
  if (this->enabled_) {
 | 
			
		||||
  if (this->enabled_ && !this->passive_) {
 | 
			
		||||
    this->enabled_ = false;
 | 
			
		||||
    this->mic_->stop();
 | 
			
		||||
    this->processed_samples_.reset();
 | 
			
		||||
@@ -63,8 +62,9 @@ void MicrophoneSource::process_audio_(const std::vector<uint8_t> &data, std::vec
 | 
			
		||||
  const size_t target_bytes_per_sample = (this->bits_per_sample_ + 7) / 8;
 | 
			
		||||
  const size_t target_bytes_per_frame = target_bytes_per_sample * this->channels_.count();
 | 
			
		||||
 | 
			
		||||
  filtered_data.reserve(target_bytes_per_frame * total_frames);
 | 
			
		||||
  filtered_data.resize(0);
 | 
			
		||||
  filtered_data.resize(target_bytes_per_frame * total_frames);
 | 
			
		||||
 | 
			
		||||
  uint8_t *current_data = filtered_data.data();
 | 
			
		||||
 | 
			
		||||
  for (uint32_t frame_index = 0; frame_index < total_frames; ++frame_index) {
 | 
			
		||||
    for (uint32_t channel_index = 0; channel_index < source_channels; ++channel_index) {
 | 
			
		||||
@@ -82,26 +82,10 @@ void MicrophoneSource::process_audio_(const std::vector<uint8_t> &data, std::vec
 | 
			
		||||
        // Clamp ``sample`` in case gain multiplication overflows 25 bits
 | 
			
		||||
        sample = clamp<int32_t>(sample, Q25_MIN_VALUE, Q25_MAX_VALUE);  // Q25
 | 
			
		||||
 | 
			
		||||
        // Copy ``target_bytes_per_sample`` bytes to the output buffer.
 | 
			
		||||
        if (target_bytes_per_sample == 1) {
 | 
			
		||||
          sample >>= 18;  // Q25 -> Q7
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample));
 | 
			
		||||
        } else if (target_bytes_per_sample == 2) {
 | 
			
		||||
          sample >>= 10;  // Q25 -> Q15
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 8));
 | 
			
		||||
        } else if (target_bytes_per_sample == 3) {
 | 
			
		||||
          sample >>= 2;  // Q25 -> Q23
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 8));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 16));
 | 
			
		||||
        } else {
 | 
			
		||||
        sample *= (1 << 6);  // Q25 -> Q31
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 8));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 16));
 | 
			
		||||
          filtered_data.push_back(static_cast<uint8_t>(sample >> 24));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        audio::pack_q31_as_audio_sample(sample, current_data, target_bytes_per_sample);
 | 
			
		||||
        current_data = current_data + target_bytes_per_sample;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,8 @@ class MicrophoneSource {
 | 
			
		||||
   * Note that this class cannot convert sample rates!
 | 
			
		||||
   */
 | 
			
		||||
 public:
 | 
			
		||||
  MicrophoneSource(Microphone *mic, uint8_t bits_per_sample, int32_t gain_factor)
 | 
			
		||||
      : mic_(mic), bits_per_sample_(bits_per_sample), gain_factor_(gain_factor) {}
 | 
			
		||||
  MicrophoneSource(Microphone *mic, uint8_t bits_per_sample, int32_t gain_factor, bool passive)
 | 
			
		||||
      : mic_(mic), bits_per_sample_(bits_per_sample), gain_factor_(gain_factor), passive_(passive) {}
 | 
			
		||||
 | 
			
		||||
  /// @brief Enables a channel to be processed through the callback.
 | 
			
		||||
  ///
 | 
			
		||||
@@ -59,8 +59,9 @@ class MicrophoneSource {
 | 
			
		||||
 | 
			
		||||
  void start();
 | 
			
		||||
  void stop();
 | 
			
		||||
  bool is_running() const { return (this->mic_->is_running() && this->enabled_); }
 | 
			
		||||
  bool is_stopped() const { return !this->enabled_; }
 | 
			
		||||
  bool is_passive() const { return this->passive_; }
 | 
			
		||||
  bool is_running() const { return (this->mic_->is_running() && (this->enabled_ || this->passive_)); }
 | 
			
		||||
  bool is_stopped() const { return !this->is_running(); };
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data);
 | 
			
		||||
@@ -72,6 +73,7 @@ class MicrophoneSource {
 | 
			
		||||
  std::bitset<8> channels_;
 | 
			
		||||
  int32_t gain_factor_;
 | 
			
		||||
  bool enabled_{false};
 | 
			
		||||
  bool passive_;  // Only pass audio if ``mic_`` is already running
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace microphone
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								esphome/components/mipi_spi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								esphome/components/mipi_spi/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
CODEOWNERS = ["@clydebarrow"]
 | 
			
		||||
 | 
			
		||||
DOMAIN = "mipi_spi"
 | 
			
		||||
 | 
			
		||||
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
 | 
			
		||||
CONF_SPI_16 = "spi_16"
 | 
			
		||||
CONF_PIXEL_MODE = "pixel_mode"
 | 
			
		||||
CONF_COLOR_DEPTH = "color_depth"
 | 
			
		||||
CONF_BUS_MODE = "bus_mode"
 | 
			
		||||
CONF_USE_AXIS_FLIPS = "use_axis_flips"
 | 
			
		||||
CONF_NATIVE_WIDTH = "native_width"
 | 
			
		||||
CONF_NATIVE_HEIGHT = "native_height"
 | 
			
		||||
 | 
			
		||||
MODE_RGB = "RGB"
 | 
			
		||||
MODE_BGR = "BGR"
 | 
			
		||||
							
								
								
									
										474
									
								
								esphome/components/mipi_spi/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										474
									
								
								esphome/components/mipi_spi/display.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,474 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import display, spi
 | 
			
		||||
from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.config_validation import ALLOW_EXTRA
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BRIGHTNESS,
 | 
			
		||||
    CONF_COLOR_ORDER,
 | 
			
		||||
    CONF_CS_PIN,
 | 
			
		||||
    CONF_DATA_RATE,
 | 
			
		||||
    CONF_DC_PIN,
 | 
			
		||||
    CONF_DIMENSIONS,
 | 
			
		||||
    CONF_ENABLE_PIN,
 | 
			
		||||
    CONF_HEIGHT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INIT_SEQUENCE,
 | 
			
		||||
    CONF_INVERT_COLORS,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_MIRROR_X,
 | 
			
		||||
    CONF_MIRROR_Y,
 | 
			
		||||
    CONF_MODEL,
 | 
			
		||||
    CONF_OFFSET_HEIGHT,
 | 
			
		||||
    CONF_OFFSET_WIDTH,
 | 
			
		||||
    CONF_RESET_PIN,
 | 
			
		||||
    CONF_ROTATION,
 | 
			
		||||
    CONF_SWAP_XY,
 | 
			
		||||
    CONF_TRANSFORM,
 | 
			
		||||
    CONF_WIDTH,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import TimePeriod
 | 
			
		||||
 | 
			
		||||
from ..const import CONF_DRAW_ROUNDING
 | 
			
		||||
from ..lvgl.defines import CONF_COLOR_DEPTH
 | 
			
		||||
from . import (
 | 
			
		||||
    CONF_BUS_MODE,
 | 
			
		||||
    CONF_DRAW_FROM_ORIGIN,
 | 
			
		||||
    CONF_NATIVE_HEIGHT,
 | 
			
		||||
    CONF_NATIVE_WIDTH,
 | 
			
		||||
    CONF_PIXEL_MODE,
 | 
			
		||||
    CONF_SPI_16,
 | 
			
		||||
    CONF_USE_AXIS_FLIPS,
 | 
			
		||||
    DOMAIN,
 | 
			
		||||
    MODE_BGR,
 | 
			
		||||
    MODE_RGB,
 | 
			
		||||
)
 | 
			
		||||
from .models import (
 | 
			
		||||
    DELAY_FLAG,
 | 
			
		||||
    MADCTL_BGR,
 | 
			
		||||
    MADCTL_MV,
 | 
			
		||||
    MADCTL_MX,
 | 
			
		||||
    MADCTL_MY,
 | 
			
		||||
    MADCTL_XFLIP,
 | 
			
		||||
    MADCTL_YFLIP,
 | 
			
		||||
    DriverChip,
 | 
			
		||||
    amoled,
 | 
			
		||||
    cyd,
 | 
			
		||||
    ili,
 | 
			
		||||
    jc,
 | 
			
		||||
    lanbon,
 | 
			
		||||
    lilygo,
 | 
			
		||||
    waveshare,
 | 
			
		||||
)
 | 
			
		||||
from .models.commands import BRIGHTNESS, DISPON, INVOFF, INVON, MADCTL, PIXFMT, SLPOUT
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
 | 
			
		||||
LOGGER = logging.getLogger(DOMAIN)
 | 
			
		||||
mipi_spi_ns = cg.esphome_ns.namespace("mipi_spi")
 | 
			
		||||
MipiSpi = mipi_spi_ns.class_(
 | 
			
		||||
    "MipiSpi", display.Display, display.DisplayBuffer, cg.Component, spi.SPIDevice
 | 
			
		||||
)
 | 
			
		||||
ColorOrder = display.display_ns.enum("ColorMode")
 | 
			
		||||
ColorBitness = display.display_ns.enum("ColorBitness")
 | 
			
		||||
Model = mipi_spi_ns.enum("Model")
 | 
			
		||||
 | 
			
		||||
COLOR_ORDERS = {
 | 
			
		||||
    MODE_RGB: ColorOrder.COLOR_ORDER_RGB,
 | 
			
		||||
    MODE_BGR: ColorOrder.COLOR_ORDER_BGR,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
COLOR_DEPTHS = {
 | 
			
		||||
    8: ColorBitness.COLOR_BITNESS_332,
 | 
			
		||||
    16: ColorBitness.COLOR_BITNESS_565,
 | 
			
		||||
}
 | 
			
		||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DriverChip("CUSTOM", initsequence={})
 | 
			
		||||
 | 
			
		||||
MODELS = DriverChip.models
 | 
			
		||||
# These statements are noops, but serve to suppress linting of side-effect-only imports
 | 
			
		||||
for _ in (ili, jc, amoled, lilygo, lanbon, cyd, waveshare):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
PixelMode = mipi_spi_ns.enum("PixelMode")
 | 
			
		||||
 | 
			
		||||
PIXEL_MODE_18BIT = "18bit"
 | 
			
		||||
PIXEL_MODE_16BIT = "16bit"
 | 
			
		||||
 | 
			
		||||
PIXEL_MODES = {
 | 
			
		||||
    PIXEL_MODE_16BIT: 0x55,
 | 
			
		||||
    PIXEL_MODE_18BIT: 0x66,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_dimension(rounding):
 | 
			
		||||
    def validator(value):
 | 
			
		||||
        value = cv.positive_int(value)
 | 
			
		||||
        if value % rounding != 0:
 | 
			
		||||
            raise cv.Invalid(f"Dimensions and offsets must be divisible by {rounding}")
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    return validator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def map_sequence(value):
 | 
			
		||||
    """
 | 
			
		||||
    The format is a repeated sequence of [CMD, <data>] where <data> is s a sequence of bytes. The length is inferred
 | 
			
		||||
    from the length of the sequence and should not be explicit.
 | 
			
		||||
    A delay can be inserted by specifying "- delay N" where N is in ms
 | 
			
		||||
    """
 | 
			
		||||
    if isinstance(value, str) and value.lower().startswith("delay "):
 | 
			
		||||
        value = value.lower()[6:]
 | 
			
		||||
        delay = cv.All(
 | 
			
		||||
            cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Range(TimePeriod(milliseconds=1), TimePeriod(milliseconds=255)),
 | 
			
		||||
        )(value)
 | 
			
		||||
        return DELAY_FLAG, delay.total_milliseconds
 | 
			
		||||
    if isinstance(value, int):
 | 
			
		||||
        return (value,)
 | 
			
		||||
    value = cv.All(cv.ensure_list(cv.int_range(0, 255)), cv.Length(1, 254))(value)
 | 
			
		||||
    return tuple(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def power_of_two(value):
 | 
			
		||||
    value = cv.int_range(1, 128)(value)
 | 
			
		||||
    if value & (value - 1) != 0:
 | 
			
		||||
        raise cv.Invalid("value must be a power of two")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def dimension_schema(rounding):
 | 
			
		||||
    return cv.Any(
 | 
			
		||||
        cv.dimensions,
 | 
			
		||||
        cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_WIDTH): validate_dimension(rounding),
 | 
			
		||||
                cv.Required(CONF_HEIGHT): validate_dimension(rounding),
 | 
			
		||||
                cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension(
 | 
			
		||||
                    rounding
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension(rounding),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def model_schema(bus_mode, model: DriverChip, swapsies: bool):
 | 
			
		||||
    transform = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_MIRROR_X): cv.boolean,
 | 
			
		||||
            cv.Required(CONF_MIRROR_Y): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    if model.get_default(CONF_SWAP_XY, False) == cv.UNDEFINED:
 | 
			
		||||
        transform = transform.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_SWAP_XY): cv.invalid(
 | 
			
		||||
                    "Axis swapping not supported by this model"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        transform = transform.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_SWAP_XY): cv.boolean,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    # CUSTOM model will need to provide a custom init sequence
 | 
			
		||||
    iseqconf = (
 | 
			
		||||
        cv.Required(CONF_INIT_SEQUENCE)
 | 
			
		||||
        if model.initsequence is None
 | 
			
		||||
        else cv.Optional(CONF_INIT_SEQUENCE)
 | 
			
		||||
    )
 | 
			
		||||
    # Dimensions are optional if the model has a default width and the transform is not overridden
 | 
			
		||||
    cv_dimensions = (
 | 
			
		||||
        cv.Optional if model.get_default(CONF_WIDTH) and not swapsies else cv.Required
 | 
			
		||||
    )
 | 
			
		||||
    pixel_modes = PIXEL_MODES if bus_mode == TYPE_SINGLE else (PIXEL_MODE_16BIT,)
 | 
			
		||||
    color_depth = (
 | 
			
		||||
        ("16", "8", "16bit", "8bit") if bus_mode == TYPE_SINGLE else ("16", "16bit")
 | 
			
		||||
    )
 | 
			
		||||
    schema = (
 | 
			
		||||
        display.FULL_DISPLAY_SCHEMA.extend(
 | 
			
		||||
            spi.spi_device_schema(
 | 
			
		||||
                cs_pin_required=False,
 | 
			
		||||
                default_mode="MODE3" if bus_mode == TYPE_OCTAL else "MODE0",
 | 
			
		||||
                default_data_rate=model.get_default(CONF_DATA_RATE, 10_000_000),
 | 
			
		||||
                mode=bus_mode,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        .extend(
 | 
			
		||||
            {
 | 
			
		||||
                model.option(pin, cv.UNDEFINED): pins.gpio_output_pin_schema
 | 
			
		||||
                for pin in (CONF_RESET_PIN, CONF_CS_PIN, CONF_DC_PIN)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        .extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(MipiSpi),
 | 
			
		||||
                cv_dimensions(CONF_DIMENSIONS): dimension_schema(
 | 
			
		||||
                    model.get_default(CONF_DRAW_ROUNDING, 1)
 | 
			
		||||
                ),
 | 
			
		||||
                model.option(CONF_ENABLE_PIN, cv.UNDEFINED): cv.ensure_list(
 | 
			
		||||
                    pins.gpio_output_pin_schema
 | 
			
		||||
                ),
 | 
			
		||||
                model.option(CONF_COLOR_ORDER, MODE_BGR): cv.enum(
 | 
			
		||||
                    COLOR_ORDERS, upper=True
 | 
			
		||||
                ),
 | 
			
		||||
                model.option(CONF_COLOR_DEPTH, 16): cv.one_of(*color_depth, lower=True),
 | 
			
		||||
                model.option(CONF_DRAW_ROUNDING, 2): power_of_two,
 | 
			
		||||
                model.option(CONF_PIXEL_MODE, PIXEL_MODE_16BIT): cv.Any(
 | 
			
		||||
                    cv.one_of(*pixel_modes, lower=True),
 | 
			
		||||
                    cv.int_range(0, 255, min_included=True, max_included=True),
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_TRANSFORM): transform,
 | 
			
		||||
                cv.Optional(CONF_BUS_MODE, default=bus_mode): cv.one_of(
 | 
			
		||||
                    bus_mode, lower=True
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
 | 
			
		||||
                iseqconf: cv.ensure_list(map_sequence),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
        .extend(
 | 
			
		||||
            {
 | 
			
		||||
                model.option(x): cv.boolean
 | 
			
		||||
                for x in [
 | 
			
		||||
                    CONF_DRAW_FROM_ORIGIN,
 | 
			
		||||
                    CONF_SPI_16,
 | 
			
		||||
                    CONF_INVERT_COLORS,
 | 
			
		||||
                    CONF_USE_AXIS_FLIPS,
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    if brightness := model.get_default(CONF_BRIGHTNESS):
 | 
			
		||||
        schema = schema.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_BRIGHTNESS, default=brightness): cv.int_range(
 | 
			
		||||
                    0, 0xFF, min_included=True, max_included=True
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    if bus_mode != TYPE_SINGLE:
 | 
			
		||||
        return cv.All(schema, cv.only_with_esp_idf)
 | 
			
		||||
    return schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rotation_as_transform(model, config):
 | 
			
		||||
    """
 | 
			
		||||
    Check if a rotation can be implemented in hardware using the MADCTL register.
 | 
			
		||||
    A rotation of 180 is always possible, 90 and 270 are possible if the model supports swapping X and Y.
 | 
			
		||||
    """
 | 
			
		||||
    rotation = config.get(CONF_ROTATION, 0)
 | 
			
		||||
    return rotation and (
 | 
			
		||||
        model.get_default(CONF_SWAP_XY) != cv.UNDEFINED or rotation == 180
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def config_schema(config):
 | 
			
		||||
    # First get the model and bus mode
 | 
			
		||||
    config = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
 | 
			
		||||
        },
 | 
			
		||||
        extra=ALLOW_EXTRA,
 | 
			
		||||
    )(config)
 | 
			
		||||
    model = MODELS[config[CONF_MODEL]]
 | 
			
		||||
    bus_modes = model.modes
 | 
			
		||||
    config = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            model.option(CONF_BUS_MODE, TYPE_SINGLE): cv.one_of(*bus_modes, lower=True),
 | 
			
		||||
            cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
 | 
			
		||||
        },
 | 
			
		||||
        extra=ALLOW_EXTRA,
 | 
			
		||||
    )(config)
 | 
			
		||||
    bus_mode = config.get(CONF_BUS_MODE, model.modes[0])
 | 
			
		||||
    swapsies = config.get(CONF_TRANSFORM, {}).get(CONF_SWAP_XY) is True
 | 
			
		||||
    config = model_schema(bus_mode, model, swapsies)(config)
 | 
			
		||||
    # Check for invalid combinations of MADCTL config
 | 
			
		||||
    if init_sequence := config.get(CONF_INIT_SEQUENCE):
 | 
			
		||||
        if MADCTL in [x[0] for x in init_sequence] and CONF_TRANSFORM in config:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"transform is not supported when MADCTL ({MADCTL:#X}) is in the init sequence"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    if bus_mode == TYPE_QUAD and CONF_DC_PIN in config:
 | 
			
		||||
        raise cv.Invalid("DC pin is not supported in quad mode")
 | 
			
		||||
    if config[CONF_PIXEL_MODE] == PIXEL_MODE_18BIT and bus_mode != TYPE_SINGLE:
 | 
			
		||||
        raise cv.Invalid("18-bit pixel mode is not supported on a quad or octal bus")
 | 
			
		||||
    if bus_mode != TYPE_QUAD and CONF_DC_PIN not in config:
 | 
			
		||||
        raise cv.Invalid(f"DC pin is required in {bus_mode} mode")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = config_schema
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_transform(model, config):
 | 
			
		||||
    can_transform = rotation_as_transform(model, config)
 | 
			
		||||
    transform = config.get(
 | 
			
		||||
        CONF_TRANSFORM,
 | 
			
		||||
        {
 | 
			
		||||
            CONF_MIRROR_X: model.get_default(CONF_MIRROR_X, False),
 | 
			
		||||
            CONF_MIRROR_Y: model.get_default(CONF_MIRROR_Y, False),
 | 
			
		||||
            CONF_SWAP_XY: model.get_default(CONF_SWAP_XY, False),
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Can we use the MADCTL register to set the rotation?
 | 
			
		||||
    if can_transform and CONF_TRANSFORM not in config:
 | 
			
		||||
        rotation = config[CONF_ROTATION]
 | 
			
		||||
        if rotation == 180:
 | 
			
		||||
            transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
 | 
			
		||||
            transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
 | 
			
		||||
        elif rotation == 90:
 | 
			
		||||
            transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
 | 
			
		||||
            transform[CONF_MIRROR_X] = not transform[CONF_MIRROR_X]
 | 
			
		||||
        else:
 | 
			
		||||
            transform[CONF_SWAP_XY] = not transform[CONF_SWAP_XY]
 | 
			
		||||
            transform[CONF_MIRROR_Y] = not transform[CONF_MIRROR_Y]
 | 
			
		||||
        transform[CONF_TRANSFORM] = True
 | 
			
		||||
    return transform
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_sequence(model, config):
 | 
			
		||||
    """
 | 
			
		||||
    Create the init sequence for the display.
 | 
			
		||||
    Use the default sequence from the model, if any, and append any custom sequence provided in the config.
 | 
			
		||||
    Append SLPOUT (if not already in the sequence) and DISPON to the end of the sequence
 | 
			
		||||
    Pixel format, color order, and orientation will be set.
 | 
			
		||||
    """
 | 
			
		||||
    sequence = list(model.initsequence)
 | 
			
		||||
    custom_sequence = config.get(CONF_INIT_SEQUENCE, [])
 | 
			
		||||
    sequence.extend(custom_sequence)
 | 
			
		||||
    # Ensure each command is a tuple
 | 
			
		||||
    sequence = [x if isinstance(x, tuple) else (x,) for x in sequence]
 | 
			
		||||
    commands = [x[0] for x in sequence]
 | 
			
		||||
    # Set pixel format if not already in the custom sequence
 | 
			
		||||
    if PIXFMT not in commands:
 | 
			
		||||
        pixel_mode = config[CONF_PIXEL_MODE]
 | 
			
		||||
        if not isinstance(pixel_mode, int):
 | 
			
		||||
            pixel_mode = PIXEL_MODES[pixel_mode]
 | 
			
		||||
        sequence.append((PIXFMT, pixel_mode))
 | 
			
		||||
    # Does the chip use the flipping bits for mirroring rather than the reverse order bits?
 | 
			
		||||
    use_flip = config[CONF_USE_AXIS_FLIPS]
 | 
			
		||||
    if MADCTL not in commands:
 | 
			
		||||
        madctl = 0
 | 
			
		||||
        transform = get_transform(model, config)
 | 
			
		||||
        if transform.get(CONF_TRANSFORM):
 | 
			
		||||
            LOGGER.info("Using hardware transform to implement rotation")
 | 
			
		||||
        if transform.get(CONF_MIRROR_X):
 | 
			
		||||
            madctl |= MADCTL_XFLIP if use_flip else MADCTL_MX
 | 
			
		||||
        if transform.get(CONF_MIRROR_Y):
 | 
			
		||||
            madctl |= MADCTL_YFLIP if use_flip else MADCTL_MY
 | 
			
		||||
        if transform.get(CONF_SWAP_XY) is True:  # Exclude Undefined
 | 
			
		||||
            madctl |= MADCTL_MV
 | 
			
		||||
        if config[CONF_COLOR_ORDER] == MODE_BGR:
 | 
			
		||||
            madctl |= MADCTL_BGR
 | 
			
		||||
        sequence.append((MADCTL, madctl))
 | 
			
		||||
    if INVON not in commands and INVOFF not in commands:
 | 
			
		||||
        if config[CONF_INVERT_COLORS]:
 | 
			
		||||
            sequence.append((INVON,))
 | 
			
		||||
        else:
 | 
			
		||||
            sequence.append((INVOFF,))
 | 
			
		||||
    if BRIGHTNESS not in commands:
 | 
			
		||||
        if brightness := config.get(
 | 
			
		||||
            CONF_BRIGHTNESS, model.get_default(CONF_BRIGHTNESS)
 | 
			
		||||
        ):
 | 
			
		||||
            sequence.append((BRIGHTNESS, brightness))
 | 
			
		||||
    if SLPOUT not in commands:
 | 
			
		||||
        sequence.append((SLPOUT,))
 | 
			
		||||
    sequence.append((DISPON,))
 | 
			
		||||
 | 
			
		||||
    # Flatten the sequence into a list of bytes, with the length of each command
 | 
			
		||||
    # or the delay flag inserted where needed
 | 
			
		||||
    return sum(
 | 
			
		||||
        tuple(
 | 
			
		||||
            (x[1], 0xFF) if x[0] == DELAY_FLAG else (x[0], len(x) - 1) + x[1:]
 | 
			
		||||
            for x in sequence
 | 
			
		||||
        ),
 | 
			
		||||
        (),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    model = MODELS[config[CONF_MODEL]]
 | 
			
		||||
    transform = get_transform(model, config)
 | 
			
		||||
    if CONF_DIMENSIONS in config:
 | 
			
		||||
        # Explicit dimensions, just use as is
 | 
			
		||||
        dimensions = config[CONF_DIMENSIONS]
 | 
			
		||||
        if isinstance(dimensions, dict):
 | 
			
		||||
            width = dimensions[CONF_WIDTH]
 | 
			
		||||
            height = dimensions[CONF_HEIGHT]
 | 
			
		||||
            offset_width = dimensions[CONF_OFFSET_WIDTH]
 | 
			
		||||
            offset_height = dimensions[CONF_OFFSET_HEIGHT]
 | 
			
		||||
        else:
 | 
			
		||||
            (width, height) = dimensions
 | 
			
		||||
            offset_width = 0
 | 
			
		||||
            offset_height = 0
 | 
			
		||||
    else:
 | 
			
		||||
        # Default dimensions, use model defaults and transform if needed
 | 
			
		||||
        width = model.get_default(CONF_WIDTH)
 | 
			
		||||
        height = model.get_default(CONF_HEIGHT)
 | 
			
		||||
        offset_width = model.get_default(CONF_OFFSET_WIDTH, 0)
 | 
			
		||||
        offset_height = model.get_default(CONF_OFFSET_HEIGHT, 0)
 | 
			
		||||
 | 
			
		||||
        # if mirroring axes and there are offsets, also mirror the offsets to cater for situations where
 | 
			
		||||
        # the offset is asymmetric
 | 
			
		||||
        if transform[CONF_MIRROR_X]:
 | 
			
		||||
            native_width = model.get_default(
 | 
			
		||||
                CONF_NATIVE_WIDTH, width + offset_width * 2
 | 
			
		||||
            )
 | 
			
		||||
            offset_width = native_width - width - offset_width
 | 
			
		||||
        if transform[CONF_MIRROR_Y]:
 | 
			
		||||
            native_height = model.get_default(
 | 
			
		||||
                CONF_NATIVE_HEIGHT, height + offset_height * 2
 | 
			
		||||
            )
 | 
			
		||||
            offset_height = native_height - height - offset_height
 | 
			
		||||
        # Swap default dimensions if swap_xy is set
 | 
			
		||||
        if transform[CONF_SWAP_XY] is True:
 | 
			
		||||
            width, height = height, width
 | 
			
		||||
            offset_height, offset_width = offset_width, offset_height
 | 
			
		||||
 | 
			
		||||
    color_depth = config[CONF_COLOR_DEPTH]
 | 
			
		||||
    if color_depth.endswith("bit"):
 | 
			
		||||
        color_depth = color_depth[:-3]
 | 
			
		||||
    color_depth = COLOR_DEPTHS[int(color_depth)]
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(
 | 
			
		||||
        config[CONF_ID], width, height, offset_width, offset_height, color_depth
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_init_sequence(get_sequence(model, config)))
 | 
			
		||||
    if rotation_as_transform(model, config):
 | 
			
		||||
        if CONF_TRANSFORM in config:
 | 
			
		||||
            LOGGER.warning("Use of 'transform' with 'rotation' is not recommended")
 | 
			
		||||
        else:
 | 
			
		||||
            config[CONF_ROTATION] = 0
 | 
			
		||||
    cg.add(var.set_model(config[CONF_MODEL]))
 | 
			
		||||
    cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN]))
 | 
			
		||||
    cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING]))
 | 
			
		||||
    cg.add(var.set_spi_16(config[CONF_SPI_16]))
 | 
			
		||||
    if enable_pin := config.get(CONF_ENABLE_PIN):
 | 
			
		||||
        enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
 | 
			
		||||
        cg.add(var.set_enable_pins(enable))
 | 
			
		||||
 | 
			
		||||
    if reset_pin := config.get(CONF_RESET_PIN):
 | 
			
		||||
        reset = await cg.gpio_pin_expression(reset_pin)
 | 
			
		||||
        cg.add(var.set_reset_pin(reset))
 | 
			
		||||
 | 
			
		||||
    if dc_pin := config.get(CONF_DC_PIN):
 | 
			
		||||
        dc_pin = await cg.gpio_pin_expression(dc_pin)
 | 
			
		||||
        cg.add(var.set_dc_pin(dc_pin))
 | 
			
		||||
 | 
			
		||||
    if lamb := config.get(CONF_LAMBDA):
 | 
			
		||||
        lambda_ = await cg.process_lambda(
 | 
			
		||||
            lamb, [(display.DisplayRef, "it")], return_type=cg.void
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
    await display.register_display(var, config)
 | 
			
		||||
    await spi.register_spi_device(var, config)
 | 
			
		||||
							
								
								
									
										481
									
								
								esphome/components/mipi_spi/mipi_spi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										481
									
								
								esphome/components/mipi_spi/mipi_spi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,481 @@
 | 
			
		||||
#include "mipi_spi.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mipi_spi {
 | 
			
		||||
 | 
			
		||||
void MipiSpi::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MIPI SPI");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
  if (this->dc_pin_ != nullptr) {
 | 
			
		||||
    this->dc_pin_->setup();
 | 
			
		||||
    this->dc_pin_->digital_write(false);
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *pin : this->enable_pins_) {
 | 
			
		||||
    pin->setup();
 | 
			
		||||
    pin->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->reset_pin_ != nullptr) {
 | 
			
		||||
    this->reset_pin_->setup();
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
    delay(5);
 | 
			
		||||
    this->reset_pin_->digital_write(false);
 | 
			
		||||
    delay(5);
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
  }
 | 
			
		||||
  this->bus_width_ = this->parent_->get_bus_width();
 | 
			
		||||
 | 
			
		||||
  // need to know when the display is ready for SLPOUT command - will be 120ms after reset
 | 
			
		||||
  auto when = millis() + 120;
 | 
			
		||||
  delay(10);
 | 
			
		||||
  size_t index = 0;
 | 
			
		||||
  auto &vec = this->init_sequence_;
 | 
			
		||||
  while (index != vec.size()) {
 | 
			
		||||
    if (vec.size() - index < 2) {
 | 
			
		||||
      ESP_LOGE(TAG, "Malformed init sequence");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    uint8_t cmd = vec[index++];
 | 
			
		||||
    uint8_t x = vec[index++];
 | 
			
		||||
    if (x == DELAY_FLAG) {
 | 
			
		||||
      ESP_LOGD(TAG, "Delay %dms", cmd);
 | 
			
		||||
      delay(cmd);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint8_t num_args = x & 0x7F;
 | 
			
		||||
      if (vec.size() - index < num_args) {
 | 
			
		||||
        ESP_LOGE(TAG, "Malformed init sequence");
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      auto arg_byte = vec[index];
 | 
			
		||||
      switch (cmd) {
 | 
			
		||||
        case SLEEP_OUT: {
 | 
			
		||||
          // are we ready, boots?
 | 
			
		||||
          int duration = when - millis();
 | 
			
		||||
          if (duration > 0) {
 | 
			
		||||
            ESP_LOGD(TAG, "Sleep %dms", duration);
 | 
			
		||||
            delay(duration);
 | 
			
		||||
          }
 | 
			
		||||
        } break;
 | 
			
		||||
 | 
			
		||||
        case INVERT_ON:
 | 
			
		||||
          this->invert_colors_ = true;
 | 
			
		||||
          break;
 | 
			
		||||
        case MADCTL_CMD:
 | 
			
		||||
          this->madctl_ = arg_byte;
 | 
			
		||||
          break;
 | 
			
		||||
        case PIXFMT:
 | 
			
		||||
          this->pixel_mode_ = arg_byte & 0x11 ? PIXEL_MODE_16 : PIXEL_MODE_18;
 | 
			
		||||
          break;
 | 
			
		||||
        case BRIGHTNESS:
 | 
			
		||||
          this->brightness_ = arg_byte;
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      const auto *ptr = vec.data() + index;
 | 
			
		||||
      ESP_LOGD(TAG, "Command %02X, length %d, byte %02X", cmd, num_args, arg_byte);
 | 
			
		||||
      this->write_command_(cmd, ptr, num_args);
 | 
			
		||||
      index += num_args;
 | 
			
		||||
      if (cmd == SLEEP_OUT)
 | 
			
		||||
        delay(10);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->setup_complete_ = true;
 | 
			
		||||
  if (this->draw_from_origin_)
 | 
			
		||||
    check_buffer_();
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MIPI SPI setup complete");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::update() {
 | 
			
		||||
  if (!this->setup_complete_ || this->is_failed()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
  if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
 | 
			
		||||
    return;
 | 
			
		||||
  ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_);
 | 
			
		||||
  // Some chips require that the drawing window be aligned on certain boundaries
 | 
			
		||||
  auto dr = this->draw_rounding_;
 | 
			
		||||
  this->x_low_ = this->x_low_ / dr * dr;
 | 
			
		||||
  this->y_low_ = this->y_low_ / dr * dr;
 | 
			
		||||
  this->x_high_ = (this->x_high_ + dr) / dr * dr - 1;
 | 
			
		||||
  this->y_high_ = (this->y_high_ + dr) / dr * dr - 1;
 | 
			
		||||
  if (this->draw_from_origin_) {
 | 
			
		||||
    this->x_low_ = 0;
 | 
			
		||||
    this->y_low_ = 0;
 | 
			
		||||
    this->x_high_ = this->width_ - 1;
 | 
			
		||||
  }
 | 
			
		||||
  int w = this->x_high_ - this->x_low_ + 1;
 | 
			
		||||
  int h = this->y_high_ - this->y_low_ + 1;
 | 
			
		||||
  this->write_to_display_(this->x_low_, this->y_low_, w, h, this->buffer_, this->x_low_, this->y_low_,
 | 
			
		||||
                          this->width_ - w - this->x_low_);
 | 
			
		||||
  // invalidate watermarks
 | 
			
		||||
  this->x_low_ = this->width_;
 | 
			
		||||
  this->y_low_ = this->height_;
 | 
			
		||||
  this->x_high_ = 0;
 | 
			
		||||
  this->y_high_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::fill(Color color) {
 | 
			
		||||
  if (!this->check_buffer_())
 | 
			
		||||
    return;
 | 
			
		||||
  this->x_low_ = 0;
 | 
			
		||||
  this->y_low_ = 0;
 | 
			
		||||
  this->x_high_ = this->get_width_internal() - 1;
 | 
			
		||||
  this->y_high_ = this->get_height_internal() - 1;
 | 
			
		||||
  switch (this->color_depth_) {
 | 
			
		||||
    case display::COLOR_BITNESS_332: {
 | 
			
		||||
      auto new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
 | 
			
		||||
      memset(this->buffer_, (uint8_t) new_color, this->buffer_bytes_);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default: {
 | 
			
		||||
      auto new_color = display::ColorUtil::color_to_565(color);
 | 
			
		||||
      if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
 | 
			
		||||
        // Upper and lower is equal can use quicker memset operation. Takes ~20ms.
 | 
			
		||||
        memset(this->buffer_, (uint8_t) new_color, this->buffer_bytes_);
 | 
			
		||||
      } else {
 | 
			
		||||
        auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
 | 
			
		||||
        auto len = this->buffer_bytes_ / 2;
 | 
			
		||||
        while (len--) {
 | 
			
		||||
          *ptr_16++ = new_color;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::draw_absolute_pixel_internal(int x, int y, Color color) {
 | 
			
		||||
  if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->check_buffer_())
 | 
			
		||||
    return;
 | 
			
		||||
  size_t pos = (y * this->width_) + x;
 | 
			
		||||
  switch (this->color_depth_) {
 | 
			
		||||
    case display::COLOR_BITNESS_332: {
 | 
			
		||||
      uint8_t new_color = display::ColorUtil::color_to_332(color);
 | 
			
		||||
      if (this->buffer_[pos] == new_color)
 | 
			
		||||
        return;
 | 
			
		||||
      this->buffer_[pos] = new_color;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case display::COLOR_BITNESS_565: {
 | 
			
		||||
      auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
 | 
			
		||||
      uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
 | 
			
		||||
      uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
 | 
			
		||||
      uint16_t new_color = hi_byte | (lo_byte << 8);  // big endian
 | 
			
		||||
      if (ptr_16[pos] == new_color)
 | 
			
		||||
        return;
 | 
			
		||||
      ptr_16[pos] = new_color;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
  // low and high watermark may speed up drawing from buffer
 | 
			
		||||
  if (x < this->x_low_)
 | 
			
		||||
    this->x_low_ = x;
 | 
			
		||||
  if (y < this->y_low_)
 | 
			
		||||
    this->y_low_ = y;
 | 
			
		||||
  if (x > this->x_high_)
 | 
			
		||||
    this->x_high_ = x;
 | 
			
		||||
  if (y > this->y_high_)
 | 
			
		||||
    this->y_high_ = y;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::reset_params_() {
 | 
			
		||||
  if (!this->is_ready())
 | 
			
		||||
    return;
 | 
			
		||||
  this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF);
 | 
			
		||||
  if (this->brightness_.has_value())
 | 
			
		||||
    this->write_command_(BRIGHTNESS, this->brightness_.value());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_init_sequence_() {
 | 
			
		||||
  size_t index = 0;
 | 
			
		||||
  auto &vec = this->init_sequence_;
 | 
			
		||||
  while (index != vec.size()) {
 | 
			
		||||
    if (vec.size() - index < 2) {
 | 
			
		||||
      ESP_LOGE(TAG, "Malformed init sequence");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    uint8_t cmd = vec[index++];
 | 
			
		||||
    uint8_t x = vec[index++];
 | 
			
		||||
    if (x == DELAY_FLAG) {
 | 
			
		||||
      ESP_LOGV(TAG, "Delay %dms", cmd);
 | 
			
		||||
      delay(cmd);
 | 
			
		||||
    } else {
 | 
			
		||||
      uint8_t num_args = x & 0x7F;
 | 
			
		||||
      if (vec.size() - index < num_args) {
 | 
			
		||||
        ESP_LOGE(TAG, "Malformed init sequence");
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const auto *ptr = vec.data() + index;
 | 
			
		||||
      this->write_command_(cmd, ptr, num_args);
 | 
			
		||||
      index += num_args;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->setup_complete_ = true;
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MIPI SPI setup complete");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
 | 
			
		||||
  ESP_LOGVV(TAG, "Set addr %d/%d, %d/%d", x1, y1, x2, y2);
 | 
			
		||||
  uint8_t buf[4];
 | 
			
		||||
  x1 += this->offset_width_;
 | 
			
		||||
  x2 += this->offset_width_;
 | 
			
		||||
  y1 += this->offset_height_;
 | 
			
		||||
  y2 += this->offset_height_;
 | 
			
		||||
  put16_be(buf, y1);
 | 
			
		||||
  put16_be(buf + 2, y2);
 | 
			
		||||
  this->write_command_(RASET, buf, sizeof buf);
 | 
			
		||||
  put16_be(buf, x1);
 | 
			
		||||
  put16_be(buf + 2, x2);
 | 
			
		||||
  this->write_command_(CASET, buf, sizeof buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
 | 
			
		||||
                             display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
 | 
			
		||||
  if (!this->setup_complete_ || this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
  if (w <= 0 || h <= 0)
 | 
			
		||||
    return;
 | 
			
		||||
  if (bitness != this->color_depth_ || big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) {
 | 
			
		||||
    Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->draw_from_origin_) {
 | 
			
		||||
    auto stride = x_offset + w + x_pad;
 | 
			
		||||
    for (int y = 0; y != h; y++) {
 | 
			
		||||
      memcpy(this->buffer_ + ((y + y_start) * this->width_ + x_start) * 2,
 | 
			
		||||
             ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2);
 | 
			
		||||
    }
 | 
			
		||||
    ptr = this->buffer_;
 | 
			
		||||
    w = this->width_;
 | 
			
		||||
    h += y_start;
 | 
			
		||||
    x_start = 0;
 | 
			
		||||
    y_start = 0;
 | 
			
		||||
    x_offset = 0;
 | 
			
		||||
    y_offset = 0;
 | 
			
		||||
  }
 | 
			
		||||
  this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_18_from_16_bit_(const uint16_t *ptr, size_t w, size_t h, size_t stride) {
 | 
			
		||||
  stride -= w;
 | 
			
		||||
  uint8_t transfer_buffer[6 * 256];
 | 
			
		||||
  size_t idx = 0;  // index into transfer_buffer
 | 
			
		||||
  while (h-- != 0) {
 | 
			
		||||
    for (auto x = w; x-- != 0;) {
 | 
			
		||||
      auto color_val = *ptr++;
 | 
			
		||||
      // deal with byte swapping
 | 
			
		||||
      transfer_buffer[idx++] = (color_val & 0xF8);                                       // Blue
 | 
			
		||||
      transfer_buffer[idx++] = ((color_val & 0x7) << 5) | ((color_val & 0xE000) >> 11);  // Green
 | 
			
		||||
      transfer_buffer[idx++] = (color_val >> 5) & 0xF8;                                  // Red
 | 
			
		||||
      if (idx == sizeof(transfer_buffer)) {
 | 
			
		||||
        this->write_array(transfer_buffer, idx);
 | 
			
		||||
        idx = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ptr += stride;
 | 
			
		||||
  }
 | 
			
		||||
  if (idx != 0)
 | 
			
		||||
    this->write_array(transfer_buffer, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_18_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride) {
 | 
			
		||||
  stride -= w;
 | 
			
		||||
  uint8_t transfer_buffer[6 * 256];
 | 
			
		||||
  size_t idx = 0;  // index into transfer_buffer
 | 
			
		||||
  while (h-- != 0) {
 | 
			
		||||
    for (auto x = w; x-- != 0;) {
 | 
			
		||||
      auto color_val = *ptr++;
 | 
			
		||||
      transfer_buffer[idx++] = color_val & 0xE0;         // Red
 | 
			
		||||
      transfer_buffer[idx++] = (color_val << 3) & 0xE0;  // Green
 | 
			
		||||
      transfer_buffer[idx++] = color_val << 6;           // Blue
 | 
			
		||||
      if (idx == sizeof(transfer_buffer)) {
 | 
			
		||||
        this->write_array(transfer_buffer, idx);
 | 
			
		||||
        idx = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ptr += stride;
 | 
			
		||||
  }
 | 
			
		||||
  if (idx != 0)
 | 
			
		||||
    this->write_array(transfer_buffer, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_16_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride) {
 | 
			
		||||
  stride -= w;
 | 
			
		||||
  uint8_t transfer_buffer[6 * 256];
 | 
			
		||||
  size_t idx = 0;  // index into transfer_buffer
 | 
			
		||||
  while (h-- != 0) {
 | 
			
		||||
    for (auto x = w; x-- != 0;) {
 | 
			
		||||
      auto color_val = *ptr++;
 | 
			
		||||
      transfer_buffer[idx++] = (color_val & 0xE0) | ((color_val & 0x1C) >> 2);
 | 
			
		||||
      transfer_buffer[idx++] = (color_val & 0x3) << 3;
 | 
			
		||||
      if (idx == sizeof(transfer_buffer)) {
 | 
			
		||||
        this->write_array(transfer_buffer, idx);
 | 
			
		||||
        idx = 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    ptr += stride;
 | 
			
		||||
  }
 | 
			
		||||
  if (idx != 0)
 | 
			
		||||
    this->write_array(transfer_buffer, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
 | 
			
		||||
                                int x_pad) {
 | 
			
		||||
  this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
 | 
			
		||||
  auto stride = x_offset + w + x_pad;
 | 
			
		||||
  const auto *offset_ptr = ptr;
 | 
			
		||||
  if (this->color_depth_ == display::COLOR_BITNESS_332) {
 | 
			
		||||
    offset_ptr += y_offset * stride + x_offset;
 | 
			
		||||
  } else {
 | 
			
		||||
    stride *= 2;
 | 
			
		||||
    offset_ptr += y_offset * stride + x_offset * 2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (this->bus_width_) {
 | 
			
		||||
    case 4:
 | 
			
		||||
      this->enable();
 | 
			
		||||
      if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
 | 
			
		||||
        // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't
 | 
			
		||||
        // bother
 | 
			
		||||
        this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w * h * 2, 4);
 | 
			
		||||
      } else {
 | 
			
		||||
        this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, nullptr, 0, 4);
 | 
			
		||||
        for (int y = 0; y != h; y++) {
 | 
			
		||||
          this->write_cmd_addr_data(0, 0, 0, 0, offset_ptr, w * 2, 4);
 | 
			
		||||
          offset_ptr += stride;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case 8:
 | 
			
		||||
      this->write_command_(WDATA);
 | 
			
		||||
      this->enable();
 | 
			
		||||
      if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
 | 
			
		||||
        this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h * 2, 8);
 | 
			
		||||
      } else {
 | 
			
		||||
        for (int y = 0; y != h; y++) {
 | 
			
		||||
          this->write_cmd_addr_data(0, 0, 0, 0, offset_ptr, w * 2, 8);
 | 
			
		||||
          offset_ptr += stride;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      this->write_command_(WDATA);
 | 
			
		||||
      this->enable();
 | 
			
		||||
 | 
			
		||||
      if (this->color_depth_ == display::COLOR_BITNESS_565) {
 | 
			
		||||
        // Source buffer is 16-bit RGB565
 | 
			
		||||
        if (this->pixel_mode_ == PIXEL_MODE_18) {
 | 
			
		||||
          // Convert RGB565 to RGB666
 | 
			
		||||
          this->write_18_from_16_bit_(reinterpret_cast<const uint16_t *>(offset_ptr), w, h, stride / 2);
 | 
			
		||||
        } else {
 | 
			
		||||
          // Direct RGB565 output
 | 
			
		||||
          if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
 | 
			
		||||
            this->write_array(ptr, w * h * 2);
 | 
			
		||||
          } else {
 | 
			
		||||
            for (int y = 0; y != h; y++) {
 | 
			
		||||
              this->write_array(offset_ptr, w * 2);
 | 
			
		||||
              offset_ptr += stride;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        // Source buffer is 8-bit RGB332
 | 
			
		||||
        if (this->pixel_mode_ == PIXEL_MODE_18) {
 | 
			
		||||
          // Convert RGB332 to RGB666
 | 
			
		||||
          this->write_18_from_8_bit_(offset_ptr, w, h, stride);
 | 
			
		||||
        } else {
 | 
			
		||||
          this->write_16_from_8_bit_(offset_ptr, w, h, stride);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  this->disable();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
 | 
			
		||||
  ESP_LOGV(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty(bytes, len).c_str());
 | 
			
		||||
  if (this->bus_width_ == 4) {
 | 
			
		||||
    this->enable();
 | 
			
		||||
    this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len);
 | 
			
		||||
    this->disable();
 | 
			
		||||
  } else if (this->bus_width_ == 8) {
 | 
			
		||||
    this->dc_pin_->digital_write(false);
 | 
			
		||||
    this->enable();
 | 
			
		||||
    this->write_cmd_addr_data(0, 0, 0, 0, &cmd, 1, 8);
 | 
			
		||||
    this->disable();
 | 
			
		||||
    this->dc_pin_->digital_write(true);
 | 
			
		||||
    if (len != 0) {
 | 
			
		||||
      this->enable();
 | 
			
		||||
      this->write_cmd_addr_data(0, 0, 0, 0, bytes, len, 8);
 | 
			
		||||
      this->disable();
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->dc_pin_->digital_write(false);
 | 
			
		||||
    this->enable();
 | 
			
		||||
    this->write_byte(cmd);
 | 
			
		||||
    this->disable();
 | 
			
		||||
    this->dc_pin_->digital_write(true);
 | 
			
		||||
    if (len != 0) {
 | 
			
		||||
      if (this->spi_16_) {
 | 
			
		||||
        for (size_t i = 0; i != len; i++) {
 | 
			
		||||
          this->enable();
 | 
			
		||||
          this->write_byte(0);
 | 
			
		||||
          this->write_byte(bytes[i]);
 | 
			
		||||
          this->disable();
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this->enable();
 | 
			
		||||
        this->write_array(bytes, len);
 | 
			
		||||
        this->disable();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MipiSpi::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MIPI_SPI Display");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Model: %s", this->model_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Width: %u", this->width_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Height: %u", this->height_);
 | 
			
		||||
  if (this->offset_width_ != 0)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Offset width: %u", this->offset_width_);
 | 
			
		||||
  if (this->offset_height_ != 0)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Offset height: %u", this->offset_height_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Swap X/Y: %s", YESNO(this->madctl_ & MADCTL_MV));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Mirror X: %s", YESNO(this->madctl_ & (MADCTL_MX | MADCTL_XFLIP)));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Mirror Y: %s", YESNO(this->madctl_ & (MADCTL_MY | MADCTL_YFLIP)));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Color depth: %d bits", this->color_depth_ == display::COLOR_BITNESS_565 ? 16 : 8);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Invert colors: %s", YESNO(this->invert_colors_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Color order: %s", this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Pixel mode: %s", this->pixel_mode_ == PIXEL_MODE_18 ? "18bit" : "16bit");
 | 
			
		||||
  if (this->brightness_.has_value())
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Brightness: %u", this->brightness_.value());
 | 
			
		||||
  if (this->spi_16_)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  SPI 16bit: YES");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Draw rounding: %u", this->draw_rounding_);
 | 
			
		||||
  if (this->draw_from_origin_)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Draw from origin: YES");
 | 
			
		||||
  LOG_PIN("  CS Pin: ", this->cs_);
 | 
			
		||||
  LOG_PIN("  Reset Pin: ", this->reset_pin_);
 | 
			
		||||
  LOG_PIN("  DC Pin: ", this->dc_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  SPI Mode: %d", this->mode_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  SPI Data rate: %dMHz", static_cast<unsigned>(this->data_rate_ / 1000000));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  SPI Bus width: %d", this->bus_width_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace mipi_spi
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										171
									
								
								esphome/components/mipi_spi/mipi_spi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								esphome/components/mipi_spi/mipi_spi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,171 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
#include "esphome/components/display/display.h"
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
#include "esphome/components/display/display_color_utils.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mipi_spi {
 | 
			
		||||
 | 
			
		||||
constexpr static const char *const TAG = "display.mipi_spi";
 | 
			
		||||
static const uint8_t SW_RESET_CMD = 0x01;
 | 
			
		||||
static const uint8_t SLEEP_OUT = 0x11;
 | 
			
		||||
static const uint8_t NORON = 0x13;
 | 
			
		||||
static const uint8_t INVERT_OFF = 0x20;
 | 
			
		||||
static const uint8_t INVERT_ON = 0x21;
 | 
			
		||||
static const uint8_t ALL_ON = 0x23;
 | 
			
		||||
static const uint8_t WRAM = 0x24;
 | 
			
		||||
static const uint8_t MIPI = 0x26;
 | 
			
		||||
static const uint8_t DISPLAY_ON = 0x29;
 | 
			
		||||
static const uint8_t RASET = 0x2B;
 | 
			
		||||
static const uint8_t CASET = 0x2A;
 | 
			
		||||
static const uint8_t WDATA = 0x2C;
 | 
			
		||||
static const uint8_t TEON = 0x35;
 | 
			
		||||
static const uint8_t MADCTL_CMD = 0x36;
 | 
			
		||||
static const uint8_t PIXFMT = 0x3A;
 | 
			
		||||
static const uint8_t BRIGHTNESS = 0x51;
 | 
			
		||||
static const uint8_t SWIRE1 = 0x5A;
 | 
			
		||||
static const uint8_t SWIRE2 = 0x5B;
 | 
			
		||||
static const uint8_t PAGESEL = 0xFE;
 | 
			
		||||
 | 
			
		||||
static const uint8_t MADCTL_MY = 0x80;     // Bit 7 Bottom to top
 | 
			
		||||
static const uint8_t MADCTL_MX = 0x40;     // Bit 6 Right to left
 | 
			
		||||
static const uint8_t MADCTL_MV = 0x20;     // Bit 5 Swap axes
 | 
			
		||||
static const uint8_t MADCTL_RGB = 0x00;    // Bit 3 Red-Green-Blue pixel order
 | 
			
		||||
static const uint8_t MADCTL_BGR = 0x08;    // Bit 3 Blue-Green-Red pixel order
 | 
			
		||||
static const uint8_t MADCTL_XFLIP = 0x02;  // Mirror the display horizontally
 | 
			
		||||
static const uint8_t MADCTL_YFLIP = 0x01;  // Mirror the display vertically
 | 
			
		||||
 | 
			
		||||
static const uint8_t DELAY_FLAG = 0xFF;
 | 
			
		||||
// store a 16 bit value in a buffer, big endian.
 | 
			
		||||
static inline void put16_be(uint8_t *buf, uint16_t value) {
 | 
			
		||||
  buf[0] = value >> 8;
 | 
			
		||||
  buf[1] = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum PixelMode {
 | 
			
		||||
  PIXEL_MODE_16,
 | 
			
		||||
  PIXEL_MODE_18,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MipiSpi : public display::DisplayBuffer,
 | 
			
		||||
                public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
 | 
			
		||||
                                      spi::DATA_RATE_1MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  MipiSpi(size_t width, size_t height, int16_t offset_width, int16_t offset_height, display::ColorBitness color_depth)
 | 
			
		||||
      : width_(width),
 | 
			
		||||
        height_(height),
 | 
			
		||||
        offset_width_(offset_width),
 | 
			
		||||
        offset_height_(offset_height),
 | 
			
		||||
        color_depth_(color_depth) {}
 | 
			
		||||
  void set_model(const char *model) { this->model_ = model; }
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  display::ColorOrder get_color_mode() {
 | 
			
		||||
    return this->madctl_ & MADCTL_BGR ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
 | 
			
		||||
  void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
 | 
			
		||||
  void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
 | 
			
		||||
  void set_invert_colors(bool invert_colors) {
 | 
			
		||||
    this->invert_colors_ = invert_colors;
 | 
			
		||||
    this->reset_params_();
 | 
			
		||||
  }
 | 
			
		||||
  void set_brightness(uint8_t brightness) {
 | 
			
		||||
    this->brightness_ = brightness;
 | 
			
		||||
    this->reset_params_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_draw_from_origin(bool draw_from_origin) { this->draw_from_origin_ = draw_from_origin; }
 | 
			
		||||
  display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  int get_width_internal() override { return this->width_; }
 | 
			
		||||
  int get_height_internal() override { return this->height_; }
 | 
			
		||||
  bool can_proceed() override { return this->setup_complete_; }
 | 
			
		||||
  void set_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequence_ = sequence; }
 | 
			
		||||
  void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
 | 
			
		||||
  void set_spi_16(bool spi_16) { this->spi_16_ = spi_16; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool check_buffer_() {
 | 
			
		||||
    if (this->is_failed())
 | 
			
		||||
      return false;
 | 
			
		||||
    if (this->buffer_ != nullptr)
 | 
			
		||||
      return true;
 | 
			
		||||
    auto bytes_per_pixel = this->color_depth_ == display::COLOR_BITNESS_565 ? 2 : 1;
 | 
			
		||||
    this->init_internal_(this->width_ * this->height_ * bytes_per_pixel);
 | 
			
		||||
    if (this->buffer_ == nullptr) {
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    this->buffer_bytes_ = this->width_ * this->height_ * bytes_per_pixel;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  void fill(Color color) override;
 | 
			
		||||
  void draw_absolute_pixel_internal(int x, int y, Color color) override;
 | 
			
		||||
  void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
 | 
			
		||||
                      display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
 | 
			
		||||
  void write_18_from_16_bit_(const uint16_t *ptr, size_t w, size_t h, size_t stride);
 | 
			
		||||
  void write_18_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride);
 | 
			
		||||
  void write_16_from_8_bit_(const uint8_t *ptr, size_t w, size_t h, size_t stride);
 | 
			
		||||
  void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
 | 
			
		||||
                         int x_pad);
 | 
			
		||||
  /**
 | 
			
		||||
   * the RM67162 in quad SPI mode seems to work like this (not in the datasheet, this is deduced from the
 | 
			
		||||
   * sample code.)
 | 
			
		||||
   *
 | 
			
		||||
   * Immediately after enabling /CS send 4 bytes in single-dataline SPI mode:
 | 
			
		||||
   *    0: either 0x2 or 0x32. The first indicates that any subsequent data bytes after the initial 4 will be
 | 
			
		||||
   *        sent in 1-dataline SPI. The second indicates quad mode.
 | 
			
		||||
   *    1: 0x00
 | 
			
		||||
   *    2: The command (register address) byte.
 | 
			
		||||
   *    3: 0x00
 | 
			
		||||
   *
 | 
			
		||||
   *    This is followed by zero or more data bytes in either 1-wire or 4-wire mode, depending on the first byte.
 | 
			
		||||
   *    At the conclusion of the write, de-assert /CS.
 | 
			
		||||
   *
 | 
			
		||||
   * @param cmd
 | 
			
		||||
   * @param bytes
 | 
			
		||||
   * @param len
 | 
			
		||||
   */
 | 
			
		||||
  void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len);
 | 
			
		||||
 | 
			
		||||
  void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); }
 | 
			
		||||
  void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); }
 | 
			
		||||
  void reset_params_();
 | 
			
		||||
  void write_init_sequence_();
 | 
			
		||||
  void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
 | 
			
		||||
 | 
			
		||||
  GPIOPin *reset_pin_{nullptr};
 | 
			
		||||
  std::vector<GPIOPin *> enable_pins_{};
 | 
			
		||||
  GPIOPin *dc_pin_{nullptr};
 | 
			
		||||
  uint16_t x_low_{1};
 | 
			
		||||
  uint16_t y_low_{1};
 | 
			
		||||
  uint16_t x_high_{0};
 | 
			
		||||
  uint16_t y_high_{0};
 | 
			
		||||
  bool setup_complete_{};
 | 
			
		||||
 | 
			
		||||
  bool invert_colors_{};
 | 
			
		||||
  size_t width_;
 | 
			
		||||
  size_t height_;
 | 
			
		||||
  int16_t offset_width_;
 | 
			
		||||
  int16_t offset_height_;
 | 
			
		||||
  size_t buffer_bytes_{0};
 | 
			
		||||
  display::ColorBitness color_depth_;
 | 
			
		||||
  PixelMode pixel_mode_{PIXEL_MODE_16};
 | 
			
		||||
  uint8_t bus_width_{};
 | 
			
		||||
  bool spi_16_{};
 | 
			
		||||
  uint8_t madctl_{};
 | 
			
		||||
  bool draw_from_origin_{false};
 | 
			
		||||
  unsigned draw_rounding_{2};
 | 
			
		||||
  optional<uint8_t> brightness_{};
 | 
			
		||||
  const char *model_{"Unknown"};
 | 
			
		||||
  std::vector<uint8_t> init_sequence_{};
 | 
			
		||||
};
 | 
			
		||||
}  // namespace mipi_spi
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										65
									
								
								esphome/components/mipi_spi/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/mipi_spi/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_HEIGHT, CONF_OFFSET_HEIGHT, CONF_OFFSET_WIDTH, CONF_WIDTH
 | 
			
		||||
 | 
			
		||||
from .. import CONF_NATIVE_HEIGHT, CONF_NATIVE_WIDTH
 | 
			
		||||
 | 
			
		||||
MADCTL_MY = 0x80  # Bit 7 Bottom to top
 | 
			
		||||
MADCTL_MX = 0x40  # Bit 6 Right to left
 | 
			
		||||
MADCTL_MV = 0x20  # Bit 5 Reverse Mode
 | 
			
		||||
MADCTL_ML = 0x10  # Bit 4 LCD refresh Bottom to top
 | 
			
		||||
MADCTL_RGB = 0x00  # Bit 3 Red-Green-Blue pixel order
 | 
			
		||||
MADCTL_BGR = 0x08  # Bit 3 Blue-Green-Red pixel order
 | 
			
		||||
MADCTL_MH = 0x04  # Bit 2 LCD refresh right to left
 | 
			
		||||
 | 
			
		||||
# These bits are used instead of the above bits on some chips, where using MX and MY results in incorrect
 | 
			
		||||
# partial updates.
 | 
			
		||||
MADCTL_XFLIP = 0x02  # Mirror the display horizontally
 | 
			
		||||
MADCTL_YFLIP = 0x01  # Mirror the display vertically
 | 
			
		||||
 | 
			
		||||
DELAY_FLAG = 0xFFF  # Special flag to indicate a delay
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def delay(ms):
 | 
			
		||||
    return DELAY_FLAG, ms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DriverChip:
 | 
			
		||||
    models = {}
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        name: str,
 | 
			
		||||
        modes=(TYPE_SINGLE, TYPE_QUAD, TYPE_OCTAL),
 | 
			
		||||
        initsequence=None,
 | 
			
		||||
        **defaults,
 | 
			
		||||
    ):
 | 
			
		||||
        name = name.upper()
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.modes = modes
 | 
			
		||||
        self.initsequence = initsequence
 | 
			
		||||
        self.defaults = defaults
 | 
			
		||||
        DriverChip.models[name] = self
 | 
			
		||||
 | 
			
		||||
    def extend(self, name, **kwargs):
 | 
			
		||||
        defaults = self.defaults.copy()
 | 
			
		||||
        if (
 | 
			
		||||
            CONF_WIDTH in defaults
 | 
			
		||||
            and CONF_OFFSET_WIDTH in kwargs
 | 
			
		||||
            and CONF_NATIVE_WIDTH not in defaults
 | 
			
		||||
        ):
 | 
			
		||||
            defaults[CONF_NATIVE_WIDTH] = defaults[CONF_WIDTH]
 | 
			
		||||
        if (
 | 
			
		||||
            CONF_HEIGHT in defaults
 | 
			
		||||
            and CONF_OFFSET_HEIGHT in kwargs
 | 
			
		||||
            and CONF_NATIVE_HEIGHT not in defaults
 | 
			
		||||
        ):
 | 
			
		||||
            defaults[CONF_NATIVE_HEIGHT] = defaults[CONF_HEIGHT]
 | 
			
		||||
        defaults.update(kwargs)
 | 
			
		||||
        return DriverChip(name, self.modes, initsequence=self.initsequence, **defaults)
 | 
			
		||||
 | 
			
		||||
    def get_default(self, key, fallback=False):
 | 
			
		||||
        return self.defaults.get(key, fallback)
 | 
			
		||||
 | 
			
		||||
    def option(self, name, fallback=False):
 | 
			
		||||
        return cv.Optional(name, default=self.get_default(name, fallback))
 | 
			
		||||
							
								
								
									
										72
									
								
								esphome/components/mipi_spi/models/amoled.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								esphome/components/mipi_spi/models/amoled.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
from esphome.components.spi import TYPE_QUAD
 | 
			
		||||
 | 
			
		||||
from .. import MODE_RGB
 | 
			
		||||
from . import DriverChip, delay
 | 
			
		||||
from .commands import MIPI, NORON, PAGESEL, PIXFMT, SLPOUT, SWIRE1, SWIRE2, TEON, WRAM
 | 
			
		||||
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "T-DISPLAY-S3-AMOLED",
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=536,
 | 
			
		||||
    cs_pin=6,
 | 
			
		||||
    reset_pin=17,
 | 
			
		||||
    enable_pin=38,
 | 
			
		||||
    bus_mode=TYPE_QUAD,
 | 
			
		||||
    brightness=0xD0,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    initsequence=(SLPOUT,),  # Requires early SLPOUT
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DriverChip(
 | 
			
		||||
    name="T-DISPLAY-S3-AMOLED-PLUS",
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=536,
 | 
			
		||||
    cs_pin=6,
 | 
			
		||||
    reset_pin=17,
 | 
			
		||||
    dc_pin=7,
 | 
			
		||||
    enable_pin=38,
 | 
			
		||||
    data_rate="40MHz",
 | 
			
		||||
    brightness=0xD0,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (PAGESEL, 4),
 | 
			
		||||
        (0x6A, 0x00),
 | 
			
		||||
        (PAGESEL, 0x05),
 | 
			
		||||
        (PAGESEL, 0x07),
 | 
			
		||||
        (0x07, 0x4F),
 | 
			
		||||
        (PAGESEL, 0x01),
 | 
			
		||||
        (0x2A, 0x02),
 | 
			
		||||
        (0x2B, 0x73),
 | 
			
		||||
        (PAGESEL, 0x0A),
 | 
			
		||||
        (0x29, 0x10),
 | 
			
		||||
        (PAGESEL, 0x00),
 | 
			
		||||
        (0x53, 0x20),
 | 
			
		||||
        (TEON, 0x00),
 | 
			
		||||
        (PIXFMT, 0x75),
 | 
			
		||||
        (0xC4, 0x80),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
RM690B0 = DriverChip(
 | 
			
		||||
    "RM690B0",
 | 
			
		||||
    brightness=0xD0,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    width=480,
 | 
			
		||||
    height=600,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (PAGESEL, 0x20),
 | 
			
		||||
        (MIPI, 0x0A),
 | 
			
		||||
        (WRAM, 0x80),
 | 
			
		||||
        (SWIRE1, 0x51),
 | 
			
		||||
        (SWIRE2, 0x2E),
 | 
			
		||||
        (PAGESEL, 0x00),
 | 
			
		||||
        (0xC2, 0x00),
 | 
			
		||||
        delay(10),
 | 
			
		||||
        (TEON, 0x00),
 | 
			
		||||
        (NORON,),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
T4_S3_AMOLED = RM690B0.extend("T4-S3", width=450, offset_width=16, bus_mode=TYPE_QUAD)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										82
									
								
								esphome/components/mipi_spi/models/commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								esphome/components/mipi_spi/models/commands.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
# MIPI DBI commands
 | 
			
		||||
 | 
			
		||||
NOP = 0x00
 | 
			
		||||
SWRESET = 0x01
 | 
			
		||||
RDDID = 0x04
 | 
			
		||||
RDDST = 0x09
 | 
			
		||||
RDMODE = 0x0A
 | 
			
		||||
RDMADCTL = 0x0B
 | 
			
		||||
RDPIXFMT = 0x0C
 | 
			
		||||
RDIMGFMT = 0x0D
 | 
			
		||||
RDSELFDIAG = 0x0F
 | 
			
		||||
SLEEP_IN = 0x10
 | 
			
		||||
SLPIN = 0x10
 | 
			
		||||
SLEEP_OUT = 0x11
 | 
			
		||||
SLPOUT = 0x11
 | 
			
		||||
PTLON = 0x12
 | 
			
		||||
NORON = 0x13
 | 
			
		||||
INVERT_OFF = 0x20
 | 
			
		||||
INVOFF = 0x20
 | 
			
		||||
INVERT_ON = 0x21
 | 
			
		||||
INVON = 0x21
 | 
			
		||||
ALL_ON = 0x23
 | 
			
		||||
WRAM = 0x24
 | 
			
		||||
GAMMASET = 0x26
 | 
			
		||||
MIPI = 0x26
 | 
			
		||||
DISPOFF = 0x28
 | 
			
		||||
DISPON = 0x29
 | 
			
		||||
CASET = 0x2A
 | 
			
		||||
PASET = 0x2B
 | 
			
		||||
RASET = 0x2B
 | 
			
		||||
RAMWR = 0x2C
 | 
			
		||||
WDATA = 0x2C
 | 
			
		||||
RAMRD = 0x2E
 | 
			
		||||
PTLAR = 0x30
 | 
			
		||||
VSCRDEF = 0x33
 | 
			
		||||
TEON = 0x35
 | 
			
		||||
MADCTL = 0x36
 | 
			
		||||
MADCTL_CMD = 0x36
 | 
			
		||||
VSCRSADD = 0x37
 | 
			
		||||
IDMOFF = 0x38
 | 
			
		||||
IDMON = 0x39
 | 
			
		||||
COLMOD = 0x3A
 | 
			
		||||
PIXFMT = 0x3A
 | 
			
		||||
GETSCANLINE = 0x45
 | 
			
		||||
BRIGHTNESS = 0x51
 | 
			
		||||
WRDISBV = 0x51
 | 
			
		||||
RDDISBV = 0x52
 | 
			
		||||
WRCTRLD = 0x53
 | 
			
		||||
SWIRE1 = 0x5A
 | 
			
		||||
SWIRE2 = 0x5B
 | 
			
		||||
IFMODE = 0xB0
 | 
			
		||||
FRMCTR1 = 0xB1
 | 
			
		||||
FRMCTR2 = 0xB2
 | 
			
		||||
FRMCTR3 = 0xB3
 | 
			
		||||
INVCTR = 0xB4
 | 
			
		||||
DFUNCTR = 0xB6
 | 
			
		||||
ETMOD = 0xB7
 | 
			
		||||
PWCTR1 = 0xC0
 | 
			
		||||
PWCTR2 = 0xC1
 | 
			
		||||
PWCTR3 = 0xC2
 | 
			
		||||
PWCTR4 = 0xC3
 | 
			
		||||
PWCTR5 = 0xC4
 | 
			
		||||
VMCTR1 = 0xC5
 | 
			
		||||
IFCTR = 0xC6
 | 
			
		||||
VMCTR2 = 0xC7
 | 
			
		||||
GMCTR = 0xC8
 | 
			
		||||
SETEXTC = 0xC8
 | 
			
		||||
PWSET = 0xD0
 | 
			
		||||
VMCTR = 0xD1
 | 
			
		||||
PWSETN = 0xD2
 | 
			
		||||
RDID4 = 0xD3
 | 
			
		||||
RDINDEX = 0xD9
 | 
			
		||||
RDID1 = 0xDA
 | 
			
		||||
RDID2 = 0xDB
 | 
			
		||||
RDID3 = 0xDC
 | 
			
		||||
RDIDX = 0xDD
 | 
			
		||||
GMCTRP1 = 0xE0
 | 
			
		||||
GMCTRN1 = 0xE1
 | 
			
		||||
CSCON = 0xF0
 | 
			
		||||
PWCTR6 = 0xF6
 | 
			
		||||
ADJCTL3 = 0xF7
 | 
			
		||||
PAGESEL = 0xFE
 | 
			
		||||
							
								
								
									
										10
									
								
								esphome/components/mipi_spi/models/cyd.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								esphome/components/mipi_spi/models/cyd.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
from .ili import ILI9341
 | 
			
		||||
 | 
			
		||||
ILI9341.extend(
 | 
			
		||||
    "ESP32-2432S028",
 | 
			
		||||
    data_rate="40MHz",
 | 
			
		||||
    cs_pin=15,
 | 
			
		||||
    dc_pin=2,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										749
									
								
								esphome/components/mipi_spi/models/ili.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										749
									
								
								esphome/components/mipi_spi/models/ili.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,749 @@
 | 
			
		||||
from esphome.components.spi import TYPE_OCTAL
 | 
			
		||||
 | 
			
		||||
from .. import MODE_RGB
 | 
			
		||||
from . import DriverChip, delay
 | 
			
		||||
from .commands import (
 | 
			
		||||
    ADJCTL3,
 | 
			
		||||
    CSCON,
 | 
			
		||||
    DFUNCTR,
 | 
			
		||||
    ETMOD,
 | 
			
		||||
    FRMCTR1,
 | 
			
		||||
    FRMCTR2,
 | 
			
		||||
    FRMCTR3,
 | 
			
		||||
    GAMMASET,
 | 
			
		||||
    GMCTR,
 | 
			
		||||
    GMCTRN1,
 | 
			
		||||
    GMCTRP1,
 | 
			
		||||
    IDMOFF,
 | 
			
		||||
    IFCTR,
 | 
			
		||||
    IFMODE,
 | 
			
		||||
    INVCTR,
 | 
			
		||||
    NORON,
 | 
			
		||||
    PWCTR1,
 | 
			
		||||
    PWCTR2,
 | 
			
		||||
    PWCTR3,
 | 
			
		||||
    PWCTR4,
 | 
			
		||||
    PWCTR5,
 | 
			
		||||
    PWSET,
 | 
			
		||||
    PWSETN,
 | 
			
		||||
    SETEXTC,
 | 
			
		||||
    SWRESET,
 | 
			
		||||
    VMCTR,
 | 
			
		||||
    VMCTR1,
 | 
			
		||||
    VMCTR2,
 | 
			
		||||
    VSCRSADD,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "M5CORE",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=240,
 | 
			
		||||
    cs_pin=14,
 | 
			
		||||
    dc_pin=27,
 | 
			
		||||
    reset_pin=33,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (SETEXTC, 0xFF, 0x93, 0x42),
 | 
			
		||||
        (PWCTR1, 0x12, 0x12),
 | 
			
		||||
        (PWCTR2, 0x03),
 | 
			
		||||
        (VMCTR1, 0xF2),
 | 
			
		||||
        (IFMODE, 0xE0),
 | 
			
		||||
        (0xF6, 0x01, 0x00, 0x00),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x89,
 | 
			
		||||
            0x4C,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x34,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x05,
 | 
			
		||||
            0x13,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x33,
 | 
			
		||||
            0x67,
 | 
			
		||||
            0x48,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x33,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
        (DFUNCTR, 0x08, 0x82, 0x1D, 0x04),
 | 
			
		||||
        (IDMOFF,),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
ILI9341 = DriverChip(
 | 
			
		||||
    "ILI9341",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=320,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xEF, 0x03, 0x80, 0x02),
 | 
			
		||||
        (0xCF, 0x00, 0xC1, 0x30),
 | 
			
		||||
        (0xED, 0x64, 0x03, 0x12, 0x81),
 | 
			
		||||
        (0xE8, 0x85, 0x00, 0x78),
 | 
			
		||||
        (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02),
 | 
			
		||||
        (0xF7, 0x20),
 | 
			
		||||
        (0xEA, 0x00, 0x00),
 | 
			
		||||
        (PWCTR1, 0x23),
 | 
			
		||||
        (PWCTR2, 0x10),
 | 
			
		||||
        (VMCTR1, 0x3E, 0x28),
 | 
			
		||||
        (VMCTR2, 0x86),
 | 
			
		||||
        (VSCRSADD, 0x00),
 | 
			
		||||
        (FRMCTR1, 0x00, 0x18),
 | 
			
		||||
        (DFUNCTR, 0x08, 0x82, 0x27),
 | 
			
		||||
        (0xF2, 0x00),
 | 
			
		||||
        (GAMMASET, 0x01),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x2B,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x4E,
 | 
			
		||||
            0xF1,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0xC1,
 | 
			
		||||
            0x48,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x36,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "ILI9481",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    use_axis_flips=True,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (PWSET, 0x07, 0x42, 0x18),
 | 
			
		||||
        (VMCTR, 0x00, 0x07, 0x10),
 | 
			
		||||
        (PWSETN, 0x01, 0x02),
 | 
			
		||||
        (PWCTR1, 0x10, 0x3B, 0x00, 0x02, 0x11),
 | 
			
		||||
        (VMCTR1, 0x03),
 | 
			
		||||
        (IFCTR, 0x83),
 | 
			
		||||
        (GMCTR, 0x32, 0x36, 0x45, 0x06, 0x16, 0x37, 0x75, 0x77, 0x54, 0x0C, 0x00),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "ILI9486",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (PWCTR3, 0x44),
 | 
			
		||||
        (VMCTR1, 0x00, 0x00, 0x00, 0x00),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x1F,
 | 
			
		||||
            0x1C,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x48,
 | 
			
		||||
            0x98,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x13,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x05,
 | 
			
		||||
            0x47,
 | 
			
		||||
            0x75,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x24,
 | 
			
		||||
            0x20,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "ILI9488",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    pixel_mode="18bit",
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x24,
 | 
			
		||||
            0x1C,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x43,
 | 
			
		||||
            0x88,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x38,
 | 
			
		||||
            0x30,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x4E,
 | 
			
		||||
            0x77,
 | 
			
		||||
            0x3C,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x05,
 | 
			
		||||
            0x23,
 | 
			
		||||
            0x1B,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (PWCTR1, 0x17, 0x15),
 | 
			
		||||
        (PWCTR2, 0x41),
 | 
			
		||||
        (VMCTR1, 0x00, 0x12, 0x80),
 | 
			
		||||
        (IFMODE, 0x00),
 | 
			
		||||
        (FRMCTR1, 0xA0),
 | 
			
		||||
        (INVCTR, 0x02),
 | 
			
		||||
        (0xE9, 0x00),
 | 
			
		||||
        (ADJCTL3, 0xA9, 0x51, 0x2C, 0x82),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
ILI9488_A = DriverChip(
 | 
			
		||||
    "ILI9488_A",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    invert_colors=False,
 | 
			
		||||
    pixel_mode="18bit",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x16,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x3F,
 | 
			
		||||
            0x78,
 | 
			
		||||
            0x4C,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x16,
 | 
			
		||||
            0x1A,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x16,
 | 
			
		||||
            0x19,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x05,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x45,
 | 
			
		||||
            0x46,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x35,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
        (PWCTR1, 0x17, 0x15),
 | 
			
		||||
        (PWCTR2, 0x41),
 | 
			
		||||
        (VMCTR1, 0x00, 0x12, 0x80),
 | 
			
		||||
        (IFMODE, 0x00),
 | 
			
		||||
        (FRMCTR1, 0xA0),
 | 
			
		||||
        (INVCTR, 0x02),
 | 
			
		||||
        (DFUNCTR, 0x02, 0x02),
 | 
			
		||||
        (0xE9, 0x00),
 | 
			
		||||
        (ADJCTL3, 0xA9, 0x51, 0x2C, 0x82),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
ST7796 = DriverChip(
 | 
			
		||||
    "ST7796",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (SWRESET,),
 | 
			
		||||
        (CSCON, 0xC3),
 | 
			
		||||
        (CSCON, 0x96),
 | 
			
		||||
        (VMCTR1, 0x1C),
 | 
			
		||||
        (IFMODE, 0x80),
 | 
			
		||||
        (INVCTR, 0x01),
 | 
			
		||||
        (DFUNCTR, 0x80, 0x02, 0x3B),
 | 
			
		||||
        (ETMOD, 0xC6),
 | 
			
		||||
        (CSCON, 0x69),
 | 
			
		||||
        (CSCON, 0x3C),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "S3BOX",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=240,
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    mirror_y=True,
 | 
			
		||||
    invert_colors=False,
 | 
			
		||||
    data_rate="40MHz",
 | 
			
		||||
    dc_pin=4,
 | 
			
		||||
    cs_pin=5,
 | 
			
		||||
    # reset_pin={CONF_INVERTED: True, CONF_NUMBER: 48},
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xEF, 0x03, 0x80, 0x02),
 | 
			
		||||
        (0xCF, 0x00, 0xC1, 0x30),
 | 
			
		||||
        (0xED, 0x64, 0x03, 0x12, 0x81),
 | 
			
		||||
        (0xE8, 0x85, 0x00, 0x78),
 | 
			
		||||
        (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02),
 | 
			
		||||
        (0xF7, 0x20),
 | 
			
		||||
        (0xEA, 0x00, 0x00),
 | 
			
		||||
        (PWCTR1, 0x23),
 | 
			
		||||
        (PWCTR2, 0x10),
 | 
			
		||||
        (VMCTR1, 0x3E, 0x28),
 | 
			
		||||
        (VMCTR2, 0x86),
 | 
			
		||||
        (VSCRSADD, 0x00),
 | 
			
		||||
        (FRMCTR1, 0x00, 0x18),
 | 
			
		||||
        (DFUNCTR, 0x08, 0x82, 0x27),
 | 
			
		||||
        (0xF2, 0x00),
 | 
			
		||||
        (GAMMASET, 0x01),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x2B,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x4E,
 | 
			
		||||
            0xF1,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0xC1,
 | 
			
		||||
            0x48,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x36,
 | 
			
		||||
            0x0F,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "S3BOXLITE",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=240,
 | 
			
		||||
    cs_pin=5,
 | 
			
		||||
    dc_pin=4,
 | 
			
		||||
    reset_pin=48,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xEF, 0x03, 0x80, 0x02),
 | 
			
		||||
        (0xCF, 0x00, 0xC1, 0x30),
 | 
			
		||||
        (0xED, 0x64, 0x03, 0x12, 0x81),
 | 
			
		||||
        (0xE8, 0x85, 0x00, 0x78),
 | 
			
		||||
        (0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02),
 | 
			
		||||
        (0xF7, 0x20),
 | 
			
		||||
        (0xEA, 0x00, 0x00),
 | 
			
		||||
        (PWCTR1, 0x23),
 | 
			
		||||
        (PWCTR2, 0x10),
 | 
			
		||||
        (VMCTR1, 0x3E, 0x28),
 | 
			
		||||
        (VMCTR2, 0x86),
 | 
			
		||||
        (VSCRSADD, 0x00),
 | 
			
		||||
        (FRMCTR1, 0x00, 0x18),
 | 
			
		||||
        (DFUNCTR, 0x08, 0x82, 0x27),
 | 
			
		||||
        (0xF2, 0x00),
 | 
			
		||||
        (GAMMASET, 0x01),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0xF0,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x15,
 | 
			
		||||
            0x2F,
 | 
			
		||||
            0x54,
 | 
			
		||||
            0x42,
 | 
			
		||||
            0x3C,
 | 
			
		||||
            0x17,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x1B,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0xE0,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x2B,
 | 
			
		||||
            0x43,
 | 
			
		||||
            0x42,
 | 
			
		||||
            0x3B,
 | 
			
		||||
            0x16,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x17,
 | 
			
		||||
            0x1B,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
ST7789V = DriverChip(
 | 
			
		||||
    "ST7789V",
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=320,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (DFUNCTR, 0x0A, 0x82),
 | 
			
		||||
        (FRMCTR2, 0x0C, 0x0C, 0x00, 0x33, 0x33),
 | 
			
		||||
        (ETMOD, 0x35),
 | 
			
		||||
        (0xBB, 0x28),
 | 
			
		||||
        (PWCTR1, 0x0C),
 | 
			
		||||
        (PWCTR3, 0x01, 0xFF),
 | 
			
		||||
        (PWCTR4, 0x10),
 | 
			
		||||
        (PWCTR5, 0x20),
 | 
			
		||||
        (IFCTR, 0x0F),
 | 
			
		||||
        (PWSET, 0xA4, 0xA1),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0xD0,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x28,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x44,
 | 
			
		||||
            0x42,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x12,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x17,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0xD0,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x28,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x54,
 | 
			
		||||
            0x47,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x1C,
 | 
			
		||||
            0x17,
 | 
			
		||||
            0x1B,
 | 
			
		||||
            0x1E,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "GC9A01A",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=240,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xEF,),
 | 
			
		||||
        (0xEB, 0x14),
 | 
			
		||||
        (0xFE,),
 | 
			
		||||
        (0xEF,),
 | 
			
		||||
        (0xEB, 0x14),
 | 
			
		||||
        (0x84, 0x40),
 | 
			
		||||
        (0x85, 0xFF),
 | 
			
		||||
        (0x86, 0xFF),
 | 
			
		||||
        (0x87, 0xFF),
 | 
			
		||||
        (0x88, 0x0A),
 | 
			
		||||
        (0x89, 0x21),
 | 
			
		||||
        (0x8A, 0x00),
 | 
			
		||||
        (0x8B, 0x80),
 | 
			
		||||
        (0x8C, 0x01),
 | 
			
		||||
        (0x8D, 0x01),
 | 
			
		||||
        (0x8E, 0xFF),
 | 
			
		||||
        (0x8F, 0xFF),
 | 
			
		||||
        (0xB6, 0x00, 0x00),
 | 
			
		||||
        (0x90, 0x08, 0x08, 0x08, 0x08),
 | 
			
		||||
        (0xBD, 0x06),
 | 
			
		||||
        (0xBC, 0x00),
 | 
			
		||||
        (0xFF, 0x60, 0x01, 0x04),
 | 
			
		||||
        (0xC3, 0x13),
 | 
			
		||||
        (0xC4, 0x13),
 | 
			
		||||
        (0xF9, 0x22),
 | 
			
		||||
        (0xBE, 0x11),
 | 
			
		||||
        (0xE1, 0x10, 0x0E),
 | 
			
		||||
        (0xDF, 0x21, 0x0C, 0x02),
 | 
			
		||||
        (0xF0, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A),
 | 
			
		||||
        (0xF1, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F),
 | 
			
		||||
        (0xF2, 0x45, 0x09, 0x08, 0x08, 0x26, 0x2A),
 | 
			
		||||
        (0xF3, 0x43, 0x70, 0x72, 0x36, 0x37, 0x6F),
 | 
			
		||||
        (0xED, 0x1B, 0x0B),
 | 
			
		||||
        (0xAE, 0x77),
 | 
			
		||||
        (0xCD, 0x63),
 | 
			
		||||
        (0xE8, 0x34),
 | 
			
		||||
        (
 | 
			
		||||
            0x62,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x71,
 | 
			
		||||
            0xED,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x71,
 | 
			
		||||
            0xEF,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x70,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0x63,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x71,
 | 
			
		||||
            0xF1,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x13,
 | 
			
		||||
            0x71,
 | 
			
		||||
            0xF3,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x70,
 | 
			
		||||
        ),
 | 
			
		||||
        (0x64, 0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07),
 | 
			
		||||
        (0x66, 0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00),
 | 
			
		||||
        (0x67, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98),
 | 
			
		||||
        (0x74, 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00),
 | 
			
		||||
        (0x98, 0x3E, 0x07),
 | 
			
		||||
        (0x35,),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "GC9D01N",
 | 
			
		||||
    width=160,
 | 
			
		||||
    height=160,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xFE,),
 | 
			
		||||
        (0xEF,),
 | 
			
		||||
        (0x80, 0xFF),
 | 
			
		||||
        (0x81, 0xFF),
 | 
			
		||||
        (0x82, 0xFF),
 | 
			
		||||
        (0x83, 0xFF),
 | 
			
		||||
        (0x84, 0xFF),
 | 
			
		||||
        (0x85, 0xFF),
 | 
			
		||||
        (0x86, 0xFF),
 | 
			
		||||
        (0x87, 0xFF),
 | 
			
		||||
        (0x88, 0xFF),
 | 
			
		||||
        (0x89, 0xFF),
 | 
			
		||||
        (0x8A, 0xFF),
 | 
			
		||||
        (0x8B, 0xFF),
 | 
			
		||||
        (0x8C, 0xFF),
 | 
			
		||||
        (0x8D, 0xFF),
 | 
			
		||||
        (0x8E, 0xFF),
 | 
			
		||||
        (0x8F, 0xFF),
 | 
			
		||||
        (0x3A, 0x05),
 | 
			
		||||
        (0xEC, 0x01),
 | 
			
		||||
        (0x74, 0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00),
 | 
			
		||||
        (0x98, 0x3E),
 | 
			
		||||
        (0x99, 0x3E),
 | 
			
		||||
        (0xB5, 0x0D, 0x0D),
 | 
			
		||||
        (0x60, 0x38, 0x0F, 0x79, 0x67),
 | 
			
		||||
        (0x61, 0x38, 0x11, 0x79, 0x67),
 | 
			
		||||
        (0x64, 0x38, 0x17, 0x71, 0x5F, 0x79, 0x67),
 | 
			
		||||
        (0x65, 0x38, 0x13, 0x71, 0x5B, 0x79, 0x67),
 | 
			
		||||
        (0x6A, 0x00, 0x00),
 | 
			
		||||
        (0x6C, 0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50),
 | 
			
		||||
        (
 | 
			
		||||
            0x6E,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x01,
 | 
			
		||||
            0x01,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x0D,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x0E,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x10,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x04,
 | 
			
		||||
        ),
 | 
			
		||||
        (0xBF, 0x01),
 | 
			
		||||
        (0xF9, 0x40),
 | 
			
		||||
        (0x9B, 0x3B, 0x93, 0x33, 0x7F, 0x00),
 | 
			
		||||
        (0x7E, 0x30),
 | 
			
		||||
        (0x70, 0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08),
 | 
			
		||||
        (0x71, 0x0D, 0x02, 0x08),
 | 
			
		||||
        (0x91, 0x0E, 0x09),
 | 
			
		||||
        (0xC3, 0x19, 0xC4, 0x19, 0xC9, 0x3C),
 | 
			
		||||
        (0xF0, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E),
 | 
			
		||||
        (0xF1, 0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F),
 | 
			
		||||
        (0xF2, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A),
 | 
			
		||||
        (0xF3, 0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "ST7735",
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    width=128,
 | 
			
		||||
    height=160,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        SWRESET,
 | 
			
		||||
        delay(10),
 | 
			
		||||
        (FRMCTR1, 0x01, 0x2C, 0x2D),
 | 
			
		||||
        (FRMCTR2, 0x01, 0x2C, 0x2D),
 | 
			
		||||
        (FRMCTR3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D),
 | 
			
		||||
        (INVCTR, 0x07),
 | 
			
		||||
        (PWCTR1, 0xA2, 0x02, 0x84),
 | 
			
		||||
        (PWCTR2, 0xC5),
 | 
			
		||||
        (PWCTR3, 0x0A, 0x00),
 | 
			
		||||
        (PWCTR4, 0x8A, 0x2A),
 | 
			
		||||
        (PWCTR5, 0x8A, 0xEE),
 | 
			
		||||
        (VMCTR1, 0x0E),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRP1,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x1C,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x12,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x29,
 | 
			
		||||
            0x2D,
 | 
			
		||||
            0x29,
 | 
			
		||||
            0x25,
 | 
			
		||||
            0x2B,
 | 
			
		||||
            0x39,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x01,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x10,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            GMCTRN1,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x1D,
 | 
			
		||||
            0x07,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x2C,
 | 
			
		||||
            0x29,
 | 
			
		||||
            0x2D,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x2E,
 | 
			
		||||
            0x37,
 | 
			
		||||
            0x3F,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x10,
 | 
			
		||||
        ),
 | 
			
		||||
        NORON,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
ST7796.extend(
 | 
			
		||||
    "WT32-SC01-PLUS",
 | 
			
		||||
    bus_mode=TYPE_OCTAL,
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    reset_pin=4,
 | 
			
		||||
    dc_pin=0,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										260
									
								
								esphome/components/mipi_spi/models/jc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								esphome/components/mipi_spi/models/jc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
from esphome.components.spi import TYPE_QUAD
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_IGNORE_STRAPPING_WARNING, CONF_NUMBER
 | 
			
		||||
 | 
			
		||||
from .. import MODE_RGB
 | 
			
		||||
from . import DriverChip
 | 
			
		||||
 | 
			
		||||
AXS15231 = DriverChip(
 | 
			
		||||
    "AXS15231",
 | 
			
		||||
    draw_rounding=8,
 | 
			
		||||
    swap_xy=cv.UNDEFINED,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    bus_mode=TYPE_QUAD,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5),
 | 
			
		||||
        (0xC1, 0x33),
 | 
			
		||||
        (0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AXS15231.extend(
 | 
			
		||||
    "JC3248W535",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    cs_pin={CONF_NUMBER: 45, CONF_IGNORE_STRAPPING_WARNING: True},
 | 
			
		||||
    data_rate="40MHz",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "JC3636W518",
 | 
			
		||||
    height=360,
 | 
			
		||||
    width=360,
 | 
			
		||||
    offset_height=1,
 | 
			
		||||
    draw_rounding=1,
 | 
			
		||||
    cs_pin=10,
 | 
			
		||||
    reset_pin=47,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
    color_order=MODE_RGB,
 | 
			
		||||
    bus_mode=TYPE_QUAD,
 | 
			
		||||
    data_rate="40MHz",
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (0xF0, 0x08),
 | 
			
		||||
        (0xF2, 0x08),
 | 
			
		||||
        (0x9B, 0x51),
 | 
			
		||||
        (0x86, 0x53),
 | 
			
		||||
        (0xF2, 0x80),
 | 
			
		||||
        (0xF0, 0x00),
 | 
			
		||||
        (0xF0, 0x01),
 | 
			
		||||
        (0xF1, 0x01),
 | 
			
		||||
        (0xB0, 0x54),
 | 
			
		||||
        (0xB1, 0x3F),
 | 
			
		||||
        (0xB2, 0x2A),
 | 
			
		||||
        (0xB4, 0x46),
 | 
			
		||||
        (0xB5, 0x34),
 | 
			
		||||
        (0xB6, 0xD5),
 | 
			
		||||
        (0xB7, 0x30),
 | 
			
		||||
        (0xBA, 0x00),
 | 
			
		||||
        (0xBB, 0x08),
 | 
			
		||||
        (0xBC, 0x08),
 | 
			
		||||
        (0xBD, 0x00),
 | 
			
		||||
        (0xC0, 0x80),
 | 
			
		||||
        (0xC1, 0x10),
 | 
			
		||||
        (0xC2, 0x37),
 | 
			
		||||
        (0xC3, 0x80),
 | 
			
		||||
        (0xC4, 0x10),
 | 
			
		||||
        (0xC5, 0x37),
 | 
			
		||||
        (0xC6, 0xA9),
 | 
			
		||||
        (0xC7, 0x41),
 | 
			
		||||
        (0xC8, 0x51),
 | 
			
		||||
        (0xC9, 0xA9),
 | 
			
		||||
        (0xCA, 0x41),
 | 
			
		||||
        (0xCB, 0x51),
 | 
			
		||||
        (0xD0, 0x91),
 | 
			
		||||
        (0xD1, 0x68),
 | 
			
		||||
        (0xD2, 0x69),
 | 
			
		||||
        (0xF5, 0x00, 0xA5),
 | 
			
		||||
        (0xDD, 0x3F),
 | 
			
		||||
        (0xDE, 0x3F),
 | 
			
		||||
        (0xF1, 0x10),
 | 
			
		||||
        (0xF0, 0x00),
 | 
			
		||||
        (0xF0, 0x02),
 | 
			
		||||
        (
 | 
			
		||||
            0xE0,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x12,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x27,
 | 
			
		||||
            0x38,
 | 
			
		||||
            0x54,
 | 
			
		||||
            0x4E,
 | 
			
		||||
            0x19,
 | 
			
		||||
            0x15,
 | 
			
		||||
            0x15,
 | 
			
		||||
            0x2C,
 | 
			
		||||
            0x2F,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xE1,
 | 
			
		||||
            0x70,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x11,
 | 
			
		||||
            0x0C,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x27,
 | 
			
		||||
            0x38,
 | 
			
		||||
            0x43,
 | 
			
		||||
            0x4C,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x14,
 | 
			
		||||
            0x2B,
 | 
			
		||||
            0x2D,
 | 
			
		||||
        ),
 | 
			
		||||
        (0xF0, 0x10),
 | 
			
		||||
        (0xF3, 0x10),
 | 
			
		||||
        (0xE0, 0x08),
 | 
			
		||||
        (0xE1, 0x00),
 | 
			
		||||
        (0xE2, 0x00),
 | 
			
		||||
        (0xE3, 0x00),
 | 
			
		||||
        (0xE4, 0xE0),
 | 
			
		||||
        (0xE5, 0x06),
 | 
			
		||||
        (0xE6, 0x21),
 | 
			
		||||
        (0xE7, 0x00),
 | 
			
		||||
        (0xE8, 0x05),
 | 
			
		||||
        (0xE9, 0x82),
 | 
			
		||||
        (0xEA, 0xDF),
 | 
			
		||||
        (0xEB, 0x89),
 | 
			
		||||
        (0xEC, 0x20),
 | 
			
		||||
        (0xED, 0x14),
 | 
			
		||||
        (0xEE, 0xFF),
 | 
			
		||||
        (0xEF, 0x00),
 | 
			
		||||
        (0xF8, 0xFF),
 | 
			
		||||
        (0xF9, 0x00),
 | 
			
		||||
        (0xFA, 0x00),
 | 
			
		||||
        (0xFB, 0x30),
 | 
			
		||||
        (0xFC, 0x00),
 | 
			
		||||
        (0xFD, 0x00),
 | 
			
		||||
        (0xFE, 0x00),
 | 
			
		||||
        (0xFF, 0x00),
 | 
			
		||||
        (0x60, 0x42),
 | 
			
		||||
        (0x61, 0xE0),
 | 
			
		||||
        (0x62, 0x40),
 | 
			
		||||
        (0x63, 0x40),
 | 
			
		||||
        (0x64, 0x02),
 | 
			
		||||
        (0x65, 0x00),
 | 
			
		||||
        (0x66, 0x40),
 | 
			
		||||
        (0x67, 0x03),
 | 
			
		||||
        (0x68, 0x00),
 | 
			
		||||
        (0x69, 0x00),
 | 
			
		||||
        (0x6A, 0x00),
 | 
			
		||||
        (0x6B, 0x00),
 | 
			
		||||
        (0x70, 0x42),
 | 
			
		||||
        (0x71, 0xE0),
 | 
			
		||||
        (0x72, 0x40),
 | 
			
		||||
        (0x73, 0x40),
 | 
			
		||||
        (0x74, 0x02),
 | 
			
		||||
        (0x75, 0x00),
 | 
			
		||||
        (0x76, 0x40),
 | 
			
		||||
        (0x77, 0x03),
 | 
			
		||||
        (0x78, 0x00),
 | 
			
		||||
        (0x79, 0x00),
 | 
			
		||||
        (0x7A, 0x00),
 | 
			
		||||
        (0x7B, 0x00),
 | 
			
		||||
        (0x80, 0x48),
 | 
			
		||||
        (0x81, 0x00),
 | 
			
		||||
        (0x82, 0x05),
 | 
			
		||||
        (0x83, 0x02),
 | 
			
		||||
        (0x84, 0xDD),
 | 
			
		||||
        (0x85, 0x00),
 | 
			
		||||
        (0x86, 0x00),
 | 
			
		||||
        (0x87, 0x00),
 | 
			
		||||
        (0x88, 0x48),
 | 
			
		||||
        (0x89, 0x00),
 | 
			
		||||
        (0x8A, 0x07),
 | 
			
		||||
        (0x8B, 0x02),
 | 
			
		||||
        (0x8C, 0xDF),
 | 
			
		||||
        (0x8D, 0x00),
 | 
			
		||||
        (0x8E, 0x00),
 | 
			
		||||
        (0x8F, 0x00),
 | 
			
		||||
        (0x90, 0x48),
 | 
			
		||||
        (0x91, 0x00),
 | 
			
		||||
        (0x92, 0x09),
 | 
			
		||||
        (0x93, 0x02),
 | 
			
		||||
        (0x94, 0xE1),
 | 
			
		||||
        (0x95, 0x00),
 | 
			
		||||
        (0x96, 0x00),
 | 
			
		||||
        (0x97, 0x00),
 | 
			
		||||
        (0x98, 0x48),
 | 
			
		||||
        (0x99, 0x00),
 | 
			
		||||
        (0x9A, 0x0B),
 | 
			
		||||
        (0x9B, 0x02),
 | 
			
		||||
        (0x9C, 0xE3),
 | 
			
		||||
        (0x9D, 0x00),
 | 
			
		||||
        (0x9E, 0x00),
 | 
			
		||||
        (0x9F, 0x00),
 | 
			
		||||
        (0xA0, 0x48),
 | 
			
		||||
        (0xA1, 0x00),
 | 
			
		||||
        (0xA2, 0x04),
 | 
			
		||||
        (0xA3, 0x02),
 | 
			
		||||
        (0xA4, 0xDC),
 | 
			
		||||
        (0xA5, 0x00),
 | 
			
		||||
        (0xA6, 0x00),
 | 
			
		||||
        (0xA7, 0x00),
 | 
			
		||||
        (0xA8, 0x48),
 | 
			
		||||
        (0xA9, 0x00),
 | 
			
		||||
        (0xAA, 0x06),
 | 
			
		||||
        (0xAB, 0x02),
 | 
			
		||||
        (0xAC, 0xDE),
 | 
			
		||||
        (0xAD, 0x00),
 | 
			
		||||
        (0xAE, 0x00),
 | 
			
		||||
        (0xAF, 0x00),
 | 
			
		||||
        (0xB0, 0x48),
 | 
			
		||||
        (0xB1, 0x00),
 | 
			
		||||
        (0xB2, 0x08),
 | 
			
		||||
        (0xB3, 0x02),
 | 
			
		||||
        (0xB4, 0xE0),
 | 
			
		||||
        (0xB5, 0x00),
 | 
			
		||||
        (0xB6, 0x00),
 | 
			
		||||
        (0xB7, 0x00),
 | 
			
		||||
        (0xB8, 0x48),
 | 
			
		||||
        (0xB9, 0x00),
 | 
			
		||||
        (0xBA, 0x0A),
 | 
			
		||||
        (0xBB, 0x02),
 | 
			
		||||
        (0xBC, 0xE2),
 | 
			
		||||
        (0xBD, 0x00),
 | 
			
		||||
        (0xBE, 0x00),
 | 
			
		||||
        (0xBF, 0x00),
 | 
			
		||||
        (0xC0, 0x12),
 | 
			
		||||
        (0xC1, 0xAA),
 | 
			
		||||
        (0xC2, 0x65),
 | 
			
		||||
        (0xC3, 0x74),
 | 
			
		||||
        (0xC4, 0x47),
 | 
			
		||||
        (0xC5, 0x56),
 | 
			
		||||
        (0xC6, 0x00),
 | 
			
		||||
        (0xC7, 0x88),
 | 
			
		||||
        (0xC8, 0x99),
 | 
			
		||||
        (0xC9, 0x33),
 | 
			
		||||
        (0xD0, 0x21),
 | 
			
		||||
        (0xD1, 0xAA),
 | 
			
		||||
        (0xD2, 0x65),
 | 
			
		||||
        (0xD3, 0x74),
 | 
			
		||||
        (0xD4, 0x47),
 | 
			
		||||
        (0xD5, 0x56),
 | 
			
		||||
        (0xD6, 0x00),
 | 
			
		||||
        (0xD7, 0x88),
 | 
			
		||||
        (0xD8, 0x99),
 | 
			
		||||
        (0xD9, 0x33),
 | 
			
		||||
        (0xF3, 0x01),
 | 
			
		||||
        (0xF0, 0x00),
 | 
			
		||||
        (0xF0, 0x01),
 | 
			
		||||
        (0xF1, 0x01),
 | 
			
		||||
        (0xA0, 0x0B),
 | 
			
		||||
        (0xA3, 0x2A),
 | 
			
		||||
        (0xA5, 0xC3),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										15
									
								
								esphome/components/mipi_spi/models/lanbon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								esphome/components/mipi_spi/models/lanbon.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
from .ili import ST7789V
 | 
			
		||||
 | 
			
		||||
ST7789V.extend(
 | 
			
		||||
    "LANBON-L8",
 | 
			
		||||
    width=240,
 | 
			
		||||
    height=320,
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    mirror_y=True,
 | 
			
		||||
    data_rate="80MHz",
 | 
			
		||||
    cs_pin=22,
 | 
			
		||||
    dc_pin=21,
 | 
			
		||||
    reset_pin=18,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										60
									
								
								esphome/components/mipi_spi/models/lilygo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								esphome/components/mipi_spi/models/lilygo.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
from esphome.components.spi import TYPE_OCTAL
 | 
			
		||||
 | 
			
		||||
from .. import MODE_BGR
 | 
			
		||||
from .ili import ST7789V, ST7796
 | 
			
		||||
 | 
			
		||||
ST7789V.extend(
 | 
			
		||||
    "T-EMBED",
 | 
			
		||||
    width=170,
 | 
			
		||||
    height=320,
 | 
			
		||||
    offset_width=35,
 | 
			
		||||
    color_order=MODE_BGR,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
    draw_rounding=1,
 | 
			
		||||
    cs_pin=10,
 | 
			
		||||
    dc_pin=13,
 | 
			
		||||
    reset_pin=9,
 | 
			
		||||
    data_rate="80MHz",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ST7789V.extend(
 | 
			
		||||
    "T-DISPLAY",
 | 
			
		||||
    height=240,
 | 
			
		||||
    width=135,
 | 
			
		||||
    offset_width=52,
 | 
			
		||||
    offset_height=40,
 | 
			
		||||
    draw_rounding=1,
 | 
			
		||||
    cs_pin=5,
 | 
			
		||||
    dc_pin=16,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
)
 | 
			
		||||
ST7789V.extend(
 | 
			
		||||
    "T-DISPLAY-S3",
 | 
			
		||||
    height=320,
 | 
			
		||||
    width=170,
 | 
			
		||||
    offset_width=35,
 | 
			
		||||
    color_order=MODE_BGR,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
    draw_rounding=1,
 | 
			
		||||
    dc_pin=7,
 | 
			
		||||
    cs_pin=6,
 | 
			
		||||
    reset_pin=5,
 | 
			
		||||
    enable_pin=[9, 15],
 | 
			
		||||
    data_rate="10MHz",
 | 
			
		||||
    bus_mode=TYPE_OCTAL,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ST7796.extend(
 | 
			
		||||
    "T-DISPLAY-S3-PRO",
 | 
			
		||||
    width=222,
 | 
			
		||||
    height=480,
 | 
			
		||||
    offset_width=49,
 | 
			
		||||
    draw_rounding=1,
 | 
			
		||||
    cs_pin=39,
 | 
			
		||||
    reset_pin=47,
 | 
			
		||||
    dc_pin=9,
 | 
			
		||||
    backlight_pin=48,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
models = {}
 | 
			
		||||
							
								
								
									
										139
									
								
								esphome/components/mipi_spi/models/waveshare.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								esphome/components/mipi_spi/models/waveshare.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
from . import DriverChip
 | 
			
		||||
from .ili import ILI9488_A
 | 
			
		||||
 | 
			
		||||
DriverChip(
 | 
			
		||||
    "WAVESHARE-4-TFT",
 | 
			
		||||
    width=320,
 | 
			
		||||
    height=480,
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
    spi_16=True,
 | 
			
		||||
    initsequence=(
 | 
			
		||||
        (
 | 
			
		||||
            0xF9,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x08,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xC0,
 | 
			
		||||
            0x19,
 | 
			
		||||
            0x1A,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xC1,
 | 
			
		||||
            0x45,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xC2,
 | 
			
		||||
            0x33,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xC5,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x28,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xB1,
 | 
			
		||||
            0xA0,
 | 
			
		||||
            0x11,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xB4,
 | 
			
		||||
            0x02,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xB6,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x42,
 | 
			
		||||
            0x3B,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xB7,
 | 
			
		||||
            0x07,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xE0,
 | 
			
		||||
            0x1F,
 | 
			
		||||
            0x25,
 | 
			
		||||
            0x22,
 | 
			
		||||
            0x0B,
 | 
			
		||||
            0x06,
 | 
			
		||||
            0x0A,
 | 
			
		||||
            0x4E,
 | 
			
		||||
            0xC6,
 | 
			
		||||
            0x39,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xE1,
 | 
			
		||||
            0x1F,
 | 
			
		||||
            0x3F,
 | 
			
		||||
            0x3F,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x1F,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x46,
 | 
			
		||||
            0x49,
 | 
			
		||||
            0x31,
 | 
			
		||||
            0x05,
 | 
			
		||||
            0x09,
 | 
			
		||||
            0x03,
 | 
			
		||||
            0x1C,
 | 
			
		||||
            0x1A,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xF1,
 | 
			
		||||
            0x36,
 | 
			
		||||
            0x04,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x3C,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0x0F,
 | 
			
		||||
            0xA4,
 | 
			
		||||
            0x02,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xF2,
 | 
			
		||||
            0x18,
 | 
			
		||||
            0xA3,
 | 
			
		||||
            0x12,
 | 
			
		||||
            0x02,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x12,
 | 
			
		||||
            0xFF,
 | 
			
		||||
            0x32,
 | 
			
		||||
            0x00,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xF4,
 | 
			
		||||
            0x40,
 | 
			
		||||
            0x00,
 | 
			
		||||
            0x08,
 | 
			
		||||
            0x91,
 | 
			
		||||
            0x04,
 | 
			
		||||
        ),
 | 
			
		||||
        (
 | 
			
		||||
            0xF8,
 | 
			
		||||
            0x21,
 | 
			
		||||
            0x04,
 | 
			
		||||
        ),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ILI9488_A.extend(
 | 
			
		||||
    "PICO-RESTOUCH-LCD-3.5",
 | 
			
		||||
    spi_16=True,
 | 
			
		||||
    pixel_mode="16bit",
 | 
			
		||||
    mirror_x=True,
 | 
			
		||||
    dc_pin=33,
 | 
			
		||||
    cs_pin=34,
 | 
			
		||||
    reset_pin=40,
 | 
			
		||||
    data_rate="20MHz",
 | 
			
		||||
    invert_colors=True,
 | 
			
		||||
)
 | 
			
		||||
@@ -64,6 +64,8 @@ constexpr const char *const MQTT_DEVICE_NAME = "name";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw";
 | 
			
		||||
constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "dir_cmd_t";
 | 
			
		||||
constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "dir_stat_t";
 | 
			
		||||
constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl";
 | 
			
		||||
constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
 | 
			
		||||
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t";
 | 
			
		||||
@@ -328,6 +330,8 @@ constexpr const char *const MQTT_DEVICE_NAME = "name";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version";
 | 
			
		||||
constexpr const char *const MQTT_DEVICE_HW_VERSION = "hw_version";
 | 
			
		||||
constexpr const char *const MQTT_DIRECTION_COMMAND_TOPIC = "direction_command_topic";
 | 
			
		||||
constexpr const char *const MQTT_DIRECTION_STATE_TOPIC = "direction_state_topic";
 | 
			
		||||
constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template";
 | 
			
		||||
constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
 | 
			
		||||
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic";
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,32 @@ void MQTTFanComponent::setup() {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (this->state_->get_traits().supports_direction()) {
 | 
			
		||||
    this->subscribe(this->get_direction_command_topic(), [this](const std::string &topic, const std::string &payload) {
 | 
			
		||||
      auto val = parse_on_off(payload.c_str(), "forward", "reverse");
 | 
			
		||||
      switch (val) {
 | 
			
		||||
        case PARSE_ON:
 | 
			
		||||
          ESP_LOGD(TAG, "'%s': Setting direction FORWARD", this->friendly_name().c_str());
 | 
			
		||||
          this->state_->make_call().set_direction(fan::FanDirection::FORWARD).perform();
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_OFF:
 | 
			
		||||
          ESP_LOGD(TAG, "'%s': Setting direction REVERSE", this->friendly_name().c_str());
 | 
			
		||||
          this->state_->make_call().set_direction(fan::FanDirection::REVERSE).perform();
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_TOGGLE:
 | 
			
		||||
          this->state_->make_call()
 | 
			
		||||
              .set_direction(this->state_->direction == fan::FanDirection::FORWARD ? fan::FanDirection::REVERSE
 | 
			
		||||
                                                                                   : fan::FanDirection::FORWARD)
 | 
			
		||||
              .perform();
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_NONE:
 | 
			
		||||
          ESP_LOGW(TAG, "Unknown direction Payload %s", payload.c_str());
 | 
			
		||||
          this->status_momentary_warning("direction", 5000);
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->state_->get_traits().supports_oscillation()) {
 | 
			
		||||
    this->subscribe(this->get_oscillation_command_topic(),
 | 
			
		||||
                    [this](const std::string &topic, const std::string &payload) {
 | 
			
		||||
@@ -94,6 +120,10 @@ void MQTTFanComponent::setup() {
 | 
			
		||||
void MQTTFanComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MQTT Fan '%s': ", this->state_->get_name().c_str());
 | 
			
		||||
  LOG_MQTT_COMPONENT(true, true);
 | 
			
		||||
  if (this->state_->get_traits().supports_direction()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Direction State Topic: '%s'", this->get_direction_state_topic().c_str());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Direction Command Topic: '%s'", this->get_direction_command_topic().c_str());
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_->get_traits().supports_oscillation()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Oscillation State Topic: '%s'", this->get_oscillation_state_topic().c_str());
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Oscillation Command Topic: '%s'", this->get_oscillation_command_topic().c_str());
 | 
			
		||||
@@ -107,6 +137,10 @@ void MQTTFanComponent::dump_config() {
 | 
			
		||||
bool MQTTFanComponent::send_initial_state() { return this->publish_state(); }
 | 
			
		||||
 | 
			
		||||
void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
 | 
			
		||||
  if (this->state_->get_traits().supports_direction()) {
 | 
			
		||||
    root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic();
 | 
			
		||||
    root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_->get_traits().supports_oscillation()) {
 | 
			
		||||
    root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
 | 
			
		||||
    root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();
 | 
			
		||||
@@ -122,6 +156,11 @@ bool MQTTFanComponent::publish_state() {
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' Sending state %s.", this->state_->get_name().c_str(), state_s);
 | 
			
		||||
  this->publish(this->get_state_topic_(), state_s);
 | 
			
		||||
  bool failed = false;
 | 
			
		||||
  if (this->state_->get_traits().supports_direction()) {
 | 
			
		||||
    bool success = this->publish(this->get_direction_state_topic(),
 | 
			
		||||
                                 this->state_->direction == fan::FanDirection::FORWARD ? "forward" : "reverse");
 | 
			
		||||
    failed = failed || !success;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_->get_traits().supports_oscillation()) {
 | 
			
		||||
    bool success = this->publish(this->get_oscillation_state_topic(),
 | 
			
		||||
                                 this->state_->oscillating ? "oscillate_on" : "oscillate_off");
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user