mirror of
https://github.com/esphome/esphome.git
synced 2026-05-28 04:55:48 +08:00
[sendspin] Add controller role and sendspin.switch action (PR2) (#15929)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, network, psram, socket, wifi
|
from esphome.components import esp32, network, psram, socket, wifi
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
from esphome.const import CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE, ID
|
||||||
|
from esphome.cpp_generator import TemplateArgsType
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
# mdns for autodiscovery
|
# mdns for autodiscovery
|
||||||
@@ -22,6 +24,13 @@ SendspinHub = sendspin_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SendspinSwitchCommandAction = sendspin_ns.class_(
|
||||||
|
"SendspinSwitchCommandAction",
|
||||||
|
automation.Action,
|
||||||
|
cg.Parented.template(SendspinHub),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SendspinConfiguration:
|
class SendspinConfiguration:
|
||||||
artwork_support: bool = False
|
artwork_support: bool = False
|
||||||
@@ -101,6 +110,41 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _request_controller_role(config: ConfigType) -> ConfigType:
|
||||||
|
"""Request the controller role for the sendspin.switch action."""
|
||||||
|
request_controller_support()
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
SENDSPIN_SIMPLE_ACTION_SCHEMA = cv.All(
|
||||||
|
automation.maybe_simple_id(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(SendspinHub),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
_request_controller_role,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"sendspin.switch",
|
||||||
|
SendspinSwitchCommandAction,
|
||||||
|
SENDSPIN_SIMPLE_ACTION_SCHEMA,
|
||||||
|
synchronous=True,
|
||||||
|
)
|
||||||
|
async def sendspin_switch_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "sendspin_hub.h"
|
||||||
|
|
||||||
|
namespace esphome::sendspin_ {
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
template<typename... Ts> class SendspinSwitchCommandAction : public Action<Ts...>, public Parented<SendspinHub> {
|
||||||
|
public:
|
||||||
|
void play(const Ts &...x) override {
|
||||||
|
// Clear any EXTERNAL_SOURCE state so the switch command is followed
|
||||||
|
this->parent_->update_state(sendspin::SendspinClientState::SYNCHRONIZED);
|
||||||
|
this->parent_->send_client_command(sendspin::SendspinControllerCommand::SWITCH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // USE_SENDSPIN_CONTROLLER
|
||||||
|
|
||||||
|
} // namespace esphome::sendspin_
|
||||||
|
|
||||||
|
#endif // USE_ESP32
|
||||||
@@ -31,6 +31,11 @@ void SendspinHub::setup() {
|
|||||||
this->client_->set_network_provider(this);
|
this->client_->set_network_provider(this);
|
||||||
this->client_->set_persistence_provider(this);
|
this->client_->set_persistence_provider(this);
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
this->controller_role_ = &this->client_->add_controller();
|
||||||
|
this->controller_role_->set_listener(this);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!this->client_->start_server()) {
|
if (!this->client_->start_server()) {
|
||||||
ESP_LOGE(TAG, "Failed to start Sendspin server");
|
ESP_LOGE(TAG, "Failed to start Sendspin server");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@@ -138,6 +143,23 @@ std::optional<uint32_t> SendspinHub::load_last_server_hash() {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Sendspin role specific methods/overrides ---
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
// THREAD CONTEXT: Main loop (invoked from ESPHome actions / other components)
|
||||||
|
void SendspinHub::send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume,
|
||||||
|
std::optional<bool> mute) {
|
||||||
|
if (this->is_ready()) {
|
||||||
|
this->controller_role_->send_command(command, volume, mute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// THREAD CONTEXT: Main loop (ControllerRoleListener override, fired from client_->loop())
|
||||||
|
void SendspinHub::on_controller_state(const sendspin::ServerStateControllerObject &state) {
|
||||||
|
this->controller_state_callbacks_.call(state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace esphome::sendspin_
|
} // namespace esphome::sendspin_
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
#include <sendspin/config.h>
|
#include <sendspin/config.h>
|
||||||
#include <sendspin/types.h>
|
#include <sendspin/types.h>
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
#include <sendspin/controller_role.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -50,6 +54,9 @@ struct LastPlayedServerPref {
|
|||||||
/// (for services the library pulls; e.g., persistence, network readiness).
|
/// (for services the library pulls; e.g., persistence, network readiness).
|
||||||
/// - User -> library communication uses exposed functions on the client and role objects that the user calls.
|
/// - User -> library communication uses exposed functions on the client and role objects that the user calls.
|
||||||
class SendspinHub final : public Component,
|
class SendspinHub final : public Component,
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
public sendspin::ControllerRoleListener,
|
||||||
|
#endif
|
||||||
public sendspin::SendspinClientListener,
|
public sendspin::SendspinClientListener,
|
||||||
public sendspin::SendspinNetworkProvider,
|
public sendspin::SendspinNetworkProvider,
|
||||||
public sendspin::SendspinPersistenceProvider {
|
public sendspin::SendspinPersistenceProvider {
|
||||||
@@ -94,6 +101,17 @@ class SendspinHub final : public Component,
|
|||||||
|
|
||||||
void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
|
void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
|
||||||
|
|
||||||
|
// --- Sendspin role specific methods ---
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
void send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume = std::nullopt,
|
||||||
|
std::optional<bool> mute = std::nullopt);
|
||||||
|
|
||||||
|
template<typename F> void add_controller_state_callback(F &&callback) {
|
||||||
|
this->controller_state_callbacks_.add(std::forward<F>(callback));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// @brief Builds the SendspinClientConfig from ESPHome configuration and platform info.
|
/// @brief Builds the SendspinClientConfig from ESPHome configuration and platform info.
|
||||||
sendspin::SendspinClientConfig build_client_config_();
|
sendspin::SendspinClientConfig build_client_config_();
|
||||||
@@ -112,6 +130,19 @@ class SendspinHub final : public Component,
|
|||||||
bool save_last_server_hash(uint32_t hash) override;
|
bool save_last_server_hash(uint32_t hash) override;
|
||||||
std::optional<uint32_t> load_last_server_hash() override;
|
std::optional<uint32_t> load_last_server_hash() override;
|
||||||
|
|
||||||
|
// --- Sendspin role specific methods/overrides/member variables ---
|
||||||
|
|
||||||
|
#ifdef USE_SENDSPIN_CONTROLLER
|
||||||
|
sendspin::ControllerRole *controller_role_{nullptr};
|
||||||
|
|
||||||
|
void on_controller_state(const sendspin::ServerStateControllerObject &state) override;
|
||||||
|
|
||||||
|
// Callback fan-out to child components; they filter as needed
|
||||||
|
CallbackManager<void(const sendspin::ServerStateControllerObject &)> controller_state_callbacks_{};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// --- Core member variables ---
|
||||||
|
|
||||||
ESPPreferenceObject last_played_server_pref_;
|
ESPPreferenceObject last_played_server_pref_;
|
||||||
|
|
||||||
std::unique_ptr<sendspin::SendspinClient> client_;
|
std::unique_ptr<sendspin::SendspinClient> client_;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# `sendspin.switch` action enables the controller role, so we use a standalone test
|
||||||
|
packages:
|
||||||
|
base: !include common.yaml
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
on_connect:
|
||||||
|
then:
|
||||||
|
- sendspin.switch:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<<: !include common-action.yaml
|
||||||
Reference in New Issue
Block a user