Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
platform_light.cpp
Go to the documentation of this file.
1/// @file platform_light.cpp
2/// @brief Binary or dimmable light entity for IO-Homecontrol devices.
3/// @ingroup hioc_platforms
4
5#include "platform_light.h"
6#include "hub_internal.h"
7#include "esphome/core/log.h"
8
9namespace esphome {
10namespace home_io_control {
11
12static const char *const TAG = "home_io_control.light";
13
15 // Lights need the shared device registry so discovery/status handling can find their node
16 // metadata the same way covers do; the delayed initial poll matches the cover path.
17 this->register_device_binding_(this, /*inverted=*/false, [this](const std::string &id, const IoDevice &dev) {
18 this->on_device_update_(id, dev);
19 });
20 // `dimmable` is a light-only YAML choice, not part of the shared add_device() signature every
21 // platform calls above — stamp it onto the now-registered device separately so profile-name
22 // logging (see hub_operations.cpp's operation_profile_name()) can tell a dimmable light from a
23 // plain binary one.
24 this->parent_->set_device_dimmable(this->device_id_, this->dimmable_);
25}
26
27light::LightTraits IOHomeLight::get_traits() {
28 auto traits = light::LightTraits();
29 traits.set_supported_color_modes({this->dimmable_ ? light::ColorMode::BRIGHTNESS : light::ColorMode::ON_OFF});
30 return traits;
31}
32
33void IOHomeLight::write_state(light::LightState *state) {
34 if (this->suppress_write_) {
35 this->suppress_write_ = false;
36 return;
37 }
38
39 if (this->dimmable_) {
40 float brightness = 0.0F;
41 // Deliberately current_values.as_brightness(), not LightState::current_values_as_brightness()
42 // — the latter applies gamma_correct (default 2.8 for a BRIGHTNESS_ONLY light), which is
43 // meant for perceptually-correcting a PWM/LED output and silently mangles the value here (a
44 // real bug caught on hardware: HA-set 50% arrived on the wire as ~15%, matching 0.5^2.8). We
45 // want the exact linear percentage the user set — the Somfy device has its own dimming
46 // behavior, if any, and gamma-correcting on top of that would be wrong regardless of curve.
47 state->current_values.as_brightness(&brightness);
48 // Convert HA brightness (0.0-1.0) to IO position (0-100) via detail::round_percent()
49 // (hub_internal.h — rounds rather than truncates, see its doc comment): this device family's
50 // convention is 0=full brightness, 100=off — the same inverted mapping platform_cover.cpp
51 // uses for non-inverted covers (HA 1.0 open -> IO 0), confirmed on real dimmable hardware.
52 auto const io_pos = detail::round_percent(1.0F - brightness);
53 this->parent_->queue_set_light_position(this->device_id_, io_pos);
54 return;
55 }
56
57 bool on = false;
58 state->current_values_as_binary(&on);
59 this->parent_->queue_set_light_state(this->device_id_, on);
60}
61
62void IOHomeLight::on_device_update_(const std::string &id, const IoDevice &dev) {
63 // We only publish a stable HA state once the device reports a settled status (shared filter),
64 // and only once the LightState object is available.
65 if (this->state_ == nullptr || !this->passes_binary_update_filter_(id, dev))
66 return;
67
68 if (this->dimmable_) {
69 // Same inverted 0-100 IO position field as the binary path, just read as a continuous value
70 // instead of collapsed at the <50 threshold.
71 const float brightness = 1.0F - (dev.position / 100.0F);
72 const bool on = brightness > 0.0F;
73 if (this->state_->current_values.is_on() == on && this->state_->remote_values.is_on() == on &&
74 this->state_->current_values.get_brightness() == brightness &&
75 this->state_->remote_values.get_brightness() == brightness)
76 return;
77
78 this->suppress_write_ = true;
79 auto call = this->state_->make_call();
80 call.set_state(on);
81 call.set_brightness(brightness);
82 call.set_save(false);
83 call.perform();
84 return;
85 }
86
87 // Binary endpoints report on/off via the shared 0-100 position field.
88 // Position < 50 is treated as "on", >= 50 as "off".
90 if (this->state_->current_values.is_on() == on && this->state_->remote_values.is_on() == on)
91 return;
92
93 // Update via LightState's call API, suppressing the write_state() echo to avoid re-sending.
94 this->suppress_write_ = true;
95 auto call = this->state_->make_call();
96 call.set_state(on);
97 call.set_save(false);
98 call.perform();
99}
100
102 ESP_LOGCONFIG(TAG, "IO-Homecontrol %s Light", this->dimmable_ ? "Dimmable" : "Binary");
103 ESP_LOGCONFIG(TAG, " Device ID: %s", this->device_id_.c_str());
104 this->log_poll_interval_config_(TAG);
105}
106
107} // namespace home_io_control
108} // 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.
void write_state(light::LightState *state) override
bool suppress_write_
Guard so that an inbound radio status (or LightState's own boot-time restore_mode push — see setup_st...
void on_device_update_(const std::string &id, const IoDevice &dev)
Handle inbound device status updates.
void dump_config() override
Dump configuration to log.
light::LightTraits get_traits() override
Return traits: ColorMode::BRIGHTNESS if dimmable, else ColorMode::ON_OFF.
void setup() override
Initialize the light entity.
Internal helpers shared by the hub implementation .cpp files.
constexpr float BINARY_ENTITY_ON_POSITION_THRESHOLD
Shared 0-100 cutoff: values below this mean binary "on".
uint8_t round_percent(float fraction)
Convert a 0.0-1.0 HA fraction (position, tilt, or brightness) to a 0-100 IO percent.
static constexpr const char * TAG
Binary or dimmable light entity for IO‑Homecontrol devices.
Runtime state of a paired IO‑Homecontrol device.
float position
Current position: 0=open, 100=closed, or UNKNOWN_POSITION.