ESPHome  2024.10.2
hub.cpp
Go to the documentation of this file.
1 #include "hub.h"
2 #include "esphome/core/helpers.h"
3 
4 #include <string>
5 
6 namespace esphome {
7 namespace opentherm {
8 
9 static const char *const TAG = "opentherm";
10 
12  OpenthermData data;
13  data.type = 0;
14  data.id = 0;
15  data.valueHB = 0;
16  data.valueLB = 0;
17 
18  // First, handle the status request. This requires special logic, because we
19  // wouldn't want to inadvertently disable domestic hot water, for example.
20  // It is also included in the macro-generated code below, but that will
21  // never be executed, because we short-circuit it here.
22  if (request_id == MessageId::STATUS) {
23  bool const ch_enabled = this->ch_enable;
24  bool dhw_enabled = this->dhw_enable;
25  bool cooling_enabled = this->cooling_enable;
26  bool otc_enabled = this->otc_active;
27  bool ch2_enabled = this->ch2_active;
28 
30  data.id = MessageId::STATUS;
31  data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4);
32 
33 // Disable incomplete switch statement warnings, because the cases in each
34 // switch are generated based on the configured sensors and inputs.
35 #pragma GCC diagnostic push
36 #pragma GCC diagnostic ignored "-Wswitch"
37 
38  // TODO: This is a placeholder for an auto-generated switch statement which builds request structure based on
39  // which sensors are enabled in config.
40 
41 #pragma GCC diagnostic pop
42 
43  return data;
44  }
45  return OpenthermData();
46 }
47 
49 
51  ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id,
52  this->opentherm_->message_id_to_str((MessageId) data.id));
53  ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str());
54 }
55 
57  ESP_LOGD(TAG, "Setting up OpenTherm component");
58  this->opentherm_ = make_unique<OpenTherm>(this->in_pin_, this->out_pin_);
59  if (!this->opentherm_->initialize()) {
60  ESP_LOGE(TAG, "Failed to initialize OpenTherm protocol. See previous log messages for details.");
61  this->mark_failed();
62  return;
63  }
64 
65  // Ensure that there is at least one request, as we are required to
66  // communicate at least once every second. Sending the status request is
67  // good practice anyway.
69 
70  this->current_message_iterator_ = this->initial_messages_.begin();
71 }
72 
73 void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
74 
76  if (this->sync_mode_) {
77  this->sync_loop_();
78  return;
79  }
80 
81  auto cur_time = millis();
82  auto const cur_mode = this->opentherm_->get_mode();
83  switch (cur_mode) {
87  if (!this->check_timings_(cur_time)) {
88  break;
89  }
90  this->last_mode_ = cur_mode;
91  break;
93  if (this->last_mode_ == OperationMode::WRITE) {
95  } else if (this->last_mode_ == OperationMode::READ) {
97  }
98 
99  this->stop_opentherm_();
100  break;
102  this->handle_timeout_error_();
103  this->stop_opentherm_();
104  break;
105  case OperationMode::IDLE:
106  if (this->should_skip_loop_(cur_time)) {
107  break;
108  }
109  this->start_conversation_();
110  break;
111  case OperationMode::SENT:
112  // Message sent, now listen for the response.
113  this->opentherm_->listen();
114  break;
116  this->read_response_();
117  break;
118  }
119 }
120 
122  if (!this->opentherm_->is_idle()) {
123  ESP_LOGE(TAG, "OpenTherm is not idle at the start of the loop");
124  return;
125  }
126 
127  auto cur_time = millis();
128 
129  this->check_timings_(cur_time);
130 
131  if (this->should_skip_loop_(cur_time)) {
132  return;
133  }
134 
135  this->start_conversation_();
136 
137  if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
138  ESP_LOGE(TAG, "Hub timeout triggered during send");
139  this->stop_opentherm_();
140  return;
141  }
142 
143  if (this->opentherm_->is_error()) {
145  this->stop_opentherm_();
146  return;
147  } else if (!this->opentherm_->is_sent()) {
148  ESP_LOGW(TAG, "Unexpected state after sending request: %s",
149  this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
150  this->stop_opentherm_();
151  return;
152  }
153 
154  // Listen for the response
155  this->opentherm_->listen();
156  if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
157  ESP_LOGE(TAG, "Hub timeout triggered during receive");
158  this->stop_opentherm_();
159  return;
160  }
161 
162  if (this->opentherm_->is_timeout()) {
163  this->handle_timeout_error_();
164  this->stop_opentherm_();
165  return;
166  } else if (this->opentherm_->is_protocol_error()) {
168  this->stop_opentherm_();
169  return;
170  } else if (!this->opentherm_->has_message()) {
171  ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
172  this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
173  this->stop_opentherm_();
174  return;
175  }
176 
177  this->read_response_();
178 }
179 
180 bool OpenthermHub::check_timings_(uint32_t cur_time) {
181  if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
182  ESP_LOGW(TAG,
183  "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
184  "components that might slow the loop down.",
185  (int) (cur_time - this->last_conversation_start_));
186  this->stop_opentherm_();
187  return false;
188  }
189 
190  return true;
191 }
192 
193 bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
194  if (this->last_conversation_end_ > 0 && (cur_time - this->last_conversation_end_) < 100) {
195  ESP_LOGV(TAG, "Less than 100 ms elapsed since last convo, skipping this iteration");
196  return true;
197  }
198 
199  return false;
200 }
201 
203  if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) {
204  this->sending_initial_ = false;
205  this->current_message_iterator_ = this->repeating_messages_.begin();
206  } else if (this->current_message_iterator_ == this->repeating_messages_.end()) {
207  this->current_message_iterator_ = this->repeating_messages_.begin();
208  }
209 
210  auto request = this->build_request_(*this->current_message_iterator_);
211 
212  ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
213  this->opentherm_->message_id_to_str((MessageId) request.id));
214  ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(request).c_str());
215  // Send the request
217  this->opentherm_->send(request);
218 }
219 
221  OpenthermData response;
222  if (!this->opentherm_->get_message(response)) {
223  ESP_LOGW(TAG, "Couldn't get the response, but flags indicated success. This is a bug.");
224  this->stop_opentherm_();
225  return;
226  }
227 
228  this->stop_opentherm_();
229 
230  this->process_response(response);
231 
233 }
234 
236  this->opentherm_->stop();
237  this->last_conversation_end_ = millis();
238 }
239 
241  ESP_LOGW(TAG, "Error while sending request: %s",
242  this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
243  ESP_LOGW(TAG, "%s", this->opentherm_->debug_data(this->last_request_).c_str());
244 }
245 
247  OpenThermError error;
248  this->opentherm_->get_protocol_error(error);
249  ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", this->opentherm_->debug_error(error).c_str());
250 }
251 
253  ESP_LOGW(TAG, "Receive response timed out at a protocol level");
254  this->stop_opentherm_();
255 }
256 
257 #define ID(x) x
258 #define SHOW2(x) #x
259 #define SHOW(x) SHOW2(x)
260 
262  ESP_LOGCONFIG(TAG, "OpenTherm:");
263  LOG_PIN(" In: ", this->in_pin_);
264  LOG_PIN(" Out: ", this->out_pin_);
265  ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_);
266  ESP_LOGCONFIG(TAG, " Initial requests:");
267  for (auto type : this->initial_messages_) {
268  ESP_LOGCONFIG(TAG, " - %d", type);
269  }
270  ESP_LOGCONFIG(TAG, " Repeating requests:");
271  for (auto type : this->repeating_messages_) {
272  ESP_LOGCONFIG(TAG, " - %d", type);
273  }
274 }
275 
276 } // namespace opentherm
277 } // namespace esphome
bool should_skip_loop_(uint32_t cur_time) const
Definition: hub.cpp:193
bool check_timings_(uint32_t cur_time)
Definition: hub.cpp:180
uint32_t last_conversation_start_
Definition: hub.h:36
std::unordered_set< MessageId > initial_messages_
Definition: hub.h:27
void process_response(OpenthermData &data)
Definition: hub.cpp:50
std::unordered_set< MessageId > repeating_messages_
Definition: hub.h:30
timeout while waiting to receive bytes
Definition: i2c_bus.h:16
InternalGPIOPin * in_pin_
Definition: hub.h:22
std::unique_ptr< OpenTherm > opentherm_
Definition: hub.h:24
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
OperationMode last_mode_
Definition: hub.h:38
OpenthermData build_request_(MessageId request_id)
Definition: hub.cpp:11
bool spin_wait_(uint32_t timeout, F func)
Definition: hub.h:58
uint32_t last_conversation_end_
Definition: hub.h:37
InternalGPIOPin * out_pin_
Definition: hub.h:22
uint8_t type
std::unordered_set< MessageId >::const_iterator current_message_iterator_
Definition: hub.h:34
void dump_config() override
Definition: hub.cpp:261
void loop() override
Definition: hub.cpp:75
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void setup() override
Definition: hub.cpp:56
void add_repeating_message(MessageId message_id)
Definition: hub.h:87
Structure to hold Opentherm data packet content.
Definition: opentherm.h:144
void on_shutdown() override
Definition: hub.cpp:73