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