1+ #pragma once
2+
13#include " esphome.h"
4+ #include " esphome/core/component.h"
5+ #include " esphome/core/log.h"
6+ #include " esphome/components/cover/cover.h"
7+ #include " esphome/components/button/button.h"
8+ #include " esphome/components/api/custom_api_device.h"
29
310// Goals
411// - Eventual consistency; retry over a longer period of time.
1522#define CLOCK_WIDTH 360
1623#define LOCKOUT_DELAY_MS 200
1724
18- static const char *TAG = " raex_blind_tx" ;
25+ namespace esphome {
26+ namespace raex_blind_tx {
27+
28+ static const char *const TAG = " raex_blind_tx" ;
1929
2030enum _raex_action {
2131 TX_RAEX_ACTION_UP = 254 ,
@@ -63,7 +73,7 @@ RaexMessage::RaexMessage(
6373 action_id (action_id) {};
6474
6575
66- class RaexBlindTransmitComponent : public Component , public CustomAPIDevice {
76+ class RaexBlindTX : public Component , public api :: CustomAPIDevice {
6777 private:
6878 std::map<uint32_t , RaexMessage*> pending_messages;
6979 int lockout_until = 0 ;
@@ -75,9 +85,9 @@ class RaexBlindTransmitComponent : public Component, public CustomAPIDevice {
7585 // think of it as the setup() call in Arduino
7686 pinMode (3 , OUTPUT );
7787
78- register_service (&RaexBlindTransmitComponent ::transmit, " transmit" ,
88+ register_service (&RaexBlindTX ::transmit, " transmit" ,
7989 {" remote_id" , " channel_id" , " action" });
80- register_service (&RaexBlindTransmitComponent ::transmit_custom, " transmit_custom" ,
90+ register_service (&RaexBlindTX ::transmit_custom, " transmit_custom" ,
8191 {" remote_id" , " channel_id" , " action" , " blocks" , " retries" });
8292 }
8393
@@ -256,3 +266,126 @@ class RaexBlindTransmitComponent : public Component, public CustomAPIDevice {
256266 }
257267};
258268
269+ class RaexBlindCover : public cover ::Cover, public Component {
270+ public:
271+ void setup () override {
272+ // Load saved state from preferences
273+ float saved_position;
274+ if (this ->pref_position_ .load (&saved_position)) {
275+ this ->position = saved_position;
276+ } else {
277+ // Default to open if no saved state
278+ this ->position = cover::COVER_OPEN ;
279+ }
280+
281+ uint8_t saved_operation;
282+ if (this ->pref_operation_ .load (&saved_operation)) {
283+ this ->current_operation = static_cast <cover::CoverOperation>(saved_operation);
284+ } else {
285+ this ->current_operation = cover::COVER_OPERATION_IDLE ;
286+ }
287+
288+ this ->publish_state ();
289+ }
290+
291+ void loop () override {
292+ // Check if we need to update state after movement
293+ if (this ->state_change_time != 0 ) {
294+ const uint32_t now = millis ();
295+ if (now - this ->state_change_time >= 5000 ) { // 5 second delay
296+ this ->state_change_time = 0 ;
297+
298+ if (this ->current_operation == cover::COVER_OPERATION_OPENING ) {
299+ this ->current_operation = cover::COVER_OPERATION_IDLE ;
300+ this ->position = cover::COVER_OPEN ;
301+ } else if (this ->current_operation == cover::COVER_OPERATION_CLOSING ) {
302+ this ->current_operation = cover::COVER_OPERATION_IDLE ;
303+ this ->position = cover::COVER_CLOSED ;
304+ }
305+
306+ // Save state when it changes
307+ this ->save_state_ ();
308+ this ->publish_state ();
309+ }
310+ }
311+ }
312+
313+ void set_parent (RaexBlindTX *parent) { parent_ = parent; }
314+ void set_remote_id (uint16_t remote_id) {
315+ remote_id_ = remote_id;
316+ // Initialize preferences with unique keys based on remote_id and channel_id
317+ this ->pref_position_ = global_preferences->make_preference <float >(remote_id_ << 8 | channel_id_);
318+ this ->pref_operation_ = global_preferences->make_preference <uint8_t >((remote_id_ << 8 | channel_id_) + 1 );
319+ }
320+ void set_channel_id (uint8_t channel_id) {
321+ channel_id_ = channel_id;
322+ // Re-initialize preferences with updated IDs
323+ this ->pref_position_ = global_preferences->make_preference <float >(remote_id_ << 8 | channel_id_);
324+ this ->pref_operation_ = global_preferences->make_preference <uint8_t >((remote_id_ << 8 | channel_id_) + 1 );
325+ }
326+
327+ cover::CoverTraits get_traits () override {
328+ auto traits = cover::CoverTraits ();
329+ traits.set_is_assumed_state (true );
330+ traits.set_supports_position (false );
331+ traits.set_supports_tilt (false );
332+ traits.set_supports_stop (true ); // Enable stop button
333+ return traits;
334+ }
335+
336+ protected:
337+ void control (const cover::CoverCall &call) override {
338+ if (call.get_stop ()) {
339+ // Handle stop command
340+ this ->parent_ ->transmit (remote_id_, channel_id_, " STOP" );
341+ this ->current_operation = cover::COVER_OPERATION_IDLE ;
342+ this ->state_change_time = 0 ;
343+ this ->save_state_ ();
344+ this ->publish_state ();
345+ } else if (call.get_position ().has_value ()) {
346+ auto pos = *call.get_position ();
347+ if (pos == cover::COVER_OPEN ) {
348+ this ->parent_ ->transmit (remote_id_, channel_id_, " OPEN" );
349+ this ->current_operation = cover::COVER_OPERATION_OPENING ;
350+ this ->state_change_time = millis ();
351+ } else if (pos == cover::COVER_CLOSED ) {
352+ this ->parent_ ->transmit (remote_id_, channel_id_, " CLOSE" );
353+ this ->current_operation = cover::COVER_OPERATION_CLOSING ;
354+ this ->state_change_time = millis ();
355+ }
356+ this ->publish_state ();
357+ }
358+ }
359+
360+ void save_state_ () {
361+ this ->pref_position_ .save (&this ->position );
362+ uint8_t operation = static_cast <uint8_t >(this ->current_operation );
363+ this ->pref_operation_ .save (&operation);
364+ }
365+
366+ RaexBlindTX *parent_;
367+ uint16_t remote_id_{0 };
368+ uint8_t channel_id_{0 };
369+ uint32_t state_change_time{0 }; // Tracks when state changes for delayed updates
370+ ESPPreferenceObject pref_position_;
371+ ESPPreferenceObject pref_operation_;
372+ };
373+
374+ class RaexBlindPairButton : public button ::Button, public Component {
375+ public:
376+ void set_parent (RaexBlindTX *parent) { parent_ = parent; }
377+ void set_remote_id (uint16_t remote_id) { remote_id_ = remote_id; }
378+ void set_channel_id (uint8_t channel_id) { channel_id_ = channel_id; }
379+
380+ protected:
381+ void press_action () override {
382+ this ->parent_ ->transmit (remote_id_, channel_id_, " PAIR" );
383+ }
384+
385+ RaexBlindTX *parent_;
386+ uint16_t remote_id_{0 };
387+ uint8_t channel_id_{0 };
388+ };
389+
390+ } // namespace raex_blind_tx
391+ } // namespace esphome
0 commit comments