mirror of
https://github.com/esphome/esphome.git
synced 2024-12-10 14:45:56 +01:00
text_sensor DAY_SCHEDULE_SYNCHRONIZED replaced with text DAY_SCHEDULE
This commit is contained in:
parent
bc00df307e
commit
164991e4f5
@ -21,6 +21,17 @@ MULTI_CONF = False
|
||||
optolink_ns = cg.esphome_ns.namespace("optolink")
|
||||
CONF_OPTOLINK_ID = "optolink_id"
|
||||
|
||||
DAY_OF_WEEK = {
|
||||
"MONDAY": 0,
|
||||
"TUESDAY": 1,
|
||||
"WEDNESDAY": 2,
|
||||
"THURSDAY": 3,
|
||||
"FRIDAY": 4,
|
||||
"SATURDAY": 5,
|
||||
"SUNDAY": 6,
|
||||
}
|
||||
CONF_DAY_OF_WEEK = "day_of_week"
|
||||
|
||||
OptolinkComponent = optolink_ns.class_("Optolink", cg.Component)
|
||||
CONF_OPTOLINK_ID = "optolink_id"
|
||||
SENSOR_BASE_SCHEMA = cv.Schema(
|
||||
|
@ -11,9 +11,6 @@ namespace optolink {
|
||||
|
||||
static const char *const TAG = "optolink.datapoint_component";
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
static std::vector<HassSubscription> hass_subscriptions_;
|
||||
|
||||
void DatapointComponent::setup_datapoint_() {
|
||||
switch (div_ratio_) {
|
||||
case 0:
|
||||
@ -261,38 +258,6 @@ void DatapointComponent::set_optolink_state_(const char *format, ...) {
|
||||
|
||||
std::string DatapointComponent::get_optolink_state_() { return optolink_->get_state(); }
|
||||
|
||||
void DatapointComponent::subscribe_hass_(const std::string &entity_id, const std::function<void(std::string)> &f) {
|
||||
for (auto &subscription : hass_subscriptions_) {
|
||||
if (subscription.entity_id == entity_id) {
|
||||
subscription.callbacks.push_back(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// NOLINTNEXTLINE
|
||||
HassSubscription subscription{entity_id, ""};
|
||||
subscription.callbacks.push_back(f);
|
||||
hass_subscriptions_.push_back(subscription);
|
||||
|
||||
#ifdef USE_API
|
||||
if (api::global_api_server != nullptr) {
|
||||
api::global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(), [entity_id](const std::string &state) {
|
||||
ESP_LOGD(TAG, "received schedule plan from HASS entity '%s': %s", entity_id.c_str(), state.c_str());
|
||||
for (auto &subscription : hass_subscriptions_) {
|
||||
if (subscription.last_state != state) {
|
||||
if (subscription.entity_id == entity_id) {
|
||||
subscription.last_state = state;
|
||||
for (const auto &callback : subscription.callbacks) {
|
||||
callback(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void conv2_100_F::encode(uint8_t *out, DPValue in) {
|
||||
int16_t tmp = floor((in.getFloat() * 100) + 0.5);
|
||||
out[1] = tmp >> 8;
|
||||
|
@ -11,12 +11,6 @@ namespace optolink {
|
||||
|
||||
class Optolink;
|
||||
|
||||
struct HassSubscription {
|
||||
std::string entity_id;
|
||||
std::string last_state;
|
||||
std::vector<std::function<void(std::string)>> callbacks;
|
||||
};
|
||||
|
||||
class DatapointComponent {
|
||||
public:
|
||||
DatapointComponent(Optolink *optolink, bool writeable = false) : dp_value_outstanding_((uint8_t) 0) {
|
||||
@ -55,8 +49,6 @@ class DatapointComponent {
|
||||
void set_optolink_state_(const char *format, ...);
|
||||
std::string get_optolink_state_();
|
||||
|
||||
void subscribe_hass_(const std::string &entity_id, const std::function<void(std::string)> &f);
|
||||
|
||||
private:
|
||||
const size_t max_retries_until_reset_ = 10;
|
||||
Optolink *optolink_;
|
||||
|
89
esphome/components/optolink/helpers.cpp
Normal file
89
esphome/components/optolink/helpers.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <string.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
static const char *const TAG = "optolink.helpers";
|
||||
|
||||
void rtrim(std::string &s) {
|
||||
if (s.empty())
|
||||
return;
|
||||
|
||||
std::string::iterator p;
|
||||
for (p = s.end(); p != s.begin() && *--p == ' ';)
|
||||
;
|
||||
|
||||
if (*p != ' ')
|
||||
p++;
|
||||
|
||||
s.erase(p, s.end());
|
||||
}
|
||||
|
||||
std::string decode_day_schedule(uint8_t *input) {
|
||||
char buffer[49];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int hour = input[i] >> 3;
|
||||
int minute = (input[i] & 0b111) * 10;
|
||||
if (input[i] != 0xFF) {
|
||||
sprintf(buffer + i * 6, "%02d:%02d ", hour, minute);
|
||||
} else {
|
||||
sprintf(buffer + i * 6, " ");
|
||||
}
|
||||
}
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
uint8_t *encode_day_schedule(const std::string &input, uint8_t *output) {
|
||||
char buffer[49];
|
||||
strncpy(buffer, input.c_str(), sizeof(buffer));
|
||||
buffer[sizeof(buffer) - 1] = 0x00;
|
||||
Time time_values[8];
|
||||
Time prev_time = {0, 0};
|
||||
int time_count = 0;
|
||||
|
||||
char *token = strtok(buffer, " ");
|
||||
while (token && time_count < 8) {
|
||||
Time current_time;
|
||||
// NOLINTNEXTLINE
|
||||
if (sscanf(token, "%d:%d", ¤t_time.hours, ¤t_time.minutes) == 2) {
|
||||
if (check_time_values(current_time) && check_time_sequence(prev_time, current_time)) {
|
||||
time_values[time_count++] = current_time;
|
||||
prev_time = current_time;
|
||||
} else {
|
||||
ESP_LOGE(
|
||||
TAG,
|
||||
"Time values should be in the format hh:mm and in increasing order within the range of 00:00 to 23:59");
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid time format");
|
||||
return nullptr;
|
||||
}
|
||||
token = strtok(nullptr, " ");
|
||||
}
|
||||
|
||||
if (time_count % 2) {
|
||||
ESP_LOGE(TAG, "Number of time values must be even");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (time_count < 8) {
|
||||
time_values[time_count++] = {31, 70};
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Time time = time_values[i];
|
||||
output[i] = (time.hours << 3) + (time.minutes / 10);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace optolink
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
31
esphome/components/optolink/helpers.h
Normal file
31
esphome/components/optolink/helpers.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
struct Time {
|
||||
int hours;
|
||||
int minutes;
|
||||
};
|
||||
|
||||
inline bool check_time_sequence(const Time &t1, const Time &t2) {
|
||||
return t2.hours > t1.hours || (t2.hours == t1.hours && t2.minutes >= t1.minutes);
|
||||
}
|
||||
|
||||
inline bool check_time_values(const Time &time) {
|
||||
return (time.hours >= 0 && time.hours <= 23) && (time.minutes >= 0 && time.minutes <= 59);
|
||||
}
|
||||
|
||||
void rtrim(std::string &s);
|
||||
|
||||
std::string decode_day_schedule(uint8_t *input);
|
||||
|
||||
uint8_t *encode_day_schedule(const std::string &input, uint8_t *output);
|
||||
|
||||
} // namespace optolink
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
62
esphome/components/optolink/text/__init__.py
Normal file
62
esphome/components/optolink/text/__init__.py
Normal file
@ -0,0 +1,62 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_BYTES,
|
||||
CONF_DIV_RATIO,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ID,
|
||||
CONF_TYPE,
|
||||
CONF_MODE,
|
||||
)
|
||||
from .. import (
|
||||
CONF_DAY_OF_WEEK,
|
||||
DAY_OF_WEEK,
|
||||
optolink_ns,
|
||||
CONF_OPTOLINK_ID,
|
||||
SENSOR_BASE_SCHEMA,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["optolink", "api"]
|
||||
CODEOWNERS = ["@j0ta29"]
|
||||
|
||||
TextType = optolink_ns.enum("TextType")
|
||||
TYPE = {"DAY_SCHEDULE": TextType.TEXT_TYPE_DAY_SCHEDULE}
|
||||
|
||||
OptolinkText = optolink_ns.class_("OptolinkText", text.Text, cg.PollingComponent)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text.TEXT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OptolinkText),
|
||||
cv.Optional(CONF_MODE, default="TEXT"): cv.enum(text.TEXT_MODES),
|
||||
cv.Required(CONF_TYPE): cv.enum(TYPE, upper=True),
|
||||
cv.Required(CONF_ADDRESS): cv.hex_uint32_t,
|
||||
cv.Required(CONF_BYTES): cv.one_of(56, int=True),
|
||||
cv.Required(CONF_DAY_OF_WEEK): cv.enum(DAY_OF_WEEK, upper=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(SENSOR_BASE_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
component = await cg.get_variable(config[CONF_OPTOLINK_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], component)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await text.register_text(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_div_ratio(config[CONF_DIV_RATIO]))
|
||||
if CONF_BYTES in config:
|
||||
cg.add(var.set_bytes(config[CONF_BYTES]))
|
||||
if CONF_DAY_OF_WEEK in config:
|
||||
cg.add(var.set_day_of_week(config[CONF_DAY_OF_WEEK]))
|
||||
if CONF_ENTITY_ID in config:
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
58
esphome/components/optolink/text/optolink_text.cpp
Normal file
58
esphome/components/optolink/text/optolink_text.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "optolink_text.h"
|
||||
#include "../optolink.h"
|
||||
#include "../datapoint_component.h"
|
||||
#include "../helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
static const char *const TAG = "optolink.text";
|
||||
|
||||
void OptolinkText::setup() {
|
||||
switch (type_) {
|
||||
case TEXT_TYPE_DAY_SCHEDULE:
|
||||
set_writeable(true);
|
||||
set_div_ratio(0);
|
||||
set_bytes(8);
|
||||
set_address(get_address_() + 8 * dow_);
|
||||
traits.set_max_length(48);
|
||||
traits.set_pattern("(((([0-1]?[0-9]|2[0-3]):[0-5]0)( |$)){2})*");
|
||||
break;
|
||||
}
|
||||
setup_datapoint_();
|
||||
};
|
||||
|
||||
void OptolinkText::control(const std::string &value) {
|
||||
ESP_LOGE(TAG, "control %s", value.c_str());
|
||||
|
||||
ESP_LOGD(TAG, "update for schedule plan for component %s: %s", get_component_name().c_str(), value.c_str());
|
||||
uint8_t buffer[8];
|
||||
uint8_t *data = encode_day_schedule(value, buffer);
|
||||
if (data) {
|
||||
write_datapoint_value_(data, 8);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "not changing any value of datapoint %s", get_component_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void OptolinkText::datapoint_value_changed(uint8_t *value, size_t length) {
|
||||
switch (type_) {
|
||||
case TEXT_TYPE_DAY_SCHEDULE:
|
||||
if (length == 8) {
|
||||
auto schedule = decode_day_schedule(value);
|
||||
rtrim(schedule);
|
||||
publish_state(schedule);
|
||||
} else {
|
||||
unfitting_value_type_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace optolink
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
37
esphome/components/optolink/text/optolink_text.h
Normal file
37
esphome/components/optolink/text/optolink_text.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/components/text/text.h"
|
||||
#include "../optolink.h"
|
||||
#include "../datapoint_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
enum TextType { TEXT_TYPE_DAY_SCHEDULE };
|
||||
|
||||
class OptolinkText : public DatapointComponent, public esphome::text::Text, public esphome::PollingComponent {
|
||||
public:
|
||||
OptolinkText(Optolink *optolink) : DatapointComponent(optolink) {}
|
||||
|
||||
void set_type(TextType type) { type_ = type; }
|
||||
void set_day_of_week(int dow) { dow_ = dow; }
|
||||
|
||||
protected:
|
||||
void setup() override;
|
||||
void update() override { datapoint_read_request_(); }
|
||||
void control(const std::string &value);
|
||||
|
||||
const StringRef &get_component_name() override { return get_name(); }
|
||||
void datapoint_value_changed(uint8_t *value, size_t length) override;
|
||||
|
||||
private:
|
||||
TextType type_;
|
||||
int dow_ = 0;
|
||||
};
|
||||
|
||||
} // namespace optolink
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -7,32 +7,28 @@ from esphome.const import (
|
||||
CONF_DIV_RATIO,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ID,
|
||||
CONF_MODE,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from .. import (
|
||||
CONF_DAY_OF_WEEK,
|
||||
DAY_OF_WEEK,
|
||||
optolink_ns,
|
||||
CONF_OPTOLINK_ID,
|
||||
SENSOR_BASE_SCHEMA,
|
||||
)
|
||||
from .. import optolink_ns, CONF_OPTOLINK_ID, SENSOR_BASE_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["optolink", "api"]
|
||||
CODEOWNERS = ["@j0ta29"]
|
||||
|
||||
TextSensorMode = optolink_ns.enum("TextSensorMode")
|
||||
MODE = {
|
||||
"MAP": TextSensorMode.MAP,
|
||||
"RAW": TextSensorMode.RAW,
|
||||
"DAY_SCHEDULE": TextSensorMode.DAY_SCHEDULE,
|
||||
"DAY_SCHEDULE_SYNCHRONIZED": TextSensorMode.DAY_SCHEDULE_SYNCHRONIZED,
|
||||
"DEVICE_INFO": TextSensorMode.DEVICE_INFO,
|
||||
"STATE_INFO": TextSensorMode.STATE_INFO,
|
||||
TextSensorType = optolink_ns.enum("TextSensorType")
|
||||
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,
|
||||
}
|
||||
DAY_OF_WEEK = {
|
||||
"MONDAY": 0,
|
||||
"TUESDAY": 1,
|
||||
"WEDNESDAY": 2,
|
||||
"THURSDAY": 3,
|
||||
"FRIDAY": 4,
|
||||
"SATURDAY": 5,
|
||||
"SUNDAY": 6,
|
||||
}
|
||||
CONF_DAY_OF_WEEK = "day_of_week"
|
||||
|
||||
OptolinkTextSensor = optolink_ns.class_(
|
||||
"OptolinkTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||
@ -41,21 +37,21 @@ OptolinkTextSensor = optolink_ns.class_(
|
||||
|
||||
def check_address():
|
||||
def validator_(config):
|
||||
modes_address_needed = [
|
||||
types_address_needed = [
|
||||
"MAP",
|
||||
"RAW",
|
||||
"DAY_SCHEDULE",
|
||||
"DAY_SCHEDULE_SYNCHRONIZED",
|
||||
]
|
||||
address_needed = config[CONF_MODE] in modes_address_needed
|
||||
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 in this modes: {modes_address_needed}"
|
||||
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 in this modes mode: {modes_address_needed}"
|
||||
f"{CONF_ADDRESS} is only allowed for this types: {types_address_needed}"
|
||||
)
|
||||
return config
|
||||
|
||||
@ -64,32 +60,32 @@ def check_address():
|
||||
|
||||
def check_bytes():
|
||||
def validator_(config):
|
||||
modes_bytes_needed = ["MAP", "RAW", "DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
|
||||
bytes_needed = config[CONF_MODE] in modes_bytes_needed
|
||||
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 in this modes: {modes_bytes_needed}"
|
||||
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 in this modes: {modes_bytes_needed}"
|
||||
f"{CONF_BYTES} is only allowed for this types: {types_bytes_needed}"
|
||||
)
|
||||
|
||||
modes_bytes_range_1_to_9 = ["MAP", "RAW"]
|
||||
if config[CONF_MODE] in modes_bytes_range_1_to_9 and config[
|
||||
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 modes: {modes_bytes_range_1_to_9}"
|
||||
f"{CONF_BYTES} must be between 1 and 9 for this types: {types_bytes_range_1_to_9}"
|
||||
)
|
||||
|
||||
modes_bytes_day_schedule = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
|
||||
if config[CONF_MODE] in modes_bytes_day_schedule and config[CONF_BYTES] not in [
|
||||
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 modes: {modes_bytes_day_schedule}"
|
||||
f"{CONF_BYTES} must be 56 for this types: {types_bytes_day_schedule}"
|
||||
)
|
||||
|
||||
return config
|
||||
@ -99,14 +95,14 @@ def check_bytes():
|
||||
|
||||
def check_dow():
|
||||
def validator_(config):
|
||||
modes_dow_needed = ["DAY_SCHEDULE", "DAY_SCHEDULE_SYNCHRONIZED"]
|
||||
if config[CONF_MODE] in modes_dow_needed and CONF_DAY_OF_WEEK not in 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 in this modes: {modes_dow_needed}"
|
||||
f"{CONF_DAY_OF_WEEK} is required for this types: {types_dow_needed}"
|
||||
)
|
||||
if config[CONF_MODE] not in modes_dow_needed and CONF_DAY_OF_WEEK in config:
|
||||
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 in this modes: {modes_dow_needed}"
|
||||
f"{CONF_DAY_OF_WEEK} is only allowed for this types: {types_dow_needed}"
|
||||
)
|
||||
return config
|
||||
|
||||
@ -115,20 +111,20 @@ def check_dow():
|
||||
|
||||
def check_entity_id():
|
||||
def validator_(config):
|
||||
modes_entitiy_id_needed = ["DAY_SCHEDULE_SYNCHRONIZED"]
|
||||
types_entitiy_id_needed = ["DAY_SCHEDULE_SYNCHRONIZED"]
|
||||
if (
|
||||
config[CONF_MODE] in modes_entitiy_id_needed
|
||||
config[CONF_TYPE] in types_entitiy_id_needed
|
||||
and CONF_ENTITY_ID not in config
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_ENTITY_ID} is required in this modes: {modes_entitiy_id_needed}"
|
||||
f"{CONF_ENTITY_ID} is required for this types: {types_entitiy_id_needed}"
|
||||
)
|
||||
if (
|
||||
config[CONF_MODE] not in modes_entitiy_id_needed
|
||||
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 in this modes: {modes_entitiy_id_needed}"
|
||||
f"{CONF_ENTITY_ID} is only allowed for this types: {types_entitiy_id_needed}"
|
||||
)
|
||||
return config
|
||||
|
||||
@ -139,7 +135,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OptolinkTextSensor),
|
||||
cv.Required(CONF_MODE): cv.enum(MODE, upper=True),
|
||||
cv.Required(CONF_TYPE): cv.enum(TYPE, upper=True),
|
||||
cv.Optional(CONF_ADDRESS): cv.hex_uint32_t,
|
||||
cv.Optional(CONF_BYTES): cv.uint8_t,
|
||||
cv.Optional(CONF_DAY_OF_WEEK): cv.enum(DAY_OF_WEEK, upper=True),
|
||||
@ -162,7 +158,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
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_div_ratio(config[CONF_DIV_RATIO]))
|
||||
|
@ -4,107 +4,31 @@
|
||||
#include "optolink_text_sensor.h"
|
||||
#include "../optolink.h"
|
||||
#include "../datapoint_component.h"
|
||||
#include "../helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
static const char *const TAG = "optolink.text_sensor";
|
||||
|
||||
struct Time {
|
||||
int hours;
|
||||
int minutes;
|
||||
};
|
||||
|
||||
bool check_time_sequence(const Time &t1, const Time &t2) {
|
||||
return t2.hours > t1.hours || (t2.hours == t1.hours && t2.minutes >= t1.minutes);
|
||||
}
|
||||
|
||||
bool check_time_values(const Time &time) {
|
||||
return (time.hours >= 0 && time.hours <= 23) && (time.minutes >= 0 && time.minutes <= 59);
|
||||
}
|
||||
|
||||
uint8_t *encode_time_string(const std::string &input) {
|
||||
char buffer[49];
|
||||
strncpy(buffer, input.c_str(), sizeof(buffer));
|
||||
buffer[sizeof(buffer) - 1] = 0x00;
|
||||
Time time_values[8];
|
||||
Time prev_time = {0, 0};
|
||||
int time_count = 0;
|
||||
|
||||
char *token = strtok(buffer, " ");
|
||||
while (token && time_count < 8) {
|
||||
Time current_time;
|
||||
// NOLINTNEXTLINE
|
||||
if (sscanf(token, "%d:%d", ¤t_time.hours, ¤t_time.minutes) == 2) {
|
||||
if (check_time_values(current_time) && check_time_sequence(prev_time, current_time)) {
|
||||
time_values[time_count++] = current_time;
|
||||
prev_time = current_time;
|
||||
} else {
|
||||
ESP_LOGE(
|
||||
TAG,
|
||||
"Time values should be in the format hh:mm and in increasing order within the range of 00:00 to 23:59");
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid time format");
|
||||
return nullptr;
|
||||
}
|
||||
token = strtok(nullptr, " ");
|
||||
}
|
||||
|
||||
if (time_count % 2) {
|
||||
ESP_LOGE(TAG, "Number of time values must be even");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (time_count < 8) {
|
||||
time_values[time_count++] = {31, 70};
|
||||
}
|
||||
|
||||
static uint8_t data[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Time time = time_values[i];
|
||||
data[i] = (time.hours << 3) + (time.minutes / 10);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void OptolinkTextSensor::setup() {
|
||||
switch (mode_) {
|
||||
case MAP:
|
||||
switch (type_) {
|
||||
case TEXT_SENSOR_TYPE_MAP:
|
||||
break;
|
||||
case RAW:
|
||||
case TEXT_SENSOR_TYPE_RAW:
|
||||
set_div_ratio(0);
|
||||
break;
|
||||
case DAY_SCHEDULE:
|
||||
case TEXT_SENSOR_TYPE_DAY_SCHEDULE:
|
||||
set_div_ratio(0);
|
||||
set_bytes(8);
|
||||
set_address(get_address_() + 8 * dow_);
|
||||
break;
|
||||
case DAY_SCHEDULE_SYNCHRONIZED:
|
||||
set_writeable(true);
|
||||
set_div_ratio(0);
|
||||
set_bytes(8);
|
||||
set_address(get_address_() + 8 * dow_);
|
||||
ESP_LOGI(TAG, "subscribing to schedule plan from HASS entity '%s' for component %s", this->entity_id_.c_str(),
|
||||
get_component_name().c_str());
|
||||
subscribe_hass_(entity_id_, [this](const std::string &state) {
|
||||
ESP_LOGD(TAG, "update for schedule plan for component %s: %s", get_component_name().c_str(), state.c_str());
|
||||
uint8_t *data = encode_time_string(state);
|
||||
if (data) {
|
||||
write_datapoint_value_(data, 8);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "not changing any value of datapoint %s", get_component_name().c_str());
|
||||
}
|
||||
});
|
||||
break;
|
||||
case DEVICE_INFO:
|
||||
case TEXT_SENSOR_TYPE_DEVICE_INFO:
|
||||
set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC);
|
||||
set_bytes(4);
|
||||
set_address(0x00f8);
|
||||
break;
|
||||
case STATE_INFO:
|
||||
case TEXT_SENSOR_TYPE_STATE_INFO:
|
||||
set_entity_category(esphome::ENTITY_CATEGORY_DIAGNOSTIC);
|
||||
return; // no datapoint setup!
|
||||
}
|
||||
@ -112,7 +36,7 @@ void OptolinkTextSensor::setup() {
|
||||
};
|
||||
|
||||
void OptolinkTextSensor::update() {
|
||||
if (mode_ == STATE_INFO) {
|
||||
if (type_ == TEXT_SENSOR_TYPE_STATE_INFO) {
|
||||
publish_state(get_optolink_state_());
|
||||
} else {
|
||||
datapoint_read_request_();
|
||||
@ -120,39 +44,30 @@ void OptolinkTextSensor::update() {
|
||||
}
|
||||
|
||||
void OptolinkTextSensor::datapoint_value_changed(uint8_t *value, size_t length) {
|
||||
switch (mode_) {
|
||||
case RAW:
|
||||
switch (type_) {
|
||||
case TEXT_SENSOR_TYPE_RAW:
|
||||
publish_state(std::string((const char *) value));
|
||||
break;
|
||||
case DAY_SCHEDULE:
|
||||
case DAY_SCHEDULE_SYNCHRONIZED:
|
||||
case TEXT_SENSOR_TYPE_DAY_SCHEDULE:
|
||||
if (length == 8) {
|
||||
char buffer[6 * length + 1];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int hour = value[i] >> 3;
|
||||
int minute = (value[i] & 0b111) * 10;
|
||||
if (value[i] != 0xFF) {
|
||||
sprintf(buffer + i * 6, "%02d:%02d ", hour, minute);
|
||||
} else {
|
||||
sprintf(buffer + i * 6, " ");
|
||||
}
|
||||
}
|
||||
publish_state(buffer);
|
||||
auto schedule = decode_day_schedule(value);
|
||||
rtrim(schedule);
|
||||
publish_state(schedule);
|
||||
} else {
|
||||
unfitting_value_type_();
|
||||
}
|
||||
break;
|
||||
case DEVICE_INFO:
|
||||
case STATE_INFO:
|
||||
case MAP:
|
||||
case TEXT_SENSOR_TYPE_DEVICE_INFO:
|
||||
case TEXT_SENSOR_TYPE_STATE_INFO:
|
||||
case TEXT_SENSOR_TYPE_MAP:
|
||||
unfitting_value_type_();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
void OptolinkTextSensor::datapoint_value_changed(uint32_t value) {
|
||||
switch (mode_) {
|
||||
case DEVICE_INFO: {
|
||||
switch (type_) {
|
||||
case TEXT_SENSOR_TYPE_DEVICE_INFO: {
|
||||
uint8_t *bytes = (uint8_t *) &value;
|
||||
uint16_t tmp = esphome::byteswap(*((uint16_t *) bytes));
|
||||
std::string geraetekennung = esphome::format_hex_pretty(&tmp, 1);
|
||||
|
@ -9,7 +9,13 @@
|
||||
namespace esphome {
|
||||
namespace optolink {
|
||||
|
||||
enum TextSensorMode { MAP, RAW, DAY_SCHEDULE, DAY_SCHEDULE_SYNCHRONIZED, DEVICE_INFO, STATE_INFO };
|
||||
enum TextSensorType {
|
||||
TEXT_SENSOR_TYPE_MAP,
|
||||
TEXT_SENSOR_TYPE_RAW,
|
||||
TEXT_SENSOR_TYPE_DAY_SCHEDULE,
|
||||
TEXT_SENSOR_TYPE_DEVICE_INFO,
|
||||
TEXT_SENSOR_TYPE_STATE_INFO
|
||||
};
|
||||
|
||||
class OptolinkTextSensor : public DatapointComponent,
|
||||
public esphome::text_sensor::TextSensor,
|
||||
@ -17,7 +23,7 @@ class OptolinkTextSensor : public DatapointComponent,
|
||||
public:
|
||||
OptolinkTextSensor(Optolink *optolink) : DatapointComponent(optolink) {}
|
||||
|
||||
void set_mode(TextSensorMode mode) { mode_ = mode; }
|
||||
void set_type(TextSensorType type) { type_ = type; }
|
||||
void set_day_of_week(int dow) { dow_ = dow; }
|
||||
void set_entity_id(const std::string &entity_id) { entity_id_ = entity_id; }
|
||||
|
||||
@ -33,7 +39,7 @@ class OptolinkTextSensor : public DatapointComponent,
|
||||
void datapoint_value_changed(uint8_t *value, size_t length) override;
|
||||
|
||||
private:
|
||||
TextSensorMode mode_ = MAP;
|
||||
TextSensorType type_ = TEXT_SENSOR_TYPE_MAP;
|
||||
int dow_ = 0;
|
||||
std::string entity_id_;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user