# ESPHome Integration for EverBlu Cyble Enhanced Meters
Native ESPHome external component for reading EverBlu Cyble Enhanced water and gas meters (RADIAN protocol, 433 MHz) with an ESP8266/ESP32 + CC1101. It integrates directly with Home Assistant via the ESPHome API (no MQTT broker required) and shares the same core radio/protocol code as the standalone MQTT firmware.
Table of Contents
**Table of Contents**
- [Documentation](#documentation)
- [For End Users](#for-end-users)
- [For Developers](#for-developers)
- [Quick Start](#quick-start)
- [Breaking Change: SPI Configuration Required](#breaking-change-spi-configuration-required)
- [1. Use as External Component](#1-use-as-external-component)
- [2. Build and Upload](#2-build-and-upload)
- [ESPHome Device Builder (Visual Dashboard)](#esphome-device-builder-visual-dashboard)
- [Board-Specific Configuration](#board-specific-configuration)
- [Arduino Nano ESP32 (ESP32S3)](#arduino-nano-esp32-esp32s3)
- [Other ESP32 Boards](#other-esp32-boards)
- [Configuration Reference](#configuration-reference)
- [Key Parameters](#key-parameters)
- [Schedule Options](#schedule-options)
- [Custom Schedule Example](#custom-schedule-example)
- [Logging](#logging)
- [Timezone Adjustment](#timezone-adjustment)
- [Features](#features)
- [Example Configurations](#example-configurations)
- [Hardware Requirements](#hardware-requirements)
- [Software Requirements](#software-requirements)
- [Wiring (ESP8266 D1 Mini)](#wiring-esp8266-d1-mini)
- [Wiring (ESP32)](#wiring-esp32)
- [Benefits](#benefits)
- [vs. Standalone MQTT Mode](#vs-standalone-mqtt-mode)
- [ESPHome Mode Advantages](#esphome-mode-advantages)
- [Home Assistant Best Practice: Utility Meter Helper](#home-assistant-best-practice-utility-meter-helper)
- [Migrating Sensor History Between Platforms](#migrating-sensor-history-between-platforms)
- [Historical Data from Meter](#historical-data-from-meter)
- [Architecture](#architecture)
- [Available Sensors](#available-sensors)
- [Numeric Sensors](#numeric-sensors)
- [Text Sensors](#text-sensors)
- [Binary Sensors](#binary-sensors)
- [Control Buttons](#control-buttons)
- [Frequency scans in multi-meter setups](#frequency-scans-in-multi-meter-setups)
- [Common Configuration Patterns](#common-configuration-patterns)
- [Water Meter - Basic](#water-meter---basic)
- [Gas Meter - Basic](#gas-meter---basic)
- [With Full Monitoring](#with-full-monitoring)
- [Troubleshooting](#troubleshooting)
- [Corrupted or Invalid Volume Readings](#corrupted-or-invalid-volume-readings)
- [No Meter Response](#no-meter-response)
- [Signal Quality Issues](#signal-quality-issues)
- [Quick Troubleshooting](#quick-troubleshooting)
- [Quick Fixes](#quick-fixes)
- [License](#license)
- [Credits](#credits)
- [Links](#links)
- [Development: Code Style & Formatting](#development-code-style--formatting)
- [Running the formatter](#running-the-formatter)
- [Linting & pre-commit](#linting--pre-commit)
## Documentation
### For End Users
- **[ESPHome Integration Guide](docs/ESPHOME_INTEGRATION_GUIDE.md)** - Complete installation and configuration guide
- Hardware requirements and wiring
- Installation steps
- Full configuration reference
- Sensor documentation
- Troubleshooting guide
- **[Home Assistant Integration](docs/ESPHOME_HOME_ASSISTANT_INTEGRATION.md)** - Accessing meter data and historical readings in Home Assistant
- Template sensors and utility meters
- Historical data extraction
- Long-term consumption tracking
- Calibration and hardware change handling
- **[Configuration Reference](#configuration-reference)** - Quick lookup for common tasks on this page
- Configuration parameter tables
- Common configuration patterns
- Wiring diagrams
- Troubleshooting quick fixes
### For Developers
- **[Developer Guide](docs/DEVELOPER_GUIDE.md)** - Technical architecture and integration patterns
- Architecture overview and design principles
- Dependency injection pattern
- ESPHome integration patterns
- How to extend the component
- Advanced customisation
- **[Component README](components/everblu_meter/README.md)** - Component structure and development
- Component file structure
- Architecture diagram
- Build process
- Development guidelines
- **[Build Notes](docs/ESPHOME_BUILD_NOTES.md)** - Advanced build configuration
- Source file access strategies
- Distribution preparation
- Build troubleshooting
- **Important**: When to run `prepare-component-release` scripts
> Developer Note: If you modify source files in `src/`, you must run `prepare-component-release.ps1/.sh` to update the `ESPHOME-release` folder.
## Quick Start
Getting a reading takes three steps: update your SPI configuration (see the breaking change below), add the external component to your ESPHome YAML, then build and flash.
## Breaking Change: SPI Configuration Required
This component uses ESPHome's native `spi:` bus integration, so any older YAML that relied on the CC1101's implicit SPI pins must be updated.
Previous configuration:
```yaml
everblu_meter:
meter_code: "21-1234567-000"
gdo0_pin: 4
time_id: ha_time
```
Current configuration:
```yaml
spi:
id: main_bus
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
everblu_meter:
spi_id: main_bus
cs_pin: GPIO15
meter_code: "21-1234567-000"
gdo0_pin: 4
time_id: ha_time
```
### 1. Use as External Component
The component is ready to use directly from the `ESPHOME-release` folder. Add to your ESPHome YAML configuration:
```yaml
external_components:
- source:
type: git
url: https://github.com/genestealer/everblu-meters-esp8266-improved
ref: main
path: ESPHOME-release
components: [ everblu_meter ]
refresh: 1d
# Force Arduino framework (required for everblu_meter)
esp32:
board: esp32dev
framework:
type: arduino
# For ESP8266, Arduino is the only framework, but you can be explicit:
# esp8266:
# board: d1_mini
# framework:
# version: recommended
# Or use locally:
# external_components:
# - source:
# type: local
# path: path/to/everblu-meters-esp8266-improved/ESPHOME-release
# components: [ everblu_meter ]
time:
- platform: homeassistant
id: ha_time
spi:
id: main_bus
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
everblu_meter:
spi_id: main_bus
cs_pin: GPIO15
meter_code: "21-1234567-000"
meter_type: water
gdo0_pin: 4
time_id: ha_time
# Optional: Enable for troubleshooting corrupted readings (default: false)
# debug_cc1101: true
volume:
name: "Water Volume"
device_class: water
state_class: total_increasing
status:
name: "Status"
```
For a manual local install, use the `external_components` feature with a local path:
```yaml
external_components:
- source:
type: local
path: /path/to/ESPHOME-release
components: [ everblu_meter ]
```
### 2. Build and Upload
```bash
esphome run your-config.yaml
```
## ESPHome Device Builder (Visual Dashboard)
ESPHome 2026.6.0 replaced the legacy dashboard with the new **Device Builder**. This component works with it, with one caveat:
- **YAML editor:** Fully supported. Paste any of the example configs (which use `external_components:`) into the Device Builder's YAML editor and it validates, compiles, flashes, and OTA-updates exactly as before.
- **Visual component catalog:** Not supported. The drag-and-drop catalog is generated from a nightly sync of ESPHome's *built-in* component schemas, so `everblu_meter` (like every other `external_components:` source) does not appear as a catalog card. This is a Device Builder limitation for all external components, not specific to this project.
## Board-Specific Configuration
### Arduino Nano ESP32 (ESP32S3)
If using an **Arduino Nano ESP32** board, you may still need the following `platformio_options` depending on your ESPHome/PlatformIO toolchain configuration. These flags address board USB mode selection at build time; they are separate from the `Stream&` compatibility fix in `WifiSerialStream`.
```yaml
esp32:
board: arduino_nano_esp32
framework:
type: arduino
# May be required on some Arduino Nano ESP32 toolchain setups
platformio_options:
build_unflags:
- -DARDUINO_USB_CDC_ON_BOOT=0
- -DARDUINO_USB_CDC_ON_BOOT=1
- -DARDUINO_USB_MODE=0
- -DARDUINO_USB_MODE=1
build_flags:
- -DARDUINO_USB_CDC_ON_BOOT=1
- -DARDUINO_USB_MODE=1
```
See [example-nano-esp32.yaml](example-nano-esp32.yaml) for a complete working configuration.
### Other ESP32 Boards
For other ESP32 boards (e.g., `esp32dev`), these platformio options are not needed:
```yaml
esp32:
board: esp32dev
framework:
type: arduino
```
## Configuration Reference
### Key Parameters
| Parameter | Type | Default | Required | Description |
| -------------------- | -------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `meter_code` | string | - | Yes | Dashed meter code: `YY-SSSSSSS` or `YY-SSSSSSS-NNN` (suffix optional) |
| `spi_id` | id | - | Yes | SPI bus ID from the top-level `spi:` block |
| `cs_pin` | pin | - | Yes | CC1101 chip select pin |
| `meter_type` | enum | water | No | `water` or `gas` |
| `gdo0_pin` | pin | - | Yes | CC1101 GDO0 pin (e.g. `GPIO4`, `D2`, `4`) |
| `gdo2_pin` | pin | - | Yes\* | **Required by default (v3.0.0+).** CC1101 GDO2 pin for hardware FIFO threshold signal. Dynamically reconfigured per phase: TX phase prevents `TXFIFO_UNDERFLOW`; RX phase eliminates unnecessary SPI reads and improves ESPHome scheduler efficiency. \*Not required only if `disable_gdo2_fifo_management: true` is set. |
| `disable_gdo2_fifo_management` | bool | false | No | Opt out of the GDO2 hardware FIFO mechanism and use the legacy SPI-polling behaviour. When `true`, `gdo2_pin` is not required and need not be wired. |
| `time_id` | id | - | Yes | Time component ID |
| `frequency` | float | 433.82 | No | RF frequency (MHz) |
| `auto_scan` | bool | false | No | Auto frequency scan |
| `auto_scan_on_failure` | bool | true | No | Auto frequency scan (once) after read attempts keep failing and the component enters cooldown, to recover from carrier-frequency drift |
| `reading_schedule` | string | Monday-Friday | No | Reading schedule. Presets: `Monday-Friday`, `Monday-Saturday`, `Monday-Sunday`, or any single day name |
| `read_hour` | int | 10 | No | Read hour (0-23) |
| `read_minute` | int | 0 | No | Read minute (0-59) |
| `timezone_offset` | int | 0 | No | Local time offset from UTC in **minutes**. Examples: `60` = UTC+1, `-300` = UTC-5, `330` = UTC+5:30. Applied to scheduling; does **not** auto-adjust for DST. |
| `auto_align_time` | bool | true | No | Automatically shift the read time to fall inside the meter's wake window |
| `auto_align_midpoint`| bool | true | No | When auto-aligning, target the midpoint of the wake window instead of its start |
| `max_retries` | int | 5 | No | Max read attempts |
| `retry_cooldown` | duration | 1h | No | Cooldown time |
| `initial_read_on_boot` | bool | false | No | Trigger a read immediately after the time component syncs (useful for fast first-boot data; disabled by default to avoid blocking during meter-absent setups) |
| `adaptive_threshold` | int | 1 | No | Successful-read count before applying a FREQEST frequency correction (1 = adjust after every read; higher values dampen frequent small corrections) |
| `gas_volume_divisor` | int | 100 | No | Gas divisor (100/1000) |
| `debug_cc1101` | bool | false | No | Enable hex dump for debugging |
| `rx_attenuation` | int | 0 | No | Front-end LNA gain limit for close-mounted installations. Values: `0` (default, no limit), `6`, `12`, `18` (dB, approximate). Increase when `*** NEAR-FIELD SATURATION DETECTED ***` is logged and moving the device further is not practical. |
### Schedule Options
- Monday-Friday - Weekdays (default)
- Monday-Saturday - Monday through Saturday
- Monday-Sunday - All days
- Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday - Read only on the selected day
### Custom Schedule Example
```yaml
everblu_meter:
spi_id: main_bus
cs_pin: GPIO15
meter_code: "21-1234567-000"
gdo0_pin: 4
meter_type: water
time_id: ha_time
# Weekend-only, early morning
reading_schedule: Saturday
read_hour: 6
read_minute: 30
# Aggressive retries
max_retries: 15
retry_cooldown: 30min
volume:
name: "Volume"
```
### Logging
```yaml
logger:
level: DEBUG
logs:
everblu_meter: VERBOSE # Maximum detail
sensor: WARN # Reduce sensor noise
```
### Timezone Adjustment
```yaml
everblu_meter:
timezone_offset: -300 # Minutes from UTC (e.g. UTC-5 = -300). Range: -720 to +720.
# ⚠️ Does NOT auto-adjust for DST. Update manually when clocks change.
```
## Features
- **Native ESPHome Integration**: Works with Home Assistant via the ESPHome API
- **Automatic Discovery**: Sensors appear automatically in Home Assistant
- **Scheduled Readings**: Configure when and how often to read the meter
- **Frequency Management**: Automatic frequency scanning and optimisation
- **Comprehensive Monitoring**: Track signal quality, battery life, and reading statistics
- **Multiple Meter Types**: Supports both water and gas meters
- **Retry Logic**: Configurable retry attempts with cooldown periods
- **Low Power**: Efficient reading schedule minimises power consumption
## Example Configurations
Five complete example configurations are provided:
1. **[Water Meter Example](example-water-meter.yaml)** - Full-featured water meter configuration (ESP8266)
2. **[Gas Meter Example](example-gas-meter-minimal.yaml)** - Minimal gas meter configuration
3. **[Advanced Example](example-advanced.yaml)** - Advanced scheduling, retry, and debug options
4. **[Multi-Meter Example](example-multi-meter.yaml)** - Two meters on one ESP with shared CC1101
5. **[Arduino Nano ESP32 Example](example-nano-esp32.yaml)** - Board-specific setup for Nano ESP32 (ESP32-S3)
## Hardware Requirements
- **ESP8266** (e.g., D1 Mini) or **ESP32** board
- **CC1101** RF transceiver module (868/915 MHz version)
- **EverBlu Cyble Enhanced** meter with RF module installed
### Software Requirements
- **ESPHome 2026.1.0 or later.** The component reports its pin assignments through
`GPIOPin::dump_summary(char *, size_t)`, which first shipped in that release. The
component's config validation enforces this, so an older install fails during
validation with a clear message rather than part-way through the C++ compile.
### Wiring (ESP8266 D1 Mini)
| CC1101 Pin | D1 Mini | GPIO |
| ---------- | ------- | ------------ |
| VCC | 3.3V | - |
| GND | GND | - |
| SCK | D5 | 14 |
| MISO | D6 | 12 |
| MOSI | D7 | 13 |
| CSN | D8 | 15 |
| GDO0 | D1 | 5 |
| GDO2 | D2 | 4 (required) |
### Wiring (ESP32)
| CC1101 | ESP32 |
| ------ | ------------- |
| VCC | 3.3V |
| GND | GND |
| SCK | 18 |
| MISO | 19 |
| MOSI | 23 |
| CSN | 25 |
| GDO0 | 4 |
| GDO2 | 27 (required) |
Important: The CC1101 requires 3.3V power. Do not connect to 5V!
> **Breaking change (v3.0.0): GDO2 is now required by default.** You must wire CC1101 GDO2 to a free GPIO and set `gdo2_pin:`. GDO2 is dynamically reconfigured per phase: during TX it acts as a TX FIFO threshold signal preventing `TXFIFO_UNDERFLOW`; during RX it signals when the RX FIFO has data (or end-of-packet), eliminating unnecessary `RXBYTES` SPI reads and allowing the ESPHome scheduler to run freely between batches. If you cannot or do not want to wire GDO2, explicitly opt out by setting `disable_gdo2_fifo_management: true`, which restores the original SPI-polling behaviour (still fully functional). If you neither set `gdo2_pin:` nor opt out, the configuration will fail validation with an error pointing you back here.
## Benefits
### vs. Standalone MQTT Mode
| Feature | ESPHome Mode | MQTT Mode |
| ------------- | ------------ | ----------- |
| Configuration | YAML | C++ defines |
| Integration | ESPHome API | MQTT Broker |
| Discovery | Automatic | Manual |
| Updates | OTA | OTA |
| Logging | ESPHome logs | Serial/WiFi |
| Complexity | Low | Medium |
### ESPHome Mode Advantages
- **Simple Configuration**: YAML-based instead of C++ defines
- **Native Integration**: Direct Home Assistant integration
- **No MQTT Broker**: Reduces infrastructure complexity
- **All ESPHome Features**: Web server, logging, diagnostics, etc.
- **Automatic Discovery**: Sensors appear in Home Assistant automatically
## Home Assistant Best Practice: Utility Meter Helper
**Recommended:** Create a Home Assistant Utility Meter helper to preserve historical data across platform or meter changes.
**Why use a utility meter helper?**
If you switch between ESPHome and MQTT, change meter serial numbers, or replace hardware, a utility meter helper acts as a stable interface. You simply update the source sensor in the helper configuration, and all your historical data, dashboards, and automations remain intact.
**Quick Setup:**
1. In Home Assistant: **Settings** → **Devices & Services** → **Helpers**
2. **Create Helper** → **Utility Meter**
3. Configure:
- **Name**: "Master Water Meter" (or "Master Gas Meter")
- **Input sensor**: Your volume sensor (e.g., `sensor.water_volume`)
- **Meter type**: Daily/monthly/yearly or none
**Benefits:**
- Preserve history when switching ESPHome ↔ MQTT
- Update the meter serial number in one place
- Single point to update for hardware changes
- Stable entity ID for dashboards and automations
**Example:**
```yaml
utility_meter:
master_water_meter:
source: sensor.water_volume # Just update this when you change platforms
name: Master Water Meter
```
When changing platforms or meters, update only the `source` - your history stays intact!
### Migrating Sensor History Between Platforms
**Migrating from MQTT to ESPHome (or replacing hardware)?** A utility meter helper protects future data, but it does not move the history that already accumulated on your *old* sensor onto the *new* one. To merge the past readings from the old entity into the new entity (both raw states and long-term statistics for the Energy dashboard), use the [HA Merge Sensor History](https://github.com/mayerwin/HA-Merge-Sensor-History) custom component. It imports the older history in front of the new sensor in a single atomic, idempotent transaction, so your consumption graphs and lifetime totals stay continuous across the switch. Back up your recorder database first.
> Thanks to [rommess](https://github.com/rommess) for sharing this tip in [discussion #129](https://github.com/genestealer/everblu-meters-esp8266-improved/discussions/129).
### Historical Data from Meter
The ESPHome component exposes a **history text sensor** containing up to 13 months of historical readings stored directly in the meter. This data is retrieved from the meter itself and provided in JSON format.
**Example JSON payload:**
```json
{
"history": [667441, 684214, 700917, 712720, 721549, 728836, 736957, 744959, 752026, 759559, 770165, 779789, 792364],
"monthly_usage": [16773, 16703, 11803, 8829, 7287, 8121, 8002, 7067, 7533, 10606, 9624, 12575],
"current_month_usage": 7276,
"months_available": 13
}
```
**Data Structure:**
- `history`: up to 13 monthly readings (oldest to newest) in L or m³
- `monthly_usage`: monthly consumption values (differences between successive history snapshots)
- `current_month_usage`: Current month consumption
- `months_available`: Months of data (typically 13)
**Use Cases:**
- Bootstrap Home Assistant with up to 13 months of existing data on first setup
- Analyse historical consumption patterns
- Compare current vs. previous month usage
- Verify readings against utility bills
- Pre-populate energy dashboards
**Accessing in Home Assistant:**
The history sensor appears as `sensor.{device_name}_meter_history_json` (e.g., `sensor.water_meter_monitor_meter_history_json`). Parse it using template sensors:
```yaml
template:
- sensor:
- name: "Last Month Usage"
unit_of_measurement: "L"
state: >-
{% set data = states('sensor.water_meter_monitor_meter_history_json') | from_json %}
{{ data.monthly_usage[-1] if data.monthly_usage else 0 }}
- name: "Average Monthly Usage"
unit_of_measurement: "L"
state: >-
{% set data = states('sensor.water_meter_monitor_meter_history_json') | from_json %}
{% if data.monthly_usage %}
{{ (data.monthly_usage[1:] | sum / (data.monthly_usage[1:] | length)) | round(0) }}
{% else %}
0
{% endif %}
```
**Note:** This is historical data from the meter's internal memory, updated when the meter is read. It's separate from Home Assistant's own historical database.
## Architecture
The component uses a clean adapter pattern to separate platform-specific code from core meter reading logic:
```text
EverbluMeterComponent (ESPHome)
├── ESPHomeConfigProvider → Configuration from YAML
├── ESPHomeTimeProvider → Time synchronisation
├── ESPHomeDataPublisher → Sensor publishing
└── MeterReader (shared)
├── CC1101 → Radio hardware
├── FrequencyManager → Frequency optimisation
└── ScheduleManager → Reading schedule
```
**Key Benefits**:
- Most code is shared between ESPHome and standalone modes
- Clean separation of concerns
- Easy to test and maintain
- Extensible for other platforms
## Available Sensors
### Numeric Sensors
- **volume** - Current meter reading (L or m³)
- **battery** - Estimated battery life (years)
- **counter** - Alternative volume counter
- **rssi** / **rssi_percentage** - Radio signal strength. `rssi_percentage` maps -120 to -40 dBm onto 0-100%; the scale is an arbitrary display convenience so Home Assistant can show a signal bar, not a calibrated measurement. Judge link quality from the dBm and LQI values instead.
- **lqi** / **lqi_percentage** - Link quality indicator (raw `lqi` is 0-127 where *lower is better*; `lqi_percentage` inverts this so higher % = better link)
- **time_start** / **time_end** - Reading timing
- **frequency_offset** - Current frequency offset (kHz)
- **tuned_frequency** - Actual tuned frequency (MHz)
- **frequency_estimate** - CC1101 frequency estimate from last reading (kHz) - helps monitor frequency drift
- **total_attempts** / **successful_reads** / **failed_reads** - Statistics
- **gdo2_timeouts** - GDO2 wiring faults since boot (failed boot self-test plus runtime FIFO-threshold timeouts). A non-zero, growing value points at a miswired or wrong-GPIO GDO2 rather than an RF problem
### Text Sensors
- **status** - Current meter status (Idle/Reading/Success/Error)
- **error** - Last error message
- **radio_state** - Radio state (Init/Scanning/Receiving/Idle)
- **timestamp** - Last successful reading time
- **history_json** - Meter history JSON payload
- **firmware_version** - Firmware version string
- **meter_serial_sensor** - Parsed serial section from `meter_code`
- **meter_year_sensor** - Parsed year (`YY`) from `meter_code`
- **meter_clock_sensor** - The meter's own real-time clock, decoded from the frame (`YYYY-MM-DD HH:MM:SS`)
- **meter_model_sensor** - The meter's type/identifier string, decoded from the frame
- **reading_schedule_sensor** - Active reading schedule
- **reading_time_utc_sensor** - Configured reading time (UTC)
### Binary Sensors
- **active_reading** - Whether a reading is currently in progress
- **radio_connected** - CC1101 radio connectivity status
### Control Buttons
- **request_reading_button** - Trigger a manual reading
- **stop_reading_button** - Cancel the current read/retry sequence. Also requests best-effort cancellation of an in-progress deep frequency scan (it bails at the next step; see [#133](https://github.com/genestealer/everblu-meters-esp8266-improved/issues/133))
- **deep_scan_button** - Trigger a Deep frequency scan (±150 kHz, fine 2.5 kHz steps, maps the response window then zooms to the carrier centre)
- **reset_frequency_button** - Reset the frequency offset
- **diagnostic_report_button** - Print a single copy-pasteable block covering the configured pins, the live SPI link self-test, the key CC1101 registers and the current GDO0/GDO2 levels. Press this first when raising an issue; it captures in one place everything needed to tell a wiring fault from an RF problem, and it works even when the radio never came up. The standalone MQTT build prints the same block from its own **Diagnostic Report** button
### Frequency scans in multi-meter setups
Frequency calibration is **radio-global, not per-meter**. There is one base
frequency, one stored offset and one tuned frequency for the whole device, no
matter how many `everblu_meter:` entries you declare. A scan therefore has two
separate properties that can disagree:
| Property | Scope |
| --- | --- |
| Which meter answers the scan | Per-entry: the meter on the `everblu_meter:` entry that triggered it |
| Which frequency range is swept | Global: centred on the single base frequency |
| Where the resulting offset is stored | Global: one value, applied to every meter |
The meter that answers is whichever entry triggered the scan. For a manual
`deep_scan_button` press that is the entry declaring the button. For the
automatic scan run by `auto_scan_on_failure` (enabled by default) it is the
meter that just exhausted its own `max_retries`, which need not be the entry
the button was declared on. Both cases are named in the log:
```text
Starting frequency scan using meter 20-0257750 (water)...
Scan centred on 433.820007 MHz (this meter is configured for 433.782712 MHz)
Running automatic frequency scan for meter 20-0259301 after failed reads...
```
The swept range, however, is **not** per-meter. It is centred on the base
frequency held in the shared `FrequencyManager`, which is set by whichever
entry initialised **last** during boot. Because of this:
> [!IMPORTANT]
> Every `everblu_meter:` entry sharing one CC1101 must be given the **same**
> `frequency:` value. If they differ, only the last entry's value takes effect
> and the others are silently ignored, so scans (and normal reads) run at a
> frequency that no longer matches those meters' configuration. The wide
> ±150 kHz manual deep scan will usually still find the carrier, but the narrow
> ±20 kHz `auto_scan_on_failure` window can end up centred well away from it.
If you need genuinely independent calibration per meter, use one CC1101 (and
one ESP) per meter. Declare `deep_scan_button`, `reset_frequency_button` and
the offset/tuned-frequency sensors on the **first** meter entry only, as in
[example-multi-meter.yaml](example-multi-meter.yaml); adding extra scan buttons
on other entries only changes which meter answers, not the swept range or the
shared offset.
## Common Configuration Patterns
### Water Meter - Basic
```yaml
everblu_meter:
meter_code: "21-1234567-000"
meter_type: water
gdo0_pin: 4
time_id: ha_time
debug_cc1101: false # Optional: Enable hex dump debugging (default: false)
volume:
name: "Water Volume"
```
### Gas Meter - Basic
```yaml
everblu_meter:
meter_code: "22-8765432-000"
meter_type: gas
gdo0_pin: 4
time_id: ha_time
gas_volume_divisor: 100
debug_cc1101: false # Optional: Enable hex dump debugging (default: false)
volume:
name: "Gas Volume"
```
### With Full Monitoring
```yaml
everblu_meter:
meter_code: "21-1234567-000"
meter_type: water
gdo0_pin: 4
time_id: ha_time
debug_cc1101: false # Optional: Enable hex dump debugging (default: false)
volume:
name: "Volume"
battery:
name: "Battery"
rssi_percentage:
name: "Signal"
status:
name: "Status"
active_reading:
name: "Reading Active"
```
## Troubleshooting
### Corrupted or Invalid Volume Readings
If you're seeing:
- Volume reading as 0, small values, or negative numbers
- Historical data showing impossible decreases
- Battery showing as 0 months
**Enable hex dump debugging:**
```yaml
everblu_meter:
debug_cc1101: true # Show raw 200-byte payload (default: false)
```
This will output detailed hex dumps of the decoded frame, helping identify:
- Wrong byte offsets for your meter variant
- Regional/manufacturing differences in data layout
- Specific bytes causing parsing errors
**Complete troubleshooting guide:** [../docs/TROUBLESHOOTING_CORRUPTED_READINGS.md](../docs/TROUBLESHOOTING_CORRUPTED_READINGS.md)
### No Meter Response
1. **Check hardware connections** (see hardware setup section)
2. **Verify frequency**: Try frequency scan button
3. **Check wake window**: Meter may only respond during specific hours
4. **Increase retries**: `max_retries: 15`
### Signal Quality Issues
- **Low RSSI (< −90 dBm)**: Move ESP closer or improve antenna
- **Very high RSSI (> −50 dBm) + CRC failures**: Near-field saturation, the signal is **too strong**. Move the device at least 1–2 m away from the meter. When the device is too close, the CC1101 front-end clips and every frame fails CRC even though RSSI looks excellent. The log will show `*** NEAR-FIELD SATURATION DETECTED ***` to confirm this. If the device must be permanently mounted close to the meter, add `rx_attenuation: 6` (or `12` / `18`) to your YAML configuration to limit the CC1101 LNA gain:
```yaml
everblu_meter:
rx_attenuation: 6 # dB, values: 0 (default), 6, 12, 18
```
- **Enable auto_scan**: `auto_scan: true` (opt-in; enables startup Deep scan for extreme frequency drift)
- **Try different times**: Signal quality varies by time of day
---
## Quick Troubleshooting
### Quick Fixes
**No readings received:**
```yaml
everblu_meter:
auto_scan: true # Enable startup Deep frequency scan (opt-in; default is false)
max_retries: 15 # Increase retry attempts
```
**Poor signal quality:**
```yaml
everblu_meter:
frequency: 433.85 # Try different frequency
auto_scan: false # Disable auto-scan once optimal found
```
**Wrong volume reading (gas meters):**
```yaml
everblu_meter:
gas_volume_divisor: 1000 # Try 100 or 1000
```
**Incorrect time or timezone:**
```yaml
everblu_meter:
timezone_offset: -300 # Minutes from UTC (UTC-5 = -300, UTC+1 = 60, UTC+5:30 = 330)
```
For detailed troubleshooting, see the [Integration Guide](docs/ESPHOME_INTEGRATION_GUIDE.md#troubleshooting).
## License
MIT License - See [LICENSE.md](../LICENSE.md)
## Credits
Based on the EverBlu Meters ESP8266 project with architectural improvements for reusability and ESPHome integration.
## Links
- **Main Project**: [Main README](../README.md)
- **GitHub Repository**:
- **ESPHome Documentation**:
- **Home Assistant**:
---
**Need Help?**
- Start with the [Integration Guide](docs/ESPHOME_INTEGRATION_GUIDE.md)
- See [Home Assistant Integration](docs/ESPHOME_HOME_ASSISTANT_INTEGRATION.md) for accessing meter data in Home Assistant
- See the Configuration Reference above for parameters and quick fixes
- See [Troubleshooting](docs/ESPHOME_INTEGRATION_GUIDE.md#troubleshooting) for common issues
- Developers: See [Developer Guide](docs/DEVELOPER_GUIDE.md) for architecture details
## Development: Code Style & Formatting
The C++ sources of this ESPHome component follow **ESPHome's own coding standards** so the
code matches what ships in ESPHome core and stays compatible with the upstream review bar.
- **Formatter:** [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html), configured with a
verbatim copy of ESPHome's upstream style.
- **Style file:** [`components/everblu_meter/.clang-format`](components/everblu_meter/.clang-format)
- copied from [esphome/esphome `.clang-format`](https://github.com/esphome/esphome/blob/dev/.clang-format).
It is placed in the component directory (not the repo root) so it applies **only** to the
ESPHome component; the portable library under [`src/`](../src) keeps its own house style.
- **Naming / `this->` / `final` conventions** follow ESPHome's
[code standards](https://developers.esphome.io/contributing/code/) and
[`.clang-tidy`](https://github.com/esphome/esphome/blob/dev/.clang-tidy)
(private/protected members use a trailing `_`, `lower_case` methods, leaf classes marked `final`).
### Running the formatter
The component clang-format style is wired into pre-commit (below), so it runs automatically on
commit. To run it manually, use the helper scripts (they auto-install the latest `clang-format`
if it is missing):
```powershell
# Windows / PowerShell
./ESPHOME/format-component.ps1 # check only (non-zero exit if changes needed)
./ESPHOME/format-component.ps1 -Fix # reformat in place
```
```bash
# Linux / macOS
ESPHOME/format-component.sh # check only
ESPHOME/format-component.sh --fix # reformat in place
```
Or invoke `clang-format` directly (it auto-discovers the component `.clang-format`):
```bash
clang-format -style=file -i ESPHOME/components/everblu_meter/everblu_meter.{cpp,h}
```
### Linting & pre-commit
Repo-wide linting mirrors ESPHome's toolchain and runs through [pre-commit](https://pre-commit.com):
| Tool | Scope | Config |
| --- | --- | --- |
| **ruff** (lint + format) | all Python | [`ruff.toml`](../ruff.toml) (mirrors ESPHome) |
| **yamllint** | all YAML | [`.yamllint`](../.yamllint) |
| **clang-format** | ESPHome component C++ | [`.clang-format`](components/everblu_meter/.clang-format) |
| trailing-whitespace, end-of-file, check-yaml, … | repo hygiene | [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) |
All tools install **unpinned** (latest), so there are no versions to maintain.
```bash
pip install pre-commit
pre-commit install # run automatically on every commit
pre-commit run --all-files # run across the whole repo now
```
On pull requests, the **[pre-commit.ci](https://pre-commit.ci)** app (if enabled for the repo,
via the `ci:` block in [`.pre-commit-config.yaml`](../.pre-commit-config.yaml)) runs these hooks
and can auto-fix them. They are advisory, not a hard merge gate.
> Note: ESPHome also enforces `clang-tidy` upstream, but that requires a full ESPHome/PlatformIO
> build environment (a compilation database with the ESPHome headers), so it is not wired into this
> repository's lightweight CI. The [`.clang-tidy`](https://github.com/esphome/esphome/blob/dev/.clang-tidy)
> rules are linked above for reference.