mirror of
https://github.com/esphome/esphome.git
synced 2026-05-20 09:31:56 +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 esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, network, psram, socket, wifi
|
||||
import esphome.config_validation as cv
|
||||
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
|
||||
|
||||
# mdns for autodiscovery
|
||||
@@ -22,6 +24,13 @@ SendspinHub = sendspin_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
SendspinSwitchCommandAction = sendspin_ns.class_(
|
||||
"SendspinSwitchCommandAction",
|
||||
automation.Action,
|
||||
cg.Parented.template(SendspinHub),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SendspinConfiguration:
|
||||
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:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
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_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()) {
|
||||
ESP_LOGE(TAG, "Failed to start Sendspin server");
|
||||
this->mark_failed();
|
||||
@@ -138,6 +143,23 @@ std::optional<uint32_t> SendspinHub::load_last_server_hash() {
|
||||
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_
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include <sendspin/config.h>
|
||||
#include <sendspin/types.h>
|
||||
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
#include <sendspin/controller_role.h>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -50,6 +54,9 @@ struct LastPlayedServerPref {
|
||||
/// (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.
|
||||
class SendspinHub final : public Component,
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
public sendspin::ControllerRoleListener,
|
||||
#endif
|
||||
public sendspin::SendspinClientListener,
|
||||
public sendspin::SendspinNetworkProvider,
|
||||
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; }
|
||||
|
||||
// --- 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:
|
||||
/// @brief Builds the SendspinClientConfig from ESPHome configuration and platform info.
|
||||
sendspin::SendspinClientConfig build_client_config_();
|
||||
@@ -112,6 +130,19 @@ class SendspinHub final : public Component,
|
||||
bool save_last_server_hash(uint32_t 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_;
|
||||
|
||||
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