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.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_BYTES,
CONF_DIV_RATIO,
CONF_ID,
CONF_LOGGER,
CONF_PROTOCOL,
CONF_RX_PIN,
CONF_TX_PIN,
CONF_TYPE,
CONF_UPDATE_INTERVAL,
)
from esphome.core import CORE
CODEOWNERS = ["@j0ta29"]
DEPENDENCIES = []
DEPENDENCIES = ["sensor"]
AUTO_LOAD = []
MULTI_CONF = False
@ -32,6 +35,73 @@ 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)
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(); }
int DatapointComponent::get_optolink_queue_size_() { return optolink_->get_queue_size(); };
void conv2_100_F::encode(uint8_t *out, DPValue in) {
int16_t tmp = floor((in.getFloat() * 100) + 0.5);
out[1] = tmp >> 8;

View file

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

View file

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

View file

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

View file

@ -7,27 +7,42 @@ from esphome.const import (
CONF_DIV_RATIO,
CONF_ID,
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"]
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", sensor.Sensor, cg.PollingComponent
)
CONFIG_SCHEMA = (
CONFIG_SCHEMA = cv.All(
sensor.SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(OptolinkSensor),
cv.Required(CONF_ADDRESS): cv.hex_uint32_t,
cv.Required(CONF_BYTES): cv.one_of(1, 2, 4, int=True),
cv.Optional(CONF_TYPE, default="DATAPOINT"): cv.enum(TYPE, upper=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_,
}
)
.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 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]))
if CONF_BYTES in config:
cg.add(var.set_bytes(config[CONF_BYTES]))
if CONF_DIV_RATIO in config:
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
if CONF_MIN_VALUE in config:
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))

View file

@ -1,6 +1,7 @@
#ifdef USE_ARDUINO
#include "optolink_sensor.h"
#include "esphome/core/application.h"
#include "../optolink.h"
namespace esphome {
@ -8,7 +9,26 @@ namespace optolink {
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
void OptolinkSensor::datapoint_value_changed(uint8_t value) {

View file

@ -10,17 +10,20 @@
namespace esphome {
namespace optolink {
enum SensorType { SENSOR_TYPE_DATAPOINT, SENSOR_TYPE_QUEUE_SIZE };
class OptolinkSensor : public DatapointComponent, public esphome::sensor::Sensor, public esphome::PollingComponent {
public:
OptolinkSensor(Optolink *optolink) : DatapointComponent(optolink) {
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:
void setup() override { setup_datapoint_(); }
void update() override { datapoint_read_request_(); }
void setup() override;
void update() override;
const StringRef &get_component_name() override { return get_name(); }
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;
private:
SensorType type_ = SENSOR_TYPE_DATAPOINT;
float min_value_ = -FLT_MAX;
};
} // namespace optolink

View file

@ -12,6 +12,9 @@ from esphome.const import (
from .. import (
CONF_DAY_OF_WEEK,
DAY_OF_WEEK,
check_address_for_types,
check_bytes_for_types,
check_dow_for_types,
optolink_ns,
CONF_OPTOLINK_ID,
SENSOR_BASE_SCHEMA,
@ -25,7 +28,6 @@ TYPE = {
"MAP": TextSensorType.TEXT_SENSOR_TYPE_MAP,
"RAW": TextSensorType.TEXT_SENSOR_TYPE_RAW,
"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,
"STATE_INFO": TextSensorType.TEXT_SENSOR_TYPE_STATE_INFO,
}
@ -34,103 +36,6 @@ OptolinkTextSensor = optolink_ns.class_(
"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(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
@ -144,10 +49,9 @@ CONFIG_SCHEMA = cv.All(
)
.extend(cv.COMPONENT_SCHEMA)
.extend(SENSOR_BASE_SCHEMA),
check_address(),
check_bytes(),
check_dow(),
check_entity_id(),
check_address_for_types(["MAP", "RAW", "DAY_SCHEDULE"]),
check_bytes_for_types(["MAP", "RAW", "DAY_SCHEDULE"]),
check_dow_for_types(["DAY_SCHEDULE"]),
)
@ -161,6 +65,7 @@ async def to_code(config):
cg.add(var.set_type(config[CONF_TYPE]))
if CONF_ADDRESS in config:
cg.add(var.set_address(config[CONF_ADDRESS]))
if CONF_DIV_RATIO in config:
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
if CONF_BYTES in config:
cg.add(var.set_bytes(config[CONF_BYTES]))

View file

@ -13,6 +13,12 @@ sensor:
icon: mdi:thermometer-chevron-down
device_class: temperature
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:
- platform: optolink