diff --git a/cspot/include/MercurySession.h b/cspot/include/MercurySession.h index 91f05f58..de53dd2a 100644 --- a/cspot/include/MercurySession.h +++ b/cspot/include/MercurySession.h @@ -26,12 +26,9 @@ class MercurySession : public bell::Task, public cspot::Session { struct Response { Header mercuryHeader; - uint8_t flags; DataParts parts; - uint64_t sequenceId; bool fail; }; - typedef std::function ResponseCallback; typedef std::function&)> AudioKeyCallback; @@ -54,6 +51,11 @@ class MercurySession : public bell::Task, public cspot::Session { COUNTRY_CODE_RESPONSE = 0x1B, }; + enum class ResponseFlag : uint8_t { + FINAL = 0x01, + PARTIAL = 0x02, + }; + std::unordered_map RequestTypeMap = { {RequestType::GET, "GET"}, {RequestType::SEND, "SEND"}, @@ -111,7 +113,8 @@ class MercurySession : public bell::Task, public cspot::Session { void runTask() override; void reconnect(); - std::unordered_map callbacks; + std::unordered_map callbacks; + std::unordered_map partials; std::unordered_map subscriptions; std::unordered_map audioKeyCallbacks; @@ -129,6 +132,6 @@ class MercurySession : public bell::Task, public cspot::Session { void failAllPending(); - Response decodeResponse(const std::vector& data); + std::pair decodeResponse(const std::vector& data); }; } // namespace cspot diff --git a/cspot/protobuf/mercury.options b/cspot/protobuf/mercury.options index e7c7718b..d0fb231b 100644 --- a/cspot/protobuf/mercury.options +++ b/cspot/protobuf/mercury.options @@ -1,2 +1,5 @@ Header.uri max_size:256, fixed_length:false Header.method max_size:64, fixed_length:false +UserField.key type:FT_POINTER +UserField.value type:FT_POINTER +Header.user_fields max_count:64, fixed_count:false \ No newline at end of file diff --git a/cspot/protobuf/mercury.proto b/cspot/protobuf/mercury.proto index 72138948..cd754d2d 100644 --- a/cspot/protobuf/mercury.proto +++ b/cspot/protobuf/mercury.proto @@ -1,4 +1,12 @@ message Header { optional string uri = 0x01; + optional string content_type = 0x02; optional string method = 0x03; + optional int32 status_code = 0x04; + repeated UserField user_fields = 0x06; } + +message UserField { + optional string key = 0x01; + optional string value = 0x02; +} \ No newline at end of file diff --git a/cspot/src/MercurySession.cpp b/cspot/src/MercurySession.cpp index 7aafbb76..4863472a 100644 --- a/cspot/src/MercurySession.cpp +++ b/cspot/src/MercurySession.cpp @@ -68,6 +68,7 @@ void MercurySession::reconnect() { try { this->conn = nullptr; this->shanConn = nullptr; + this->partials.clear(); this->connectWithRandomAp(); this->authenticate(this->authBlob); @@ -174,19 +175,26 @@ void MercurySession::handlePacket() { CSPOT_LOG(debug, "Received mercury packet"); auto response = this->decodeResponse(packet.data); - if (this->callbacks.count(response.sequenceId) > 0) { - auto seqId = response.sequenceId; - this->callbacks[response.sequenceId](response); - this->callbacks.erase(this->callbacks.find(seqId)); + if (response.first == static_cast(ResponseFlag::FINAL)) { + auto partial = this->partials.find(response.second); + if (this->callbacks.count(response.second)) { + this->callbacks[response.second](partial->second); + this->callbacks.erase(this->callbacks.find(response.second)); + } + this->partials.erase(partial); } break; } case RequestType::SUBRES: { auto response = decodeResponse(packet.data); - auto uri = std::string(response.mercuryHeader.uri); - if (this->subscriptions.count(uri) > 0) { - this->subscriptions[uri](response); + if (response.first == static_cast(ResponseFlag::FINAL)) { + auto partial = this->partials.find(response.second); + auto uri = std::string(partial->second.mercuryHeader.uri); + if (this->subscriptions.count(uri) > 0) { + this->subscriptions[uri](partial->second); + } + this->partials.erase(partial); } break; } @@ -214,33 +222,60 @@ void MercurySession::failAllPending() { this->callbacks = {}; } -MercurySession::Response MercurySession::decodeResponse( +std::pair MercurySession::decodeResponse( const std::vector& data) { - Response response = {}; - response.parts = {}; - auto sequenceLength = ntohs(extract(data, 0)); - response.sequenceId = hton64(extract(data, 2)); - - auto partsNumber = ntohs(extract(data, 11)); - - auto headerSize = ntohs(extract(data, 13)); - auto headerBytes = - std::vector(data.begin() + 15, data.begin() + 15 + headerSize); - - auto pos = 15 + headerSize; - while (pos < data.size()) { + uint64_t sequenceId; + uint8_t flag; + if (sequenceLength == 2) + sequenceId = ntohs(extract(data, 2)); + else if (sequenceLength == 4) + sequenceId = ntohl(extract(data, 2)); + else if (sequenceLength == 8) + sequenceId = hton64(extract(data, 2)); + else + return std::make_pair(0, 0); + + size_t pos = 2 + sequenceLength; + flag = (uint8_t)data[pos]; + pos++; + auto parts = ntohs(extract(data, pos)); + pos += 2; + auto partial = partials.find(sequenceId); + if (partial == partials.end()) { + CSPOT_LOG(debug, + "Creating new Mercury Response, seq: %llu, flags: %i, parts: %i", + sequenceId, flag, parts); + partial = this->partials.insert({sequenceId, Response()}).first; + partial->second.parts = {}; + partial->second.fail = false; + } else + CSPOT_LOG(debug, + "Adding to Mercury Response, seq: %llu, flags: %i, parts: %i", + sequenceId, flag, parts); + uint8_t index = 0; + while (parts) { + if (data.size() <= pos || partial->second.fail) + break; auto partSize = ntohs(extract(data, pos)); - - response.parts.push_back(std::vector( - data.begin() + pos + 2, data.begin() + pos + 2 + partSize)); - pos += 2 + partSize; + pos += 2; + if (!partial->second.mercuryHeader.has_uri) { + partial->second.fail = false; + auto headerBytes = std::vector(data.begin() + pos, + data.begin() + pos + partSize); + pbDecode(partial->second.mercuryHeader, Header_fields, headerBytes); + } else { + if (index >= partial->second.parts.size()) + partial->second.parts.push_back(std::vector{}); + partial->second.parts[index].insert(partial->second.parts[index].end(), + data.begin() + pos, + data.begin() + pos + partSize); + index++; + } + pos += partSize; + parts--; } - - pbDecode(response.mercuryHeader, Header_fields, headerBytes); - response.fail = false; - - return response; + return std::make_pair(flag, sequenceId); } uint64_t MercurySession::executeSubscription(RequestType method, diff --git a/flake.nix b/flake.nix index 9c07fb73..59955e12 100644 --- a/flake.nix +++ b/flake.nix @@ -30,15 +30,7 @@ clang-tools = pkgs.clang-tools.override {llvmPackages = llvm;}; - apps = { - }; - - packages = { - target-cli = llvm.stdenv.mkDerivation { - name = "cspotcli"; - src = ./.; - cmakeFlags = ["-DCSPOT_TARGET_CLI=ON"]; - nativeBuildInputs = with pkgs; [ + cspot-pkgs = with pkgs; [ avahi avahi-compat cmake @@ -50,6 +42,16 @@ portaudio protobuf ]; + + apps = { + }; + + packages = { + target-cli = llvm.stdenv.mkDerivation { + name = "cspotcli"; + src = ./.; + cmakeFlags = ["-DCSPOT_TARGET_CLI=ON"]; + nativeBuildInputs = cspot-pkgs; # Patch nanopb shebangs to refer to provided python postPatch = '' patchShebangs cspot/bell/external/nanopb/generator/* @@ -60,7 +62,7 @@ devShells = { default = pkgs.mkShell { - packages = with pkgs; [cmake unstable.mbedtls ninja python3] ++ [clang-tools llvm.clang]; + packages = cspot-pkgs; }; }; in { diff --git a/targets/cli/CliPlayer.cpp b/targets/cli/CliPlayer.cpp index 1642c4ec..69cbfba7 100644 --- a/targets/cli/CliPlayer.cpp +++ b/targets/cli/CliPlayer.cpp @@ -118,8 +118,6 @@ void CliPlayer::runTask() { continue; } else { if (lastHash != chunk->trackHash) { - std::cout << " Last hash " << lastHash << " new hash " - << chunk->trackHash << std::endl; lastHash = chunk->trackHash; this->handler->notifyAudioReachedPlayback(); } diff --git a/targets/esp32/main/EspPlayer.cpp b/targets/esp32/main/EspPlayer.cpp new file mode 100644 index 00000000..0d7ed823 --- /dev/null +++ b/targets/esp32/main/EspPlayer.cpp @@ -0,0 +1,134 @@ +#include "EspPlayer.h" + +#include // for uint8_t +#include // for __base +#include // for operator<<, basic_ostream, endl, cout +#include // for shared_ptr, make_shared, make_unique +#include // for scoped_lock +#include // for hash, string_view +#include // for remove_extent_t +#include // for move +#include // for get +#include // for vector + +#include "BellUtils.h" // for BELL_SLEEP_MS +#include "CircularBuffer.h" +#include "Logger.h" +#include "SpircHandler.h" // for SpircHandler, SpircHandler::EventType +#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16 +#include "TrackPlayer.h" // for TrackPlayer + +EspPlayer::EspPlayer(std::unique_ptr sink, + std::shared_ptr handler) + : bell::Task("player", 32 * 1024, 0, 1) { + this->handler = handler; + this->audioSink = std::move(sink); + + this->circularBuffer = std::make_shared(1024 * 128); + + auto hashFunc = std::hash(); + + this->handler->getTrackPlayer()->setDataCallback( + [this, &hashFunc](uint8_t* data, size_t bytes, std::string_view trackId) { + auto hash = hashFunc(trackId); + this->feedData(data, bytes, hash); + return bytes; + }); + + this->isPaused = false; + + this->handler->setEventHandler( + [this, &hashFunc](std::unique_ptr event) { + switch (event->eventType) { + case cspot::SpircHandler::EventType::PLAY_PAUSE: + if (std::get(event->data)) { + this->pauseRequested = true; + } else { + this->isPaused = false; + this->pauseRequested = false; + } + break; + case cspot::SpircHandler::EventType::DISC: + this->circularBuffer->emptyBuffer(); + break; + case cspot::SpircHandler::EventType::FLUSH: + this->circularBuffer->emptyBuffer(); + break; + case cspot::SpircHandler::EventType::SEEK: + this->circularBuffer->emptyBuffer(); + break; + case cspot::SpircHandler::EventType::PLAYBACK_START: + this->isPaused = true; + this->playlistEnd = false; + this->circularBuffer->emptyBuffer(); + break; + case cspot::SpircHandler::EventType::DEPLETED: + this->playlistEnd = true; + break; + case cspot::SpircHandler::EventType::VOLUME: { + int volume = std::get(event->data); + break; + } + default: + break; + } + }); + startTask(); +} + +void EspPlayer::feedData(uint8_t* data, size_t len, size_t trackId) { + size_t toWrite = len; + + while (toWrite > 0) { + this->current_hash = trackId; + size_t written = + this->circularBuffer->write(data + (len - toWrite), toWrite); + if (written == 0) { + BELL_SLEEP_MS(10); + } + + toWrite -= written; + } +} + +void EspPlayer::runTask() { + std::vector outBuf = std::vector(1024); + + std::scoped_lock lock(runningMutex); + + size_t lastHash = 0; + + while (isRunning) { + if (!this->isPaused) { + size_t read = this->circularBuffer->read(outBuf.data(), outBuf.size()); + if (this->pauseRequested) { + this->pauseRequested = false; + std::cout << "Pause requested!" << std::endl; + this->isPaused = true; + } + + this->audioSink->feedPCMFrames(outBuf.data(), read); + + if (read == 0) { + if (this->playlistEnd) { + this->handler->notifyAudioEnded(); + this->playlistEnd = false; + } + BELL_SLEEP_MS(10); + continue; + } else { + if (lastHash != current_hash) { + lastHash = current_hash; + this->handler->notifyAudioReachedPlayback(); + } + } + } else { + BELL_SLEEP_MS(100); + } + } +} + +void EspPlayer::disconnect() { + isRunning = false; + std::scoped_lock lock(runningMutex); +} diff --git a/targets/esp32/main/EspPlayer.h b/targets/esp32/main/EspPlayer.h new file mode 100644 index 00000000..47189231 --- /dev/null +++ b/targets/esp32/main/EspPlayer.h @@ -0,0 +1,41 @@ +#pragma once + +#include // for size_t +#include // for uint8_t +#include // for atomic +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for string + +#include "AudioSink.h" // for AudioSink +#include "BellTask.h" // for Task + +namespace bell { +class CircularBuffer; +} // namespace bell +namespace cspot { +class SpircHandler; +} // namespace cspot + +class EspPlayer : public bell::Task { + public: + EspPlayer(std::unique_ptr sink, + std::shared_ptr spircHandler); + void disconnect(); + + private: + std::string currentTrackId; + std::shared_ptr handler; + std::unique_ptr audioSink; + std::shared_ptr circularBuffer; + void feedData(uint8_t* data, size_t len, size_t); + + std::atomic pauseRequested = false; + std::atomic isPaused = true; + std::atomic isRunning = true; + std::mutex runningMutex; + std::atomic playlistEnd = false; + size_t current_hash; + + void runTask() override; +}; diff --git a/targets/esp32/main/Kconfig.projbuild b/targets/esp32/main/Kconfig.projbuild index b0a46638..64e164cb 100644 --- a/targets/esp32/main/Kconfig.projbuild +++ b/targets/esp32/main/Kconfig.projbuild @@ -52,6 +52,31 @@ menu "CSPOT Configuration" config CSPOT_STATUS_LED_TYPE_RMT bool "RMT - Addressable LED" endchoice + choice CSPOT_LOGIN + prompt "Login type" + default CSPOT_LOGIN_ZEROCONF + help + Select login form + + config CSPOT_LOGIN_ZEROCONF + bool "Login with zeroconfServer" + help + login with zeroconf + config CSPOT_LOGIN_PASS + bool "Login with password" + endchoice + + menu "username & password" + visible if CSPOT_LOGIN_PASS + config CSPOT_LOGIN_USERNAME + string "Spotify username" + default "username" + config CSPOT_LOGIN_PASSWORD + string "Spotify password" + default "password" + help + login with username and password + endmenu config CSPOT_STATUS_LED_GPIO int "Status LED GPIO number" diff --git a/targets/esp32/main/main.cpp b/targets/esp32/main/main.cpp index 8e518803..d0ba2ba5 100644 --- a/targets/esp32/main/main.cpp +++ b/targets/esp32/main/main.cpp @@ -7,6 +7,7 @@ #include #include #include "BellHTTPServer.h" +#include "BellLogger.h" // for setDefaultLogger, AbstractLogger #include "BellTask.h" #include "civetweb.h" #include "esp_event.h" @@ -21,6 +22,8 @@ #include "protocol_examples_common.h" #include "sdkconfig.h" +#include "EspPlayer.h" + #include #include #include @@ -30,11 +33,7 @@ #include "CircularBuffer.h" #include "BellUtils.h" -#include "ES8311AudioSink.h" -#include "ESPStatusLed.h" #include "Logger.h" -#include "freertos/ringbuf.h" -#include "freertos/task.h" #define DEVICE_NAME CONFIG_CSPOT_DEVICE_NAME @@ -59,303 +58,182 @@ static const char* TAG = "cspot"; -std::shared_ptr statusLed; -std::string credentialsFileName = "/spiffs/authBlob.json"; -bool createdFromZeroconf = false; extern "C" { -void app_main(void); + void app_main(void); } -class CSpotPlayer : public bell::Task { - private: - std::shared_ptr handler; - std::unique_ptr audioSink; - std::unique_ptr circularBuffer; - std::atomic isPaused; - - public: - CSpotPlayer(std::shared_ptr handler) - : bell::Task("cspot", 8 * 1024, 0, 0) { - this->handler = handler; - this->audioSink = std::make_unique(); - this->audioSink->setParams(44100, 2, 16); - this->audioSink->volumeChanged(160); - - this->circularBuffer = - std::make_unique(1024 * 128 * 8); - - this->handler->getTrackPlayer()->setDataCallback( - [this](uint8_t* data, size_t bytes) { this->feedData(data, bytes); }); - this->isPaused = false; - - this->handler->setEventHandler( - [this](std::unique_ptr event) { - switch (event->eventType) { - case cspot::SpircHandler::EventType::PLAY_PAUSE: - this->isPaused = std::get(event->data); - break; - case cspot::SpircHandler::EventType::FLUSH: - this->circularBuffer->emptyBuffer(); - break; - case cspot::SpircHandler::EventType::SEEK: - this->circularBuffer->emptyBuffer(); - break; - case cspot::SpircHandler::EventType::PLAYBACK_START: - this->circularBuffer->emptyBuffer(); - default: - break; - } - }); - startTask(); - } +class ZeroconfAuthenticator { +public: + ZeroconfAuthenticator() {}; + ~ZeroconfAuthenticator() {}; - void feedData(uint8_t* data, size_t len) { - size_t toWrite = len; + // Authenticator state + int serverPort = 7864; - while (toWrite > 0) { - size_t written = - this->circularBuffer->write(data + (len - toWrite), toWrite); - if (written == 0) { - BELL_SLEEP_MS(10); - } + // Use bell's HTTP server to handle the authentication, although anything can be used + std::unique_ptr server; + std::shared_ptr blob; - toWrite -= written; - } - } + std::function onAuthSuccess; + std::function onClose; - void runTask() { - std::vector outBuf = std::vector(1024); + void registerHandlers() { + this->server = std::make_unique(serverPort); + + server->registerGet("/spotify_info", [this](struct mg_connection* conn) { + return this->server->makeJsonResponse(this->blob->buildZeroconfInfo()); + }); + + + server->registerGet("/close", [this](struct mg_connection* conn) { + this->onClose(); + return this->server->makeEmptyResponse(); + }); + + + server->registerPost("/spotify_info", [this](struct mg_connection* conn) { + nlohmann::json obj; + // Prepare a success response for spotify + obj["status"] = 101; + obj["spotifyError"] = 0; + obj["statusString"] = "ERROR-OK"; + + std::string body = ""; + auto requestInfo = mg_get_request_info(conn); + if (requestInfo->content_length > 0) { + body.resize(requestInfo->content_length); + mg_read(conn, body.data(), requestInfo->content_length); - while (true) { - if (!this->isPaused) { - size_t read = this->circularBuffer->read(outBuf.data(), outBuf.size()); - this->audioSink->feedPCMFrames(outBuf.data(), read); + mg_header hd[10]; + int num = mg_split_form_urlencoded(body.data(), hd, 10); + std::map queryMap; - if (read == 0) { - BELL_SLEEP_MS(100); + // Parse the form data + for (int i = 0; i < num; i++) { + queryMap[hd[i].name] = hd[i].value; } - } else { - BELL_SLEEP_MS(100); + + CSPOT_LOG(info, "Received zeroauth POST data"); + + // Pass user's credentials to the blob + blob->loadZeroconfQuery(queryMap); + + // We have the blob, proceed to login + onAuthSuccess(); } - } + + return server->makeJsonResponse(obj.dump()); + }); + + + // Register mdns service, for spotify to find us + bell::MDNSService::registerService( + blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort, + { {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} }); + std::cout << "Waiting for spotify app to connect..." << std::endl; } }; class CSpotTask : public bell::Task { - public: - CSpotTask() : bell::Task("cspot", 32 * 1024, 0, 1) { startTask(); } +private: + std::unique_ptr handler; + std::unique_ptr audioSink; +public: + CSpotTask() : bell::Task("cspot", 8 * 1024, 0, 0) { startTask(); } void runTask() { + mdns_init(); mdns_hostname_set("cspot"); - std::atomic gotBlob = false; - - auto blob = std::make_shared(DEVICE_NAME); - - auto server = std::make_unique(8080); - server->registerGet( - "/spotify_info", [&server, blob](struct mg_connection* conn) { - return server->makeJsonResponse(blob->buildZeroconfInfo()); - }); - server->registerPost( - "/spotify_info", [&server, blob, &gotBlob](struct mg_connection* conn) { - nlohmann::json obj; - obj["status"] = 101; - obj["spotifyError"] = 0; - obj["statusString"] = "ERROR-OK"; - - std::string body = ""; - auto requestInfo = mg_get_request_info(conn); - if (requestInfo->content_length > 0) { - body.resize(requestInfo->content_length); - mg_read(conn, body.data(), requestInfo->content_length); - - mg_header hd[10]; - int num = mg_split_form_urlencoded(body.data(), hd, 10); - std::map queryMap; - - for (int i = 0; i < num; i++) { - queryMap[hd[i].name] = hd[i].value; - } - - blob->loadZeroconfQuery(queryMap); - gotBlob = true; - } - - return server->makeJsonResponse(obj.dump()); - }); +#ifdef CONFIG_CSPOT_SINK_INTERNAL +auto audioSink = std::make_unique(); +#endif +#ifdef CONFIG_CSPOT_SINK_AC101 +auto audioSink = std::make_unique(); +#endif +#ifdef CONFIG_CSPOT_SINK_ES8388 +auto audioSink = std::make_unique(); +#endif +#ifdef CONFIG_CSPOT_SINK_ES9018 +auto audioSink = std::make_unique(); +#endif +#ifdef CONFIG_CSPOT_SINK_PCM5102 +auto audioSink = std::make_unique(); +#endif +#ifdef CONFIG_CSPOT_SINK_TAS5711 +auto audioSink = std::make_unique(); +#endif + audioSink->setParams(44100, 2, 16); + audioSink->volumeChanged(160); - bell::MDNSService::registerService( - blob->getDeviceName(), "_spotify-connect", "_tcp", "", 8080, - {{"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"}}); + auto loggedInSemaphore = std::make_shared(1); - while (!gotBlob) { - BELL_SLEEP_MS(1000); - BELL_LOG(info, "cspot", "Waiting for spotify app to connect..."); - } + auto zeroconfServer = std::make_unique(); + std::atomic isRunning = true; - BELL_LOG(info, "cspot", "Got blob!"); - if (gotBlob) { - auto ctx = cspot::Context::createFromBlob(blob); - CSPOT_LOG(info, "Creating player"); - ctx->session->connectWithRandomAp(); - auto token = ctx->session->authenticate(blob); - - // Auth successful - if (token.size() > 0) { - ctx->session->startTask(); - auto handler = std::make_shared(ctx); - handler->subscribeToMercury(); - auto player = std::make_shared(handler); - - while (true) { - ctx->session->handlePacket(); - } + zeroconfServer->onClose = [&isRunning]() { + isRunning = false; + }; - handler->disconnect(); - // player->disconnect(); + auto loginBlob = std::make_shared(DEVICE_NAME); +#ifdef CONFIG_CSPOT_LOGIN_PASS + loginBlob->loadUserPass(CONFIG_CSPOT_LOGIN_USERNAME, CONFIG_CSPOT_LOGIN_PASSWORD); + loggedInSemaphore->give(); + +#else + zeroconfServer->blob = loginBlob; + zeroconfServer->onAuthSuccess = [loggedInSemaphore]() { + loggedInSemaphore->give(); + }; + zeroconfServer->registerHandlers(); +#endif + loggedInSemaphore->wait(); + auto ctx = cspot::Context::createFromBlob(loginBlob); + ctx->session->connectWithRandomAp(); + ctx->config.authData = ctx->session->authenticate(loginBlob); + if (ctx->config.authData.size() > 0) { + // when credentials file is set, then store reusable credentials + + // Start spirc task + auto handler = std::make_shared(ctx); + + // Start handling mercury messages + ctx->session->startTask(); + + // Create a player, pass the handler + auto player = std::make_shared(std::move(audioSink), std::move(handler)); + + // If we wanted to handle multiple devices, we would halt this loop + // when a new zeroconf login is requested, and reinitialize the session + while (isRunning) { + ctx->session->handlePacket(); } + + // Never happens, but required for above case + handler->disconnect(); + player->disconnect(); + } } }; -static void cspotTask(void* pvParameters) { - - // #ifdef CONFIG_CSPOT_SINK_INTERNAL - // auto audioSink = std::make_shared(); - // #endif - // #ifdef CONFIG_CSPOT_SINK_AC101 - // auto audioSink = std::make_shared(); - // #endif - // #ifdef CONFIG_CSPOT_SINK_ES8388 - // auto audioSink = std::make_shared(); - // #endif - // #ifdef CONFIG_CSPOT_SINK_ES9018 - // auto audioSink = std::make_shared(); - // #endif - // #ifdef CONFIG_CSPOT_SINK_PCM5102 - // auto audioSink = std::make_shared(); - // #endif - // #ifdef CONFIG_CSPOT_SINK_TAS5711 - // auto audioSink = std::make_shared(); - // #endif - - // // Config file - // file = std::make_shared(); - // configMan = std::make_shared("/spiffs/config.json", file); - - // if (!configMan->load()) { - // CSPOT_LOG(error, "Config error"); - // } - - // configMan->deviceName = DEVICE_NAME; - // #ifdef CONFIG_CSPOT_QUALITY_96 - // configMan->format = AudioFormat_OGG_VORBIS_96; - // #endif - // #ifdef CONFIG_CSPOT_QUALITY_160 - // configMan->format = AudioFormat_OGG_VORBIS_160; - // #endif - // #ifdef CONFIG_CSPOT_QUALITY_320 - // configMan->format = AudioFormat_OGG_VORBIS_320; - // #endif - - // auto createPlayerCallback = [audioSink](std::shared_ptr blob) { - - // // heap_trace_start(HEAP_TRACE_LEAKS); - // // esp_dump_per_task_heap_info(); - - // CSPOT_LOG(info, "Creating player"); - // statusLed->setStatus(StatusLed::SPOT_INITIALIZING); - - // auto session = std::make_unique(); - // session->connectWithRandomAp(); - // auto token = session->authenticate(blob); - - // // Auth successful - // if (token.size() > 0) - // { - // if (createdFromZeroconf) { - // file->writeFile(credentialsFileName, blob->toJson()); - // } - - // statusLed->setStatus(StatusLed::SPOT_READY); - - // mercuryManager = std::make_shared(std::move(session)); - // mercuryManager->startTask(); - - // spircController = std::make_shared(mercuryManager, blob->username, audioSink); - - // spircController->setEventHandler([](CSpotEvent& event) { - // switch (event.eventType) { - // case CSpotEventType::TRACK_INFO: - // CSPOT_LOG(info, "Track Info"); - // break; - // case CSpotEventType::PLAY_PAUSE: - // CSPOT_LOG(info, "Track Pause"); - // break; - // case CSpotEventType::SEEK: - // CSPOT_LOG(info, "Track Seek"); - // break; - // case CSpotEventType::DISC: - // CSPOT_LOG(info, "Disconnect"); - // spircController->stopPlayer(); - // mercuryManager->stop(); - // break; - // case CSpotEventType::PREV: - // CSPOT_LOG(info, "Track Previous"); - // break; - // case CSpotEventType::NEXT: - // CSPOT_LOG(info, "Track Next"); - // break; - // default: - // break; - // } - // }); - - // mercuryManager->reconnectedCallback = []() { - // return spircController->subscribe(); - // }; - - // mercuryManager->handleQueue(); - - // mercuryManager.reset(); - // spircController.reset(); - // } - - // BELL_SLEEP_MS(10000); - // // heap_trace_stop(); - // // heap_trace_dump(); - // ESP_LOGI(TAG, "Player exited"); - // auto memUsage = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - // auto memUsage2 = heap_caps_get_free_size(MALLOC_CAP_DMA | MALLOC_CAP_8BIT); - - // BELL_LOG(info, "esp32", "Free RAM %d | %d", memUsage, memUsage2); - // }; - - // createdFromZeroconf = true; - // auto httpServer = std::make_shared(2137); - // auto authenticator = std::make_shared(createPlayerCallback, httpServer); - // authenticator->registerHandlers(); - // httpServer->listen(); - - vTaskSuspend(NULL); -} - void init_spiffs() { - esp_vfs_spiffs_conf_t conf = {.base_path = "/spiffs", + esp_vfs_spiffs_conf_t conf = { .base_path = "/spiffs", .partition_label = NULL, .max_files = 5, - .format_if_mount_failed = true}; + .format_if_mount_failed = true }; esp_err_t ret = esp_vfs_spiffs_register(&conf); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount or format filesystem"); - } else if (ret == ESP_ERR_NOT_FOUND) { + } + else if (ret == ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "Failed to find SPIFFS partition"); - } else { + } + else { ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); } return; @@ -365,8 +243,9 @@ void init_spiffs() { ret = esp_spiffs_info(conf.partition_label, &total, &used); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", - esp_err_to_name(ret)); - } else { + esp_err_to_name(ret)); + } + else { ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); } } @@ -377,7 +256,7 @@ void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || - ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } @@ -398,7 +277,7 @@ void app_main(void) { //auto taskHandle = xTaskCreatePinnedToCore(&cspotTask, "cspot", 12*1024, NULL, 5, NULL, 1); /*auto taskHandle = */ bell::setDefaultLogger(); - + bell::enableTimestampLogging(); auto task = std::make_unique(); vTaskSuspend(NULL); } diff --git a/targets/esp32/sdkconfig.defaults b/targets/esp32/sdkconfig.defaults index e7db07f6..2bd5878a 100644 --- a/targets/esp32/sdkconfig.defaults +++ b/targets/esp32/sdkconfig.defaults @@ -4,14 +4,14 @@ # CONFIG_IDF_CMAKE=y CONFIG_IDF_TARGET_ARCH_XTENSA=y -CONFIG_IDF_TARGET="esp32s3" -CONFIG_IDF_TARGET_ESP32S3=y -CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 +CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_TARGET_ESP32=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 # # SDK tool configuration # -CONFIG_SDK_TOOLPREFIX="xtensa-esp32s3-elf-" +CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-" # CONFIG_SDK_TOOLCHAIN_SUPPORTS_TIME_WIDE_64_BITS is not set # end of SDK tool configuration @@ -38,7 +38,6 @@ CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 # # Bootloader config # -CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set @@ -90,27 +89,26 @@ CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y # CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200 # CONFIG_ESPTOOLPY_NO_STUB is not set -# CONFIG_ESPTOOLPY_OCT_FLASH is not set # CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set # CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set CONFIG_ESPTOOLPY_FLASHMODE_DIO=y # CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set CONFIG_ESPTOOLPY_FLASHFREQ_80M=y # CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set # CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set CONFIG_ESPTOOLPY_FLASHFREQ="80m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -130,6 +128,7 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200 CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # end of Serial flasher config + # # Partition Table # @@ -554,115 +553,155 @@ CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y # end of ESP-TLS # -# ESP32S3-Specific +# ESP32-specific # -# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set -# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160 is not set -CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y -CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 - -# -# Cache config -# -CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y -# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set -CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 -# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set -CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y -CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 -# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set -CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y -CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 -# CONFIG_ESP32S3_INSTRUCTION_CACHE_WRAP is not set -# CONFIG_ESP32S3_DATA_CACHE_16KB is not set -CONFIG_ESP32S3_DATA_CACHE_32KB=y -# CONFIG_ESP32S3_DATA_CACHE_64KB is not set -CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 -# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set -CONFIG_ESP32S3_DATA_CACHE_8WAYS=y -CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 -# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set -CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y -# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set -CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 -# CONFIG_ESP32S3_DATA_CACHE_WRAP is not set -# end of Cache config - -CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_ESP32_REV_MIN_0=y +# CONFIG_ESP32_REV_MIN_1 is not set +# CONFIG_ESP32_REV_MIN_2 is not set +# CONFIG_ESP32_REV_MIN_3 is not set +CONFIG_ESP32_REV_MIN=0 +CONFIG_ESP32_DPORT_WORKAROUND=y +# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set +# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_ESP32_SPIRAM_SUPPORT=y # # SPI RAM config # -CONFIG_SPIRAM_MODE_QUAD=y -# CONFIG_SPIRAM_MODE_OCT is not set CONFIG_SPIRAM_TYPE_AUTO=y -# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set # CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set # CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set CONFIG_SPIRAM_SIZE=-1 - -# -# PSRAM Clock and CS IO for ESP32S3 -# -CONFIG_DEFAULT_PSRAM_CLK_IO=30 -CONFIG_DEFAULT_PSRAM_CS_IO=26 -# end of PSRAM Clock and CS IO for ESP32S3 - -# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set -# CONFIG_SPIRAM_RODATA is not set -CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y -# CONFIG_SPIRAM_SPEED_120M is not set -CONFIG_SPIRAM_SPEED_80M=y # CONFIG_SPIRAM_SPEED_40M is not set +CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM=y CONFIG_SPIRAM_BOOT_INIT=y -# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set # CONFIG_SPIRAM_USE_MEMMAP is not set # CONFIG_SPIRAM_USE_CAPS_ALLOC is not set CONFIG_SPIRAM_USE_MALLOC=y CONFIG_SPIRAM_MEMTEST=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 -CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y -CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256 +# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_CACHE_WORKAROUND=y + +# +# SPIRAM cache workaround debugging +# +CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_MEMW=y +# CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_DUPLDST is not set +# CONFIG_SPIRAM_CACHE_WORKAROUND_STRATEGY_NOPS is not set +# end of SPIRAM cache workaround debugging + +CONFIG_SPIRAM_BANKSWITCH_ENABLE=y +CONFIG_SPIRAM_BANKSWITCH_RESERVE=8 +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y +# CONFIG_SPIRAM_OCCUPY_HSPI_HOST is not set +CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y +# CONFIG_SPIRAM_OCCUPY_NO_HOST is not set + +# +# PSRAM clock and cs IO for ESP32-DOWD +# +CONFIG_D0WD_PSRAM_CLK_IO=17 +CONFIG_D0WD_PSRAM_CS_IO=16 +# end of PSRAM clock and cs IO for ESP32-DOWD + +# +# PSRAM clock and cs IO for ESP32-D2WD +# +CONFIG_D2WD_PSRAM_CLK_IO=9 +CONFIG_D2WD_PSRAM_CS_IO=10 +# end of PSRAM clock and cs IO for ESP32-D2WD + +# +# PSRAM clock and cs IO for ESP32-PICO +# +CONFIG_PICO_PSRAM_CS_IO=10 +# end of PSRAM clock and cs IO for ESP32-PICO + +# CONFIG_SPIRAM_2T_MODE is not set # end of SPI RAM config -# CONFIG_ESP32S3_TRAX is not set -CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 -# CONFIG_ESP32S3_ULP_COPROC_ENABLED is not set -CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM=0 -CONFIG_ESP32S3_DEBUG_OCDAWARE=y -CONFIG_ESP32S3_BROWNOUT_DET=y -CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set -# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set -CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 -CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y -# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set -# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set -# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set -CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y -# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set -# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set -# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set -CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 -CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 -# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set -# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set -# end of ESP32S3-Specific +# CONFIG_ESP32_TRAX is not set +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 +# CONFIG_ESP32_ULP_COPROC_ENABLED is not set +CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=0 +CONFIG_ESP32_DEBUG_OCDAWARE=y +CONFIG_ESP32_BROWNOUT_DET=y +CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_7 is not set +CONFIG_ESP32_BROWNOUT_DET_LVL=0 +CONFIG_ESP32_REDUCE_PHY_TX_POWER=y +CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32_XTAL_FREQ_40=y +# CONFIG_ESP32_XTAL_FREQ_26 is not set +# CONFIG_ESP32_XTAL_FREQ_AUTO is not set +CONFIG_ESP32_XTAL_FREQ=40 +# CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE is not set +# CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set +# CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set +CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL=5 +# end of ESP32-specific # # ADC-Calibration # # end of ADC-Calibration + # # Common ESP-related # CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_TX_GPIO=1 +CONFIG_ESP_CONSOLE_UART_RX_GPIO=3 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=800 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT_OFFSET=2 +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y # end of Common ESP-related #