mirror of
https://github.com/esphome/esphome.git
synced 2024-12-02 19:54:14 +01:00
Initial DALY Modbus BMS implementation
This commit is contained in:
parent
529ff4bd52
commit
5d767d9f92
4 changed files with 230 additions and 0 deletions
33
esphome/components/daly_hkms_bms/__init__.py
Normal file
33
esphome/components/daly_hkms_bms/__init__.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import modbus
|
||||||
|
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||||
|
|
||||||
|
CODEOWNERS = ["@patagonaa"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
DEPENDENCIES = ["modbus"]
|
||||||
|
|
||||||
|
CONF_DALY_HKMS_BMS_ID = "daly_hkms_bms_id"
|
||||||
|
|
||||||
|
daly_hkms_bms = cg.esphome_ns.namespace("daly_hkms_bms")
|
||||||
|
DalyHkmsBmsComponent = daly_hkms_bms.class_(
|
||||||
|
"DalyHkmsBmsComponent", cg.PollingComponent, modbus.ModbusDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(DalyHkmsBmsComponent),
|
||||||
|
cv.GenerateID(modbus.CONF_MODBUS_ID): cv.use_id(modbus.Modbus),
|
||||||
|
cv.Optional(CONF_ADDRESS, default=1): cv.positive_int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("30s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await modbus.register_modbus_device(var, config)
|
||||||
|
cg.add(var.set_daly_address(config[CONF_ADDRESS]))
|
92
esphome/components/daly_hkms_bms/daly_hkms_bms.cpp
Normal file
92
esphome/components/daly_hkms_bms/daly_hkms_bms.cpp
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#include "daly_hkms_bms.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace daly_hkms_bms {
|
||||||
|
|
||||||
|
static const char *const TAG = "daly_hkms_bms";
|
||||||
|
|
||||||
|
// the DALY BMS only _kinda_ does Modbus. The device address is offset by 0x80 (so BMS #1 has address 0x81)
|
||||||
|
// which would be fine, however the Modbus address of the response has a different offset of 0x50,
|
||||||
|
// which makes this very much non-standard-compliant...
|
||||||
|
static const uint8_t DALY_MODBUS_REQUEST_ADDRESS_OFFSET = 0x80;
|
||||||
|
static const uint8_t DALY_MODBUS_RESPONSE_ADDRESS_OFFSET = 0x50;
|
||||||
|
|
||||||
|
static const uint8_t MODBUS_CMD_READ_HOLDING_REGISTERS = 0x03;
|
||||||
|
|
||||||
|
static const uint16_t MODBUS_ADDR_SUM_VOLT = 56;
|
||||||
|
static const uint16_t MODBUS_REGISTER_COUNT = 3;
|
||||||
|
|
||||||
|
void DalyHkmsBmsComponent::set_daly_address(uint8_t daly_address) {
|
||||||
|
this->daly_address_ = daly_address;
|
||||||
|
|
||||||
|
// set ModbusDevice address to the response address so the modbus component forwards
|
||||||
|
// the response of this device to this component
|
||||||
|
uint8_t modbus_response_address = daly_address + DALY_MODBUS_RESPONSE_ADDRESS_OFFSET;
|
||||||
|
this->set_address(modbus_response_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DalyHkmsBmsComponent::loop() {
|
||||||
|
// If update() was unable to send we retry until we can send.
|
||||||
|
if (!this->waiting_to_update_)
|
||||||
|
return;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DalyHkmsBmsComponent::update() {
|
||||||
|
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - this->last_send_ < this->get_update_interval() / 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bus might be slow, or there might be other devices, or other components might be talking to our device.
|
||||||
|
if (this->waiting_for_response()) {
|
||||||
|
this->waiting_to_update_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->waiting_to_update_ = false;
|
||||||
|
|
||||||
|
// send the request using Modbus directly instead of ModbusDevice so we can send the data with the request address
|
||||||
|
this->parent_->send(this->daly_address_ + DALY_MODBUS_REQUEST_ADDRESS_OFFSET, MODBUS_CMD_READ_HOLDING_REGISTERS, MODBUS_ADDR_SUM_VOLT, MODBUS_REGISTER_COUNT, 0, nullptr);
|
||||||
|
|
||||||
|
this->last_send_ = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DalyHkmsBmsComponent::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||||
|
// Other components might be sending commands to our device. But we don't get called with enough
|
||||||
|
// context to know what is what. So if we didn't do a send, we ignore the data.
|
||||||
|
if (!this->last_send_)
|
||||||
|
return;
|
||||||
|
this->last_send_ = 0;
|
||||||
|
|
||||||
|
// Also ignore the data if the message is too short. Otherwise we will publish invalid values.
|
||||||
|
if (data.size() < MODBUS_REGISTER_COUNT * 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
if (this->voltage_sensor_) {
|
||||||
|
float voltage = encode_uint16(data[0], data[1]) / 10.0;
|
||||||
|
this->voltage_sensor_->publish_state(voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->current_sensor_) {
|
||||||
|
float current = (encode_uint16(data[2], data[3]) - 30000) / 10.0;
|
||||||
|
this->current_sensor_->publish_state(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->battery_level_sensor_) {
|
||||||
|
float current = encode_uint16(data[4], data[5]) / 10.0;
|
||||||
|
this->battery_level_sensor_->publish_state(current);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DalyHkmsBmsComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "DALY HKMS BMS:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->daly_address_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace daly_hkms_bms
|
||||||
|
} // namespace esphome
|
35
esphome/components/daly_hkms_bms/daly_hkms_bms.h
Normal file
35
esphome/components/daly_hkms_bms/daly_hkms_bms.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/modbus/modbus.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace daly_hkms_bms {
|
||||||
|
|
||||||
|
class DalyHkmsBmsComponent : public PollingComponent, public modbus::ModbusDevice {
|
||||||
|
public:
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void set_daly_address(uint8_t address);
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
SUB_SENSOR(voltage)
|
||||||
|
SUB_SENSOR(current)
|
||||||
|
SUB_SENSOR(battery_level)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool waiting_to_update_;
|
||||||
|
uint32_t last_send_;
|
||||||
|
uint8_t daly_address_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace daly_hkms_bms
|
||||||
|
} // namespace esphome
|
70
esphome/components/daly_hkms_bms/sensor.py
Normal file
70
esphome/components/daly_hkms_bms/sensor.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
CONF_BATTERY_LEVEL,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_WATT,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
ICON_PERCENT,
|
||||||
|
)
|
||||||
|
from . import DalyHkmsBmsComponent, CONF_DALY_HKMS_BMS_ID
|
||||||
|
|
||||||
|
ICON_CURRENT_DC = "mdi:current-dc"
|
||||||
|
|
||||||
|
TYPES = [
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_BATTERY_LEVEL,
|
||||||
|
]
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_DALY_HKMS_BMS_ID): cv.use_id(DalyHkmsBmsComponent),
|
||||||
|
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
icon=ICON_CURRENT_DC,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
icon=ICON_PERCENT,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_BATTERY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def setup_conf(config, key, hub):
|
||||||
|
if sensor_config := config.get(key):
|
||||||
|
sens = await sensor.new_sensor(sensor_config)
|
||||||
|
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
hub = await cg.get_variable(config[CONF_DALY_HKMS_BMS_ID])
|
||||||
|
for key in TYPES:
|
||||||
|
await setup_conf(config, key, hub)
|
Loading…
Reference in a new issue