Add Alpha3 pump component (#3787)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
jan-hofmeier 2023-07-11 07:12:43 +02:00 committed by GitHub
parent a391815921
commit f3cdcc008a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 364 additions and 0 deletions

View file

@ -21,6 +21,7 @@ esphome/components/airthings_wave_base/* @jeromelaban @ncareau
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix

View file

@ -0,0 +1 @@
CODEOWNERS = ["@jan-hofmeier"]

View file

@ -0,0 +1,189 @@
#include "alpha3.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <lwip/sockets.h> //gives ntohl
#ifdef USE_ESP32
namespace esphome {
namespace alpha3 {
static const char *const TAG = "alpha3";
void Alpha3::dump_config() {
ESP_LOGCONFIG(TAG, "ALPHA3");
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Head", this->head_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
}
void Alpha3::setup() {}
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor) {
if (sensor == nullptr)
return;
// we need to handle cases where a value is split over two packets
const int16_t value_length = 4; // 32bit float
// offset inside current response packet
auto rel_offset = value_offset - response_offset;
if (rel_offset <= -value_length)
return; // aready passed the value completly
if (rel_offset >= length)
return; // value not in this packet
auto start_offset = std::max(0, rel_offset);
auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
auto copy_length = end_offset - start_offset;
auto buffer_offset = std::max(-rel_offset, 0);
std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
if (rel_offset + value_length <= length) {
// we have the whole value
void *buffer = this->buffer_; // to prevent warnings when casting the pointer
*((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian
float fvalue = *((float *) buffer);
sensor->publish_state(fvalue * factor);
}
}
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
}
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum
this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
}
auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
float factor) {
this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
} else {
ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
this->response_type_[6], this->response_type_[7]);
}
this->response_offset_ += length;
}
void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
this->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
return;
auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
if (ret) {
ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->node_state = espbt::ClientState::IDLE;
if (this->flow_sensor_ != nullptr)
this->flow_sensor_->publish_state(NAN);
if (this->head_sensor_ != nullptr)
this->head_sensor_->publish_state(NAN);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(NAN);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->speed_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->voltage_sensor_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
this->geni_handle_ = chr->handle;
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
this->update();
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.handle == this->geni_handle_) {
this->handle_geni_response_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
void Alpha3::send_request_(uint8_t *request, size_t len) {
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status)
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
}
void Alpha3::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
return;
}
if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
delay(25); // need to wait between requests
}
if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
this->voltage_sensor_ != nullptr) {
uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
this->send_request_(geni_request_power, sizeof(geni_request_power));
delay(25); // need to wait between requests
}
}
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent {
public:
void setup() override;
void update() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
protected:
sensor::Sensor *flow_sensor_{nullptr};
sensor::Sensor *head_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
uint16_t geni_handle_;
int16_t response_length_;
int16_t response_offset_;
uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
uint8_t buffer_[4];
void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor);
void handle_geni_response_(const uint8_t *response, uint16_t length);
void send_request_(uint8_t *request, size_t len);
bool is_current_response_type_(const uint8_t *response_type);
};
} // namespace alpha3
} // namespace esphome
#endif

View file

@ -0,0 +1,85 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_CURRENT,
CONF_FLOW,
CONF_HEAD,
CONF_POWER,
CONF_SPEED,
CONF_VOLTAGE,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_METER,
UNIT_CUBIC_METER_PER_HOUR,
UNIT_REVOLUTIONS_PER_MINUTE,
)
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Alpha3),
cv.Optional(CONF_FLOW): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
accuracy_decimals=2,
),
cv.Optional(CONF_HEAD): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("15s"))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_FLOW in config:
sens = await sensor.new_sensor(config[CONF_FLOW])
cg.add(var.set_flow_sensor(sens))
if CONF_HEAD in config:
sens = await sensor.new_sensor(config[CONF_HEAD])
cg.add(var.set_head_sensor(sens))
if CONF_POWER in config:
sens = await sensor.new_sensor(config[CONF_POWER])
cg.add(var.set_power_sensor(sens))
if CONF_CURRENT in config:
sens = await sensor.new_sensor(config[CONF_CURRENT])
cg.add(var.set_current_sensor(sens))
if CONF_SPEED in config:
sens = await sensor.new_sensor(config[CONF_SPEED])
cg.add(var.set_speed_sensor(sens))
if CONF_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_VOLTAGE])
cg.add(var.set_voltage_sensor(sens))

View file

@ -262,6 +262,7 @@ CONF_FINGER_ID = "finger_id"
CONF_FINGERPRINT_COUNT = "fingerprint_count"
CONF_FLASH_LENGTH = "flash_length"
CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length"
CONF_FLOW = "flow"
CONF_FLOW_CONTROL_PIN = "flow_control_pin"
CONF_FOR = "for"
CONF_FORCE_UPDATE = "force_update"
@ -286,6 +287,7 @@ CONF_GPIO = "gpio"
CONF_GREEN = "green"
CONF_GROUP = "group"
CONF_HARDWARE_UART = "hardware_uart"
CONF_HEAD = "head"
CONF_HEARTBEAT = "heartbeat"
CONF_HEAT_ACTION = "heat_action"
CONF_HEAT_DEADBAND = "heat_deadband"
@ -891,6 +893,7 @@ UNIT_CENTIMETER = "cm"
UNIT_COUNT_DECILITRE = "/dL"
UNIT_COUNTS_PER_CUBIC_METER = "#/m³"
UNIT_CUBIC_METER = ""
UNIT_CUBIC_METER_PER_HOUR = "m³/h"
UNIT_DECIBEL = "dB"
UNIT_DECIBEL_MILLIWATT = "dBm"
UNIT_DEGREE_PER_SECOND = "°/s"
@ -928,6 +931,7 @@ UNIT_PERCENT = "%"
UNIT_PH = "pH"
UNIT_PULSES = "pulses"
UNIT_PULSES_PER_MINUTE = "pulses/min"
UNIT_REVOLUTIONS_PER_MINUTE = "RPM"
UNIT_SECOND = "s"
UNIT_STEPS = "steps"
UNIT_VOLT = "V"

View file

@ -325,6 +325,7 @@ ble_client:
accept: True
- mac_address: C4:4F:33:11:22:33
id: my_bedjet_ble_client
bedjet:
- ble_client_id: my_bedjet_ble_client
id: my_bedjet_client
@ -1269,6 +1270,16 @@ sensor:
pressure:
name: "MPL3115A2 Pressure"
update_interval: 10s
- platform: alpha3
ble_client_id: ble_foo
flow:
name: "Radiator Pump Flow"
head:
name: "Radiator Pump Head"
power:
name: "Radiator Pump Power"
speed:
name: "Radiator Pump Speed"
- platform: ld2410
moving_distance:
name: "Moving distance (cm)"