Step 7.5b1: cspot (philippe44 fork) builds and links on the P4

- external/cspot: git submodule, pinned to philippe44/cspot 3010349
  (bell ed2d6e9); GPL-3.0.
- components/spotify: project component wrapping cspot. bell trimmed to
  what cspot needs (Tremor Vorbis; no codec wrapper, sinks, MQTT, web
  server, fmt, regex; cJSON from IDF). Xtensa biquad assembly filtered
  out (P4 is RISC-V). CMAKE_POLICY_VERSION_MINIMUM 3.5 for CMake 4.
- Patches in components/spotify/patches, applied at configure time:
  nanopb generator works with protobuf >= 5 (MakeClass removed);
  URLParser.cpp missing <cstdio>/<cstring> for GCC 14.
- CONFIG_COMPILER_CXX_EXCEPTIONS=y (cspot needs exceptions).
- spotify_init() only builds a LoginBlob (link check).
- CLAUDE.md: submodule init and IDF-venv Python packages for nanopb.
- Verified on board: "cspot linked: device "P4 AES67", zeroconf info 588
  bytes"; HLS still playing, PTP locked; image 1.83 MB (70% free).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 15:25:10 +10:00
parent 7bbda0a1e8
commit 039ec75473
11 changed files with 125 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "external/cspot"]
path = external/cspot
url = https://github.com/philippe44/cspot.git
+1
View File
@@ -27,6 +27,7 @@ Repo: https://gitea.apointless.space/bsncubed/aes67-ESP32-P4
- Our board: **32 MB** flash (GigaDevice c8/4019). App slots must stay below 16 MB (cache mapping above 16 MB is experimental in IDF). - Our board: **32 MB** flash (GigaDevice c8/4019). App slots must stay below 16 MB (cache mapping above 16 MB is experimental in IDF).
- Rev < 3 also limits Espressif's prebuilt audio libraries: `esp_audio_codec` must stay < 2.6 and `esp_audio_effects` < 1.4 (newer versions use P4 assembly that needs rev >= 3; the build fails with a message saying so). Check this for any new Espressif binary component. - Rev < 3 also limits Espressif's prebuilt audio libraries: `esp_audio_codec` must stay < 2.6 and `esp_audio_effects` < 1.4 (newer versions use P4 assembly that needs rev >= 3; the build fails with a message saying so). Check this for any new Espressif binary component.
- Embed `web/index.html` via `EMBED_TXTFILES` in `aes67_web`. - Embed `web/index.html` via `EMBED_TXTFILES` in `aes67_web`.
- cspot (Spotify, `external/cspot` git submodule, philippe44 fork, pinned): after cloning run `git submodule update --init external/cspot && git -C external/cspot submodule update --init cspot/bell`. Its nanopb code generator needs, in the IDF Python env: `python -m pip install protobuf grpcio-tools 'setuptools<81'` (after `. export.sh`). Our fixes to cspot/bell live in `components/spotify/patches/{cspot,bell}/*.patch` and are applied automatically at configure time; don't edit the submodule directly, add a patch.
- Flash over the network (normal way since step 2b; keep USB for recovery): - Flash over the network (normal way since step 2b; keep USB for recovery):
`curl -f --data-binary @build/aes67_p4.bin -H 'Content-Type: application/octet-stream' http://p4-aes67/api/ota` `curl -f --data-binary @build/aes67_p4.bin -H 'Content-Type: application/octet-stream' http://p4-aes67/api/ota`
The board reboots into the new image on trial; check `GET /api/ota` shows the new version with `pending_verify: false`. The board reboots into the new image on trial; check `GET /api/ota` shows the new version with `pending_verify: false`.
+49
View File
@@ -0,0 +1,49 @@
# Spotify Connect via cspot (philippe44 fork, git submodule external/cspot). GPL-3.0.
# Project component (not part of the reusable aes67_* core).
idf_component_register(SRCS "spotify.cpp"
INCLUDE_DIRS "include"
PRIV_REQUIRES espressif__mdns mbedtls json pthread lwip esp_timer)
# bell: only what cspot needs (Tremor Vorbis inside; no codec wrapper, sinks, MQTT, web server).
set(BELL_DISABLE_CODECS ON CACHE BOOL "" FORCE)
set(BELL_DISABLE_SINKS ON CACHE BOOL "" FORCE)
set(BELL_DISABLE_MQTT ON CACHE BOOL "" FORCE)
set(BELL_DISABLE_WEBSERVER ON CACHE BOOL "" FORCE)
set(BELL_DISABLE_FMT ON CACHE BOOL "" FORCE)
set(BELL_DISABLE_REGEX ON CACHE BOOL "" FORCE)
set(BELL_ONLY_CJSON ON CACHE BOOL "" FORCE)
set(BELL_EXTERNAL_CJSON idf::json CACHE STRING "" FORCE)
option(BUILD_TESTING OFF)
# Our fixes to the pinned cspot / bell sources (newer GCC, protobuf, CMake), kept as patch files:
# patches/cspot/*.patch apply in external/cspot, patches/bell/*.patch in external/cspot/cspot/bell.
# Each is applied once (skipped when it already reverse-applies).
function(spotify_apply_patches dir repo)
file(GLOB patches "${CMAKE_CURRENT_LIST_DIR}/patches/${dir}/*.patch")
list(SORT patches)
foreach(p ${patches})
execute_process(COMMAND git apply --reverse --check "${p}" WORKING_DIRECTORY "${repo}"
RESULT_VARIABLE applied OUTPUT_QUIET ERROR_QUIET)
if(NOT applied EQUAL 0)
execute_process(COMMAND git apply "${p}" WORKING_DIRECTORY "${repo}" RESULT_VARIABLE rc)
if(NOT rc EQUAL 0)
message(FATAL_ERROR "spotify: patch does not apply: ${p}")
endif()
message(STATUS "spotify: applied ${p}")
endif()
endforeach()
endfunction()
spotify_apply_patches(cspot "${PROJECT_DIR}/external/cspot")
spotify_apply_patches(bell "${PROJECT_DIR}/external/cspot/cspot/bell")
# cspot/bell declare cmake_minimum_required(2.8.12); CMake 4 rejects < 3.5 unless told otherwise.
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
add_subdirectory(${PROJECT_DIR}/external/cspot/cspot ${CMAKE_CURRENT_BINARY_DIR}/cspot)
# bell adds Xtensa (ESP32) assembly for a DSP biquad; the P4 is RISC-V and cspot does not use it.
get_target_property(BELL_SRCS bell SOURCES)
list(FILTER BELL_SRCS EXCLUDE REGEX "\\.S$")
set_target_properties(bell PROPERTIES SOURCES "${BELL_SRCS}")
target_link_libraries(${COMPONENT_LIB} PRIVATE cspot)
target_compile_options(${COMPONENT_LIB} PRIVATE -std=gnu++20)
+14
View File
@@ -0,0 +1,14 @@
// Spotify Connect (cspot). Project code; GPL-3.0 through cspot.
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
esp_err_t spotify_init(const char *device_name);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,18 @@
diff --git a/external/nanopb/generator/nanopb_generator.py b/external/nanopb/generator/nanopb_generator.py
index e40f18b..64dad5e 100755
--- a/external/nanopb/generator/nanopb_generator.py
+++ b/external/nanopb/generator/nanopb_generator.py
@@ -1463,7 +1463,12 @@ class Message(ProtoElement):
optional_only.ClearField(str('extension'))
optional_only.ClearField(str('enum_type'))
desc = google.protobuf.descriptor.MakeDescriptor(optional_only)
- msg = reflection.MakeClass(desc)()
+ # protobuf >= 5 removed reflection.MakeClass (patch for this project)
+ try:
+ msg = reflection.MakeClass(desc)()
+ except AttributeError:
+ from google.protobuf import message_factory
+ msg = message_factory.GetMessageClass(desc)()
for field in optional_only.field:
if field.type == FieldD.TYPE_STRING:
@@ -0,0 +1,13 @@
diff --git a/main/io/URLParser.cpp b/main/io/URLParser.cpp
index 4a50ab1..af679ec 100644
--- a/main/io/URLParser.cpp
+++ b/main/io/URLParser.cpp
@@ -1,5 +1,8 @@
#include "URLParser.h"
+#include <cstdio> // sscanf (GCC 14 / ESP-IDF 5.5: not included transitively)
+#include <cstring> // strstr
+
namespace bell {
#ifdef BELL_DISABLE_REGEX
+17
View File
@@ -0,0 +1,17 @@
#include "spotify.h"
#include <memory>
#include "LoginBlob.h"
#include "esp_log.h"
static const char *TAG = "spotify";
// Step 7.5b1: link check only. Zeroconf, login and audio follow in b2/b3.
extern "C" esp_err_t spotify_init(const char *device_name)
{
auto blob = std::make_shared<cspot::LoginBlob>(device_name);
std::string info = blob->buildZeroconfInfo();
ESP_LOGI(TAG, "cspot linked: device \"%s\", zeroconf info %u bytes", device_name, (unsigned)info.size());
return ESP_OK;
}
Vendored Submodule
+1
Submodule external/cspot added at 3010349b46
+1 -1
View File
@@ -2,4 +2,4 @@ idf_component_register(SRCS "main.c" "project_cfg.c" "player.c" "audio_ring.c" "
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES esp_app_format esp_hw_support heap esp_http_client mbedtls esp_timer REQUIRES esp_app_format esp_hw_support heap esp_http_client mbedtls esp_timer
aes67_board aes67_health aes67_net aes67_ota aes67_ptp aes67_sdp_sap aes67_board aes67_health aes67_net aes67_ota aes67_ptp aes67_sdp_sap
aes67_syslog aes67_tx aes67_web) aes67_syslog aes67_tx aes67_web spotify)
+5 -1
View File
@@ -8,6 +8,7 @@
#include "audio_ring.h" #include "audio_ring.h"
#include "decoder.h" #include "decoder.h"
#include "hls.h" #include "hls.h"
#include "spotify.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@@ -118,7 +119,10 @@ esp_err_t player_init(void)
if (err != ESP_OK) { if (err != ESP_OK) {
return err; return err;
} }
err = hls_start(decoder_feed); cJSON *sc = cfg_get("source");
spotify_init(cJSON_GetObjectItemCaseSensitive(sc, "spotify_name")->valuestring);
cJSON_Delete(sc);
err = hls_start(decoder_feed);
if (err != ESP_OK) { if (err != ESP_OK) {
return err; return err;
} }
+3
View File
@@ -28,3 +28,6 @@ CONFIG_LWIP_MAX_SOCKETS=16
# 32 MB PSRAM in the P4 package (hex mode, 200 MHz; 250 MHz needs rev >= 3). Needed for step 7: # 32 MB PSRAM in the P4 package (hex mode, 200 MHz; 250 MHz needs rev >= 3). Needed for step 7:
# HLS segment buffers, cspot, decoders. malloc() puts blocks >16 KB in PSRAM; DMA buffers stay internal. # HLS segment buffers, cspot, decoders. malloc() puts blocks >16 KB in PSRAM; DMA buffers stay internal.
CONFIG_SPIRAM=y CONFIG_SPIRAM=y
# cspot (Spotify Connect, step 7.5) needs C++ exceptions.
CONFIG_COMPILER_CXX_EXCEPTIONS=y