ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
aranet.cpp
Go to the documentation of this file.
1#include "aranet.h"
2
3#include <cinttypes>
4
6#include "esphome/core/log.h"
7
8namespace esphome::aranet {
9
10ESPHOME_LOG_TAG(TAG, "aranet");
11static constexpr uint16_t ARANET_MANUFACTURER_ID = 0x0702;
12static constexpr size_t ARANET4_DATA_LENGTH = 22;
13static constexpr size_t OTHER_ARANET_DATA_LENGTH = 24;
14
21
22static inline void publish_if_set(sensor::Sensor *sensor, float value) {
23 if (sensor != nullptr)
24 sensor->publish_state(value);
25}
26
27// Aranet does not publish an official advertisement specification. The layout below is corroborated by these
28// independently maintained implementations:
29// https://github.com/Anrijs/Aranet4-Python/blob/master/aranet4/client.py
30// https://github.com/Anrijs/Aranet4-ESP32/blob/main/src/Aranet4.h
31
33 ESP_LOGCONFIG(TAG, "Aranet");
34 LOG_SENSOR(" ", "Carbon Dioxide", this->co2_sensor_);
35 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
36 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
37 LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
38 LOG_SENSOR(" ", "Battery Level", this->battery_level_sensor_);
39 LOG_SENSOR(" ", "Measurement Interval", this->measurement_interval_sensor_);
40 LOG_SENSOR(" ", "Measurement Age", this->measurement_age_sensor_);
41 LOG_SENSOR(" ", "Signal Strength", this->signal_strength_sensor_);
42 LOG_SENSOR(" ", "Radon Concentration", this->radon_sensor_);
43 LOG_SENSOR(" ", "Radiation Dose Rate", this->radiation_rate_sensor_);
44 LOG_SENSOR(" ", "Radiation Total Dose", this->radiation_total_sensor_);
45 LOG_SENSOR(" ", "Radiation Dose Duration", this->radiation_duration_sensor_);
46}
47
49 if (device.address_uint64() != this->address_)
50 return false;
51
52 const auto manufacturer_uuid = ble_device_base::ESPBTUUID::from_uint16(ARANET_MANUFACTURER_ID);
53 for (const auto &manufacturer_data : device.get_manufacturer_datas()) {
54 if (manufacturer_data.uuid != manufacturer_uuid)
55 continue;
56
57 const auto &data = manufacturer_data.data;
58 if (data.size() != ARANET4_DATA_LENGTH && data.size() != OTHER_ARANET_DATA_LENGTH) {
59 if ((this->warned_ & WARNED_INVALID_LENGTH) == 0) {
60 ESP_LOGW(TAG, "Unexpected manufacturer data length: %u", static_cast<unsigned>(data.size()));
62 }
63 return false;
64 }
65
66 // Aranet4 has no device type byte, so its flags and trailer start one byte earlier
67 const bool is_aranet4 = data.size() == ARANET4_DATA_LENGTH;
68 if ((data[is_aranet4 ? 0 : 1] & 0x20) == 0) {
69 if ((this->warned_ & WARNED_INTEGRATION_DISABLED) == 0) {
70 ESP_LOGW(TAG, "Smart Home integration is disabled on the Aranet device");
72 }
73 return false;
74 }
75
76 if (!is_aranet4 && (data[0] == 0 || data[0] > 3)) {
77 if ((this->warned_ & WARNED_UNSUPPORTED_TYPE) == 0) {
78 ESP_LOGW(TAG, "Unsupported Aranet device type: %u", data[0]);
80 }
81 return false;
82 }
83 const auto device_type =
84 is_aranet4 ? AranetDeviceType::ARANET_DEVICE_TYPE_ARANET4 : static_cast<AranetDeviceType>(data[0]);
85
86 // Battery, measurement interval, measurement age and measurement counter
87 const uint8_t *trailer = &data[is_aranet4 ? 15 : 17];
88 const uint16_t measurement_interval = encode_uint16(trailer[3], trailer[2]);
89 const uint16_t measurement_age = encode_uint16(trailer[5], trailer[4]);
90 const uint8_t measurement_counter = trailer[6];
91
92 publish_if_set(this->measurement_age_sensor_, measurement_age);
93 publish_if_set(this->signal_strength_sensor_, device.get_rssi());
94
95 if (measurement_counter == this->last_measurement_counter_)
96 return true;
97 this->last_measurement_counter_ = measurement_counter;
98
99 // Not meaningful for Aranet Radiation, which stores its dose duration in these bytes
100 const float temperature = encode_uint16(data[11], data[10]) / 20.0f;
101 const float pressure = encode_uint16(data[13], data[12]) / 10.0f;
102 switch (device_type) {
104 const uint16_t co2 = encode_uint16(data[9], data[8]);
105 publish_if_set(this->co2_sensor_, co2);
106 publish_if_set(this->temperature_sensor_, temperature);
107 publish_if_set(this->pressure_sensor_, pressure);
108 publish_if_set(this->humidity_sensor_, data[14]);
109 ESP_LOGD(TAG, "Aranet4 CO2: %u ppm, temperature: %.1f C, humidity: %u%%, pressure: %.1f hPa", co2, temperature,
110 data[14], pressure);
111 break;
112 }
114 const float humidity = encode_uint16(data[15], data[14]) / 10.0f;
115 publish_if_set(this->temperature_sensor_, temperature);
116 publish_if_set(this->humidity_sensor_, humidity);
117 ESP_LOGD(TAG, "Aranet2 temperature: %.1f C, humidity: %.1f%%", temperature, humidity);
118 break;
119 }
121 const uint32_t radiation_total = encode_uint32(data[9], data[8], data[7], data[6]);
122 const uint32_t radiation_duration = encode_uint32(data[13], data[12], data[11], data[10]);
123 const uint16_t radiation_rate = encode_uint16(data[15], data[14]);
124 publish_if_set(this->radiation_rate_sensor_, radiation_rate / 1000.0f);
125 publish_if_set(this->radiation_total_sensor_, radiation_total);
126 publish_if_set(this->radiation_duration_sensor_, radiation_duration);
127 ESP_LOGD(TAG, "Aranet Radiation dose rate: %u nSv/h, total dose: %" PRIu32 " nSv", radiation_rate,
128 radiation_total);
129 break;
130 }
132 const uint16_t radon = encode_uint16(data[9], data[8]);
133 const float humidity = encode_uint16(data[15], data[14]) / 10.0f;
134 publish_if_set(this->radon_sensor_, radon);
135 // Aranet Radon One uses zero temperature and humidity to indicate fields not supported by that model.
136 if (temperature != 0)
137 publish_if_set(this->temperature_sensor_, temperature);
138 publish_if_set(this->pressure_sensor_, pressure);
139 if (humidity != 0)
140 publish_if_set(this->humidity_sensor_, humidity);
141 ESP_LOGD(TAG, "Aranet Radon: %u Bq/m3, temperature: %.1f C, humidity: %.1f%%, pressure: %.1f hPa", radon,
142 temperature, humidity, pressure);
143 break;
144 }
145 default:
146 break;
147 }
148 publish_if_set(this->battery_level_sensor_, trailer[0]);
149 publish_if_set(this->measurement_interval_sensor_, measurement_interval);
150
151 return true;
152 }
153
154 return false;
155}
156
157} // namespace esphome::aranet
uint16_t last_measurement_counter_
Definition aranet.h:22
static constexpr uint8_t WARNED_INVALID_LENGTH
Definition aranet.h:17
bool parse_device(const ble_device_base::ESPBTDevice &device) override
Definition aranet.cpp:48
static constexpr uint8_t WARNED_UNSUPPORTED_TYPE
Definition aranet.h:19
void dump_config() override
Definition aranet.cpp:32
static constexpr uint8_t WARNED_INTEGRATION_DISABLED
Definition aranet.h:18
uint64_t address_uint64() const
Return MAC as packed uint64 (byte 0 in LSB — matches esp32's address_uint64).
const std::vector< ServiceData > & get_manufacturer_datas() const
Definition ble_device.h:224
static ESPBTUUID from_uint16(uint16_t uuid)
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
ESPHOME_LOG_TAG(TAG, "aranet")
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:988
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:980
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12
uint16_t trailer
uint8_t pressure
Definition tt21100.cpp:7