1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-12 07:58:17 +00:00

Merge branch 'dev' into jesserockz-2023-304

This commit is contained in:
Jesse Hills 2023-09-12 09:50:22 +12:00 committed by GitHub
commit 3941f16465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2541 additions and 620 deletions

View File

@ -8,7 +8,7 @@ on:
branches: [dev, beta, release] branches: [dev, beta, release]
paths: paths:
- "docker/**" - "docker/**"
- ".github/workflows/**" - ".github/workflows/ci-docker.yml"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py" - "script/platformio_install_deps.py"
@ -16,7 +16,7 @@ on:
pull_request: pull_request:
paths: paths:
- "docker/**" - "docker/**"
- ".github/workflows/**" - ".github/workflows/ci-docker.yml"
- "requirements*.txt" - "requirements*.txt"
- "platformio.ini" - "platformio.ini"
- "script/platformio_install_deps.py" - "script/platformio_install_deps.py"

View File

@ -41,7 +41,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@ -232,7 +232,7 @@ jobs:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
matrix: matrix:
file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 9, 9.1] file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 9, 9.1, 10]
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -302,7 +302,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio - name: Cache platformio
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.2
with: with:
path: ~/.platformio path: ~/.platformio
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

6
.gitignore vendored
View File

@ -13,6 +13,12 @@ __pycache__/
# Intellij Idea # Intellij Idea
.idea .idea
# Eclipse
.project
.cproject
.pydevproject
.settings/
# Vim # Vim
*.swp *.swp

View File

@ -2,7 +2,7 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.7.0 rev: 23.7.0
hooks: hooks:
- id: black - id: black

View File

@ -49,6 +49,7 @@ esphome/components/bl0942/* @dbuezas
esphome/components/ble_client/* @buxtronix esphome/components/ble_client/* @buxtronix
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @martgras esphome/components/bmp3xx/* @martgras
esphome/components/bmp581/* @kahrendt esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
@ -270,6 +271,8 @@ esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/speaker/* @jesserockz esphome/components/speaker/* @jesserockz
esphome/components/spi/* @esphome/core esphome/components/spi/* @esphome/core
esphome/components/spi_device/* @clydebarrow
esphome/components/spi_led_strip/* @clydebarrow
esphome/components/sprinkler/* @kbx81 esphome/components/sprinkler/* @kbx81
esphome/components/sps30/* @martgras esphome/components/sps30/* @martgras
esphome/components/ssd1322_base/* @kbx81 esphome/components/ssd1322_base/* @kbx81
@ -330,6 +333,7 @@ esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wl_134/* @hobbypunk90 esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_lywsd03mmc/* @ahpohl

View File

@ -35,11 +35,16 @@ if bashio::config.has_value 'default_compile_process_limit'; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit') export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
else else
if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1; export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1
fi fi
fi fi
mkdir -p "${pio_cache_base}" mkdir -p "${pio_cache_base}"
if bashio::fs.directory_exists '/config/esphome/.esphome'; then
bashio::log.info "Removing old .esphome directory..."
rm -rf /config/esphome/.esphome
fi
bashio::log.info "Starting ESPHome dashboard..." bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon

View File

@ -85,6 +85,8 @@ def choose_upload_log_host(
options = [] options = []
for port in get_serial_ports(): for port in get_serial_ports():
options.append((f"{port.path} ({port.description})", port.path)) options.append((f"{port.path} ({port.description})", port.path))
if default == "SERIAL":
return choose_prompt(options, purpose=purpose)
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address)) options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == "OTA": if default == "OTA":
@ -218,14 +220,16 @@ def compile_program(args, config):
return 0 if idedata is not None else 1 return 0 if idedata is not None else 1
def upload_using_esptool(config, port): def upload_using_esptool(config, port, file):
from esphome import platformio_api from esphome import platformio_api
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
"upload_speed", 460800 "upload_speed", 460800
) )
def run_esptool(baud_rate): if file is not None:
flash_images = [platformio_api.FlashImage(path=file, offset="0x0")]
else:
idedata = platformio_api.get_idedata(config) idedata = platformio_api.get_idedata(config)
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
@ -236,12 +240,13 @@ def upload_using_esptool(config, port):
*idedata.extra_flash_images, *idedata.extra_flash_images,
] ]
mcu = "esp8266" mcu = "esp8266"
if CORE.is_esp32: if CORE.is_esp32:
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
mcu = get_esp32_variant().lower() mcu = get_esp32_variant().lower()
def run_esptool(baud_rate):
cmd = [ cmd = [
"esptool.py", "esptool.py",
"--before", "--before",
@ -292,7 +297,8 @@ def upload_using_platformio(config, port):
def upload_program(config, args, host): def upload_program(config, args, host):
if get_port_type(host) == "SERIAL": if get_port_type(host) == "SERIAL":
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
return upload_using_esptool(config, host) file = getattr(args, "file", None)
return upload_using_esptool(config, host, file)
if CORE.target_platform in (PLATFORM_RP2040): if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, args.device) return upload_using_platformio(config, args.device)

View File

@ -5,6 +5,10 @@ from esphome.const import CONF_ANALOG, CONF_INPUT
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import (
PLATFORM_ESP8266,
PLATFORM_RP2040,
)
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@ -143,7 +147,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
def validate_adc_pin(value): def validate_adc_pin(value):
if str(value).upper() == "VCC": if str(value).upper() == "VCC":
return cv.only_on_esp8266("VCC") return cv.only_on([PLATFORM_ESP8266, PLATFORM_RP2040])("VCC")
if str(value).upper() == "TEMPERATURE": if str(value).upper() == "TEMPERATURE":
return cv.only_on_rp2040("TEMPERATURE") return cv.only_on_rp2040("TEMPERATURE")

View File

@ -12,6 +12,9 @@ ADC_MODE(ADC_VCC)
#endif #endif
#ifdef USE_RP2040 #ifdef USE_RP2040
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif
#include <hardware/adc.h> #include <hardware/adc.h>
#endif #endif
@ -123,13 +126,19 @@ void ADCSensor::dump_config() {
} }
} }
#endif // USE_ESP32 #endif // USE_ESP32
#ifdef USE_RP2040 #ifdef USE_RP2040
if (this->is_temperature_) { if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature"); ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else { } else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
#endif // USE_ADC_SENSOR_VCC
} }
#endif #endif // USE_RP2040
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
@ -238,7 +247,20 @@ float ADCSensor::sample() {
delay(1); delay(1);
adc_select_input(4); adc_select_input(4);
} else { } else {
uint8_t pin = this->pin_->get_pin(); uint8_t pin;
#ifdef USE_ADC_SENSOR_VCC
#ifdef CYW43_USES_VSYS_PIN
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
#endif // CYW43_USES_VSYS_PIN
pin = PICO_VSYS_PIN;
#else
pin = this->pin_->get_pin();
#endif // USE_ADC_SENSOR_VCC
adc_gpio_init(pin); adc_gpio_init(pin);
adc_select_input(pin - 26); adc_select_input(pin - 26);
} }
@ -246,11 +268,23 @@ float ADCSensor::sample() {
int32_t raw = adc_read(); int32_t raw = adc_read();
if (this->is_temperature_) { if (this->is_temperature_) {
adc_set_temp_sensor_enabled(false); adc_set_temp_sensor_enabled(false);
} else {
#ifdef USE_ADC_SENSOR_VCC
#ifdef CYW43_USES_VSYS_PIN
cyw43_thread_exit();
#endif // CYW43_USES_VSYS_PIN
#endif // USE_ADC_SENSOR_VCC
} }
if (output_raw_) { if (output_raw_) {
return raw; return raw;
} }
return raw * 3.3f / 4096.0f; float coeff = 1.0;
#ifdef USE_ADC_SENSOR_VCC
// As per Raspberry Pico (W) datasheet (section 2.1) the VSYS/3 is measured
coeff = 3.0;
#endif // USE_ADC_SENSOR_VCC
return raw * 3.3f / 4096.0f * coeff;
} }
#endif #endif

View File

@ -6,6 +6,9 @@ from esphome.const import (
CONF_REACTIVE_POWER, CONF_REACTIVE_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_CURRENT, CONF_CURRENT,
CONF_PHASE_A,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_FREQUENCY, CONF_FREQUENCY,
@ -31,10 +34,6 @@ from esphome.const import (
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_LINE_FREQUENCY = "line_frequency" CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga" CONF_GAIN_PGA = "gain_pga"

View File

@ -0,0 +1 @@
CODEOWNERS = ["@flaviut"]

View 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

View 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

View 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))

View File

@ -17,6 +17,8 @@
#include <esp32/rom/rtc.h> #include <esp32/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32C3) #elif defined(USE_ESP32_VARIANT_ESP32C3)
#include <esp32c3/rom/rtc.h> #include <esp32c3/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32C6)
#include <esp32c6/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S2) #elif defined(USE_ESP32_VARIANT_ESP32S2)
#include <esp32s2/rom/rtc.h> #include <esp32s2/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32S3)
@ -119,6 +121,8 @@ void DebugComponent::dump_config() {
model = "ESP32"; model = "ESP32";
#elif defined(USE_ESP32_VARIANT_ESP32C3) #elif defined(USE_ESP32_VARIANT_ESP32C3)
model = "ESP32-C3"; model = "ESP32-C3";
#elif defined(USE_ESP32_VARIANT_ESP32C6)
model = "ESP32-C6";
#elif defined(USE_ESP32_VARIANT_ESP32S2) #elif defined(USE_ESP32_VARIANT_ESP32S2)
model = "ESP32-S2"; model = "ESP32-S2";
#elif defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32S3)
@ -202,9 +206,11 @@ void DebugComponent::dump_config() {
case RTCWDT_SYS_RESET: case RTCWDT_SYS_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core"; reset_reason = "RTC Watch Dog Reset Digital Core";
break; break;
#if !defined(USE_ESP32_VARIANT_ESP32C6)
case INTRUSION_RESET: case INTRUSION_RESET:
reset_reason = "Intrusion Reset CPU"; reset_reason = "Intrusion Reset CPU";
break; break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case TGWDT_CPU_RESET: case TGWDT_CPU_RESET:
reset_reason = "Timer Group Reset CPU"; reset_reason = "Timer Group Reset CPU";

View File

@ -22,6 +22,7 @@ from esphome.const import (
CONF_IGNORE_EFUSE_MAC_CRC, CONF_IGNORE_EFUSE_MAC_CRC,
KEY_CORE, KEY_CORE,
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
KEY_NAME,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
TYPE_GIT, TYPE_GIT,
@ -37,6 +38,7 @@ from .const import ( # noqa
KEY_BOARD, KEY_BOARD,
KEY_COMPONENTS, KEY_COMPONENTS,
KEY_ESP32, KEY_ESP32,
KEY_EXTRA_BUILD_FILES,
KEY_PATH, KEY_PATH,
KEY_REF, KEY_REF,
KEY_REFRESH, KEY_REFRESH,
@ -73,6 +75,8 @@ def set_core_data(config):
) )
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
return config return config
@ -166,6 +170,24 @@ def add_idf_component(
} }
def add_extra_script(stage: str, filename: str, path: str):
"""Add an extra script to the project."""
key = f"{stage}:{filename}"
if add_extra_build_file(filename, path):
cg.add_platformio_option("extra_scripts", [key])
def add_extra_build_file(filename: str, path: str) -> bool:
"""Add an extra build file to the project."""
if filename not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES][filename] = {
KEY_NAME: filename,
KEY_PATH: path,
}
return True
return False
def _format_framework_arduino_version(ver: cv.Version) -> str: def _format_framework_arduino_version(ver: cv.Version) -> str:
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
# a PIO platformio/framework-arduinoespressif32 value # a PIO platformio/framework-arduinoespressif32 value
@ -390,7 +412,11 @@ async def to_code(config):
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option("extra_scripts", ["post:post_build.py"]) add_extra_script(
"post",
"post_build2.py",
os.path.join(os.path.dirname(__file__), "post_build.py.script"),
)
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option("framework", "espidf") cg.add_platformio_option("framework", "espidf")
@ -604,9 +630,15 @@ def copy_files():
ignore_dangling_symlinks=True, ignore_dangling_symlinks=True,
) )
dir = os.path.dirname(__file__) for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
post_build_file = os.path.join(dir, "post_build.py.script") if file[KEY_PATH].startswith("http"):
copy_file_if_changed( import requests
post_build_file,
CORE.relative_build_path("post_build.py"), mkdir_p(CORE.relative_build_path(os.path.dirname(file[KEY_NAME])))
) with open(CORE.relative_build_path(file[KEY_NAME]), "wb") as f:
f.write(requests.get(file[KEY_PATH], timeout=30).content)
else:
copy_file_if_changed(
file[KEY_PATH],
CORE.relative_build_path(file[KEY_NAME]),
)

View File

@ -10,6 +10,7 @@ KEY_REF = "ref"
KEY_REFRESH = "refresh" KEY_REFRESH = "refresh"
KEY_PATH = "path" KEY_PATH = "path"
KEY_SUBMODULES = "submodules" KEY_SUBMODULES = "submodules"
KEY_EXTRA_BUILD_FILES = "extra_build_files"
VARIANT_ESP32 = "ESP32" VARIANT_ESP32 = "ESP32"
VARIANT_ESP32S2 = "ESP32S2" VARIANT_ESP32S2 = "ESP32S2"

View File

@ -53,7 +53,11 @@ void arch_init() {
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
#if ESP_IDF_VERSION_MAJOR >= 5
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
#else
uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); } uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
#endif
uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); } uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); }
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF

View File

@ -98,10 +98,9 @@ def validate_truetype_file(value):
def _compute_local_font_dir(name) -> Path: def _compute_local_font_dir(name) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
h = hashlib.new("sha256") h = hashlib.new("sha256")
h.update(name.encode()) h.update(name.encode())
return base_dir / h.hexdigest()[:8] return Path(CORE.data_dir) / DOMAIN / h.hexdigest()[:8]
def _compute_gfonts_local_path(value) -> Path: def _compute_gfonts_local_path(value) -> Path:

View File

@ -15,8 +15,14 @@ CODEOWNERS = ["@esphome/core"]
globals_ns = cg.esphome_ns.namespace("globals") globals_ns = cg.esphome_ns.namespace("globals")
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
RestoringGlobalStringComponent = globals_ns.class_(
"RestoringGlobalStringComponent", cg.Component
)
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action) GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
MULTI_CONF = True MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
@ -24,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Required(CONF_TYPE): cv.string_strict, cv.Required(CONF_TYPE): cv.string_strict,
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict, cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -32,12 +39,19 @@ CONFIG_SCHEMA = cv.Schema(
@coroutine_with_priority(-100.0) @coroutine_with_priority(-100.0)
async def to_code(config): async def to_code(config):
type_ = cg.RawExpression(config[CONF_TYPE]) type_ = cg.RawExpression(config[CONF_TYPE])
template_args = cg.TemplateArguments(type_)
restore = config[CONF_RESTORE_VALUE] restore = config[CONF_RESTORE_VALUE]
type = RestoringGlobalsComponent if restore else GlobalsComponent # Special casing the strings to their own class with a different save/restore mechanism
res_type = type.template(template_args) if str(type_) == "std::string" and restore:
template_args = cg.TemplateArguments(
type_, config.get(CONF_MAX_RESTORE_DATA_LENGTH, 63) + 1
)
type = RestoringGlobalStringComponent
else:
template_args = cg.TemplateArguments(type_)
type = RestoringGlobalsComponent if restore else GlobalsComponent
res_type = type.template(template_args)
initial_value = None initial_value = None
if CONF_INITIAL_VALUE in config: if CONF_INITIAL_VALUE in config:
initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE]) initial_value = cg.RawExpression(config[CONF_INITIAL_VALUE])

View File

@ -65,6 +65,64 @@ template<typename T> class RestoringGlobalsComponent : public Component {
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
}; };
// Use with string or subclasses of strings
template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public Component {
public:
using value_type = T;
explicit RestoringGlobalStringComponent() = default;
explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; }
explicit RestoringGlobalStringComponent(
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
memcpy(this->value_, initial_value.data(), sizeof(T));
}
T &value() { return this->value_; }
void setup() override {
char temp[SZ];
this->rtc_ = global_preferences->make_preference<uint8_t[SZ]>(1944399030U ^ this->name_hash_);
bool hasdata = this->rtc_.load(&temp);
if (hasdata) {
this->value_.assign(temp + 1, temp[0]);
}
this->prev_value_.assign(this->value_);
}
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void loop() override { store_value_(); }
void on_shutdown() override { store_value_(); }
void set_name_hash(uint32_t name_hash) { this->name_hash_ = name_hash; }
protected:
void store_value_() {
int diff = this->value_.compare(this->prev_value_);
if (diff != 0) {
// Make it into a length prefixed thing
unsigned char temp[SZ];
// If string is bigger than the allocation, do not save it.
// We don't need to waste ram setting prev_value either.
int size = this->value_.size();
// Less than, not less than or equal, SZ includes the length byte.
if (size < SZ) {
memcpy(temp + 1, this->value_.c_str(), size);
// SZ should be pre checked at the schema level, it can't go past the char range.
temp[0] = ((unsigned char) size);
this->rtc_.save(&temp);
this->prev_value_.assign(this->value_);
}
}
}
T value_{};
T prev_value_{};
uint32_t name_hash_{};
ESPPreferenceObject rtc_;
};
template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> { template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...> {
public: public:
explicit GlobalVarSetAction(C *parent) : parent_(parent) {} explicit GlobalVarSetAction(C *parent) : parent_(parent) {}
@ -81,6 +139,7 @@ template<class C, typename... Ts> class GlobalVarSetAction : public Action<Ts...
template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); } template<typename T> T &id(GlobalsComponent<T> *value) { return value->value(); }
template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); } template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
} // namespace globals } // namespace globals
} // namespace esphome } // namespace esphome

View File

@ -6,6 +6,9 @@ from esphome.const import (
CONF_CURRENT, CONF_CURRENT,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_ID, CONF_ID,
CONF_PHASE_A,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
@ -21,10 +24,6 @@ from esphome.const import (
UNIT_WATT, UNIT_WATT,
) )
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
CONF_TOTAL_GENERATION_TIME = "total_generation_time" CONF_TOTAL_GENERATION_TIME = "total_generation_time"

View File

@ -6,6 +6,9 @@ from esphome.const import (
CONF_CURRENT, CONF_CURRENT,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_ID, CONF_ID,
CONF_PHASE_A,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_REACTIVE_POWER, CONF_REACTIVE_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
@ -24,9 +27,6 @@ from esphome.const import (
UNIT_WATT, UNIT_WATT,
) )
CONF_PHASE_A = "phase_a"
CONF_PHASE_B = "phase_b"
CONF_PHASE_C = "phase_c"
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
CONF_TOTAL_GENERATION_TIME = "total_generation_time" CONF_TOTAL_GENERATION_TIME = "total_generation_time"

View File

@ -52,7 +52,7 @@ Image_ = image_ns.class_("Image")
def _compute_local_icon_path(value) -> Path: def _compute_local_icon_path(value) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi" base_dir = Path(CORE.data_dir) / DOMAIN / "mdi"
return base_dir / f"{value[CONF_ICON]}.svg" return base_dir / f"{value[CONF_ICON]}.svg"

View File

@ -168,9 +168,9 @@ def _notify_old_style(config):
# NOTE: Keep this in mind when updating the recommended version: # NOTE: Keep this in mind when updating the recommended version:
# * For all constants below, update platformio.ini (in this repo) # * For all constants below, update platformio.ini (in this repo)
ARDUINO_VERSIONS = { ARDUINO_VERSIONS = {
"dev": (cv.Version(0, 0, 0), "https://github.com/kuba2k2/libretiny.git"), "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"),
"latest": (cv.Version(0, 0, 0), None), "latest": (cv.Version(0, 0, 0), None),
"recommended": (cv.Version(1, 3, 0), None), "recommended": (cv.Version(1, 4, 0), None),
} }

View File

@ -54,7 +54,7 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) { bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
uint8_t i = get_mifare_classic_ndef_start_index(data); uint8_t i = get_mifare_classic_ndef_start_index(data);
if (i < 0 || data[i] != 0x03) { if (data[i] != 0x03) {
ESP_LOGE(TAG, "Error, Can't decode message length."); ESP_LOGE(TAG, "Error, Can't decode message length.");
return false; return false;
} }

View File

@ -7,6 +7,7 @@
#include "esphome/components/nfc/nfc.h" #include "esphome/components/nfc/nfc.h"
#include "esphome/components/nfc/automation.h" #include "esphome/components/nfc/automation.h"
#include <cinttypes>
#include <vector> #include <vector>
namespace esphome { namespace esphome {
@ -74,10 +75,11 @@ class PN532 : public PollingComponent {
bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid); std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid);
bool read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &data); bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
bool is_mifare_ultralight_formatted_(); bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
uint16_t read_mifare_ultralight_capacity_(); uint16_t read_mifare_ultralight_capacity_();
bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index); bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index);
bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data); bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_(); bool clean_mifare_ultralight_();

View File

@ -9,93 +9,104 @@ namespace pn532 {
static const char *const TAG = "pn532.mifare_ultralight"; static const char *const TAG = "pn532.mifare_ultralight";
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) { std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) {
if (!this->is_mifare_ultralight_formatted_()) { std::vector<uint8_t> data;
ESP_LOGD(TAG, "Not NDEF formatted"); // pages 3 to 6 contain various info we are interested in -- do one read to grab it all
if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
data)) {
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
}
if (!this->is_mifare_ultralight_formatted_(data)) {
ESP_LOGW(TAG, "Not NDEF formatted");
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2); return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
} }
uint8_t message_length; uint8_t message_length;
uint8_t message_start_index; uint8_t message_start_index;
if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { if (!this->find_mifare_ultralight_ndef_(data, message_length, message_start_index)) {
ESP_LOGW(TAG, "Couldn't find NDEF message");
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2); return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
} }
ESP_LOGVV(TAG, "message length: %d, start: %d", message_length, message_start_index); ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
if (message_length == 0) { if (message_length == 0) {
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2); return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
} }
std::vector<uint8_t> data; // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
std::vector<uint8_t> page_data; if (read_length) {
if (!this->read_mifare_ultralight_page_(page, page_data)) { if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) {
ESP_LOGE(TAG, "Error reading page %d", page); ESP_LOGE(TAG, "Error reading tag data");
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2); return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
} }
data.insert(data.end(), page_data.begin(), page_data.end());
if (data.size() >= (message_length + message_start_index))
break;
} }
// we need to trim off page 3 as well as any bytes ahead of message_start_index
data.erase(data.begin(), data.begin() + message_start_index); data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
data.erase(data.begin() + message_length, data.end());
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data); return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data);
} }
bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &data) { bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
if (!this->write_command_({ const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
PN532_COMMAND_INDATAEXCHANGE, std::vector<uint8_t> response;
0x01, // One card
nfc::MIFARE_CMD_READ, for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
page_num, if (!this->write_command_({
})) { PN532_COMMAND_INDATAEXCHANGE,
return false; 0x01, // One card
nfc::MIFARE_CMD_READ,
uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
})) {
return false;
}
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
return false;
}
uint16_t bytes_offset = (i + 1) * read_increment;
auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes);
if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) {
data.insert(data.end(), response.begin() + 1, pages_in_end_itr);
}
} }
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
return false;
}
data.erase(data.begin());
// We only want 1 page of data but the PN532 returns 4 at once.
data.erase(data.begin() + 4, data.end());
ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str());
return true; return true;
} }
bool PN532::is_mifare_ultralight_formatted_() { bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
std::vector<uint8_t> data; const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
if (this->read_mifare_ultralight_page_(4, data)) {
return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); return (page_3_to_6.size() > p4_offset + 3) &&
} !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) &&
return true; (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF));
} }
uint16_t PN532::read_mifare_ultralight_capacity_() { uint16_t PN532::read_mifare_ultralight_capacity_() {
std::vector<uint8_t> data; std::vector<uint8_t> data;
if (this->read_mifare_ultralight_page_(3, data)) { if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) {
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
return data[2] * 8U; return data[2] * 8U;
} }
return 0; return 0;
} }
bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) { bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
std::vector<uint8_t> data; uint8_t &message_start_index) {
for (int page = 4; page < 6; page++) { const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
std::vector<uint8_t> page_data;
if (!this->read_mifare_ultralight_page_(page, page_data)) { if (!(page_3_to_6.size() > p4_offset + 5)) {
return false; return false;
}
data.insert(data.end(), page_data.begin(), page_data.end());
} }
if (data[0] == 0x03) {
message_length = data[1]; if (page_3_to_6[p4_offset + 0] == 0x03) {
message_length = page_3_to_6[p4_offset + 1];
message_start_index = 2; message_start_index = 2;
return true; return true;
} else if (data[5] == 0x03) { } else if (page_3_to_6[p4_offset + 5] == 0x03) {
message_length = data[6]; message_length = page_3_to_6[p4_offset + 6];
message_start_index = 7; message_start_index = 7;
return true; return true;
} }
@ -111,7 +122,7 @@ bool PN532::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMes
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
if (buffer_length > capacity) { if (buffer_length > capacity) {
ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity); ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
return false; return false;
} }
@ -164,13 +175,13 @@ bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t>
}); });
data.insert(data.end(), write_data.begin(), write_data.end()); data.insert(data.end(), write_data.begin(), write_data.end());
if (!this->write_command_(data)) { if (!this->write_command_(data)) {
ESP_LOGE(TAG, "Error writing page %d", page_num); ESP_LOGE(TAG, "Error writing page %u", page_num);
return false; return false;
} }
std::vector<uint8_t> response; std::vector<uint8_t> response;
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) { if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
ESP_LOGE(TAG, "Error writing page %d", page_num); ESP_LOGE(TAG, "Error writing page %u", page_num);
return false; return false;
} }

View File

@ -87,12 +87,7 @@ def get_firmware(value):
url = value[CONF_URL] url = value[CONF_URL]
if CONF_SHA256 in value: # we have a hash, enable caching if CONF_SHA256 in value: # we have a hash, enable caching
path = ( path = Path(CORE.data_dir) / DOMAIN / (value[CONF_SHA256] + "_fw_stm.bin")
Path(CORE.config_dir)
/ ".esphome"
/ DOMAIN
/ (value[CONF_SHA256] + "_fw_stm.bin")
)
if not path.is_file(): if not path.is_file():
firmware_data, dl_hash = dl(url) firmware_data, dl_hash = dl(url)

View File

@ -1,6 +1,17 @@
import re
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.components.esp32.const import (
KEY_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
)
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_CLK_PIN, CONF_CLK_PIN,
@ -9,6 +20,11 @@ from esphome.const import (
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_SPI_ID, CONF_SPI_ID,
CONF_CS_PIN, CONF_CS_PIN,
CONF_NUMBER,
CONF_INVERTED,
KEY_CORE,
KEY_TARGET_PLATFORM,
KEY_VARIANT,
) )
from esphome.core import coroutine_with_priority, CORE from esphome.core import coroutine_with_priority, CORE
@ -34,10 +50,147 @@ SPI_DATA_RATE_OPTIONS = {
} }
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
MULTI_CONF = True
CONF_FORCE_SW = "force_sw" CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index"
CONFIG_SCHEMA = cv.All(
def get_target_platform():
return (
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM]
if KEY_TARGET_PLATFORM in CORE.data[KEY_CORE]
else ""
)
def get_target_variant():
return (
CORE.data[KEY_ESP32][KEY_VARIANT] if KEY_VARIANT in CORE.data[KEY_ESP32] else ""
)
# Get a list of available hardware interfaces based on target and variant.
# The returned value is a list of lists of names
def get_hw_interface_list():
target_platform = get_target_platform()
if target_platform == "esp8266":
return [["spi", "hspi"]]
if target_platform == "esp32":
if get_target_variant() in [
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
]:
return [["spi", "spi2"]]
return [["spi", "spi2"], ["spi3"]]
if target_platform == "rp2040":
return [["spi"]]
return []
# Given an SPI name, return the index of it in the available list
def get_spi_index(name):
for i, ilist in enumerate(get_hw_interface_list()):
if name in ilist:
return i
# Should never get to here.
raise cv.Invalid(f"{name} is not an available SPI")
# Check that pins are suitable for HW spi
# TODO verify that the pins are internal
def validate_hw_pins(spi):
clk_pin = spi[CONF_CLK_PIN]
if clk_pin[CONF_INVERTED]:
return False
clk_pin_no = clk_pin[CONF_NUMBER]
sdo_pin_no = -1
sdi_pin_no = -1
if CONF_MOSI_PIN in spi:
sdo_pin = spi[CONF_MOSI_PIN]
if sdo_pin[CONF_INVERTED]:
return False
sdo_pin_no = sdo_pin[CONF_NUMBER]
if CONF_MISO_PIN in spi:
sdi_pin = spi[CONF_MISO_PIN]
if sdi_pin[CONF_INVERTED]:
return False
sdi_pin_no = sdi_pin[CONF_NUMBER]
target_platform = get_target_platform()
if target_platform == "esp8266":
if clk_pin_no == 6:
return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7)
if clk_pin_no == 14:
return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12)
return False
if target_platform == "esp32":
return clk_pin_no >= 0
return False
def validate_spi_config(config):
available = list(range(len(get_hw_interface_list())))
for spi in config:
interface = spi[CONF_INTERFACE]
if spi[CONF_FORCE_SW]:
if interface == "any":
spi[CONF_INTERFACE] = interface = "software"
elif interface != "software":
raise cv.Invalid("force_sw is deprecated - use interface: software")
if interface == "software":
pass
elif interface == "any":
if not validate_hw_pins(spi):
spi[CONF_INTERFACE] = "software"
elif interface == "hardware":
if len(available) == 0:
raise cv.Invalid("No hardware interface available")
index = spi[CONF_INTERFACE_INDEX] = available[0]
available.remove(index)
else:
# Must be a specific name
index = spi[CONF_INTERFACE_INDEX] = get_spi_index(interface)
if index not in available:
raise cv.Invalid(
f"interface '{interface}' not available here (may be already assigned)"
)
available.remove(index)
# Second time around:
# Any specific names and any 'hardware' requests will have already been filled,
# so just need to assign remaining hardware to 'any' requests.
for spi in config:
if spi[CONF_INTERFACE] == "any" and len(available) != 0:
index = available[0]
spi[CONF_INTERFACE_INDEX] = index
available.remove(index)
if CONF_INTERFACE_INDEX in spi and not validate_hw_pins(spi):
raise cv.Invalid("Invalid pin selections for hardware SPI interface")
return config
# Given an SPI index, convert to a string that represents the C++ object for it.
def get_spi_interface(index):
if CORE.using_esp_idf:
return ["SPI2_HOST", "SPI3_HOST"][index]
# Arduino code follows
platform = get_target_platform()
if platform == "rp2040":
return "&spi1"
if index == 0:
return "&SPI"
# Following code can't apply to C2, H2 or 8266 since they have only one SPI
if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2):
return "new SPIClass(FSPI)"
return "return new SPIClass(HSPI)"
SPI_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(SPIComponent), cv.GenerateID(): cv.declare_id(SPIComponent),
@ -45,28 +198,47 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_FORCE_SW, default=False): cv.boolean, cv.Optional(CONF_FORCE_SW, default=False): cv.boolean,
cv.Optional(CONF_INTERFACE, default="any"): cv.one_of(
*sum(get_hw_interface_list(), ["software", "hardware", "any"]),
lower=True,
),
} }
), ),
cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
cv.only_on(["esp32", "esp8266", "rp2040"]), cv.only_on(["esp32", "esp8266", "rp2040"]),
) )
CONFIG_SCHEMA = cv.All(
cv.ensure_list(SPI_SCHEMA),
validate_spi_config,
)
@coroutine_with_priority(1.0) @coroutine_with_priority(1.0)
async def to_code(config): async def to_code(configs):
cg.add_global(spi_ns.using) cg.add_global(spi_ns.using)
var = cg.new_Pvariable(config[CONF_ID]) for spi in configs:
await cg.register_component(var, config) var = cg.new_Pvariable(spi[CONF_ID])
await cg.register_component(var, spi)
clk = await cg.gpio_pin_expression(config[CONF_CLK_PIN]) clk = await cg.gpio_pin_expression(spi[CONF_CLK_PIN])
cg.add(var.set_clk(clk)) cg.add(var.set_clk(clk))
cg.add(var.set_force_sw(config[CONF_FORCE_SW])) if CONF_MISO_PIN in spi:
if CONF_MISO_PIN in config: miso = await cg.gpio_pin_expression(spi[CONF_MISO_PIN])
miso = await cg.gpio_pin_expression(config[CONF_MISO_PIN]) cg.add(var.set_miso(miso))
cg.add(var.set_miso(miso)) if CONF_MOSI_PIN in spi:
if CONF_MOSI_PIN in config: mosi = await cg.gpio_pin_expression(spi[CONF_MOSI_PIN])
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi))
cg.add(var.set_mosi(mosi)) if CONF_INTERFACE_INDEX in spi:
index = spi[CONF_INTERFACE_INDEX]
cg.add(var.set_interface(cg.RawExpression(get_spi_interface(index))))
cg.add(
var.set_interface_name(
re.sub(
r"\W", "", get_spi_interface(index).replace("new SPIClass", "")
)
)
)
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("SPI", None) cg.add_library("SPI", None)

View File

@ -1,268 +1,116 @@
#include "spi.h" #include "spi.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
namespace esphome { namespace esphome {
namespace spi { namespace spi {
static const char *const TAG = "spi"; const char *const TAG = "spi";
void IRAM_ATTR HOT SPIComponent::disable() { SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
#ifdef USE_SPI_ARDUINO_BACKEND new SPIDelegateDummy();
if (this->hw_spi_ != nullptr) { // https://bugs.llvm.org/show_bug.cgi?id=48040
this->hw_spi_->endTransaction();
} bool SPIDelegate::is_ready() { return true; }
#endif // USE_SPI_ARDUINO_BACKEND
if (this->active_cs_) { GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
this->active_cs_->digital_write(true);
this->active_cs_ = nullptr; SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
GPIOPin *cs_pin) {
if (this->devices_.count(device) != 0) {
ESP_LOGE(TAG, "SPI device already registered");
return this->devices_[device];
} }
SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin); // NOLINT
this->devices_[device] = delegate;
return delegate;
} }
void SPIComponent::unregister_device(SPIClient *device) {
if (this->devices_.count(device) == 0) {
esph_log_e(TAG, "SPI device not registered");
return;
}
delete this->devices_[device]; // NOLINT
this->devices_.erase(device);
}
void SPIComponent::setup() { void SPIComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up SPI bus..."); ESP_LOGD(TAG, "Setting up SPI bus...");
this->clk_->setup();
this->clk_->digital_write(true);
#ifdef USE_SPI_ARDUINO_BACKEND if (this->sdo_pin_ == nullptr)
bool use_hw_spi = !this->force_sw_; this->sdo_pin_ = NullPin::NULL_PIN;
const bool has_miso = this->miso_ != nullptr; if (this->sdi_pin_ == nullptr)
const bool has_mosi = this->mosi_ != nullptr; this->sdi_pin_ = NullPin::NULL_PIN;
int8_t clk_pin = -1, miso_pin = -1, mosi_pin = -1; if (this->clk_pin_ == nullptr) {
ESP_LOGE(TAG, "No clock pin for SPI");
if (!this->clk_->is_internal()) this->mark_failed();
use_hw_spi = false;
if (has_miso && !miso_->is_internal())
use_hw_spi = false;
if (has_mosi && !mosi_->is_internal())
use_hw_spi = false;
if (use_hw_spi) {
auto *clk_internal = (InternalGPIOPin *) clk_;
auto *miso_internal = (InternalGPIOPin *) miso_;
auto *mosi_internal = (InternalGPIOPin *) mosi_;
if (clk_internal->is_inverted())
use_hw_spi = false;
if (has_miso && miso_internal->is_inverted())
use_hw_spi = false;
if (has_mosi && mosi_internal->is_inverted())
use_hw_spi = false;
if (use_hw_spi) {
clk_pin = clk_internal->get_pin();
miso_pin = has_miso ? miso_internal->get_pin() : -1;
mosi_pin = has_mosi ? mosi_internal->get_pin() : -1;
}
}
#ifdef USE_ESP8266
if (!(clk_pin == 6 && miso_pin == 7 && mosi_pin == 8) &&
!(clk_pin == 14 && (!has_miso || miso_pin == 12) && (!has_mosi || mosi_pin == 13)))
use_hw_spi = false;
if (use_hw_spi) {
this->hw_spi_ = &SPI;
this->hw_spi_->pins(clk_pin, miso_pin, mosi_pin, 0);
this->hw_spi_->begin();
return; return;
} }
#endif // USE_ESP8266
#ifdef USE_ESP32
static uint8_t spi_bus_num = 0;
if (spi_bus_num >= 2) {
use_hw_spi = false;
}
if (use_hw_spi) { if (this->using_hw_) {
if (spi_bus_num == 0) { this->spi_bus_ = SPIComponent::get_bus(this->interface_, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
this->hw_spi_ = &SPI; if (this->spi_bus_ == nullptr) {
} else { ESP_LOGE(TAG, "Unable to allocate SPI interface");
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ this->mark_failed();
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C6)
this->hw_spi_ = new SPIClass(FSPI); // NOLINT(cppcoreguidelines-owning-memory)
#else
this->hw_spi_ = new SPIClass(HSPI); // NOLINT(cppcoreguidelines-owning-memory)
#endif // USE_ESP32_VARIANT
} }
spi_bus_num++; } else {
this->hw_spi_->begin(clk_pin, miso_pin, mosi_pin); this->spi_bus_ = new SPIBus(this->clk_pin_, this->sdo_pin_, this->sdi_pin_); // NOLINT
return; this->clk_pin_->setup();
} this->clk_pin_->digital_write(true);
#endif // USE_ESP32 this->sdo_pin_->setup();
#ifdef USE_RP2040 this->sdi_pin_->setup();
static uint8_t spi_bus_num = 0;
if (spi_bus_num >= 2) {
use_hw_spi = false;
}
if (use_hw_spi) {
SPIClassRP2040 *spi;
if (spi_bus_num == 0) {
spi = &SPI;
} else {
spi = &SPI1;
}
spi_bus_num++;
if (miso_pin != -1)
spi->setRX(miso_pin);
if (mosi_pin != -1)
spi->setTX(mosi_pin);
spi->setSCK(clk_pin);
this->hw_spi_ = spi;
this->hw_spi_->begin();
return;
}
#endif // USE_RP2040
#endif // USE_SPI_ARDUINO_BACKEND
if (this->miso_ != nullptr) {
this->miso_->setup();
}
if (this->mosi_ != nullptr) {
this->mosi_->setup();
this->mosi_->digital_write(false);
} }
} }
void SPIComponent::dump_config() { void SPIComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SPI bus:"); ESP_LOGCONFIG(TAG, "SPI bus:");
LOG_PIN(" CLK Pin: ", this->clk_); LOG_PIN(" CLK Pin: ", this->clk_pin_)
LOG_PIN(" MISO Pin: ", this->miso_); LOG_PIN(" SDI Pin: ", this->sdi_pin_)
LOG_PIN(" MOSI Pin: ", this->mosi_); LOG_PIN(" SDO Pin: ", this->sdo_pin_)
#ifdef USE_SPI_ARDUINO_BACKEND if (this->spi_bus_->is_hw()) {
ESP_LOGCONFIG(TAG, " Using HW SPI: %s", YESNO(this->hw_spi_ != nullptr)); ESP_LOGCONFIG(TAG, " Using HW SPI: %s", this->interface_name_);
#endif // USE_SPI_ARDUINO_BACKEND } else {
} ESP_LOGCONFIG(TAG, " Using software SPI");
float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } }
void SPIComponent::cycle_clock_(bool value) {
uint32_t start = arch_get_cpu_cycle_count();
while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
;
this->clk_->digital_write(value);
start += this->wait_cycle_;
while (start - arch_get_cpu_cycle_count() < this->wait_cycle_)
;
} }
// NOLINTNEXTLINE void SPIDelegateDummy::begin_transaction() { ESP_LOGE(TAG, "SPIDevice not initialised - did you call spi_setup()?"); }
#ifndef CLANG_TIDY
#pragma GCC optimize("unroll-loops")
// NOLINTNEXTLINE
#pragma GCC optimize("O2")
#endif // CLANG_TIDY
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE> uint8_t SPIDelegateBitBash::transfer(uint8_t data) {
uint8_t HOT SPIComponent::transfer_(uint8_t data) {
// Clock starts out at idle level // Clock starts out at idle level
this->clk_->digital_write(CLOCK_POLARITY); this->clk_pin_->digital_write(clock_polarity_);
uint8_t out_data = 0; uint8_t out_data = 0;
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
uint8_t shift; uint8_t shift;
if (BIT_ORDER == BIT_ORDER_MSB_FIRST) { if (bit_order_ == BIT_ORDER_MSB_FIRST) {
shift = 7 - i; shift = 7 - i;
} else { } else {
shift = i; shift = i;
} }
if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { if (clock_phase_ == CLOCK_PHASE_LEADING) {
// sampling on leading edge // sampling on leading edge
if (WRITE) { this->sdo_pin_->digital_write(data & (1 << shift));
this->mosi_->digital_write(data & (1 << shift)); this->cycle_clock_();
} out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
this->clk_pin_->digital_write(!this->clock_polarity_);
// SAMPLE! this->cycle_clock_();
this->cycle_clock_(!CLOCK_POLARITY); this->clk_pin_->digital_write(this->clock_polarity_);
if (READ) {
out_data |= uint8_t(this->miso_->digital_read()) << shift;
}
this->cycle_clock_(CLOCK_POLARITY);
} else { } else {
// sampling on trailing edge // sampling on trailing edge
this->cycle_clock_(!CLOCK_POLARITY); this->cycle_clock_();
this->clk_pin_->digital_write(!this->clock_polarity_);
if (WRITE) { this->sdo_pin_->digital_write(data & (1 << shift));
this->mosi_->digital_write(data & (1 << shift)); this->cycle_clock_();
} out_data |= uint8_t(this->sdi_pin_->digital_read()) << shift;
this->clk_pin_->digital_write(this->clock_polarity_);
// SAMPLE!
this->cycle_clock_(CLOCK_POLARITY);
if (READ) {
out_data |= uint8_t(this->miso_->digital_read()) << shift;
}
} }
} }
App.feed_wdt(); App.feed_wdt();
return out_data; return out_data;
} }
// Generate with (py3):
//
// from itertools import product
// bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST']
// clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH']
// clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING']
// reads = [False, True]
// writes = [False, True]
// cpp_bool = {False: 'false', True: 'true'}
// for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes):
// if not r and not w:
// continue
// print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t
// data);")
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>(
uint8_t data);
template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>(
uint8_t data);
} // namespace spi } // namespace spi
} // namespace esphome } // namespace esphome

View File

@ -2,16 +2,34 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <vector> #include <vector>
#include <map>
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#define USE_SPI_ARDUINO_BACKEND
#endif
#ifdef USE_SPI_ARDUINO_BACKEND
#include <SPI.h> #include <SPI.h>
#ifdef USE_RP2040
using SPIInterface = SPIClassRP2040 *;
#else
using SPIInterface = SPIClass *;
#endif #endif
#endif
#ifdef USE_ESP_IDF
#include "driver/spi_master.h"
using SPIInterface = spi_host_device_t;
#endif // USE_ESP_IDF
/**
* Implementation of SPI Controller mode.
*/
namespace esphome { namespace esphome {
namespace spi { namespace spi {
@ -48,10 +66,19 @@ enum SPIClockPhase {
/// The data is sampled on a trailing clock edge. (CPHA=1) /// The data is sampled on a trailing clock edge. (CPHA=1)
CLOCK_PHASE_TRAILING, CLOCK_PHASE_TRAILING,
}; };
/** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW.
* So effectively the rate of bytes can be calculated using /**
* Modes mapping to clock phase and polarity.
* *
* effective_byte_rate = spi_data_rate / 16 */
enum SPIMode {
MODE0 = 0,
MODE1 = 1,
MODE2 = 2,
MODE3 = 3,
};
/** The SPI clock signal frequency, which determines the transfer bit rate/second.
* *
* Implementations can use the pre-defined constants here, or use an integer in the template definition * Implementations can use the pre-defined constants here, or use an integer in the template definition
* to manually use a specific data rate. * to manually use a specific data rate.
@ -71,270 +98,340 @@ enum SPIDataRate : uint32_t {
DATA_RATE_80MHZ = 80000000, DATA_RATE_80MHZ = 80000000,
}; };
class SPIComponent : public Component { /**
* A pin to replace those that don't exist.
*/
class NullPin : public GPIOPin {
friend class SPIComponent;
friend class SPIDelegate;
friend class Utility;
public: public:
void set_clk(GPIOPin *clk) { clk_ = clk; } void setup() override {}
void set_miso(GPIOPin *miso) { miso_ = miso; }
void set_mosi(GPIOPin *mosi) { mosi_ = mosi; }
void set_force_sw(bool force_sw) { force_sw_ = force_sw; }
void setup() override; void pin_mode(gpio::Flags flags) override {}
void dump_config() override; bool digital_read() override { return false; }
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> uint8_t read_byte() { void digital_write(bool value) override {}
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
return this->hw_spi_->transfer(0x00);
}
#endif // USE_SPI_ARDUINO_BACKEND
return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, false>(0x00);
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> std::string dump_summary() const override { return std::string(); }
void read_array(uint8_t *data, size_t length) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
this->hw_spi_->transfer(data, length);
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
for (size_t i = 0; i < length; i++) {
data[i] = this->read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>();
}
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
void write_byte(uint8_t data) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
#ifdef USE_RP2040
this->hw_spi_->transfer(data);
#else
this->hw_spi_->write(data);
#endif
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, false, true>(data);
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
void write_byte16(const uint16_t data) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
#ifdef USE_RP2040
this->hw_spi_->transfer16(data);
#else
this->hw_spi_->write16(data);
#endif
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data >> 8);
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
void write_array16(const uint16_t *data, size_t length) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
for (size_t i = 0; i < length; i++) {
#ifdef USE_RP2040
this->hw_spi_->transfer16(data[i]);
#else
this->hw_spi_->write16(data[i]);
#endif
}
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
for (size_t i = 0; i < length; i++) {
this->write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
}
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
void write_array(const uint8_t *data, size_t length) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
auto *data_c = const_cast<uint8_t *>(data);
#ifdef USE_RP2040
this->hw_spi_->transfer(data_c, length);
#else
this->hw_spi_->writeBytes(data_c, length);
#endif
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
for (size_t i = 0; i < length; i++) {
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
}
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
uint8_t transfer_byte(uint8_t data) {
if (this->miso_ != nullptr) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
return this->hw_spi_->transfer(data);
} else {
#endif // USE_SPI_ARDUINO_BACKEND
return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, true>(data);
#ifdef USE_SPI_ARDUINO_BACKEND
}
#endif // USE_SPI_ARDUINO_BACKEND
}
this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
return 0;
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE>
void transfer_array(uint8_t *data, size_t length) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
if (this->miso_ != nullptr) {
this->hw_spi_->transfer(data, length);
} else {
#ifdef USE_RP2040
this->hw_spi_->transfer(data, length);
#else
this->hw_spi_->writeBytes(data, length);
#endif
}
return;
}
#endif // USE_SPI_ARDUINO_BACKEND
if (this->miso_ != nullptr) {
for (size_t i = 0; i < length; i++) {
data[i] = this->transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]);
}
} else {
this->write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
}
}
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, uint32_t DATA_RATE>
void enable(GPIOPin *cs) {
#ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) {
uint8_t data_mode = SPI_MODE0;
if (!CLOCK_POLARITY && CLOCK_PHASE) {
data_mode = SPI_MODE1;
} else if (CLOCK_POLARITY && !CLOCK_PHASE) {
data_mode = SPI_MODE2;
} else if (CLOCK_POLARITY && CLOCK_PHASE) {
data_mode = SPI_MODE3;
}
#ifdef USE_RP2040
SPISettings settings(DATA_RATE, static_cast<BitOrder>(BIT_ORDER), data_mode);
#else
SPISettings settings(DATA_RATE, BIT_ORDER, data_mode);
#endif
this->hw_spi_->beginTransaction(settings);
} else {
#endif // USE_SPI_ARDUINO_BACKEND
this->clk_->digital_write(CLOCK_POLARITY);
uint32_t cpu_freq_hz = arch_get_cpu_freq_hz();
this->wait_cycle_ = uint32_t(cpu_freq_hz) / DATA_RATE / 2ULL;
#ifdef USE_SPI_ARDUINO_BACKEND
}
#endif // USE_SPI_ARDUINO_BACKEND
if (cs != nullptr) {
this->active_cs_ = cs;
this->active_cs_->digital_write(false);
}
}
void disable();
float get_setup_priority() const override;
protected: protected:
inline void cycle_clock_(bool value); static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// https://bugs.llvm.org/show_bug.cgi?id=48040
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE>
uint8_t transfer_(uint8_t data);
GPIOPin *clk_;
GPIOPin *miso_{nullptr};
GPIOPin *mosi_{nullptr};
GPIOPin *active_cs_{nullptr};
bool force_sw_{false};
#ifdef USE_SPI_ARDUINO_BACKEND
SPIClass *hw_spi_{nullptr};
#endif // USE_SPI_ARDUINO_BACKEND
uint32_t wait_cycle_;
}; };
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE> class Utility {
class SPIDevice {
public: public:
SPIDevice() = default; static int get_pin_no(GPIOPin *pin) {
SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} if (pin == nullptr || !pin->is_internal())
return -1;
if (((InternalGPIOPin *) pin)->is_inverted())
return -1;
return ((InternalGPIOPin *) pin)->get_pin();
}
void set_spi_parent(SPIComponent *parent) { parent_ = parent; } static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) {
void set_cs_pin(GPIOPin *cs) { cs_ = cs; } if (polarity == CLOCK_POLARITY_HIGH) {
return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3;
}
return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1;
}
void spi_setup() { static SPIClockPhase get_phase(SPIMode mode) {
if (this->cs_) { switch (mode) {
this->cs_->setup(); case MODE0:
this->cs_->digital_write(true); case MODE2:
return CLOCK_PHASE_LEADING;
default:
return CLOCK_PHASE_TRAILING;
} }
} }
void enable() { this->parent_->template enable<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, DATA_RATE>(this->cs_); } static SPIClockPolarity get_polarity(SPIMode mode) {
switch (mode) {
case MODE0:
case MODE1:
return CLOCK_POLARITY_LOW;
default:
return CLOCK_POLARITY_HIGH;
}
}
};
void disable() { this->parent_->disable(); } class SPIDelegateDummy;
uint8_t read_byte() { return this->parent_->template read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(); } // represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is
// a thin wrapper over SPIClass.
class SPIDelegate {
friend class SPIClient;
void read_array(uint8_t *data, size_t length) { public:
return this->parent_->template read_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); SPIDelegate() = default;
SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin)
: bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) {
if (this->cs_pin_ == nullptr)
this->cs_pin_ = NullPin::NULL_PIN;
this->cs_pin_->setup();
this->cs_pin_->digital_write(true);
} }
template<size_t N> std::array<uint8_t, N> read_array() { virtual ~SPIDelegate(){};
std::array<uint8_t, N> data;
this->read_array(data.data(), N); // enable CS if configured.
return data; virtual void begin_transaction() { this->cs_pin_->digital_write(false); }
// end the transaction
virtual void end_transaction() { this->cs_pin_->digital_write(true); }
// transfer one byte, return the byte that was read.
virtual uint8_t transfer(uint8_t data) = 0;
// transfer a buffer, replace the contents with read data
virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); }
virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) {
for (size_t i = 0; i != length; i++)
rxbuf[i] = this->transfer(txbuf[i]);
} }
void write_byte(uint8_t data) { // write 16 bits
return this->parent_->template write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data); virtual void write16(uint16_t data) {
if (this->bit_order_ == BIT_ORDER_MSB_FIRST) {
uint16_t buffer;
buffer = (data >> 8) | (data << 8);
this->write_array(reinterpret_cast<const uint8_t *>(&buffer), 2);
} else {
this->write_array(reinterpret_cast<const uint8_t *>(&data), 2);
}
} }
void write_byte16(uint16_t data) { virtual void write_array16(const uint16_t *data, size_t length) {
return this->parent_->template write_byte16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data); for (size_t i = 0; i != length; i++) {
this->write16(data[i]);
}
} }
void write_array16(const uint16_t *data, size_t length) { // write the contents of a buffer, ignore read data (buffer is unchanged.)
this->parent_->template write_array16<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); virtual void write_array(const uint8_t *ptr, size_t length) {
for (size_t i = 0; i != length; i++)
this->transfer(ptr[i]);
} }
void write_array(const uint8_t *data, size_t length) { // read into a buffer, write nulls
this->parent_->template write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); virtual void read_array(uint8_t *ptr, size_t length) {
for (size_t i = 0; i != length; i++)
ptr[i] = this->transfer(0);
} }
// check if device is ready
virtual bool is_ready();
protected:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
uint32_t data_rate_{1000000};
SPIMode mode_{MODE0};
GPIOPin *cs_pin_{NullPin::NULL_PIN};
static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
};
/**
* A dummy SPIDelegate that complains if it's used.
*/
class SPIDelegateDummy : public SPIDelegate {
public:
SPIDelegateDummy() = default;
uint8_t transfer(uint8_t data) override { return 0; }
void begin_transaction() override;
};
/**
* An implementation of SPI that relies only on software toggling of pins.
*
*/
class SPIDelegateBitBash : public SPIDelegate {
public:
SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin,
GPIOPin *sdo_pin, GPIOPin *sdi_pin)
: SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) {
// this calculation is pretty meaningless except at very low bit rates.
this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL;
this->clock_polarity_ = Utility::get_polarity(this->mode_);
this->clock_phase_ = Utility::get_phase(this->mode_);
}
uint8_t transfer(uint8_t data) override;
protected:
GPIOPin *clk_pin_;
GPIOPin *sdo_pin_;
GPIOPin *sdi_pin_;
uint32_t last_transition_{0};
uint32_t wait_cycle_;
SPIClockPolarity clock_polarity_;
SPIClockPhase clock_phase_;
void HOT cycle_clock_() {
while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_)
continue;
this->last_transition_ += this->wait_cycle_;
}
};
class SPIBus {
public:
SPIBus() = default;
SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {}
virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) {
return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
}
virtual bool is_hw() { return false; }
protected:
GPIOPin *clk_pin_{};
GPIOPin *sdo_pin_{};
GPIOPin *sdi_pin_{};
};
class SPIClient;
class SPIComponent : public Component {
public:
SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
GPIOPin *cs_pin);
void unregister_device(SPIClient *device);
void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; }
void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; }
void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; }
void set_interface(SPIInterface interface) {
this->interface_ = interface;
this->using_hw_ = true;
}
void set_interface_name(const char *name) { this->interface_name_ = name; }
float get_setup_priority() const override { return setup_priority::BUS; }
void setup() override;
void dump_config() override;
protected:
GPIOPin *clk_pin_{nullptr};
GPIOPin *sdi_pin_{nullptr};
GPIOPin *sdo_pin_{nullptr};
SPIInterface interface_{};
bool using_hw_{false};
const char *interface_name_{nullptr};
SPIBus *spi_bus_{};
std::map<SPIClient *, SPIDelegate *> devices_;
static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi);
};
/**
* Base class for SPIDevice, un-templated.
*/
class SPIClient {
public:
SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate)
: bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {}
virtual void spi_setup() {
this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_);
}
virtual void spi_teardown() {
this->parent_->unregister_device(this);
this->delegate_ = SPIDelegate::NULL_DELEGATE;
}
bool spi_is_ready() { return this->delegate_->is_ready(); }
protected:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
SPIMode mode_{MODE0};
uint32_t data_rate_{1000000};
SPIComponent *parent_{nullptr};
GPIOPin *cs_{nullptr};
SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
};
/**
* The SPIDevice is what components using the SPI will create.
*
* @tparam BIT_ORDER
* @tparam CLOCK_POLARITY
* @tparam CLOCK_PHASE
* @tparam DATA_RATE
*/
template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE>
class SPIDevice : public SPIClient {
public:
SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {}
SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) {
this->set_spi_parent(parent);
this->set_cs_pin(cs_pin);
}
void spi_setup() override { SPIClient::spi_setup(); }
void spi_teardown() override { SPIClient::spi_teardown(); }
void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; }
void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; }
void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; }
void set_bit_order(SPIBitOrder order) {
this->bit_order_ = order;
esph_log_d("spi.h", "bit order set to %d", order);
}
void set_mode(SPIMode mode) { this->mode_ = mode; }
uint8_t read_byte() { return this->delegate_->transfer(0); }
void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); }
void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); }
void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); }
uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); }
// the driver will byte-swap if required.
void write_byte16(uint16_t data) { this->delegate_->write16(data); }
// avoid use of this if possible. It's inefficient and ugly.
void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); }
void enable() { this->delegate_->begin_transaction(); }
void disable() { this->delegate_->end_transaction(); }
void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); }
template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); } template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); }
void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); } void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); }
uint8_t transfer_byte(uint8_t data) {
return this->parent_->template transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data);
}
void transfer_array(uint8_t *data, size_t length) {
this->parent_->template transfer_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length);
}
template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); } template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); }
protected:
SPIComponent *parent_{nullptr};
GPIOPin *cs_{nullptr};
}; };
} // namespace spi } // namespace spi

View 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

View 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

View 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)

View 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

View 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

View File

@ -0,0 +1,2 @@
CODEOWNERS = ["@clydebarrow"]
DEPENDENCIES = ["spi"]

View 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)

View 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

View File

@ -40,6 +40,9 @@ void WiFiComponent::setup() {
if (this->enable_on_boot_) { if (this->enable_on_boot_) {
this->start(); this->start();
} else { } else {
#ifdef USE_ESP32
esp_netif_init();
#endif
this->state_ = WIFI_COMPONENT_STATE_DISABLED; this->state_ = WIFI_COMPONENT_STATE_DISABLED;
} }
} }

View 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)

View 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))

View 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))

View 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

View 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

View File

@ -298,6 +298,9 @@ CONF_GLYPHS = "glyphs"
CONF_GPIO = "gpio" CONF_GPIO = "gpio"
CONF_GREEN = "green" CONF_GREEN = "green"
CONF_GROUP = "group" CONF_GROUP = "group"
CONF_GYROSCOPE_X = "gyroscope_x"
CONF_GYROSCOPE_Y = "gyroscope_y"
CONF_GYROSCOPE_Z = "gyroscope_z"
CONF_HARDWARE_UART = "hardware_uart" CONF_HARDWARE_UART = "hardware_uart"
CONF_HEAD = "head" CONF_HEAD = "head"
CONF_HEARTBEAT = "heartbeat" CONF_HEARTBEAT = "heartbeat"
@ -538,8 +541,11 @@ CONF_PAYLOAD_AVAILABLE = "payload_available"
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available" CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
CONF_PERIOD = "period" CONF_PERIOD = "period"
CONF_PH = "ph" CONF_PH = "ph"
CONF_PHASE_A = "phase_a"
CONF_PHASE_ANGLE = "phase_angle" CONF_PHASE_ANGLE = "phase_angle"
CONF_PHASE_B = "phase_b"
CONF_PHASE_BALANCER = "phase_balancer" CONF_PHASE_BALANCER = "phase_balancer"
CONF_PHASE_C = "phase_c"
CONF_PIN = "pin" CONF_PIN = "pin"
CONF_PIN_A = "pin_a" CONF_PIN_A = "pin_a"
CONF_PIN_B = "pin_b" CONF_PIN_B = "pin_b"
@ -864,6 +870,9 @@ ICON_FLOWER = "mdi:flower"
ICON_GAS_CYLINDER = "mdi:gas-cylinder" ICON_GAS_CYLINDER = "mdi:gas-cylinder"
ICON_GAUGE = "mdi:gauge" ICON_GAUGE = "mdi:gauge"
ICON_GRAIN = "mdi:grain" ICON_GRAIN = "mdi:grain"
ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise"
ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise"
ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise"
ICON_HEATING_COIL = "mdi:heating-coil" ICON_HEATING_COIL = "mdi:heating-coil"
ICON_KEY_PLUS = "mdi:key-plus" ICON_KEY_PLUS = "mdi:key-plus"
ICON_LIGHTBULB = "mdi:lightbulb" ICON_LIGHTBULB = "mdi:lightbulb"

View File

@ -554,6 +554,12 @@ class EsphomeCore:
def config_dir(self): def config_dir(self):
return os.path.dirname(self.config_path) return os.path.dirname(self.config_path)
@property
def data_dir(self):
if is_ha_addon():
return os.path.join("/data")
return self.relative_config_path(".esphome")
@property @property
def config_filename(self): def config_filename(self):
return os.path.basename(self.config_path) return os.path.basename(self.config_path)
@ -563,7 +569,7 @@ class EsphomeCore:
return os.path.join(self.config_dir, path_) return os.path.join(self.config_dir, path_)
def relative_internal_path(self, *path: str) -> str: def relative_internal_path(self, *path: str) -> str:
return self.relative_config_path(".esphome", *path) return os.path.join(self.data_dir, *path)
def relative_build_path(self, *path): def relative_build_path(self, *path):
path_ = os.path.expanduser(os.path.join(*path)) path_ = os.path.expanduser(os.path.join(*path))
@ -573,13 +579,9 @@ class EsphomeCore:
return self.relative_build_path("src", *path) return self.relative_build_path("src", *path)
def relative_pioenvs_path(self, *path): def relative_pioenvs_path(self, *path):
if is_ha_addon():
return os.path.join("/data", self.name, ".pioenvs", *path)
return self.relative_build_path(".pioenvs", *path) return self.relative_build_path(".pioenvs", *path)
def relative_piolibdeps_path(self, *path): def relative_piolibdeps_path(self, *path):
if is_ha_addon():
return os.path.join("/data", self.name, ".piolibdeps", *path)
return self.relative_build_path(".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path)
@property @property

View File

@ -249,7 +249,11 @@ template<typename... Ts> class RepeatAction : public Action<Ts...> {
void play_complex(Ts... x) override { void play_complex(Ts... x) override {
this->num_running_++; this->num_running_++;
this->var_ = std::make_tuple(x...); this->var_ = std::make_tuple(x...);
this->then_.play(0, x...); if (this->count_.value(x...) > 0) {
this->then_.play(0, x...);
} else {
this->play_next_tuple_(this->var_);
}
} }
void play(Ts... x) override { /* ignore - see play_complex */ void play(Ts... x) override { /* ignore - see play_complex */

View File

@ -198,8 +198,8 @@ def preload_core_config(config, result):
CORE.data[KEY_CORE] = {} CORE.data[KEY_CORE] = {}
if CONF_BUILD_PATH not in conf: if CONF_BUILD_PATH not in conf:
conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" conf[CONF_BUILD_PATH] = f"build/{CORE.name}"
CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) CORE.build_path = CORE.relative_internal_path(conf[CONF_BUILD_PATH])
has_oldstyle = CONF_PLATFORM in conf has_oldstyle = CONF_PLATFORM in conf
newstyle_found = [key for key in TARGET_PLATFORMS if key in config] newstyle_found = [key for key in TARGET_PLATFORMS if key in config]

View File

@ -49,6 +49,11 @@ std::string ESPTime::strftime(const std::string &format) {
struct tm c_tm = this->to_c_tm(); struct tm c_tm = this->to_c_tm();
size_t len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm); size_t len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
while (len == 0) { while (len == 0) {
if (timestr.size() >= 128) {
// strftime has failed for reasons unrelated to the size of the buffer
// so return a formatting error
return "ERROR";
}
timestr.resize(timestr.size() * 2); timestr.resize(timestr.size() * 2);
len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm); len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
} }

View File

@ -45,6 +45,10 @@ struct ESPTime {
* *
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some * @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
* microcontrollers. * microcontrollers.
*
* @warning This method can return "ERROR" when the underlying strftime() call fails, e.g. when the
* format string contains unsupported specifiers or when the format string doesn't produce any
* output.
*/ */
std::string strftime(const std::string &format); std::string strftime(const std::string &format);

View File

@ -663,7 +663,11 @@ async def process_lambda(
:param return_type: The return type of the lambda. :param return_type: The return type of the lambda.
:return: The generated lambda expression. :return: The generated lambda expression.
""" """
from esphome.components.globals import GlobalsComponent, RestoringGlobalsComponent from esphome.components.globals import (
GlobalsComponent,
RestoringGlobalsComponent,
RestoringGlobalStringComponent,
)
if value is None: if value is None:
return return
@ -676,6 +680,7 @@ async def process_lambda(
and ( and (
full_id.type.inherits_from(GlobalsComponent) full_id.type.inherits_from(GlobalsComponent)
or full_id.type.inherits_from(RestoringGlobalsComponent) or full_id.type.inherits_from(RestoringGlobalsComponent)
or full_id.type.inherits_from(RestoringGlobalStringComponent)
) )
): ):
parts[i * 3 + 1] = var.value() parts[i * 3 + 1] = var.value()

View File

@ -32,6 +32,7 @@ import yaml
from tornado.log import access_log from tornado.log import access_log
from esphome import const, platformio_api, util, yaml_util from esphome import const, platformio_api, util, yaml_util
from esphome.core import CORE
from esphome.helpers import get_bool_env, mkdir_p, run_system_command from esphome.helpers import get_bool_env, mkdir_p, run_system_command
from esphome.storage_json import ( from esphome.storage_json import (
EsphomeStorageJSON, EsphomeStorageJSON,
@ -70,6 +71,7 @@ class DashboardSettings:
self.password_hash = password_hash(password) self.password_hash = password_hash(password)
self.config_dir = args.configuration self.config_dir = args.configuration
self.absolute_config_dir = Path(self.config_dir).resolve() self.absolute_config_dir = Path(self.config_dir).resolve()
CORE.config_path = os.path.join(self.config_dir, ".")
@property @property
def relative_url(self): def relative_url(self):
@ -534,13 +536,16 @@ class DownloadListRequestHandler(BaseHandler):
@authenticated @authenticated
@bind_config @bind_config
def get(self, configuration=None): def get(self, configuration=None):
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is None: if storage_json is None:
self.send_error(404) self.send_error(404)
return return
from esphome.components.esp32 import get_download_types as esp32_types from esphome.components.esp32 import (
get_download_types as esp32_types,
VARIANTS as ESP32_VARIANTS,
)
from esphome.components.esp8266 import get_download_types as esp8266_types from esphome.components.esp8266 import get_download_types as esp8266_types
from esphome.components.rp2040 import get_download_types as rp2040_types from esphome.components.rp2040 import get_download_types as rp2040_types
from esphome.components.libretiny import get_download_types as libretiny_types from esphome.components.libretiny import get_download_types as libretiny_types
@ -551,7 +556,7 @@ class DownloadListRequestHandler(BaseHandler):
downloads = rp2040_types(storage_json) downloads = rp2040_types(storage_json)
elif platform == const.PLATFORM_ESP8266: elif platform == const.PLATFORM_ESP8266:
downloads = esp8266_types(storage_json) downloads = esp8266_types(storage_json)
elif platform == const.PLATFORM_ESP32: elif platform.upper() in ESP32_VARIANTS:
downloads = esp32_types(storage_json) downloads = esp32_types(storage_json)
elif platform == const.PLATFORM_BK72XX: elif platform == const.PLATFORM_BK72XX:
downloads = libretiny_types(storage_json) downloads = libretiny_types(storage_json)
@ -574,7 +579,7 @@ class DownloadBinaryRequestHandler(BaseHandler):
def get(self, configuration=None): def get(self, configuration=None):
compressed = self.get_argument("compressed", "0") == "1" compressed = self.get_argument("compressed", "0") == "1"
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
storage_json = StorageJSON.load(storage_path) storage_json = StorageJSON.load(storage_path)
if storage_json is None: if storage_json is None:
self.send_error(404) self.send_error(404)
@ -663,9 +668,7 @@ class DashboardEntry:
@property @property
def storage(self) -> Optional[StorageJSON]: def storage(self) -> Optional[StorageJSON]:
if not self._loaded_storage: if not self._loaded_storage:
self._storage = StorageJSON.load( self._storage = StorageJSON.load(ext_storage_path(self.filename))
ext_storage_path(settings.config_dir, self.filename)
)
self._loaded_storage = True self._loaded_storage = True
return self._storage return self._storage
@ -1041,9 +1044,9 @@ class DeleteRequestHandler(BaseHandler):
@bind_config @bind_config
def post(self, configuration=None): def post(self, configuration=None):
config_file = settings.rel_path(configuration) config_file = settings.rel_path(configuration)
storage_path = ext_storage_path(settings.config_dir, configuration) storage_path = ext_storage_path(configuration)
trash_path = trash_storage_path(settings.config_dir) trash_path = trash_storage_path()
mkdir_p(trash_path) mkdir_p(trash_path)
shutil.move(config_file, os.path.join(trash_path, configuration)) shutil.move(config_file, os.path.join(trash_path, configuration))
@ -1064,7 +1067,7 @@ class UndoDeleteRequestHandler(BaseHandler):
@bind_config @bind_config
def post(self, configuration=None): def post(self, configuration=None):
config_file = settings.rel_path(configuration) config_file = settings.rel_path(configuration)
trash_path = trash_storage_path(settings.config_dir) trash_path = trash_storage_path()
shutil.move(os.path.join(trash_path, configuration), config_file) shutil.move(os.path.join(trash_path, configuration), config_file)
@ -1322,10 +1325,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
def start_web_server(args): def start_web_server(args):
settings.parse_args(args) settings.parse_args(args)
mkdir_p(settings.rel_path(".esphome"))
if settings.using_auth: if settings.using_auth:
path = esphome_storage_path(settings.config_dir) path = esphome_storage_path()
storage = EsphomeStorageJSON.load(path) storage = EsphomeStorageJSON.load(path)
if storage is None: if storage is None:
storage = EsphomeStorageJSON.get_default() storage = EsphomeStorageJSON.get_default()

View File

@ -35,7 +35,7 @@ def run_git_command(cmd, cwd=None) -> str:
def _compute_destination_path(key: str, domain: str) -> Path: def _compute_destination_path(key: str, domain: str) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / domain base_dir = Path(CORE.data_dir) / domain
h = hashlib.new("sha256") h = hashlib.new("sha256")
h.update(key.encode()) h.update(key.encode())
return base_dir / h.hexdigest()[:8] return base_dir / h.hexdigest()[:8]

View File

@ -22,19 +22,19 @@ _LOGGER = logging.getLogger(__name__)
def storage_path() -> str: def storage_path() -> str:
return CORE.relative_internal_path(f"{CORE.config_filename}.json") return os.path.join(CORE.data_dir, "storage", f"{CORE.config_filename}.json")
def ext_storage_path(base_path: str, config_filename: str) -> str: def ext_storage_path(config_filename: str) -> str:
return os.path.join(base_path, ".esphome", f"{config_filename}.json") return os.path.join(CORE.data_dir, "storage", f"{config_filename}.json")
def esphome_storage_path(base_path: str) -> str: def esphome_storage_path() -> str:
return os.path.join(base_path, ".esphome", "esphome.json") return os.path.join(CORE.data_dir, "esphome.json")
def trash_storage_path(base_path: str) -> str: def trash_storage_path() -> str:
return os.path.join(base_path, ".esphome", "trash") return CORE.relative_config_path("trash")
class StorageJSON: class StorageJSON:

View File

@ -6,12 +6,12 @@ import unicodedata
import voluptuous as vol import voluptuous as vol
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
from esphome.core import CORE
from esphome.helpers import get_bool_env, write_file from esphome.helpers import get_bool_env, write_file
from esphome.log import color, Fore from esphome.log import Fore, color
from esphome.storage_json import StorageJSON, ext_storage_path from esphome.storage_json import StorageJSON, ext_storage_path
from esphome.util import safe_print from esphome.util import safe_print
from esphome.const import ALLOWED_NAME_CHARS, ENV_QUICKWIZARD
CORE_BIG = r""" _____ ____ _____ ______ CORE_BIG = r""" _____ ____ _____ ______
/ ____/ __ \| __ \| ____| / ____/ __ \| __ \| ____|
@ -193,10 +193,10 @@ captive_portal:
def wizard_write(path, **kwargs): def wizard_write(path, **kwargs):
from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.esp32 import boards as esp32_boards
from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
name = kwargs["name"] name = kwargs["name"]
@ -225,7 +225,7 @@ def wizard_write(path, **kwargs):
write_file(path, wizard_file(**kwargs)) write_file(path, wizard_file(**kwargs))
storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware) storage = StorageJSON.from_wizard(name, name, f"{name}.local", hardware)
storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage_path = ext_storage_path(os.path.basename(path))
storage.save(storage_path) storage.save(storage_path)
return True return True
@ -265,9 +265,9 @@ def strip_accents(value):
def wizard(path): def wizard(path):
from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
if not path.endswith(".yaml") and not path.endswith(".yml"): if not path.endswith(".yaml") and not path.endswith(".yml"):
@ -280,6 +280,9 @@ def wizard(path):
f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file." f"Uh oh, it seems like {color(Fore.CYAN, path)} already exists, please delete that file first or chose another configuration file."
) )
return 2 return 2
CORE.config_path = path
safe_print("Hi there!") safe_print("Hi there!")
sleep(1.5) sleep(1.5)
safe_print("I'm the wizard of ESPHome :)") safe_print("I'm the wizard of ESPHome :)")

View File

@ -123,6 +123,7 @@ lib_deps =
DNSServer ; captive_portal (Arduino built-in) DNSServer ; captive_portal (Arduino built-in)
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
droscy/esp_wireguard@0.3.2 ; wireguard
build_flags = build_flags =
${common:arduino.build_flags} ${common:arduino.build_flags}
-DUSE_ESP32 -DUSE_ESP32
@ -141,6 +142,7 @@ framework = espidf
lib_deps = lib_deps =
${common:idf.lib_deps} ${common:idf.lib_deps}
espressif/esp32-camera@1.0.0 ; esp32_camera espressif/esp32-camera@1.0.0 ; esp32_camera
droscy/esp_wireguard@0.3.2 ; wireguard
build_flags = build_flags =
${common:idf.build_flags} ${common:idf.build_flags}
-Wno-nonnull-compare -Wno-nonnull-compare

View File

@ -11,7 +11,7 @@ esptool==4.6.2
click==8.1.7 click==8.1.7
esphome-dashboard==20230904.0 esphome-dashboard==20230904.0
aioesphomeapi==15.0.0 aioesphomeapi==15.0.0
zeroconf==0.88.0 zeroconf==0.102.0
# esp-idf requires this, but doesn't bundle it by default # esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

View File

@ -5,7 +5,7 @@ pyupgrade==3.10.1 # also change in .pre-commit-config.yaml when updating
pre-commit pre-commit
# Unit tests # Unit tests
pytest==7.4.1 pytest==7.4.2
pytest-cov==4.1.0 pytest-cov==4.1.0
pytest-mock==3.11.1 pytest-mock==3.11.1
pytest-asyncio==0.21.1 pytest-asyncio==0.21.1

View File

@ -27,3 +27,4 @@ Current test_.yaml file contents.
| test6.yaml | RP2040 | wifi | N/A | test6.yaml | RP2040 | wifi | N/A
| test7.yaml | ESP32-C3 | wifi | N/A | test7.yaml | ESP32-C3 | wifi | N/A
| test8.yaml | ESP32-S3 | wifi | None | test8.yaml | ESP32-S3 | wifi | None
| test10.yaml | ESP32 | wifi | None

View File

@ -915,6 +915,23 @@ sensor:
temperature: temperature:
name: MPU6886 Temperature name: MPU6886 Temperature
i2c_id: i2c_bus i2c_id: i2c_bus
- platform: bmi160
address: 0x68
acceleration_x:
name: BMI160 Accel X
acceleration_y:
name: BMI160 Accel Y
acceleration_z:
name: BMI160 Accel z
gyroscope_x:
name: BMI160 Gyro X
gyroscope_y:
name: BMI160 Gyro Y
gyroscope_z:
name: BMI160 Gyro z
temperature:
name: BMI160 Temperature
i2c_id: i2c_bus
- platform: mmc5603 - platform: mmc5603
address: 0x30 address: 0x30
field_strength_x: field_strength_x:

48
tests/test10.yaml Normal file
View 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'

View File

@ -5,6 +5,13 @@ esphome:
board: nodemcu-32s board: nodemcu-32s
build_path: build/test2 build_path: build/test2
globals:
- id: my_global_string
type: std::string
restore_value: yes
max_restore_data_length: 70
initial_value: '"DefaultValue"'
substitutions: substitutions:
devicename: test2 devicename: test2

View File

@ -32,6 +32,7 @@ spi:
clk_pin: GPIO21 clk_pin: GPIO21
mosi_pin: GPIO22 mosi_pin: GPIO22
miso_pin: GPIO23 miso_pin: GPIO23
interface: hardware
uart: uart:
- id: uart115200 - id: uart115200

View File

@ -563,6 +563,13 @@ script:
then: then:
- logger.log: looping! - logger.log: looping!
- id: zero_repeat_test
then:
- repeat:
count: !lambda "return 0;"
then:
- logger.log: shouldn't see mee!
switch: switch:
- platform: modbus_controller - platform: modbus_controller
modbus_controller_id: modbus_controller_test modbus_controller_id: modbus_controller_test

View File

@ -62,3 +62,6 @@ switch:
sensor: sensor:
- platform: internal_temperature - platform: internal_temperature
name: Internal Temperature name: Internal Temperature
- platform: adc
pin: VCC
name: VSYS

View File

@ -29,10 +29,25 @@ light:
name: neopixel-enable name: neopixel-enable
internal: false internal: false
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
- platform: spi_led_strip
num_leds: 4
color_correct: [80%, 60%, 100%]
id: rgb_led
name: "RGB LED"
data_rate: 8MHz
spi: spi:
id: spi_id_1
clk_pin: GPIO7 clk_pin: GPIO7
mosi_pin: GPIO6 mosi_pin: GPIO6
interface: any
spi_device:
id: spidev
data_rate: 2MHz
spi_id: spi_id_1
mode: 3
bit_order: lsb_first
display: display:
- platform: ili9xxx - platform: ili9xxx

View File

@ -1,7 +1,9 @@
"""Tests for the wizard.py file.""" """Tests for the wizard.py file."""
import os
import esphome.wizard as wz import esphome.wizard as wz
import pytest import pytest
from esphome.core import CORE
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
@ -110,6 +112,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch):
# Given # Given
del default_config["platform"] del default_config["platform"]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -130,6 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266(
default_config["board"] = [*ESP8266_BOARD_PINS][0] default_config["board"] = [*ESP8266_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -150,6 +154,7 @@ def test_wizard_write_defaults_platform_from_board_esp32(
default_config["board"] = [*ESP32_BOARD_PINS][0] default_config["board"] = [*ESP32_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -170,6 +175,7 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
default_config["board"] = [*BK72XX_BOARD_PINS][0] default_config["board"] = [*BK72XX_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)
@ -190,6 +196,7 @@ def test_wizard_write_defaults_platform_from_board_rtl87xx(
default_config["board"] = [*RTL87XX_BOARD_PINS][0] default_config["board"] = [*RTL87XX_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock()) monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When # When
wz.wizard_write(tmp_path, **default_config) wz.wizard_write(tmp_path, **default_config)