ESPHome 2025.5.0
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, "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
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
88 this->send_message_(msg, msg_len);
89 this->read_command_(command);
90}
91
92void 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
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
199void 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
250void 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) {
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
280uint16_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
send_message_t send_message_
bool is_failed() const
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:39
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 write_array(const uint8_t *data, size_t len)
Definition uart.h:21
uint16_t crc16_ccitt(const uint8_t *buffer, int len)
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:301
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28