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