ESPHome  2024.11.0
kamstrup_kmp.cpp
Go to the documentation of this file.
1 #include "kamstrup_kmp.h"
2 
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace kamstrup_kmp {
7 
8 static const char *const TAG = "kamstrup_kmp";
9 
11  ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
12  if (this->is_failed()) {
13  ESP_LOGE(TAG, "Communication with Kamstrup meter failed!");
14  }
15  LOG_UPDATE_INTERVAL(this);
16 
17  LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_);
18  LOG_SENSOR(" ", "Power", this->power_sensor_);
19  LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_);
20  LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_);
21  LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_);
22  LOG_SENSOR(" ", "Flow", this->flow_sensor_);
23  LOG_SENSOR(" ", "Volume", this->volume_sensor_);
24 
25  for (int i = 0; i < this->custom_sensors_.size(); i++) {
26  LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
27  ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
28  }
29 
31 }
32 
34 
36  if (this->heat_energy_sensor_ != nullptr) {
37  this->command_queue_.push(CMD_HEAT_ENERGY);
38  }
39 
40  if (this->power_sensor_ != nullptr) {
41  this->command_queue_.push(CMD_POWER);
42  }
43 
44  if (this->temp1_sensor_ != nullptr) {
45  this->command_queue_.push(CMD_TEMP1);
46  }
47 
48  if (this->temp2_sensor_ != nullptr) {
49  this->command_queue_.push(CMD_TEMP2);
50  }
51 
52  if (this->temp_diff_sensor_ != nullptr) {
53  this->command_queue_.push(CMD_TEMP_DIFF);
54  }
55 
56  if (this->flow_sensor_ != nullptr) {
57  this->command_queue_.push(CMD_FLOW);
58  }
59 
60  if (this->volume_sensor_ != nullptr) {
61  this->command_queue_.push(CMD_VOLUME);
62  }
63 
64  for (uint16_t custom_command : this->custom_commands_) {
65  this->command_queue_.push(custom_command);
66  }
67 }
68 
70  if (!this->command_queue_.empty()) {
71  uint16_t command = this->command_queue_.front();
72  this->send_command_(command);
73  this->command_queue_.pop();
74  }
75 }
76 
77 void KamstrupKMPComponent::send_command_(uint16_t command) {
78  uint32_t msg_len = 5;
79  uint8_t msg[msg_len];
80 
81  msg[0] = 0x3F;
82  msg[1] = 0x10;
83  msg[2] = 0x01;
84  msg[3] = command >> 8;
85  msg[4] = command & 0xFF;
86 
87  this->clear_uart_rx_buffer_();
88  this->send_message_(msg, msg_len);
89  this->read_command_(command);
90 }
91 
92 void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) {
93  int buffer_len = msg_len + 2;
94  uint8_t buffer[buffer_len];
95 
96  // Prepare the basic message and appand CRC
97  for (int i = 0; i < msg_len; i++) {
98  buffer[i] = msg[i];
99  }
100 
101  buffer[buffer_len - 2] = 0;
102  buffer[buffer_len - 1] = 0;
103 
104  uint16_t crc = crc16_ccitt(buffer, buffer_len);
105  buffer[buffer_len - 2] = crc >> 8;
106  buffer[buffer_len - 1] = crc & 0xFF;
107 
108  // Prepare actual TX message
109  uint8_t tx_msg[20];
110  int tx_msg_len = 1;
111  tx_msg[0] = 0x80; // prefix
112 
113  for (int i = 0; i < buffer_len; i++) {
114  if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) {
115  tx_msg[tx_msg_len++] = 0x1b;
116  tx_msg[tx_msg_len++] = buffer[i] ^ 0xff;
117  } else {
118  tx_msg[tx_msg_len++] = buffer[i];
119  }
120  }
121 
122  tx_msg[tx_msg_len++] = 0x0D; // EOM
123 
124  this->write_array(tx_msg, tx_msg_len);
125 }
126 
128  uint8_t tmp;
129  while (this->available()) {
130  this->read_byte(&tmp);
131  }
132 }
133 
134 void KamstrupKMPComponent::read_command_(uint16_t command) {
135  uint8_t buffer[20] = {0};
136  int buffer_len = 0;
137  int data;
138  int timeout = 250; // ms
139 
140  // Read the data from the UART
141  while (timeout > 0) {
142  if (this->available()) {
143  data = this->read();
144  if (data > -1) {
145  if (data == 0x40) { // start of message
146  buffer_len = 0;
147  }
148  buffer[buffer_len++] = (uint8_t) data;
149  if (data == 0x0D) {
150  break;
151  }
152  } else {
153  ESP_LOGE(TAG, "Error while reading from UART");
154  }
155  } else {
156  delay(1);
157  timeout--;
158  }
159  }
160 
161  if (timeout == 0 || buffer_len == 0) {
162  ESP_LOGE(TAG, "Request timed out");
163  return;
164  }
165 
166  // Validate message (prefix and suffix)
167  if (buffer[0] != 0x40) {
168  ESP_LOGE(TAG, "Received invalid message (prefix mismatch received 0x%02X, expected 0x40)", buffer[0]);
169  return;
170  }
171 
172  if (buffer[buffer_len - 1] != 0x0D) {
173  ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]);
174  return;
175  }
176 
177  // Decode
178  uint8_t msg[20] = {0};
179  int msg_len = 0;
180  for (int i = 1; i < buffer_len - 1; i++) {
181  if (buffer[i] == 0x1B) {
182  msg[msg_len++] = buffer[i + 1] ^ 0xFF;
183  i++;
184  } else {
185  msg[msg_len++] = buffer[i];
186  }
187  }
188 
189  // Validate CRC
190  if (crc16_ccitt(msg, msg_len)) {
191  ESP_LOGE(TAG, "Received invalid message (CRC mismatch)");
192  return;
193  }
194 
195  // All seems good. Now parse the message
196  this->parse_command_message_(command, msg, msg_len);
197 }
198 
199 void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) {
200  // Validate the message
201  if (msg_len < 8) {
202  ESP_LOGE(TAG, "Received invalid message (message too small)");
203  return;
204  }
205 
206  if (msg[0] != 0x3F || msg[1] != 0x10) {
207  ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]);
208  return;
209  }
210 
211  uint16_t recv_command = msg[2] << 8 | msg[3];
212  if (recv_command != command) {
213  ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)",
214  recv_command, command);
215  return;
216  }
217 
218  uint8_t unit_idx = msg[4];
219  uint8_t mantissa_range = msg[5];
220 
221  if (mantissa_range > 4) {
222  ESP_LOGE(TAG, "Received invalid message (mantissa size too large %d, expected 4)", mantissa_range);
223  return;
224  }
225 
226  // Calculate exponent
227  float exponent = msg[6] & 0x3F;
228  if (msg[6] & 0x40) {
229  exponent = -exponent;
230  }
231  exponent = powf(10, exponent);
232  if (msg[6] & 0x80) {
233  exponent = -exponent;
234  }
235 
236  // Calculate mantissa
237  uint32_t mantissa = 0;
238  for (int i = 0; i < mantissa_range; i++) {
239  mantissa <<= 8;
240  mantissa |= msg[i + 7];
241  }
242 
243  // Calculate the actual value
244  float value = mantissa * exponent;
245 
246  // Set sensor value
247  this->set_sensor_value_(command, value, unit_idx);
248 }
249 
250 void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
251  const char *unit = UNITS[unit_idx];
252 
253  // Standard sensors
254  if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
255  this->heat_energy_sensor_->publish_state(value);
256  } else if (command == CMD_POWER && this->power_sensor_ != nullptr) {
257  this->power_sensor_->publish_state(value);
258  } else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) {
259  this->temp1_sensor_->publish_state(value);
260  } else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) {
261  this->temp2_sensor_->publish_state(value);
262  } else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) {
263  this->temp_diff_sensor_->publish_state(value);
264  } else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) {
265  this->flow_sensor_->publish_state(value);
266  } else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) {
267  this->volume_sensor_->publish_state(value);
268  }
269 
270  // Custom sensors
271  for (int i = 0; i < this->custom_commands_.size(); i++) {
272  if (command == this->custom_commands_[i]) {
273  this->custom_sensors_[i]->publish_state(value);
274  }
275  }
276 
277  ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
278 }
279 
280 uint16_t crc16_ccitt(const uint8_t *buffer, int len) {
281  uint32_t poly = 0x1021;
282  uint32_t reg = 0x00;
283  for (int i = 0; i < len; i++) {
284  int mask = 0x80;
285  while (mask > 0) {
286  reg <<= 1;
287  if (buffer[i] & mask) {
288  reg |= 1;
289  }
290  mask >>= 1;
291  if (reg & 0x10000) {
292  reg &= 0xffff;
293  reg ^= poly;
294  }
295  }
296  }
297  return (uint16_t) reg;
298 }
299 
300 } // namespace kamstrup_kmp
301 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void send_message_(const uint8_t *msg, int msg_len)
bool is_failed() const
Definition: component.cpp:143
void parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len)
void set_sensor_value_(uint16_t command, float value, uint8_t unit_idx)
std::vector< sensor::Sensor * > custom_sensors_
Definition: kamstrup_kmp.h:105
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition: uart.cpp:13
bool read_byte(uint8_t *data)
Definition: uart.h:29
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
std::string size_t len
Definition: helpers.h:293
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
uint16_t crc16_ccitt(const uint8_t *buffer, int len)
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26