mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 17:39:00 +08:00
Add a generic_image platform component and have Sendspin impelement it for artwork
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -1,4 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
@@ -6,9 +6,13 @@ from esphome.components import esp32, network, psram, socket, wifi
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_FORMAT,
|
||||
CONF_HEIGHT,
|
||||
CONF_ID,
|
||||
CONF_SAMPLE_RATE,
|
||||
CONF_SOURCE,
|
||||
CONF_TASK_STACK_IN_PSRAM,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import TemplateArgsType
|
||||
@@ -21,6 +25,7 @@ DEPENDENCIES = ["network"]
|
||||
DOMAIN = "sendspin"
|
||||
|
||||
CONF_SENDSPIN_ID = "sendspin_id"
|
||||
CONF_SLOT = "slot"
|
||||
|
||||
CONF_INITIAL_STATIC_DELAY = "initial_static_delay"
|
||||
CONF_FIXED_DELAY = "fixed_delay"
|
||||
@@ -35,9 +40,21 @@ CODEC_FORMAT_OPUS = SendspinCodecFormat.enum("OPUS")
|
||||
CODEC_FORMAT_PCM = SendspinCodecFormat.enum("PCM")
|
||||
CODEC_FORMAT_UNSUPPORTED = SendspinCodecFormat.enum("UNSUPPORTED")
|
||||
|
||||
SendspinImageFormat = sendspin_library_ns.enum("SendspinImageFormat", is_class=True)
|
||||
IMAGE_FORMAT_JPEG = SendspinImageFormat.enum("JPEG")
|
||||
IMAGE_FORMAT_PNG = SendspinImageFormat.enum("PNG")
|
||||
IMAGE_FORMAT_BMP = SendspinImageFormat.enum("BMP")
|
||||
|
||||
SendspinImageSource = sendspin_library_ns.enum("SendspinImageSource", is_class=True)
|
||||
IMAGE_SOURCE_ALBUM = SendspinImageSource.enum("ALBUM")
|
||||
IMAGE_SOURCE_ARTIST = SendspinImageSource.enum("ARTIST")
|
||||
IMAGE_SOURCE_NONE = SendspinImageSource.enum("NONE")
|
||||
|
||||
# Library Structs
|
||||
AudioSupportedFormatObject = sendspin_library_ns.struct("AudioSupportedFormatObject")
|
||||
PlayerRoleConfig = sendspin_library_ns.struct("PlayerRoleConfig")
|
||||
ArtworkRoleConfig = sendspin_library_ns.struct("ArtworkRoleConfig")
|
||||
ImageSlotPreference = sendspin_library_ns.struct("ImageSlotPreference")
|
||||
|
||||
# Trailing underscore avoids clashing with sendspin-cpp's global `sendspin` namespace.
|
||||
# Analysis tools strip the trailing underscore (same pattern as `template_`).
|
||||
@@ -63,6 +80,7 @@ class SendspinConfiguration:
|
||||
player_support: bool = False
|
||||
visualizer_support: bool = False
|
||||
|
||||
artwork_preferences: list[ConfigType] = field(default_factory=list)
|
||||
player_config: ConfigType | None = None
|
||||
|
||||
|
||||
@@ -97,6 +115,12 @@ def request_visualizer_support() -> None:
|
||||
_get_data().visualizer_support = True
|
||||
|
||||
|
||||
def register_artwork_preference(config: ConfigType) -> None:
|
||||
"""Register an artwork slot preference from an image subcomponent."""
|
||||
request_artwork_support()
|
||||
_get_data().artwork_preferences.append(config)
|
||||
|
||||
|
||||
def register_player_config(config: ConfigType) -> None:
|
||||
"""Register the player role config from the media source subcomponent."""
|
||||
data = _get_data()
|
||||
@@ -203,6 +227,27 @@ async def to_code(config: ConfigType) -> None:
|
||||
# and disable building unused code paths in the sendspin-cpp library (IDF SDKConfig via CONFIG_SENDSPIN_ENABLE_*).
|
||||
if data.artwork_support:
|
||||
cg.add_define("USE_SENDSPIN_ARTWORK", True)
|
||||
|
||||
preference_structs = [
|
||||
cg.StructInitializer(
|
||||
ImageSlotPreference,
|
||||
("slot", pref[CONF_SLOT]),
|
||||
("source", pref[CONF_SOURCE]),
|
||||
("format", pref[CONF_FORMAT]),
|
||||
("width", pref[CONF_WIDTH]),
|
||||
("height", pref[CONF_HEIGHT]),
|
||||
)
|
||||
for pref in data.artwork_preferences
|
||||
]
|
||||
|
||||
artwork_psram_stack = bool(config.get(CONF_TASK_STACK_IN_PSRAM))
|
||||
artwork_config = cg.StructInitializer(
|
||||
ArtworkRoleConfig,
|
||||
("preferred_formats", preference_structs),
|
||||
("psram_stack", artwork_psram_stack),
|
||||
("priority", 2),
|
||||
)
|
||||
cg.add(var.set_artwork_config(artwork_config))
|
||||
else:
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_SENDSPIN_ENABLE_ARTWORK", False)
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"""Sendspin generic_image platform."""
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import runtime_image
|
||||
from esphome.components.image import CONF_TRANSPARENCY, add_metadata
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FORMAT,
|
||||
CONF_HEIGHT,
|
||||
CONF_ID,
|
||||
CONF_RESIZE,
|
||||
CONF_SOURCE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import (
|
||||
CONF_SENDSPIN_ID,
|
||||
CONF_SLOT,
|
||||
IMAGE_FORMAT_BMP,
|
||||
IMAGE_FORMAT_JPEG,
|
||||
IMAGE_FORMAT_PNG,
|
||||
IMAGE_SOURCE_ALBUM,
|
||||
IMAGE_SOURCE_ARTIST,
|
||||
IMAGE_SOURCE_NONE,
|
||||
SendspinHub,
|
||||
register_artwork_preference,
|
||||
sendspin_ns,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["runtime_image"]
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
DEPENDENCIES = ["sendspin"]
|
||||
|
||||
MAX_IMAGE_SLOTS = 4
|
||||
_SLOT_COUNTER_KEY = "sendspin_image_slot_counter"
|
||||
|
||||
CONF_ON_IMAGE_DISPLAY = "on_image_display"
|
||||
CONF_ON_IMAGE_ERROR = "on_image_error"
|
||||
|
||||
# Map runtime_image's format string to the sendspin library's SendspinImageFormat enum.
|
||||
_FORMAT_TO_SENDSPIN_ENUM = {
|
||||
"JPEG": IMAGE_FORMAT_JPEG,
|
||||
"PNG": IMAGE_FORMAT_PNG,
|
||||
"BMP": IMAGE_FORMAT_BMP,
|
||||
}
|
||||
|
||||
IMAGE_SOURCES = {
|
||||
"ALBUM": IMAGE_SOURCE_ALBUM,
|
||||
"ARTIST": IMAGE_SOURCE_ARTIST,
|
||||
"NONE": IMAGE_SOURCE_NONE,
|
||||
}
|
||||
|
||||
SendspinImage = sendspin_ns.class_(
|
||||
"SendspinImage",
|
||||
runtime_image.RuntimeImage,
|
||||
cg.Component,
|
||||
)
|
||||
|
||||
SendspinImageDisplayTrigger = sendspin_ns.class_(
|
||||
"SendspinImageDisplayTrigger", automation.Trigger.template()
|
||||
)
|
||||
SendspinImageErrorTrigger = sendspin_ns.class_(
|
||||
"SendspinImageErrorTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
|
||||
def _assign_slot_and_register(config: ConfigType) -> ConfigType:
|
||||
"""Auto-assign a slot, validate the max count, and register the artwork preference with the hub."""
|
||||
current = CORE.data.get(_SLOT_COUNTER_KEY, 0)
|
||||
if current >= MAX_IMAGE_SLOTS:
|
||||
raise cv.Invalid(
|
||||
f"Too many Sendspin generic_image components. Maximum is {MAX_IMAGE_SLOTS}."
|
||||
)
|
||||
CORE.data[_SLOT_COUNTER_KEY] = current + 1
|
||||
config[CONF_SLOT] = current
|
||||
|
||||
width, height = config[CONF_RESIZE]
|
||||
register_artwork_preference(
|
||||
{
|
||||
CONF_SLOT: current,
|
||||
CONF_SOURCE: config[CONF_SOURCE],
|
||||
CONF_FORMAT: _FORMAT_TO_SENDSPIN_ENUM[config[CONF_FORMAT]],
|
||||
CONF_WIDTH: width,
|
||||
CONF_HEIGHT: height,
|
||||
}
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
runtime_image.runtime_image_schema(SendspinImage).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SendspinImage),
|
||||
cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub),
|
||||
cv.Required(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_SOURCE, default="ALBUM"): cv.enum(
|
||||
IMAGE_SOURCES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ON_IMAGE_DISPLAY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
SendspinImageDisplayTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_IMAGE_ERROR): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
SendspinImageErrorTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
runtime_image.validate_runtime_image_settings,
|
||||
cv.only_on_esp32,
|
||||
_assign_slot_and_register,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
settings = await runtime_image.process_runtime_image_config(config)
|
||||
|
||||
add_metadata(
|
||||
config[CONF_ID],
|
||||
settings.width,
|
||||
settings.height,
|
||||
config[CONF_TYPE],
|
||||
config[CONF_TRANSPARENCY],
|
||||
)
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
settings.width,
|
||||
settings.height,
|
||||
settings.format_enum,
|
||||
settings.image_type_enum,
|
||||
settings.transparent,
|
||||
settings.byte_order_big_endian,
|
||||
settings.placeholder,
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_SENDSPIN_ID])
|
||||
|
||||
cg.add(var.set_slot(config[CONF_SLOT]))
|
||||
cg.add(var.set_image_source(IMAGE_SOURCES[config[CONF_SOURCE]]))
|
||||
|
||||
for conf in config.get(CONF_ON_IMAGE_DISPLAY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_IMAGE_ERROR, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "sendspin_generic_image.h"
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_SENDSPIN_ARTWORK)
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::sendspin_ {
|
||||
|
||||
static const char *const TAG = "sendspin.generic_image";
|
||||
|
||||
SendspinImage::SendspinImage(int fixed_width, int fixed_height, runtime_image::ImageFormat format,
|
||||
image::ImageType type, image::Transparency transparency, bool is_big_endian,
|
||||
image::Image *placeholder)
|
||||
: runtime_image::RuntimeImage(format, type, transparency, placeholder, is_big_endian, fixed_width, fixed_height) {}
|
||||
|
||||
// THREAD CONTEXT: Main loop. The decode callback registered below fires on the artwork
|
||||
// decode thread; the display and clear callbacks fire on the main loop.
|
||||
void SendspinImage::setup() {
|
||||
this->parent_->add_image_decode_callback(
|
||||
[this](uint8_t slot, const uint8_t *data, size_t length, sendspin::SendspinImageFormat format) {
|
||||
if (slot == this->slot_)
|
||||
this->on_decode_(data, length);
|
||||
});
|
||||
this->parent_->add_image_display_callback([this](uint8_t slot) {
|
||||
if (slot == this->slot_)
|
||||
this->on_display_();
|
||||
});
|
||||
this->parent_->add_image_clear_callback([this](uint8_t slot) {
|
||||
if (slot == this->slot_)
|
||||
this->on_clear_();
|
||||
});
|
||||
}
|
||||
|
||||
// THREAD CONTEXT: Dedicated artwork decode thread (via SendspinHub's decode callback).
|
||||
// Decode synchronously into the back buffer; heavy CPU work is allowed here.
|
||||
void SendspinImage::on_decode_(const uint8_t *data, size_t length) {
|
||||
this->begin_decode(length);
|
||||
size_t total_consumed = 0;
|
||||
while (total_consumed < length) {
|
||||
int consumed = this->feed_data(const_cast<uint8_t *>(data) + total_consumed, length - total_consumed);
|
||||
if (consumed < 0) {
|
||||
ESP_LOGE(TAG, "Error decoding image data at offset %zu", total_consumed);
|
||||
this->image_error_callback_.call();
|
||||
return;
|
||||
}
|
||||
total_consumed += consumed;
|
||||
}
|
||||
if (!this->end_decode()) {
|
||||
ESP_LOGE(TAG, "Failed to finalize image after decoding");
|
||||
this->image_error_callback_.call();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// THREAD CONTEXT: Main loop (fired once the server display timestamp is reached)
|
||||
void SendspinImage::on_display_() { this->image_display_callback_.call(); }
|
||||
|
||||
// THREAD CONTEXT: Main loop
|
||||
void SendspinImage::on_clear_() {
|
||||
this->release();
|
||||
this->image_display_callback_.call();
|
||||
}
|
||||
|
||||
} // namespace esphome::sendspin_
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if defined(USE_ESP32) && defined(USE_SENDSPIN_ARTWORK)
|
||||
|
||||
#include "esphome/components/runtime_image/runtime_image.h"
|
||||
#include "esphome/components/sendspin/sendspin_hub.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#include <sendspin/artwork_role.h>
|
||||
|
||||
namespace esphome::sendspin_ {
|
||||
|
||||
class SendspinImage : public SendspinChild, public runtime_image::RuntimeImage {
|
||||
public:
|
||||
SendspinImage(int fixed_width, int fixed_height, runtime_image::ImageFormat format, image::ImageType type,
|
||||
image::Transparency transparency, bool is_big_endian = false, image::Image *placeholder = nullptr);
|
||||
|
||||
void setup() override;
|
||||
|
||||
template<typename F> void add_on_image_display_callback(F &&callback) {
|
||||
this->image_display_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
template<typename F> void add_on_image_error_callback(F &&callback) {
|
||||
this->image_error_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
void set_image_source(sendspin::SendspinImageSource source) { this->source_ = source; }
|
||||
void set_slot(uint8_t slot) { this->slot_ = slot; }
|
||||
|
||||
protected:
|
||||
// Artwork thread. Decodes encoded bytes synchronously; buffer is valid only for this call.
|
||||
void on_decode_(const uint8_t *data, size_t length);
|
||||
// Main loop thread. Trigger when art should be displayed.
|
||||
void on_display_();
|
||||
// Main loop thread. Releases the decoded image and refires the display trigger so listeners re-render.
|
||||
void on_clear_();
|
||||
|
||||
LazyCallbackManager<void()> image_display_callback_{};
|
||||
LazyCallbackManager<void()> image_error_callback_{};
|
||||
|
||||
sendspin::SendspinImageSource source_{sendspin::SendspinImageSource::ALBUM};
|
||||
uint8_t slot_{0};
|
||||
};
|
||||
|
||||
class SendspinImageDisplayTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit SendspinImageDisplayTrigger(SendspinImage *parent) {
|
||||
parent->add_on_image_display_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class SendspinImageErrorTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit SendspinImageErrorTrigger(SendspinImage *parent) {
|
||||
parent->add_on_image_error_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::sendspin_
|
||||
|
||||
#endif
|
||||
@@ -34,6 +34,10 @@ void SendspinHub::setup() {
|
||||
this->client_->set_network_provider(this);
|
||||
this->client_->set_persistence_provider(this);
|
||||
|
||||
#ifdef USE_SENDSPIN_ARTWORK
|
||||
this->client_->add_artwork(this->artwork_config_).set_listener(this);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
this->controller_role_ = &this->client_->add_controller();
|
||||
this->controller_role_->set_listener(this);
|
||||
@@ -157,6 +161,20 @@ std::optional<uint32_t> SendspinHub::load_last_server_hash() {
|
||||
|
||||
// --- Sendspin role specific methods/overrides ---
|
||||
|
||||
#ifdef USE_SENDSPIN_ARTWORK
|
||||
// THREAD CONTEXT: Dedicated artwork decode thread; downstream callbacks run here too
|
||||
void SendspinHub::on_image_decode(uint8_t slot, const uint8_t *data, size_t length,
|
||||
sendspin::SendspinImageFormat format) {
|
||||
this->artwork_image_decode_callbacks_.call(slot, data, length, format);
|
||||
}
|
||||
|
||||
// THREAD CONTEXT: Main loop (fired from client_->loop() once the server display timestamp is reached)
|
||||
void SendspinHub::on_image_display(uint8_t slot) { this->artwork_image_display_callbacks_.call(slot); }
|
||||
|
||||
// THREAD CONTEXT: Main loop (fired from client_->loop())
|
||||
void SendspinHub::on_image_clear(uint8_t slot) { this->artwork_image_clear_callbacks_.call(slot); }
|
||||
#endif
|
||||
|
||||
#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,
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include <sendspin/config.h>
|
||||
#include <sendspin/types.h>
|
||||
|
||||
#ifdef USE_SENDSPIN_ARTWORK
|
||||
#include <sendspin/artwork_role.h>
|
||||
#endif
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
#include <sendspin/controller_role.h>
|
||||
#endif
|
||||
@@ -67,6 +70,9 @@ struct StaticDelayPref {
|
||||
/// (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_ARTWORK
|
||||
public sendspin::ArtworkRoleListener,
|
||||
#endif
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
public sendspin::ControllerRoleListener,
|
||||
#endif
|
||||
@@ -119,6 +125,20 @@ class SendspinHub final : public Component,
|
||||
|
||||
// --- Sendspin role specific methods ---
|
||||
|
||||
#ifdef USE_SENDSPIN_ARTWORK
|
||||
void set_artwork_config(const sendspin::ArtworkRoleConfig &config) { this->artwork_config_ = config; }
|
||||
|
||||
template<typename F> void add_image_decode_callback(F &&callback) {
|
||||
this->artwork_image_decode_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
template<typename F> void add_image_display_callback(F &&callback) {
|
||||
this->artwork_image_display_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
template<typename F> void add_image_clear_callback(F &&callback) {
|
||||
this->artwork_image_clear_callbacks_.add(std::forward<F>(callback));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
void send_client_command(sendspin::SendspinControllerCommand command, std::optional<uint8_t> volume = std::nullopt,
|
||||
std::optional<bool> mute = std::nullopt);
|
||||
@@ -165,6 +185,23 @@ class SendspinHub final : public Component,
|
||||
|
||||
// --- Sendspin role specific methods/overrides/member variables ---
|
||||
|
||||
#ifdef USE_SENDSPIN_ARTWORK
|
||||
void on_image_decode(uint8_t slot, const uint8_t *data, size_t length, sendspin::SendspinImageFormat format) override;
|
||||
|
||||
void on_image_display(uint8_t slot) override;
|
||||
|
||||
void on_image_clear(uint8_t slot) override;
|
||||
|
||||
sendspin::ArtworkRoleConfig artwork_config_{};
|
||||
|
||||
// Callback fan-out to child components; they filter by slot as needed.
|
||||
// decode and display fire from the library's artwork thread; clear fires from the main loop.
|
||||
CallbackManager<void(uint8_t, const uint8_t *, size_t, sendspin::SendspinImageFormat)>
|
||||
artwork_image_decode_callbacks_{};
|
||||
CallbackManager<void(uint8_t)> artwork_image_display_callbacks_{};
|
||||
CallbackManager<void(uint8_t)> artwork_image_clear_callbacks_{};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENDSPIN_CONTROLLER
|
||||
sendspin::ControllerRole *controller_role_{nullptr};
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
spi_id: spi_bus
|
||||
id: main_lcd
|
||||
model: ili9342
|
||||
cs_pin: 20
|
||||
dc_pin: 13
|
||||
reset_pin: 21
|
||||
invert_colors: true
|
||||
lambda: |-
|
||||
it.fill(Color(0, 0, 0));
|
||||
it.image(0, 0, id(album_art));
|
||||
|
||||
generic_image:
|
||||
- platform: sendspin
|
||||
id: album_art
|
||||
format: JPEG
|
||||
type: RGB565
|
||||
resize: 240x240
|
||||
source: ALBUM
|
||||
on_image_display:
|
||||
- logger.log: "Album art displayed"
|
||||
on_image_error:
|
||||
- logger.log: "Album art error"
|
||||
- platform: sendspin
|
||||
id: artist_art
|
||||
format: PNG
|
||||
type: RGB565
|
||||
resize: 96x96
|
||||
source: ARTIST
|
||||
@@ -0,0 +1 @@
|
||||
<<: !include common-generic_image.yaml
|
||||
Reference in New Issue
Block a user