mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 16:37:52 +01:00
Added Kamstrup Multical 40x component (#4200)
Co-authored-by: Chris Feenstra <chris@cfeenstra.nl> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: cfeenstra1024 <git@cfeenstra.nl>
This commit is contained in:
parent
b34b10888b
commit
64a47f840e
@ -173,6 +173,7 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
|
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE, CONF_VOLUME
|
||||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
@ -19,7 +19,6 @@ DFPlayerIsPlayingCondition = dfplayer_ns.class_(
|
||||
MULTI_CONF = True
|
||||
CONF_FOLDER = "folder"
|
||||
CONF_LOOP = "loop"
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_EQ_PRESET = "eq_preset"
|
||||
CONF_ON_FINISHED_PLAYBACK = "on_finished_playback"
|
||||
|
||||
|
@ -1,7 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_COMMAND,
|
||||
CONF_ID,
|
||||
CONF_DURATION,
|
||||
CONF_VOLUME,
|
||||
)
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
@ -9,7 +15,6 @@ CODEOWNERS = ["@carlos-sarmiento"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
|
||||
|
||||
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
|
||||
|
0
esphome/components/kamstrup_kmp/__init__.py
Normal file
0
esphome/components/kamstrup_kmp/__init__.py
Normal file
301
esphome/components/kamstrup_kmp/kamstrup_kmp.cpp
Normal file
301
esphome/components/kamstrup_kmp/kamstrup_kmp.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
#include "kamstrup_kmp.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kamstrup_kmp {
|
||||
|
||||
static const char *const TAG = "kamstrup_kmp";
|
||||
|
||||
void KamstrupKMPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with Kamstrup meter failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Heat Energy", this->heat_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temp1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temp2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Difference", this->temp_diff_sensor_);
|
||||
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
|
||||
LOG_SENSOR(" ", "Volume", this->volume_sensor_);
|
||||
|
||||
for (int i = 0; i < this->custom_sensors_.size(); i++) {
|
||||
LOG_SENSOR(" ", "Custom Sensor", this->custom_sensors_[i]);
|
||||
ESP_LOGCONFIG(TAG, " Command: 0x%04X", this->custom_commands_[i]);
|
||||
}
|
||||
|
||||
this->check_uart_settings(1200, 2, uart::UART_CONFIG_PARITY_NONE, 8);
|
||||
}
|
||||
|
||||
float KamstrupKMPComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void KamstrupKMPComponent::update() {
|
||||
if (this->heat_energy_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_HEAT_ENERGY);
|
||||
}
|
||||
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_POWER);
|
||||
}
|
||||
|
||||
if (this->temp1_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP1);
|
||||
}
|
||||
|
||||
if (this->temp2_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP2);
|
||||
}
|
||||
|
||||
if (this->temp_diff_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_TEMP_DIFF);
|
||||
}
|
||||
|
||||
if (this->flow_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_FLOW);
|
||||
}
|
||||
|
||||
if (this->volume_sensor_ != nullptr) {
|
||||
this->command_queue_.push(CMD_VOLUME);
|
||||
}
|
||||
|
||||
for (uint16_t custom_command : this->custom_commands_) {
|
||||
this->command_queue_.push(custom_command);
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::loop() {
|
||||
if (!this->command_queue_.empty()) {
|
||||
uint16_t command = this->command_queue_.front();
|
||||
this->send_command_(command);
|
||||
this->command_queue_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::send_command_(uint16_t command) {
|
||||
uint32_t msg_len = 5;
|
||||
uint8_t msg[msg_len];
|
||||
|
||||
msg[0] = 0x3F;
|
||||
msg[1] = 0x10;
|
||||
msg[2] = 0x01;
|
||||
msg[3] = command >> 8;
|
||||
msg[4] = command & 0xFF;
|
||||
|
||||
this->clear_uart_rx_buffer_();
|
||||
this->send_message_(msg, msg_len);
|
||||
this->read_command_(command);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::send_message_(const uint8_t *msg, int msg_len) {
|
||||
int buffer_len = msg_len + 2;
|
||||
uint8_t buffer[buffer_len];
|
||||
|
||||
// Prepare the basic message and appand CRC
|
||||
for (int i = 0; i < msg_len; i++) {
|
||||
buffer[i] = msg[i];
|
||||
}
|
||||
|
||||
buffer[buffer_len - 2] = 0;
|
||||
buffer[buffer_len - 1] = 0;
|
||||
|
||||
uint16_t crc = crc16_ccitt(buffer, buffer_len);
|
||||
buffer[buffer_len - 2] = crc >> 8;
|
||||
buffer[buffer_len - 1] = crc & 0xFF;
|
||||
|
||||
// Prepare actual TX message
|
||||
uint8_t tx_msg[20];
|
||||
int tx_msg_len = 1;
|
||||
tx_msg[0] = 0x80; // prefix
|
||||
|
||||
for (int i = 0; i < buffer_len; i++) {
|
||||
if (buffer[i] == 0x06 || buffer[i] == 0x0d || buffer[i] == 0x1b || buffer[i] == 0x40 || buffer[i] == 0x80) {
|
||||
tx_msg[tx_msg_len++] = 0x1b;
|
||||
tx_msg[tx_msg_len++] = buffer[i] ^ 0xff;
|
||||
} else {
|
||||
tx_msg[tx_msg_len++] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
tx_msg[tx_msg_len++] = 0x0D; // EOM
|
||||
|
||||
this->write_array(tx_msg, tx_msg_len);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::clear_uart_rx_buffer_() {
|
||||
uint8_t tmp;
|
||||
while (this->available()) {
|
||||
this->read_byte(&tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::read_command_(uint16_t command) {
|
||||
uint8_t buffer[20] = {0};
|
||||
int buffer_len = 0;
|
||||
int data;
|
||||
int timeout = 250; // ms
|
||||
|
||||
// Read the data from the UART
|
||||
while (timeout > 0) {
|
||||
if (this->available()) {
|
||||
data = this->read();
|
||||
if (data > -1) {
|
||||
if (data == 0x40) { // start of message
|
||||
buffer_len = 0;
|
||||
}
|
||||
buffer[buffer_len++] = (uint8_t) data;
|
||||
if (data == 0x0D) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error while reading from UART");
|
||||
}
|
||||
} else {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout == 0 || buffer_len == 0) {
|
||||
ESP_LOGE(TAG, "Request timed out");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate message (prefix and suffix)
|
||||
if (buffer[0] != 0x40) {
|
||||
ESP_LOGE(TAG, "Received invalid message (prefix mismatch received 0x%02X, expected 0x40)", buffer[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer[buffer_len - 1] != 0x0D) {
|
||||
ESP_LOGE(TAG, "Received invalid message (EOM mismatch received 0x%02X, expected 0x0D)", buffer[buffer_len - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode
|
||||
uint8_t msg[20] = {0};
|
||||
int msg_len = 0;
|
||||
for (int i = 1; i < buffer_len - 1; i++) {
|
||||
if (buffer[i] == 0x1B) {
|
||||
msg[msg_len++] = buffer[i + 1] ^ 0xFF;
|
||||
i++;
|
||||
} else {
|
||||
msg[msg_len++] = buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Validate CRC
|
||||
if (crc16_ccitt(msg, msg_len)) {
|
||||
ESP_LOGE(TAG, "Received invalid message (CRC mismatch)");
|
||||
return;
|
||||
}
|
||||
|
||||
// All seems good. Now parse the message
|
||||
this->parse_command_message_(command, msg, msg_len);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len) {
|
||||
// Validate the message
|
||||
if (msg_len < 8) {
|
||||
ESP_LOGE(TAG, "Received invalid message (message too small)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg[0] != 0x3F || msg[1] != 0x10) {
|
||||
ESP_LOGE(TAG, "Received invalid message (invalid header received 0x%02X%02X, expected 0x3F10)", msg[0], msg[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t recv_command = msg[2] << 8 | msg[3];
|
||||
if (recv_command != command) {
|
||||
ESP_LOGE(TAG, "Received invalid message (invalid unexpected command received 0x%04X, expected 0x%04X)",
|
||||
recv_command, command);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t unit_idx = msg[4];
|
||||
uint8_t mantissa_range = msg[5];
|
||||
|
||||
if (mantissa_range > 4) {
|
||||
ESP_LOGE(TAG, "Received invalid message (mantissa size too large %d, expected 4)", mantissa_range);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate exponent
|
||||
float exponent = msg[6] & 0x3F;
|
||||
if (msg[6] & 0x40) {
|
||||
exponent = -exponent;
|
||||
}
|
||||
exponent = powf(10, exponent);
|
||||
if (msg[6] & 0x80) {
|
||||
exponent = -exponent;
|
||||
}
|
||||
|
||||
// Calculate mantissa
|
||||
uint32_t mantissa = 0;
|
||||
for (int i = 0; i < mantissa_range; i++) {
|
||||
mantissa <<= 8;
|
||||
mantissa |= msg[i + 7];
|
||||
}
|
||||
|
||||
// Calculate the actual value
|
||||
float value = mantissa * exponent;
|
||||
|
||||
// Set sensor value
|
||||
this->set_sensor_value_(command, value, unit_idx);
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
|
||||
const char *unit = UNITS[unit_idx];
|
||||
|
||||
// Standard sensors
|
||||
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
|
||||
this->heat_energy_sensor_->publish_state(value);
|
||||
} else if (command == CMD_POWER && this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP1 && this->temp1_sensor_ != nullptr) {
|
||||
this->temp1_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP2 && this->temp2_sensor_ != nullptr) {
|
||||
this->temp2_sensor_->publish_state(value);
|
||||
} else if (command == CMD_TEMP_DIFF && this->temp_diff_sensor_ != nullptr) {
|
||||
this->temp_diff_sensor_->publish_state(value);
|
||||
} else if (command == CMD_FLOW && this->flow_sensor_ != nullptr) {
|
||||
this->flow_sensor_->publish_state(value);
|
||||
} else if (command == CMD_VOLUME && this->volume_sensor_ != nullptr) {
|
||||
this->volume_sensor_->publish_state(value);
|
||||
}
|
||||
|
||||
// Custom sensors
|
||||
for (int i = 0; i < this->custom_commands_.size(); i++) {
|
||||
if (command == this->custom_commands_[i]) {
|
||||
this->custom_sensors_[i]->publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
|
||||
}
|
||||
|
||||
uint16_t crc16_ccitt(const uint8_t *buffer, int len) {
|
||||
uint32_t poly = 0x1021;
|
||||
uint32_t reg = 0x00;
|
||||
for (int i = 0; i < len; i++) {
|
||||
int mask = 0x80;
|
||||
while (mask > 0) {
|
||||
reg <<= 1;
|
||||
if (buffer[i] & mask) {
|
||||
reg |= 1;
|
||||
}
|
||||
mask >>= 1;
|
||||
if (reg & 0x10000) {
|
||||
reg &= 0xffff;
|
||||
reg ^= poly;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (uint16_t) reg;
|
||||
}
|
||||
|
||||
} // namespace kamstrup_kmp
|
||||
} // namespace esphome
|
131
esphome/components/kamstrup_kmp/kamstrup_kmp.h
Normal file
131
esphome/components/kamstrup_kmp/kamstrup_kmp.h
Normal file
@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kamstrup_kmp {
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
=== KAMSTRUP KMP ===
|
||||
===========================================================================
|
||||
|
||||
Kamstrup Meter Protocol (KMP) is a protocol used with Kamstrup district
|
||||
heating meters, e.g. Kamstrup MULTICAL 403.
|
||||
These devices register consumed heat from a district heating system.
|
||||
It does this by measuring the incoming and outgoing water temperature
|
||||
and by measuring the water flow. The temperature difference (delta T)
|
||||
together with the water flow results in consumed energy, typically
|
||||
in giga joule (GJ).
|
||||
|
||||
The Kamstrup Multical has an optical interface just above the display.
|
||||
This interface is essentially an RS-232 interface using a proprietary
|
||||
protocol (Kamstrup Meter Protocol [KMP]).
|
||||
|
||||
The integration uses this optical interface to periodically read the
|
||||
configured values (sensors) from the meter. Supported sensors are:
|
||||
- Heat Energy [GJ]
|
||||
- Current Power Consumption [kW]
|
||||
- Temperature 1 [°C]
|
||||
- Temperature 2 [°C]
|
||||
- Temperature Difference [°K]
|
||||
- Water Flow [l/h]
|
||||
- Volume [m3]
|
||||
|
||||
Apart from these supported 'fixed' sensors, the user can configure up to
|
||||
five custom sensors. The KMP command (16 bit unsigned int) has to be
|
||||
provided in that case.
|
||||
|
||||
Note:
|
||||
The optical interface is enabled as soon as a button on the meter is pushed.
|
||||
The interface stays active for a few minutes. To keep the interface 'alive'
|
||||
magnets must be placed around the optical sensor.
|
||||
|
||||
Units:
|
||||
Units are set using the regular Sensor config in the user yaml. However,
|
||||
KMP does also send the correct unit with every value. When DEBUG logging
|
||||
is enabled, the received value with the received unit are logged.
|
||||
|
||||
Acknowledgement:
|
||||
This interface was inspired by:
|
||||
- https://atomstar.tweakblogs.net/blog/19110/reading-out-kamstrup-multical-402-403-with-home-built-optical-head
|
||||
- https://wiki.hal9k.dk/projects/kamstrup
|
||||
*/
|
||||
|
||||
// KMP Commands
|
||||
static const uint16_t CMD_HEAT_ENERGY = 0x003C;
|
||||
static const uint16_t CMD_POWER = 0x0050;
|
||||
static const uint16_t CMD_TEMP1 = 0x0056;
|
||||
static const uint16_t CMD_TEMP2 = 0x0057;
|
||||
static const uint16_t CMD_TEMP_DIFF = 0x0059;
|
||||
static const uint16_t CMD_FLOW = 0x004A;
|
||||
static const uint16_t CMD_VOLUME = 0x0044;
|
||||
|
||||
// KMP units
|
||||
static const char *const UNITS[] = {
|
||||
"", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal",
|
||||
"kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh",
|
||||
"GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA",
|
||||
"kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l",
|
||||
"m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd",
|
||||
"mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield",
|
||||
"s", "ms", "days", "RTC-Q", "Datetime"};
|
||||
|
||||
class KamstrupKMPComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
|
||||
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
|
||||
void set_temp1_sensor(sensor::Sensor *sensor) { this->temp1_sensor_ = sensor; }
|
||||
void set_temp2_sensor(sensor::Sensor *sensor) { this->temp2_sensor_ = sensor; }
|
||||
void set_temp_diff_sensor(sensor::Sensor *sensor) { this->temp_diff_sensor_ = sensor; }
|
||||
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
|
||||
void set_volume_sensor(sensor::Sensor *sensor) { this->volume_sensor_ = sensor; }
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void add_custom_sensor(sensor::Sensor *sensor, uint16_t command) {
|
||||
this->custom_sensors_.push_back(sensor);
|
||||
this->custom_commands_.push_back(command);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Sensors
|
||||
sensor::Sensor *heat_energy_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *temp1_sensor_{nullptr};
|
||||
sensor::Sensor *temp2_sensor_{nullptr};
|
||||
sensor::Sensor *temp_diff_sensor_{nullptr};
|
||||
sensor::Sensor *flow_sensor_{nullptr};
|
||||
sensor::Sensor *volume_sensor_{nullptr};
|
||||
|
||||
// Custom sensors and commands
|
||||
std::vector<sensor::Sensor *> custom_sensors_;
|
||||
std::vector<uint16_t> custom_commands_;
|
||||
|
||||
// Command queue
|
||||
std::queue<uint16_t> command_queue_;
|
||||
|
||||
// Methods
|
||||
|
||||
// Sends a command to the meter and receives its response
|
||||
void send_command_(uint16_t command);
|
||||
// Sends a message to the meter. A prefix/suffix and CRC are added
|
||||
void send_message_(const uint8_t *msg, int msg_len);
|
||||
// Clears and data that might be in the UART Rx buffer
|
||||
void clear_uart_rx_buffer_();
|
||||
// Reads and validates the response to a send command
|
||||
void read_command_(uint16_t command);
|
||||
// Parses a received message
|
||||
void parse_command_message_(uint16_t command, const uint8_t *msg, int msg_len);
|
||||
// Sets the received value to the correct sensor
|
||||
void set_sensor_value_(uint16_t command, float value, uint8_t unit_idx);
|
||||
};
|
||||
|
||||
// "true" CCITT CRC-16
|
||||
uint16_t crc16_ccitt(const uint8_t *buffer, int len);
|
||||
|
||||
} // namespace kamstrup_kmp
|
||||
} // namespace esphome
|
132
esphome/components/kamstrup_kmp/sensor.py
Normal file
132
esphome/components/kamstrup_kmp/sensor.py
Normal file
@ -0,0 +1,132 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_FLOW,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_VOLUME,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLUME,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_CUBIC_METER,
|
||||
UNIT_EMPTY,
|
||||
UNIT_KELVIN,
|
||||
UNIT_KILOWATT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@cfeenstra1024"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
kamstrup_kmp_ns = cg.esphome_ns.namespace("kamstrup_kmp")
|
||||
KamstrupKMPComponent = kamstrup_kmp_ns.class_(
|
||||
"KamstrupKMPComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONF_HEAT_ENERGY = "heat_energy"
|
||||
CONF_TEMP1 = "temp1"
|
||||
CONF_TEMP2 = "temp2"
|
||||
CONF_TEMP_DIFF = "temp_diff"
|
||||
|
||||
UNIT_GIGA_JOULE = "GJ"
|
||||
UNIT_LITRE_PER_HOUR = "l/h"
|
||||
|
||||
# Note: The sensor units are set automatically based un the received data from the meter
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(KamstrupKMPComponent),
|
||||
cv.Optional(CONF_HEAT_ENERGY): sensor.sensor_schema(
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
unit_of_measurement=UNIT_GIGA_JOULE,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_KILOWATT,
|
||||
),
|
||||
cv.Optional(CONF_TEMP1): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
),
|
||||
cv.Optional(CONF_TEMP2): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
),
|
||||
cv.Optional(CONF_TEMP_DIFF): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_KELVIN,
|
||||
),
|
||||
cv.Optional(CONF_FLOW): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLUME,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_LITRE_PER_HOUR,
|
||||
),
|
||||
cv.Optional(CONF_VOLUME): sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLUME,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
unit_of_measurement=UNIT_CUBIC_METER,
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM): cv.ensure_list(
|
||||
sensor.sensor_schema(
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
).extend({cv.Required(CONF_COMMAND): cv.hex_uint16_t})
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"kamstrup_kmp", baud_rate=1200, require_rx=True, require_tx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
# Standard sensors
|
||||
for key in [
|
||||
CONF_HEAT_ENERGY,
|
||||
CONF_POWER,
|
||||
CONF_TEMP1,
|
||||
CONF_TEMP2,
|
||||
CONF_TEMP_DIFF,
|
||||
CONF_FLOW,
|
||||
CONF_VOLUME,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
|
||||
# Custom sensors
|
||||
if CONF_CUSTOM in config:
|
||||
for conf in config[CONF_CUSTOM]:
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.add_custom_sensor(sens, conf[CONF_COMMAND]))
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
|
||||
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID, CONF_VOLUME
|
||||
from esphome.core import CORE
|
||||
from esphome.coroutine import coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
@ -43,7 +43,6 @@ VolumeSetAction = media_player_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_ON_IDLE = "on_idle"
|
||||
CONF_ON_PLAY = "on_play"
|
||||
CONF_ON_PAUSE = "on_pause"
|
||||
|
@ -856,6 +856,7 @@ CONF_VISUAL = "visual"
|
||||
CONF_VOLTAGE = "voltage"
|
||||
CONF_VOLTAGE_ATTENUATION = "voltage_attenuation"
|
||||
CONF_VOLTAGE_DIVIDER = "voltage_divider"
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_WAIT_TIME = "wait_time"
|
||||
CONF_WAIT_UNTIL = "wait_until"
|
||||
CONF_WAKEUP_PIN = "wakeup_pin"
|
||||
|
25
tests/components/kamstrup_kmp/common.yaml
Normal file
25
tests/components/kamstrup_kmp/common.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
uart:
|
||||
tx_pin: ${uart_tx_pin}
|
||||
rx_pin: ${uart_rx_pin}
|
||||
baud_rate: 1200
|
||||
stop_bits: 2
|
||||
|
||||
sensor:
|
||||
- platform: kamstrup_kmp
|
||||
heat_energy:
|
||||
name: Heat Energy
|
||||
power:
|
||||
name: Power
|
||||
temp1:
|
||||
name: Temperature 1
|
||||
temp2:
|
||||
name: Temperature 2
|
||||
temp_diff:
|
||||
name: Temperature Difference
|
||||
flow:
|
||||
name: Flow
|
||||
volume:
|
||||
name: Volume
|
||||
custom:
|
||||
- name: Custom 1
|
||||
command: 0x1234
|
5
tests/components/kamstrup_kmp/test.esp32-idf.yaml
Normal file
5
tests/components/kamstrup_kmp/test.esp32-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
uart_tx_pin: GPIO1
|
||||
uart_rx_pin: GPIO3
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/kamstrup_kmp/test.esp32.yaml
Normal file
5
tests/components/kamstrup_kmp/test.esp32.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
uart_tx_pin: GPIO1
|
||||
uart_rx_pin: GPIO3
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/kamstrup_kmp/test.esp8266.yaml
Normal file
5
tests/components/kamstrup_kmp/test.esp8266.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
uart_tx_pin: GPIO1
|
||||
uart_rx_pin: GPIO3
|
||||
|
||||
<<: !include common.yaml
|
Loading…
Reference in New Issue
Block a user