ESPHome 2026.8.0-dev
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
136
145
146// --- SendspinNetworkProvider override ---
147
148// THREAD CONTEXT: Main loop (polled by client_->loop())
150
151// --- SendspinPersistenceProvider overrides ---
152
153// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events)
155 LastPlayedServerPref pref{.server_id_hash = hash};
156 bool ok = this->last_played_server_pref_.save(&pref);
157 if (ok) {
158 ESP_LOGD(TAG, "Persisted last played server hash: 0x%08" PRIX32, hash);
159 } else {
160 ESP_LOGW(TAG, "Failed to persist last played server hash");
161 }
162 return ok;
163}
164
165// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events)
166std::optional<uint32_t> SendspinHub::load_last_server_hash() {
168 if (this->last_played_server_pref_.load(&pref)) {
169 ESP_LOGI(TAG, "Loaded last played server hash: 0x%08" PRIX32, pref.server_id_hash);
170 return pref.server_id_hash;
171 }
172 return std::nullopt;
173}
174
175// --- Sendspin role specific methods/overrides ---
176
177#ifdef USE_SENDSPIN_CONTROLLER
178// THREAD CONTEXT: Main loop (invoked from ESPHome actions / other components)
179void SendspinHub::send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume,
180 std::optional<bool> mute) {
181 if (this->is_ready()) {
182 this->controller_role_->send_command(command, volume, mute);
183 }
184}
185
186// THREAD CONTEXT: Main loop (ControllerRoleListener override, fired from client_->loop())
187void SendspinHub::on_controller_state(const sendspin::ServerStateControllerObject &state) {
188 this->controller_state_callbacks_.call(state);
189}
190#endif
191
192#ifdef USE_SENDSPIN_METADATA
193// THREAD CONTEXT: Main loop (MetadataRoleListener override, fired from client_->loop())
194void SendspinHub::on_metadata(const sendspin::ServerMetadataStateObject &metadata) {
195 this->metadata_update_callbacks_.call(metadata);
196}
197
198// THREAD CONTEXT: Main loop (invoked from Sendspin components)
200 if (this->is_ready()) {
201 return this->metadata_role_->get_track_progress_ms();
202 }
203 return 0;
204}
205#endif
206
207#ifdef USE_SENDSPIN_PLAYER
208// THREAD CONTEXT: Main loop, called from child component setup() after player role is created and configured
209sendspin::PlayerRole *SendspinHub::get_player_role() {
210 if (this->is_ready()) {
211 return this->client_->player();
212 }
213 return nullptr;
214}
215
216// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override)
217bool SendspinHub::save_static_delay(uint16_t delay_ms) {
218 StaticDelayPref pref{.delay_ms = delay_ms};
219 bool ok = this->static_delay_pref_.save(&pref);
220 if (ok) {
221 ESP_LOGD(TAG, "Persisted static delay: %u ms", delay_ms);
222 } else {
223 ESP_LOGW(TAG, "Failed to persist static delay");
224 }
225 return ok;
226}
227
228// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override)
229std::optional<uint16_t> SendspinHub::load_static_delay() {
230 StaticDelayPref pref{};
231 if (this->static_delay_pref_.load(&pref)) {
232 ESP_LOGI(TAG, "Loaded static delay: %u ms", pref.delay_ms);
233 return pref.delay_ms;
234 }
235 return std::nullopt;
236}
237
238#endif
239
240} // namespace esphome::sendspin_
241
242#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.
void release_roaming_suppression()
Release a roaming suppression request.
void request_roaming_suppression()
Request that post-connect roaming scans be suppressed.
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:28
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:750
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:848
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.