mirror of
https://github.com/esphome/esphome.git
synced 2025-02-18 01:03:12 +01:00
Add support for pvvx mithermometer display via ble client (#3333)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
01a4443b6c
commit
0688deca6b
5 changed files with 360 additions and 0 deletions
56
esphome/components/pvvx_mithermometer/display/__init__.py
Normal file
56
esphome/components/pvvx_mithermometer/display/__init__.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import ble_client, display, time
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_AUTO_CLEAR_ENABLED,
|
||||||
|
CONF_DISCONNECT_DELAY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_TIME_ID,
|
||||||
|
CONF_VALIDITY_PERIOD,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["ble_client"]
|
||||||
|
|
||||||
|
pvvx_ns = cg.esphome_ns.namespace("pvvx_mithermometer")
|
||||||
|
PVVXDisplay = pvvx_ns.class_(
|
||||||
|
"PVVXDisplay", cg.PollingComponent, ble_client.BLEClientNode
|
||||||
|
)
|
||||||
|
PVVXDisplayRef = PVVXDisplay.operator("ref")
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
display.BASIC_DISPLAY_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(PVVXDisplay),
|
||||||
|
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
|
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISCONNECT_DELAY, default="5s"): cv.positive_time_period,
|
||||||
|
cv.Optional(CONF_VALIDITY_PERIOD, default="5min"): cv.All(
|
||||||
|
cv.positive_time_period_seconds,
|
||||||
|
cv.Range(max=cv.TimePeriod(seconds=65535)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await display.register_display(var, config)
|
||||||
|
await ble_client.register_ble_node(var, config)
|
||||||
|
cg.add(var.set_disconnect_delay(config[CONF_DISCONNECT_DELAY].total_milliseconds))
|
||||||
|
cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
|
||||||
|
cg.add(var.set_validity_period(config[CONF_VALIDITY_PERIOD].total_seconds))
|
||||||
|
|
||||||
|
if CONF_TIME_ID in config:
|
||||||
|
time_ = await cg.get_variable(config[CONF_TIME_ID])
|
||||||
|
cg.add(var.set_time(time_))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
lambda_ = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA], [(PVVXDisplayRef, "it")], return_type=cg.void
|
||||||
|
)
|
||||||
|
cg.add(var.set_writer(lambda_))
|
154
esphome/components/pvvx_mithermometer/display/pvvx_display.cpp
Normal file
154
esphome/components/pvvx_mithermometer/display/pvvx_display.cpp
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
#include "pvvx_display.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
namespace esphome {
|
||||||
|
namespace pvvx_mithermometer {
|
||||||
|
|
||||||
|
static const char *const TAG = "display.pvvx_mithermometer";
|
||||||
|
|
||||||
|
void PVVXDisplay::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "PVVX MiThermometer display:");
|
||||||
|
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Characteristic UUID : %s", this->char_uuid_.to_string().c_str());
|
||||||
|
ESP_LOGCONFIG(TAG, " Auto clear : %s", YESNO(this->auto_clear_enabled_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Set time on connection: %s", YESNO(this->time_ != nullptr));
|
||||||
|
ESP_LOGCONFIG(TAG, " Disconnect delay : %dms", this->disconnect_delay_ms_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::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:
|
||||||
|
ESP_LOGV(TAG, "[%s] Connected successfully!", this->parent_->address_str().c_str());
|
||||||
|
this->delayed_disconnect_();
|
||||||
|
break;
|
||||||
|
case ESP_GATTC_DISCONNECT_EVT:
|
||||||
|
ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str().c_str());
|
||||||
|
this->connection_established_ = false;
|
||||||
|
this->cancel_timeout("disconnect");
|
||||||
|
this->char_handle_ = 0;
|
||||||
|
break;
|
||||||
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
|
auto *chr = this->parent_->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||||
|
if (chr == nullptr) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->parent_->address_str().c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->connection_established_ = true;
|
||||||
|
this->char_handle_ = chr->handle;
|
||||||
|
#ifdef USE_TIME
|
||||||
|
this->sync_time_();
|
||||||
|
#endif
|
||||||
|
this->display();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::update() {
|
||||||
|
if (this->auto_clear_enabled_)
|
||||||
|
this->clear();
|
||||||
|
if (this->writer_.has_value())
|
||||||
|
(*this->writer_)(*this);
|
||||||
|
this->display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::display() {
|
||||||
|
if (!this->parent_->enabled) {
|
||||||
|
ESP_LOGD(TAG, "[%s] BLE client not enabled. Init connection.", this->parent_->address_str().c_str());
|
||||||
|
this->parent_->set_enabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->connection_established_) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
|
||||||
|
this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->char_handle_) {
|
||||||
|
ESP_LOGW(TAG, "[%s] No ble handle to BLE client. State update can not be written.",
|
||||||
|
this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "[%s] Send to display: bignum %d, smallnum: %d, cfg: 0x%02x, validity period: %u.",
|
||||||
|
this->parent_->address_str().c_str(), this->bignum_, this->smallnum_, this->cfg_, this->validity_period_);
|
||||||
|
uint8_t blk[8] = {};
|
||||||
|
blk[0] = 0x22;
|
||||||
|
blk[1] = this->bignum_ & 0xff;
|
||||||
|
blk[2] = (this->bignum_ >> 8) & 0xff;
|
||||||
|
blk[3] = this->smallnum_ & 0xff;
|
||||||
|
blk[4] = (this->smallnum_ >> 8) & 0xff;
|
||||||
|
blk[5] = this->validity_period_ & 0xff;
|
||||||
|
blk[6] = (this->validity_period_ >> 8) & 0xff;
|
||||||
|
blk[7] = this->cfg_;
|
||||||
|
this->send_to_setup_char_(blk, sizeof(blk));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::setcfgbit_(uint8_t bit, bool value) {
|
||||||
|
uint8_t mask = 1 << bit;
|
||||||
|
if (value) {
|
||||||
|
this->cfg_ |= mask;
|
||||||
|
} else {
|
||||||
|
this->cfg_ &= (0xFF ^ mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) {
|
||||||
|
if (!this->connection_established_) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Not connected to BLE client.", this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, size, blk,
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "[%s] send %u bytes", this->parent_->address_str().c_str(), size);
|
||||||
|
this->delayed_disconnect_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PVVXDisplay::delayed_disconnect_() {
|
||||||
|
if (this->disconnect_delay_ms_ == 0)
|
||||||
|
return;
|
||||||
|
this->cancel_timeout("disconnect");
|
||||||
|
this->set_timeout("disconnect", this->disconnect_delay_ms_, [this]() { this->parent_->set_enabled(false); });
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
void PVVXDisplay::sync_time_() {
|
||||||
|
if (this->time_ == nullptr)
|
||||||
|
return;
|
||||||
|
if (!this->connection_established_) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Not connected to BLE client. Time can not be synced.", this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->char_handle_) {
|
||||||
|
ESP_LOGW(TAG, "[%s] No ble handle to BLE client. Time can not be synced.", this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto time = this->time_->now();
|
||||||
|
if (!time.is_valid()) {
|
||||||
|
ESP_LOGW(TAG, "[%s] Time is not yet valid. Time can not be synced.", this->parent_->address_str().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
time.recalc_timestamp_utc(true); // calculate timestamp of local time
|
||||||
|
uint8_t blk[5] = {};
|
||||||
|
ESP_LOGD(TAG, "[%s] Sync time with timestamp %lu.", this->parent_->address_str().c_str(), time.timestamp);
|
||||||
|
blk[0] = 0x23;
|
||||||
|
blk[1] = time.timestamp & 0xff;
|
||||||
|
blk[2] = (time.timestamp >> 8) & 0xff;
|
||||||
|
blk[3] = (time.timestamp >> 16) & 0xff;
|
||||||
|
blk[4] = (time.timestamp >> 24) & 0xff;
|
||||||
|
this->send_to_setup_char_(blk, sizeof(blk));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace pvvx_mithermometer
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
133
esphome/components/pvvx_mithermometer/display/pvvx_display.h
Normal file
133
esphome/components/pvvx_mithermometer/display/pvvx_display.h
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/components/ble_client/ble_client.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include <esp_gattc_api.h>
|
||||||
|
#ifdef USE_TIME
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pvvx_mithermometer {
|
||||||
|
|
||||||
|
class PVVXDisplay;
|
||||||
|
|
||||||
|
/// Possible units for the big number
|
||||||
|
enum UNIT {
|
||||||
|
UNIT_NONE = 0, ///< do not show a unit
|
||||||
|
UNIT_DEG_GHE, ///< show "°Г"
|
||||||
|
UNIT_MINUS, ///< show " -"
|
||||||
|
UNIT_DEG_F, ///< show "°F"
|
||||||
|
UNIT_LOWDASH, ///< show " _"
|
||||||
|
UNIT_DEG_C, ///< show "°C"
|
||||||
|
UNIT_LINES, ///< show " ="
|
||||||
|
UNIT_DEG_E, ///< show "°E"
|
||||||
|
};
|
||||||
|
|
||||||
|
using pvvx_writer_t = std::function<void(PVVXDisplay &)>;
|
||||||
|
|
||||||
|
class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void set_writer(pvvx_writer_t &&writer) { this->writer_ = writer; }
|
||||||
|
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
|
||||||
|
void set_disconnect_delay(uint32_t ms) { this->disconnect_delay_ms_ = ms; }
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// Set validity period of the display information in seconds (1..65535)
|
||||||
|
void set_validity_period(uint16_t validity_period) { this->validity_period_ = validity_period; }
|
||||||
|
/// Clear the screen
|
||||||
|
void clear() {
|
||||||
|
this->bignum_ = 0;
|
||||||
|
this->smallnum_ = 0;
|
||||||
|
this->cfg_ = 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Print the big number
|
||||||
|
*
|
||||||
|
* Valid values are from -99.5 to 1999.5. Smaller values are displayed as Lo, higher as Hi.
|
||||||
|
* It will printed as it fits in the screen.
|
||||||
|
*/
|
||||||
|
void print_bignum(float bignum) { this->bignum_ = bignum * 10; }
|
||||||
|
/**
|
||||||
|
* Print the small number
|
||||||
|
*
|
||||||
|
* Valid values are from -9 to 99. Smaller values are displayed as Lo, higher as Hi.
|
||||||
|
*/
|
||||||
|
void print_smallnum(float smallnum) { this->smallnum_ = smallnum; }
|
||||||
|
/**
|
||||||
|
* Print a happy face
|
||||||
|
*
|
||||||
|
* Can be combined with print_sad() print_bracket().
|
||||||
|
* Possible ouputs are:
|
||||||
|
*
|
||||||
|
* @verbatim
|
||||||
|
* bracket sad happy
|
||||||
|
* 0 0 0 " "
|
||||||
|
* 0 0 1 " ^_^ "
|
||||||
|
* 0 1 0 " -∧- "
|
||||||
|
* 0 1 1 " Δ△Δ "
|
||||||
|
* 1 0 0 "( )"
|
||||||
|
* 1 0 1 "(^_^)"
|
||||||
|
* 1 1 0 "(-∧-)"
|
||||||
|
* 1 1 1 "(Δ△Δ)"
|
||||||
|
* @endverbatim
|
||||||
|
*/
|
||||||
|
void print_happy(bool happy = true) { this->setcfgbit_(0, happy); }
|
||||||
|
/// Print a sad face
|
||||||
|
void print_sad(bool sad = true) { this->setcfgbit_(1, sad); }
|
||||||
|
/// Print round brackets around the face
|
||||||
|
void print_bracket(bool bracket = true) { this->setcfgbit_(2, bracket); }
|
||||||
|
/// Print percent sign at small number
|
||||||
|
void print_percent(bool percent = true) { this->setcfgbit_(3, percent); }
|
||||||
|
/// Print battery sign
|
||||||
|
void print_battery(bool battery = true) { this->setcfgbit_(4, battery); }
|
||||||
|
/// Print unit of big number
|
||||||
|
void print_unit(UNIT unit) { this->cfg_ = (this->cfg_ & 0x1F) | ((unit & 0x7) << 5); }
|
||||||
|
|
||||||
|
void display();
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
void set_time(time::RealTimeClock *time) { this->time_ = time; };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool auto_clear_enabled_{true};
|
||||||
|
uint32_t disconnect_delay_ms_ = 5000;
|
||||||
|
uint16_t validity_period_ = 300;
|
||||||
|
uint16_t bignum_ = 0;
|
||||||
|
uint16_t smallnum_ = 0;
|
||||||
|
uint8_t cfg_ = 0;
|
||||||
|
|
||||||
|
void setcfgbit_(uint8_t bit, bool value);
|
||||||
|
void send_to_setup_char_(uint8_t *blk, size_t size);
|
||||||
|
void delayed_disconnect_();
|
||||||
|
#ifdef USE_TIME
|
||||||
|
void sync_time_();
|
||||||
|
time::RealTimeClock *time_ = nullptr;
|
||||||
|
#endif
|
||||||
|
uint16_t char_handle_ = 0;
|
||||||
|
bool connection_established_ = false;
|
||||||
|
|
||||||
|
esp32_ble_tracker::ESPBTUUID service_uuid_ =
|
||||||
|
esp32_ble_tracker::ESPBTUUID::from_raw("00001f10-0000-1000-8000-00805f9b34fb");
|
||||||
|
esp32_ble_tracker::ESPBTUUID char_uuid_ =
|
||||||
|
esp32_ble_tracker::ESPBTUUID::from_raw("00001f1f-0000-1000-8000-00805f9b34fb");
|
||||||
|
|
||||||
|
optional<pvvx_writer_t> writer_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pvvx_mithermometer
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
|
@ -173,6 +173,7 @@ CONF_DIR_PIN = "dir_pin"
|
||||||
CONF_DIRECTION = "direction"
|
CONF_DIRECTION = "direction"
|
||||||
CONF_DIRECTION_OUTPUT = "direction_output"
|
CONF_DIRECTION_OUTPUT = "direction_output"
|
||||||
CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
|
CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
|
||||||
|
CONF_DISCONNECT_DELAY = "disconnect_delay"
|
||||||
CONF_DISCOVERY = "discovery"
|
CONF_DISCOVERY = "discovery"
|
||||||
CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator"
|
CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator"
|
||||||
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
||||||
|
@ -736,6 +737,7 @@ CONF_USE_ABBREVIATIONS = "use_abbreviations"
|
||||||
CONF_USE_ADDRESS = "use_address"
|
CONF_USE_ADDRESS = "use_address"
|
||||||
CONF_USERNAME = "username"
|
CONF_USERNAME = "username"
|
||||||
CONF_UUID = "uuid"
|
CONF_UUID = "uuid"
|
||||||
|
CONF_VALIDITY_PERIOD = "validity_period"
|
||||||
CONF_VALUE = "value"
|
CONF_VALUE = "value"
|
||||||
CONF_VALUE_FONT = "value_font"
|
CONF_VALUE_FONT = "value_font"
|
||||||
CONF_VARIABLES = "variables"
|
CONF_VARIABLES = "variables"
|
||||||
|
|
|
@ -2436,6 +2436,21 @@ display:
|
||||||
it.fill(Color::WHITE);
|
it.fill(Color::WHITE);
|
||||||
id(glob_bool_processed) = true;
|
id(glob_bool_processed) = true;
|
||||||
}
|
}
|
||||||
|
- platform: pvvx_mithermometer
|
||||||
|
ble_client_id: ble_foo
|
||||||
|
time_id: sntp_time
|
||||||
|
disconnect_delay: 3s
|
||||||
|
update_interval: 10min
|
||||||
|
validity_period: 20min
|
||||||
|
lambda: |-
|
||||||
|
it.print_bignum(188.8);
|
||||||
|
it.print_unit(pvvx_mithermometer::UNIT_DEG_E);
|
||||||
|
it.print_smallnum(88);
|
||||||
|
it.print_percent(true);
|
||||||
|
it.print_happy(true);
|
||||||
|
it.print_sad(true);
|
||||||
|
it.print_bracket(true);
|
||||||
|
it.print_battery(true);
|
||||||
|
|
||||||
tm1651:
|
tm1651:
|
||||||
id: tm1651_battery
|
id: tm1651_battery
|
||||||
|
|
Loading…
Add table
Reference in a new issue