mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Merge branch 'dev' into jesserockz-2023-304
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,7 +8,7 @@ on:
 | 
				
			|||||||
    branches: [dev, beta, release]
 | 
					    branches: [dev, beta, release]
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - "docker/**"
 | 
					      - "docker/**"
 | 
				
			||||||
      - ".github/workflows/**"
 | 
					      - ".github/workflows/ci-docker.yml"
 | 
				
			||||||
      - "requirements*.txt"
 | 
					      - "requirements*.txt"
 | 
				
			||||||
      - "platformio.ini"
 | 
					      - "platformio.ini"
 | 
				
			||||||
      - "script/platformio_install_deps.py"
 | 
					      - "script/platformio_install_deps.py"
 | 
				
			||||||
@@ -16,7 +16,7 @@ on:
 | 
				
			|||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - "docker/**"
 | 
					      - "docker/**"
 | 
				
			||||||
      - ".github/workflows/**"
 | 
					      - ".github/workflows/ci-docker.yml"
 | 
				
			||||||
      - "requirements*.txt"
 | 
					      - "requirements*.txt"
 | 
				
			||||||
      - "platformio.ini"
 | 
					      - "platformio.ini"
 | 
				
			||||||
      - "script/platformio_install_deps.py"
 | 
					      - "script/platformio_install_deps.py"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -41,7 +41,7 @@ jobs:
 | 
				
			|||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
      - name: Restore Python virtual environment
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
        id: cache-venv
 | 
					        id: cache-venv
 | 
				
			||||||
        uses: actions/cache@v3.3.1
 | 
					        uses: actions/cache@v3.3.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: venv
 | 
					          path: venv
 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
@@ -232,7 +232,7 @@ jobs:
 | 
				
			|||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      max-parallel: 2
 | 
					      max-parallel: 2
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 9, 9.1]
 | 
					        file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 9, 9.1, 10]
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
@@ -302,7 +302,7 @@ jobs:
 | 
				
			|||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        uses: actions/cache@v3.3.1
 | 
					        uses: actions/cache@v3.3.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,12 @@ __pycache__/
 | 
				
			|||||||
# Intellij Idea
 | 
					# Intellij Idea
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Eclipse
 | 
				
			||||||
 | 
					.project
 | 
				
			||||||
 | 
					.cproject
 | 
				
			||||||
 | 
					.pydevproject
 | 
				
			||||||
 | 
					.settings/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Vim
 | 
					# Vim
 | 
				
			||||||
*.swp
 | 
					*.swp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
# See https://pre-commit.com for more information
 | 
					# See https://pre-commit.com for more information
 | 
				
			||||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
					# See https://pre-commit.com/hooks.html for more hooks
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/psf/black
 | 
					  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
				
			||||||
    rev: 23.7.0
 | 
					    rev: 23.7.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: black
 | 
					      - id: black
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,7 @@ esphome/components/bl0942/* @dbuezas
 | 
				
			|||||||
esphome/components/ble_client/* @buxtronix
 | 
					esphome/components/ble_client/* @buxtronix
 | 
				
			||||||
esphome/components/bluetooth_proxy/* @jesserockz
 | 
					esphome/components/bluetooth_proxy/* @jesserockz
 | 
				
			||||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
					esphome/components/bme680_bsec/* @trvrnrth
 | 
				
			||||||
 | 
					esphome/components/bmi160/* @flaviut
 | 
				
			||||||
esphome/components/bmp3xx/* @martgras
 | 
					esphome/components/bmp3xx/* @martgras
 | 
				
			||||||
esphome/components/bmp581/* @kahrendt
 | 
					esphome/components/bmp581/* @kahrendt
 | 
				
			||||||
esphome/components/bp1658cj/* @Cossid
 | 
					esphome/components/bp1658cj/* @Cossid
 | 
				
			||||||
@@ -270,6 +271,8 @@ esphome/components/socket/* @esphome/core
 | 
				
			|||||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
					esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
				
			||||||
esphome/components/speaker/* @jesserockz
 | 
					esphome/components/speaker/* @jesserockz
 | 
				
			||||||
esphome/components/spi/* @esphome/core
 | 
					esphome/components/spi/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/spi_device/* @clydebarrow
 | 
				
			||||||
 | 
					esphome/components/spi_led_strip/* @clydebarrow
 | 
				
			||||||
esphome/components/sprinkler/* @kbx81
 | 
					esphome/components/sprinkler/* @kbx81
 | 
				
			||||||
esphome/components/sps30/* @martgras
 | 
					esphome/components/sps30/* @martgras
 | 
				
			||||||
esphome/components/ssd1322_base/* @kbx81
 | 
					esphome/components/ssd1322_base/* @kbx81
 | 
				
			||||||
@@ -330,6 +333,7 @@ esphome/components/web_server_idf/* @dentra
 | 
				
			|||||||
esphome/components/whirlpool/* @glmnet
 | 
					esphome/components/whirlpool/* @glmnet
 | 
				
			||||||
esphome/components/whynter/* @aeonsablaze
 | 
					esphome/components/whynter/* @aeonsablaze
 | 
				
			||||||
esphome/components/wiegand/* @ssieb
 | 
					esphome/components/wiegand/* @ssieb
 | 
				
			||||||
 | 
					esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
 | 
				
			||||||
esphome/components/wl_134/* @hobbypunk90
 | 
					esphome/components/wl_134/* @hobbypunk90
 | 
				
			||||||
esphome/components/x9c/* @EtienneMD
 | 
					esphome/components/x9c/* @EtienneMD
 | 
				
			||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
					esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,11 +35,16 @@ if bashio::config.has_value 'default_compile_process_limit'; then
 | 
				
			|||||||
    export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
 | 
					    export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
    if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
 | 
					    if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
 | 
				
			||||||
        export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1;
 | 
					        export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir -p "${pio_cache_base}"
 | 
					mkdir -p "${pio_cache_base}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if bashio::fs.directory_exists '/config/esphome/.esphome'; then
 | 
				
			||||||
 | 
					    bashio::log.info "Removing old .esphome directory..."
 | 
				
			||||||
 | 
					    rm -rf /config/esphome/.esphome
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bashio::log.info "Starting ESPHome dashboard..."
 | 
					bashio::log.info "Starting ESPHome dashboard..."
 | 
				
			||||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
 | 
					exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,6 +85,8 @@ def choose_upload_log_host(
 | 
				
			|||||||
    options = []
 | 
					    options = []
 | 
				
			||||||
    for port in get_serial_ports():
 | 
					    for port in get_serial_ports():
 | 
				
			||||||
        options.append((f"{port.path} ({port.description})", port.path))
 | 
					        options.append((f"{port.path} ({port.description})", port.path))
 | 
				
			||||||
 | 
					    if default == "SERIAL":
 | 
				
			||||||
 | 
					        return choose_prompt(options, purpose=purpose)
 | 
				
			||||||
    if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
 | 
					    if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
 | 
				
			||||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
					        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
@@ -218,14 +220,16 @@ def compile_program(args, config):
 | 
				
			|||||||
    return 0 if idedata is not None else 1
 | 
					    return 0 if idedata is not None else 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_using_esptool(config, port):
 | 
					def upload_using_esptool(config, port, file):
 | 
				
			||||||
    from esphome import platformio_api
 | 
					    from esphome import platformio_api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
					    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
				
			||||||
        "upload_speed", 460800
 | 
					        "upload_speed", 460800
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run_esptool(baud_rate):
 | 
					    if file is not None:
 | 
				
			||||||
 | 
					        flash_images = [platformio_api.FlashImage(path=file, offset="0x0")]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
        idedata = platformio_api.get_idedata(config)
 | 
					        idedata = platformio_api.get_idedata(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
 | 
					        firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
 | 
				
			||||||
@@ -236,12 +240,13 @@ def upload_using_esptool(config, port):
 | 
				
			|||||||
            *idedata.extra_flash_images,
 | 
					            *idedata.extra_flash_images,
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mcu = "esp8266"
 | 
					    mcu = "esp8266"
 | 
				
			||||||
        if CORE.is_esp32:
 | 
					    if CORE.is_esp32:
 | 
				
			||||||
            from esphome.components.esp32 import get_esp32_variant
 | 
					        from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mcu = get_esp32_variant().lower()
 | 
					        mcu = get_esp32_variant().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run_esptool(baud_rate):
 | 
				
			||||||
        cmd = [
 | 
					        cmd = [
 | 
				
			||||||
            "esptool.py",
 | 
					            "esptool.py",
 | 
				
			||||||
            "--before",
 | 
					            "--before",
 | 
				
			||||||
@@ -292,7 +297,8 @@ def upload_using_platformio(config, port):
 | 
				
			|||||||
def upload_program(config, args, host):
 | 
					def upload_program(config, args, host):
 | 
				
			||||||
    if get_port_type(host) == "SERIAL":
 | 
					    if get_port_type(host) == "SERIAL":
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
					        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
				
			||||||
            return upload_using_esptool(config, host)
 | 
					            file = getattr(args, "file", None)
 | 
				
			||||||
 | 
					            return upload_using_esptool(config, host, file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_RP2040):
 | 
					        if CORE.target_platform in (PLATFORM_RP2040):
 | 
				
			||||||
            return upload_using_platformio(config, args.device)
 | 
					            return upload_using_platformio(config, args.device)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,10 @@ from esphome.const import CONF_ANALOG, CONF_INPUT
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from esphome.core import CORE
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.components.esp32 import get_esp32_variant
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    PLATFORM_ESP8266,
 | 
				
			||||||
 | 
					    PLATFORM_RP2040,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from esphome.components.esp32.const import (
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
    VARIANT_ESP32,
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
    VARIANT_ESP32C2,
 | 
					    VARIANT_ESP32C2,
 | 
				
			||||||
@@ -143,7 +147,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def validate_adc_pin(value):
 | 
					def validate_adc_pin(value):
 | 
				
			||||||
    if str(value).upper() == "VCC":
 | 
					    if str(value).upper() == "VCC":
 | 
				
			||||||
        return cv.only_on_esp8266("VCC")
 | 
					        return cv.only_on([PLATFORM_ESP8266, PLATFORM_RP2040])("VCC")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if str(value).upper() == "TEMPERATURE":
 | 
					    if str(value).upper() == "TEMPERATURE":
 | 
				
			||||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
					        return cv.only_on_rp2040("TEMPERATURE")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC)
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_RP2040
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					#ifdef CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					#include "pico/cyw43_arch.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#include <hardware/adc.h>
 | 
					#include <hardware/adc.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,13 +126,19 @@ void ADCSensor::dump_config() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_RP2040
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
  if (this->is_temperature_) {
 | 
					  if (this->is_temperature_) {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
					    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
    LOG_PIN("  Pin: ", pin_);
 | 
					    LOG_PIN("  Pin: ", pin_);
 | 
				
			||||||
 | 
					#endif  // USE_ADC_SENSOR_VCC
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif  // USE_RP2040
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -238,7 +247,20 @@ float ADCSensor::sample() {
 | 
				
			|||||||
    delay(1);
 | 
					    delay(1);
 | 
				
			||||||
    adc_select_input(4);
 | 
					    adc_select_input(4);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    uint8_t pin = this->pin_->get_pin();
 | 
					    uint8_t pin;
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					#ifdef CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					    // Measuring VSYS on Raspberry Pico W needs to be wrapped with
 | 
				
			||||||
 | 
					    // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
 | 
				
			||||||
 | 
					    // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
 | 
				
			||||||
 | 
					    // VSYS ADC both share GPIO29
 | 
				
			||||||
 | 
					    cyw43_thread_enter();
 | 
				
			||||||
 | 
					#endif  // CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					    pin = PICO_VSYS_PIN;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    pin = this->pin_->get_pin();
 | 
				
			||||||
 | 
					#endif  // USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    adc_gpio_init(pin);
 | 
					    adc_gpio_init(pin);
 | 
				
			||||||
    adc_select_input(pin - 26);
 | 
					    adc_select_input(pin - 26);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -246,11 +268,23 @@ float ADCSensor::sample() {
 | 
				
			|||||||
  int32_t raw = adc_read();
 | 
					  int32_t raw = adc_read();
 | 
				
			||||||
  if (this->is_temperature_) {
 | 
					  if (this->is_temperature_) {
 | 
				
			||||||
    adc_set_temp_sensor_enabled(false);
 | 
					    adc_set_temp_sensor_enabled(false);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					#ifdef CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					    cyw43_thread_exit();
 | 
				
			||||||
 | 
					#endif  // CYW43_USES_VSYS_PIN
 | 
				
			||||||
 | 
					#endif  // USE_ADC_SENSOR_VCC
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (output_raw_) {
 | 
					  if (output_raw_) {
 | 
				
			||||||
    return raw;
 | 
					    return raw;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return raw * 3.3f / 4096.0f;
 | 
					  float coeff = 1.0;
 | 
				
			||||||
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					  // As per Raspberry Pico (W) datasheet (section 2.1) the VSYS/3 is measured
 | 
				
			||||||
 | 
					  coeff = 3.0;
 | 
				
			||||||
 | 
					#endif  // USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					  return raw * 3.3f / 4096.0f * coeff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_REACTIVE_POWER,
 | 
					    CONF_REACTIVE_POWER,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
    CONF_CURRENT,
 | 
					    CONF_CURRENT,
 | 
				
			||||||
 | 
					    CONF_PHASE_A,
 | 
				
			||||||
 | 
					    CONF_PHASE_B,
 | 
				
			||||||
 | 
					    CONF_PHASE_C,
 | 
				
			||||||
    CONF_POWER,
 | 
					    CONF_POWER,
 | 
				
			||||||
    CONF_POWER_FACTOR,
 | 
					    CONF_POWER_FACTOR,
 | 
				
			||||||
    CONF_FREQUENCY,
 | 
					    CONF_FREQUENCY,
 | 
				
			||||||
@@ -31,10 +34,6 @@ from esphome.const import (
 | 
				
			|||||||
    UNIT_WATT_HOURS,
 | 
					    UNIT_WATT_HOURS,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_PHASE_A = "phase_a"
 | 
					 | 
				
			||||||
CONF_PHASE_B = "phase_b"
 | 
					 | 
				
			||||||
CONF_PHASE_C = "phase_c"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CONF_LINE_FREQUENCY = "line_frequency"
 | 
					CONF_LINE_FREQUENCY = "line_frequency"
 | 
				
			||||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
 | 
					CONF_CHIP_TEMPERATURE = "chip_temperature"
 | 
				
			||||||
CONF_GAIN_PGA = "gain_pga"
 | 
					CONF_GAIN_PGA = "gain_pga"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								esphome/components/bmi160/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bmi160/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@flaviut"]
 | 
				
			||||||
							
								
								
									
										270
									
								
								esphome/components/bmi160/bmi160.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								esphome/components/bmi160/bmi160.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,270 @@
 | 
				
			|||||||
 | 
					#include "bmi160.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bmi160 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "bmi160";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_CHIPID = 0x00;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_CMD = 0x7E;
 | 
				
			||||||
 | 
					enum class Cmd : uint8_t {
 | 
				
			||||||
 | 
					  START_FOC = 0x03,
 | 
				
			||||||
 | 
					  ACCL_SET_PMU_MODE = 0b00010000,  // last 2 bits are mode
 | 
				
			||||||
 | 
					  GYRO_SET_PMU_MODE = 0b00010100,  // last 2 bits are mode
 | 
				
			||||||
 | 
					  MAG_SET_PMU_MODE = 0b00011000,   // last 2 bits are mode
 | 
				
			||||||
 | 
					  PROG_NVM = 0xA0,
 | 
				
			||||||
 | 
					  FIFO_FLUSH = 0xB0,
 | 
				
			||||||
 | 
					  INT_RESET = 0xB1,
 | 
				
			||||||
 | 
					  SOFT_RESET = 0xB6,
 | 
				
			||||||
 | 
					  STEP_CNT_CLR = 0xB2,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class GyroPmuMode : uint8_t {
 | 
				
			||||||
 | 
					  SUSPEND = 0b00,
 | 
				
			||||||
 | 
					  NORMAL = 0b01,
 | 
				
			||||||
 | 
					  LOW_POWER = 0b10,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class AcclPmuMode : uint8_t {
 | 
				
			||||||
 | 
					  SUSPEND = 0b00,
 | 
				
			||||||
 | 
					  NORMAL = 0b01,
 | 
				
			||||||
 | 
					  FAST_STARTUP = 0b11,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class MagPmuMode : uint8_t {
 | 
				
			||||||
 | 
					  SUSPEND = 0b00,
 | 
				
			||||||
 | 
					  NORMAL = 0b01,
 | 
				
			||||||
 | 
					  LOW_POWER = 0b10,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_ACCEL_CONFIG = 0x40;
 | 
				
			||||||
 | 
					enum class AcclFilterMode : uint8_t {
 | 
				
			||||||
 | 
					  POWER_SAVING = 0b00000000,
 | 
				
			||||||
 | 
					  PERF = 0b10000000,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class AcclBandwidth : uint8_t {
 | 
				
			||||||
 | 
					  OSR4_AVG1 = 0b00000000,
 | 
				
			||||||
 | 
					  OSR2_AVG2 = 0b00010000,
 | 
				
			||||||
 | 
					  NORMAL_AVG4 = 0b00100000,
 | 
				
			||||||
 | 
					  RES_AVG8 = 0b00110000,
 | 
				
			||||||
 | 
					  RES_AVG16 = 0b01000000,
 | 
				
			||||||
 | 
					  RES_AVG32 = 0b01010000,
 | 
				
			||||||
 | 
					  RES_AVG64 = 0b01100000,
 | 
				
			||||||
 | 
					  RES_AVG128 = 0b01110000,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class AccelOutputDataRate : uint8_t {
 | 
				
			||||||
 | 
					  HZ_25_32 = 0b0001,  // 25/32 Hz
 | 
				
			||||||
 | 
					  HZ_25_16 = 0b0010,  // 25/16 Hz
 | 
				
			||||||
 | 
					  HZ_25_8 = 0b0011,   // 25/8 Hz
 | 
				
			||||||
 | 
					  HZ_25_4 = 0b0100,   // 25/4 Hz
 | 
				
			||||||
 | 
					  HZ_25_2 = 0b0101,   // 25/2 Hz
 | 
				
			||||||
 | 
					  HZ_25 = 0b0110,     // 25 Hz
 | 
				
			||||||
 | 
					  HZ_50 = 0b0111,     // 50 Hz
 | 
				
			||||||
 | 
					  HZ_100 = 0b1000,    // 100 Hz
 | 
				
			||||||
 | 
					  HZ_200 = 0b1001,    // 200 Hz
 | 
				
			||||||
 | 
					  HZ_400 = 0b1010,    // 400 Hz
 | 
				
			||||||
 | 
					  HZ_800 = 0b1011,    // 800 Hz
 | 
				
			||||||
 | 
					  HZ_1600 = 0b1100,   // 1600 Hz
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_ACCEL_RANGE = 0x41;
 | 
				
			||||||
 | 
					enum class AccelRange : uint8_t {
 | 
				
			||||||
 | 
					  RANGE_2G = 0b0011,
 | 
				
			||||||
 | 
					  RANGE_4G = 0b0101,
 | 
				
			||||||
 | 
					  RANGE_8G = 0b1000,
 | 
				
			||||||
 | 
					  RANGE_16G = 0b1100,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_GYRO_CONFIG = 0x42;
 | 
				
			||||||
 | 
					enum class GyroBandwidth : uint8_t {
 | 
				
			||||||
 | 
					  OSR4 = 0x00,
 | 
				
			||||||
 | 
					  OSR2 = 0x10,
 | 
				
			||||||
 | 
					  NORMAL = 0x20,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					enum class GyroOuputDataRate : uint8_t {
 | 
				
			||||||
 | 
					  HZ_25 = 0x06,
 | 
				
			||||||
 | 
					  HZ_50 = 0x07,
 | 
				
			||||||
 | 
					  HZ_100 = 0x08,
 | 
				
			||||||
 | 
					  HZ_200 = 0x09,
 | 
				
			||||||
 | 
					  HZ_400 = 0x0A,
 | 
				
			||||||
 | 
					  HZ_800 = 0x0B,
 | 
				
			||||||
 | 
					  HZ_1600 = 0x0C,
 | 
				
			||||||
 | 
					  HZ_3200 = 0x0D,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_GYRO_RANGE = 0x43;
 | 
				
			||||||
 | 
					enum class GyroRange : uint8_t {
 | 
				
			||||||
 | 
					  RANGE_2000_DPS = 0x0,  // ±2000 °/s
 | 
				
			||||||
 | 
					  RANGE_1000_DPS = 0x1,
 | 
				
			||||||
 | 
					  RANGE_500_DPS = 0x2,
 | 
				
			||||||
 | 
					  RANGE_250_DPS = 0x3,
 | 
				
			||||||
 | 
					  RANGE_125_DPS = 0x4,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_X_LSB = 0x0C;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_X_MSB = 0x0D;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_Y_LSB = 0x0E;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_Y_MSB = 0x0F;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_Z_LSB = 0x10;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_GYRO_Z_MSB = 0x11;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_X_LSB = 0x12;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_X_MSB = 0x13;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_LSB = 0x14;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_Y_MSB = 0x15;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_LSB = 0x16;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_ACCEL_Z_MSB = 0x17;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_TEMP_LSB = 0x20;
 | 
				
			||||||
 | 
					const uint8_t BMI160_REGISTER_DATA_TEMP_MSB = 0x21;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const float GRAVITY_EARTH = 9.80665f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BMI160Component::internal_setup_(int stage) {
 | 
				
			||||||
 | 
					  switch (stage) {
 | 
				
			||||||
 | 
					    case 0:
 | 
				
			||||||
 | 
					      ESP_LOGCONFIG(TAG, "Setting up BMI160...");
 | 
				
			||||||
 | 
					      uint8_t chipid;
 | 
				
			||||||
 | 
					      if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Bringing accelerometer out of sleep...");
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::ACCL_SET_PMU_MODE | (uint8_t) AcclPmuMode::NORMAL)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Waiting for accelerometer to wake up...");
 | 
				
			||||||
 | 
					      // need to wait (max delay in datasheet) because we can't send commands while another is in progress
 | 
				
			||||||
 | 
					      // min 5ms, 10ms
 | 
				
			||||||
 | 
					      this->set_timeout(10, [this]() { this->internal_setup_(1); });
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Bringing gyroscope out of sleep...");
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_CMD, (uint8_t) Cmd::GYRO_SET_PMU_MODE | (uint8_t) GyroPmuMode::NORMAL)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Waiting for gyroscope to wake up...");
 | 
				
			||||||
 | 
					      // wait between 51 & 81ms, doing 100 to be safe
 | 
				
			||||||
 | 
					      this->set_timeout(10, [this]() { this->internal_setup_(2); });
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 2:
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Setting up Gyro Config...");
 | 
				
			||||||
 | 
					      uint8_t gyro_config = (uint8_t) GyroBandwidth::OSR4 | (uint8_t) GyroOuputDataRate::HZ_25;
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config));
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_GYRO_CONFIG, gyro_config)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Setting up Gyro Range...");
 | 
				
			||||||
 | 
					      uint8_t gyro_range = (uint8_t) GyroRange::RANGE_2000_DPS;
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Output gyro_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_range));
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_GYRO_RANGE, gyro_range)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Setting up Accel Config...");
 | 
				
			||||||
 | 
					      uint8_t accel_config =
 | 
				
			||||||
 | 
					          (uint8_t) AcclFilterMode::PERF | (uint8_t) AcclBandwidth::RES_AVG16 | (uint8_t) AccelOutputDataRate::HZ_25;
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config));
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_ACCEL_CONFIG, accel_config)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Setting up Accel Range...");
 | 
				
			||||||
 | 
					      uint8_t accel_range = (uint8_t) AccelRange::RANGE_16G;
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "  Output accel_range: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_range));
 | 
				
			||||||
 | 
					      if (!this->write_byte(BMI160_REGISTER_ACCEL_RANGE, accel_range)) {
 | 
				
			||||||
 | 
					        this->mark_failed();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->setup_complete_ = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BMI160Component::setup() { this->internal_setup_(0); }
 | 
				
			||||||
 | 
					void BMI160Component::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "BMI160:");
 | 
				
			||||||
 | 
					  LOG_I2C_DEVICE(this);
 | 
				
			||||||
 | 
					  if (this->is_failed()) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Communication with BMI160 failed!");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Acceleration X", this->accel_x_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Acceleration Y", this->accel_y_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Acceleration Z", this->accel_z_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Gyro X", this->gyro_x_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Gyro Y", this->gyro_y_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Gyro Z", this->gyro_z_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
 | 
				
			||||||
 | 
					  uint8_t raw_data[len * 2];
 | 
				
			||||||
 | 
					  // read using read_register because we have little-endian data, and read_bytes_16 will swap it
 | 
				
			||||||
 | 
					  i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
 | 
				
			||||||
 | 
					  if (err != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    return err;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (int i = 0; i < len; i++) {
 | 
				
			||||||
 | 
					    value[i] = (int16_t) ((uint16_t) raw_data[i * 2] | ((uint16_t) raw_data[i * 2 + 1] << 8));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return err;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BMI160Component::update() {
 | 
				
			||||||
 | 
					  if (!this->setup_complete_) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "    Updating BMI160...");
 | 
				
			||||||
 | 
					  int16_t data[6];
 | 
				
			||||||
 | 
					  if (this->read_le_int16_(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6) != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float gyro_x = (float) data[0] / (float) INT16_MAX * 2000.f;
 | 
				
			||||||
 | 
					  float gyro_y = (float) data[1] / (float) INT16_MAX * 2000.f;
 | 
				
			||||||
 | 
					  float gyro_z = (float) data[2] / (float) INT16_MAX * 2000.f;
 | 
				
			||||||
 | 
					  float accel_x = (float) data[3] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
 | 
				
			||||||
 | 
					  float accel_y = (float) data[4] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
 | 
				
			||||||
 | 
					  float accel_z = (float) data[5] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int16_t raw_temperature;
 | 
				
			||||||
 | 
					  if (this->read_le_int16_(BMI160_REGISTER_DATA_TEMP_LSB, &raw_temperature, 1) != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  float temperature = (float) raw_temperature / (float) INT16_MAX * 64.5f + 23.f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG,
 | 
				
			||||||
 | 
					           "Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, "
 | 
				
			||||||
 | 
					           "gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C",
 | 
				
			||||||
 | 
					           accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->accel_x_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->accel_x_sensor_->publish_state(accel_x);
 | 
				
			||||||
 | 
					  if (this->accel_y_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->accel_y_sensor_->publish_state(accel_y);
 | 
				
			||||||
 | 
					  if (this->accel_z_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->accel_z_sensor_->publish_state(accel_z);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->temperature_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->temperature_sensor_->publish_state(temperature);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->gyro_x_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->gyro_x_sensor_->publish_state(gyro_x);
 | 
				
			||||||
 | 
					  if (this->gyro_y_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->gyro_y_sensor_->publish_state(gyro_y);
 | 
				
			||||||
 | 
					  if (this->gyro_z_sensor_ != nullptr)
 | 
				
			||||||
 | 
					    this->gyro_z_sensor_->publish_state(gyro_z);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->status_clear_warning();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					float BMI160Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bmi160
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										44
									
								
								esphome/components/bmi160/bmi160.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/bmi160/bmi160.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bmi160 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BMI160Component : public PollingComponent, public i2c::I2CDevice {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; }
 | 
				
			||||||
 | 
					  void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; }
 | 
				
			||||||
 | 
					  void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; }
 | 
				
			||||||
 | 
					  void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
 | 
				
			||||||
 | 
					  void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; }
 | 
				
			||||||
 | 
					  void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; }
 | 
				
			||||||
 | 
					  void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  sensor::Sensor *accel_x_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *accel_y_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *accel_z_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *temperature_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *gyro_x_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *gyro_y_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *gyro_z_sensor_{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void internal_setup_(int stage);
 | 
				
			||||||
 | 
					  bool setup_complete_{false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** reads `len` 16-bit little-endian integers from the given i2c register */
 | 
				
			||||||
 | 
					  i2c::ErrorCode read_le_int16_(uint8_t reg, int16_t *value, uint8_t len);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bmi160
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										102
									
								
								esphome/components/bmi160/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								esphome/components/bmi160/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
 | 
					    CONF_ACCELERATION_X,
 | 
				
			||||||
 | 
					    CONF_ACCELERATION_Y,
 | 
				
			||||||
 | 
					    CONF_ACCELERATION_Z,
 | 
				
			||||||
 | 
					    CONF_GYROSCOPE_X,
 | 
				
			||||||
 | 
					    CONF_GYROSCOPE_Y,
 | 
				
			||||||
 | 
					    CONF_GYROSCOPE_Z,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_METER_PER_SECOND_SQUARED,
 | 
				
			||||||
 | 
					    ICON_ACCELERATION_X,
 | 
				
			||||||
 | 
					    ICON_ACCELERATION_Y,
 | 
				
			||||||
 | 
					    ICON_ACCELERATION_Z,
 | 
				
			||||||
 | 
					    ICON_GYROSCOPE_X,
 | 
				
			||||||
 | 
					    ICON_GYROSCOPE_Y,
 | 
				
			||||||
 | 
					    ICON_GYROSCOPE_Z,
 | 
				
			||||||
 | 
					    UNIT_DEGREE_PER_SECOND,
 | 
				
			||||||
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bmi160_ns = cg.esphome_ns.namespace("bmi160")
 | 
				
			||||||
 | 
					BMI160Component = bmi160_ns.class_(
 | 
				
			||||||
 | 
					    "BMI160Component", cg.PollingComponent, i2c.I2CDevice
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					accel_schema = {
 | 
				
			||||||
 | 
					    "unit_of_measurement": UNIT_METER_PER_SECOND_SQUARED,
 | 
				
			||||||
 | 
					    "accuracy_decimals": 2,
 | 
				
			||||||
 | 
					    "state_class": STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					gyro_schema = {
 | 
				
			||||||
 | 
					    "unit_of_measurement": UNIT_DEGREE_PER_SECOND,
 | 
				
			||||||
 | 
					    "accuracy_decimals": 2,
 | 
				
			||||||
 | 
					    "state_class": STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(BMI160Component),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACCELERATION_X): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_ACCELERATION_X,
 | 
				
			||||||
 | 
					                **accel_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACCELERATION_Y): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_ACCELERATION_Y,
 | 
				
			||||||
 | 
					                **accel_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACCELERATION_Z): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_ACCELERATION_Z,
 | 
				
			||||||
 | 
					                **accel_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_GYROSCOPE_X): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_GYROSCOPE_X,
 | 
				
			||||||
 | 
					                **gyro_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_GYROSCOPE_Y): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_GYROSCOPE_Y,
 | 
				
			||||||
 | 
					                **gyro_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_GYROSCOPE_Z): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                icon=ICON_GYROSCOPE_Z,
 | 
				
			||||||
 | 
					                **gyro_schema,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
 | 
					    .extend(i2c.i2c_device_schema(0x68))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await i2c.register_i2c_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for d in ["x", "y", "z"]:
 | 
				
			||||||
 | 
					        accel_key = f"acceleration_{d}"
 | 
				
			||||||
 | 
					        if accel_key in config:
 | 
				
			||||||
 | 
					            sens = await sensor.new_sensor(config[accel_key])
 | 
				
			||||||
 | 
					            cg.add(getattr(var, f"set_accel_{d}_sensor")(sens))
 | 
				
			||||||
 | 
					        accel_key = f"gyroscope_{d}"
 | 
				
			||||||
 | 
					        if accel_key in config:
 | 
				
			||||||
 | 
					            sens = await sensor.new_sensor(config[accel_key])
 | 
				
			||||||
 | 
					            cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_TEMPERATURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_temperature_sensor(sens))
 | 
				
			||||||
@@ -17,6 +17,8 @@
 | 
				
			|||||||
#include <esp32/rom/rtc.h>
 | 
					#include <esp32/rom/rtc.h>
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
#include <esp32c3/rom/rtc.h>
 | 
					#include <esp32c3/rom/rtc.h>
 | 
				
			||||||
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
				
			||||||
 | 
					#include <esp32c6/rom/rtc.h>
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
				
			||||||
#include <esp32s2/rom/rtc.h>
 | 
					#include <esp32s2/rom/rtc.h>
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
@@ -119,6 +121,8 @@ void DebugComponent::dump_config() {
 | 
				
			|||||||
  model = "ESP32";
 | 
					  model = "ESP32";
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
  model = "ESP32-C3";
 | 
					  model = "ESP32-C3";
 | 
				
			||||||
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
				
			||||||
 | 
					  model = "ESP32-C6";
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
				
			||||||
  model = "ESP32-S2";
 | 
					  model = "ESP32-S2";
 | 
				
			||||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
@@ -202,9 +206,11 @@ void DebugComponent::dump_config() {
 | 
				
			|||||||
    case RTCWDT_SYS_RESET:
 | 
					    case RTCWDT_SYS_RESET:
 | 
				
			||||||
      reset_reason = "RTC Watch Dog Reset Digital Core";
 | 
					      reset_reason = "RTC Watch Dog Reset Digital Core";
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C6)
 | 
				
			||||||
    case INTRUSION_RESET:
 | 
					    case INTRUSION_RESET:
 | 
				
			||||||
      reset_reason = "Intrusion Reset CPU";
 | 
					      reset_reason = "Intrusion Reset CPU";
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
					#if defined(USE_ESP32_VARIANT_ESP32)
 | 
				
			||||||
    case TGWDT_CPU_RESET:
 | 
					    case TGWDT_CPU_RESET:
 | 
				
			||||||
      reset_reason = "Timer Group Reset CPU";
 | 
					      reset_reason = "Timer Group Reset CPU";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_IGNORE_EFUSE_MAC_CRC,
 | 
					    CONF_IGNORE_EFUSE_MAC_CRC,
 | 
				
			||||||
    KEY_CORE,
 | 
					    KEY_CORE,
 | 
				
			||||||
    KEY_FRAMEWORK_VERSION,
 | 
					    KEY_FRAMEWORK_VERSION,
 | 
				
			||||||
 | 
					    KEY_NAME,
 | 
				
			||||||
    KEY_TARGET_FRAMEWORK,
 | 
					    KEY_TARGET_FRAMEWORK,
 | 
				
			||||||
    KEY_TARGET_PLATFORM,
 | 
					    KEY_TARGET_PLATFORM,
 | 
				
			||||||
    TYPE_GIT,
 | 
					    TYPE_GIT,
 | 
				
			||||||
@@ -37,6 +38,7 @@ from .const import (  # noqa
 | 
				
			|||||||
    KEY_BOARD,
 | 
					    KEY_BOARD,
 | 
				
			||||||
    KEY_COMPONENTS,
 | 
					    KEY_COMPONENTS,
 | 
				
			||||||
    KEY_ESP32,
 | 
					    KEY_ESP32,
 | 
				
			||||||
 | 
					    KEY_EXTRA_BUILD_FILES,
 | 
				
			||||||
    KEY_PATH,
 | 
					    KEY_PATH,
 | 
				
			||||||
    KEY_REF,
 | 
					    KEY_REF,
 | 
				
			||||||
    KEY_REFRESH,
 | 
					    KEY_REFRESH,
 | 
				
			||||||
@@ -73,6 +75,8 @@ def set_core_data(config):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
 | 
					    CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
 | 
				
			||||||
    CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
 | 
					    CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
 | 
				
			||||||
 | 
					    CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return config
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -166,6 +170,24 @@ def add_idf_component(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_extra_script(stage: str, filename: str, path: str):
 | 
				
			||||||
 | 
					    """Add an extra script to the project."""
 | 
				
			||||||
 | 
					    key = f"{stage}:{filename}"
 | 
				
			||||||
 | 
					    if add_extra_build_file(filename, path):
 | 
				
			||||||
 | 
					        cg.add_platformio_option("extra_scripts", [key])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_extra_build_file(filename: str, path: str) -> bool:
 | 
				
			||||||
 | 
					    """Add an extra build file to the project."""
 | 
				
			||||||
 | 
					    if filename not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
 | 
				
			||||||
 | 
					        CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES][filename] = {
 | 
				
			||||||
 | 
					            KEY_NAME: filename,
 | 
				
			||||||
 | 
					            KEY_PATH: path,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
 | 
					def _format_framework_arduino_version(ver: cv.Version) -> str:
 | 
				
			||||||
    # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
 | 
					    # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
 | 
				
			||||||
    # a PIO platformio/framework-arduinoespressif32 value
 | 
					    # a PIO platformio/framework-arduinoespressif32 value
 | 
				
			||||||
@@ -390,7 +412,11 @@ async def to_code(config):
 | 
				
			|||||||
    conf = config[CONF_FRAMEWORK]
 | 
					    conf = config[CONF_FRAMEWORK]
 | 
				
			||||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
					    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
 | 
					    add_extra_script(
 | 
				
			||||||
 | 
					        "post",
 | 
				
			||||||
 | 
					        "post_build2.py",
 | 
				
			||||||
 | 
					        os.path.join(os.path.dirname(__file__), "post_build.py.script"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
 | 
					    if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
 | 
				
			||||||
        cg.add_platformio_option("framework", "espidf")
 | 
					        cg.add_platformio_option("framework", "espidf")
 | 
				
			||||||
@@ -604,9 +630,15 @@ def copy_files():
 | 
				
			|||||||
                        ignore_dangling_symlinks=True,
 | 
					                        ignore_dangling_symlinks=True,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dir = os.path.dirname(__file__)
 | 
					    for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
 | 
				
			||||||
    post_build_file = os.path.join(dir, "post_build.py.script")
 | 
					        if file[KEY_PATH].startswith("http"):
 | 
				
			||||||
    copy_file_if_changed(
 | 
					            import requests
 | 
				
			||||||
        post_build_file,
 | 
					
 | 
				
			||||||
        CORE.relative_build_path("post_build.py"),
 | 
					            mkdir_p(CORE.relative_build_path(os.path.dirname(file[KEY_NAME])))
 | 
				
			||||||
    )
 | 
					            with open(CORE.relative_build_path(file[KEY_NAME]), "wb") as f:
 | 
				
			||||||
 | 
					                f.write(requests.get(file[KEY_PATH], timeout=30).content)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            copy_file_if_changed(
 | 
				
			||||||
 | 
					                file[KEY_PATH],
 | 
				
			||||||
 | 
					                CORE.relative_build_path(file[KEY_NAME]),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ KEY_REF = "ref"
 | 
				
			|||||||
KEY_REFRESH = "refresh"
 | 
					KEY_REFRESH = "refresh"
 | 
				
			||||||
KEY_PATH = "path"
 | 
					KEY_PATH = "path"
 | 
				
			||||||
KEY_SUBMODULES = "submodules"
 | 
					KEY_SUBMODULES = "submodules"
 | 
				
			||||||
 | 
					KEY_EXTRA_BUILD_FILES = "extra_build_files"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VARIANT_ESP32 = "ESP32"
 | 
					VARIANT_ESP32 = "ESP32"
 | 
				
			||||||
VARIANT_ESP32S2 = "ESP32S2"
 | 
					VARIANT_ESP32S2 = "ESP32S2"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,7 +53,11 @@ void arch_init() {
 | 
				
			|||||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
 | 
					void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
 | 
					uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
 | 
				
			||||||
 | 
					#if ESP_IDF_VERSION_MAJOR >= 5
 | 
				
			||||||
 | 
					uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
 | 
					uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); }
 | 
					uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,10 +98,9 @@ def validate_truetype_file(value):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _compute_local_font_dir(name) -> Path:
 | 
					def _compute_local_font_dir(name) -> Path:
 | 
				
			||||||
    base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
 | 
					 | 
				
			||||||
    h = hashlib.new("sha256")
 | 
					    h = hashlib.new("sha256")
 | 
				
			||||||
    h.update(name.encode())
 | 
					    h.update(name.encode())
 | 
				
			||||||
    return base_dir / h.hexdigest()[:8]
 | 
					    return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _compute_gfonts_local_path(value) -> Path:
 | 
					def _compute_gfonts_local_path(value) -> Path:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,14 @@ CODEOWNERS = ["@esphome/core"]
 | 
				
			|||||||
globals_ns = cg.esphome_ns.namespace("globals")
 | 
					globals_ns = cg.esphome_ns.namespace("globals")
 | 
				
			||||||
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
 | 
					GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
 | 
				
			||||||
RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
 | 
					RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
 | 
				
			||||||
 | 
					RestoringGlobalStringComponent = globals_ns.class_(
 | 
				
			||||||
 | 
					    "RestoringGlobalStringComponent", cg.Component
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
 | 
					GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MULTI_CONF = True
 | 
					MULTI_CONF = True
 | 
				
			||||||
CONFIG_SCHEMA = cv.Schema(
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
        cv.Required(CONF_TYPE): cv.string_strict,
 | 
					        cv.Required(CONF_TYPE): cv.string_strict,
 | 
				
			||||||
        cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
 | 
					        cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
 | 
				
			||||||
        cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
 | 
					        cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
).extend(cv.COMPONENT_SCHEMA)
 | 
					).extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
@coroutine_with_priority(-100.0)
 | 
					@coroutine_with_priority(-100.0)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    type_ = cg.RawExpression(config[CONF_TYPE])
 | 
					    type_ = cg.RawExpression(config[CONF_TYPE])
 | 
				
			||||||
    template_args = cg.TemplateArguments(type_)
 | 
					 | 
				
			||||||
    restore = config[CONF_RESTORE_VALUE]
 | 
					    restore = config[CONF_RESTORE_VALUE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    type = RestoringGlobalsComponent if restore else GlobalsComponent
 | 
					    # Special casing the strings to their own class with a different save/restore mechanism
 | 
				
			||||||
    res_type = type.template(template_args)
 | 
					    if str(type_) == "std::string" and restore:
 | 
				
			||||||
 | 
					        template_args = cg.TemplateArguments(
 | 
				
			||||||
 | 
					            type_, config.get(CONF_MAX_RESTORE_DATA_LENGTH, 63) + 1
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        type = RestoringGlobalStringComponent
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        template_args = cg.TemplateArguments(type_)
 | 
				
			||||||
 | 
					        type = RestoringGlobalsComponent if restore else GlobalsComponent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res_type = type.template(template_args)
 | 
				
			||||||
    initial_value = None
 | 
					    initial_value = None
 | 
				
			||||||
    if CONF_INITIAL_VALUE in config:
 | 
					    if CONF_INITIAL_VALUE in config:
 | 
				
			||||||
        initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE])
 | 
					        initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,6 +65,64 @@ template<typename T> class RestoringGlobalsComponent : public Component {
 | 
				
			|||||||
  ESPPreferenceObject rtc_;
 | 
					  ESPPreferenceObject rtc_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Use with string or subclasses of strings
 | 
				
			||||||
 | 
					template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public Component {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  using value_type = T;
 | 
				
			||||||
 | 
					  explicit RestoringGlobalStringComponent() = default;
 | 
				
			||||||
 | 
					  explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; }
 | 
				
			||||||
 | 
					  explicit RestoringGlobalStringComponent(
 | 
				
			||||||
 | 
					      std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
 | 
				
			||||||
 | 
					    memcpy(this->value_, initial_value.data(), sizeof(T));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  T &value() { return this->value_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void setup() override {
 | 
				
			||||||
 | 
					    char temp[SZ];
 | 
				
			||||||
 | 
					    this->rtc_ = global_preferences->make_preference<uint8_t[SZ]>(1944399030U ^ this->name_hash_);
 | 
				
			||||||
 | 
					    bool hasdata = this->rtc_.load(&temp);
 | 
				
			||||||
 | 
					    if (hasdata) {
 | 
				
			||||||
 | 
					      this->value_.assign(temp + 1, temp[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->prev_value_.assign(this->value_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void loop() override { store_value_(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void on_shutdown() override { store_value_(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void store_value_() {
 | 
				
			||||||
 | 
					    int diff = this->value_.compare(this->prev_value_);
 | 
				
			||||||
 | 
					    if (diff != 0) {
 | 
				
			||||||
 | 
					      // Make it into a length prefixed thing
 | 
				
			||||||
 | 
					      unsigned char temp[SZ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // If string is bigger than the allocation, do not save it.
 | 
				
			||||||
 | 
					      // We don't need to waste ram setting prev_value either.
 | 
				
			||||||
 | 
					      int size = this->value_.size();
 | 
				
			||||||
 | 
					      // Less than, not less than or equal, SZ includes the length byte.
 | 
				
			||||||
 | 
					      if (size < SZ) {
 | 
				
			||||||
 | 
					        memcpy(temp + 1, this->value_.c_str(), size);
 | 
				
			||||||
 | 
					        // SZ should be pre checked at the schema level, it can't go past the char range.
 | 
				
			||||||
 | 
					        temp[0] = ((unsigned char) size);
 | 
				
			||||||
 | 
					        this->rtc_.save(&temp);
 | 
				
			||||||
 | 
					        this->prev_value_.assign(this->value_);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  T value_{};
 | 
				
			||||||
 | 
					  T prev_value_{};
 | 
				
			||||||
 | 
					  uint32_t name_hash_{};
 | 
				
			||||||
 | 
					  ESPPreferenceObject rtc_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> {
 | 
					template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  explicit GlobalVarSetAction(C *parent) : parent_(parent) {}
 | 
					  explicit GlobalVarSetAction(C *parent) : parent_(parent) {}
 | 
				
			||||||
@@ -81,6 +139,7 @@ template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); }
 | 
					template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); }
 | 
				
			||||||
template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
 | 
					template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
 | 
				
			||||||
 | 
					template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace globals
 | 
					}  // namespace globals
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_CURRENT,
 | 
					    CONF_CURRENT,
 | 
				
			||||||
    CONF_FREQUENCY,
 | 
					    CONF_FREQUENCY,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_PHASE_A,
 | 
				
			||||||
 | 
					    CONF_PHASE_B,
 | 
				
			||||||
 | 
					    CONF_PHASE_C,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
    DEVICE_CLASS_ENERGY,
 | 
					    DEVICE_CLASS_ENERGY,
 | 
				
			||||||
@@ -21,10 +24,6 @@ from esphome.const import (
 | 
				
			|||||||
    UNIT_WATT,
 | 
					    UNIT_WATT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_PHASE_A = "phase_a"
 | 
					 | 
				
			||||||
CONF_PHASE_B = "phase_b"
 | 
					 | 
				
			||||||
CONF_PHASE_C = "phase_c"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
 | 
					CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
 | 
				
			||||||
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
					CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
				
			||||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
					CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_CURRENT,
 | 
					    CONF_CURRENT,
 | 
				
			||||||
    CONF_FREQUENCY,
 | 
					    CONF_FREQUENCY,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_PHASE_A,
 | 
				
			||||||
 | 
					    CONF_PHASE_B,
 | 
				
			||||||
 | 
					    CONF_PHASE_C,
 | 
				
			||||||
    CONF_REACTIVE_POWER,
 | 
					    CONF_REACTIVE_POWER,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
@@ -24,9 +27,6 @@ from esphome.const import (
 | 
				
			|||||||
    UNIT_WATT,
 | 
					    UNIT_WATT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_PHASE_A = "phase_a"
 | 
					 | 
				
			||||||
CONF_PHASE_B = "phase_b"
 | 
					 | 
				
			||||||
CONF_PHASE_C = "phase_c"
 | 
					 | 
				
			||||||
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
 | 
					CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
 | 
				
			||||||
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
					CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
				
			||||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
					CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,7 @@ Image_ = image_ns.class_("Image")
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _compute_local_icon_path(value) -> Path:
 | 
					def _compute_local_icon_path(value) -> Path:
 | 
				
			||||||
    base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi"
 | 
					    base_dir = Path(CORE.data_dir) / DOMAIN / "mdi"
 | 
				
			||||||
    return base_dir / f"{value[CONF_ICON]}.svg"
 | 
					    return base_dir / f"{value[CONF_ICON]}.svg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,9 +168,9 @@ def _notify_old_style(config):
 | 
				
			|||||||
# NOTE: Keep this in mind when updating the recommended version:
 | 
					# NOTE: Keep this in mind when updating the recommended version:
 | 
				
			||||||
#  * For all constants below, update platformio.ini (in this repo)
 | 
					#  * For all constants below, update platformio.ini (in this repo)
 | 
				
			||||||
ARDUINO_VERSIONS = {
 | 
					ARDUINO_VERSIONS = {
 | 
				
			||||||
    "dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"),
 | 
					    "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"),
 | 
				
			||||||
    "latest": (cv.Version(0, 0, 0), None),
 | 
					    "latest": (cv.Version(0, 0, 0), None),
 | 
				
			||||||
    "recommended": (cv.Version(1, 3, 0), None),
 | 
					    "recommended": (cv.Version(1, 4, 0), None),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,7 +54,7 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
 | 
					bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
 | 
				
			||||||
  uint8_t i = get_mifare_classic_ndef_start_index(data);
 | 
					  uint8_t i = get_mifare_classic_ndef_start_index(data);
 | 
				
			||||||
  if (i < 0 || data[i] != 0x03) {
 | 
					  if (data[i] != 0x03) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Error, Can't decode message length.");
 | 
					    ESP_LOGE(TAG, "Error, Can't decode message length.");
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@
 | 
				
			|||||||
#include "esphome/components/nfc/nfc.h"
 | 
					#include "esphome/components/nfc/nfc.h"
 | 
				
			||||||
#include "esphome/components/nfc/automation.h"
 | 
					#include "esphome/components/nfc/automation.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cinttypes>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -74,10 +75,11 @@ class PN532 : public PollingComponent {
 | 
				
			|||||||
  bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
					  bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid);
 | 
					  std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid);
 | 
				
			||||||
  bool read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &data);
 | 
					  bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
 | 
				
			||||||
  bool is_mifare_ultralight_formatted_();
 | 
					  bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
 | 
				
			||||||
  uint16_t read_mifare_ultralight_capacity_();
 | 
					  uint16_t read_mifare_ultralight_capacity_();
 | 
				
			||||||
  bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index);
 | 
					  bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
 | 
				
			||||||
 | 
					                                    uint8_t &message_start_index);
 | 
				
			||||||
  bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
 | 
					  bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
 | 
				
			||||||
  bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
					  bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
				
			||||||
  bool clean_mifare_ultralight_();
 | 
					  bool clean_mifare_ultralight_();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,93 +9,104 @@ namespace pn532 {
 | 
				
			|||||||
static const char *const TAG = "pn532.mifare_ultralight";
 | 
					static const char *const TAG = "pn532.mifare_ultralight";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) {
 | 
					std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) {
 | 
				
			||||||
  if (!this->is_mifare_ultralight_formatted_()) {
 | 
					  std::vector<uint8_t> data;
 | 
				
			||||||
    ESP_LOGD(TAG, "Not NDEF formatted");
 | 
					  // pages 3 to 6 contain various info we are interested in -- do one read to grab it all
 | 
				
			||||||
 | 
					  if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
 | 
				
			||||||
 | 
					                                           data)) {
 | 
				
			||||||
 | 
					    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!this->is_mifare_ultralight_formatted_(data)) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Not NDEF formatted");
 | 
				
			||||||
    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
					    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t message_length;
 | 
					  uint8_t message_length;
 | 
				
			||||||
  uint8_t message_start_index;
 | 
					  uint8_t message_start_index;
 | 
				
			||||||
  if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) {
 | 
					  if (!this->find_mifare_ultralight_ndef_(data, message_length, message_start_index)) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Couldn't find NDEF message");
 | 
				
			||||||
    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
					    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index);
 | 
					  ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (message_length == 0) {
 | 
					  if (message_length == 0) {
 | 
				
			||||||
    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
					    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  std::vector<uint8_t> data;
 | 
					  // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
 | 
				
			||||||
  for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) {
 | 
					  const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
 | 
				
			||||||
    std::vector<uint8_t> page_data;
 | 
					  if (read_length) {
 | 
				
			||||||
    if (!this->read_mifare_ultralight_page_(page, page_data)) {
 | 
					    if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) {
 | 
				
			||||||
      ESP_LOGE(TAG, "Error reading page %d", page);
 | 
					      ESP_LOGE(TAG, "Error reading tag data");
 | 
				
			||||||
      return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
					      return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    data.insert(data.end(), page_data.begin(), page_data.end());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (data.size() >= (message_length + message_start_index))
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  // we need to trim off page 3 as well as any bytes ahead of message_start_index
 | 
				
			||||||
  data.erase(data.begin(), data.begin() + message_start_index);
 | 
					  data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
 | 
				
			||||||
  data.erase(data.begin() + message_length, data.end());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data);
 | 
					  return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &data) {
 | 
					bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
 | 
				
			||||||
  if (!this->write_command_({
 | 
					  const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
 | 
				
			||||||
          PN532_COMMAND_INDATAEXCHANGE,
 | 
					  std::vector<uint8_t> response;
 | 
				
			||||||
          0x01,  // One card
 | 
					
 | 
				
			||||||
          nfc::MIFARE_CMD_READ,
 | 
					  for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
 | 
				
			||||||
          page_num,
 | 
					    if (!this->write_command_({
 | 
				
			||||||
      })) {
 | 
					            PN532_COMMAND_INDATAEXCHANGE,
 | 
				
			||||||
    return false;
 | 
					            0x01,  // One card
 | 
				
			||||||
 | 
					            nfc::MIFARE_CMD_READ,
 | 
				
			||||||
 | 
					            uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
 | 
				
			||||||
 | 
					        })) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    uint16_t bytes_offset = (i + 1) * read_increment;
 | 
				
			||||||
 | 
					    auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) {
 | 
				
			||||||
 | 
					      data.insert(data.end(), response.begin() + 1, pages_in_end_itr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) {
 | 
					  ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  data.erase(data.begin());
 | 
					 | 
				
			||||||
  // We only want 1 page of data but the PN532 returns 4 at once.
 | 
					 | 
				
			||||||
  data.erase(data.begin() + 4, data.end());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool PN532::is_mifare_ultralight_formatted_() {
 | 
					bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
 | 
				
			||||||
  std::vector<uint8_t> data;
 | 
					  const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;  // page 4 will begin 4 bytes into the vector
 | 
				
			||||||
  if (this->read_mifare_ultralight_page_(4, data)) {
 | 
					
 | 
				
			||||||
    return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF);
 | 
					  return (page_3_to_6.size() > p4_offset + 3) &&
 | 
				
			||||||
  }
 | 
					         !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) &&
 | 
				
			||||||
  return true;
 | 
					           (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint16_t PN532::read_mifare_ultralight_capacity_() {
 | 
					uint16_t PN532::read_mifare_ultralight_capacity_() {
 | 
				
			||||||
  std::vector<uint8_t> data;
 | 
					  std::vector<uint8_t> data;
 | 
				
			||||||
  if (this->read_mifare_ultralight_page_(3, data)) {
 | 
					  if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) {
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
 | 
				
			||||||
    return data[2] * 8U;
 | 
					    return data[2] * 8U;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return 0;
 | 
					  return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) {
 | 
					bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
 | 
				
			||||||
  std::vector<uint8_t> data;
 | 
					                                         uint8_t &message_start_index) {
 | 
				
			||||||
  for (int page = 4; page < 6; page++) {
 | 
					  const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;  // page 4 will begin 4 bytes into the vector
 | 
				
			||||||
    std::vector<uint8_t> page_data;
 | 
					
 | 
				
			||||||
    if (!this->read_mifare_ultralight_page_(page, page_data)) {
 | 
					  if (!(page_3_to_6.size() > p4_offset + 5)) {
 | 
				
			||||||
      return false;
 | 
					    return false;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    data.insert(data.end(), page_data.begin(), page_data.end());
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (data[0] == 0x03) {
 | 
					
 | 
				
			||||||
    message_length = data[1];
 | 
					  if (page_3_to_6[p4_offset + 0] == 0x03) {
 | 
				
			||||||
 | 
					    message_length = page_3_to_6[p4_offset + 1];
 | 
				
			||||||
    message_start_index = 2;
 | 
					    message_start_index = 2;
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  } else if (data[5] == 0x03) {
 | 
					  } else if (page_3_to_6[p4_offset + 5] == 0x03) {
 | 
				
			||||||
    message_length = data[6];
 | 
					    message_length = page_3_to_6[p4_offset + 6];
 | 
				
			||||||
    message_start_index = 7;
 | 
					    message_start_index = 7;
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -111,7 +122,7 @@ bool PN532::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMes
 | 
				
			|||||||
  uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
 | 
					  uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (buffer_length > capacity) {
 | 
					  if (buffer_length > capacity) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity);
 | 
					    ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,13 +175,13 @@ bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t>
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  data.insert(data.end(), write_data.begin(), write_data.end());
 | 
					  data.insert(data.end(), write_data.begin(), write_data.end());
 | 
				
			||||||
  if (!this->write_command_(data)) {
 | 
					  if (!this->write_command_(data)) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Error writing page %d", page_num);
 | 
					    ESP_LOGE(TAG, "Error writing page %u", page_num);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> response;
 | 
					  std::vector<uint8_t> response;
 | 
				
			||||||
  if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
 | 
					  if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Error writing page %d", page_num);
 | 
					    ESP_LOGE(TAG, "Error writing page %u", page_num);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,12 +87,7 @@ def get_firmware(value):
 | 
				
			|||||||
    url = value[CONF_URL]
 | 
					    url = value[CONF_URL]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_SHA256 in value:  # we have a hash, enable caching
 | 
					    if CONF_SHA256 in value:  # we have a hash, enable caching
 | 
				
			||||||
        path = (
 | 
					        path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin")
 | 
				
			||||||
            Path(CORE.config_dir)
 | 
					 | 
				
			||||||
            / ".esphome"
 | 
					 | 
				
			||||||
            / DOMAIN
 | 
					 | 
				
			||||||
            / (value[CONF_SHA256] + "_fw_stm.bin")
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not path.is_file():
 | 
					        if not path.is_file():
 | 
				
			||||||
            firmware_data, dl_hash = dl(url)
 | 
					            firmware_data, dl_hash = dl(url)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,17 @@
 | 
				
			|||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
import esphome.final_validate as fv
 | 
					import esphome.final_validate as fv
 | 
				
			||||||
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
 | 
					    KEY_ESP32,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C6,
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from esphome import pins
 | 
					from esphome import pins
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_CLK_PIN,
 | 
					    CONF_CLK_PIN,
 | 
				
			||||||
@@ -9,6 +20,11 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_MOSI_PIN,
 | 
					    CONF_MOSI_PIN,
 | 
				
			||||||
    CONF_SPI_ID,
 | 
					    CONF_SPI_ID,
 | 
				
			||||||
    CONF_CS_PIN,
 | 
					    CONF_CS_PIN,
 | 
				
			||||||
 | 
					    CONF_NUMBER,
 | 
				
			||||||
 | 
					    CONF_INVERTED,
 | 
				
			||||||
 | 
					    KEY_CORE,
 | 
				
			||||||
 | 
					    KEY_TARGET_PLATFORM,
 | 
				
			||||||
 | 
					    KEY_VARIANT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import coroutine_with_priority, CORE
 | 
					from esphome.core import coroutine_with_priority, CORE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,10 +50,147 @@ SPI_DATA_RATE_OPTIONS = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
 | 
					SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MULTI_CONF = True
 | 
					 | 
				
			||||||
CONF_FORCE_SW = "force_sw"
 | 
					CONF_FORCE_SW = "force_sw"
 | 
				
			||||||
 | 
					CONF_INTERFACE = "interface"
 | 
				
			||||||
 | 
					CONF_INTERFACE_INDEX = "interface_index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					
 | 
				
			||||||
 | 
					def get_target_platform():
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        CORE.data[KEY_CORE][KEY_TARGET_PLATFORM]
 | 
				
			||||||
 | 
					        if KEY_TARGET_PLATFORM in CORE.data[KEY_CORE]
 | 
				
			||||||
 | 
					        else ""
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_target_variant():
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else ""
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Get a list of available hardware interfaces based on target and variant.
 | 
				
			||||||
 | 
					# The returned value is a list of lists of names
 | 
				
			||||||
 | 
					def get_hw_interface_list():
 | 
				
			||||||
 | 
					    target_platform = get_target_platform()
 | 
				
			||||||
 | 
					    if target_platform == "esp8266":
 | 
				
			||||||
 | 
					        return [["spi", "hspi"]]
 | 
				
			||||||
 | 
					    if target_platform == "esp32":
 | 
				
			||||||
 | 
					        if get_target_variant() in [
 | 
				
			||||||
 | 
					            VARIANT_ESP32C2,
 | 
				
			||||||
 | 
					            VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					            VARIANT_ESP32C6,
 | 
				
			||||||
 | 
					            VARIANT_ESP32H2,
 | 
				
			||||||
 | 
					        ]:
 | 
				
			||||||
 | 
					            return [["spi", "spi2"]]
 | 
				
			||||||
 | 
					        return [["spi", "spi2"], ["spi3"]]
 | 
				
			||||||
 | 
					    if target_platform == "rp2040":
 | 
				
			||||||
 | 
					        return [["spi"]]
 | 
				
			||||||
 | 
					    return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given an SPI name, return the index of it in the available list
 | 
				
			||||||
 | 
					def get_spi_index(name):
 | 
				
			||||||
 | 
					    for i, ilist in enumerate(get_hw_interface_list()):
 | 
				
			||||||
 | 
					        if name in ilist:
 | 
				
			||||||
 | 
					            return i
 | 
				
			||||||
 | 
					    # Should never get to here.
 | 
				
			||||||
 | 
					    raise cv.Invalid(f"{name} is not an available SPI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Check that pins are suitable for HW spi
 | 
				
			||||||
 | 
					# TODO verify that the pins are internal
 | 
				
			||||||
 | 
					def validate_hw_pins(spi):
 | 
				
			||||||
 | 
					    clk_pin = spi[CONF_CLK_PIN]
 | 
				
			||||||
 | 
					    if clk_pin[CONF_INVERTED]:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    clk_pin_no = clk_pin[CONF_NUMBER]
 | 
				
			||||||
 | 
					    sdo_pin_no = -1
 | 
				
			||||||
 | 
					    sdi_pin_no = -1
 | 
				
			||||||
 | 
					    if CONF_MOSI_PIN in spi:
 | 
				
			||||||
 | 
					        sdo_pin = spi[CONF_MOSI_PIN]
 | 
				
			||||||
 | 
					        if sdo_pin[CONF_INVERTED]:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        sdo_pin_no = sdo_pin[CONF_NUMBER]
 | 
				
			||||||
 | 
					    if CONF_MISO_PIN in spi:
 | 
				
			||||||
 | 
					        sdi_pin = spi[CONF_MISO_PIN]
 | 
				
			||||||
 | 
					        if sdi_pin[CONF_INVERTED]:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        sdi_pin_no = sdi_pin[CONF_NUMBER]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    target_platform = get_target_platform()
 | 
				
			||||||
 | 
					    if target_platform == "esp8266":
 | 
				
			||||||
 | 
					        if clk_pin_no == 6:
 | 
				
			||||||
 | 
					            return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7)
 | 
				
			||||||
 | 
					        if clk_pin_no == 14:
 | 
				
			||||||
 | 
					            return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if target_platform == "esp32":
 | 
				
			||||||
 | 
					        return clk_pin_no >= 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_spi_config(config):
 | 
				
			||||||
 | 
					    available = list(range(len(get_hw_interface_list())))
 | 
				
			||||||
 | 
					    for spi in config:
 | 
				
			||||||
 | 
					        interface = spi[CONF_INTERFACE]
 | 
				
			||||||
 | 
					        if spi[CONF_FORCE_SW]:
 | 
				
			||||||
 | 
					            if interface == "any":
 | 
				
			||||||
 | 
					                spi[CONF_INTERFACE] = interface = "software"
 | 
				
			||||||
 | 
					            elif interface != "software":
 | 
				
			||||||
 | 
					                raise cv.Invalid("force_sw is deprecated - use interface: software")
 | 
				
			||||||
 | 
					        if interface == "software":
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif interface == "any":
 | 
				
			||||||
 | 
					            if not validate_hw_pins(spi):
 | 
				
			||||||
 | 
					                spi[CONF_INTERFACE] = "software"
 | 
				
			||||||
 | 
					        elif interface == "hardware":
 | 
				
			||||||
 | 
					            if len(available) == 0:
 | 
				
			||||||
 | 
					                raise cv.Invalid("No hardware interface available")
 | 
				
			||||||
 | 
					            index = spi[CONF_INTERFACE_INDEX] = available[0]
 | 
				
			||||||
 | 
					            available.remove(index)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Must be a specific name
 | 
				
			||||||
 | 
					            index = spi[CONF_INTERFACE_INDEX] = get_spi_index(interface)
 | 
				
			||||||
 | 
					            if index not in available:
 | 
				
			||||||
 | 
					                raise cv.Invalid(
 | 
				
			||||||
 | 
					                    f"interface '{interface}' not available here (may be already assigned)"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            available.remove(index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Second time around:
 | 
				
			||||||
 | 
					    # Any specific names and any 'hardware' requests will have already been filled,
 | 
				
			||||||
 | 
					    # so just need to assign remaining hardware to 'any' requests.
 | 
				
			||||||
 | 
					    for spi in config:
 | 
				
			||||||
 | 
					        if spi[CONF_INTERFACE] == "any" and len(available) != 0:
 | 
				
			||||||
 | 
					            index = available[0]
 | 
				
			||||||
 | 
					            spi[CONF_INTERFACE_INDEX] = index
 | 
				
			||||||
 | 
					            available.remove(index)
 | 
				
			||||||
 | 
					        if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(spi):
 | 
				
			||||||
 | 
					            raise cv.Invalid("Invalid pin selections for hardware SPI interface")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given an SPI index, convert to a string that represents the C++ object for it.
 | 
				
			||||||
 | 
					def get_spi_interface(index):
 | 
				
			||||||
 | 
					    if CORE.using_esp_idf:
 | 
				
			||||||
 | 
					        return ["SPI2_HOST", "SPI3_HOST"][index]
 | 
				
			||||||
 | 
					    # Arduino code follows
 | 
				
			||||||
 | 
					    platform = get_target_platform()
 | 
				
			||||||
 | 
					    if platform == "rp2040":
 | 
				
			||||||
 | 
					        return "&spi1"
 | 
				
			||||||
 | 
					    if index == 0:
 | 
				
			||||||
 | 
					        return "&SPI"
 | 
				
			||||||
 | 
					    # Following code can't apply to C2, H2 or 8266 since they have only one SPI
 | 
				
			||||||
 | 
					    if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2):
 | 
				
			||||||
 | 
					        return "new SPIClass(FSPI)"
 | 
				
			||||||
 | 
					    return "return new SPIClass(HSPI)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SPI_SCHEMA = cv.All(
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(SPIComponent),
 | 
					            cv.GenerateID(): cv.declare_id(SPIComponent),
 | 
				
			||||||
@@ -45,28 +198,47 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
 | 
					            cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
 | 
				
			||||||
            cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
 | 
					            cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
 | 
				
			||||||
            cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
 | 
					            cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
 | 
				
			||||||
 | 
					                *sum(get_hw_interface_list(), ["software", "hardware", "any"]),
 | 
				
			||||||
 | 
					                lower=True,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
 | 
					    cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
 | 
				
			||||||
    cv.only_on(["esp32", "esp8266", "rp2040"]),
 | 
					    cv.only_on(["esp32", "esp8266", "rp2040"]),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    cv.ensure_list(SPI_SCHEMA),
 | 
				
			||||||
 | 
					    validate_spi_config,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(1.0)
 | 
					@coroutine_with_priority(1.0)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(configs):
 | 
				
			||||||
    cg.add_global(spi_ns.using)
 | 
					    cg.add_global(spi_ns.using)
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    for spi in configs:
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					        var = cg.new_Pvariable(spi[CONF_ID])
 | 
				
			||||||
 | 
					        await cg.register_component(var, spi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN])
 | 
					        clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
 | 
				
			||||||
    cg.add(var.set_clk(clk))
 | 
					        cg.add(var.set_clk(clk))
 | 
				
			||||||
    cg.add(var.set_force_sw(config[CONF_FORCE_SW]))
 | 
					        if CONF_MISO_PIN in spi:
 | 
				
			||||||
    if CONF_MISO_PIN in config:
 | 
					            miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
 | 
				
			||||||
        miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN])
 | 
					            cg.add(var.set_miso(miso))
 | 
				
			||||||
        cg.add(var.set_miso(miso))
 | 
					        if CONF_MOSI_PIN in spi:
 | 
				
			||||||
    if CONF_MOSI_PIN in config:
 | 
					            mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
 | 
				
			||||||
        mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
 | 
					            cg.add(var.set_mosi(mosi))
 | 
				
			||||||
        cg.add(var.set_mosi(mosi))
 | 
					        if CONF_INTERFACE_INDEX in spi:
 | 
				
			||||||
 | 
					            index = spi[CONF_INTERFACE_INDEX]
 | 
				
			||||||
 | 
					            cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
 | 
				
			||||||
 | 
					            cg.add(
 | 
				
			||||||
 | 
					                var.set_interface_name(
 | 
				
			||||||
 | 
					                    re.sub(
 | 
				
			||||||
 | 
					                        r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CORE.using_arduino:
 | 
					    if CORE.using_arduino:
 | 
				
			||||||
        cg.add_library("SPI", None)
 | 
					        cg.add_library("SPI", None)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,268 +1,116 @@
 | 
				
			|||||||
#include "spi.h"
 | 
					#include "spi.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace spi {
 | 
					namespace spi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "spi";
 | 
					const char *const TAG = "spi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void IRAM_ATTR HOT SPIComponent::disable() {
 | 
					SPIDelegate *const SPIDelegate::NULL_DELEGATE =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					    new SPIDelegateDummy();
 | 
				
			||||||
  if (this->hw_spi_ != nullptr) {
 | 
					// https://bugs.llvm.org/show_bug.cgi?id=48040
 | 
				
			||||||
    this->hw_spi_->endTransaction();
 | 
					
 | 
				
			||||||
  }
 | 
					bool SPIDelegate::is_ready() { return true; }
 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					
 | 
				
			||||||
  if (this->active_cs_) {
 | 
					GPIOPin *const NullPin::NULL_PIN = new NullPin();  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
    this->active_cs_->digital_write(true);
 | 
					
 | 
				
			||||||
    this->active_cs_ = nullptr;
 | 
					SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
 | 
				
			||||||
 | 
					                                           GPIOPin *cs_pin) {
 | 
				
			||||||
 | 
					  if (this->devices_.count(device) != 0) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "SPI device already registered");
 | 
				
			||||||
 | 
					    return this->devices_[device];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin);  // NOLINT
 | 
				
			||||||
 | 
					  this->devices_[device] = delegate;
 | 
				
			||||||
 | 
					  return delegate;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SPIComponent::unregister_device(SPIClient *device) {
 | 
				
			||||||
 | 
					  if (this->devices_.count(device) == 0) {
 | 
				
			||||||
 | 
					    esph_log_e(TAG, "SPI device not registered");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  delete this->devices_[device];  // NOLINT
 | 
				
			||||||
 | 
					  this->devices_.erase(device);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SPIComponent::setup() {
 | 
					void SPIComponent::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up SPI bus...");
 | 
					  ESP_LOGD(TAG, "Setting up SPI bus...");
 | 
				
			||||||
  this->clk_->setup();
 | 
					 | 
				
			||||||
  this->clk_->digital_write(true);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					  if (this->sdo_pin_ == nullptr)
 | 
				
			||||||
  bool use_hw_spi = !this->force_sw_;
 | 
					    this->sdo_pin_ = NullPin::NULL_PIN;
 | 
				
			||||||
  const bool has_miso = this->miso_ != nullptr;
 | 
					  if (this->sdi_pin_ == nullptr)
 | 
				
			||||||
  const bool has_mosi = this->mosi_ != nullptr;
 | 
					    this->sdi_pin_ = NullPin::NULL_PIN;
 | 
				
			||||||
  int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1;
 | 
					  if (this->clk_pin_ == nullptr) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "No clock pin for SPI");
 | 
				
			||||||
  if (!this->clk_->is_internal())
 | 
					    this->mark_failed();
 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
  if (has_miso && !miso_->is_internal())
 | 
					 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
  if (has_mosi && !mosi_->is_internal())
 | 
					 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
  if (use_hw_spi) {
 | 
					 | 
				
			||||||
    auto *clk_internal = (InternalGPIOPin *) clk_;
 | 
					 | 
				
			||||||
    auto *miso_internal = (InternalGPIOPin *) miso_;
 | 
					 | 
				
			||||||
    auto *mosi_internal = (InternalGPIOPin *) mosi_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (clk_internal->is_inverted())
 | 
					 | 
				
			||||||
      use_hw_spi = false;
 | 
					 | 
				
			||||||
    if (has_miso && miso_internal->is_inverted())
 | 
					 | 
				
			||||||
      use_hw_spi = false;
 | 
					 | 
				
			||||||
    if (has_mosi && mosi_internal->is_inverted())
 | 
					 | 
				
			||||||
      use_hw_spi = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (use_hw_spi) {
 | 
					 | 
				
			||||||
      clk_pin = clk_internal->get_pin();
 | 
					 | 
				
			||||||
      miso_pin = has_miso ? miso_internal->get_pin() : -1;
 | 
					 | 
				
			||||||
      mosi_pin = has_mosi ? mosi_internal->get_pin() : -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					 | 
				
			||||||
  if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) &&
 | 
					 | 
				
			||||||
      !(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)))
 | 
					 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (use_hw_spi) {
 | 
					 | 
				
			||||||
    this->hw_spi_ = &SPI;
 | 
					 | 
				
			||||||
    this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0);
 | 
					 | 
				
			||||||
    this->hw_spi_->begin();
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif  // USE_ESP8266
 | 
					 | 
				
			||||||
#ifdef USE_ESP32
 | 
					 | 
				
			||||||
  static uint8_t spi_bus_num = 0;
 | 
					 | 
				
			||||||
  if (spi_bus_num >= 2) {
 | 
					 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (use_hw_spi) {
 | 
					  if (this->using_hw_) {
 | 
				
			||||||
    if (spi_bus_num == 0) {
 | 
					    this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
 | 
				
			||||||
      this->hw_spi_ = &SPI;
 | 
					    if (this->spi_bus_ == nullptr) {
 | 
				
			||||||
    } else {
 | 
					      ESP_LOGE(TAG, "Unable to allocate SPI interface");
 | 
				
			||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
 | 
					      this->mark_failed();
 | 
				
			||||||
    defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6)
 | 
					 | 
				
			||||||
      this->hw_spi_ = new SPIClass(FSPI);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
      this->hw_spi_ = new SPIClass(HSPI);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
					 | 
				
			||||||
#endif  // USE_ESP32_VARIANT
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    spi_bus_num++;
 | 
					  } else {
 | 
				
			||||||
    this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin);
 | 
					    this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_);  // NOLINT
 | 
				
			||||||
    return;
 | 
					    this->clk_pin_->setup();
 | 
				
			||||||
  }
 | 
					    this->clk_pin_->digital_write(true);
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					    this->sdo_pin_->setup();
 | 
				
			||||||
#ifdef USE_RP2040
 | 
					    this->sdi_pin_->setup();
 | 
				
			||||||
  static uint8_t spi_bus_num = 0;
 | 
					 | 
				
			||||||
  if (spi_bus_num >= 2) {
 | 
					 | 
				
			||||||
    use_hw_spi = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (use_hw_spi) {
 | 
					 | 
				
			||||||
    SPIClassRP2040 *spi;
 | 
					 | 
				
			||||||
    if (spi_bus_num == 0) {
 | 
					 | 
				
			||||||
      spi = &SPI;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      spi = &SPI1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    spi_bus_num++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (miso_pin != -1)
 | 
					 | 
				
			||||||
      spi->setRX(miso_pin);
 | 
					 | 
				
			||||||
    if (mosi_pin != -1)
 | 
					 | 
				
			||||||
      spi->setTX(mosi_pin);
 | 
					 | 
				
			||||||
    spi->setSCK(clk_pin);
 | 
					 | 
				
			||||||
    this->hw_spi_ = spi;
 | 
					 | 
				
			||||||
    this->hw_spi_->begin();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif  // USE_RP2040
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (this->miso_ != nullptr) {
 | 
					 | 
				
			||||||
    this->miso_->setup();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (this->mosi_ != nullptr) {
 | 
					 | 
				
			||||||
    this->mosi_->setup();
 | 
					 | 
				
			||||||
    this->mosi_->digital_write(false);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SPIComponent::dump_config() {
 | 
					void SPIComponent::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "SPI bus:");
 | 
					  ESP_LOGCONFIG(TAG, "SPI bus:");
 | 
				
			||||||
  LOG_PIN("  CLK Pin: ", this->clk_);
 | 
					  LOG_PIN("  CLK Pin: ", this->clk_pin_)
 | 
				
			||||||
  LOG_PIN("  MISO Pin: ", this->miso_);
 | 
					  LOG_PIN("  SDI Pin: ", this->sdi_pin_)
 | 
				
			||||||
  LOG_PIN("  MOSI Pin: ", this->mosi_);
 | 
					  LOG_PIN("  SDO Pin: ", this->sdo_pin_)
 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					  if (this->spi_bus_->is_hw()) {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr));
 | 
					    ESP_LOGCONFIG(TAG, "  Using HW SPI: %s", this->interface_name_);
 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					  } else {
 | 
				
			||||||
}
 | 
					    ESP_LOGCONFIG(TAG, "  Using software SPI");
 | 
				
			||||||
float SPIComponent::get_setup_priority() const { return setup_priority::BUS; }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
void SPIComponent::cycle_clock_(bool value) {
 | 
					 | 
				
			||||||
  uint32_t start = arch_get_cpu_cycle_count();
 | 
					 | 
				
			||||||
  while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
 | 
					 | 
				
			||||||
    ;
 | 
					 | 
				
			||||||
  this->clk_->digital_write(value);
 | 
					 | 
				
			||||||
  start += this->wait_cycle_;
 | 
					 | 
				
			||||||
  while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
 | 
					 | 
				
			||||||
    ;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NOLINTNEXTLINE
 | 
					void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); }
 | 
				
			||||||
#ifndef CLANG_TIDY
 | 
					 | 
				
			||||||
#pragma GCC optimize("unroll-loops")
 | 
					 | 
				
			||||||
// NOLINTNEXTLINE
 | 
					 | 
				
			||||||
#pragma GCC optimize("O2")
 | 
					 | 
				
			||||||
#endif  // CLANG_TIDY
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
 | 
					uint8_t SPIDelegateBitBash::transfer(uint8_t data) {
 | 
				
			||||||
uint8_t HOT SPIComponent::transfer_(uint8_t data) {
 | 
					 | 
				
			||||||
  // Clock starts out at idle level
 | 
					  // Clock starts out at idle level
 | 
				
			||||||
  this->clk_->digital_write(CLOCK_POLARITY);
 | 
					  this->clk_pin_->digital_write(clock_polarity_);
 | 
				
			||||||
  uint8_t out_data = 0;
 | 
					  uint8_t out_data = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
					  for (uint8_t i = 0; i < 8; i++) {
 | 
				
			||||||
    uint8_t shift;
 | 
					    uint8_t shift;
 | 
				
			||||||
    if (BIT_ORDER == BIT_ORDER_MSB_FIRST) {
 | 
					    if (bit_order_ == BIT_ORDER_MSB_FIRST) {
 | 
				
			||||||
      shift = 7 - i;
 | 
					      shift = 7 - i;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      shift = i;
 | 
					      shift = i;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (CLOCK_PHASE == CLOCK_PHASE_LEADING) {
 | 
					    if (clock_phase_ == CLOCK_PHASE_LEADING) {
 | 
				
			||||||
      // sampling on leading edge
 | 
					      // sampling on leading edge
 | 
				
			||||||
      if (WRITE) {
 | 
					      this->sdo_pin_->digital_write(data & (1 << shift));
 | 
				
			||||||
        this->mosi_->digital_write(data & (1 << shift));
 | 
					      this->cycle_clock_();
 | 
				
			||||||
      }
 | 
					      out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
 | 
				
			||||||
 | 
					      this->clk_pin_->digital_write(!this->clock_polarity_);
 | 
				
			||||||
      // SAMPLE!
 | 
					      this->cycle_clock_();
 | 
				
			||||||
      this->cycle_clock_(!CLOCK_POLARITY);
 | 
					      this->clk_pin_->digital_write(this->clock_polarity_);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (READ) {
 | 
					 | 
				
			||||||
        out_data |= uint8_t(this->miso_->digital_read()) << shift;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this->cycle_clock_(CLOCK_POLARITY);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // sampling on trailing edge
 | 
					      // sampling on trailing edge
 | 
				
			||||||
      this->cycle_clock_(!CLOCK_POLARITY);
 | 
					      this->cycle_clock_();
 | 
				
			||||||
 | 
					      this->clk_pin_->digital_write(!this->clock_polarity_);
 | 
				
			||||||
      if (WRITE) {
 | 
					      this->sdo_pin_->digital_write(data & (1 << shift));
 | 
				
			||||||
        this->mosi_->digital_write(data & (1 << shift));
 | 
					      this->cycle_clock_();
 | 
				
			||||||
      }
 | 
					      out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
 | 
				
			||||||
 | 
					      this->clk_pin_->digital_write(this->clock_polarity_);
 | 
				
			||||||
      // SAMPLE!
 | 
					 | 
				
			||||||
      this->cycle_clock_(CLOCK_POLARITY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (READ) {
 | 
					 | 
				
			||||||
        out_data |= uint8_t(this->miso_->digital_read()) << shift;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  App.feed_wdt();
 | 
					  App.feed_wdt();
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return out_data;
 | 
					  return out_data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Generate with (py3):
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// from itertools import product
 | 
					 | 
				
			||||||
// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST']
 | 
					 | 
				
			||||||
// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH']
 | 
					 | 
				
			||||||
// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING']
 | 
					 | 
				
			||||||
// reads = [False, True]
 | 
					 | 
				
			||||||
// writes = [False, True]
 | 
					 | 
				
			||||||
// cpp_bool = {False: 'false', True: 'true'}
 | 
					 | 
				
			||||||
// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes):
 | 
					 | 
				
			||||||
//     if not r and not w:
 | 
					 | 
				
			||||||
//         continue
 | 
					 | 
				
			||||||
//     print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t
 | 
					 | 
				
			||||||
//     data);")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
 | 
					 | 
				
			||||||
    uint8_t data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace spi
 | 
					}  // namespace spi
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,16 +2,34 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
#define USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
#include <SPI.h>
 | 
					#include <SPI.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					using SPIInterface = SPIClassRP2040 *;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					using SPIInterface = SPIClass *;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "driver/spi_master.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using SPIInterface = spi_host_device_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP_IDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Implementation of SPI Controller mode.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace spi {
 | 
					namespace spi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,10 +66,19 @@ enum SPIClockPhase {
 | 
				
			|||||||
  /// The data is sampled on a trailing clock edge. (CPHA=1)
 | 
					  /// The data is sampled on a trailing clock edge. (CPHA=1)
 | 
				
			||||||
  CLOCK_PHASE_TRAILING,
 | 
					  CLOCK_PHASE_TRAILING,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW.
 | 
					
 | 
				
			||||||
 * So effectively the rate of bytes can be calculated using
 | 
					/**
 | 
				
			||||||
 | 
					 * Modes mapping to clock phase and polarity.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * effective_byte_rate = spi_data_rate / 16
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum SPIMode {
 | 
				
			||||||
 | 
					  MODE0 = 0,
 | 
				
			||||||
 | 
					  MODE1 = 1,
 | 
				
			||||||
 | 
					  MODE2 = 2,
 | 
				
			||||||
 | 
					  MODE3 = 3,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					/** The SPI clock signal frequency, which determines the transfer bit rate/second.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Implementations can use the pre-defined constants here, or use an integer in the template definition
 | 
					 * Implementations can use the pre-defined constants here, or use an integer in the template definition
 | 
				
			||||||
 * to manually use a specific data rate.
 | 
					 * to manually use a specific data rate.
 | 
				
			||||||
@@ -71,270 +98,340 @@ enum SPIDataRate : uint32_t {
 | 
				
			|||||||
  DATA_RATE_80MHZ = 80000000,
 | 
					  DATA_RATE_80MHZ = 80000000,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SPIComponent : public Component {
 | 
					/**
 | 
				
			||||||
 | 
					 * A pin to replace those that don't exist.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class NullPin : public GPIOPin {
 | 
				
			||||||
 | 
					  friend class SPIComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  friend class SPIDelegate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  friend class Utility;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void set_clk(GPIOPin *clk) { clk_ = clk; }
 | 
					  void setup() override {}
 | 
				
			||||||
  void set_miso(GPIOPin *miso) { miso_ = miso; }
 | 
					 | 
				
			||||||
  void set_mosi(GPIOPin *mosi) { mosi_ = mosi; }
 | 
					 | 
				
			||||||
  void set_force_sw(bool force_sw) { force_sw_ = force_sw; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setup() override;
 | 
					  void pin_mode(gpio::Flags flags) override {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void dump_config() override;
 | 
					  bool digital_read() override { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> uint8_t read_byte() {
 | 
					  void digital_write(bool value) override {}
 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      return this->hw_spi_->transfer(0x00);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, false>(0x00);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					  std::string dump_summary() const override { return std::string(); }
 | 
				
			||||||
  void read_array(uint8_t *data, size_t length) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      this->hw_spi_->transfer(data, length);
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < length; i++) {
 | 
					 | 
				
			||||||
      data[i] = this->read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  void write_byte(uint8_t data) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
      this->hw_spi_->transfer(data);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
      this->hw_spi_->write(data);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, false, true>(data);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  void write_byte16(const uint16_t data) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
      this->hw_spi_->transfer16(data);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
      this->hw_spi_->write16(data);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data >> 8);
 | 
					 | 
				
			||||||
    this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  void write_array16(const uint16_t *data, size_t length) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      for (size_t i = 0; i < length; i++) {
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
        this->hw_spi_->transfer16(data[i]);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
        this->hw_spi_->write16(data[i]);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < length; i++) {
 | 
					 | 
				
			||||||
      this->write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  void write_array(const uint8_t *data, size_t length) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      auto *data_c = const_cast<uint8_t *>(data);
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
      this->hw_spi_->transfer(data_c, length);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
      this->hw_spi_->writeBytes(data_c, length);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < length; i++) {
 | 
					 | 
				
			||||||
      this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  uint8_t transfer_byte(uint8_t data) {
 | 
					 | 
				
			||||||
    if (this->miso_ != nullptr) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
      if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
        return this->hw_spi_->transfer(data);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
        return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, true>(data);
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
					 | 
				
			||||||
  void transfer_array(uint8_t *data, size_t length) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      if (this->miso_ != nullptr) {
 | 
					 | 
				
			||||||
        this->hw_spi_->transfer(data, length);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
        this->hw_spi_->transfer(data, length);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
        this->hw_spi_->writeBytes(data, length);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this->miso_ != nullptr) {
 | 
					 | 
				
			||||||
      for (size_t i = 0; i < length; i++) {
 | 
					 | 
				
			||||||
        data[i] = this->transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this->write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
 | 
					 | 
				
			||||||
  void enable(GPIOPin *cs) {
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    if (this->hw_spi_ != nullptr) {
 | 
					 | 
				
			||||||
      uint8_t data_mode = SPI_MODE0;
 | 
					 | 
				
			||||||
      if (!CLOCK_POLARITY && CLOCK_PHASE) {
 | 
					 | 
				
			||||||
        data_mode = SPI_MODE1;
 | 
					 | 
				
			||||||
      } else if (CLOCK_POLARITY && !CLOCK_PHASE) {
 | 
					 | 
				
			||||||
        data_mode = SPI_MODE2;
 | 
					 | 
				
			||||||
      } else if (CLOCK_POLARITY && CLOCK_PHASE) {
 | 
					 | 
				
			||||||
        data_mode = SPI_MODE3;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
#ifdef USE_RP2040
 | 
					 | 
				
			||||||
      SPISettings settings(DATA_RATE, static_cast<BitOrder>(BIT_ORDER), data_mode);
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
      SPISettings settings(DATA_RATE, BIT_ORDER, data_mode);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      this->hw_spi_->beginTransaction(settings);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
      this->clk_->digital_write(CLOCK_POLARITY);
 | 
					 | 
				
			||||||
      uint32_t cpu_freq_hz = arch_get_cpu_freq_hz();
 | 
					 | 
				
			||||||
      this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL;
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (cs != nullptr) {
 | 
					 | 
				
			||||||
      this->active_cs_ = cs;
 | 
					 | 
				
			||||||
      this->active_cs_->digital_write(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void disable();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  float get_setup_priority() const override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  inline void cycle_clock_(bool value);
 | 
					  static GPIOPin *const NULL_PIN;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					  // https://bugs.llvm.org/show_bug.cgi?id=48040
 | 
				
			||||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
 | 
					 | 
				
			||||||
  uint8_t transfer_(uint8_t data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  GPIOPin *clk_;
 | 
					 | 
				
			||||||
  GPIOPin *miso_{nullptr};
 | 
					 | 
				
			||||||
  GPIOPin *mosi_{nullptr};
 | 
					 | 
				
			||||||
  GPIOPin *active_cs_{nullptr};
 | 
					 | 
				
			||||||
  bool force_sw_{false};
 | 
					 | 
				
			||||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
  SPIClass *hw_spi_{nullptr};
 | 
					 | 
				
			||||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
					 | 
				
			||||||
  uint32_t wait_cycle_;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE>
 | 
					class Utility {
 | 
				
			||||||
class SPIDevice {
 | 
					 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  SPIDevice() = default;
 | 
					  static int get_pin_no(GPIOPin *pin) {
 | 
				
			||||||
  SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {}
 | 
					    if (pin == nullptr || !pin->is_internal())
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    if (((InternalGPIOPin *) pin)->is_inverted())
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    return ((InternalGPIOPin *) pin)->get_pin();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_spi_parent(SPIComponent *parent) { parent_ = parent; }
 | 
					  static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) {
 | 
				
			||||||
  void set_cs_pin(GPIOPin *cs) { cs_ = cs; }
 | 
					    if (polarity == CLOCK_POLARITY_HIGH) {
 | 
				
			||||||
 | 
					      return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void spi_setup() {
 | 
					  static SPIClockPhase get_phase(SPIMode mode) {
 | 
				
			||||||
    if (this->cs_) {
 | 
					    switch (mode) {
 | 
				
			||||||
      this->cs_->setup();
 | 
					      case MODE0:
 | 
				
			||||||
      this->cs_->digital_write(true);
 | 
					      case MODE2:
 | 
				
			||||||
 | 
					        return CLOCK_PHASE_LEADING;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return CLOCK_PHASE_TRAILING;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void enable() { this->parent_->template enable<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, DATA_RATE>(this->cs_); }
 | 
					  static SPIClockPolarity get_polarity(SPIMode mode) {
 | 
				
			||||||
 | 
					    switch (mode) {
 | 
				
			||||||
 | 
					      case MODE0:
 | 
				
			||||||
 | 
					      case MODE1:
 | 
				
			||||||
 | 
					        return CLOCK_POLARITY_LOW;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return CLOCK_POLARITY_HIGH;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void disable() { this->parent_->disable(); }
 | 
					class SPIDelegateDummy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t read_byte() { return this->parent_->template read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(); }
 | 
					// represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is
 | 
				
			||||||
 | 
					// a thin wrapper over SPIClass.
 | 
				
			||||||
 | 
					class SPIDelegate {
 | 
				
			||||||
 | 
					  friend class SPIClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void read_array(uint8_t *data, size_t length) {
 | 
					 public:
 | 
				
			||||||
    return this->parent_->template read_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
					  SPIDelegate() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin)
 | 
				
			||||||
 | 
					      : bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) {
 | 
				
			||||||
 | 
					    if (this->cs_pin_ == nullptr)
 | 
				
			||||||
 | 
					      this->cs_pin_ = NullPin::NULL_PIN;
 | 
				
			||||||
 | 
					    this->cs_pin_->setup();
 | 
				
			||||||
 | 
					    this->cs_pin_->digital_write(true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template<size_t N> std::array<uint8_t, N> read_array() {
 | 
					  virtual ~SPIDelegate(){};
 | 
				
			||||||
    std::array<uint8_t, N> data;
 | 
					
 | 
				
			||||||
    this->read_array(data.data(), N);
 | 
					  // enable CS if configured.
 | 
				
			||||||
    return data;
 | 
					  virtual void begin_transaction() { this->cs_pin_->digital_write(false); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // end the transaction
 | 
				
			||||||
 | 
					  virtual void end_transaction() { this->cs_pin_->digital_write(true); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // transfer one byte, return the byte that was read.
 | 
				
			||||||
 | 
					  virtual uint8_t transfer(uint8_t data) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // transfer a buffer, replace the contents with read data
 | 
				
			||||||
 | 
					  virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) {
 | 
				
			||||||
 | 
					    for (size_t i = 0; i != length; i++)
 | 
				
			||||||
 | 
					      rxbuf[i] = this->transfer(txbuf[i]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void write_byte(uint8_t data) {
 | 
					  // write 16 bits
 | 
				
			||||||
    return this->parent_->template write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
					  virtual void write16(uint16_t data) {
 | 
				
			||||||
 | 
					    if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
 | 
				
			||||||
 | 
					      uint16_t buffer;
 | 
				
			||||||
 | 
					      buffer = (data >> 8) | (data << 8);
 | 
				
			||||||
 | 
					      this->write_array(reinterpret_cast<const uint8_t *>(&buffer), 2);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->write_array(reinterpret_cast<const uint8_t *>(&data), 2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void write_byte16(uint16_t data) {
 | 
					  virtual void write_array16(const uint16_t *data, size_t length) {
 | 
				
			||||||
    return this->parent_->template write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
					    for (size_t i = 0; i != length; i++) {
 | 
				
			||||||
 | 
					      this->write16(data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void write_array16(const uint16_t *data, size_t length) {
 | 
					  // write the contents of a buffer, ignore read data (buffer is unchanged.)
 | 
				
			||||||
    this->parent_->template write_array16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
					  virtual void write_array(const uint8_t *ptr, size_t length) {
 | 
				
			||||||
 | 
					    for (size_t i = 0; i != length; i++)
 | 
				
			||||||
 | 
					      this->transfer(ptr[i]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void write_array(const uint8_t *data, size_t length) {
 | 
					  // read into a buffer, write nulls
 | 
				
			||||||
    this->parent_->template write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
					  virtual void read_array(uint8_t *ptr, size_t length) {
 | 
				
			||||||
 | 
					    for (size_t i = 0; i != length; i++)
 | 
				
			||||||
 | 
					      ptr[i] = this->transfer(0);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // check if device is ready
 | 
				
			||||||
 | 
					  virtual bool is_ready();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
 | 
				
			||||||
 | 
					  uint32_t data_rate_{1000000};
 | 
				
			||||||
 | 
					  SPIMode mode_{MODE0};
 | 
				
			||||||
 | 
					  GPIOPin *cs_pin_{NullPin::NULL_PIN};
 | 
				
			||||||
 | 
					  static SPIDelegate *const NULL_DELEGATE;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A dummy SPIDelegate that complains if it's used.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIDelegateDummy : public SPIDelegate {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDelegateDummy() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t transfer(uint8_t data) override { return 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void begin_transaction() override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * An implementation of SPI that relies only on software toggling of pins.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class SPIDelegateBitBash : public SPIDelegate {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin,
 | 
				
			||||||
 | 
					                     GPIOPin *sdo_pin, GPIOPin *sdi_pin)
 | 
				
			||||||
 | 
					      : SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) {
 | 
				
			||||||
 | 
					    // this calculation is pretty meaningless except at very low bit rates.
 | 
				
			||||||
 | 
					    this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL;
 | 
				
			||||||
 | 
					    this->clock_polarity_ = Utility::get_polarity(this->mode_);
 | 
				
			||||||
 | 
					    this->clock_phase_ = Utility::get_phase(this->mode_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t transfer(uint8_t data) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  GPIOPin *clk_pin_;
 | 
				
			||||||
 | 
					  GPIOPin *sdo_pin_;
 | 
				
			||||||
 | 
					  GPIOPin *sdi_pin_;
 | 
				
			||||||
 | 
					  uint32_t last_transition_{0};
 | 
				
			||||||
 | 
					  uint32_t wait_cycle_;
 | 
				
			||||||
 | 
					  SPIClockPolarity clock_polarity_;
 | 
				
			||||||
 | 
					  SPIClockPhase clock_phase_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void HOT cycle_clock_() {
 | 
				
			||||||
 | 
					    while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_)
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    this->last_transition_ += this->wait_cycle_;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIBus {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIBus() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) {
 | 
				
			||||||
 | 
					    return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  virtual bool is_hw() { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  GPIOPin *clk_pin_{};
 | 
				
			||||||
 | 
					  GPIOPin *sdo_pin_{};
 | 
				
			||||||
 | 
					  GPIOPin *sdi_pin_{};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIComponent : public Component {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
 | 
				
			||||||
 | 
					                               GPIOPin *cs_pin);
 | 
				
			||||||
 | 
					  void unregister_device(SPIClient *device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_interface(SPIInterface interface) {
 | 
				
			||||||
 | 
					    this->interface_ = interface;
 | 
				
			||||||
 | 
					    this->using_hw_ = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_interface_name(const char *name) { this->interface_name_ = name; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  GPIOPin *clk_pin_{nullptr};
 | 
				
			||||||
 | 
					  GPIOPin *sdi_pin_{nullptr};
 | 
				
			||||||
 | 
					  GPIOPin *sdo_pin_{nullptr};
 | 
				
			||||||
 | 
					  SPIInterface interface_{};
 | 
				
			||||||
 | 
					  bool using_hw_{false};
 | 
				
			||||||
 | 
					  const char *interface_name_{nullptr};
 | 
				
			||||||
 | 
					  SPIBus *spi_bus_{};
 | 
				
			||||||
 | 
					  std::map<SPIClient *, SPIDelegate *> devices_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Base class for SPIDevice, un-templated.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class SPIClient {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate)
 | 
				
			||||||
 | 
					      : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  virtual void spi_setup() {
 | 
				
			||||||
 | 
					    this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  virtual void spi_teardown() {
 | 
				
			||||||
 | 
					    this->parent_->unregister_device(this);
 | 
				
			||||||
 | 
					    this->delegate_ = SPIDelegate::NULL_DELEGATE;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool spi_is_ready() { return this->delegate_->is_ready(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
 | 
				
			||||||
 | 
					  SPIMode mode_{MODE0};
 | 
				
			||||||
 | 
					  uint32_t data_rate_{1000000};
 | 
				
			||||||
 | 
					  SPIComponent *parent_{nullptr};
 | 
				
			||||||
 | 
					  GPIOPin *cs_{nullptr};
 | 
				
			||||||
 | 
					  SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * The SPIDevice is what components using the SPI will create.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @tparam BIT_ORDER
 | 
				
			||||||
 | 
					 * @tparam CLOCK_POLARITY
 | 
				
			||||||
 | 
					 * @tparam CLOCK_PHASE
 | 
				
			||||||
 | 
					 * @tparam DATA_RATE
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE>
 | 
				
			||||||
 | 
					class SPIDevice : public SPIClient {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) {
 | 
				
			||||||
 | 
					    this->set_spi_parent(parent);
 | 
				
			||||||
 | 
					    this->set_cs_pin(cs_pin);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void spi_setup() override { SPIClient::spi_setup(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void spi_teardown() override { SPIClient::spi_teardown(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_bit_order(SPIBitOrder order) {
 | 
				
			||||||
 | 
					    this->bit_order_ = order;
 | 
				
			||||||
 | 
					    esph_log_d("spi.h", "bit order set to %d", order);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_mode(SPIMode mode) { this->mode_ = mode; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t read_byte() { return this->delegate_->transfer(0); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // the driver will byte-swap if required.
 | 
				
			||||||
 | 
					  void write_byte16(uint16_t data) { this->delegate_->write16(data); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // avoid use of this if possible. It's inefficient and ugly.
 | 
				
			||||||
 | 
					  void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void enable() { this->delegate_->begin_transaction(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void disable() { this->delegate_->end_transaction(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); }
 | 
					  template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); }
 | 
					  void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t transfer_byte(uint8_t data) {
 | 
					 | 
				
			||||||
    return this->parent_->template transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void transfer_array(uint8_t *data, size_t length) {
 | 
					 | 
				
			||||||
    this->parent_->template transfer_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); }
 | 
					  template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  SPIComponent *parent_{nullptr};
 | 
					 | 
				
			||||||
  GPIOPin *cs_{nullptr};
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace spi
 | 
					}  // namespace spi
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										89
									
								
								esphome/components/spi/spi_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								esphome/components/spi/spi_arduino.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					#include "spi.h"
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace spi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "spi-esp-arduino";
 | 
				
			||||||
 | 
					class SPIDelegateHw : public SPIDelegate {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin)
 | 
				
			||||||
 | 
					      : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void begin_transaction() override {
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					    SPISettings const settings(this->data_rate_, static_cast<BitOrder>(this->bit_order_), this->mode_);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    SPISettings const settings(this->data_rate_, this->bit_order_, this->mode_);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    this->channel_->beginTransaction(settings);
 | 
				
			||||||
 | 
					    SPIDelegate::begin_transaction();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void transfer(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void end_transaction() override {
 | 
				
			||||||
 | 
					    this->channel_->endTransaction();
 | 
				
			||||||
 | 
					    SPIDelegate::end_transaction();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t transfer(uint8_t data) override { return this->channel_->transfer(data); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write16(uint16_t data) override { this->channel_->transfer16(data); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					  void write_array(const uint8_t *ptr, size_t length) override {
 | 
				
			||||||
 | 
					    // avoid overwriting the supplied buffer
 | 
				
			||||||
 | 
					    uint8_t *rxbuf = new uint8_t[length];  // NOLINT(cppcoreguidelines-owning-memory)
 | 
				
			||||||
 | 
					    memcpy(rxbuf, ptr, length);
 | 
				
			||||||
 | 
					    this->channel_->transfer((void *) rxbuf, length);
 | 
				
			||||||
 | 
					    delete[] rxbuf;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  void write_array(const uint8_t *ptr, size_t length) override { this->channel_->writeBytes(ptr, length); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void read_array(uint8_t *ptr, size_t length) override { this->channel_->transfer(ptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIInterface channel_{};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIBusHw : public SPIBus {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					    channel->pins(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1);
 | 
				
			||||||
 | 
					    channel->begin();
 | 
				
			||||||
 | 
					#endif  // USE_ESP8266
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					    channel->begin(Utility::get_pin_no(clk), Utility::get_pin_no(sdi), Utility::get_pin_no(sdo), -1);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_RP2040
 | 
				
			||||||
 | 
					    if (Utility::get_pin_no(sdi) != -1)
 | 
				
			||||||
 | 
					      channel->setRX(Utility::get_pin_no(sdi));
 | 
				
			||||||
 | 
					    if (Utility::get_pin_no(sdo) != -1)
 | 
				
			||||||
 | 
					      channel->setTX(Utility::get_pin_no(sdo));
 | 
				
			||||||
 | 
					    channel->setSCK(Utility::get_pin_no(clk));
 | 
				
			||||||
 | 
					    channel->begin();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override {
 | 
				
			||||||
 | 
					    return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIInterface channel_{};
 | 
				
			||||||
 | 
					  bool is_hw() override { return true; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
 | 
				
			||||||
 | 
					  return new SPIBusHw(clk, sdo, sdi, interface);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ARDUINO
 | 
				
			||||||
 | 
					}  // namespace spi
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										163
									
								
								esphome/components/spi/spi_esp_idf.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								esphome/components/spi/spi_esp_idf.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
				
			|||||||
 | 
					#include "spi.h"
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace spi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
 | 
					static const char *const TAG = "spi-esp-idf";
 | 
				
			||||||
 | 
					static const size_t MAX_TRANSFER_SIZE = 4092;  // dictated by ESP-IDF API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIDelegateHw : public SPIDelegate {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
 | 
				
			||||||
 | 
					                bool write_only)
 | 
				
			||||||
 | 
					      : SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) {
 | 
				
			||||||
 | 
					    spi_device_interface_config_t config = {};
 | 
				
			||||||
 | 
					    config.mode = static_cast<uint8_t>(mode);
 | 
				
			||||||
 | 
					    config.clock_speed_hz = static_cast<int>(data_rate);
 | 
				
			||||||
 | 
					    config.spics_io_num = -1;
 | 
				
			||||||
 | 
					    config.flags = 0;
 | 
				
			||||||
 | 
					    config.queue_size = 1;
 | 
				
			||||||
 | 
					    config.pre_cb = nullptr;
 | 
				
			||||||
 | 
					    config.post_cb = nullptr;
 | 
				
			||||||
 | 
					    if (bit_order == BIT_ORDER_LSB_FIRST)
 | 
				
			||||||
 | 
					      config.flags |= SPI_DEVICE_BIT_LSBFIRST;
 | 
				
			||||||
 | 
					    if (write_only)
 | 
				
			||||||
 | 
					      config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY;
 | 
				
			||||||
 | 
					    esp_err_t const err = spi_bus_add_device(channel, &config, &this->handle_);
 | 
				
			||||||
 | 
					    if (err != ESP_OK)
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Add device failed - err %X", err);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool is_ready() override { return this->handle_ != nullptr; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void begin_transaction() override {
 | 
				
			||||||
 | 
					    if (this->is_ready()) {
 | 
				
			||||||
 | 
					      if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK)
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "Failed to acquire SPI bus");
 | 
				
			||||||
 | 
					      SPIDelegate::begin_transaction();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "spi_setup called before initialisation");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void end_transaction() override {
 | 
				
			||||||
 | 
					    if (this->is_ready()) {
 | 
				
			||||||
 | 
					      SPIDelegate::end_transaction();
 | 
				
			||||||
 | 
					      spi_device_release_bus(this->handle_);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ~SPIDelegateHw() override {
 | 
				
			||||||
 | 
					    esp_err_t const err = spi_bus_remove_device(this->handle_);
 | 
				
			||||||
 | 
					    if (err != ESP_OK)
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Remove device failed - err %X", err);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // do a transfer. either txbuf or rxbuf (but not both) may be null.
 | 
				
			||||||
 | 
					  // transfers above the maximum size will be split.
 | 
				
			||||||
 | 
					  // TODO - make use of the queue for interrupt transfers to provide a (short) pipeline of blocks
 | 
				
			||||||
 | 
					  // when splitting is required.
 | 
				
			||||||
 | 
					  void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) override {
 | 
				
			||||||
 | 
					    if (rxbuf != nullptr && this->write_only_) {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Attempted read from write-only channel");
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    spi_transaction_t desc = {};
 | 
				
			||||||
 | 
					    desc.flags = 0;
 | 
				
			||||||
 | 
					    while (length != 0) {
 | 
				
			||||||
 | 
					      size_t const partial = std::min(length, MAX_TRANSFER_SIZE);
 | 
				
			||||||
 | 
					      desc.length = partial * 8;
 | 
				
			||||||
 | 
					      desc.rxlength = this->write_only_ ? 0 : partial * 8;
 | 
				
			||||||
 | 
					      desc.tx_buffer = txbuf;
 | 
				
			||||||
 | 
					      desc.rx_buffer = rxbuf;
 | 
				
			||||||
 | 
					      esp_err_t const err = spi_device_transmit(this->handle_, &desc);
 | 
				
			||||||
 | 
					      if (err != ESP_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "Transmit failed - err %X", err);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      length -= partial;
 | 
				
			||||||
 | 
					      if (txbuf != nullptr)
 | 
				
			||||||
 | 
					        txbuf += partial;
 | 
				
			||||||
 | 
					      if (rxbuf != nullptr)
 | 
				
			||||||
 | 
					        rxbuf += partial;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void transfer(uint8_t *ptr, size_t length) override { this->transfer(ptr, ptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t transfer(uint8_t data) override {
 | 
				
			||||||
 | 
					    uint8_t rxbuf;
 | 
				
			||||||
 | 
					    this->transfer(&data, &rxbuf, 1);
 | 
				
			||||||
 | 
					    return rxbuf;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write16(uint16_t data) override {
 | 
				
			||||||
 | 
					    if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
 | 
				
			||||||
 | 
					      uint16_t txbuf = SPI_SWAP_DATA_TX(data, 16);
 | 
				
			||||||
 | 
					      this->transfer((uint8_t *) &txbuf, nullptr, 2);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->transfer((uint8_t *) &data, nullptr, 2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_array(const uint8_t *ptr, size_t length) override { this->transfer(ptr, nullptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_array16(const uint16_t *data, size_t length) override {
 | 
				
			||||||
 | 
					    if (this->bit_order_ == BIT_ORDER_LSB_FIRST) {
 | 
				
			||||||
 | 
					      this->write_array((uint8_t *) data, length * 2);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      uint16_t buffer[MAX_TRANSFER_SIZE / 2];
 | 
				
			||||||
 | 
					      while (length != 0) {
 | 
				
			||||||
 | 
					        size_t const partial = std::min(length, MAX_TRANSFER_SIZE / 2);
 | 
				
			||||||
 | 
					        for (size_t i = 0; i != partial; i++) {
 | 
				
			||||||
 | 
					          buffer[i] = SPI_SWAP_DATA_TX(*data++, 16);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this->write_array((const uint8_t *) buffer, partial * 2);
 | 
				
			||||||
 | 
					        length -= partial;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIInterface channel_{};
 | 
				
			||||||
 | 
					  spi_device_handle_t handle_{};
 | 
				
			||||||
 | 
					  bool write_only_{false};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIBusHw : public SPIBus {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  SPIBusHw(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, SPIInterface channel) : SPIBus(clk, sdo, sdi), channel_(channel) {
 | 
				
			||||||
 | 
					    spi_bus_config_t buscfg = {};
 | 
				
			||||||
 | 
					    buscfg.mosi_io_num = Utility::get_pin_no(sdo);
 | 
				
			||||||
 | 
					    buscfg.miso_io_num = Utility::get_pin_no(sdi);
 | 
				
			||||||
 | 
					    buscfg.sclk_io_num = Utility::get_pin_no(clk);
 | 
				
			||||||
 | 
					    buscfg.quadwp_io_num = -1;
 | 
				
			||||||
 | 
					    buscfg.quadhd_io_num = -1;
 | 
				
			||||||
 | 
					    buscfg.max_transfer_sz = MAX_TRANSFER_SIZE;
 | 
				
			||||||
 | 
					    auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO);
 | 
				
			||||||
 | 
					    if (err != ESP_OK)
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Bus init failed - err %X", err);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override {
 | 
				
			||||||
 | 
					    return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin,
 | 
				
			||||||
 | 
					                             Utility::get_pin_no(this->sdi_pin_) == -1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  SPIInterface channel_{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool is_hw() override { return true; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) {
 | 
				
			||||||
 | 
					  return new SPIBusHw(clk, sdo, sdi, interface);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}  // namespace spi
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										49
									
								
								esphome/components/spi_device/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								esphome/components/spi_device/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import spi
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_DATA_RATE, CONF_MODE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["spi"]
 | 
				
			||||||
 | 
					CODEOWNERS = ["@clydebarrow"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MULTI_CONF = True
 | 
				
			||||||
 | 
					spi_device_ns = cg.esphome_ns.namespace("spi_device")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					spi_device = spi_device_ns.class_("SPIDeviceComponent", cg.Component, spi.SPIDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mode = spi.spi_ns.enum("SPIMode")
 | 
				
			||||||
 | 
					MODES = {
 | 
				
			||||||
 | 
					    "0": Mode.MODE0,
 | 
				
			||||||
 | 
					    "1": Mode.MODE1,
 | 
				
			||||||
 | 
					    "2": Mode.MODE2,
 | 
				
			||||||
 | 
					    "3": Mode.MODE3,
 | 
				
			||||||
 | 
					    "MODE0": Mode.MODE0,
 | 
				
			||||||
 | 
					    "MODE1": Mode.MODE1,
 | 
				
			||||||
 | 
					    "MODE2": Mode.MODE2,
 | 
				
			||||||
 | 
					    "MODE3": Mode.MODE3,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BitOrder = spi.spi_ns.enum("SPIBitOrder")
 | 
				
			||||||
 | 
					ORDERS = {
 | 
				
			||||||
 | 
					    "msb_first": BitOrder.BIT_ORDER_MSB_FIRST,
 | 
				
			||||||
 | 
					    "lsb_first": BitOrder.BIT_ORDER_LSB_FIRST,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					CONF_BIT_ORDER = "bit_order"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_ID): cv.declare_id(spi_device),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					).extend(spi.spi_device_schema(False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
 | 
				
			||||||
 | 
					    cg.add(var.set_mode(config[CONF_MODE]))
 | 
				
			||||||
 | 
					    cg.add(var.set_bit_order(config[CONF_BIT_ORDER]))
 | 
				
			||||||
 | 
					    await spi.register_spi_device(var, config)
 | 
				
			||||||
							
								
								
									
										30
									
								
								esphome/components/spi_device/spi_device.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/spi_device/spi_device.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#include "spi_device.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace spi_device {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "spi_device";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SPIDeviceComponent::setup() {
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Setting up SPIDevice...");
 | 
				
			||||||
 | 
					  this->spi_setup();
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "SPIDevice started!");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SPIDeviceComponent::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "SPIDevice");
 | 
				
			||||||
 | 
					  LOG_PIN("  CS pin: ", this->cs_);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Mode: %d", this->mode_);
 | 
				
			||||||
 | 
					  if (this->data_rate_ < 1000000) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "  Data rate: %dkHz", this->data_rate_ / 1000);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "  Data rate: %dMHz", this->data_rate_ / 1000000);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float SPIDeviceComponent::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace spi_device
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										22
									
								
								esphome/components/spi_device/spi_device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/spi_device/spi_device.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/spi/spi.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace spi_device {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SPIDeviceComponent : public Component,
 | 
				
			||||||
 | 
					                           public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
 | 
				
			||||||
 | 
					                                                 spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace spi_device
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										2
									
								
								esphome/components/spi_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								esphome/components/spi_led_strip/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@clydebarrow"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["spi"]
 | 
				
			||||||
							
								
								
									
										27
									
								
								esphome/components/spi_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/spi_led_strip/light.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import light
 | 
				
			||||||
 | 
					from esphome.components import spi
 | 
				
			||||||
 | 
					from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_DATA_RATE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip")
 | 
				
			||||||
 | 
					SpiLedStrip = spi_led_strip_ns.class_(
 | 
				
			||||||
 | 
					    "SpiLedStrip", light.AddressableLight, spi.SPIDevice
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					).extend(spi.spi_device_schema(False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
				
			||||||
 | 
					    cg.add(var.set_data_rate(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]]))
 | 
				
			||||||
 | 
					    cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
 | 
				
			||||||
 | 
					    await light.register_light(var, config)
 | 
				
			||||||
 | 
					    await spi.register_spi_device(var, config)
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
							
								
								
									
										91
									
								
								esphome/components/spi_led_strip/spi_led_strip.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/spi_led_strip/spi_led_strip.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/components/light/addressable_light.h"
 | 
				
			||||||
 | 
					#include "esphome/components/spi/spi.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace spi_led_strip {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "spi_led_strip";
 | 
				
			||||||
 | 
					class SpiLedStrip : public light::AddressableLight,
 | 
				
			||||||
 | 
					                    public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
 | 
				
			||||||
 | 
					                                          spi::DATA_RATE_1MHZ> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() { this->spi_setup(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int32_t size() const override { return this->num_leds_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  light::LightTraits get_traits() override {
 | 
				
			||||||
 | 
					    auto traits = light::LightTraits();
 | 
				
			||||||
 | 
					    traits.set_supported_color_modes({light::ColorMode::RGB});
 | 
				
			||||||
 | 
					    return traits;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  void set_num_leds(uint16_t num_leds) {
 | 
				
			||||||
 | 
					    this->num_leds_ = num_leds;
 | 
				
			||||||
 | 
					    ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
 | 
				
			||||||
 | 
					    this->buffer_size_ = num_leds * 4 + 8;
 | 
				
			||||||
 | 
					    this->buf_ = allocator.allocate(this->buffer_size_);
 | 
				
			||||||
 | 
					    if (this->buf_ == nullptr) {
 | 
				
			||||||
 | 
					      esph_log_e(TAG, "Failed to allocate buffer of size %u", this->buffer_size_);
 | 
				
			||||||
 | 
					      this->mark_failed();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this->effect_data_ = allocator.allocate(num_leds);
 | 
				
			||||||
 | 
					    if (this->effect_data_ == nullptr) {
 | 
				
			||||||
 | 
					      esph_log_e(TAG, "Failed to allocate effect data of size %u", num_leds);
 | 
				
			||||||
 | 
					      this->mark_failed();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    memset(this->buf_, 0xFF, this->buffer_size_);
 | 
				
			||||||
 | 
					    memset(this->buf_, 0, 4);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void dump_config() {
 | 
				
			||||||
 | 
					    esph_log_config(TAG, "SPI LED Strip:");
 | 
				
			||||||
 | 
					    esph_log_config(TAG, "  LEDs: %d", this->num_leds_);
 | 
				
			||||||
 | 
					    if (this->data_rate_ >= spi::DATA_RATE_1MHZ)
 | 
				
			||||||
 | 
					      esph_log_config(TAG, "  Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000));
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      esph_log_config(TAG, "  Data rate: %ukHz", (unsigned) (this->data_rate_ / 1000));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void write_state(light::LightState *state) override {
 | 
				
			||||||
 | 
					    if (this->is_failed())
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
 | 
				
			||||||
 | 
					      char strbuf[49];
 | 
				
			||||||
 | 
					      size_t len = std::min(this->buffer_size_, (size_t) (sizeof(strbuf) - 1) / 3);
 | 
				
			||||||
 | 
					      memset(strbuf, 0, sizeof(strbuf));
 | 
				
			||||||
 | 
					      for (size_t i = 0; i != len; i++) {
 | 
				
			||||||
 | 
					        sprintf(strbuf + i * 3, "%02X ", this->buf_[i]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      esph_log_v(TAG, "write_state: buf = %s", strbuf);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->enable();
 | 
				
			||||||
 | 
					    this->write_array(this->buf_, this->buffer_size_);
 | 
				
			||||||
 | 
					    this->disable();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void clear_effect_data() override {
 | 
				
			||||||
 | 
					    for (int i = 0; i < this->size(); i++)
 | 
				
			||||||
 | 
					      this->effect_data_[i] = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  light::ESPColorView get_view_internal(int32_t index) const override {
 | 
				
			||||||
 | 
					    size_t pos = index * 4 + 5;
 | 
				
			||||||
 | 
					    return {this->buf_ + pos + 2,       this->buf_ + pos + 1, this->buf_ + pos + 0, nullptr,
 | 
				
			||||||
 | 
					            this->effect_data_ + index, &this->correction_};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size_t buffer_size_{};
 | 
				
			||||||
 | 
					  uint8_t *effect_data_{nullptr};
 | 
				
			||||||
 | 
					  uint8_t *buf_{nullptr};
 | 
				
			||||||
 | 
					  uint16_t num_leds_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace spi_led_strip
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -40,6 +40,9 @@ void WiFiComponent::setup() {
 | 
				
			|||||||
  if (this->enable_on_boot_) {
 | 
					  if (this->enable_on_boot_) {
 | 
				
			||||||
    this->start();
 | 
					    this->start();
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					    esp_netif_init();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    this->state_ = WIFI_COMPONENT_STATE_DISABLED;
 | 
					    this->state_ = WIFI_COMPONENT_STATE_DISABLED;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										113
									
								
								esphome/components/wireguard/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								esphome/components/wireguard/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					import re
 | 
				
			||||||
 | 
					import ipaddress
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_TIME_ID,
 | 
				
			||||||
 | 
					    CONF_ADDRESS,
 | 
				
			||||||
 | 
					    CONF_REBOOT_TIMEOUT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.components import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_NETMASK = "netmask"
 | 
				
			||||||
 | 
					CONF_PRIVATE_KEY = "private_key"
 | 
				
			||||||
 | 
					CONF_PEER_ENDPOINT = "peer_endpoint"
 | 
				
			||||||
 | 
					CONF_PEER_PUBLIC_KEY = "peer_public_key"
 | 
				
			||||||
 | 
					CONF_PEER_PORT = "peer_port"
 | 
				
			||||||
 | 
					CONF_PEER_PRESHARED_KEY = "peer_preshared_key"
 | 
				
			||||||
 | 
					CONF_PEER_ALLOWED_IPS = "peer_allowed_ips"
 | 
				
			||||||
 | 
					CONF_PEER_PERSISTENT_KEEPALIVE = "peer_persistent_keepalive"
 | 
				
			||||||
 | 
					CONF_REQUIRE_CONNECTION_TO_PROCEED = "require_connection_to_proceed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["time", "esp32"]
 | 
				
			||||||
 | 
					CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The key validation regex has been described by Jason Donenfeld himself
 | 
				
			||||||
 | 
					# url: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html
 | 
				
			||||||
 | 
					_WG_KEY_REGEX = re.compile(r"^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					wireguard_ns = cg.esphome_ns.namespace("wireguard")
 | 
				
			||||||
 | 
					Wireguard = wireguard_ns.class_("Wireguard", cg.Component, cg.PollingComponent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _wireguard_key(value):
 | 
				
			||||||
 | 
					    if _WG_KEY_REGEX.match(cv.string(value)) is not None:
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					    raise cv.Invalid(f"Invalid WireGuard key: {value}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _cidr_network(value):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        ipaddress.ip_network(value, strict=False)
 | 
				
			||||||
 | 
					    except ValueError as err:
 | 
				
			||||||
 | 
					        raise cv.Invalid(f"Invalid network in CIDR notation: {err}")
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(Wireguard),
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
				
			||||||
 | 
					        cv.Required(CONF_ADDRESS): cv.ipv4,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_NETMASK, default="255.255.255.255"): cv.ipv4,
 | 
				
			||||||
 | 
					        cv.Required(CONF_PRIVATE_KEY): _wireguard_key,
 | 
				
			||||||
 | 
					        cv.Required(CONF_PEER_ENDPOINT): cv.string,
 | 
				
			||||||
 | 
					        cv.Required(CONF_PEER_PUBLIC_KEY): _wireguard_key,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_PEER_PORT, default=51820): cv.port,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_PEER_PRESHARED_KEY): _wireguard_key,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_PEER_ALLOWED_IPS, default=["0.0.0.0/0"]): cv.ensure_list(
 | 
				
			||||||
 | 
					            _cidr_network
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_PEER_PERSISTENT_KEEPALIVE, default=0): cv.Any(
 | 
				
			||||||
 | 
					            cv.positive_time_period_seconds,
 | 
				
			||||||
 | 
					            cv.uint16_t,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(
 | 
				
			||||||
 | 
					            CONF_REBOOT_TIMEOUT, default="15min"
 | 
				
			||||||
 | 
					        ): cv.positive_time_period_milliseconds,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_REQUIRE_CONNECTION_TO_PROCEED, default=False): cv.boolean,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					).extend(cv.polling_component_schema("10s"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_address(str(config[CONF_ADDRESS])))
 | 
				
			||||||
 | 
					    cg.add(var.set_netmask(str(config[CONF_NETMASK])))
 | 
				
			||||||
 | 
					    cg.add(var.set_private_key(config[CONF_PRIVATE_KEY]))
 | 
				
			||||||
 | 
					    cg.add(var.set_peer_endpoint(config[CONF_PEER_ENDPOINT]))
 | 
				
			||||||
 | 
					    cg.add(var.set_peer_public_key(config[CONF_PEER_PUBLIC_KEY]))
 | 
				
			||||||
 | 
					    cg.add(var.set_peer_port(config[CONF_PEER_PORT]))
 | 
				
			||||||
 | 
					    cg.add(var.set_keepalive(config[CONF_PEER_PERSISTENT_KEEPALIVE]))
 | 
				
			||||||
 | 
					    cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_PEER_PRESHARED_KEY in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_preshared_key(config[CONF_PEER_PRESHARED_KEY]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    allowed_ips = list(
 | 
				
			||||||
 | 
					        ipaddress.collapse_addresses(
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                ipaddress.ip_network(ip, strict=False)
 | 
				
			||||||
 | 
					                for ip in config[CONF_PEER_ALLOWED_IPS]
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for ip in allowed_ips:
 | 
				
			||||||
 | 
					        cg.add(var.add_allowed_ip(str(ip.network_address), str(ip.netmask)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_srctime(await cg.get_variable(config[CONF_TIME_ID])))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config[CONF_REQUIRE_CONNECTION_TO_PROCEED]:
 | 
				
			||||||
 | 
					        cg.add(var.disable_auto_proceed())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # This flag is added here because the esp_wireguard library statically
 | 
				
			||||||
 | 
					    # set the size of its allowed_ips list at compile time using this value;
 | 
				
			||||||
 | 
					    # the '+1' modifier is relative to the device's own address that will
 | 
				
			||||||
 | 
					    # be automatically added to the provided list.
 | 
				
			||||||
 | 
					    cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}")
 | 
				
			||||||
 | 
					    cg.add_library("droscy/esp_wireguard", "0.3.2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
							
								
								
									
										28
									
								
								esphome/components/wireguard/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/wireguard/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import binary_sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_STATUS,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_CONNECTIVITY,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import Wireguard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_WIREGUARD_ID = "wireguard_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["wireguard"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = {
 | 
				
			||||||
 | 
					    cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard),
 | 
				
			||||||
 | 
					    cv.Optional(CONF_STATUS): binary_sensor.binary_sensor_schema(
 | 
				
			||||||
 | 
					        device_class=DEVICE_CLASS_CONNECTIVITY,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    parent = await cg.get_variable(config[CONF_WIREGUARD_ID])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if status_config := config.get(CONF_STATUS):
 | 
				
			||||||
 | 
					        sens = await binary_sensor.new_binary_sensor(status_config)
 | 
				
			||||||
 | 
					        cg.add(parent.set_status_sensor(sens))
 | 
				
			||||||
							
								
								
									
										30
									
								
								esphome/components/wireguard/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/wireguard/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TIMESTAMP,
 | 
				
			||||||
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import Wireguard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_WIREGUARD_ID = "wireguard_id"
 | 
				
			||||||
 | 
					CONF_LATEST_HANDSHAKE = "latest_handshake"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["wireguard"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = {
 | 
				
			||||||
 | 
					    cv.GenerateID(CONF_WIREGUARD_ID): cv.use_id(Wireguard),
 | 
				
			||||||
 | 
					    cv.Optional(CONF_LATEST_HANDSHAKE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					        device_class=DEVICE_CLASS_TIMESTAMP,
 | 
				
			||||||
 | 
					        entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    parent = await cg.get_variable(config[CONF_WIREGUARD_ID])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if latest_handshake_config := config.get(CONF_LATEST_HANDSHAKE):
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(latest_handshake_config)
 | 
				
			||||||
 | 
					        cg.add(parent.set_handshake_sensor(sens))
 | 
				
			||||||
							
								
								
									
										296
									
								
								esphome/components/wireguard/wireguard.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								esphome/components/wireguard/wireguard.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,296 @@
 | 
				
			|||||||
 | 
					#include "wireguard.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ctime>
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/time.h"
 | 
				
			||||||
 | 
					#include "esphome/components/network/util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_err.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_wireguard.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// includes for resume/suspend wdt
 | 
				
			||||||
 | 
					#if defined(USE_ESP_IDF)
 | 
				
			||||||
 | 
					#include <esp_task_wdt.h>
 | 
				
			||||||
 | 
					#if ESP_IDF_VERSION_MAJOR >= 5
 | 
				
			||||||
 | 
					#include <spi_flash_mmap.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#elif defined(USE_ARDUINO)
 | 
				
			||||||
 | 
					#include <esp32-hal.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace wireguard {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "wireguard";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const LOGMSG_PEER_STATUS = "WireGuard remote peer is %s (latest handshake %s)";
 | 
				
			||||||
 | 
					static const char *const LOGMSG_ONLINE = "online";
 | 
				
			||||||
 | 
					static const char *const LOGMSG_OFFLINE = "offline";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::setup() {
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "initializing WireGuard...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->wg_config_.address = this->address_.c_str();
 | 
				
			||||||
 | 
					  this->wg_config_.private_key = this->private_key_.c_str();
 | 
				
			||||||
 | 
					  this->wg_config_.endpoint = this->peer_endpoint_.c_str();
 | 
				
			||||||
 | 
					  this->wg_config_.public_key = this->peer_public_key_.c_str();
 | 
				
			||||||
 | 
					  this->wg_config_.port = this->peer_port_;
 | 
				
			||||||
 | 
					  this->wg_config_.netmask = this->netmask_.c_str();
 | 
				
			||||||
 | 
					  this->wg_config_.persistent_keepalive = this->keepalive_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->preshared_key_.length() > 0)
 | 
				
			||||||
 | 
					    this->wg_config_.preshared_key = this->preshared_key_.c_str();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this->wg_initialized_ = esp_wireguard_init(&(this->wg_config_), &(this->wg_ctx_));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->wg_initialized_ == ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "WireGuard initialized");
 | 
				
			||||||
 | 
					    this->wg_peer_offline_time_ = millis();
 | 
				
			||||||
 | 
					    this->srctime_->add_on_time_sync_callback(std::bind(&Wireguard::start_connection_, this));
 | 
				
			||||||
 | 
					    this->defer(std::bind(&Wireguard::start_connection_, this));  // defer to avoid blocking setup
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "cannot initialize WireGuard, error code %d", this->wg_initialized_);
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::loop() {
 | 
				
			||||||
 | 
					  if ((this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) && (!network::is_connected())) {
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG, "local network connection has been lost, stopping WireGuard...");
 | 
				
			||||||
 | 
					    this->stop_connection_();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::update() {
 | 
				
			||||||
 | 
					  bool peer_up = this->is_peer_up();
 | 
				
			||||||
 | 
					  time_t lhs = this->get_latest_handshake();
 | 
				
			||||||
 | 
					  bool lhs_updated = (lhs > this->latest_saved_handshake_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "handshake: latest=%.0f, saved=%.0f, updated=%d", (double) lhs, (double) this->latest_saved_handshake_,
 | 
				
			||||||
 | 
					           (int) lhs_updated);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (lhs_updated) {
 | 
				
			||||||
 | 
					    this->latest_saved_handshake_ = lhs;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::string latest_handshake =
 | 
				
			||||||
 | 
					      (this->latest_saved_handshake_ > 0)
 | 
				
			||||||
 | 
					          ? ESPTime::from_epoch_local(this->latest_saved_handshake_).strftime("%Y-%m-%d %H:%M:%S %Z")
 | 
				
			||||||
 | 
					          : "timestamp not available";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (peer_up) {
 | 
				
			||||||
 | 
					    if (this->wg_peer_offline_time_ != 0) {
 | 
				
			||||||
 | 
					      ESP_LOGI(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
 | 
				
			||||||
 | 
					      this->wg_peer_offline_time_ = 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_ONLINE, latest_handshake.c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if (this->wg_peer_offline_time_ == 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
 | 
				
			||||||
 | 
					      this->wg_peer_offline_time_ = millis();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str());
 | 
				
			||||||
 | 
					      this->start_connection_();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // check reboot timeout every time the peer is down
 | 
				
			||||||
 | 
					    if (this->reboot_timeout_ > 0) {
 | 
				
			||||||
 | 
					      if (millis() - this->wg_peer_offline_time_ > this->reboot_timeout_) {
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "WireGuard remote peer is unreachable, rebooting...");
 | 
				
			||||||
 | 
					        App.reboot();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  if (this->status_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					    this->status_sensor_->publish_state(peer_up);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					  if (this->handshake_sensor_ != nullptr && lhs_updated) {
 | 
				
			||||||
 | 
					    this->handshake_sensor_->publish_state((double) this->latest_saved_handshake_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "WireGuard:");
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Address: %s", this->address_.c_str());
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Netmask: %s", this->netmask_.c_str());
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Private Key: " LOG_SECRET("%s"), mask_key(this->private_key_).c_str());
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Endpoint: " LOG_SECRET("%s"), this->peer_endpoint_.c_str());
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Port: " LOG_SECRET("%d"), this->peer_port_);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str());
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Pre-shared Key: " LOG_SECRET("%s"),
 | 
				
			||||||
 | 
					                (this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE"));
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Allowed IPs:");
 | 
				
			||||||
 | 
					  for (auto &allowed_ip : this->allowed_ips_) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "    - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Peer Persistent Keepalive: %d%s", this->keepalive_,
 | 
				
			||||||
 | 
					                (this->keepalive_ > 0 ? "s" : " (DISABLED)"));
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Reboot Timeout: %d%s", (this->reboot_timeout_ / 1000),
 | 
				
			||||||
 | 
					                (this->reboot_timeout_ != 0 ? "s" : " (DISABLED)"));
 | 
				
			||||||
 | 
					  // be careful: if proceed_allowed_ is true, require connection is false
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Require Connection to Proceed: %s", (this->proceed_allowed_ ? "NO" : "YES"));
 | 
				
			||||||
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::on_shutdown() { this->stop_connection_(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Wireguard::can_proceed() { return (this->proceed_allowed_ || this->is_peer_up()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Wireguard::is_peer_up() const {
 | 
				
			||||||
 | 
					  return (this->wg_initialized_ == ESP_OK) && (this->wg_connected_ == ESP_OK) &&
 | 
				
			||||||
 | 
					         (esp_wireguardif_peer_is_up(&(this->wg_ctx_)) == ESP_OK);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					time_t Wireguard::get_latest_handshake() const {
 | 
				
			||||||
 | 
					  time_t result;
 | 
				
			||||||
 | 
					  if (esp_wireguard_latest_handshake(&(this->wg_ctx_), &result) != ESP_OK) {
 | 
				
			||||||
 | 
					    result = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::set_address(const std::string &address) { this->address_ = address; }
 | 
				
			||||||
 | 
					void Wireguard::set_netmask(const std::string &netmask) { this->netmask_ = netmask; }
 | 
				
			||||||
 | 
					void Wireguard::set_private_key(const std::string &key) { this->private_key_ = key; }
 | 
				
			||||||
 | 
					void Wireguard::set_peer_endpoint(const std::string &endpoint) { this->peer_endpoint_ = endpoint; }
 | 
				
			||||||
 | 
					void Wireguard::set_peer_public_key(const std::string &key) { this->peer_public_key_ = key; }
 | 
				
			||||||
 | 
					void Wireguard::set_peer_port(const uint16_t port) { this->peer_port_ = port; }
 | 
				
			||||||
 | 
					void Wireguard::set_preshared_key(const std::string &key) { this->preshared_key_ = key; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::add_allowed_ip(const std::string &ip, const std::string &netmask) {
 | 
				
			||||||
 | 
					  this->allowed_ips_.emplace_back(ip, netmask);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; }
 | 
				
			||||||
 | 
					void Wireguard::set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; }
 | 
				
			||||||
 | 
					void Wireguard::set_srctime(time::RealTimeClock *srctime) { this->srctime_ = srctime; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					void Wireguard::set_status_sensor(binary_sensor::BinarySensor *sensor) { this->status_sensor_ = sensor; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					void Wireguard::set_handshake_sensor(sensor::Sensor *sensor) { this->handshake_sensor_ = sensor; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::disable_auto_proceed() { this->proceed_allowed_ = false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::start_connection_() {
 | 
				
			||||||
 | 
					  if (this->wg_initialized_ != ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "cannot start WireGuard, initialization in error with code %d", this->wg_initialized_);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!network::is_connected()) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "WireGuard is waiting for local network connection to be available");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!this->srctime_->now().is_valid()) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "WireGuard is waiting for system time to be synchronized");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->wg_connected_ == ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG, "WireGuard connection already started");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "starting WireGuard connection...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * The function esp_wireguard_connect() contains a DNS resolution
 | 
				
			||||||
 | 
					   * that could trigger the watchdog, so before it we suspend (or
 | 
				
			||||||
 | 
					   * increase the time, it depends on the platform) the wdt and
 | 
				
			||||||
 | 
					   * then we resume the normal timeout.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  suspend_wdt();
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "executing esp_wireguard_connect");
 | 
				
			||||||
 | 
					  this->wg_connected_ = esp_wireguard_connect(&(this->wg_ctx_));
 | 
				
			||||||
 | 
					  resume_wdt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->wg_connected_ == ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "WireGuard connection started");
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "cannot start WireGuard connection, error code %d", this->wg_connected_);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "configuring WireGuard allowed IPs list...");
 | 
				
			||||||
 | 
					  bool allowed_ips_ok = true;
 | 
				
			||||||
 | 
					  for (std::tuple<std::string, std::string> ip : this->allowed_ips_) {
 | 
				
			||||||
 | 
					    allowed_ips_ok &=
 | 
				
			||||||
 | 
					        (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), std::get<0>(ip).c_str(), std::get<1>(ip).c_str()) == ESP_OK);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (allowed_ips_ok) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "allowed IPs list configured correctly");
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "cannot configure WireGuard allowed IPs list, aborting...");
 | 
				
			||||||
 | 
					    this->stop_connection_();
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Wireguard::stop_connection_() {
 | 
				
			||||||
 | 
					  if (this->wg_initialized_ == ESP_OK && this->wg_connected_ == ESP_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "stopping WireGuard connection...");
 | 
				
			||||||
 | 
					    esp_wireguard_disconnect(&(this->wg_ctx_));
 | 
				
			||||||
 | 
					    this->wg_connected_ = ESP_FAIL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void suspend_wdt() {
 | 
				
			||||||
 | 
					#if defined(USE_ESP_IDF)
 | 
				
			||||||
 | 
					#if ESP_IDF_VERSION_MAJOR >= 5
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15000 ms");
 | 
				
			||||||
 | 
					  esp_task_wdt_config_t wdtc;
 | 
				
			||||||
 | 
					  wdtc.timeout_ms = 15000;
 | 
				
			||||||
 | 
					  wdtc.idle_core_mask = 0;
 | 
				
			||||||
 | 
					  wdtc.trigger_panic = false;
 | 
				
			||||||
 | 
					  esp_task_wdt_reconfigure(&wdtc);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "temporarily increasing wdt timeout to 15 seconds");
 | 
				
			||||||
 | 
					  esp_task_wdt_init(15, false);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#elif defined(USE_ARDUINO)
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "temporarily disabling the wdt");
 | 
				
			||||||
 | 
					  disableLoopWDT();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void resume_wdt() {
 | 
				
			||||||
 | 
					#if defined(USE_ESP_IDF)
 | 
				
			||||||
 | 
					#if ESP_IDF_VERSION_MAJOR >= 5
 | 
				
			||||||
 | 
					  wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
 | 
				
			||||||
 | 
					  esp_task_wdt_reconfigure(&wdtc);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "wdt resumed with %d ms timeout", wdtc.timeout_ms);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "wdt resumed with %d seconds timeout", CONFIG_ESP_TASK_WDT_TIMEOUT_S);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#elif defined(USE_ARDUINO)
 | 
				
			||||||
 | 
					  enableLoopWDT();
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "wdt resumed");
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string mask_key(const std::string &key) { return (key.substr(0, 5) + "[...]="); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace wireguard
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										122
									
								
								esphome/components/wireguard/wireguard.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/wireguard/wireguard.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ctime>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <tuple>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_wireguard.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace wireguard {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Wireguard : public PollingComponent {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  void on_shutdown() override;
 | 
				
			||||||
 | 
					  bool can_proceed() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return esphome::setup_priority::BEFORE_CONNECTION; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_address(const std::string &address);
 | 
				
			||||||
 | 
					  void set_netmask(const std::string &netmask);
 | 
				
			||||||
 | 
					  void set_private_key(const std::string &key);
 | 
				
			||||||
 | 
					  void set_peer_endpoint(const std::string &endpoint);
 | 
				
			||||||
 | 
					  void set_peer_public_key(const std::string &key);
 | 
				
			||||||
 | 
					  void set_peer_port(uint16_t port);
 | 
				
			||||||
 | 
					  void set_preshared_key(const std::string &key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void add_allowed_ip(const std::string &ip, const std::string &netmask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_keepalive(uint16_t seconds);
 | 
				
			||||||
 | 
					  void set_reboot_timeout(uint32_t seconds);
 | 
				
			||||||
 | 
					  void set_srctime(time::RealTimeClock *srctime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  void set_status_sensor(binary_sensor::BinarySensor *sensor);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					  void set_handshake_sensor(sensor::Sensor *sensor);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Block the setup step until peer is connected.
 | 
				
			||||||
 | 
					  void disable_auto_proceed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool is_peer_up() const;
 | 
				
			||||||
 | 
					  time_t get_latest_handshake() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  std::string address_;
 | 
				
			||||||
 | 
					  std::string netmask_;
 | 
				
			||||||
 | 
					  std::string private_key_;
 | 
				
			||||||
 | 
					  std::string peer_endpoint_;
 | 
				
			||||||
 | 
					  std::string peer_public_key_;
 | 
				
			||||||
 | 
					  std::string preshared_key_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::vector<std::tuple<std::string, std::string>> allowed_ips_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint16_t peer_port_;
 | 
				
			||||||
 | 
					  uint16_t keepalive_;
 | 
				
			||||||
 | 
					  uint32_t reboot_timeout_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  time::RealTimeClock *srctime_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  binary_sensor::BinarySensor *status_sensor_ = nullptr;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_SENSOR
 | 
				
			||||||
 | 
					  sensor::Sensor *handshake_sensor_ = nullptr;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Set to false to block the setup step until peer is connected.
 | 
				
			||||||
 | 
					  bool proceed_allowed_ = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  wireguard_config_t wg_config_ = ESP_WIREGUARD_CONFIG_DEFAULT();
 | 
				
			||||||
 | 
					  wireguard_ctx_t wg_ctx_ = ESP_WIREGUARD_CONTEXT_DEFAULT();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  esp_err_t wg_initialized_ = ESP_FAIL;
 | 
				
			||||||
 | 
					  esp_err_t wg_connected_ = ESP_FAIL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The last time the remote peer become offline.
 | 
				
			||||||
 | 
					  uint32_t wg_peer_offline_time_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** \brief The latest saved handshake.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * This is used to save (and log) the latest completed handshake even
 | 
				
			||||||
 | 
					   * after a full refresh of the wireguard keys (for example after a
 | 
				
			||||||
 | 
					   * stop/start connection cycle).
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  time_t latest_saved_handshake_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void start_connection_();
 | 
				
			||||||
 | 
					  void stop_connection_();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// These are used for possibly long DNS resolution to temporarily suspend the watchdog
 | 
				
			||||||
 | 
					void suspend_wdt();
 | 
				
			||||||
 | 
					void resume_wdt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Strip most part of the key only for secure printing
 | 
				
			||||||
 | 
					std::string mask_key(const std::string &key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace wireguard
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
@@ -298,6 +298,9 @@ CONF_GLYPHS = "glyphs"
 | 
				
			|||||||
CONF_GPIO = "gpio"
 | 
					CONF_GPIO = "gpio"
 | 
				
			||||||
CONF_GREEN = "green"
 | 
					CONF_GREEN = "green"
 | 
				
			||||||
CONF_GROUP = "group"
 | 
					CONF_GROUP = "group"
 | 
				
			||||||
 | 
					CONF_GYROSCOPE_X = "gyroscope_x"
 | 
				
			||||||
 | 
					CONF_GYROSCOPE_Y = "gyroscope_y"
 | 
				
			||||||
 | 
					CONF_GYROSCOPE_Z = "gyroscope_z"
 | 
				
			||||||
CONF_HARDWARE_UART = "hardware_uart"
 | 
					CONF_HARDWARE_UART = "hardware_uart"
 | 
				
			||||||
CONF_HEAD = "head"
 | 
					CONF_HEAD = "head"
 | 
				
			||||||
CONF_HEARTBEAT = "heartbeat"
 | 
					CONF_HEARTBEAT = "heartbeat"
 | 
				
			||||||
@@ -538,8 +541,11 @@ CONF_PAYLOAD_AVAILABLE = "payload_available"
 | 
				
			|||||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
 | 
					CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
 | 
				
			||||||
CONF_PERIOD = "period"
 | 
					CONF_PERIOD = "period"
 | 
				
			||||||
CONF_PH = "ph"
 | 
					CONF_PH = "ph"
 | 
				
			||||||
 | 
					CONF_PHASE_A = "phase_a"
 | 
				
			||||||
CONF_PHASE_ANGLE = "phase_angle"
 | 
					CONF_PHASE_ANGLE = "phase_angle"
 | 
				
			||||||
 | 
					CONF_PHASE_B = "phase_b"
 | 
				
			||||||
CONF_PHASE_BALANCER = "phase_balancer"
 | 
					CONF_PHASE_BALANCER = "phase_balancer"
 | 
				
			||||||
 | 
					CONF_PHASE_C = "phase_c"
 | 
				
			||||||
CONF_PIN = "pin"
 | 
					CONF_PIN = "pin"
 | 
				
			||||||
CONF_PIN_A = "pin_a"
 | 
					CONF_PIN_A = "pin_a"
 | 
				
			||||||
CONF_PIN_B = "pin_b"
 | 
					CONF_PIN_B = "pin_b"
 | 
				
			||||||
@@ -864,6 +870,9 @@ ICON_FLOWER = "mdi:flower"
 | 
				
			|||||||
ICON_GAS_CYLINDER = "mdi:gas-cylinder"
 | 
					ICON_GAS_CYLINDER = "mdi:gas-cylinder"
 | 
				
			||||||
ICON_GAUGE = "mdi:gauge"
 | 
					ICON_GAUGE = "mdi:gauge"
 | 
				
			||||||
ICON_GRAIN = "mdi:grain"
 | 
					ICON_GRAIN = "mdi:grain"
 | 
				
			||||||
 | 
					ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise"
 | 
				
			||||||
 | 
					ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise"
 | 
				
			||||||
 | 
					ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise"
 | 
				
			||||||
ICON_HEATING_COIL = "mdi:heating-coil"
 | 
					ICON_HEATING_COIL = "mdi:heating-coil"
 | 
				
			||||||
ICON_KEY_PLUS = "mdi:key-plus"
 | 
					ICON_KEY_PLUS = "mdi:key-plus"
 | 
				
			||||||
ICON_LIGHTBULB = "mdi:lightbulb"
 | 
					ICON_LIGHTBULB = "mdi:lightbulb"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -554,6 +554,12 @@ class EsphomeCore:
 | 
				
			|||||||
    def config_dir(self):
 | 
					    def config_dir(self):
 | 
				
			||||||
        return os.path.dirname(self.config_path)
 | 
					        return os.path.dirname(self.config_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def data_dir(self):
 | 
				
			||||||
 | 
					        if is_ha_addon():
 | 
				
			||||||
 | 
					            return os.path.join("/data")
 | 
				
			||||||
 | 
					        return self.relative_config_path(".esphome")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def config_filename(self):
 | 
					    def config_filename(self):
 | 
				
			||||||
        return os.path.basename(self.config_path)
 | 
					        return os.path.basename(self.config_path)
 | 
				
			||||||
@@ -563,7 +569,7 @@ class EsphomeCore:
 | 
				
			|||||||
        return os.path.join(self.config_dir, path_)
 | 
					        return os.path.join(self.config_dir, path_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def relative_internal_path(self, *path: str) -> str:
 | 
					    def relative_internal_path(self, *path: str) -> str:
 | 
				
			||||||
        return self.relative_config_path(".esphome", *path)
 | 
					        return os.path.join(self.data_dir, *path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def relative_build_path(self, *path):
 | 
					    def relative_build_path(self, *path):
 | 
				
			||||||
        path_ = os.path.expanduser(os.path.join(*path))
 | 
					        path_ = os.path.expanduser(os.path.join(*path))
 | 
				
			||||||
@@ -573,13 +579,9 @@ class EsphomeCore:
 | 
				
			|||||||
        return self.relative_build_path("src", *path)
 | 
					        return self.relative_build_path("src", *path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def relative_pioenvs_path(self, *path):
 | 
					    def relative_pioenvs_path(self, *path):
 | 
				
			||||||
        if is_ha_addon():
 | 
					 | 
				
			||||||
            return os.path.join("/data", self.name, ".pioenvs", *path)
 | 
					 | 
				
			||||||
        return self.relative_build_path(".pioenvs", *path)
 | 
					        return self.relative_build_path(".pioenvs", *path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def relative_piolibdeps_path(self, *path):
 | 
					    def relative_piolibdeps_path(self, *path):
 | 
				
			||||||
        if is_ha_addon():
 | 
					 | 
				
			||||||
            return os.path.join("/data", self.name, ".piolibdeps", *path)
 | 
					 | 
				
			||||||
        return self.relative_build_path(".piolibdeps", *path)
 | 
					        return self.relative_build_path(".piolibdeps", *path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -249,7 +249,11 @@ template<typename... Ts> class RepeatAction : public Action<Ts...> {
 | 
				
			|||||||
  void play_complex(Ts... x) override {
 | 
					  void play_complex(Ts... x) override {
 | 
				
			||||||
    this->num_running_++;
 | 
					    this->num_running_++;
 | 
				
			||||||
    this->var_ = std::make_tuple(x...);
 | 
					    this->var_ = std::make_tuple(x...);
 | 
				
			||||||
    this->then_.play(0, x...);
 | 
					    if (this->count_.value(x...) > 0) {
 | 
				
			||||||
 | 
					      this->then_.play(0, x...);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->play_next_tuple_(this->var_);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void play(Ts... x) override { /* ignore - see play_complex */
 | 
					  void play(Ts... x) override { /* ignore - see play_complex */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,8 +198,8 @@ def preload_core_config(config, result):
 | 
				
			|||||||
    CORE.data[KEY_CORE] = {}
 | 
					    CORE.data[KEY_CORE] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_BUILD_PATH not in conf:
 | 
					    if CONF_BUILD_PATH not in conf:
 | 
				
			||||||
        conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}"
 | 
					        conf[CONF_BUILD_PATH] = f"build/{CORE.name}"
 | 
				
			||||||
    CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH])
 | 
					    CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    has_oldstyle = CONF_PLATFORM in conf
 | 
					    has_oldstyle = CONF_PLATFORM in conf
 | 
				
			||||||
    newstyle_found = [key for key in TARGET_PLATFORMS if key in config]
 | 
					    newstyle_found = [key for key in TARGET_PLATFORMS if key in config]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,11 @@ std::string ESPTime::strftime(const std::string &format) {
 | 
				
			|||||||
  struct tm c_tm = this->to_c_tm();
 | 
					  struct tm c_tm = this->to_c_tm();
 | 
				
			||||||
  size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
 | 
					  size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
 | 
				
			||||||
  while (len == 0) {
 | 
					  while (len == 0) {
 | 
				
			||||||
 | 
					    if (timestr.size() >= 128) {
 | 
				
			||||||
 | 
					      // strftime has failed for reasons unrelated to the size of the buffer
 | 
				
			||||||
 | 
					      // so return a formatting error
 | 
				
			||||||
 | 
					      return "ERROR";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    timestr.resize(timestr.size() * 2);
 | 
					    timestr.resize(timestr.size() * 2);
 | 
				
			||||||
    len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
 | 
					    len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,6 +45,10 @@ struct ESPTime {
 | 
				
			|||||||
   *
 | 
					   *
 | 
				
			||||||
   * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
 | 
					   * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
 | 
				
			||||||
   * microcontrollers.
 | 
					   * microcontrollers.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the
 | 
				
			||||||
 | 
					   * format string contains unsupported specifiers or when the format string doesn't produce any
 | 
				
			||||||
 | 
					   * output.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  std::string strftime(const std::string &format);
 | 
					  std::string strftime(const std::string &format);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -663,7 +663,11 @@ async def process_lambda(
 | 
				
			|||||||
    :param return_type: The return type of the lambda.
 | 
					    :param return_type: The return type of the lambda.
 | 
				
			||||||
    :return: The generated lambda expression.
 | 
					    :return: The generated lambda expression.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent
 | 
					    from esphome.components.globals import (
 | 
				
			||||||
 | 
					        GlobalsComponent,
 | 
				
			||||||
 | 
					        RestoringGlobalsComponent,
 | 
				
			||||||
 | 
					        RestoringGlobalStringComponent,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if value is None:
 | 
					    if value is None:
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
@@ -676,6 +680,7 @@ async def process_lambda(
 | 
				
			|||||||
            and (
 | 
					            and (
 | 
				
			||||||
                full_id.type.inherits_from(GlobalsComponent)
 | 
					                full_id.type.inherits_from(GlobalsComponent)
 | 
				
			||||||
                or full_id.type.inherits_from(RestoringGlobalsComponent)
 | 
					                or full_id.type.inherits_from(RestoringGlobalsComponent)
 | 
				
			||||||
 | 
					                or full_id.type.inherits_from(RestoringGlobalStringComponent)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            parts[i * 3 + 1] = var.value()
 | 
					            parts[i * 3 + 1] = var.value()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ import yaml
 | 
				
			|||||||
from tornado.log import access_log
 | 
					from tornado.log import access_log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome import const, platformio_api, util, yaml_util
 | 
					from esphome import const, platformio_api, util, yaml_util
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.helpers import get_bool_env, mkdir_p, run_system_command
 | 
					from esphome.helpers import get_bool_env, mkdir_p, run_system_command
 | 
				
			||||||
from esphome.storage_json import (
 | 
					from esphome.storage_json import (
 | 
				
			||||||
    EsphomeStorageJSON,
 | 
					    EsphomeStorageJSON,
 | 
				
			||||||
@@ -70,6 +71,7 @@ class DashboardSettings:
 | 
				
			|||||||
            self.password_hash = password_hash(password)
 | 
					            self.password_hash = password_hash(password)
 | 
				
			||||||
        self.config_dir = args.configuration
 | 
					        self.config_dir = args.configuration
 | 
				
			||||||
        self.absolute_config_dir = Path(self.config_dir).resolve()
 | 
					        self.absolute_config_dir = Path(self.config_dir).resolve()
 | 
				
			||||||
 | 
					        CORE.config_path = os.path.join(self.config_dir, ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def relative_url(self):
 | 
					    def relative_url(self):
 | 
				
			||||||
@@ -534,13 +536,16 @@ class DownloadListRequestHandler(BaseHandler):
 | 
				
			|||||||
    @authenticated
 | 
					    @authenticated
 | 
				
			||||||
    @bind_config
 | 
					    @bind_config
 | 
				
			||||||
    def get(self, configuration=None):
 | 
					    def get(self, configuration=None):
 | 
				
			||||||
        storage_path = ext_storage_path(settings.config_dir, configuration)
 | 
					        storage_path = ext_storage_path(configuration)
 | 
				
			||||||
        storage_json = StorageJSON.load(storage_path)
 | 
					        storage_json = StorageJSON.load(storage_path)
 | 
				
			||||||
        if storage_json is None:
 | 
					        if storage_json is None:
 | 
				
			||||||
            self.send_error(404)
 | 
					            self.send_error(404)
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from esphome.components.esp32 import get_download_types as esp32_types
 | 
					        from esphome.components.esp32 import (
 | 
				
			||||||
 | 
					            get_download_types as esp32_types,
 | 
				
			||||||
 | 
					            VARIANTS as ESP32_VARIANTS,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        from esphome.components.esp8266 import get_download_types as esp8266_types
 | 
					        from esphome.components.esp8266 import get_download_types as esp8266_types
 | 
				
			||||||
        from esphome.components.rp2040 import get_download_types as rp2040_types
 | 
					        from esphome.components.rp2040 import get_download_types as rp2040_types
 | 
				
			||||||
        from esphome.components.libretiny import get_download_types as libretiny_types
 | 
					        from esphome.components.libretiny import get_download_types as libretiny_types
 | 
				
			||||||
@@ -551,7 +556,7 @@ class DownloadListRequestHandler(BaseHandler):
 | 
				
			|||||||
            downloads = rp2040_types(storage_json)
 | 
					            downloads = rp2040_types(storage_json)
 | 
				
			||||||
        elif platform == const.PLATFORM_ESP8266:
 | 
					        elif platform == const.PLATFORM_ESP8266:
 | 
				
			||||||
            downloads = esp8266_types(storage_json)
 | 
					            downloads = esp8266_types(storage_json)
 | 
				
			||||||
        elif platform == const.PLATFORM_ESP32:
 | 
					        elif platform.upper() in ESP32_VARIANTS:
 | 
				
			||||||
            downloads = esp32_types(storage_json)
 | 
					            downloads = esp32_types(storage_json)
 | 
				
			||||||
        elif platform == const.PLATFORM_BK72XX:
 | 
					        elif platform == const.PLATFORM_BK72XX:
 | 
				
			||||||
            downloads = libretiny_types(storage_json)
 | 
					            downloads = libretiny_types(storage_json)
 | 
				
			||||||
@@ -574,7 +579,7 @@ class DownloadBinaryRequestHandler(BaseHandler):
 | 
				
			|||||||
    def get(self, configuration=None):
 | 
					    def get(self, configuration=None):
 | 
				
			||||||
        compressed = self.get_argument("compressed", "0") == "1"
 | 
					        compressed = self.get_argument("compressed", "0") == "1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        storage_path = ext_storage_path(settings.config_dir, configuration)
 | 
					        storage_path = ext_storage_path(configuration)
 | 
				
			||||||
        storage_json = StorageJSON.load(storage_path)
 | 
					        storage_json = StorageJSON.load(storage_path)
 | 
				
			||||||
        if storage_json is None:
 | 
					        if storage_json is None:
 | 
				
			||||||
            self.send_error(404)
 | 
					            self.send_error(404)
 | 
				
			||||||
@@ -663,9 +668,7 @@ class DashboardEntry:
 | 
				
			|||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def storage(self) -> Optional[StorageJSON]:
 | 
					    def storage(self) -> Optional[StorageJSON]:
 | 
				
			||||||
        if not self._loaded_storage:
 | 
					        if not self._loaded_storage:
 | 
				
			||||||
            self._storage = StorageJSON.load(
 | 
					            self._storage = StorageJSON.load(ext_storage_path(self.filename))
 | 
				
			||||||
                ext_storage_path(settings.config_dir, self.filename)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            self._loaded_storage = True
 | 
					            self._loaded_storage = True
 | 
				
			||||||
        return self._storage
 | 
					        return self._storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1041,9 +1044,9 @@ class DeleteRequestHandler(BaseHandler):
 | 
				
			|||||||
    @bind_config
 | 
					    @bind_config
 | 
				
			||||||
    def post(self, configuration=None):
 | 
					    def post(self, configuration=None):
 | 
				
			||||||
        config_file = settings.rel_path(configuration)
 | 
					        config_file = settings.rel_path(configuration)
 | 
				
			||||||
        storage_path = ext_storage_path(settings.config_dir, configuration)
 | 
					        storage_path = ext_storage_path(configuration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        trash_path = trash_storage_path(settings.config_dir)
 | 
					        trash_path = trash_storage_path()
 | 
				
			||||||
        mkdir_p(trash_path)
 | 
					        mkdir_p(trash_path)
 | 
				
			||||||
        shutil.move(config_file, os.path.join(trash_path, configuration))
 | 
					        shutil.move(config_file, os.path.join(trash_path, configuration))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1064,7 +1067,7 @@ class UndoDeleteRequestHandler(BaseHandler):
 | 
				
			|||||||
    @bind_config
 | 
					    @bind_config
 | 
				
			||||||
    def post(self, configuration=None):
 | 
					    def post(self, configuration=None):
 | 
				
			||||||
        config_file = settings.rel_path(configuration)
 | 
					        config_file = settings.rel_path(configuration)
 | 
				
			||||||
        trash_path = trash_storage_path(settings.config_dir)
 | 
					        trash_path = trash_storage_path()
 | 
				
			||||||
        shutil.move(os.path.join(trash_path, configuration), config_file)
 | 
					        shutil.move(os.path.join(trash_path, configuration), config_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1322,10 +1325,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def start_web_server(args):
 | 
					def start_web_server(args):
 | 
				
			||||||
    settings.parse_args(args)
 | 
					    settings.parse_args(args)
 | 
				
			||||||
    mkdir_p(settings.rel_path(".esphome"))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if settings.using_auth:
 | 
					    if settings.using_auth:
 | 
				
			||||||
        path = esphome_storage_path(settings.config_dir)
 | 
					        path = esphome_storage_path()
 | 
				
			||||||
        storage = EsphomeStorageJSON.load(path)
 | 
					        storage = EsphomeStorageJSON.load(path)
 | 
				
			||||||
        if storage is None:
 | 
					        if storage is None:
 | 
				
			||||||
            storage = EsphomeStorageJSON.get_default()
 | 
					            storage = EsphomeStorageJSON.get_default()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ def run_git_command(cmd, cwd=None) -> str:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _compute_destination_path(key: str, domain: str) -> Path:
 | 
					def _compute_destination_path(key: str, domain: str) -> Path:
 | 
				
			||||||
    base_dir = Path(CORE.config_dir) / ".esphome" / domain
 | 
					    base_dir = Path(CORE.data_dir) / domain
 | 
				
			||||||
    h = hashlib.new("sha256")
 | 
					    h = hashlib.new("sha256")
 | 
				
			||||||
    h.update(key.encode())
 | 
					    h.update(key.encode())
 | 
				
			||||||
    return base_dir / h.hexdigest()[:8]
 | 
					    return base_dir / h.hexdigest()[:8]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,19 +22,19 @@ _LOGGER = logging.getLogger(__name__)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def storage_path() -> str:
 | 
					def storage_path() -> str:
 | 
				
			||||||
    return CORE.relative_internal_path(f"{CORE.config_filename}.json")
 | 
					    return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def ext_storage_path(base_path: str, config_filename: str) -> str:
 | 
					def ext_storage_path(config_filename: str) -> str:
 | 
				
			||||||
    return os.path.join(base_path, ".esphome", f"{config_filename}.json")
 | 
					    return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def esphome_storage_path(base_path: str) -> str:
 | 
					def esphome_storage_path() -> str:
 | 
				
			||||||
    return os.path.join(base_path, ".esphome", "esphome.json")
 | 
					    return os.path.join(CORE.data_dir, "esphome.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def trash_storage_path(base_path: str) -> str:
 | 
					def trash_storage_path() -> str:
 | 
				
			||||||
    return os.path.join(base_path, ".esphome", "trash")
 | 
					    return CORE.relative_config_path("trash")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StorageJSON:
 | 
					class StorageJSON:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,12 +6,12 @@ import unicodedata
 | 
				
			|||||||
import voluptuous as vol
 | 
					import voluptuous as vol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.helpers import get_bool_env, write_file
 | 
					from esphome.helpers import get_bool_env, write_file
 | 
				
			||||||
from esphome.log import color, Fore
 | 
					from esphome.log import Fore, color
 | 
				
			||||||
 | 
					 | 
				
			||||||
from esphome.storage_json import StorageJSON, ext_storage_path
 | 
					from esphome.storage_json import StorageJSON, ext_storage_path
 | 
				
			||||||
from esphome.util import safe_print
 | 
					from esphome.util import safe_print
 | 
				
			||||||
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CORE_BIG = r"""    _____ ____  _____  ______
 | 
					CORE_BIG = r"""    _____ ____  _____  ______
 | 
				
			||||||
   / ____/ __ \|  __ \|  ____|
 | 
					   / ____/ __ \|  __ \|  ____|
 | 
				
			||||||
@@ -193,10 +193,10 @@ captive_portal:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def wizard_write(path, **kwargs):
 | 
					def wizard_write(path, **kwargs):
 | 
				
			||||||
    from esphome.components.esp8266 import boards as esp8266_boards
 | 
					 | 
				
			||||||
    from esphome.components.esp32 import boards as esp32_boards
 | 
					 | 
				
			||||||
    from esphome.components.rp2040 import boards as rp2040_boards
 | 
					 | 
				
			||||||
    from esphome.components.bk72xx import boards as bk72xx_boards
 | 
					    from esphome.components.bk72xx import boards as bk72xx_boards
 | 
				
			||||||
 | 
					    from esphome.components.esp32 import boards as esp32_boards
 | 
				
			||||||
 | 
					    from esphome.components.esp8266 import boards as esp8266_boards
 | 
				
			||||||
 | 
					    from esphome.components.rp2040 import boards as rp2040_boards
 | 
				
			||||||
    from esphome.components.rtl87xx import boards as rtl87xx_boards
 | 
					    from esphome.components.rtl87xx import boards as rtl87xx_boards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = kwargs["name"]
 | 
					    name = kwargs["name"]
 | 
				
			||||||
@@ -225,7 +225,7 @@ def wizard_write(path, **kwargs):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    write_file(path, wizard_file(**kwargs))
 | 
					    write_file(path, wizard_file(**kwargs))
 | 
				
			||||||
    storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
 | 
					    storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
 | 
				
			||||||
    storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path))
 | 
					    storage_path = ext_storage_path(os.path.basename(path))
 | 
				
			||||||
    storage.save(storage_path)
 | 
					    storage.save(storage_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return True
 | 
					    return True
 | 
				
			||||||
@@ -265,9 +265,9 @@ def strip_accents(value):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def wizard(path):
 | 
					def wizard(path):
 | 
				
			||||||
 | 
					    from esphome.components.bk72xx import boards as bk72xx_boards
 | 
				
			||||||
    from esphome.components.esp32 import boards as esp32_boards
 | 
					    from esphome.components.esp32 import boards as esp32_boards
 | 
				
			||||||
    from esphome.components.esp8266 import boards as esp8266_boards
 | 
					    from esphome.components.esp8266 import boards as esp8266_boards
 | 
				
			||||||
    from esphome.components.bk72xx import boards as bk72xx_boards
 | 
					 | 
				
			||||||
    from esphome.components.rtl87xx import boards as rtl87xx_boards
 | 
					    from esphome.components.rtl87xx import boards as rtl87xx_boards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not path.endswith(".yaml") and not path.endswith(".yml"):
 | 
					    if not path.endswith(".yaml") and not path.endswith(".yml"):
 | 
				
			||||||
@@ -280,6 +280,9 @@ def wizard(path):
 | 
				
			|||||||
            f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
 | 
					            f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return 2
 | 
					        return 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CORE.config_path = path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    safe_print("Hi there!")
 | 
					    safe_print("Hi there!")
 | 
				
			||||||
    sleep(1.5)
 | 
					    sleep(1.5)
 | 
				
			||||||
    safe_print("I'm the wizard of ESPHome :)")
 | 
					    safe_print("I'm the wizard of ESPHome :)")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -123,6 +123,7 @@ lib_deps =
 | 
				
			|||||||
    DNSServer                            ; captive_portal (Arduino built-in)
 | 
					    DNSServer                            ; captive_portal (Arduino built-in)
 | 
				
			||||||
    esphome/ESP32-audioI2S@2.0.7         ; i2s_audio
 | 
					    esphome/ESP32-audioI2S@2.0.7         ; i2s_audio
 | 
				
			||||||
    crankyoldgit/IRremoteESP8266@2.7.12  ; heatpumpir
 | 
					    crankyoldgit/IRremoteESP8266@2.7.12  ; heatpumpir
 | 
				
			||||||
 | 
					    droscy/esp_wireguard@0.3.2           ; wireguard
 | 
				
			||||||
build_flags =
 | 
					build_flags =
 | 
				
			||||||
    ${common:arduino.build_flags}
 | 
					    ${common:arduino.build_flags}
 | 
				
			||||||
    -DUSE_ESP32
 | 
					    -DUSE_ESP32
 | 
				
			||||||
@@ -141,6 +142,7 @@ framework = espidf
 | 
				
			|||||||
lib_deps =
 | 
					lib_deps =
 | 
				
			||||||
    ${common:idf.lib_deps}
 | 
					    ${common:idf.lib_deps}
 | 
				
			||||||
    espressif/esp32-camera@1.0.0  ; esp32_camera
 | 
					    espressif/esp32-camera@1.0.0  ; esp32_camera
 | 
				
			||||||
 | 
					    droscy/esp_wireguard@0.3.2    ; wireguard
 | 
				
			||||||
build_flags =
 | 
					build_flags =
 | 
				
			||||||
    ${common:idf.build_flags}
 | 
					    ${common:idf.build_flags}
 | 
				
			||||||
    -Wno-nonnull-compare
 | 
					    -Wno-nonnull-compare
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ esptool==4.6.2
 | 
				
			|||||||
click==8.1.7
 | 
					click==8.1.7
 | 
				
			||||||
esphome-dashboard==20230904.0
 | 
					esphome-dashboard==20230904.0
 | 
				
			||||||
aioesphomeapi==15.0.0
 | 
					aioesphomeapi==15.0.0
 | 
				
			||||||
zeroconf==0.88.0
 | 
					zeroconf==0.102.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# esp-idf requires this, but doesn't bundle it by default
 | 
					# esp-idf requires this, but doesn't bundle it by default
 | 
				
			||||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
 | 
					# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ pyupgrade==3.10.1  # also change in .pre-commit-config.yaml when updating
 | 
				
			|||||||
pre-commit
 | 
					pre-commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Unit tests
 | 
					# Unit tests
 | 
				
			||||||
pytest==7.4.1
 | 
					pytest==7.4.2
 | 
				
			||||||
pytest-cov==4.1.0
 | 
					pytest-cov==4.1.0
 | 
				
			||||||
pytest-mock==3.11.1
 | 
					pytest-mock==3.11.1
 | 
				
			||||||
pytest-asyncio==0.21.1
 | 
					pytest-asyncio==0.21.1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,3 +27,4 @@ Current test_.yaml file contents.
 | 
				
			|||||||
| test6.yaml | RP2040 | wifi | N/A
 | 
					| test6.yaml | RP2040 | wifi | N/A
 | 
				
			||||||
| test7.yaml | ESP32-C3 | wifi | N/A
 | 
					| test7.yaml | ESP32-C3 | wifi | N/A
 | 
				
			||||||
| test8.yaml | ESP32-S3 | wifi | None
 | 
					| test8.yaml | ESP32-S3 | wifi | None
 | 
				
			||||||
 | 
					| test10.yaml | ESP32 | wifi | None
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -915,6 +915,23 @@ sensor:
 | 
				
			|||||||
    temperature:
 | 
					    temperature:
 | 
				
			||||||
      name: MPU6886 Temperature
 | 
					      name: MPU6886 Temperature
 | 
				
			||||||
    i2c_id: i2c_bus
 | 
					    i2c_id: i2c_bus
 | 
				
			||||||
 | 
					  - platform: bmi160
 | 
				
			||||||
 | 
					    address: 0x68
 | 
				
			||||||
 | 
					    acceleration_x:
 | 
				
			||||||
 | 
					      name: BMI160 Accel X
 | 
				
			||||||
 | 
					    acceleration_y:
 | 
				
			||||||
 | 
					      name: BMI160 Accel Y
 | 
				
			||||||
 | 
					    acceleration_z:
 | 
				
			||||||
 | 
					      name: BMI160 Accel z
 | 
				
			||||||
 | 
					    gyroscope_x:
 | 
				
			||||||
 | 
					      name: BMI160 Gyro X
 | 
				
			||||||
 | 
					    gyroscope_y:
 | 
				
			||||||
 | 
					      name: BMI160 Gyro Y
 | 
				
			||||||
 | 
					    gyroscope_z:
 | 
				
			||||||
 | 
					      name: BMI160 Gyro z
 | 
				
			||||||
 | 
					    temperature:
 | 
				
			||||||
 | 
					      name: BMI160 Temperature
 | 
				
			||||||
 | 
					    i2c_id: i2c_bus
 | 
				
			||||||
  - platform: mmc5603
 | 
					  - platform: mmc5603
 | 
				
			||||||
    address: 0x30
 | 
					    address: 0x30
 | 
				
			||||||
    field_strength_x:
 | 
					    field_strength_x:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										48
									
								
								tests/test10.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								tests/test10.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					esphome:
 | 
				
			||||||
 | 
					  name: test10
 | 
				
			||||||
 | 
					  build_path: build/test10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					esp32:
 | 
				
			||||||
 | 
					  board: esp32doit-devkit-v1
 | 
				
			||||||
 | 
					  framework:
 | 
				
			||||||
 | 
					    type: arduino
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					wifi:
 | 
				
			||||||
 | 
					  ssid: "MySSID1"
 | 
				
			||||||
 | 
					  password: "password1"
 | 
				
			||||||
 | 
					  reboot_timeout: 3min
 | 
				
			||||||
 | 
					  power_save_mode: high
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger:
 | 
				
			||||||
 | 
					  level: VERBOSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					api:
 | 
				
			||||||
 | 
					  reboot_timeout: 10min
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					time:
 | 
				
			||||||
 | 
					  - platform: sntp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					wireguard:
 | 
				
			||||||
 | 
					  id: vpn
 | 
				
			||||||
 | 
					  address: 172.16.34.100
 | 
				
			||||||
 | 
					  netmask: 255.255.255.0
 | 
				
			||||||
 | 
					  # NEVER use the following keys for your vpn, they are now public!
 | 
				
			||||||
 | 
					  private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8=
 | 
				
			||||||
 | 
					  peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc=
 | 
				
			||||||
 | 
					  peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60=
 | 
				
			||||||
 | 
					  peer_endpoint: wg.server.example
 | 
				
			||||||
 | 
					  peer_persistent_keepalive: 25s
 | 
				
			||||||
 | 
					  peer_allowed_ips:
 | 
				
			||||||
 | 
					    - 172.16.34.0/24
 | 
				
			||||||
 | 
					    - 192.168.4.0/24
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					binary_sensor:
 | 
				
			||||||
 | 
					  - platform: wireguard
 | 
				
			||||||
 | 
					    status:
 | 
				
			||||||
 | 
					      name: 'WireGuard Status'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sensor:
 | 
				
			||||||
 | 
					  - platform: wireguard
 | 
				
			||||||
 | 
					    latest_handshake:
 | 
				
			||||||
 | 
					      name: 'WireGuard Latest Handshake'
 | 
				
			||||||
@@ -5,6 +5,13 @@ esphome:
 | 
				
			|||||||
  board: nodemcu-32s
 | 
					  board: nodemcu-32s
 | 
				
			||||||
  build_path: build/test2
 | 
					  build_path: build/test2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					globals:
 | 
				
			||||||
 | 
					  - id: my_global_string
 | 
				
			||||||
 | 
					    type: std::string
 | 
				
			||||||
 | 
					    restore_value: yes
 | 
				
			||||||
 | 
					    max_restore_data_length: 70
 | 
				
			||||||
 | 
					    initial_value: '"DefaultValue"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
substitutions:
 | 
					substitutions:
 | 
				
			||||||
  devicename: test2
 | 
					  devicename: test2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ spi:
 | 
				
			|||||||
  clk_pin: GPIO21
 | 
					  clk_pin: GPIO21
 | 
				
			||||||
  mosi_pin: GPIO22
 | 
					  mosi_pin: GPIO22
 | 
				
			||||||
  miso_pin: GPIO23
 | 
					  miso_pin: GPIO23
 | 
				
			||||||
 | 
					  interface: hardware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uart:
 | 
					uart:
 | 
				
			||||||
  - id: uart115200
 | 
					  - id: uart115200
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -563,6 +563,13 @@ script:
 | 
				
			|||||||
          then:
 | 
					          then:
 | 
				
			||||||
            - logger.log: looping!
 | 
					            - logger.log: looping!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: zero_repeat_test
 | 
				
			||||||
 | 
					    then:
 | 
				
			||||||
 | 
					      - repeat:
 | 
				
			||||||
 | 
					          count: !lambda "return 0;"
 | 
				
			||||||
 | 
					          then:
 | 
				
			||||||
 | 
					            - logger.log: shouldn't see mee!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
switch:
 | 
					switch:
 | 
				
			||||||
  - platform: modbus_controller
 | 
					  - platform: modbus_controller
 | 
				
			||||||
    modbus_controller_id: modbus_controller_test
 | 
					    modbus_controller_id: modbus_controller_test
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,3 +62,6 @@ switch:
 | 
				
			|||||||
sensor:
 | 
					sensor:
 | 
				
			||||||
  - platform: internal_temperature
 | 
					  - platform: internal_temperature
 | 
				
			||||||
    name: Internal Temperature
 | 
					    name: Internal Temperature
 | 
				
			||||||
 | 
					  - platform: adc
 | 
				
			||||||
 | 
					    pin: VCC
 | 
				
			||||||
 | 
					    name: VSYS
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,10 +29,25 @@ light:
 | 
				
			|||||||
    name: neopixel-enable
 | 
					    name: neopixel-enable
 | 
				
			||||||
    internal: false
 | 
					    internal: false
 | 
				
			||||||
    restore_mode: ALWAYS_OFF
 | 
					    restore_mode: ALWAYS_OFF
 | 
				
			||||||
 | 
					  - platform: spi_led_strip
 | 
				
			||||||
 | 
					    num_leds: 4
 | 
				
			||||||
 | 
					    color_correct: [80%, 60%, 100%]
 | 
				
			||||||
 | 
					    id: rgb_led
 | 
				
			||||||
 | 
					    name: "RGB LED"
 | 
				
			||||||
 | 
					    data_rate: 8MHz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
spi:
 | 
					spi:
 | 
				
			||||||
 | 
					  id: spi_id_1
 | 
				
			||||||
  clk_pin: GPIO7
 | 
					  clk_pin: GPIO7
 | 
				
			||||||
  mosi_pin: GPIO6
 | 
					  mosi_pin: GPIO6
 | 
				
			||||||
 | 
					  interface: any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					spi_device:
 | 
				
			||||||
 | 
					  id: spidev
 | 
				
			||||||
 | 
					  data_rate: 2MHz
 | 
				
			||||||
 | 
					  spi_id: spi_id_1
 | 
				
			||||||
 | 
					  mode: 3
 | 
				
			||||||
 | 
					  bit_order: lsb_first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
display:
 | 
					display:
 | 
				
			||||||
  - platform: ili9xxx
 | 
					  - platform: ili9xxx
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
"""Tests for the wizard.py file."""
 | 
					"""Tests for the wizard.py file."""
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import esphome.wizard as wz
 | 
					import esphome.wizard as wz
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
 | 
					from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
 | 
				
			||||||
from esphome.components.esp32.boards import ESP32_BOARD_PINS
 | 
					from esphome.components.esp32.boards import ESP32_BOARD_PINS
 | 
				
			||||||
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
 | 
					from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
 | 
				
			||||||
@@ -110,6 +112,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
 | 
				
			|||||||
    # Given
 | 
					    # Given
 | 
				
			||||||
    del default_config["platform"]
 | 
					    del default_config["platform"]
 | 
				
			||||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
					    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
				
			||||||
 | 
					    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # When
 | 
					    # When
 | 
				
			||||||
    wz.wizard_write(tmp_path, **default_config)
 | 
					    wz.wizard_write(tmp_path, **default_config)
 | 
				
			||||||
@@ -130,6 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266(
 | 
				
			|||||||
    default_config["board"] = [*ESP8266_BOARD_PINS][0]
 | 
					    default_config["board"] = [*ESP8266_BOARD_PINS][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
					    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
				
			||||||
 | 
					    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # When
 | 
					    # When
 | 
				
			||||||
    wz.wizard_write(tmp_path, **default_config)
 | 
					    wz.wizard_write(tmp_path, **default_config)
 | 
				
			||||||
@@ -150,6 +154,7 @@ def test_wizard_write_defaults_platform_from_board_esp32(
 | 
				
			|||||||
    default_config["board"] = [*ESP32_BOARD_PINS][0]
 | 
					    default_config["board"] = [*ESP32_BOARD_PINS][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
					    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
				
			||||||
 | 
					    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # When
 | 
					    # When
 | 
				
			||||||
    wz.wizard_write(tmp_path, **default_config)
 | 
					    wz.wizard_write(tmp_path, **default_config)
 | 
				
			||||||
@@ -170,6 +175,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
 | 
				
			|||||||
    default_config["board"] = [*BK72XX_BOARD_PINS][0]
 | 
					    default_config["board"] = [*BK72XX_BOARD_PINS][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
					    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
				
			||||||
 | 
					    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # When
 | 
					    # When
 | 
				
			||||||
    wz.wizard_write(tmp_path, **default_config)
 | 
					    wz.wizard_write(tmp_path, **default_config)
 | 
				
			||||||
@@ -190,6 +196,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx(
 | 
				
			|||||||
    default_config["board"] = [*RTL87XX_BOARD_PINS][0]
 | 
					    default_config["board"] = [*RTL87XX_BOARD_PINS][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
					    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
				
			||||||
 | 
					    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # When
 | 
					    # When
 | 
				
			||||||
    wz.wizard_write(tmp_path, **default_config)
 | 
					    wz.wizard_write(tmp_path, **default_config)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user