ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
sendspin_hub.h
Go to the documentation of this file.
1#pragma once
2
4
5#ifdef USE_ESP_IDF
6
13
14#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
16#endif
17
18#include <sendspin/client.h>
19#include <sendspin/config.h>
20#include <sendspin/types.h>
21
22#ifdef USE_SENDSPIN_ARTWORK
23#include <sendspin/artwork_role.h>
24#endif
25#ifdef USE_SENDSPIN_CONTROLLER
26#include <sendspin/controller_role.h>
27#endif
28#ifdef USE_SENDSPIN_METADATA
29#include <sendspin/metadata_role.h>
30#endif
31#ifdef USE_SENDSPIN_PLAYER
32#include <sendspin/player_role.h>
33#endif
34
35#include <array>
36#include <cstddef>
37#include <cstdint>
38#include <functional>
39#include <memory>
40#include <optional>
41#include <string>
42#include <utility>
43#include <vector>
44
45namespace esphome::sendspin_ {
46
52namespace sendspin_priority {
53// AFTER_WIFI so the hub runs after the wifi/ethernet drivers are up and we can read the active
54// interface's MAC address for the device info.
56inline constexpr float CHILD = HUB - 1.0f;
57} // namespace sendspin_priority
58
59// PREFERENCE KEYS: each library persistence key has its own preference, hashed from "sendspin_" + name. Renaming
60// one erases it.
61
63inline constexpr size_t SENDSPIN_RECORD_SLOTS = sendspin::SendspinClientConfig::DEFAULT_MAX_PAIRING_RECORDS;
64
69
85class SendspinHub final : public Component,
86#ifdef USE_SENDSPIN_ARTWORK
87 public sendspin::ArtworkRoleListener,
88#endif
89#ifdef USE_SENDSPIN_CONTROLLER
90 public sendspin::ControllerRoleListener,
91#endif
92#ifdef USE_SENDSPIN_METADATA
93 public sendspin::MetadataRoleListener,
94#endif
95 public sendspin::SendspinClientListener,
96 public sendspin::SendspinNetworkProvider,
97 public sendspin::SendspinPersistenceProvider {
98 public:
100
101 float get_setup_priority() const override { return sendspin_priority::HUB; }
102 void setup() override;
103 void loop() override;
104 void on_shutdown() override;
105 void dump_config() override;
106
112 void connect_to_server(const std::string &url);
113
123 void disconnect_from_server(sendspin::SendspinGoodbyeReason reason);
124
129 void leave_group();
130
136
141
142 // --- Configuration setters (called from codegen) ---
143
144 template<typename F> void add_group_update_callback(F &&callback) {
145 this->group_update_callbacks_.add(std::forward<F>(callback));
146 }
147
148 template<typename F> void add_on_open_pairing_window_callback(F &&callback) {
149 this->open_pairing_window_callbacks_.add(std::forward<F>(callback));
150 }
151
152 template<typename F> void add_on_close_pairing_window_callback(F &&callback) {
153 this->close_pairing_window_callbacks_.add(std::forward<F>(callback));
154 }
155
156 template<typename F> void add_on_display_pairing_code_callback(F &&callback) {
157 this->display_pairing_code_callbacks_.add(std::forward<F>(callback));
158 }
159
160 template<typename F> void add_on_clear_pairing_code_callback(F &&callback) {
161 this->clear_pairing_code_callbacks_.add(std::forward<F>(callback));
162 }
163
164 template<typename F> void add_on_pairing_succeeded_callback(F &&callback) {
165 this->pairing_succeeded_callbacks_.add(std::forward<F>(callback));
166 }
167
168 template<typename F> void add_on_pairing_failed_callback(F &&callback) {
169 this->pairing_failed_callbacks_.add(std::forward<F>(callback));
170 }
171
172 void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
173
180 void set_enabled(bool enabled);
181
184 void set_unpaired_access_enabled(bool enabled);
185
187 bool is_client_running() const { return this->client_ != nullptr && this->client_->is_started(); }
188
190 bool is_disabled() const { return this->enabled_.has_value() && !this->enabled_.value(); }
191
194
199 std::optional<std::string> get_pairing_token() const;
200
205 StringRef get_client_id() const;
206
212 void set_manufacturer(const char *manufacturer) { this->manufacturer_ = manufacturer; }
213 void set_model(const char *model) { this->model_ = model; }
214 void set_firmware_version(const char *firmware_version) { this->firmware_version_ = firmware_version; }
215
216#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
217 void set_mdns(mdns::MDNSComponent *mdns) { this->mdns_ = mdns; }
218#endif
219
221 void set_static_pairing_code(const char *code) { this->static_pairing_code_ = code; }
222
225 void set_pairing_code_display_supported(bool supported) { this->pairing_code_display_supported_ = supported; }
226
227 // --- Sendspin role specific methods ---
228
229#ifdef USE_SENDSPIN_ARTWORK
230 void set_artwork_config(const sendspin::ArtworkRoleConfig &config) { this->artwork_config_ = config; }
231
237 void artwork_frame_done(uint8_t slot);
238
239 template<typename F> void add_image_decode_callback(F &&callback) {
240 this->artwork_image_decode_callbacks_.add(std::forward<F>(callback));
241 }
242 template<typename F> void add_image_display_callback(F &&callback) {
243 this->artwork_image_display_callbacks_.add(std::forward<F>(callback));
244 }
245 template<typename F> void add_image_clear_callback(F &&callback) {
246 this->artwork_image_clear_callbacks_.add(std::forward<F>(callback));
247 }
248#endif
249
250#ifdef USE_SENDSPIN_CONTROLLER
251 void send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume = std::nullopt,
252 std::optional<bool> mute = std::nullopt);
253
255 void switch_client();
256
257 template<typename F> void add_controller_state_callback(F &&callback) {
258 this->controller_state_callbacks_.add(std::forward<F>(callback));
259 }
260
262 template<typename F> void add_controller_state_clear_callback(F &&callback) {
263 this->controller_state_clear_callbacks_.add(std::forward<F>(callback));
264 }
265#endif
266
267#ifdef USE_SENDSPIN_METADATA
272 template<typename F> void add_metadata_update_callback(F &&callback) {
273 this->metadata_update_callbacks_.add(std::forward<F>(callback));
274 }
275
278#endif
279
280#ifdef USE_SENDSPIN_PLAYER
281 void set_listener(sendspin::PlayerRoleListener *listener) { this->player_listener_ = listener; }
282 void set_player_config(const sendspin::PlayerRoleConfig &config) { this->player_config_ = config; }
283
285 sendspin::PlayerRole *get_player_role();
286#endif
287
288 protected:
290 sendspin::SendspinClientConfig build_client_config_();
291
294 std::pair<ESPPreferenceObject *, size_t> pref_for_key_(const std::string &key);
295
297 const char *get_product_name_() const;
298
299 const char *pairing_code_method_() const;
300
303 static const char *get_mac_address_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
304
305#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
308#endif
309
310 // --- SendspinClientListener overrides ---
311 void on_group_update(const sendspin::GroupUpdateObject &group) override;
312
313 void on_request_high_performance() override;
314
315 void on_release_high_performance() override;
316
317 void on_open_pairing_window() override;
318
319 void on_close_pairing_window() override;
320
321 void on_display_pairing_code(const std::string &code, sendspin::SendspinPairingCodeFormat format) override;
322
323 void on_clear_pairing_code() override;
324
325 void on_pairing_succeeded(const std::string &server_id) override;
326
327 void on_pairing_failed(const std::string &server_id, sendspin::SendspinPairAbortReason reason) override;
328
329 // --- SendspinNetworkProvider override ---
330 bool is_network_ready() override;
331
332 // --- SendspinPersistenceProvider overrides ---
333 // The library calls commit() after writing pairing secrets, so they reach flash right away.
334 std::optional<std::vector<uint8_t>> load_blob(const std::string &key) override;
335 bool save_blob(const std::string &key, const uint8_t *data, size_t len) override;
336 bool commit() override;
337
338 // --- Sendspin role specific methods/overrides/member variables ---
339
340#ifdef USE_SENDSPIN_ARTWORK
341 void on_image_decode(uint8_t slot, const uint8_t *data, size_t length, sendspin::SendspinImageFormat format) override;
342
343 void on_image_display(uint8_t slot, uint32_t lateness_ms) override;
344
345 void on_image_clear(uint8_t slot) override;
346
347 sendspin::ArtworkRoleConfig artwork_config_{};
348 sendspin::ArtworkRole *artwork_role_{nullptr};
349
350 // Callback fan-out to child components; they filter by slot as needed.
351 CallbackManager<void(uint8_t, const uint8_t *, size_t, sendspin::SendspinImageFormat)>
355#endif
356
357#ifdef USE_SENDSPIN_CONTROLLER
358 sendspin::ControllerRole *controller_role_{nullptr};
359
360 void on_controller_state(const sendspin::ServerStateControllerObject &state) override;
361
362 void on_controller_state_clear() override;
363
364 // Callback fan-out to child components; they filter as needed. Only a media_player subscribes, while the switch
365 // action and the media source enable the controller role without one, so keep the idle cost to a single pointer.
366 LazyCallbackManager<void(const sendspin::ServerStateControllerObject &)> controller_state_callbacks_{};
368#endif
369
370#ifdef USE_SENDSPIN_METADATA
371 sendspin::MetadataRole *metadata_role_{nullptr};
372
373 void on_metadata(const sendspin::ServerMetadataStateObject &metadata) override;
374
375 void on_metadata_clear() override;
376
377 // Callback fan-out to child components; they filter as needed
378 CallbackManager<void(const sendspin::ServerMetadataStateObject &)> metadata_update_callbacks_{};
379#endif
380
381#ifdef USE_SENDSPIN_PLAYER
382 sendspin::PlayerRoleListener *player_listener_{nullptr};
383 sendspin::PlayerRoleConfig player_config_{};
384#endif
385
386 // --- Core member variables ---
387
388 // Built once in setup(): make_preference() allocates a backend that is never freed.
393 std::array<ESPPreferenceObject, SENDSPIN_RECORD_SLOTS> record_slot_prefs_;
395
396 std::unique_ptr<sendspin::SendspinClient> client_;
397
398 // Callback fan-out to child components
399 CallbackManager<void(const sendspin::GroupUpdateObject &)> group_update_callbacks_{};
400
401 // Lazy: each pairing callback is fed by an optional YAML surface.
408
409 const char *static_pairing_code_{nullptr}; // Codegen string literal, or nullptr when not configured
412#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
413 bool mdns_advertised_{false}; // Last state requested from mdns
414#endif
415
416 // Requested client state, applied from loop(). The client does not start until both are set.
417 std::optional<bool> enabled_;
418 std::optional<bool> unpaired_access_;
419
420 // Device information sent in the `client/hello` message. Defaults apply when neither the
421 // sendspin configuration nor the project information supplies a value.
422 const char *manufacturer_{"ESPHome"};
423 const char *model_{nullptr}; // nullptr reports the device name instead
424 const char *firmware_version_{ESPHOME_VERSION};
425
426#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
428#endif
429};
430
437class SendspinChild : public Component, public Parented<SendspinHub> {
438 public:
439 float get_setup_priority() const override { return sendspin_priority::CHILD; }
440};
441
447class SendspinPollingChild : public PollingComponent, public Parented<SendspinHub> {
448 public:
449 float get_setup_priority() const override { return sendspin_priority::CHILD; }
450};
451
452extern SendspinHub *global_sendspin_hub; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
453
454} // namespace esphome::sendspin_
455
456#endif // USE_ESP_IDF
Helper class to easily give an object a parent of type T.
Definition helpers.h:1997
This class simplifies creating components that periodically check a state.
Definition component.h:514
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
Base class for all sendspin subcomponents.
float get_setup_priority() const override
Thin adapter over sendspin::SendspinClient.
ESPPreferenceObject output_delay_pref_
uint32_t get_feature_flags() const
Returns the SendspinFeature flags reported to API clients.
CallbackManager< void(uint8_t, const uint8_t *, size_t, sendspin::SendspinImageFormat)> artwork_image_decode_callbacks_
void add_image_clear_callback(F &&callback)
CallbackManager< void(uint8_t, uint32_t)> artwork_image_display_callbacks_
sendspin::PlayerRoleListener * player_listener_
void set_firmware_version(const char *firmware_version)
void add_on_clear_pairing_code_callback(F &&callback)
const char * get_product_name_() const
Returns the product name reported to the server: the configured model, or the device name.
void set_static_pairing_code(const char *code)
The static pairing code, used on every boot.
std::optional< bool > unpaired_access_
void on_controller_state_clear() override
std::optional< std::vector< uint8_t > > load_blob(const std::string &key) override
void set_artwork_config(const sendspin::ArtworkRoleConfig &config)
void confirm_pairing_window()
Confirms a pairing attempt on the device.
CallbackManager< void(const sendspin::ServerMetadataStateObject &)> metadata_update_callbacks_
ESPPreferenceObject record_order_pref_
std::optional< std::string > get_pairing_token() const
Returns the pairing token a Sendspin server uses to pair with this device.
LazyCallbackManager< void(const std::string &, StringRef)> pairing_failed_callbacks_
void add_image_decode_callback(F &&callback)
void set_unpaired_access_enabled(bool enabled)
Turns unpaired (Sentinel) access on or off.
LazyCallbackManager< void(const sendspin::ServerStateControllerObject &)> controller_state_callbacks_
void add_group_update_callback(F &&callback)
void on_release_high_performance() override
void set_manufacturer(const char *manufacturer)
Sets the device information reported to the server in the client/hello message.
bool save_blob(const std::string &key, const uint8_t *data, size_t len) override
LazyCallbackManager< void()> clear_pairing_code_callbacks_
sendspin::MetadataRole * metadata_role_
void on_display_pairing_code(const std::string &code, sendspin::SendspinPairingCodeFormat format) override
bool is_client_running() const
Returns whether the Sendspin client is running.
void add_on_open_pairing_window_callback(F &&callback)
const char * pairing_code_method_() const
CallbackManager< void(const sendspin::GroupUpdateObject &)> group_update_callbacks_
LazyCallbackManager< void(const std::string &)> display_pairing_code_callbacks_
void switch_client()
Sends the SWITCH controller command; exposed as the sendspin.switch action.
void leave_group()
Leaves the current group so another source can use the speaker.
sendspin::PlayerRoleConfig player_config_
void set_pairing_code_display_supported(bool supported)
Set when on_display_pairing_code or a pairing_code text sensor is configured, so the device offers th...
void on_metadata(const sendspin::ServerMetadataStateObject &metadata) override
void cancel_pairing_window()
Closes an open pairing window, so a waiting pairing attempt is not confirmed.
void add_on_pairing_succeeded_callback(F &&callback)
ESPPreferenceObject last_played_pref_
void add_on_close_pairing_window_callback(F &&callback)
void set_player_config(const sendspin::PlayerRoleConfig &config)
LazyCallbackManager< void(const std::string &)> pairing_succeeded_callbacks_
static const char * get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Writes the active network interface's MAC, in lowercase, into buf and returns its data pointer.
sendspin::PlayerRole * get_player_role()
Child components call this to get the PlayerRole instance after setup, so they can push updates to it...
void set_listener(sendspin::PlayerRoleListener *listener)
void on_image_clear(uint8_t slot) override
void on_pairing_failed(const std::string &server_id, sendspin::SendspinPairAbortReason reason) override
void add_controller_state_clear_callback(F &&callback)
Registers a callback that fires when the connection is lost and the cached controller state is droppe...
bool is_disabled() const
Returns whether the client has been turned off with set_enabled().
void disconnect_from_server(sendspin::SendspinGoodbyeReason reason)
Disconnects the underlying client from the current server.
std::unique_ptr< sendspin::SendspinClient > client_
sendspin::ArtworkRoleConfig artwork_config_
void update_mdns_service_()
Keeps the _sendspin mDNS service advertised while the client is running.
void add_on_pairing_failed_callback(F &&callback)
sendspin::SendspinClientConfig build_client_config_()
Builds the SendspinClientConfig from ESPHome configuration and platform info.
mdns::MDNSComponent * mdns_
sendspin::ControllerRole * controller_role_
void on_pairing_succeeded(const std::string &server_id) override
void add_on_display_pairing_code_callback(F &&callback)
uint32_t get_track_progress_ms() const
Returns the interpolated track progress in milliseconds, or 0 if the hub is not yet ready.
void add_controller_state_callback(F &&callback)
std::optional< bool > enabled_
StringRef get_client_id() const
Returns the Sendspin client_id: base64url of the client's static public key.
void set_mdns(mdns::MDNSComponent *mdns)
void send_client_command(sendspin::SendspinControllerCommand command, std::optional< uint8_t > volume=std::nullopt, std::optional< bool > mute=std::nullopt)
void set_model(const char *model)
LazyCallbackManager< void()> controller_state_clear_callbacks_
CallbackManager< void(uint8_t)> artwork_image_clear_callbacks_
ESPPreferenceObject keypair_pref_
LazyCallbackManager< void()> open_pairing_window_callbacks_
void set_task_stack_in_psram(bool task_stack_in_psram)
sendspin::ArtworkRole * artwork_role_
void on_request_high_performance() override
float get_setup_priority() const override
std::pair< ESPPreferenceObject *, size_t > pref_for_key_(const std::string &key)
The preference a library key is stored in and its blob size, or {nullptr, 0} for a key with no storag...
void set_enabled(bool enabled)
Requests the Sendspin client, including the server, the roles and the mDNS advertisement,...
std::array< ESPPreferenceObject, SENDSPIN_RECORD_SLOTS > record_slot_prefs_
void on_image_display(uint8_t slot, uint32_t lateness_ms) override
void on_image_decode(uint8_t slot, const uint8_t *data, size_t length, sendspin::SendspinImageFormat format) override
ESPPreferenceObject pairing_psk_pref_
void on_controller_state(const sendspin::ServerStateControllerObject &state) override
LazyCallbackManager< void()> close_pairing_window_callbacks_
void connect_to_server(const std::string &url)
Connects the underlying client to the given Sendspin server.
void add_image_display_callback(F &&callback)
void on_group_update(const sendspin::GroupUpdateObject &group) override
void add_metadata_update_callback(F &&callback)
Registers a callback that fires when the server sends metadata.
void artwork_frame_done(uint8_t slot)
Acknowledges the most recent artwork delivery (display or clear) for a slot.
Base class for sendspin subcomponents that need polling behavior.
float get_setup_priority() const override
bool state
Definition fan.h:2
const char * format
constexpr size_t SENDSPIN_RECORD_SLOTS
Pairing-record slots, handed to the client as max_pairing_records.
SendspinFeature
Feature flags reported to API clients in DeviceCapabilitiesResponse.
SendspinHub * global_sendspin_hub
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:55
const void size_t len
Definition hal.h:68
static void uint32_t