From 11b829dda17b8cca2174272069caf36d0dca2628 Mon Sep 17 00:00:00 2001 From: Daniel Kent <129895318+danielkent-net@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:59:17 -0400 Subject: [PATCH] [spa06_spi] Add SPA06-003 Temperature and Pressure Sensor - SPI support (Part 3 of 3) (#14523) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/spa06_spi/__init__.py | 0 esphome/components/spa06_spi/sensor.py | 41 +++++++++++ esphome/components/spa06_spi/spa06_spi.cpp | 72 +++++++++++++++++++ esphome/components/spa06_spi/spa06_spi.h | 22 ++++++ tests/components/spa06_spi/common.yaml | 15 ++++ .../components/spa06_spi/test.esp32-idf.yaml | 7 ++ .../spa06_spi/test.esp8266-ard.yaml | 7 ++ .../components/spa06_spi/test.rp2040-ard.yaml | 7 ++ 9 files changed, 172 insertions(+) create mode 100644 esphome/components/spa06_spi/__init__.py create mode 100644 esphome/components/spa06_spi/sensor.py create mode 100644 esphome/components/spa06_spi/spa06_spi.cpp create mode 100644 esphome/components/spa06_spi/spa06_spi.h create mode 100644 tests/components/spa06_spi/common.yaml create mode 100644 tests/components/spa06_spi/test.esp32-idf.yaml create mode 100644 tests/components/spa06_spi/test.esp8266-ard.yaml create mode 100644 tests/components/spa06_spi/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index e3e09cbc11..afe4cdb871 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -459,6 +459,7 @@ esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/sound_level/* @kahrendt esphome/components/spa06_base/* @danielkent-net esphome/components/spa06_i2c/* @danielkent-net +esphome/components/spa06_spi/* @danielkent-net esphome/components/speaker/* @jesserockz @kahrendt esphome/components/speaker/media_player/* @kahrendt @synesthesiam esphome/components/speaker_source/* @kahrendt diff --git a/esphome/components/spa06_spi/__init__.py b/esphome/components/spa06_spi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/spa06_spi/sensor.py b/esphome/components/spa06_spi/sensor.py new file mode 100644 index 0000000000..b82186ec21 --- /dev/null +++ b/esphome/components/spa06_spi/sensor.py @@ -0,0 +1,41 @@ +import logging + +import esphome.codegen as cg +from esphome.components import spi +from esphome.components.spi import CONF_SPI_MODE +import esphome.config_validation as cv + +from ..spa06_base import CONFIG_SCHEMA_BASE, to_code_base + +AUTO_LOAD = ["spa06_base"] +CODEOWNERS = ["@danielkent-net"] +DEPENDENCIES = ["spi"] + +spa06_ns = cg.esphome_ns.namespace("spa06_spi") +SPA06SPIComponent = spa06_ns.class_( + "SPA06SPIComponent", cg.PollingComponent, spi.SPIDevice +) + +_LOGGER = logging.getLogger(__name__) + +VALID_SPI_MODES = {3: "MODE3", "3": "MODE3", "MODE3": "MODE3"} + + +def check_spi_mode(config): + spi_mode = config.get(CONF_SPI_MODE) + if spi_mode not in VALID_SPI_MODES: + raise cv.Invalid("SPA06 only supports SPI mode 3") + return config + + +CONFIG_SCHEMA = cv.All( + CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend( + {cv.GenerateID(): cv.declare_id(SPA06SPIComponent)} + ), + check_spi_mode, +) + + +async def to_code(config): + var = await to_code_base(config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/spa06_spi/spa06_spi.cpp b/esphome/components/spa06_spi/spa06_spi.cpp new file mode 100644 index 0000000000..9f3683d219 --- /dev/null +++ b/esphome/components/spa06_spi/spa06_spi.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include "spa06_spi.h" +#include "esphome/components/spa06_base/spa06_base.h" +#include "esphome/components/spi/spi.h" + +// OR (|) register with SPA06_SPI_READ for read. +inline constexpr uint8_t SPA06_SPI_READ = 0x80; + +// AND (&) register with SPA06_SPI_WRITE for write. +inline constexpr uint8_t SPA06_SPI_WRITE = 0x7F; + +namespace esphome::spa06_spi { + +static const char *const TAG = "spa06_spi"; + +void SPA06SPIComponent::dump_config() { + SPA06Component::dump_config(); + LOG_SPI_DEVICE(this) +} + +void SPA06SPIComponent::setup() { + this->spi_setup(); + SPA06Component::setup(); +} + +void SPA06SPIComponent::protocol_reset() { + // Forces the device into SPI mode using a dummy read + uint8_t dummy_read = 0; + this->spa_read_byte(spa06_base::SPA06_ID, &dummy_read); +} + +// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address +// is not used and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read). +// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, +// the byte 0x77 is transferred, for read access, the byte 0xF7 is transferred. +// The expressions SPA06_SPI_READ (| with register) and SPA06_SPI_WRITE (& with register) +// are defined for readability. + +bool SPA06SPIComponent::spa_read_byte(uint8_t a_register, uint8_t *data) { + this->enable(); + this->transfer_byte(a_register | SPA06_SPI_READ); + *data = this->transfer_byte(0); + this->disable(); + return true; +} + +bool SPA06SPIComponent::spa_write_byte(uint8_t a_register, uint8_t data) { + this->enable(); + this->transfer_byte(a_register & SPA06_SPI_WRITE); + this->transfer_byte(data); + this->disable(); + return true; +} + +bool SPA06SPIComponent::spa_read_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->transfer_byte(a_register | SPA06_SPI_READ); + this->read_array(data, len); + this->disable(); + return true; +} + +bool SPA06SPIComponent::spa_write_bytes(uint8_t a_register, uint8_t *data, size_t len) { + this->enable(); + this->transfer_byte(a_register & SPA06_SPI_WRITE); + this->write_array(data, len); + this->disable(); + return true; +} +} // namespace esphome::spa06_spi diff --git a/esphome/components/spa06_spi/spa06_spi.h b/esphome/components/spa06_spi/spa06_spi.h new file mode 100644 index 0000000000..ffbc162d6f --- /dev/null +++ b/esphome/components/spa06_spi/spa06_spi.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/components/spa06_base/spa06_base.h" +#include "esphome/components/spi/spi.h" + +namespace esphome::spa06_spi { + +class SPA06SPIComponent : public spa06_base::SPA06Component, + public spi::SPIDevice { + void setup() override; + bool spa_read_byte(uint8_t a_register, uint8_t *data) override; + bool spa_write_byte(uint8_t a_register, uint8_t data) override; + bool spa_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + bool spa_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override; + void dump_config() override; + + protected: + void protocol_reset() override; +}; + +} // namespace esphome::spa06_spi diff --git a/tests/components/spa06_spi/common.yaml b/tests/components/spa06_spi/common.yaml new file mode 100644 index 0000000000..9263202ab4 --- /dev/null +++ b/tests/components/spa06_spi/common.yaml @@ -0,0 +1,15 @@ +sensor: + - platform: spa06_spi + spi_id: spi_bus + cs_pin: ${cs_pin} + temperature: + id: spa06_spi_temperature + name: Outside Temperature + sample_rate: 1 + oversampling: NONE + pressure: + name: Outside Pressure + id: spa06_spi_pressure + sample_rate: 25p4 + oversampling: 16X + update_interval: 15s diff --git a/tests/components/spa06_spi/test.esp32-idf.yaml b/tests/components/spa06_spi/test.esp32-idf.yaml new file mode 100644 index 0000000000..a3352cf880 --- /dev/null +++ b/tests/components/spa06_spi/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + cs_pin: GPIO5 + +packages: + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/spa06_spi/test.esp8266-ard.yaml b/tests/components/spa06_spi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..595f31046a --- /dev/null +++ b/tests/components/spa06_spi/test.esp8266-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + cs_pin: GPIO15 + +packages: + spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/spa06_spi/test.rp2040-ard.yaml b/tests/components/spa06_spi/test.rp2040-ard.yaml new file mode 100644 index 0000000000..93e19cfea4 --- /dev/null +++ b/tests/components/spa06_spi/test.rp2040-ard.yaml @@ -0,0 +1,7 @@ +substitutions: + cs_pin: GPIO17 + +packages: + spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml + +<<: !include common.yaml