Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
platform_fan.cpp
Go to the documentation of this file.
1/// @file platform_fan.cpp
2/// @brief Experimental fan entity for IO-Homecontrol exterior heating (a heat level, 0-100 %).
3/// @ingroup hioc_platforms
4
5#include "platform_fan.h"
6
7#include "entity_helpers.h"
9
10#include "esphome/core/log.h"
11
12#include <algorithm>
13#include <cmath>
14
15namespace esphome {
16namespace home_io_control {
17
18static const char *const TAG = "home_io_control.fan";
19
20namespace {
21
22/// Number of speed steps: one per percent of heat.
23constexpr int HEAT_STEPS = 100;
24
25} // namespace
26
28 // A known device of another family is a configuration mistake; an unknown type may still resolve
29 // at discovery, as on the other platforms.
30 if (this->device_type_ != DeviceType::UNKNOWN &&
32 ESP_LOGE(TAG,
33 "Device %s is declared as %s, which a fan entity cannot drive (it drives exterior heating) — disabling it",
34 this->device_id_.c_str(), format_device_kind(this->device_type_, this->subtype_).c_str());
35 this->mark_failed();
36 return;
37 }
38 this->register_device_binding_(this, /*inverted=*/false, [this](const std::string &id, const IoDevice &dev) {
39 this->on_device_update_(id, dev);
40 });
41}
42
43fan::FanTraits IOHomeFan::get_traits() {
44 // Every exterior heater is sent the main parameter only, but only subtype 58 is on/off with no level.
45 const bool has_level = this->subtype_ != SUBTYPE_MAIN_PARAMETER_ONLY;
46 return fan::FanTraits(/*oscillation=*/false, /*speed=*/has_level, /*direction=*/false, HEAT_STEPS);
47}
48
49void IOHomeFan::control(const fan::FanCall &call) {
50 const bool has_level = this->get_traits().supports_speed();
51 const auto state = call.get_state();
52 const auto speed = call.get_speed();
53
54 int heat = 0;
55 if (state.has_value() && !*state) {
56 heat = 0;
57 } else if (speed.has_value() && has_level) {
58 heat = std::clamp(*speed, 0, HEAT_STEPS);
59 } else if (state.has_value()) {
60 // On without a level: the last level the heater was at, else full heat.
61 heat = (has_level && this->speed > 0) ? this->speed : HEAT_STEPS;
62 } else {
63 return;
64 }
65
66 // Heat p % is IO position 100 - p, the dimmable light's mapping (detail::round_percent rounds).
67 const auto io_position = detail::round_percent(1.0F - (static_cast<float>(heat) / static_cast<float>(HEAT_STEPS)));
68 this->parent_->queue_set_fan_position(this->device_id_, io_position);
69
70 // The commanded state stands in until the device's status reply replaces it.
71 this->state = heat > 0;
72 if (heat > 0 && has_level)
73 this->speed = heat;
74 this->publish_state();
75}
76
77void IOHomeFan::on_device_update_(const std::string &id, const IoDevice &dev) {
78 // Publish a stable state only: the shared filter wants a known position and a device at rest
79 // (an exterior heater has no motion, so a status reply always counts as settled).
80 if (!this->passes_binary_update_filter_(id, dev))
81 return;
82 const int heat = std::clamp(static_cast<int>(std::lround(100.0F - dev.position)), 0, HEAT_STEPS);
83 const bool on = heat > 0;
84 const bool has_level = this->get_traits().supports_speed();
85 const int speed = (on && has_level) ? heat : this->speed; // an off heater keeps its last level
86 if (this->state == on && this->speed == speed)
87 return;
88 this->state = on;
89 this->speed = speed;
90 this->publish_state();
91}
92
94 ESP_LOGCONFIG(TAG, "IO-Homecontrol Exterior Heating Fan:");
95 ESP_LOGCONFIG(TAG, " Device ID: %s", this->device_id_.c_str());
97 ESP_LOGCONFIG(TAG, " Status: experimental, unvalidated on hardware");
98}
99
100} // namespace home_io_control
101} // namespace esphome
void register_device_binding_(Component *self, bool inverted, std::function< void(const std::string &, const IoDevice &)> on_update)
Perform the shared setup() registration ritual.
void log_poll_interval_config_(const char *tag) const
Emit the shared two-branch poll-interval line for dump_config().
bool passes_binary_update_filter_(const std::string &id, const IoDevice &dev) const
Shared inbound-update guard for binary endpoints.
virtual void queue_set_fan_position(const std::string &device_id, uint8_t position)
Async form of set_fan_position() that keeps radio work serialized on the main loop.
fan::FanTraits get_traits() override
Traits: a 100-step speed (the heat level) unless the device takes the main parameter only (subtype 58...
void dump_config() override
Dump configuration to the log.
void on_device_update_(const std::string &id, const IoDevice &dev)
Show a settled device status: heat level = 100 - position.
void setup() override
Register the entity with the shared hub and schedule the first status poll.
void control(const fan::FanCall &call) override
Apply a Home Assistant fan request: a heat level, or on/off.
Conversions and renderers shared by the hub and its Home Assistant entities.
uint8_t round_percent(float fraction)
Convert a 0.0-1.0 HA fraction (position, tilt, or brightness) to a 0-100 IO percent.
DeviceCapabilityClass device_capability_class(DeviceType type)
Map a raw IO‑Homecontrol type to the closest ESPHome/Home Assistant entity family.
static constexpr const char * TAG
@ UNKNOWN
Unknown/unspecified device.
@ HEATER_LEVEL
Heater with a heat level in the position field (exterior heating).
std::string format_device_kind(DeviceType type, uint8_t subtype)
Human-readable description of a (type, subtype) for logs and reports.
static const char *const TAG
Experimental fan entity for IO-Homecontrol exterior heating (a heat level, 0-100 %).
The IO node-type and subtype table: names, entity class, platform and behaviour flags.
Runtime state of a paired IO‑Homecontrol device.
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.