247 lines
8.0 KiB
YAML
247 lines
8.0 KiB
YAML
|
|
# ============================================================
|
||
|
|
# Attic Climate Sensor — ESP32-C3 SuperMini (Battery-Powered)
|
||
|
|
# Board: ESP32-C3 SuperMini (RISC-V, single-core, 160MHz)
|
||
|
|
# Sensor: AHT20 + BMP280 combo (I2C)
|
||
|
|
# Battery: 18650 Li-Ion
|
||
|
|
# Schedule: Wake every 6 hours, report to HA, deep sleep
|
||
|
|
# OTA: Hold GPIO3 LOW (jumper to GND) to prevent deep sleep
|
||
|
|
# ============================================================
|
||
|
|
#
|
||
|
|
# WIRING:
|
||
|
|
# AHT20/BMP280 Combo Board:
|
||
|
|
# SDA → GPIO4
|
||
|
|
# SCL → GPIO5
|
||
|
|
# VCC → 3.3V
|
||
|
|
# GND → GND
|
||
|
|
#
|
||
|
|
# Battery Voltage Monitor (voltage divider):
|
||
|
|
# BAT+ → 100kΩ → GPIO0 (ADC) → 100kΩ → GND
|
||
|
|
#
|
||
|
|
# OTA Stay-Awake Jumper:
|
||
|
|
# GPIO3 → 2-pin header → GND (bridge to stay awake)
|
||
|
|
#
|
||
|
|
# NOTES:
|
||
|
|
# - GPIO8 = onboard blue LED (active LOW / inverted)
|
||
|
|
# - GPIO9 = BOOT button (avoid for general use)
|
||
|
|
# - GPIO2, GPIO10 = strapping pins (avoid)
|
||
|
|
# - All GPIO are 3.3V logic only
|
||
|
|
# ============================================================
|
||
|
|
|
||
|
|
substitutions:
|
||
|
|
device_name: "attic-climate-sensor"
|
||
|
|
friendly_name: "Attic Climate Sensor"
|
||
|
|
# Deep sleep duration: 6 hours = 21600 seconds
|
||
|
|
sleep_duration: "21600s"
|
||
|
|
# Max awake time before forced sleep (safety net)
|
||
|
|
run_duration: "60s"
|
||
|
|
|
||
|
|
esphome:
|
||
|
|
name: ${device_name}
|
||
|
|
friendly_name: ${friendly_name}
|
||
|
|
on_boot:
|
||
|
|
priority: -10
|
||
|
|
then:
|
||
|
|
- wait_until:
|
||
|
|
condition:
|
||
|
|
api.connected:
|
||
|
|
timeout: 45s
|
||
|
|
# Give sensors time to stabilize and publish
|
||
|
|
- delay: 5s
|
||
|
|
- if:
|
||
|
|
condition:
|
||
|
|
binary_sensor.is_on: ota_stay_awake
|
||
|
|
then:
|
||
|
|
- logger.log: "OTA jumper detected — staying awake for maintenance"
|
||
|
|
- deep_sleep.prevent: deep_sleep_control
|
||
|
|
- light.turn_on: onboard_led
|
||
|
|
else:
|
||
|
|
- logger.log: "Publishing complete — entering deep sleep"
|
||
|
|
- deep_sleep.enter: deep_sleep_control
|
||
|
|
|
||
|
|
esp32:
|
||
|
|
board: esp32-c3-devkitm-1
|
||
|
|
variant: esp32c3
|
||
|
|
framework:
|
||
|
|
type: arduino
|
||
|
|
|
||
|
|
# ── Logging ────────────────────────────────────────────────
|
||
|
|
logger:
|
||
|
|
level: DEBUG
|
||
|
|
# The C3 SuperMini uses native USB — hardware UART is on GPIO20/21
|
||
|
|
# For production, reduce to save power:
|
||
|
|
# level: WARN
|
||
|
|
|
||
|
|
# ── Network ────────────────────────────────────────────────
|
||
|
|
wifi:
|
||
|
|
ssid: !secret wifi_iot_ssid
|
||
|
|
password: !secret wifi_password
|
||
|
|
# Static IP dramatically reduces WiFi connect time (~3-5s saved per wake)
|
||
|
|
manual_ip:
|
||
|
|
static_ip: !secret attic_sensor_ip
|
||
|
|
gateway: !secret gateway
|
||
|
|
subnet: !secret subnet
|
||
|
|
dns1: !secret dns1
|
||
|
|
fast_connect: true
|
||
|
|
power_save_mode: none
|
||
|
|
|
||
|
|
ap:
|
||
|
|
ssid: "${device_name}-fallback"
|
||
|
|
password: !secret wifi_password
|
||
|
|
|
||
|
|
captive_portal:
|
||
|
|
|
||
|
|
# ── API & OTA ──────────────────────────────────────────────
|
||
|
|
api:
|
||
|
|
encryption:
|
||
|
|
key: !secret api_encryption_key
|
||
|
|
|
||
|
|
ota:
|
||
|
|
- platform: esphome
|
||
|
|
password: !secret ota_password
|
||
|
|
|
||
|
|
# ── I2C Bus ────────────────────────────────────────────────
|
||
|
|
# AHT20: 0x38 | BMP280: 0x77 (or 0x76 depending on board)
|
||
|
|
# Using GPIO4 (SDA) and GPIO5 (SCL) — safe general-purpose pins
|
||
|
|
i2c:
|
||
|
|
sda: GPIO4
|
||
|
|
scl: GPIO5
|
||
|
|
scan: true
|
||
|
|
|
||
|
|
# ── Deep Sleep ─────────────────────────────────────────────
|
||
|
|
deep_sleep:
|
||
|
|
id: deep_sleep_control
|
||
|
|
run_duration: ${run_duration}
|
||
|
|
sleep_duration: ${sleep_duration}
|
||
|
|
|
||
|
|
# ── Status LED ─────────────────────────────────────────────
|
||
|
|
# Onboard blue LED on GPIO8 (active LOW / inverted)
|
||
|
|
output:
|
||
|
|
- platform: gpio
|
||
|
|
pin:
|
||
|
|
number: GPIO8
|
||
|
|
inverted: true
|
||
|
|
id: blue_led_output
|
||
|
|
|
||
|
|
light:
|
||
|
|
- platform: binary
|
||
|
|
name: "${friendly_name} Status LED"
|
||
|
|
output: blue_led_output
|
||
|
|
id: onboard_led
|
||
|
|
entity_category: diagnostic
|
||
|
|
|
||
|
|
# ── Binary Sensors ─────────────────────────────────────────
|
||
|
|
binary_sensor:
|
||
|
|
# OTA Stay-Awake Jumper
|
||
|
|
# Wire a 2-pin header from GPIO3 → GND
|
||
|
|
# Bridge jumper = stay awake for OTA | Remove = normal deep sleep
|
||
|
|
- platform: gpio
|
||
|
|
pin:
|
||
|
|
number: GPIO3
|
||
|
|
mode:
|
||
|
|
input: true
|
||
|
|
pullup: true
|
||
|
|
inverted: true # LOW (jumper bridged) = ON = stay awake
|
||
|
|
name: "${friendly_name} OTA Stay Awake"
|
||
|
|
id: ota_stay_awake
|
||
|
|
entity_category: diagnostic
|
||
|
|
on_press:
|
||
|
|
- logger.log: "OTA jumper enabled — preventing deep sleep"
|
||
|
|
- deep_sleep.prevent: deep_sleep_control
|
||
|
|
- light.turn_on: onboard_led
|
||
|
|
on_release:
|
||
|
|
- logger.log: "OTA jumper removed — allowing deep sleep"
|
||
|
|
- deep_sleep.allow: deep_sleep_control
|
||
|
|
- light.turn_off: onboard_led
|
||
|
|
|
||
|
|
# ── Sensors ────────────────────────────────────────────────
|
||
|
|
sensor:
|
||
|
|
# ── AHT20: Temperature & Humidity ──
|
||
|
|
- platform: aht10
|
||
|
|
variant: AHT20
|
||
|
|
temperature:
|
||
|
|
name: "${friendly_name} Temperature"
|
||
|
|
unit_of_measurement: "°F"
|
||
|
|
accuracy_decimals: 1
|
||
|
|
filters:
|
||
|
|
- lambda: return x * 9.0f / 5.0f + 32.0f;
|
||
|
|
- sliding_window_moving_average:
|
||
|
|
window_size: 3
|
||
|
|
send_every: 1
|
||
|
|
humidity:
|
||
|
|
name: "${friendly_name} Humidity"
|
||
|
|
accuracy_decimals: 1
|
||
|
|
filters:
|
||
|
|
- sliding_window_moving_average:
|
||
|
|
window_size: 3
|
||
|
|
send_every: 1
|
||
|
|
update_interval: 5s
|
||
|
|
|
||
|
|
# ── BMP280: Barometric Pressure (+ backup temp) ──
|
||
|
|
- platform: bmp280_i2c
|
||
|
|
address: 0x77 # Change to 0x76 if your board uses that
|
||
|
|
temperature:
|
||
|
|
name: "${friendly_name} BMP280 Temperature"
|
||
|
|
unit_of_measurement: "°F"
|
||
|
|
accuracy_decimals: 1
|
||
|
|
filters:
|
||
|
|
- lambda: return x * 9.0f / 5.0f + 32.0f;
|
||
|
|
disabled_by_default: true
|
||
|
|
pressure:
|
||
|
|
name: "${friendly_name} Barometric Pressure"
|
||
|
|
accuracy_decimals: 1
|
||
|
|
unit_of_measurement: "inHg"
|
||
|
|
filters:
|
||
|
|
- multiply: 0.0295299831
|
||
|
|
update_interval: 5s
|
||
|
|
|
||
|
|
# ── Battery Voltage via ADC ──
|
||
|
|
# Voltage divider: 100kΩ + 100kΩ from BAT+ to GND, midpoint → GPIO0
|
||
|
|
# GPIO0 is ADC1_CH0 on the C3 — safe for ADC use
|
||
|
|
# 2:1 divider ratio — multiply by 2 to get actual battery voltage
|
||
|
|
- platform: adc
|
||
|
|
pin: GPIO0
|
||
|
|
name: "${friendly_name} Battery Voltage"
|
||
|
|
id: battery_voltage
|
||
|
|
accuracy_decimals: 2
|
||
|
|
update_interval: 5s
|
||
|
|
attenuation: 11db
|
||
|
|
filters:
|
||
|
|
- multiply: 2.0
|
||
|
|
|
||
|
|
# ── Battery Percentage (estimated) ──
|
||
|
|
# 18650 range: ~3.0V (empty) to ~4.2V (full)
|
||
|
|
- platform: template
|
||
|
|
name: "${friendly_name} Battery Percentage"
|
||
|
|
unit_of_measurement: "%"
|
||
|
|
device_class: battery
|
||
|
|
accuracy_decimals: 0
|
||
|
|
update_interval: 5s
|
||
|
|
lambda: |-
|
||
|
|
float voltage = id(battery_voltage).state;
|
||
|
|
if (voltage >= 4.2) return 100.0;
|
||
|
|
if (voltage <= 3.0) return 0.0;
|
||
|
|
return (voltage - 3.0) / (4.2 - 3.0) * 100.0;
|
||
|
|
|
||
|
|
# ── WiFi Signal ──
|
||
|
|
- platform: wifi_signal
|
||
|
|
name: "${friendly_name} WiFi Signal"
|
||
|
|
update_interval: 10s
|
||
|
|
entity_category: diagnostic
|
||
|
|
|
||
|
|
# ── Text Sensors ───────────────────────────────────────────
|
||
|
|
text_sensor:
|
||
|
|
- platform: wifi_info
|
||
|
|
ip_address:
|
||
|
|
name: "${friendly_name} IP Address"
|
||
|
|
entity_category: diagnostic
|
||
|
|
|
||
|
|
# ── Switches ───────────────────────────────────────────────
|
||
|
|
switch:
|
||
|
|
# Manual deep sleep trigger from HA (for testing)
|
||
|
|
- platform: template
|
||
|
|
name: "${friendly_name} Force Sleep"
|
||
|
|
icon: "mdi:sleep"
|
||
|
|
entity_category: config
|
||
|
|
turn_on_action:
|
||
|
|
- deep_sleep.enter: deep_sleep_control
|