ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
tx20.cpp
Go to the documentation of this file.
1#include "tx20.h"
3#include "esphome/core/log.h"
4
5#include <array>
6
7namespace esphome::tx20 {
8
9static const char *const TAG = "tx20";
10static const uint8_t MAX_BUFFER_SIZE = 41;
11static const uint16_t TX20_MAX_TIME = MAX_BUFFER_SIZE * 1200 + 5000;
12static const uint16_t TX20_BIT_TIME = 1200;
13static const char *const DIRECTIONS[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
14 "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"};
15
17 this->pin_->setup();
18
19 this->store_.buffer = new uint16_t[MAX_BUFFER_SIZE];
20 this->store_.pin = this->pin_->to_isr();
21 this->store_.reset();
22
24}
26 ESP_LOGCONFIG(TAG, "Tx20:");
27
28 LOG_SENSOR(" ", "Wind speed:", this->wind_speed_sensor_);
29 LOG_SENSOR(" ", "Wind direction degrees:", this->wind_direction_degrees_sensor_);
30
31 LOG_PIN(" Pin: ", this->pin_);
32}
34 if (this->store_.tx20_available) {
35 this->decode_and_publish_();
36 this->store_.reset();
37 }
38}
39
41
43 ESP_LOGVV(TAG, "Decode Tx20");
44
45 std::array<bool, MAX_BUFFER_SIZE> bit_buffer{};
46 size_t bit_pos = 0;
47 bool current_bit = true;
48 // Cap at MAX_BUFFER_SIZE - 1 to prevent out-of-bounds access (buffer_index can exceed MAX_BUFFER_SIZE in ISR)
49 const int max_buffer_index =
50 std::min(static_cast<int>(this->store_.buffer_index), static_cast<int>(MAX_BUFFER_SIZE - 1));
51
52 for (int i = 1; i <= max_buffer_index; i++) {
53 uint8_t repeat = this->store_.buffer[i] / TX20_BIT_TIME;
54 // ignore segments at the end that were too short
55 for (uint8_t j = 0; j < repeat && bit_pos < MAX_BUFFER_SIZE; j++) {
56 bit_buffer[bit_pos++] = current_bit;
57 }
58 current_bit = !current_bit;
59 }
60 current_bit = !current_bit;
61 size_t bits_before_padding = bit_pos;
62 while (bit_pos < MAX_BUFFER_SIZE) {
63 bit_buffer[bit_pos++] = current_bit;
64 }
65
66 uint8_t tx20_sa = 0;
67 uint8_t tx20_sb = 0;
68 uint8_t tx20_sd = 0;
69 uint8_t tx20_se = 0;
70 uint16_t tx20_sc = 0;
71 uint16_t tx20_sf = 0;
72 uint8_t tx20_wind_direction = 0;
73 float tx20_wind_speed_kmh = 0;
74 uint8_t bit_count = 0;
75
76 for (int i = 41; i > 0; i--) {
77 uint8_t bit = bit_buffer.at(bit_count);
78 bit_count++;
79 if (i > 41 - 5) {
80 // start, inverted
81 tx20_sa = (tx20_sa << 1) | (bit ^ 1);
82 } else if (i > 41 - 5 - 4) {
83 // wind dir, inverted
84 tx20_sb = tx20_sb >> 1 | ((bit ^ 1) << 3);
85 } else if (i > 41 - 5 - 4 - 12) {
86 // windspeed, inverted
87 tx20_sc = tx20_sc >> 1 | ((bit ^ 1) << 11);
88 } else if (i > 41 - 5 - 4 - 12 - 4) {
89 // checksum, inverted
90 tx20_sd = tx20_sd >> 1 | ((bit ^ 1) << 3);
91 } else if (i > 41 - 5 - 4 - 12 - 4 - 4) {
92 // wind dir
93 tx20_se = tx20_se >> 1 | (bit << 3);
94 } else {
95 // windspeed
96 tx20_sf = tx20_sf >> 1 | (bit << 11);
97 }
98 }
99
100 uint8_t chk = (tx20_sb + (tx20_sc & 0xf) + ((tx20_sc >> 4) & 0xf) + ((tx20_sc >> 8) & 0xf));
101 chk &= 0xf;
102 bool value_set = false;
103 // checks:
104 // 1. Check that the start frame is 00100 (0x04)
105 // 2. Check received checksum matches calculated checksum
106 // 3. Check that Wind Direction matches Wind Direction (Inverted)
107 // 4. Check that Wind Speed matches Wind Speed (Inverted)
108#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
109 // Build debug strings from completed data
110 char debug_buf[320]; // buffer values: max 40 entries * 7 chars each
111 size_t debug_pos = 0;
112 for (int i = 1; i <= max_buffer_index; i++) {
113 debug_pos = buf_append_printf(debug_buf, sizeof(debug_buf), debug_pos, "%u, ", this->store_.buffer[i]);
114 }
115 if (bits_before_padding < MAX_BUFFER_SIZE) {
116 buf_append_printf(debug_buf, sizeof(debug_buf), debug_pos, "%zu, ", MAX_BUFFER_SIZE - bits_before_padding);
117 }
118 char bits_buf[MAX_BUFFER_SIZE + 1];
119 for (size_t i = 0; i < MAX_BUFFER_SIZE; i++) {
120 bits_buf[i] = bit_buffer[i] ? '1' : '0';
121 }
122 bits_buf[MAX_BUFFER_SIZE] = '\0';
123 ESP_LOGVV(TAG, "BUFFER %s", debug_buf);
124 ESP_LOGVV(TAG, "Decoded bits %s", bits_buf);
125#endif
126
127 if (tx20_sa == 4) {
128 if (chk == tx20_sd) {
129 if (tx20_sf == tx20_sc) {
130 tx20_wind_speed_kmh = float(tx20_sc) * 0.36f;
131 ESP_LOGV(TAG, "WindSpeed %f", tx20_wind_speed_kmh);
132 if (this->wind_speed_sensor_ != nullptr)
133 this->wind_speed_sensor_->publish_state(tx20_wind_speed_kmh);
134 value_set = true;
135 }
136 if (tx20_se == tx20_sb) {
137 tx20_wind_direction = tx20_se;
138 if (tx20_wind_direction < 16) {
139 wind_cardinal_direction_ = DIRECTIONS[tx20_wind_direction];
140 }
141 ESP_LOGV(TAG, "WindDirection %d", tx20_wind_direction);
142 if (this->wind_direction_degrees_sensor_ != nullptr)
143 this->wind_direction_degrees_sensor_->publish_state(float(tx20_wind_direction) * 22.5f);
144 value_set = true;
145 }
146 if (!value_set) {
147 ESP_LOGW(TAG, "No value set!");
148 }
149 } else {
150 ESP_LOGW(TAG, "Checksum wrong!");
151 }
152 } else {
153 ESP_LOGW(TAG, "Start wrong!");
154 }
155}
156
158 arg->pin_state = arg->pin.digital_read();
159 const uint32_t now = micros();
160 if (!arg->start_time) {
161 // only detect a start if the bit is high
162 if (!arg->pin_state) {
163 return;
164 }
165 arg->buffer[arg->buffer_index] = 1;
166 arg->start_time = now;
167 arg->buffer_index += 1;
168 return;
169 }
170 const uint32_t delay = now - arg->start_time;
171 const uint8_t index = arg->buffer_index;
172
173 // first delay has to be ~2400
174 if (index == 1 && (delay > 3000 || delay < 2400)) {
175 arg->reset();
176 return;
177 }
178 // second delay has to be ~1200
179 if (index == 2 && (delay > 1500 || delay < 1200)) {
180 arg->reset();
181 return;
182 }
183 // third delay has to be ~2400
184 if (index == 3 && (delay > 3000 || delay < 2400)) {
185 arg->reset();
186 return;
187 }
188
189 if (arg->tx20_available || ((arg->spent_time + delay > TX20_MAX_TIME) && arg->start_time)) {
190 arg->tx20_available = true;
191 return;
192 }
193 if (index < MAX_BUFFER_SIZE) {
194 arg->buffer[index] = delay;
195 }
196 arg->spent_time += delay;
197 arg->start_time = now;
198 arg->buffer_index += 1;
199}
201 tx20_available = false;
202 buffer_index = 0;
203 spent_time = 0;
204 // rearm it!
205 start_time = 0;
206}
207
208} // namespace esphome::tx20
virtual void setup()=0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:107
virtual ISRInternalGPIOPin to_isr() const =0
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
sensor::Sensor * wind_direction_degrees_sensor_
Definition tx20.h:45
std::string get_wind_cardinal_direction() const
Get the textual representation of the wind direction ('N', 'SSE', ..).
Definition tx20.cpp:40
std::string wind_cardinal_direction_
Definition tx20.h:42
void loop() override
Definition tx20.cpp:33
void setup() override
Definition tx20.cpp:16
Tx20ComponentStore store_
Definition tx20.h:46
sensor::Sensor * wind_speed_sensor_
Definition tx20.h:44
InternalGPIOPin * pin_
Definition tx20.h:43
void dump_config() override
Definition tx20.cpp:25
@ INTERRUPT_ANY_EDGE
Definition gpio.h:52
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
void HOT delay(uint32_t ms)
Definition hal.cpp:85
static void uint32_t
Store data in a class that doesn't use multiple-inheritance (vtables in flash)
Definition tx20.h:10
ISRInternalGPIOPin pin
Definition tx20.h:17
volatile uint32_t start_time
Definition tx20.h:12
volatile uint32_t spent_time
Definition tx20.h:14
static void gpio_intr(Tx20ComponentStore *arg)
Definition tx20.cpp:157
volatile uint16_t * buffer
Definition tx20.h:11
volatile uint8_t buffer_index
Definition tx20.h:13
volatile bool tx20_available
Definition tx20.h:15