Hi everyone,
I wanted to share a project that completely eliminates the need for external software bridges (like OpenMQTTGateway or rtl_433) or complex custom C++ components to read data from a Davis Vantage Pro2 / Vue (868MHz EU Variant) station.
Thanks to recent updates in the official ESPHome core, we can now configure the CC1101 transceiver natively into Packet Mode directly via YAML, catching and decoding the 19200 baud GFSK binary packets flawlessly on an ESP32-WROOM.
Special credits to the community (ventillo and dekay projects) for the binary specifications and layout logic.
Shopping List / Hardware Components
To build this receiver, you only need a few inexpensive hardware components and tools:
-
ESP32 Development Board (WROOM Core): A standard 30-pin or 38-pin ESP32-WROOM board. (Do not use an ESP32-C3 for this specific layout, as it requires a different pin configuration). This is the one i used:
-
CC1101 868MHz Wireless Module with Ribbon Cable: Ensure you purchase the 868MHz version (often sold with a green or blue PCB). Most modules come with a small copper spiral antenna included, which can be used when distance is small.
-
Connection Options: You can either use temporary Dupont jumper wires for testing, or use a soldering station and a multi-core cable to solder the ribbon cable directly to the ESP32 pins. A soldered connection is highly recommended for long-term reliability.
This is my setup:
I still have to design and print a housing, but could not wait to share what i made so far.
Hardware Requirements & Wiring (ESP32-WROOM)
Connect your CC1101 module to the hardware pins of your ESP32-WROOM using the pinout reference image below:
| CC1101 Pin | ESP32 WROOM Pin | Function |
|---|---|---|
| VCC | 3V3 | Power (Do NOT use 5V!) |
| GND | GND | Ground |
| MOSI | GPIO 23 | SPI MOSI |
| SCLK | GPIO 18 | SPI Clock |
| MISO | GPIO 19 | SPI MISO |
| GDO2 | Not Connected | Leave disconnected |
| GDO0 | GPIO 4 | Data Interrupt Pin |
| CSN | GPIO 5 | SPI Chip Select |
| ANT | Antenna | Solder antenna wire here |
Important Range & Antenna Warning
Please note that when using the standard copper spiral antenna that comes with most CC1101 modules, the effective range is relatively short. To avoid excessive packet loss and CRC errors, you should keep the distance between the outdoor ISS unit and the CC1101 to a maximum of 15 meters. Upgrading to a better external antenna can significantly improve results and extend this range.
In my own setup, the distance is about 5 meters. My weather mast is mounted directly on the garage wall, allowing me to place the ESP32 and CC1101 inside the garage. This location was perfect because there is already a Wi-Fi access point present inside the garage to ensure a rock-solid network connection.
The Passive Channel-Hopping Trick
Davis stations hop across 5 EU channels between ~868.04 and ~868.52 MHz. Instead of programming complex synchronized channel-hopping, we center the CC1101 right in the middle at 868.35MHz and open the filter_bandwidth wide to 325kHz. This broad window captures the transmissions across all hop-channels passively and reliably!
Step-by-Step Installation Guide
Step 1: Prepare the ESPHome Configuration
Create a new device in ESPHome, open the code editor, and paste the complete YAML configuration provided below. Make sure to update the password and key sections under wifi api and ota with your own credentials.
yaml
substitutions:
name: davis-vantage-receiver
friendly_name: Davis Vantage Receiver
esphome:
name: ${name}
friendly_name: ${friendly_name}
esp32:
board: esp32dev
framework:
type: arduino
advanced:
minimum_chip_revision: "3.1"
sram1_as_iram: true
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Davis-Vantage-Receiver"
password: "CREATEYOUROWN"
captive_portal:
web_server:
port: 80
api:
encryption:
key: "CREATEYOUROWN"
logger:
level: INFO # Keeps your logs clean, only logs validated station updates
ota:
- platform: esphome
password: "CREATEYOUROWN"
spi:
clk_pin: GPIO18
mosi_pin: GPIO23
miso_pin: GPIO19
globals:
- id: rain_count_prev
type: int
initial_value: '-1'
- id: rain_total_mm
type: float
initial_value: '0.0'
- id: raw_wind_dir
type: float
initial_value: '0.0'
- id: known_unit_id
type: int
initial_value: '-1'
cc1101:
id: my_cc1101
cs_pin: GPIO5
gdo0_pin: GPIO4
frequency: 868.35MHz
modulation_type: GFSK
symbol_rate: 19200
fsk_deviation: 9.5kHz
filter_bandwidth: 325kHz
magn_target: 33dB
packet_mode: true
packet_length: 8
sync_mode: 16/16
sync1: 0xCB
sync0: 0x89
crc_enable: false
whitening: false
num_preamble: 2
on_packet:
then:
- lambda: |-
if (x.size() < 8) return;
// 1. Bit-reversal (LSB naar MSB mapping)
std::vector<uint8_t> d(8);
for (int i = 0; i < 8; i++) {
uint8_t b = x[i];
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
d[i] = b;
}
// 2. De robuuste rotatieloop die de bit-shifts feilloos opvangt
bool crc_ok = false;
for (int shift = 0; shift < 3; shift++) {
uint16_t crc = 0;
for (int i = 0; i < 6; i++) {
crc ^= (uint16_t)d[i] << 8;
for (int j = 0; j < 8; j++) {
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
uint16_t ontvangen_crc_1 = ((uint16_t)d[6] << 8) | d[7];
uint16_t ontvangen_crc_2 = ((uint16_t)d[7] << 8) | d[6];
if (crc == ontvangen_crc_1 || crc == ontvangen_crc_2) {
crc_ok = true;
break;
}
uint8_t carry = 0;
for (int i = 0; i < 8; i++) {
uint8_t next_carry = (d[i] & 0x01) << 7;
d[i] = (d[i] >> 1) | carry;
carry = next_carry;
}
}
if (!crc_ok) return;
// 3. Zender-ID lock (ID 1 = intern 0)
uint8_t unit_id = d[0] & 0x07;
bool battery_low = (d[0] & 0x08) != 0;
if (id(known_unit_id) < 0) {
id(known_unit_id) = unit_id;
ESP_LOGI("davis", "Station-ID succesvol vastgelegd op: %d", unit_id);
} else if (unit_id != id(known_unit_id)) {
return;
}
// Verzend status naar HA
id(davis_battery_low).publish_state(battery_low);
id(davis_rssi).publish_state(rssi);
id(davis_lqi).publish_state(lqi);
// Windverwerking (bytes 1 en 2)
float wind_kmh = d[1] * 1.60934f;
float wind_dir = d[2] * (360.0f / 255.0f);
if (wind_kmh >= 0.0f && wind_kmh < 160.0f) {
id(davis_wind_speed).publish_state(wind_kmh);
if (std::isnan(id(davis_windgust).state)) {
id(davis_windgust).publish_state(wind_kmh);
}
}
if (wind_dir >= 0.0f && wind_dir <= 360.0f) {
id(raw_wind_dir) = wind_dir;
id(davis_wind_dir_combined).update();
}
// Berichttypes ontleden
uint8_t ptype = d[0] >> 4;
if (ptype == 8) {
// Buitentemperatuur
uint16_t raw = ((uint16_t)d[3] << 8) | d[4];
float temp_c = (raw / 160.0f - 32.0f) * 5.0f / 9.0f;
if (temp_c > -15.0f && temp_c < 45.0f) {
id(davis_temp).publish_state(temp_c);
ESP_LOGI("davis", "Weather Update - Temperature: %.1f°C", temp_c);
}
} else if (ptype == 9) {
// Windvlaag
float gust_kmh = d[3] * 1.60934f;
if (gust_kmh >= 0.0f && gust_kmh < 200.0f) id(davis_windgust).publish_state(gust_kmh);
} else if (ptype == 10) {
// Luchtvochtigheid
uint16_t raw = (((uint16_t)(d[4] >> 4) & 0x03) << 8) | d[3];
float hum = raw / 10.0f;
if (hum > 0.0f && hum <= 100.0f) {
id(davis_hum).publish_state(hum);
ESP_LOGI("davis", "Weather Update - Luchtvochtigheid: %.0f%%", hum);
}
} else if (ptype == 14) {
// Regenval
int tips = ((int)d[3] + (((int)d[4] >> 7) << 8)) & 0x7F;
if (id(rain_count_prev) >= 0) {
int delta = tips - id(rain_count_prev);
if (delta < 0) delta += 128;
if (delta > 0 && delta < 10) {
id(rain_total_mm) += delta * 0.2f;
id(davis_rain).publish_state(id(rain_total_mm));
}
}
id(rain_count_prev) = tips;
}
binary_sensor:
- platform: template
name: "Davis Battery Low"
id: davis_battery_low
device_class: battery
sensor:
- platform: template
name: "Davis Temperature"
id: davis_temp
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
accuracy_decimals: 1
- platform: template
name: "Davis Humidity"
id: davis_hum
unit_of_measurement: "%"
device_class: humidity
state_class: measurement
accuracy_decimals: 0
- platform: template
name: "Davis Wind Speed"
id: davis_wind_speed
unit_of_measurement: "km/h"
device_class: wind_speed
state_class: measurement
accuracy_decimals: 1
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
- platform: template
name: "Davis Wind Gust"
id: davis_windgust
unit_of_measurement: "km/h"
device_class: wind_speed
state_class: measurement
accuracy_decimals: 1
- platform: template
name: "Davis RSSI"
id: davis_rssi
unit_of_measurement: "dBm"
state_class: measurement
device_class: signal_strength
entity_category: diagnostic
accuracy_decimals: 1
- platform: template
name: "Davis LQI"
id: davis_lqi
state_class: measurement
entity_category: diagnostic
accuracy_decimals: 0
- platform: template
name: "Davis Daily Rain"
id: davis_rain
unit_of_measurement: "mm"
device_class: precipitation
state_class: total_increasing
accuracy_decimals: 1
text_sensor:
- platform: template
name: "Davis Wind Direction"
id: davis_wind_dir_combined
icon: "mdi:compass-outline"
lambda: |-
float deg = id(raw_wind_dir);
int index = int((deg + 11.25f) / 22.5f) % 16;
const char* compassPoints[] = {
"N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW"
};
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.0f° %s", deg, compassPoints[index]);
return std::string(buffer);
Step 2: Compile and Flash
Click Save and then Install. Connect your ESP32 via USB for the initial flash. Once the program compiles and uploads successfully, the device will boot up and automatically connect to your local Wi-Fi network. Subsequent updates can be done completely wireless over-the-air (OTA).
Step 3: Integrate into Home Assistant
Go to your Home Assistant dashboard, navigate to Settings ➔ Devices & Services. You will see a notification stating "Discovered: Davis Vantage Receiver". Click Configure, click Submit, and all the sensors will be added natively to the "overview" dashboard instantly.
Step 4: Understanding Station-ID Configuration
Davis stations can transmit on 8 different channels (Transmitter IDs).
- Auto-Lock Mode: By default,
known_unit_idis set to-1. The ESP32 will automatically lock onto the first valid Davis station it hears in the air and ignore all other neighboring stations from that point forward. - Neighbor interference: Once locked, it will silently ignore any packets originating from other transmitter IDs.
- Manual override: If your station is set to an ID other than 1, or if you want to explicitly hardcode it, change the
initial_value: '-1'underid: known_unit_idin theglobals:section. Note that Davis IDs map to the code internally as follows: Transmitter ID 1 =0, ID 2 =1, ID 3 =2, up to ID 8 =7.
Hope this helps anyone looking to run a lightweight, native Davis integration on ESPHome! Enjoy
![]()


