ESPHome 2026.11.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
8ESPHOME_LOG_TAG(TAG, "kamstrup_kmp");
9
11 ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
12 LOG_UPDATE_INTERVAL(this);
13
14 LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_);
15 LOG_SENSOR(" ", "Power", this->power_sensor_);
16 LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_);
17 LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_);
18 LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_);
19 LOG_SENSOR(" ", "Flow", this->flow_sensor_);
20 LOG_SENSOR(" ", "Volume", this->volume_sensor_);
21
22 for (size_t i = 0; i < this->custom_sensors_.size(); i++) {
23 LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
24 ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
25 }
26}
27
29 if (this->heat_energy_sensor_ != nullptr) {
30 this->command_queue_.push(CMD_HEAT_ENERGY);
31 }
32
33 if (this->power_sensor_ != nullptr) {
34 this->command_queue_.push(CMD_POWER);
35 }
36
37 if (this->temp1_sensor_ != nullptr) {
38 this->command_queue_.push(CMD_TEMP1);
39 }
40
41 if (this->temp2_sensor_ != nullptr) {
42 this->command_queue_.push(CMD_TEMP2);
43 }
44
45 if (this->temp_diff_sensor_ != nullptr) {
46 this->command_queue_.push(CMD_TEMP_DIFF);
47 }
48
49 if (this->flow_sensor_ != nullptr) {
50 this->command_queue_.push(CMD_FLOW);
51 }
52
53 if (this->volume_sensor_ != nullptr) {
54 this->command_queue_.push(CMD_VOLUME);
55 }
56
57 for (uint16_t custom_command : this->custom_commands_) {
58 this->command_queue_.push(custom_command);
59 }
60}
61
63 if (!this->command_queue_.empty()) {
64 uint16_t command = this->command_queue_.front();
65 this->send_command_(command);
66 this->command_queue_.pop();
67 }
68}
69
71 uint32_t msg_len = 5;
72 uint8_t msg[msg_len];
73
74 msg[0] = 0x3F;
75 msg[1] = 0x10;
76 msg[2] = 0x01;
77 msg[3] = command >> 8;
78 msg[4] = command & 0xFF;
79
80 this->discard_available();
81 this->send_message_(msg, msg_len);
82 this->read_command_(command);
83}
84
85void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) {
86 int buffer_len = msg_len + 2;
87 uint8_t buffer[buffer_len];
88
89 // Prepare the basic message and appand CRC
90 for (int i = 0; i < msg_len; i++) {
91 buffer[i] = msg[i];
92 }
93
94 uint16_t crc = crc16be(buffer, buffer_len - 2);
95 buffer[buffer_len - 2] = crc >> 8;
96 buffer[buffer_len - 1] = crc & 0xFF;
97
98 // Prepare actual TX message
99 uint8_t tx_msg[20];
100 int tx_msg_len = 1;
101 tx_msg[0] = 0x80; // prefix
102
103 for (int i = 0; i < buffer_len; i++) {
104 if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) {
105 if (tx_msg_len + 2 >= static_cast<int>(sizeof(tx_msg))) {
106 ESP_LOGE(TAG, "TX message overflow");
107 return;
108 }
109 tx_msg[tx_msg_len++] = 0x1b;
110 tx_msg[tx_msg_len++] = buffer[i] ^ 0xff;
111 } else {
112 if (tx_msg_len + 1 >= static_cast<int>(sizeof(tx_msg))) {
113 ESP_LOGE(TAG, "TX message overflow");
114 return;
115 }
116 tx_msg[tx_msg_len++] = buffer[i];
117 }
118 }
119
120 tx_msg[tx_msg_len++] = 0x0D; // EOM
121
122 this->write_array(tx_msg, tx_msg_len);
123}
124
126 uint8_t buffer[20] = {0};
127 size_t buffer_len = 0;
128 int data;
129 int timeout = 250; // ms
130
131 // Read the data from the UART
132 while (timeout > 0 && buffer_len < sizeof(buffer)) {
133 if (this->available()) {
134 data = this->read();
135 if (data > -1) {
136 if (data == 0x40) { // start of message
137 buffer_len = 0;
138 } else if (buffer_len == 0) {
139 // Ignore request echoes until the response starts.
140 continue;
141 }
142 buffer[buffer_len++] = (uint8_t) data;
143 if (data == 0x0D) {
144 break;
145 }
146 } else {
147 ESP_LOGE(TAG, "Error while reading from UART");
148 }
149 } else {
150 delay(1);
151 timeout--;
152 }
153 }
154
155 if (timeout == 0 || buffer_len == 0) {
156 ESP_LOGE(TAG, "Request timed out");
157 return;
158 }
159
160 // Only bytes from the 0x40 start byte on are stored, so just the end of the message needs checking
161 if (buffer[buffer_len - 1] != 0x0D) {
162 ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]);
163 return;
164 }
165
166 // Decode
167 uint8_t msg[20] = {0};
168 int msg_len = 0;
169 for (size_t i = 1; i < buffer_len - 1 && msg_len < (int) sizeof(msg); i++) {
170 if (buffer[i] == 0x1B) {
171 msg[msg_len++] = buffer[i + 1] ^ 0xFF;
172 i++;
173 } else {
174 msg[msg_len++] = buffer[i];
175 }
176 }
177
178 // Validate CRC; a frame shorter than the CRC itself has nothing to check
179 if (msg_len < 2 || crc16be(msg, msg_len - 2) != encode_uint16(msg[msg_len - 2], msg[msg_len - 1])) {
180 ESP_LOGE(TAG, "Received invalid message (CRC mismatch)");
181 return;
182 }
183
184 // All seems good. Now parse the message
185 this->parse_command_message_(command, msg, msg_len);
186}
187
188void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) {
189 // Validate the message
190 if (msg_len < 8) {
191 ESP_LOGE(TAG, "Received invalid message (message too small)");
192 return;
193 }
194
195 if (msg[0] != 0x3F || msg[1] != 0x10) {
196 ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]);
197 return;
198 }
199
200 uint16_t recv_command = msg[2] << 8 | msg[3];
201 if (recv_command != command) {
202 ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)",
203 recv_command, command);
204 return;
205 }
206
207 uint8_t unit_idx = msg[4];
208 uint8_t mantissa_range = msg[5];
209
210 if (mantissa_range > 4 || msg_len < 7 + mantissa_range) {
211 ESP_LOGE(TAG, "Received invalid message (mantissa size %d, msg_len %d)", mantissa_range, msg_len);
212 return;
213 }
214
215 // Calculate exponent
216 int8_t exp_val = msg[6] & 0x3F;
217 if (msg[6] & 0x40) {
218 exp_val = -exp_val;
219 }
220 float exponent = pow10_int(exp_val);
221 if (msg[6] & 0x80) {
222 exponent = -exponent;
223 }
224
225 // Calculate mantissa
226 uint32_t mantissa = 0;
227 for (int i = 0; i < mantissa_range; i++) {
228 mantissa <<= 8;
229 mantissa |= msg[i + 7];
230 }
231
232 // Calculate the actual value
233 float value = mantissa * exponent;
234
235 // Set sensor value
236 this->set_sensor_value_(command, value, unit_idx);
237}
238
239void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
240 const char *unit = unit_idx < sizeof(UNITS) / sizeof(UNITS[0]) ? UNITS[unit_idx] : "";
241
242 // Standard sensors
243 if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
245 } else if (command == CMD_POWER && this->power_sensor_ != nullptr) {
246 this->power_sensor_->publish_state(value);
247 } else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) {
248 this->temp1_sensor_->publish_state(value);
249 } else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) {
250 this->temp2_sensor_->publish_state(value);
251 } else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) {
252 this->temp_diff_sensor_->publish_state(value);
253 } else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) {
254 this->flow_sensor_->publish_state(value);
255 } else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) {
256 this->volume_sensor_->publish_state(value);
257 }
258
259 // Custom sensors
260 for (size_t i = 0; i < this->custom_commands_.size(); i++) {
261 if (command == this->custom_commands_[i]) {
262 this->custom_sensors_[i]->publish_state(value);
263 }
264 }
265
266 ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
267}
268
269} // namespace esphome::kamstrup_kmp
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:65
void discard_available()
Read and drop every byte waiting in the receive buffer.
Definition uart.cpp:36
void write_array(const uint8_t *data, size_t len)
Definition uart.h:30
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "kamstrup_kmp")
uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, bool refin, bool refout)
Definition helpers.cpp:135
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:998
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:874
static void uint32_t