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