Add support for Daly Smart BMS

This commit is contained in:
Silvio 2021-08-14 19:20:13 +02:00
parent 5edebaf468
commit d59c40a32c
6 changed files with 590 additions and 0 deletions

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
CODEOWNERS = ["@s1lvi0"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"]
CONF_BSM_DALY_ID = "bms_daly_id"
daly_bms = cg.esphome_ns.namespace("daly_bms")
DalyBmsComponent = daly_bms.class_("DalyBmsComponent", uart.UARTDevice, cg.Component)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DalyBmsComponent),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
.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 uart.register_uart_device(var, config)

View file

@ -0,0 +1,46 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_ID
from . import DalyBmsComponent, CONF_BSM_DALY_ID
CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled"
CONF_DISCHARGING_MOS_ENABLED = "discharging_mos_enabled"
TYPES = [
CONF_CHARGING_MOS_ENABLED,
CONF_DISCHARGING_MOS_ENABLED,
]
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_BSM_DALY_ID): cv.use_id(DalyBmsComponent),
cv.Optional(CONF_CHARGING_MOS_ENABLED): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
}
),
cv.Optional(CONF_DISCHARGING_MOS_ENABLED): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
}
),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await binary_sensor.register_binary_sensor(sens, conf)
cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BSM_DALY_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,188 @@
#include "daly_bms.h"
#include "esphome/core/log.h"
namespace esphome {
namespace daly_bms {
static const char *const TAG = "daly_bms";
static const unsigned char DALY_TEMPERATURE_OFFSET = 40;
static const unsigned char DALY_REQUEST_battery_level = 0x90;
static const unsigned char DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91;
static const unsigned char DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92;
static const unsigned char DALY_REQUEST_MOS = 0x93;
static const unsigned char DALY_REQUEST_STATUS = 0x94;
static const unsigned char DALY_REQUEST_TEMPERATURE = 0x96;
void DalyBmsComponent::setup() {}
void DalyBmsComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DALY_BMS:");
this->check_uart_settings(9600);
}
void DalyBmsComponent::update() {
this->request_data(DALY_REQUEST_battery_level);
this->request_data(DALY_REQUEST_MIN_MAX_VOLTAGE);
this->request_data(DALY_REQUEST_MIN_MAX_TEMPERATURE);
this->request_data(DALY_REQUEST_MOS);
this->request_data(DALY_REQUEST_STATUS);
this->request_data(DALY_REQUEST_TEMPERATURE);
unsigned char *get_battery_level_data;
int available_data = this->available();
if (available_data >= 13) {
get_battery_level_data = (unsigned char *) malloc(available_data);
this->read_array(get_battery_level_data, available_data);
this->decode_data(get_battery_level_data, available_data);
}
}
float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; }
void DalyBmsComponent::request_data(unsigned char data_id) {
unsigned char request_message[13];
request_message[0] = 0xA5; // Start Flag
request_message[1] = 0x80; // Communication Module Address
request_message[2] = data_id; // Data ID
request_message[3] = 0x08; // Data Length (Fixed)
request_message[4] = 0x00; // Empty Data
request_message[5] = 0x00; // |
request_message[6] = 0x00; // |
request_message[7] = 0x00; // |
request_message[8] = 0x00; // |
request_message[9] = 0x00; // |
request_message[10] = 0x00; // |
request_message[11] = 0x00; // Empty Data
request_message[12] = (unsigned char) (request_message[0] + request_message[1] + request_message[2] +
request_message[3]); // Checksum (Lower byte of the other bytes sum)
this->write_array(request_message, sizeof(request_message));
this->flush();
}
void DalyBmsComponent::decode_data(unsigned char *data, int length) {
unsigned char *start_flag_position;
while (data != NULL) {
start_flag_position = (unsigned char *) strchr((const char *) data, 0xA5);
if (start_flag_position != NULL) {
length = length - (start_flag_position - data);
data = start_flag_position;
if (length >= 13 && data[1] == 0x01) {
unsigned char checksum;
int sum = 0;
for (int i = 0; i < 12; i++) {
sum += data[i];
}
checksum = sum;
if (checksum == data[12]) {
switch (data[2]) {
case DALY_REQUEST_battery_level:
if (this->voltage_sensor_) {
this->voltage_sensor_->publish_state((float) (((data[4] << 8) | data[5]) / 10));
}
if (this->current_sensor_) {
this->current_sensor_->publish_state((float) ((((data[8] << 8) | data[9]) - 30000) / 10));
}
if (this->battery_level_sensor_) {
this->battery_level_sensor_->publish_state((float) (((data[10] << 8) | data[11]) / 10));
}
break;
case DALY_REQUEST_MIN_MAX_VOLTAGE:
if (this->max_cell_voltage_) {
this->max_cell_voltage_->publish_state((float) ((data[4] << 8) | data[5]) / 1000);
}
if (this->max_cell_volatge_number_) {
this->max_cell_volatge_number_->publish_state(data[6]);
}
if (this->min_cell_voltage_) {
this->min_cell_voltage_->publish_state((float) ((data[7] << 8) | data[8]) / 1000);
}
if (this->min_cell_voltage_number_) {
this->min_cell_voltage_number_->publish_state(data[9]);
}
break;
case DALY_REQUEST_MIN_MAX_TEMPERATURE:
if (this->max_temperature_) {
this->max_temperature_->publish_state(data[4] - DALY_TEMPERATURE_OFFSET);
}
if (this->max_temperature_probe_number_) {
this->max_temperature_probe_number_->publish_state(data[5]);
}
if (this->min_temperature_) {
this->min_temperature_->publish_state(data[6] - DALY_TEMPERATURE_OFFSET);
}
if (this->min_temperature_probe_number_) {
this->min_temperature_probe_number_->publish_state(data[7]);
}
break;
case DALY_REQUEST_MOS:
if (this->status_text_sensor_ != nullptr) {
switch (data[4]) {
case 0:
this->status_text_sensor_->publish_state("Stationary");
break;
case 1:
this->status_text_sensor_->publish_state("Charging");
break;
case 2:
this->status_text_sensor_->publish_state("Discharging");
break;
default:
break;
}
}
if (this->charging_mos_enabled_) {
this->charging_mos_enabled_->publish_state(data[5]);
}
if (this->discharging_mos_enabled_) {
this->discharging_mos_enabled_->publish_state(data[6]);
}
if (this->remaining_capacity_) {
this->remaining_capacity_->publish_state(
(float) ((data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]) / 1000);
}
break;
case DALY_REQUEST_STATUS:
break;
case DALY_REQUEST_TEMPERATURE:
if (data[4] == 1) {
if (this->temperature_1_sensor_) {
this->temperature_1_sensor_->publish_state(data[5] - DALY_TEMPERATURE_OFFSET);
}
if (this->temperature_2_sensor_) {
this->temperature_2_sensor_->publish_state(data[6] - DALY_TEMPERATURE_OFFSET);
}
}
break;
default:
break;
}
data = &data[13];
}
} else {
data = NULL;
}
} else {
data = NULL;
}
}
free(data);
}
} // namespace daly_bms
} // namespace esphome

View file

@ -0,0 +1,81 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace daly_bms {
class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
public:
DalyBmsComponent() = default;
// SENSORS
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; }
void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; }
void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_volatge_number) {
max_cell_volatge_number_ = max_cell_volatge_number;
}
void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; }
void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) {
min_cell_voltage_number_ = min_cell_voltage_number;
}
void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; }
void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) {
max_temperature_probe_number_ = max_temperature_probe_number;
}
void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; }
void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) {
min_temperature_probe_number_ = min_temperature_probe_number;
}
void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; }
void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; }
void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; }
// TEXT_SENSORS
void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; }
// BINARY_SENSORS
void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) {
charging_mos_enabled_ = charging_mos_enabled;
}
void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) {
discharging_mos_enabled_ = discharging_mos_enabled;
}
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override;
protected:
void request_data(unsigned char data_id);
void decode_data(unsigned char *data, int length);
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *battery_level_sensor_{nullptr};
sensor::Sensor *max_cell_voltage_{nullptr};
sensor::Sensor *max_cell_volatge_number_{nullptr};
sensor::Sensor *min_cell_voltage_{nullptr};
sensor::Sensor *min_cell_voltage_number_{nullptr};
sensor::Sensor *max_temperature_{nullptr};
sensor::Sensor *max_temperature_probe_number_{nullptr};
sensor::Sensor *min_temperature_{nullptr};
sensor::Sensor *min_temperature_probe_number_{nullptr};
sensor::Sensor *remaining_capacity_{nullptr};
sensor::Sensor *temperature_1_sensor_{nullptr};
sensor::Sensor *temperature_2_sensor_{nullptr};
text_sensor::TextSensor *status_text_sensor_{nullptr};
binary_sensor::BinarySensor *charging_mos_enabled_{nullptr};
binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr};
};
} // namespace daly_bms
} // namespace esphome

View file

@ -0,0 +1,203 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_VOLTAGE,
CONF_CURRENT,
CONF_BATTERY_LEVEL,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_EMPTY,
ICON_FLASH,
ICON_PERCENT,
ICON_COUNTER,
ICON_THERMOMETER,
ICON_GAUGE,
)
from . import DalyBmsComponent, CONF_BSM_DALY_ID
CONF_MAX_CELL_VOLTAGE = "max_cell_voltage"
CONF_MAX_CELL_VOLTAGE_NUMBER = "max_cell_voltage_number"
CONF_MIN_CELL_VOLTAGE = "min_cell_voltage"
CONF_MIN_CELL_VOLTAGE_NUMBER = "min_cell_voltage_number"
CONF_MAX_TEMPERATURE = "max_temperature"
CONF_MAX_TEMPERATURE_PROBE_NUMBER = "max_temperature_probe_number"
CONF_MIN_TEMPERATURE = "min_temperature"
CONF_MIN_TEMPERATURE_PROBE_NUMBER = "min_temperature_probe_number"
CONF_STATUS = "status"
CONF_CELLS_NUMBER = "cells_number"
CONF_TEMPERATURE_PROBES_NUMBER = "temperature_probe_number"
CONF_REMAINING_CAPACITY = "remaining_capacity"
CONF_TEMPERATURE_1 = "temperature_1"
CONF_TEMPERATURE_2 = "temperature_2"
ICON_CURRENT_DC = "mdi:current-dc"
ICON_BATTERY_OUTLINE = "mdi:battery-outline"
ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up"
ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down"
ICON_CAR_BATTERY = "mdi:car-battery"
UNIT_AMPERE_HOUR = "Ah"
TYPES = [
CONF_VOLTAGE,
CONF_CURRENT,
CONF_BATTERY_LEVEL,
CONF_MAX_CELL_VOLTAGE,
CONF_MAX_CELL_VOLTAGE_NUMBER,
CONF_MIN_CELL_VOLTAGE,
CONF_MIN_CELL_VOLTAGE_NUMBER,
CONF_MAX_TEMPERATURE,
CONF_MAX_TEMPERATURE_PROBE_NUMBER,
CONF_MIN_TEMPERATURE,
CONF_MIN_TEMPERATURE_PROBE_NUMBER,
CONF_CELLS_NUMBER,
CONF_TEMPERATURE_PROBES_NUMBER,
CONF_REMAINING_CAPACITY,
CONF_TEMPERATURE_1,
CONF_TEMPERATURE_2,
]
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_BSM_DALY_ID): cv.use_id(DalyBmsComponent),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT,
ICON_FLASH,
1,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE,
ICON_CURRENT_DC,
1,
DEVICE_CLASS_CURRENT,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT,
ICON_PERCENT,
1,
DEVICE_CLASS_BATTERY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT,
ICON_FLASH,
2,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT,
ICON_FLASH,
2,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER_CHEVRON_UP,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER_CHEVRON_DOWN,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema(
UNIT_AMPERE_HOUR,
ICON_GAUGE,
2,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_TEMPERATURE_PROBES_NUMBER): sensor.sensor_schema(
UNIT_EMPTY,
ICON_COUNTER,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_THERMOMETER,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BSM_DALY_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ICON, CONF_ID
from . import DalyBmsComponent, CONF_BSM_DALY_ID
CONF_STATUS = "status"
ICON_CAR_BATTERY = "mdi:car-battery"
TYPES = [
CONF_STATUS,
]
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_BSM_DALY_ID): cv.use_id(DalyBmsComponent),
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
}
),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BSM_DALY_ID])
for key in TYPES:
await setup_conf(config, key, hub)