Queue size sensor (#15)

* added automatic queue size sensor

* queue size sensor as regular sensor component
This commit is contained in:
j0ta29 2024-07-22 19:37:00 +02:00 committed by GitHub
parent e4628689b2
commit 3188efcf62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 149 additions and 120 deletions

View file

@ -3,18 +3,21 @@ from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ADDRESS,
CONF_BYTES,
CONF_DIV_RATIO, CONF_DIV_RATIO,
CONF_ID, CONF_ID,
CONF_LOGGER, CONF_LOGGER,
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_RX_PIN, CONF_RX_PIN,
CONF_TX_PIN, CONF_TX_PIN,
CONF_TYPE,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.core import CORE from esphome.core import CORE
CODEOWNERS = ["@j0ta29"] CODEOWNERS = ["@j0ta29"]
DEPENDENCIES = [] DEPENDENCIES = ["sensor"]
AUTO_LOAD = [] AUTO_LOAD = []
MULTI_CONF = False MULTI_CONF = False
@ -32,6 +35,73 @@ DAY_OF_WEEK = {
} }
CONF_DAY_OF_WEEK = "day_of_week" CONF_DAY_OF_WEEK = "day_of_week"
def check_address_for_types(types_address_needed):
def validator_(config):
address_needed = config[CONF_TYPE] in types_address_needed
address_defined = CONF_ADDRESS in config
if address_needed and not address_defined:
raise cv.Invalid(
f"{CONF_ADDRESS} is required for this types: {types_address_needed}"
)
if not address_needed and address_defined:
raise cv.Invalid(
f"{CONF_ADDRESS} is only allowed for this types: {types_address_needed}"
)
return config
return validator_
def check_bytes_for_types(types_bytes_needed):
def validator_(config):
bytes_needed = config[CONF_TYPE] in types_bytes_needed
bytes_defined = CONF_BYTES in config
if bytes_needed and not bytes_defined:
raise cv.Invalid(
f"{CONF_BYTES} is required for this types: {types_bytes_needed}"
)
if not bytes_needed and bytes_defined:
raise cv.Invalid(
f"{CONF_BYTES} is only allowed for this types: {types_bytes_needed}"
)
types_bytes_range_1_to_9 = ["MAP", "RAW"]
if config[CONF_TYPE] in types_bytes_range_1_to_9 and config[
CONF_BYTES
] not in range(0, 10):
raise cv.Invalid(
f"{CONF_BYTES} must be between 1 and 9 for this types: {types_bytes_range_1_to_9}"
)
types_bytes_day_schedule = ["DAY_SCHEDULE"]
if config[CONF_TYPE] in types_bytes_day_schedule and config[CONF_BYTES] not in [
56
]:
raise cv.Invalid(
f"{CONF_BYTES} must be 56 for this types: {types_bytes_day_schedule}"
)
return config
return validator_
def check_dow_for_types(types_dow_needed):
def validator_(config):
if config[CONF_TYPE] in types_dow_needed and CONF_DAY_OF_WEEK not in config:
raise cv.Invalid(
f"{CONF_DAY_OF_WEEK} is required for this types: {types_dow_needed}"
)
if config[CONF_TYPE] not in types_dow_needed and CONF_DAY_OF_WEEK in config:
raise cv.Invalid(
f"{CONF_DAY_OF_WEEK} is only allowed for this types: {types_dow_needed}"
)
return config
return validator_
OptolinkComponent = optolink_ns.class_("Optolink", cg.Component) OptolinkComponent = optolink_ns.class_("Optolink", cg.Component)
SENSOR_BASE_SCHEMA = cv.Schema( SENSOR_BASE_SCHEMA = cv.Schema(
{ {

View file

@ -258,6 +258,8 @@ void DatapointComponent::set_optolink_state_(const char *format, ...) {
std::string DatapointComponent::get_optolink_state_() { return optolink_->get_state(); } std::string DatapointComponent::get_optolink_state_() { return optolink_->get_state(); }
int DatapointComponent::get_optolink_queue_size_() { return optolink_->get_queue_size(); };
void conv2_100_F::encode(uint8_t *out, DPValue in) { void conv2_100_F::encode(uint8_t *out, DPValue in) {
int16_t tmp = floor((in.getFloat() * 100) + 0.5); int16_t tmp = floor((in.getFloat() * 100) + 0.5);
out[1] = tmp >> 8; out[1] = tmp >> 8;

View file

@ -48,6 +48,7 @@ class DatapointComponent {
void unfitting_value_type_(); void unfitting_value_type_();
void set_optolink_state_(const char *format, ...); void set_optolink_state_(const char *format, ...);
std::string get_optolink_state_(); std::string get_optolink_state_();
int get_optolink_queue_size_();
private: private:
const size_t max_retries_until_reset_ = 10; const size_t max_retries_until_reset_ = 10;

View file

@ -32,10 +32,9 @@ void Optolink::setup() {
#endif #endif
} }
void Optolink::loop() { void Optolink::loop() { VitoWiFi.loop(); }
// ESP_LOGD(TAG, "queue size: %d", VitoWiFi.queueSize());
VitoWiFi.loop(); int Optolink::get_queue_size() { return VitoWiFi.queueSize(); }
}
void Optolink::set_state(const char *format, ...) { void Optolink::set_state(const char *format, ...) {
va_list args; va_list args;

View file

@ -3,6 +3,7 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "VitoWiFi.h" #include "VitoWiFi.h"
namespace esphome { namespace esphome {
@ -34,6 +35,8 @@ class Optolink : public esphome::Component, public Print {
void set_state(const char *format, ...); void set_state(const char *format, ...);
std::string get_state() { return state_; } std::string get_state() { return state_; }
int get_queue_size();
}; };
} // namespace optolink } // namespace optolink

View file

@ -7,27 +7,42 @@ from esphome.const import (
CONF_DIV_RATIO, CONF_DIV_RATIO,
CONF_ID, CONF_ID,
CONF_MIN_VALUE, CONF_MIN_VALUE,
CONF_TYPE,
)
from .. import (
CONF_OPTOLINK_ID,
SENSOR_BASE_SCHEMA,
check_address_for_types,
check_bytes_for_types,
optolink_ns,
) )
from .. import CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA, optolink_ns
DEPENDENCIES = ["optolink"] DEPENDENCIES = ["optolink"]
CODEOWNERS = ["@j0ta29"] CODEOWNERS = ["@j0ta29"]
SensorType = optolink_ns.enum("SensorType")
TYPE = {
"DATAPOINT": SensorType.SENSOR_TYPE_DATAPOINT,
"QUEUE_SIZE": SensorType.SENSOR_TYPE_QUEUE_SIZE,
}
OptolinkSensor = optolink_ns.class_( OptolinkSensor = optolink_ns.class_(
"OptolinkSensor", sensor.Sensor, cg.PollingComponent "OptolinkSensor", sensor.Sensor, cg.PollingComponent
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = cv.All(
sensor.SENSOR_SCHEMA.extend( sensor.SENSOR_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(OptolinkSensor), cv.GenerateID(): cv.declare_id(OptolinkSensor),
cv.Required(CONF_ADDRESS): cv.hex_uint32_t, cv.Optional(CONF_TYPE, default="DATAPOINT"): cv.enum(TYPE, upper=True),
cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True), cv.Optional(CONF_ADDRESS): cv.hex_uint32_t,
cv.Optional(CONF_BYTES): cv.one_of(1, 2, 4, int=True),
cv.Optional(CONF_MIN_VALUE): cv.float_, cv.Optional(CONF_MIN_VALUE): cv.float_,
} }
) )
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
.extend(SENSOR_BASE_SCHEMA) .extend(SENSOR_BASE_SCHEMA),
check_address_for_types(["DATAPOINT"]),
check_bytes_for_types(["DATAPOINT"]),
) )
@ -38,8 +53,12 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await sensor.register_sensor(var, config) await sensor.register_sensor(var, config)
cg.add(var.set_type(config[CONF_TYPE]))
if CONF_ADDRESS in config:
cg.add(var.set_address(config[CONF_ADDRESS])) cg.add(var.set_address(config[CONF_ADDRESS]))
if CONF_BYTES in config:
cg.add(var.set_bytes(config[CONF_BYTES])) cg.add(var.set_bytes(config[CONF_BYTES]))
if CONF_DIV_RATIO in config:
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
if CONF_MIN_VALUE in config: if CONF_MIN_VALUE in config:
cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_min_value(config[CONF_MIN_VALUE]))

View file

@ -1,6 +1,7 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "optolink_sensor.h" #include "optolink_sensor.h"
#include "esphome/core/application.h"
#include "../optolink.h" #include "../optolink.h"
namespace esphome { namespace esphome {
@ -8,7 +9,26 @@ namespace optolink {
static const char *const TAG = "optolink.sensor"; static const char *const TAG = "optolink.sensor";
void OptolinkSensor::set_min_value(float min_value) { min_value_ = -29.3; } void OptolinkSensor::setup() {
switch (type_) {
case SENSOR_TYPE_DATAPOINT:
setup_datapoint_();
break;
case SENSOR_TYPE_QUEUE_SIZE:
break;
}
};
void OptolinkSensor::update() {
switch (type_) {
case SENSOR_TYPE_DATAPOINT:
datapoint_read_request_();
break;
case SENSOR_TYPE_QUEUE_SIZE:
publish_state(get_optolink_queue_size_());
break;
}
}
// NOLINTBEGIN // NOLINTBEGIN
void OptolinkSensor::datapoint_value_changed(uint8_t value) { void OptolinkSensor::datapoint_value_changed(uint8_t value) {

View file

@ -10,17 +10,20 @@
namespace esphome { namespace esphome {
namespace optolink { namespace optolink {
enum SensorType { SENSOR_TYPE_DATAPOINT, SENSOR_TYPE_QUEUE_SIZE };
class OptolinkSensor : public DatapointComponent, public esphome::sensor::Sensor, public esphome::PollingComponent { class OptolinkSensor : public DatapointComponent, public esphome::sensor::Sensor, public esphome::PollingComponent {
public: public:
OptolinkSensor(Optolink *optolink) : DatapointComponent(optolink) { OptolinkSensor(Optolink *optolink) : DatapointComponent(optolink) {
set_state_class(esphome::sensor::STATE_CLASS_MEASUREMENT); set_state_class(esphome::sensor::STATE_CLASS_MEASUREMENT);
} }
void set_min_value(float min_value); void set_type(SensorType type) { type_ = type; }
void set_min_value(float min_value) { min_value_ = min_value; }
protected: protected:
void setup() override { setup_datapoint_(); } void setup() override;
void update() override { datapoint_read_request_(); } void update() override;
const StringRef &get_component_name() override { return get_name(); } const StringRef &get_component_name() override { return get_name(); }
void datapoint_value_changed(float value) override { publish_state(value); }; void datapoint_value_changed(float value) override { publish_state(value); };
@ -29,6 +32,7 @@ class OptolinkSensor : public DatapointComponent, public esphome::sensor::Sensor
void datapoint_value_changed(uint32_t value) override; void datapoint_value_changed(uint32_t value) override;
private: private:
SensorType type_ = SENSOR_TYPE_DATAPOINT;
float min_value_ = -FLT_MAX; float min_value_ = -FLT_MAX;
}; };
} // namespace optolink } // namespace optolink

View file

@ -12,6 +12,9 @@ from esphome.const import (
from .. import ( from .. import (
CONF_DAY_OF_WEEK, CONF_DAY_OF_WEEK,
DAY_OF_WEEK, DAY_OF_WEEK,
check_address_for_types,
check_bytes_for_types,
check_dow_for_types,
optolink_ns, optolink_ns,
CONF_OPTOLINK_ID, CONF_OPTOLINK_ID,
SENSOR_BASE_SCHEMA, SENSOR_BASE_SCHEMA,
@ -25,7 +28,6 @@ TYPE = {
"MAP": TextSensorType.TEXT_SENSOR_TYPE_MAP, "MAP": TextSensorType.TEXT_SENSOR_TYPE_MAP,
"RAW": TextSensorType.TEXT_SENSOR_TYPE_RAW, "RAW": TextSensorType.TEXT_SENSOR_TYPE_RAW,
"DAY_SCHEDULE": TextSensorType.TEXT_SENSOR_TYPE_DAY_SCHEDULE, "DAY_SCHEDULE": TextSensorType.TEXT_SENSOR_TYPE_DAY_SCHEDULE,
"DAY_SCHEDULE_SYNCHRONIZED": TextSensorType.TEXT_SENSOR_TYPE_DAY_SCHEDULE_SYNCHRONIZED,
"DEVICE_INFO": TextSensorType.TEXT_SENSOR_TYPE_DEVICE_INFO, "DEVICE_INFO": TextSensorType.TEXT_SENSOR_TYPE_DEVICE_INFO,
"STATE_INFO": TextSensorType.TEXT_SENSOR_TYPE_STATE_INFO, "STATE_INFO": TextSensorType.TEXT_SENSOR_TYPE_STATE_INFO,
} }
@ -34,103 +36,6 @@ OptolinkTextSensor = optolink_ns.class_(
"OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent "OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent
) )
def check_address():
def validator_(config):
types_address_needed = [
"MAP",
"RAW",
"DAY_SCHEDULE",
"DAY_SCHEDULE_SYNCHRONIZED",
]
address_needed = config[CONF_TYPE] in types_address_needed
address_defined = CONF_ADDRESS in config
if address_needed and not address_defined:
raise cv.Invalid(
f"{CONF_ADDRESS} is required for this types: {types_address_needed}"
)
if not address_needed and address_defined:
raise cv.Invalid(
f"{CONF_ADDRESS} is only allowed for this types: {types_address_needed}"
)
return config
return validator_
def check_bytes():
def validator_(config):
types_bytes_needed = ["MAP", "RAW", "DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
bytes_needed = config[CONF_TYPE] in types_bytes_needed
bytes_defined = CONF_BYTES in config
if bytes_needed and not bytes_defined:
raise cv.Invalid(
f"{CONF_BYTES} is required for this types: {types_bytes_needed}"
)
if not bytes_needed and bytes_defined:
raise cv.Invalid(
f"{CONF_BYTES} is only allowed for this types: {types_bytes_needed}"
)
types_bytes_range_1_to_9 = ["MAP", "RAW"]
if config[CONF_TYPE] in types_bytes_range_1_to_9 and config[
CONF_BYTES
] not in range(0, 10):
raise cv.Invalid(
f"{CONF_BYTES} must be between 1 and 9 for this types: {types_bytes_range_1_to_9}"
)
types_bytes_day_schedule = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
if config[CONF_TYPE] in types_bytes_day_schedule and config[CONF_BYTES] not in [
56
]:
raise cv.Invalid(
f"{CONF_BYTES} must be 56 for this types: {types_bytes_day_schedule}"
)
return config
return validator_
def check_dow():
def validator_(config):
types_dow_needed = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
if config[CONF_TYPE] in types_dow_needed and CONF_DAY_OF_WEEK not in config:
raise cv.Invalid(
f"{CONF_DAY_OF_WEEK} is required for this types: {types_dow_needed}"
)
if config[CONF_TYPE] not in types_dow_needed and CONF_DAY_OF_WEEK in config:
raise cv.Invalid(
f"{CONF_DAY_OF_WEEK} is only allowed for this types: {types_dow_needed}"
)
return config
return validator_
def check_entity_id():
def validator_(config):
types_entitiy_id_needed = ["DAY_SCHEDULE_SYNCHRONIZED"]
if (
config[CONF_TYPE] in types_entitiy_id_needed
and CONF_ENTITY_ID not in config
):
raise cv.Invalid(
f"{CONF_ENTITY_ID} is required for this types: {types_entitiy_id_needed}"
)
if (
config[CONF_TYPE] not in types_entitiy_id_needed
and CONF_ENTITY_ID in config
):
raise cv.Invalid(
f"{CONF_ENTITY_ID} is only allowed for this types: {types_entitiy_id_needed}"
)
return config
return validator_
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
text_sensor.TEXT_SENSOR_SCHEMA.extend( text_sensor.TEXT_SENSOR_SCHEMA.extend(
{ {
@ -144,10 +49,9 @@ CONFIG_SCHEMA = cv.All(
) )
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
.extend(SENSOR_BASE_SCHEMA), .extend(SENSOR_BASE_SCHEMA),
check_address(), check_address_for_types(["MAP", "RAW", "DAY_SCHEDULE"]),
check_bytes(), check_bytes_for_types(["MAP", "RAW", "DAY_SCHEDULE"]),
check_dow(), check_dow_for_types(["DAY_SCHEDULE"]),
check_entity_id(),
) )
@ -161,6 +65,7 @@ async def to_code(config):
cg.add(var.set_type(config[CONF_TYPE])) cg.add(var.set_type(config[CONF_TYPE]))
if CONF_ADDRESS in config: if CONF_ADDRESS in config:
cg.add(var.set_address(config[CONF_ADDRESS])) cg.add(var.set_address(config[CONF_ADDRESS]))
if CONF_DIV_RATIO in config:
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
if CONF_BYTES in config: if CONF_BYTES in config:
cg.add(var.set_bytes(config[CONF_BYTES])) cg.add(var.set_bytes(config[CONF_BYTES]))

View file

@ -13,6 +13,12 @@ sensor:
icon: mdi:thermometer-chevron-down icon: mdi:thermometer-chevron-down
device_class: temperature device_class: temperature
update_interval: 120s update_interval: 120s
- platform: optolink
type: QUEUE_SIZE
name: Optolink Queue Size
icon: mdi:queue-first-in-last-out
entity_category: diagnostic
update_interval: 1s
text_sensor: text_sensor:
- platform: optolink - platform: optolink