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]
 | 
			
		||||
    paths:
 | 
			
		||||
      - "docker/**"
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - ".github/workflows/ci-docker.yml"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "script/platformio_install_deps.py"
 | 
			
		||||
@@ -16,7 +16,7 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
      - "docker/**"
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - ".github/workflows/ci-docker.yml"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "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 }}
 | 
			
		||||
      - name: Restore Python virtual environment
 | 
			
		||||
        id: cache-venv
 | 
			
		||||
        uses: actions/cache@v3.3.1
 | 
			
		||||
        uses: actions/cache@v3.3.2
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
@@ -232,7 +232,7 @@ jobs:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 2
 | 
			
		||||
      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:
 | 
			
		||||
      - name: Check out code from GitHub
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
@@ -302,7 +302,7 @@ jobs:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        uses: actions/cache@v3.3.1
 | 
			
		||||
        uses: actions/cache@v3.3.2
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,12 @@ __pycache__/
 | 
			
		||||
# Intellij Idea
 | 
			
		||||
.idea
 | 
			
		||||
 | 
			
		||||
# Eclipse
 | 
			
		||||
.project
 | 
			
		||||
.cproject
 | 
			
		||||
.pydevproject
 | 
			
		||||
.settings/
 | 
			
		||||
 | 
			
		||||
# Vim
 | 
			
		||||
*.swp
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
# See https://pre-commit.com for more information
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/psf/black
 | 
			
		||||
  - repo: https://github.com/psf/black-pre-commit-mirror
 | 
			
		||||
    rev: 23.7.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ esphome/components/bl0942/* @dbuezas
 | 
			
		||||
esphome/components/ble_client/* @buxtronix
 | 
			
		||||
esphome/components/bluetooth_proxy/* @jesserockz
 | 
			
		||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
			
		||||
esphome/components/bmi160/* @flaviut
 | 
			
		||||
esphome/components/bmp3xx/* @martgras
 | 
			
		||||
esphome/components/bmp581/* @kahrendt
 | 
			
		||||
esphome/components/bp1658cj/* @Cossid
 | 
			
		||||
@@ -270,6 +271,8 @@ esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/speaker/* @jesserockz
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
esphome/components/spi_device/* @clydebarrow
 | 
			
		||||
esphome/components/spi_led_strip/* @clydebarrow
 | 
			
		||||
esphome/components/sprinkler/* @kbx81
 | 
			
		||||
esphome/components/sps30/* @martgras
 | 
			
		||||
esphome/components/ssd1322_base/* @kbx81
 | 
			
		||||
@@ -330,6 +333,7 @@ esphome/components/web_server_idf/* @dentra
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/whynter/* @aeonsablaze
 | 
			
		||||
esphome/components/wiegand/* @ssieb
 | 
			
		||||
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
 | 
			
		||||
esphome/components/wl_134/* @hobbypunk90
 | 
			
		||||
esphome/components/x9c/* @EtienneMD
 | 
			
		||||
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')
 | 
			
		||||
else
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
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..."
 | 
			
		||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,8 @@ def choose_upload_log_host(
 | 
			
		||||
    options = []
 | 
			
		||||
    for port in get_serial_ports():
 | 
			
		||||
        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):
 | 
			
		||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
			
		||||
        if default == "OTA":
 | 
			
		||||
@@ -218,14 +220,16 @@ def compile_program(args, config):
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
			
		||||
        "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)
 | 
			
		||||
 | 
			
		||||
        firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
 | 
			
		||||
@@ -242,6 +246,7 @@ def upload_using_esptool(config, port):
 | 
			
		||||
 | 
			
		||||
        mcu = get_esp32_variant().lower()
 | 
			
		||||
 | 
			
		||||
    def run_esptool(baud_rate):
 | 
			
		||||
        cmd = [
 | 
			
		||||
            "esptool.py",
 | 
			
		||||
            "--before",
 | 
			
		||||
@@ -292,7 +297,8 @@ def upload_using_platformio(config, port):
 | 
			
		||||
def upload_program(config, args, host):
 | 
			
		||||
    if get_port_type(host) == "SERIAL":
 | 
			
		||||
        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):
 | 
			
		||||
            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.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    PLATFORM_RP2040,
 | 
			
		||||
)
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C2,
 | 
			
		||||
@@ -143,7 +147,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
 | 
			
		||||
def validate_adc_pin(value):
 | 
			
		||||
    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":
 | 
			
		||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#ifdef CYW43_USES_VSYS_PIN
 | 
			
		||||
#include "pico/cyw43_arch.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include <hardware/adc.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -123,13 +126,19 @@ void ADCSensor::dump_config() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
			
		||||
  } else {
 | 
			
		||||
#ifdef USE_ADC_SENSOR_VCC
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
			
		||||
#else
 | 
			
		||||
    LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
#endif  // USE_ADC_SENSOR_VCC
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -238,7 +247,20 @@ float ADCSensor::sample() {
 | 
			
		||||
    delay(1);
 | 
			
		||||
    adc_select_input(4);
 | 
			
		||||
  } 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_select_input(pin - 26);
 | 
			
		||||
  }
 | 
			
		||||
@@ -246,11 +268,23 @@ float ADCSensor::sample() {
 | 
			
		||||
  int32_t raw = adc_read();
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    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_) {
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_REACTIVE_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_PHASE_A,
 | 
			
		||||
    CONF_PHASE_B,
 | 
			
		||||
    CONF_PHASE_C,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_POWER_FACTOR,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
@@ -31,10 +34,6 @@ from esphome.const import (
 | 
			
		||||
    UNIT_WATT_HOURS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_PHASE_A = "phase_a"
 | 
			
		||||
CONF_PHASE_B = "phase_b"
 | 
			
		||||
CONF_PHASE_C = "phase_c"
 | 
			
		||||
 | 
			
		||||
CONF_LINE_FREQUENCY = "line_frequency"
 | 
			
		||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
 | 
			
		||||
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>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
#include <esp32c3/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
#include <esp32c6/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
			
		||||
#include <esp32s2/rom/rtc.h>
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
@@ -119,6 +121,8 @@ void DebugComponent::dump_config() {
 | 
			
		||||
  model = "ESP32";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
			
		||||
  model = "ESP32-C3";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
  model = "ESP32-C6";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S2)
 | 
			
		||||
  model = "ESP32-S2";
 | 
			
		||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
			
		||||
@@ -202,9 +206,11 @@ void DebugComponent::dump_config() {
 | 
			
		||||
    case RTCWDT_SYS_RESET:
 | 
			
		||||
      reset_reason = "RTC Watch Dog Reset Digital Core";
 | 
			
		||||
      break;
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C6)
 | 
			
		||||
    case INTRUSION_RESET:
 | 
			
		||||
      reset_reason = "Intrusion Reset CPU";
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32)
 | 
			
		||||
    case TGWDT_CPU_RESET:
 | 
			
		||||
      reset_reason = "Timer Group Reset CPU";
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_IGNORE_EFUSE_MAC_CRC,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
    KEY_NAME,
 | 
			
		||||
    KEY_TARGET_FRAMEWORK,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
    TYPE_GIT,
 | 
			
		||||
@@ -37,6 +38,7 @@ from .const import (  # noqa
 | 
			
		||||
    KEY_BOARD,
 | 
			
		||||
    KEY_COMPONENTS,
 | 
			
		||||
    KEY_ESP32,
 | 
			
		||||
    KEY_EXTRA_BUILD_FILES,
 | 
			
		||||
    KEY_PATH,
 | 
			
		||||
    KEY_REF,
 | 
			
		||||
    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_VARIANT] = config[CONF_VARIANT]
 | 
			
		||||
    CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
    # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
 | 
			
		||||
    # a PIO platformio/framework-arduinoespressif32 value
 | 
			
		||||
@@ -390,7 +412,11 @@ async def to_code(config):
 | 
			
		||||
    conf = config[CONF_FRAMEWORK]
 | 
			
		||||
    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:
 | 
			
		||||
        cg.add_platformio_option("framework", "espidf")
 | 
			
		||||
@@ -604,9 +630,15 @@ def copy_files():
 | 
			
		||||
                        ignore_dangling_symlinks=True,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
    dir = os.path.dirname(__file__)
 | 
			
		||||
    post_build_file = os.path.join(dir, "post_build.py.script")
 | 
			
		||||
    for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
 | 
			
		||||
        if file[KEY_PATH].startswith("http"):
 | 
			
		||||
            import requests
 | 
			
		||||
 | 
			
		||||
            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(
 | 
			
		||||
        post_build_file,
 | 
			
		||||
        CORE.relative_build_path("post_build.py"),
 | 
			
		||||
                file[KEY_PATH],
 | 
			
		||||
                CORE.relative_build_path(file[KEY_NAME]),
 | 
			
		||||
            )
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ KEY_REF = "ref"
 | 
			
		||||
KEY_REFRESH = "refresh"
 | 
			
		||||
KEY_PATH = "path"
 | 
			
		||||
KEY_SUBMODULES = "submodules"
 | 
			
		||||
KEY_EXTRA_BUILD_FILES = "extra_build_files"
 | 
			
		||||
 | 
			
		||||
VARIANT_ESP32 = "ESP32"
 | 
			
		||||
VARIANT_ESP32S2 = "ESP32S2"
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,11 @@ void arch_init() {
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
 | 
			
		||||
 | 
			
		||||
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(); }
 | 
			
		||||
#endif
 | 
			
		||||
uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 
 | 
			
		||||
@@ -98,10 +98,9 @@ def validate_truetype_file(value):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _compute_local_font_dir(name) -> Path:
 | 
			
		||||
    base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
 | 
			
		||||
    h = hashlib.new("sha256")
 | 
			
		||||
    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:
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,14 @@ CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
globals_ns = cg.esphome_ns.namespace("globals")
 | 
			
		||||
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
 | 
			
		||||
RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
 | 
			
		||||
RestoringGlobalStringComponent = globals_ns.class_(
 | 
			
		||||
    "RestoringGlobalStringComponent", cg.Component
 | 
			
		||||
)
 | 
			
		||||
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
@@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.string_strict,
 | 
			
		||||
        cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
@@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
@coroutine_with_priority(-100.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    type_ = cg.RawExpression(config[CONF_TYPE])
 | 
			
		||||
    template_args = cg.TemplateArguments(type_)
 | 
			
		||||
    restore = config[CONF_RESTORE_VALUE]
 | 
			
		||||
 | 
			
		||||
    # Special casing the strings to their own class with a different save/restore mechanism
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    res_type = type.template(template_args)
 | 
			
		||||
    initial_value = None
 | 
			
		||||
    if CONF_INITIAL_VALUE in config:
 | 
			
		||||
        initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE])
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,64 @@ template<typename T> class RestoringGlobalsComponent : public Component {
 | 
			
		||||
  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...> {
 | 
			
		||||
 public:
 | 
			
		||||
  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(RestoringGlobalsComponent<T> *value) { return value->value(); }
 | 
			
		||||
template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace globals
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PHASE_A,
 | 
			
		||||
    CONF_PHASE_B,
 | 
			
		||||
    CONF_PHASE_C,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_ENERGY,
 | 
			
		||||
@@ -21,10 +24,6 @@ from esphome.const import (
 | 
			
		||||
    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_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
			
		||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PHASE_A,
 | 
			
		||||
    CONF_PHASE_B,
 | 
			
		||||
    CONF_PHASE_C,
 | 
			
		||||
    CONF_REACTIVE_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
@@ -24,9 +27,6 @@ from esphome.const import (
 | 
			
		||||
    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_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
 | 
			
		||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ Image_ = image_ns.class_("Image")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -168,9 +168,9 @@ def _notify_old_style(config):
 | 
			
		||||
# NOTE: Keep this in mind when updating the recommended version:
 | 
			
		||||
#  * For all constants below, update platformio.ini (in this repo)
 | 
			
		||||
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),
 | 
			
		||||
    "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) {
 | 
			
		||||
  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.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
#include "esphome/components/nfc/nfc.h"
 | 
			
		||||
#include "esphome/components/nfc/automation.h"
 | 
			
		||||
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -74,10 +75,11 @@ class PN532 : public PollingComponent {
 | 
			
		||||
  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);
 | 
			
		||||
  bool read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &data);
 | 
			
		||||
  bool is_mifare_ultralight_formatted_();
 | 
			
		||||
  bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
 | 
			
		||||
  bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
 | 
			
		||||
  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_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
			
		||||
  bool clean_mifare_ultralight_();
 | 
			
		||||
 
 | 
			
		||||
@@ -9,93 +9,104 @@ namespace pn532 {
 | 
			
		||||
static const char *const TAG = "pn532.mifare_ultralight";
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) {
 | 
			
		||||
  if (!this->is_mifare_ultralight_formatted_()) {
 | 
			
		||||
    ESP_LOGD(TAG, "Not NDEF formatted");
 | 
			
		||||
  std::vector<uint8_t> data;
 | 
			
		||||
  // 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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t message_length;
 | 
			
		||||
  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);
 | 
			
		||||
  }
 | 
			
		||||
  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) {
 | 
			
		||||
    return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
 | 
			
		||||
  }
 | 
			
		||||
  std::vector<uint8_t> data;
 | 
			
		||||
  for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) {
 | 
			
		||||
    std::vector<uint8_t> page_data;
 | 
			
		||||
    if (!this->read_mifare_ultralight_page_(page, page_data)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Error reading page %d", page);
 | 
			
		||||
  // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
 | 
			
		||||
  const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
 | 
			
		||||
  if (read_length) {
 | 
			
		||||
    if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) {
 | 
			
		||||
      ESP_LOGE(TAG, "Error reading tag data");
 | 
			
		||||
      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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  data.erase(data.begin(), data.begin() + message_start_index);
 | 
			
		||||
  data.erase(data.begin() + message_length, data.end());
 | 
			
		||||
  // 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 + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
  const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
 | 
			
		||||
  std::vector<uint8_t> response;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
 | 
			
		||||
    if (!this->write_command_({
 | 
			
		||||
            PN532_COMMAND_INDATAEXCHANGE,
 | 
			
		||||
            0x01,  // One card
 | 
			
		||||
            nfc::MIFARE_CMD_READ,
 | 
			
		||||
          page_num,
 | 
			
		||||
            uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
 | 
			
		||||
        })) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) {
 | 
			
		||||
    if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
 | 
			
		||||
      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());
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str());
 | 
			
		||||
    if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) {
 | 
			
		||||
      data.insert(data.end(), response.begin() + 1, pages_in_end_itr);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PN532::is_mifare_ultralight_formatted_() {
 | 
			
		||||
  std::vector<uint8_t> data;
 | 
			
		||||
  if (this->read_mifare_ultralight_page_(4, data)) {
 | 
			
		||||
    return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF);
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
 | 
			
		||||
  const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;  // page 4 will begin 4 bytes into the vector
 | 
			
		||||
 | 
			
		||||
  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) &&
 | 
			
		||||
           (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t PN532::read_mifare_ultralight_capacity_() {
 | 
			
		||||
  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 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) {
 | 
			
		||||
  std::vector<uint8_t> data;
 | 
			
		||||
  for (int page = 4; page < 6; page++) {
 | 
			
		||||
    std::vector<uint8_t> page_data;
 | 
			
		||||
    if (!this->read_mifare_ultralight_page_(page, page_data)) {
 | 
			
		||||
bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
 | 
			
		||||
                                         uint8_t &message_start_index) {
 | 
			
		||||
  const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;  // page 4 will begin 4 bytes into the vector
 | 
			
		||||
 | 
			
		||||
  if (!(page_3_to_6.size() > p4_offset + 5)) {
 | 
			
		||||
    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;
 | 
			
		||||
    return true;
 | 
			
		||||
  } else if (data[5] == 0x03) {
 | 
			
		||||
    message_length = data[6];
 | 
			
		||||
  } else if (page_3_to_6[p4_offset + 5] == 0x03) {
 | 
			
		||||
    message_length = page_3_to_6[p4_offset + 6];
 | 
			
		||||
    message_start_index = 7;
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -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());
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> 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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -87,12 +87,7 @@ def get_firmware(value):
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
 | 
			
		||||
    if CONF_SHA256 in value:  # we have a hash, enable caching
 | 
			
		||||
        path = (
 | 
			
		||||
            Path(CORE.config_dir)
 | 
			
		||||
            / ".esphome"
 | 
			
		||||
            / DOMAIN
 | 
			
		||||
            / (value[CONF_SHA256] + "_fw_stm.bin")
 | 
			
		||||
        )
 | 
			
		||||
        path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin")
 | 
			
		||||
 | 
			
		||||
        if not path.is_file():
 | 
			
		||||
            firmware_data, dl_hash = dl(url)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,17 @@
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
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.const import (
 | 
			
		||||
    CONF_CLK_PIN,
 | 
			
		||||
@@ -9,6 +20,11 @@ from esphome.const import (
 | 
			
		||||
    CONF_MOSI_PIN,
 | 
			
		||||
    CONF_SPI_ID,
 | 
			
		||||
    CONF_CS_PIN,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
    KEY_VARIANT,
 | 
			
		||||
)
 | 
			
		||||
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))
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
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.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_MOSI_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            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.only_on(["esp32", "esp8266", "rp2040"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.ensure_list(SPI_SCHEMA),
 | 
			
		||||
    validate_spi_config,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(1.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
async def to_code(configs):
 | 
			
		||||
    cg.add_global(spi_ns.using)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    for spi in configs:
 | 
			
		||||
        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_force_sw(config[CONF_FORCE_SW]))
 | 
			
		||||
    if CONF_MISO_PIN in config:
 | 
			
		||||
        miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN])
 | 
			
		||||
        if CONF_MISO_PIN in spi:
 | 
			
		||||
            miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
 | 
			
		||||
            cg.add(var.set_miso(miso))
 | 
			
		||||
    if CONF_MOSI_PIN in config:
 | 
			
		||||
        mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
 | 
			
		||||
        if CONF_MOSI_PIN in spi:
 | 
			
		||||
            mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
 | 
			
		||||
            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:
 | 
			
		||||
        cg.add_library("SPI", None)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,268 +1,116 @@
 | 
			
		||||
#include "spi.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace spi {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "spi";
 | 
			
		||||
const char *const TAG = "spi";
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR HOT SPIComponent::disable() {
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
  if (this->hw_spi_ != nullptr) {
 | 
			
		||||
    this->hw_spi_->endTransaction();
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
  if (this->active_cs_) {
 | 
			
		||||
    this->active_cs_->digital_write(true);
 | 
			
		||||
    this->active_cs_ = nullptr;
 | 
			
		||||
SPIDelegate *const SPIDelegate::NULL_DELEGATE =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
    new SPIDelegateDummy();
 | 
			
		||||
// https://bugs.llvm.org/show_bug.cgi?id=48040
 | 
			
		||||
 | 
			
		||||
bool SPIDelegate::is_ready() { return true; }
 | 
			
		||||
 | 
			
		||||
GPIOPin *const NullPin::NULL_PIN = new NullPin();  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up SPI bus...");
 | 
			
		||||
  this->clk_->setup();
 | 
			
		||||
  this->clk_->digital_write(true);
 | 
			
		||||
  ESP_LOGD(TAG, "Setting up SPI bus...");
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
  bool use_hw_spi = !this->force_sw_;
 | 
			
		||||
  const bool has_miso = this->miso_ != nullptr;
 | 
			
		||||
  const bool has_mosi = this->mosi_ != nullptr;
 | 
			
		||||
  int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1;
 | 
			
		||||
 | 
			
		||||
  if (!this->clk_->is_internal())
 | 
			
		||||
    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();
 | 
			
		||||
  if (this->sdo_pin_ == nullptr)
 | 
			
		||||
    this->sdo_pin_ = NullPin::NULL_PIN;
 | 
			
		||||
  if (this->sdi_pin_ == nullptr)
 | 
			
		||||
    this->sdi_pin_ = NullPin::NULL_PIN;
 | 
			
		||||
  if (this->clk_pin_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "No clock pin for SPI");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    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 (spi_bus_num == 0) {
 | 
			
		||||
      this->hw_spi_ = &SPI;
 | 
			
		||||
  if (this->using_hw_) {
 | 
			
		||||
    this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
 | 
			
		||||
    if (this->spi_bus_ == nullptr) {
 | 
			
		||||
      ESP_LOGE(TAG, "Unable to allocate SPI interface");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
 | 
			
		||||
    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++;
 | 
			
		||||
    this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  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);
 | 
			
		||||
    this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_);  // NOLINT
 | 
			
		||||
    this->clk_pin_->setup();
 | 
			
		||||
    this->clk_pin_->digital_write(true);
 | 
			
		||||
    this->sdo_pin_->setup();
 | 
			
		||||
    this->sdi_pin_->setup();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SPIComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "SPI bus:");
 | 
			
		||||
  LOG_PIN("  CLK Pin: ", this->clk_);
 | 
			
		||||
  LOG_PIN("  MISO Pin: ", this->miso_);
 | 
			
		||||
  LOG_PIN("  MOSI Pin: ", this->mosi_);
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr));
 | 
			
		||||
#endif  // USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
}
 | 
			
		||||
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_)
 | 
			
		||||
    ;
 | 
			
		||||
  LOG_PIN("  CLK Pin: ", this->clk_pin_)
 | 
			
		||||
  LOG_PIN("  SDI Pin: ", this->sdi_pin_)
 | 
			
		||||
  LOG_PIN("  SDO Pin: ", this->sdo_pin_)
 | 
			
		||||
  if (this->spi_bus_->is_hw()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Using HW SPI: %s", this->interface_name_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Using software SPI");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE
 | 
			
		||||
#ifndef CLANG_TIDY
 | 
			
		||||
#pragma GCC optimize("unroll-loops")
 | 
			
		||||
// NOLINTNEXTLINE
 | 
			
		||||
#pragma GCC optimize("O2")
 | 
			
		||||
#endif  // CLANG_TIDY
 | 
			
		||||
void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); }
 | 
			
		||||
 | 
			
		||||
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
 | 
			
		||||
uint8_t HOT SPIComponent::transfer_(uint8_t data) {
 | 
			
		||||
uint8_t SPIDelegateBitBash::transfer(uint8_t data) {
 | 
			
		||||
  // Clock starts out at idle level
 | 
			
		||||
  this->clk_->digital_write(CLOCK_POLARITY);
 | 
			
		||||
  this->clk_pin_->digital_write(clock_polarity_);
 | 
			
		||||
  uint8_t out_data = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    uint8_t shift;
 | 
			
		||||
    if (BIT_ORDER == BIT_ORDER_MSB_FIRST) {
 | 
			
		||||
    if (bit_order_ == BIT_ORDER_MSB_FIRST) {
 | 
			
		||||
      shift = 7 - i;
 | 
			
		||||
    } else {
 | 
			
		||||
      shift = i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (CLOCK_PHASE == CLOCK_PHASE_LEADING) {
 | 
			
		||||
    if (clock_phase_ == CLOCK_PHASE_LEADING) {
 | 
			
		||||
      // sampling on leading edge
 | 
			
		||||
      if (WRITE) {
 | 
			
		||||
        this->mosi_->digital_write(data & (1 << shift));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // SAMPLE!
 | 
			
		||||
      this->cycle_clock_(!CLOCK_POLARITY);
 | 
			
		||||
 | 
			
		||||
      if (READ) {
 | 
			
		||||
        out_data |= uint8_t(this->miso_->digital_read()) << shift;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this->cycle_clock_(CLOCK_POLARITY);
 | 
			
		||||
      this->sdo_pin_->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_);
 | 
			
		||||
      this->cycle_clock_();
 | 
			
		||||
      this->clk_pin_->digital_write(this->clock_polarity_);
 | 
			
		||||
    } else {
 | 
			
		||||
      // sampling on trailing edge
 | 
			
		||||
      this->cycle_clock_(!CLOCK_POLARITY);
 | 
			
		||||
 | 
			
		||||
      if (WRITE) {
 | 
			
		||||
        this->mosi_->digital_write(data & (1 << shift));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // SAMPLE!
 | 
			
		||||
      this->cycle_clock_(CLOCK_POLARITY);
 | 
			
		||||
 | 
			
		||||
      if (READ) {
 | 
			
		||||
        out_data |= uint8_t(this->miso_->digital_read()) << shift;
 | 
			
		||||
      this->cycle_clock_();
 | 
			
		||||
      this->clk_pin_->digital_write(!this->clock_polarity_);
 | 
			
		||||
      this->sdo_pin_->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_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  App.feed_wdt();
 | 
			
		||||
 | 
			
		||||
  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 esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,34 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#define USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPI_ARDUINO_BACKEND
 | 
			
		||||
#include <SPI.h>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
using SPIInterface = SPIClassRP2040 *;
 | 
			
		||||
#else
 | 
			
		||||
using SPIInterface = SPIClass *;
 | 
			
		||||
#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 spi {
 | 
			
		||||
 | 
			
		||||
@@ -48,10 +66,19 @@ enum SPIClockPhase {
 | 
			
		||||
  /// The data is sampled on a trailing clock edge. (CPHA=1)
 | 
			
		||||
  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
 | 
			
		||||
 * to manually use a specific data rate.
 | 
			
		||||
@@ -71,270 +98,340 @@ enum SPIDataRate : uint32_t {
 | 
			
		||||
  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:
 | 
			
		||||
  void set_clk(GPIOPin *clk) { clk_ = clk; }
 | 
			
		||||
  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 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() {
 | 
			
		||||
#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);
 | 
			
		||||
  }
 | 
			
		||||
  void digital_write(bool value) override {}
 | 
			
		||||
 | 
			
		||||
  template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
 | 
			
		||||
  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;
 | 
			
		||||
  std::string dump_summary() const override { return std::string(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  inline void cycle_clock_(bool value);
 | 
			
		||||
 | 
			
		||||
  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_;
 | 
			
		||||
  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, SPIDataRate DATA_RATE>
 | 
			
		||||
class SPIDevice {
 | 
			
		||||
class Utility {
 | 
			
		||||
 public:
 | 
			
		||||
  SPIDevice() = default;
 | 
			
		||||
  SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {}
 | 
			
		||||
  static int get_pin_no(GPIOPin *pin) {
 | 
			
		||||
    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; }
 | 
			
		||||
  void set_cs_pin(GPIOPin *cs) { cs_ = cs; }
 | 
			
		||||
  static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) {
 | 
			
		||||
    if (polarity == CLOCK_POLARITY_HIGH) {
 | 
			
		||||
      return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3;
 | 
			
		||||
    }
 | 
			
		||||
    return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void spi_setup() {
 | 
			
		||||
    if (this->cs_) {
 | 
			
		||||
      this->cs_->setup();
 | 
			
		||||
      this->cs_->digital_write(true);
 | 
			
		||||
  static SPIClockPhase get_phase(SPIMode mode) {
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
      case MODE0:
 | 
			
		||||
      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) {
 | 
			
		||||
    return this->parent_->template read_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
			
		||||
 public:
 | 
			
		||||
  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() {
 | 
			
		||||
    std::array<uint8_t, N> data;
 | 
			
		||||
    this->read_array(data.data(), N);
 | 
			
		||||
    return data;
 | 
			
		||||
  virtual ~SPIDelegate(){};
 | 
			
		||||
 | 
			
		||||
  // enable CS if configured.
 | 
			
		||||
  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) {
 | 
			
		||||
    return this->parent_->template write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
			
		||||
  // write 16 bits
 | 
			
		||||
  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) {
 | 
			
		||||
    return this->parent_->template write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
 | 
			
		||||
  virtual void write_array16(const uint16_t *data, size_t length) {
 | 
			
		||||
    for (size_t i = 0; i != length; i++) {
 | 
			
		||||
      this->write16(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void write_array16(const uint16_t *data, size_t length) {
 | 
			
		||||
    this->parent_->template write_array16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
			
		||||
  // write the contents of a buffer, ignore read data (buffer is unchanged.)
 | 
			
		||||
  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) {
 | 
			
		||||
    this->parent_->template write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
 | 
			
		||||
  // read into a buffer, write nulls
 | 
			
		||||
  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); }
 | 
			
		||||
 | 
			
		||||
  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); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  SPIComponent *parent_{nullptr};
 | 
			
		||||
  GPIOPin *cs_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // 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_) {
 | 
			
		||||
    this->start();
 | 
			
		||||
  } else {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
    esp_netif_init();
 | 
			
		||||
#endif
 | 
			
		||||
    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_GREEN = "green"
 | 
			
		||||
CONF_GROUP = "group"
 | 
			
		||||
CONF_GYROSCOPE_X = "gyroscope_x"
 | 
			
		||||
CONF_GYROSCOPE_Y = "gyroscope_y"
 | 
			
		||||
CONF_GYROSCOPE_Z = "gyroscope_z"
 | 
			
		||||
CONF_HARDWARE_UART = "hardware_uart"
 | 
			
		||||
CONF_HEAD = "head"
 | 
			
		||||
CONF_HEARTBEAT = "heartbeat"
 | 
			
		||||
@@ -538,8 +541,11 @@ CONF_PAYLOAD_AVAILABLE = "payload_available"
 | 
			
		||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
 | 
			
		||||
CONF_PERIOD = "period"
 | 
			
		||||
CONF_PH = "ph"
 | 
			
		||||
CONF_PHASE_A = "phase_a"
 | 
			
		||||
CONF_PHASE_ANGLE = "phase_angle"
 | 
			
		||||
CONF_PHASE_B = "phase_b"
 | 
			
		||||
CONF_PHASE_BALANCER = "phase_balancer"
 | 
			
		||||
CONF_PHASE_C = "phase_c"
 | 
			
		||||
CONF_PIN = "pin"
 | 
			
		||||
CONF_PIN_A = "pin_a"
 | 
			
		||||
CONF_PIN_B = "pin_b"
 | 
			
		||||
@@ -864,6 +870,9 @@ ICON_FLOWER = "mdi:flower"
 | 
			
		||||
ICON_GAS_CYLINDER = "mdi:gas-cylinder"
 | 
			
		||||
ICON_GAUGE = "mdi:gauge"
 | 
			
		||||
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_KEY_PLUS = "mdi:key-plus"
 | 
			
		||||
ICON_LIGHTBULB = "mdi:lightbulb"
 | 
			
		||||
 
 | 
			
		||||
@@ -554,6 +554,12 @@ class EsphomeCore:
 | 
			
		||||
    def config_dir(self):
 | 
			
		||||
        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
 | 
			
		||||
    def config_filename(self):
 | 
			
		||||
        return os.path.basename(self.config_path)
 | 
			
		||||
@@ -563,7 +569,7 @@ class EsphomeCore:
 | 
			
		||||
        return os.path.join(self.config_dir, path_)
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        path_ = os.path.expanduser(os.path.join(*path))
 | 
			
		||||
@@ -573,13 +579,9 @@ class EsphomeCore:
 | 
			
		||||
        return self.relative_build_path("src", *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)
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
 
 | 
			
		||||
@@ -249,7 +249,11 @@ template<typename... Ts> class RepeatAction : public Action<Ts...> {
 | 
			
		||||
  void play_complex(Ts... x) override {
 | 
			
		||||
    this->num_running_++;
 | 
			
		||||
    this->var_ = std::make_tuple(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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -198,8 +198,8 @@ def preload_core_config(config, result):
 | 
			
		||||
    CORE.data[KEY_CORE] = {}
 | 
			
		||||
 | 
			
		||||
    if CONF_BUILD_PATH not in conf:
 | 
			
		||||
        conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}"
 | 
			
		||||
    CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH])
 | 
			
		||||
        conf[CONF_BUILD_PATH] = f"build/{CORE.name}"
 | 
			
		||||
    CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
 | 
			
		||||
 | 
			
		||||
    has_oldstyle = CONF_PLATFORM in conf
 | 
			
		||||
    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();
 | 
			
		||||
  size_t len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
 | 
			
		||||
  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);
 | 
			
		||||
    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
 | 
			
		||||
   * 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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -663,7 +663,11 @@ async def process_lambda(
 | 
			
		||||
    :param return_type: The return type of the lambda.
 | 
			
		||||
    :return: The generated lambda expression.
 | 
			
		||||
    """
 | 
			
		||||
    from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent
 | 
			
		||||
    from esphome.components.globals import (
 | 
			
		||||
        GlobalsComponent,
 | 
			
		||||
        RestoringGlobalsComponent,
 | 
			
		||||
        RestoringGlobalStringComponent,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if value is None:
 | 
			
		||||
        return
 | 
			
		||||
@@ -676,6 +680,7 @@ async def process_lambda(
 | 
			
		||||
            and (
 | 
			
		||||
                full_id.type.inherits_from(GlobalsComponent)
 | 
			
		||||
                or full_id.type.inherits_from(RestoringGlobalsComponent)
 | 
			
		||||
                or full_id.type.inherits_from(RestoringGlobalStringComponent)
 | 
			
		||||
            )
 | 
			
		||||
        ):
 | 
			
		||||
            parts[i * 3 + 1] = var.value()
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ import yaml
 | 
			
		||||
from tornado.log import access_log
 | 
			
		||||
 | 
			
		||||
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.storage_json import (
 | 
			
		||||
    EsphomeStorageJSON,
 | 
			
		||||
@@ -70,6 +71,7 @@ class DashboardSettings:
 | 
			
		||||
            self.password_hash = password_hash(password)
 | 
			
		||||
        self.config_dir = args.configuration
 | 
			
		||||
        self.absolute_config_dir = Path(self.config_dir).resolve()
 | 
			
		||||
        CORE.config_path = os.path.join(self.config_dir, ".")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def relative_url(self):
 | 
			
		||||
@@ -534,13 +536,16 @@ class DownloadListRequestHandler(BaseHandler):
 | 
			
		||||
    @authenticated
 | 
			
		||||
    @bind_config
 | 
			
		||||
    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)
 | 
			
		||||
        if storage_json is None:
 | 
			
		||||
            self.send_error(404)
 | 
			
		||||
            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.rp2040 import get_download_types as rp2040_types
 | 
			
		||||
        from esphome.components.libretiny import get_download_types as libretiny_types
 | 
			
		||||
@@ -551,7 +556,7 @@ class DownloadListRequestHandler(BaseHandler):
 | 
			
		||||
            downloads = rp2040_types(storage_json)
 | 
			
		||||
        elif platform == const.PLATFORM_ESP8266:
 | 
			
		||||
            downloads = esp8266_types(storage_json)
 | 
			
		||||
        elif platform == const.PLATFORM_ESP32:
 | 
			
		||||
        elif platform.upper() in ESP32_VARIANTS:
 | 
			
		||||
            downloads = esp32_types(storage_json)
 | 
			
		||||
        elif platform == const.PLATFORM_BK72XX:
 | 
			
		||||
            downloads = libretiny_types(storage_json)
 | 
			
		||||
@@ -574,7 +579,7 @@ class DownloadBinaryRequestHandler(BaseHandler):
 | 
			
		||||
    def get(self, configuration=None):
 | 
			
		||||
        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)
 | 
			
		||||
        if storage_json is None:
 | 
			
		||||
            self.send_error(404)
 | 
			
		||||
@@ -663,9 +668,7 @@ class DashboardEntry:
 | 
			
		||||
    @property
 | 
			
		||||
    def storage(self) -> Optional[StorageJSON]:
 | 
			
		||||
        if not self._loaded_storage:
 | 
			
		||||
            self._storage = StorageJSON.load(
 | 
			
		||||
                ext_storage_path(settings.config_dir, self.filename)
 | 
			
		||||
            )
 | 
			
		||||
            self._storage = StorageJSON.load(ext_storage_path(self.filename))
 | 
			
		||||
            self._loaded_storage = True
 | 
			
		||||
        return self._storage
 | 
			
		||||
 | 
			
		||||
@@ -1041,9 +1044,9 @@ class DeleteRequestHandler(BaseHandler):
 | 
			
		||||
    @bind_config
 | 
			
		||||
    def post(self, configuration=None):
 | 
			
		||||
        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)
 | 
			
		||||
        shutil.move(config_file, os.path.join(trash_path, configuration))
 | 
			
		||||
 | 
			
		||||
@@ -1064,7 +1067,7 @@ class UndoDeleteRequestHandler(BaseHandler):
 | 
			
		||||
    @bind_config
 | 
			
		||||
    def post(self, configuration=None):
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1322,10 +1325,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
 | 
			
		||||
 | 
			
		||||
def start_web_server(args):
 | 
			
		||||
    settings.parse_args(args)
 | 
			
		||||
    mkdir_p(settings.rel_path(".esphome"))
 | 
			
		||||
 | 
			
		||||
    if settings.using_auth:
 | 
			
		||||
        path = esphome_storage_path(settings.config_dir)
 | 
			
		||||
        path = esphome_storage_path()
 | 
			
		||||
        storage = EsphomeStorageJSON.load(path)
 | 
			
		||||
        if storage is None:
 | 
			
		||||
            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:
 | 
			
		||||
    base_dir = Path(CORE.config_dir) / ".esphome" / domain
 | 
			
		||||
    base_dir = Path(CORE.data_dir) / domain
 | 
			
		||||
    h = hashlib.new("sha256")
 | 
			
		||||
    h.update(key.encode())
 | 
			
		||||
    return base_dir / h.hexdigest()[:8]
 | 
			
		||||
 
 | 
			
		||||
@@ -22,19 +22,19 @@ _LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", f"{config_filename}.json")
 | 
			
		||||
def ext_storage_path(config_filename: str) -> str:
 | 
			
		||||
    return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esphome_storage_path(base_path: str) -> str:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", "esphome.json")
 | 
			
		||||
def esphome_storage_path() -> str:
 | 
			
		||||
    return os.path.join(CORE.data_dir, "esphome.json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def trash_storage_path(base_path: str) -> str:
 | 
			
		||||
    return os.path.join(base_path, ".esphome", "trash")
 | 
			
		||||
def trash_storage_path() -> str:
 | 
			
		||||
    return CORE.relative_config_path("trash")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StorageJSON:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ import unicodedata
 | 
			
		||||
import voluptuous as vol
 | 
			
		||||
 | 
			
		||||
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.log import color, Fore
 | 
			
		||||
 | 
			
		||||
from esphome.log import Fore, color
 | 
			
		||||
from esphome.storage_json import StorageJSON, ext_storage_path
 | 
			
		||||
from esphome.util import safe_print
 | 
			
		||||
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
 | 
			
		||||
 | 
			
		||||
CORE_BIG = r"""    _____ ____  _____  ______
 | 
			
		||||
   / ____/ __ \|  __ \|  ____|
 | 
			
		||||
@@ -193,10 +193,10 @@ captive_portal:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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.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
 | 
			
		||||
 | 
			
		||||
    name = kwargs["name"]
 | 
			
		||||
@@ -225,7 +225,7 @@ def wizard_write(path, **kwargs):
 | 
			
		||||
 | 
			
		||||
    write_file(path, wizard_file(**kwargs))
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    return True
 | 
			
		||||
@@ -265,9 +265,9 @@ def strip_accents(value):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wizard(path):
 | 
			
		||||
    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.bk72xx import boards as bk72xx_boards
 | 
			
		||||
    from esphome.components.rtl87xx import boards as rtl87xx_boards
 | 
			
		||||
 | 
			
		||||
    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."
 | 
			
		||||
        )
 | 
			
		||||
        return 2
 | 
			
		||||
 | 
			
		||||
    CORE.config_path = path
 | 
			
		||||
 | 
			
		||||
    safe_print("Hi there!")
 | 
			
		||||
    sleep(1.5)
 | 
			
		||||
    safe_print("I'm the wizard of ESPHome :)")
 | 
			
		||||
 
 | 
			
		||||
@@ -123,6 +123,7 @@ lib_deps =
 | 
			
		||||
    DNSServer                            ; captive_portal (Arduino built-in)
 | 
			
		||||
    esphome/ESP32-audioI2S@2.0.7         ; i2s_audio
 | 
			
		||||
    crankyoldgit/IRremoteESP8266@2.7.12  ; heatpumpir
 | 
			
		||||
    droscy/esp_wireguard@0.3.2           ; wireguard
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:arduino.build_flags}
 | 
			
		||||
    -DUSE_ESP32
 | 
			
		||||
@@ -141,6 +142,7 @@ framework = espidf
 | 
			
		||||
lib_deps =
 | 
			
		||||
    ${common:idf.lib_deps}
 | 
			
		||||
    espressif/esp32-camera@1.0.0  ; esp32_camera
 | 
			
		||||
    droscy/esp_wireguard@0.3.2    ; wireguard
 | 
			
		||||
build_flags =
 | 
			
		||||
    ${common:idf.build_flags}
 | 
			
		||||
    -Wno-nonnull-compare
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ esptool==4.6.2
 | 
			
		||||
click==8.1.7
 | 
			
		||||
esphome-dashboard==20230904.0
 | 
			
		||||
aioesphomeapi==15.0.0
 | 
			
		||||
zeroconf==0.88.0
 | 
			
		||||
zeroconf==0.102.0
 | 
			
		||||
 | 
			
		||||
# esp-idf requires this, but doesn't bundle it by default
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Unit tests
 | 
			
		||||
pytest==7.4.1
 | 
			
		||||
pytest==7.4.2
 | 
			
		||||
pytest-cov==4.1.0
 | 
			
		||||
pytest-mock==3.11.1
 | 
			
		||||
pytest-asyncio==0.21.1
 | 
			
		||||
 
 | 
			
		||||
@@ -27,3 +27,4 @@ Current test_.yaml file contents.
 | 
			
		||||
| test6.yaml | RP2040 | wifi | N/A
 | 
			
		||||
| test7.yaml | ESP32-C3 | wifi | N/A
 | 
			
		||||
| test8.yaml | ESP32-S3 | wifi | None
 | 
			
		||||
| test10.yaml | ESP32 | wifi | None
 | 
			
		||||
 
 | 
			
		||||
@@ -915,6 +915,23 @@ sensor:
 | 
			
		||||
    temperature:
 | 
			
		||||
      name: MPU6886 Temperature
 | 
			
		||||
    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
 | 
			
		||||
    address: 0x30
 | 
			
		||||
    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
 | 
			
		||||
  build_path: build/test2
 | 
			
		||||
 | 
			
		||||
globals:
 | 
			
		||||
  - id: my_global_string
 | 
			
		||||
    type: std::string
 | 
			
		||||
    restore_value: yes
 | 
			
		||||
    max_restore_data_length: 70
 | 
			
		||||
    initial_value: '"DefaultValue"'
 | 
			
		||||
 | 
			
		||||
substitutions:
 | 
			
		||||
  devicename: test2
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ spi:
 | 
			
		||||
  clk_pin: GPIO21
 | 
			
		||||
  mosi_pin: GPIO22
 | 
			
		||||
  miso_pin: GPIO23
 | 
			
		||||
  interface: hardware
 | 
			
		||||
 | 
			
		||||
uart:
 | 
			
		||||
  - id: uart115200
 | 
			
		||||
 
 | 
			
		||||
@@ -563,6 +563,13 @@ script:
 | 
			
		||||
          then:
 | 
			
		||||
            - logger.log: looping!
 | 
			
		||||
 | 
			
		||||
  - id: zero_repeat_test
 | 
			
		||||
    then:
 | 
			
		||||
      - repeat:
 | 
			
		||||
          count: !lambda "return 0;"
 | 
			
		||||
          then:
 | 
			
		||||
            - logger.log: shouldn't see mee!
 | 
			
		||||
 | 
			
		||||
switch:
 | 
			
		||||
  - platform: modbus_controller
 | 
			
		||||
    modbus_controller_id: modbus_controller_test
 | 
			
		||||
 
 | 
			
		||||
@@ -62,3 +62,6 @@ switch:
 | 
			
		||||
sensor:
 | 
			
		||||
  - platform: internal_temperature
 | 
			
		||||
    name: Internal Temperature
 | 
			
		||||
  - platform: adc
 | 
			
		||||
    pin: VCC
 | 
			
		||||
    name: VSYS
 | 
			
		||||
 
 | 
			
		||||
@@ -29,10 +29,25 @@ light:
 | 
			
		||||
    name: neopixel-enable
 | 
			
		||||
    internal: false
 | 
			
		||||
    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:
 | 
			
		||||
  id: spi_id_1
 | 
			
		||||
  clk_pin: GPIO7
 | 
			
		||||
  mosi_pin: GPIO6
 | 
			
		||||
  interface: any
 | 
			
		||||
 | 
			
		||||
spi_device:
 | 
			
		||||
  id: spidev
 | 
			
		||||
  data_rate: 2MHz
 | 
			
		||||
  spi_id: spi_id_1
 | 
			
		||||
  mode: 3
 | 
			
		||||
  bit_order: lsb_first
 | 
			
		||||
 | 
			
		||||
display:
 | 
			
		||||
  - platform: ili9xxx
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
"""Tests for the wizard.py file."""
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import esphome.wizard as wz
 | 
			
		||||
import pytest
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
 | 
			
		||||
from esphome.components.esp32.boards import ESP32_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
 | 
			
		||||
    del default_config["platform"]
 | 
			
		||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
			
		||||
    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
			
		||||
 | 
			
		||||
    # When
 | 
			
		||||
    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]
 | 
			
		||||
 | 
			
		||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
			
		||||
    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
			
		||||
 | 
			
		||||
    # When
 | 
			
		||||
    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]
 | 
			
		||||
 | 
			
		||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
			
		||||
    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
			
		||||
 | 
			
		||||
    # When
 | 
			
		||||
    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]
 | 
			
		||||
 | 
			
		||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
			
		||||
    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
			
		||||
 | 
			
		||||
    # When
 | 
			
		||||
    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]
 | 
			
		||||
 | 
			
		||||
    monkeypatch.setattr(wz, "write_file", MagicMock())
 | 
			
		||||
    monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
 | 
			
		||||
 | 
			
		||||
    # When
 | 
			
		||||
    wz.wizard_write(tmp_path, **default_config)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user