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