ESPHome 2026.4.0-dev
Loading...
Searching...
No Matches
rd03d.cpp
Go to the documentation of this file.
1#include "rd03d.h"
3#include "esphome/core/log.h"
4
5#include <cinttypes>
6#include <cmath>
7
8namespace esphome::rd03d {
9
10static const char *const TAG = "rd03d";
11
12// Delay before sending configuration commands to allow radar to initialize
13static constexpr uint32_t SETUP_TIMEOUT_MS = 100;
14
15// Data frame format (radar -> host)
16static constexpr uint8_t FRAME_HEADER[] = {0xAA, 0xFF, 0x03, 0x00};
17static constexpr uint8_t FRAME_FOOTER[] = {0x55, 0xCC};
18
19// Command frame format (host -> radar)
20static constexpr uint8_t CMD_FRAME_HEADER[] = {0xFD, 0xFC, 0xFB, 0xFA};
21static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01};
22
23// RD-03D tracking mode commands
24static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080;
25static constexpr uint16_t CMD_MULTI_TARGET = 0x0090;
26
27// Speed sentinel values (cm/s) - radar outputs these when no valid Doppler measurement
28// FMCW radars detect motion via Doppler shift; targets with these speeds are likely noise
29static constexpr int16_t SPEED_SENTINEL_248 = 248;
30static constexpr int16_t SPEED_SENTINEL_256 = 256;
31
32// Decode coordinate/speed value from RD-03D format
33// Per datasheet: MSB=1 means positive, MSB=0 means negative
34static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) {
35 int16_t value = ((high_byte & 0x7F) << 8) | low_byte;
36 if ((high_byte & 0x80) == 0) {
37 value = -value;
38 }
39 return value;
40}
41
42// Check if speed value indicates a valid Doppler measurement
43// Zero, ±248, or ±256 cm/s are sentinel values from the radar firmware
44static constexpr bool is_speed_valid(int16_t speed) {
45 int16_t abs_speed = speed < 0 ? -speed : speed;
46 return speed != 0 && abs_speed != SPEED_SENTINEL_248 && abs_speed != SPEED_SENTINEL_256;
47}
48
50 ESP_LOGCONFIG(TAG, "Setting up RD-03D...");
51 this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); });
52}
53
55 ESP_LOGCONFIG(TAG, "RD-03D:");
56 if (this->tracking_mode_.has_value()) {
57 ESP_LOGCONFIG(TAG, " Tracking Mode: %s",
58 *this->tracking_mode_ == TrackingMode::SINGLE_TARGET ? "single" : "multi");
59 }
60 if (this->throttle_ > 0) {
61 ESP_LOGCONFIG(TAG, " Throttle: %" PRIu32 "ms", this->throttle_);
62 }
63#ifdef USE_SENSOR
64 LOG_SENSOR(" ", "Target Count", this->target_count_sensor_);
65#endif
66#ifdef USE_BINARY_SENSOR
67 LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
68#endif
69 for (uint8_t i = 0; i < MAX_TARGETS; i++) {
70 ESP_LOGCONFIG(TAG, " Target %d:", i + 1);
71#ifdef USE_SENSOR
72 LOG_SENSOR(" ", "X", this->targets_[i].x);
73 LOG_SENSOR(" ", "Y", this->targets_[i].y);
74 LOG_SENSOR(" ", "Speed", this->targets_[i].speed);
75 LOG_SENSOR(" ", "Distance", this->targets_[i].distance);
76 LOG_SENSOR(" ", "Resolution", this->targets_[i].resolution);
77 LOG_SENSOR(" ", "Angle", this->targets_[i].angle);
78#endif
79#ifdef USE_BINARY_SENSOR
80 LOG_BINARY_SENSOR(" ", "Presence", this->target_presence_[i]);
81#endif
82 }
83}
84
86 // Read all available bytes in batches to reduce UART call overhead.
87 size_t avail = this->available();
88 uint8_t buf[64];
89 while (avail > 0) {
90 size_t to_read = std::min(avail, sizeof(buf));
91 if (!this->read_array(buf, to_read)) {
92 break;
93 }
94 avail -= to_read;
95 for (size_t i = 0; i < to_read; i++) {
96 uint8_t byte = buf[i];
97 ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_);
98
99 // Check if we're looking for frame header
100 if (this->buffer_pos_ < FRAME_HEADER_SIZE) {
101 if (byte == FRAME_HEADER[this->buffer_pos_]) {
102 this->buffer_[this->buffer_pos_++] = byte;
103 } else if (byte == FRAME_HEADER[0]) {
104 // Start over if we see a potential new header
105 this->buffer_[0] = byte;
106 this->buffer_pos_ = 1;
107 } else {
108 this->buffer_pos_ = 0;
109 }
110 continue;
111 }
112
113 // Accumulate data bytes
114 this->buffer_[this->buffer_pos_++] = byte;
115
116 // Check if we have a complete frame
117 if (this->buffer_pos_ == FRAME_SIZE) {
118 // Validate footer
119 if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) {
120 this->process_frame_();
121 } else {
122 ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2],
123 this->buffer_[FRAME_SIZE - 1]);
124 }
125 this->buffer_pos_ = 0;
126 }
127 }
128 }
129}
130
132 // Apply throttle if configured
133 if (this->throttle_ > 0) {
134 uint32_t now = millis();
135 if (now - this->last_publish_time_ < this->throttle_) {
136 return;
137 }
138 this->last_publish_time_ = now;
139 }
140
141 uint8_t target_count = 0;
142
143 for (uint8_t i = 0; i < MAX_TARGETS; i++) {
144 // Calculate offset for this target's data
145 // Header is 4 bytes, each target is 8 bytes
146 uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE);
147
148 // Extract raw bytes for this target (per datasheet Table 5-2: X, Y, Speed, Resolution)
149 uint8_t x_low = this->buffer_[offset + 0];
150 uint8_t x_high = this->buffer_[offset + 1];
151 uint8_t y_low = this->buffer_[offset + 2];
152 uint8_t y_high = this->buffer_[offset + 3];
153 uint8_t speed_low = this->buffer_[offset + 4];
154 uint8_t speed_high = this->buffer_[offset + 5];
155 uint8_t res_low = this->buffer_[offset + 6];
156 uint8_t res_high = this->buffer_[offset + 7];
157
158 // Decode values per RD-03D format
159 int16_t x = decode_value(x_low, x_high);
160 int16_t y = decode_value(y_low, y_high);
161 int16_t speed = decode_value(speed_low, speed_high);
162 uint16_t resolution = (res_high << 8) | res_low;
163
164 // Check if target is present
165 // Requires non-zero coordinates AND valid speed (not a sentinel value)
166 // FMCW radars detect motion via Doppler; sentinel speed indicates no real target
167 bool has_position = (x != 0 || y != 0);
168 bool has_valid_speed = is_speed_valid(speed);
169 bool target_present = has_position && has_valid_speed;
170 if (target_present) {
171 target_count++;
172 }
173
174#ifdef USE_SENSOR
175 this->publish_target_(i, x, y, speed, resolution);
176#endif
177
178#ifdef USE_BINARY_SENSOR
179 if (this->target_presence_[i] != nullptr) {
180 this->target_presence_[i]->publish_state(target_present);
181 }
182#endif
183 }
184
185#ifdef USE_SENSOR
186 if (this->target_count_sensor_ != nullptr) {
187 this->target_count_sensor_->publish_state(target_count);
188 }
189#endif
190
191#ifdef USE_BINARY_SENSOR
192 if (this->target_binary_sensor_ != nullptr) {
193 this->target_binary_sensor_->publish_state(target_count > 0);
194 }
195#endif
196}
197
198#ifdef USE_SENSOR
199void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) {
200 TargetSensor &target = this->targets_[target_num];
201 bool valid = is_speed_valid(speed);
202
203 // Publish X coordinate (mm) - NaN if target invalid
204 if (target.x != nullptr) {
205 target.x->publish_state(valid ? static_cast<float>(x) : NAN);
206 }
207
208 // Publish Y coordinate (mm) - NaN if target invalid
209 if (target.y != nullptr) {
210 target.y->publish_state(valid ? static_cast<float>(y) : NAN);
211 }
212
213 // Publish speed (convert from cm/s to mm/s) - NaN if target invalid
214 if (target.speed != nullptr) {
215 target.speed->publish_state(valid ? static_cast<float>(speed) * 10.0f : NAN);
216 }
217
218 // Publish resolution (mm)
219 if (target.resolution != nullptr) {
221 }
222
223 // Calculate and publish distance (mm) - NaN if target invalid
224 if (target.distance != nullptr) {
225 if (valid) {
226 target.distance->publish_state(std::hypot(static_cast<float>(x), static_cast<float>(y)));
227 } else {
228 target.distance->publish_state(NAN);
229 }
230 }
231
232 // Calculate and publish angle (degrees) - NaN if target invalid
233 // Angle is measured from the Y axis (radar forward direction)
234 if (target.angle != nullptr) {
235 if (valid) {
236 float angle = std::atan2(static_cast<float>(x), static_cast<float>(y)) * 180.0f / M_PI;
237 target.angle->publish_state(angle);
238 } else {
239 target.angle->publish_state(NAN);
240 }
241 }
242}
243#endif
244
245void RD03DComponent::send_command_(uint16_t command, const uint8_t *data, uint8_t data_len) {
246 // Send header
247 this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER));
248
249 // Send length (command word + data)
250 uint16_t len = 2 + data_len;
251 this->write_byte(len & 0xFF);
252 this->write_byte((len >> 8) & 0xFF);
253
254 // Send command word (little-endian)
255 this->write_byte(command & 0xFF);
256 this->write_byte((command >> 8) & 0xFF);
257
258 // Send data if any
259 if (data != nullptr && data_len > 0) {
260 this->write_array(data, data_len);
261 }
262
263 // Send footer
264 this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
265
266 ESP_LOGD(TAG, "Sent command 0x%04X with %d bytes of data", command, data_len);
267}
268
270 if (this->tracking_mode_.has_value()) {
271 uint16_t mode_cmd = (*this->tracking_mode_ == TrackingMode::SINGLE_TARGET) ? CMD_SINGLE_TARGET : CMD_MULTI_TARGET;
272 this->send_command_(mode_cmd);
273 }
274}
275
276} // namespace esphome::rd03d
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:497
void publish_state(bool new_state)
Publish a new state to the front-end.
optional< TrackingMode > tracking_mode_
Definition rd03d.h:84
std::array< TargetSensor, MAX_TARGETS > targets_
Definition rd03d.h:75
void dump_config() override
Definition rd03d.cpp:54
void send_command_(uint16_t command, const uint8_t *data=nullptr, uint8_t data_len=0)
Definition rd03d.cpp:245
binary_sensor::BinarySensor * target_binary_sensor_
Definition rd03d.h:80
std::array< uint8_t, FRAME_SIZE > buffer_
Definition rd03d.h:88
void publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution)
Definition rd03d.cpp:199
std::array< binary_sensor::BinarySensor *, MAX_TARGETS > target_presence_
Definition rd03d.h:79
sensor::Sensor * target_count_sensor_
Definition rd03d.h:76
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
void write_byte(uint8_t data)
Definition uart.h:18
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
int speed
Definition fan.h:3
Resolution resolution
Definition msa3xx.h:1
std::string size_t len
Definition helpers.h:1045
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
bool valid
static void uint32_t
sensor::Sensor * resolution
Definition rd03d.h:35
sensor::Sensor * distance
Definition rd03d.h:34
sensor::Sensor * x
Definition rd03d.h:31
sensor::Sensor * speed
Definition rd03d.h:33
sensor::Sensor * y
Definition rd03d.h:32
sensor::Sensor * angle
Definition rd03d.h:36
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6