ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
sendspin_hub.cpp
Go to the documentation of this file.
1#include "sendspin_hub.h"
2
3#ifdef USE_ESP32
4
6#ifdef USE_ETHERNET
8#endif
9#ifdef USE_WIFI
11#endif
12
15#include "esphome/core/log.h"
17
18#include <esp_log.h>
19
20namespace esphome::sendspin_ {
21
22static const char *const TAG = "sendspin.hub";
23
25 auto config = this->build_client_config_();
26 this->client_ = std::make_unique<sendspin::SendspinClient>(std::move(config));
27
28 // Set up persistence (preferences must be initialized before providers are added to the client)
31#ifdef USE_SENDSPIN_PLAYER
33#endif
34
35 // Wire providers and client listener
36 this->client_->set_listener(this);
37 this->client_->set_network_provider(this);
38 this->client_->set_persistence_provider(this);
39
40#ifdef USE_SENDSPIN_CONTROLLER
41 this->controller_role_ = &this->client_->add_controller();
42 this->controller_role_->set_listener(this);
43#endif
44
45#ifdef USE_SENDSPIN_METADATA
46 this->metadata_role_ = &this->client_->add_metadata();
47 this->metadata_role_->set_listener(this);
48#endif
49
50#ifdef USE_SENDSPIN_PLAYER
51 this->client_->add_player(this->player_config_).set_listener(this->player_listener_);
52#endif
53
54 if (!this->client_->start_server()) {
55 ESP_LOGE(TAG, "Failed to start Sendspin server");
56 this->mark_failed();
57 return;
58 }
59}
60
61void SendspinHub::loop() { this->client_->loop(); }
62
64 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
65 ESP_LOGCONFIG(TAG,
66 "Sendspin Hub:\n"
67 " Client ID: %s\n"
68 " Task stack in PSRAM: %s",
69 get_client_id_into_buffer(mac_buf), YESNO(this->task_stack_in_psram_));
70}
71
72// --- Delegating methods ---
73
74// THREAD CONTEXT: Main loop (invoked from Sendspin components)
75void SendspinHub::connect_to_server(const std::string &url) {
76 if (this->is_ready()) {
77 this->client_->connect_to(url);
78 }
79}
80
81// THREAD CONTEXT: Main loop (invoked from Sendspin components)
82void SendspinHub::disconnect_from_server(sendspin::SendspinGoodbyeReason reason) {
83 if (this->is_ready()) {
84 this->client_->disconnect(reason);
85 }
86}
87
88// THREAD CONTEXT: Main loop (invoked from Sendspin components)
89void SendspinHub::update_state(sendspin::SendspinClientState state) {
90 if (this->is_ready()) {
91 this->client_->update_state(state);
92 }
93}
94
95const char *SendspinHub::get_client_id_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
96 // The server matches client_id against the L2 source MAC of the device's multicast traffic.
97 // ESP-IDF derives the ethernet MAC as base+3 by default on ESP32-S3, so we cannot use the
98 // eFuse base MAC when ethernet is the active interface.
99#ifdef USE_ETHERNET
100 if (ethernet::global_eth_component != nullptr) {
102 }
103#endif
105}
106
107sendspin::SendspinClientConfig SendspinHub::build_client_config_() {
108 sendspin::SendspinClientConfig config;
109
110 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
111 config.client_id = SendspinHub::get_client_id_into_buffer(mac_buf);
112 config.name = App.get_friendly_name();
113 config.product_name = App.get_name();
114 config.manufacturer = "ESPHome";
115 config.software_version = ESPHOME_VERSION;
116 config.httpd_psram_stack = this->task_stack_in_psram_;
117
118 return config;
119}
120
121// --- SendspinClientListener overrides ---
122// THREAD CONTEXT: Main loop (fired from client_->loop())
123
124void SendspinHub::on_group_update(const sendspin::GroupUpdateObject &group) {
125 this->group_update_callbacks_.call(group);
126}
127
129#ifdef USE_WIFI
130 if (wifi::global_wifi_component != nullptr) {
132 }
133#endif
134}
135
137#ifdef USE_WIFI
138 if (wifi::global_wifi_component != nullptr) {
140 }
141#endif
142}
143
144// --- SendspinNetworkProvider override ---
145
146// THREAD CONTEXT: Main loop (polled by client_->loop())
148
149// --- SendspinPersistenceProvider overrides ---
150
151// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events)
153 LastPlayedServerPref pref{.server_id_hash = hash};
154 bool ok = this->last_played_server_pref_.save(&pref);
155 if (ok) {
156 ESP_LOGD(TAG, "Persisted last played server hash: 0x%08X", hash);
157 } else {
158 ESP_LOGW(TAG, "Failed to persist last played server hash");
159 }
160 return ok;
161}
162
163// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events)
164std::optional<uint32_t> SendspinHub::load_last_server_hash() {
166 if (this->last_played_server_pref_.load(&pref)) {
167 ESP_LOGI(TAG, "Loaded last played server hash: 0x%08X", pref.server_id_hash);
168 return pref.server_id_hash;
169 }
170 return std::nullopt;
171}
172
173// --- Sendspin role specific methods/overrides ---
174
175#ifdef USE_SENDSPIN_CONTROLLER
176// THREAD CONTEXT: Main loop (invoked from ESPHome actions / other components)
177void SendspinHub::send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume,
178 std::optional<bool> mute) {
179 if (this->is_ready()) {
180 this->controller_role_->send_command(command, volume, mute);
181 }
182}
183
184// THREAD CONTEXT: Main loop (ControllerRoleListener override, fired from client_->loop())
185void SendspinHub::on_controller_state(const sendspin::ServerStateControllerObject &state) {
186 this->controller_state_callbacks_.call(state);
187}
188#endif
189
190#ifdef USE_SENDSPIN_METADATA
191// THREAD CONTEXT: Main loop (MetadataRoleListener override, fired from client_->loop())
192void SendspinHub::on_metadata(const sendspin::ServerMetadataStateObject &metadata) {
193 this->metadata_update_callbacks_.call(metadata);
194}
195
196// THREAD CONTEXT: Main loop (invoked from Sendspin components)
198 if (this->is_ready()) {
199 return this->metadata_role_->get_track_progress_ms();
200 }
201 return 0;
202}
203#endif
204
205#ifdef USE_SENDSPIN_PLAYER
206// THREAD CONTEXT: Main loop, called from child component setup() after player role is created and configured
207sendspin::PlayerRole *SendspinHub::get_player_role() {
208 if (this->is_ready()) {
209 return this->client_->player();
210 }
211 return nullptr;
212}
213
214// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override)
215bool SendspinHub::save_static_delay(uint16_t delay_ms) {
216 StaticDelayPref pref{.delay_ms = delay_ms};
217 bool ok = this->static_delay_pref_.save(&pref);
218 if (ok) {
219 ESP_LOGD(TAG, "Persisted static delay: %u ms", delay_ms);
220 } else {
221 ESP_LOGW(TAG, "Failed to persist static delay");
222 }
223 return ok;
224}
225
226// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override)
227std::optional<uint16_t> SendspinHub::load_static_delay() {
228 StaticDelayPref pref{};
229 if (this->static_delay_pref_.load(&pref)) {
230 ESP_LOGI(TAG, "Loaded static delay: %u ms", pref.delay_ms);
231 return pref.delay_ms;
232 }
233 return std::nullopt;
234}
235
236#endif
237
238} // namespace esphome::sendspin_
239
240#endif // USE_ESP32
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
void mark_failed()
Mark this component as failed.
bool is_ready() const
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0") std const char * get_eth_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
sendspin::PlayerRoleListener * player_listener_
void update_state(sendspin::SendspinClientState state)
Updates the client's reported playback state on the server.
bool save_last_server_hash(uint32_t hash) override
bool save_static_delay(uint16_t delay_ms) override
CallbackManager< void(const sendspin::ServerMetadataStateObject &)> metadata_update_callbacks_
CallbackManager< void(const sendspin::ServerStateControllerObject &)> controller_state_callbacks_
static const char * get_client_id_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Writes the active network interface's MAC into buf and returns its data pointer.
void on_release_high_performance() override
sendspin::MetadataRole * metadata_role_
CallbackManager< void(const sendspin::GroupUpdateObject &)> group_update_callbacks_
sendspin::PlayerRoleConfig player_config_
void on_metadata(const sendspin::ServerMetadataStateObject &metadata) override
sendspin::PlayerRole * get_player_role()
Child components call this to get the PlayerRole instance after setup, so they can push updates to it...
void disconnect_from_server(sendspin::SendspinGoodbyeReason reason)
Disconnects the underlying client from the current server.
std::optional< uint16_t > load_static_delay() override
std::unique_ptr< sendspin::SendspinClient > client_
sendspin::SendspinClientConfig build_client_config_()
Builds the SendspinClientConfig from ESPHome configuration and platform info.
sendspin::ControllerRole * controller_role_
ESPPreferenceObject last_played_server_pref_
uint32_t get_track_progress_ms() const
Returns the interpolated track progress in milliseconds, or 0 if the hub is not yet ready.
void send_client_command(sendspin::SendspinControllerCommand command, std::optional< uint8_t > volume=std::nullopt, std::optional< bool > mute=std::nullopt)
ESPPreferenceObject static_delay_pref_
void on_request_high_performance() override
std::optional< uint32_t > load_last_server_hash() override
void on_controller_state(const sendspin::ServerStateControllerObject &state) override
void connect_to_server(const std::string &url)
Connects the underlying client to the given Sendspin server.
void on_group_update(const sendspin::GroupUpdateObject &group) override
bool request_high_performance()
Request high-performance mode (no power saving) for improved WiFi latency.
bool release_high_performance()
Release a high-performance mode request.
bool state
Definition fan.h:2
EthernetComponent * global_eth_component
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.h:27
WiFiComponent * global_wifi_component
ESPPreferences * global_preferences
const char * get_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in colon-separated uppercase hex notation.
Definition helpers.cpp:751
Application App
Global storage of Application pointer - only one Application can exist.
constexpr uint32_t fnv1a_hash(const char *str)
Calculate a FNV-1a hash of str.
Definition helpers.h:834
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24
Persistent storage structure for last played server hash.
Persistent storage structure for player static delay.