Add internet radio streaming with runtime API, modernize web UI, remove auto-tune/beep at boot

Streaming (main feature this session):
- New WebRadioConfig/WebRadioJson core types, ISecureStore-backed persistence
- New webradio::WebRadioService (thread-safe live config) + GET/POST /api/streaming
- web_radio_stream task now runtime-toggleable (no reboot), no hardcoded URL
- Content-Type diagnostic: warns clearly when a URL is a webpage, not an audio stream

Boot cleanup:
- Removed boot-time auto FM/DAB tune, auto-beep, and the (now-concluded) Si4684
  crystal IBIAS/CTUN empirical sweep from main.cpp — tuning/beep are on-demand
  via the existing REST API only

Web UI:
- Modernized styling (cards, gradients, toggle switches, light/dark theme)
- New Stream tab wired to /api/streaming

Fixes found via real idf.py build (not just clangd):
- Restored wrongly-removed si4684/Si4684Tuner.hpp include in main.cpp
- Fixed MP3Decode() argument types in web_radio_stream.cpp (unsigned char**/int*)

Quality-gate fixes:
- Host-test stub headers (esp_log.h, freertos/*) so TunerService.cpp's
  scanForStation logging/pacing compiles for station_service_test /
  integration_service_test instead of running stale binaries
- Added WifiScanner and WebRadioService manual sections; filled in missing
  Doxygen docs on BluetoothService, i2s_sdata_probe, test_firmware, Bt1035At
- Ignore clangd's .cache/ index directory

Also includes prior uncommitted work carried in the tree: Wi-Fi/Bluetooth
device scan REST API and UI (WifiScanner, BT scan), SigmaStudio TCP bridge,
and the current ADAU1701 SigmaStudio DSP program export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 21:15:22 +02:00
co-authored by Claude Sonnet 5
parent 8a8523515d
commit 6f7b6dd12c
131 changed files with 20309 additions and 1702 deletions
+3
View File
@@ -18,6 +18,9 @@ docs/api/
__pycache__/
*.pyc
# clangd index cache
.cache/
# Proprietary Si4684 firmware blobs (Skyworks / uGreen — local only, not in git)
Firmware/Si4684-Firmware/*.bin
Firmware/Si4684-Firmware/.ugreen-extract-*/
-1
View File
@@ -1,6 +1,5 @@
# DigiRadio firmware — top-level ESP-IDF project
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# Nested driver/service components live under components/
@@ -1,3 +1,9 @@
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x6C , 0xAC ,
0x00 , 0x80 , 0x00 , 0x00 ,
0x00 , 0x80 , 0x00 , 0x00 ,
0x00 , 0x80 , 0x00 , 0x00 ,
0x00 , 0x80 , 0x00 , 0x00 ,
0x00 , 0x80 , 0x00 , 0x00 ,
0x00 , 0x80 , 0x00 , 0x00 ,
@@ -1016,10 +1022,4 @@
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 ,
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
/*
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadio_IC_1.h
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadioModificato\DigiRadio_IC_1.h
*
* Created: Monday, July 6, 2026 2:41:28 PM
* Created: Friday, August 7, 2026 3:19:11 AM
* Description: DigiRadio:IC 1 program data.
*
* This software is distributed in the hope that it will be useful,
@@ -23,7 +23,7 @@
#include "DigiRadio_IC_1_REG.h"
#define DEVICE_ARCHITECTURE_IC_1 "ADAU1701"
#define DEVICE_ADDR_IC_1 0x68
#define DEVICE_ADDR_IC_1 0x1
/* DSP Program Data */
#define PROGRAM_SIZE_IC_1 5120
@@ -41,310 +41,332 @@ ADI_REG_TYPE Program_Data_IC_1[PROGRAM_SIZE_IC_1] = {
0x00, 0x20, 0x00, 0xE8, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x28, 0x00, 0xE8, 0x01,
0x00, 0x12, 0x00, 0x20, 0x01,
0x00, 0x30, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x1A, 0x01, 0x20, 0x01,
0x00, 0x02, 0x01, 0xA0, 0x01,
0xFF, 0xE1, 0x07, 0x20, 0x01,
0x00, 0x38, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x22, 0x02, 0x20, 0x01,
0x00, 0x40, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x2A, 0x03, 0x20, 0x01,
0x00, 0x42, 0x02, 0x20, 0x01,
0x00, 0x52, 0x00, 0x22, 0x01,
0x00, 0x58, 0x00, 0xE2, 0x01,
0x00, 0x42, 0x00, 0x20, 0x01,
0x00, 0x59, 0x08, 0x22, 0x41,
0x00, 0x39, 0x08, 0x22, 0x01,
0x00, 0x31, 0x08, 0x22, 0x41,
0x00, 0x48, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x32, 0x04, 0x20, 0x01,
0x00, 0x3A, 0x04, 0x40, 0x01,
0x00, 0x42, 0x05, 0x22, 0x01,
0x00, 0x4A, 0x05, 0x44, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x12, 0x03, 0x20, 0x01,
0x00, 0x60, 0x00, 0xE2, 0x01,
0x01, 0x98, 0x00, 0xE4, 0x01,
0x00, 0x8A, 0x09, 0x20, 0x01,
0x00, 0x82, 0x0A, 0x22, 0x01,
0x00, 0x72, 0x09, 0x34, 0x01,
0x00, 0x6A, 0x0A, 0x22, 0x01,
0x00, 0x62, 0x06, 0x22, 0x01,
0x00, 0x5A, 0x07, 0x22, 0x01,
0x00, 0x52, 0x08, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x1A, 0x04, 0x20, 0x01,
0x00, 0x68, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x22, 0x05, 0x20, 0x01,
0x00, 0x70, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x2A, 0x06, 0x20, 0x01,
0x00, 0x78, 0x00, 0xE2, 0x01,
0x00, 0x90, 0x00, 0xF2, 0x01,
0x00, 0xBA, 0x0E, 0x20, 0x01,
0x00, 0xB2, 0x0F, 0x22, 0x01,
0x00, 0xA2, 0x0E, 0x34, 0x01,
0x00, 0x9A, 0x0F, 0x22, 0x01,
0x00, 0x7A, 0x0B, 0x22, 0x01,
0x00, 0x72, 0x0C, 0x22, 0x01,
0x00, 0x6A, 0x0D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x5A, 0x07, 0x20, 0x01,
0x00, 0x80, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x82, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x88, 0x00, 0xE2, 0x01,
0x00, 0x82, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x90, 0x00, 0xE2, 0x01,
0x00, 0x62, 0x09, 0x20, 0x01,
0x00, 0x6A, 0x09, 0x40, 0x01,
0x00, 0x72, 0x0A, 0x22, 0x01,
0x00, 0x7A, 0x0A, 0x44, 0x01,
0x00, 0x8A, 0x0B, 0x22, 0x01,
0x00, 0x92, 0x0B, 0x44, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0xA8, 0x00, 0xE2, 0x01,
0x00, 0xC0, 0x00, 0xF2, 0x01,
0x00, 0xEA, 0x13, 0x20, 0x01,
0x00, 0xE2, 0x14, 0x22, 0x01,
0x00, 0xD2, 0x13, 0x34, 0x01,
0x00, 0xCA, 0x14, 0x22, 0x01,
0x00, 0xAA, 0x10, 0x22, 0x01,
0x00, 0xA2, 0x11, 0x22, 0x01,
0x00, 0x9A, 0x12, 0x22, 0x01,
0x01, 0xE0, 0x00, 0xE4, 0x01,
0x00, 0xD2, 0x0F, 0x20, 0x01,
0x00, 0xCA, 0x10, 0x22, 0x01,
0x00, 0xBA, 0x0F, 0x34, 0x01,
0x00, 0xB2, 0x10, 0x22, 0x01,
0x00, 0xAA, 0x0C, 0x22, 0x01,
0x00, 0xA2, 0x0D, 0x22, 0x01,
0x00, 0x9A, 0x0E, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0xD8, 0x00, 0xE2, 0x01,
0x00, 0xF0, 0x00, 0xF2, 0x01,
0x01, 0x1A, 0x18, 0x20, 0x01,
0x01, 0x12, 0x19, 0x22, 0x01,
0x01, 0x02, 0x18, 0x34, 0x01,
0x00, 0xFA, 0x19, 0x22, 0x01,
0x00, 0xDA, 0x15, 0x22, 0x01,
0x00, 0xD2, 0x16, 0x22, 0x01,
0x00, 0xCA, 0x17, 0x22, 0x01,
0x00, 0xC0, 0x00, 0xE2, 0x01,
0x00, 0xD8, 0x00, 0xF2, 0x01,
0x01, 0x02, 0x14, 0x20, 0x01,
0x00, 0xFA, 0x15, 0x22, 0x01,
0x00, 0xEA, 0x14, 0x34, 0x01,
0x00, 0xE2, 0x15, 0x22, 0x01,
0x00, 0xC2, 0x11, 0x22, 0x01,
0x00, 0xBA, 0x12, 0x22, 0x01,
0x00, 0xB2, 0x13, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x08, 0x00, 0xE2, 0x01,
0x01, 0x20, 0x00, 0xF2, 0x01,
0x01, 0x4A, 0x1D, 0x20, 0x01,
0x01, 0x42, 0x1E, 0x22, 0x01,
0x01, 0x32, 0x1D, 0x34, 0x01,
0x01, 0x2A, 0x1E, 0x22, 0x01,
0x01, 0x0A, 0x1A, 0x22, 0x01,
0x01, 0x02, 0x1B, 0x22, 0x01,
0x00, 0xFA, 0x1C, 0x22, 0x01,
0x00, 0xF0, 0x00, 0xE2, 0x01,
0x01, 0x08, 0x00, 0xF2, 0x01,
0x01, 0x32, 0x19, 0x20, 0x01,
0x01, 0x2A, 0x1A, 0x22, 0x01,
0x01, 0x1A, 0x19, 0x34, 0x01,
0x01, 0x12, 0x1A, 0x22, 0x01,
0x00, 0xF2, 0x16, 0x22, 0x01,
0x00, 0xEA, 0x17, 0x22, 0x01,
0x00, 0xE2, 0x18, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x38, 0x00, 0xE2, 0x01,
0x01, 0x50, 0x00, 0xF2, 0x01,
0x01, 0x7A, 0x22, 0x20, 0x01,
0x01, 0x72, 0x23, 0x22, 0x01,
0x01, 0x62, 0x22, 0x34, 0x01,
0x01, 0x5A, 0x23, 0x22, 0x01,
0x01, 0x3A, 0x1F, 0x22, 0x01,
0x01, 0x32, 0x20, 0x22, 0x01,
0x01, 0x2A, 0x21, 0x22, 0x01,
0x01, 0x20, 0x00, 0xE2, 0x01,
0x01, 0x38, 0x00, 0xF2, 0x01,
0x01, 0x62, 0x1E, 0x20, 0x01,
0x01, 0x5A, 0x1F, 0x22, 0x01,
0x01, 0x4A, 0x1E, 0x34, 0x01,
0x01, 0x42, 0x1F, 0x22, 0x01,
0x01, 0x22, 0x1B, 0x22, 0x01,
0x01, 0x1A, 0x1C, 0x22, 0x01,
0x01, 0x12, 0x1D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x68, 0x00, 0xE2, 0x01,
0x01, 0x80, 0x00, 0xF2, 0x01,
0x01, 0xC2, 0x09, 0x20, 0x01,
0x01, 0xBA, 0x0A, 0x22, 0x01,
0x01, 0xAA, 0x09, 0x34, 0x01,
0x01, 0xA2, 0x0A, 0x22, 0x01,
0x01, 0x9A, 0x06, 0x22, 0x01,
0x01, 0x92, 0x07, 0x22, 0x01,
0x01, 0x8A, 0x08, 0x22, 0x01,
0x01, 0x50, 0x00, 0xE2, 0x01,
0x01, 0x68, 0x00, 0xF2, 0x01,
0x01, 0x92, 0x23, 0x20, 0x01,
0x01, 0x8A, 0x24, 0x22, 0x01,
0x01, 0x7A, 0x23, 0x34, 0x01,
0x01, 0x72, 0x24, 0x22, 0x01,
0x01, 0x52, 0x20, 0x22, 0x01,
0x01, 0x4A, 0x21, 0x22, 0x01,
0x01, 0x42, 0x22, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x80, 0x00, 0xE2, 0x01,
0x01, 0x98, 0x00, 0xF2, 0x01,
0x01, 0xC2, 0x28, 0x20, 0x01,
0x01, 0xBA, 0x29, 0x22, 0x01,
0x01, 0xAA, 0x28, 0x34, 0x01,
0x01, 0xA2, 0x29, 0x22, 0x01,
0x01, 0x82, 0x25, 0x22, 0x01,
0x01, 0x7A, 0x26, 0x22, 0x01,
0x01, 0x72, 0x27, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0xB0, 0x00, 0xE2, 0x01,
0x01, 0xC8, 0x00, 0xF2, 0x01,
0x01, 0xF2, 0x0E, 0x20, 0x01,
0x01, 0xEA, 0x0F, 0x22, 0x01,
0x01, 0xDA, 0x0E, 0x34, 0x01,
0x01, 0xD2, 0x0F, 0x22, 0x01,
0x01, 0xB2, 0x0B, 0x22, 0x01,
0x01, 0xAA, 0x0C, 0x22, 0x01,
0x01, 0xA2, 0x0D, 0x22, 0x01,
0x02, 0x0A, 0x0F, 0x20, 0x01,
0x02, 0x02, 0x10, 0x22, 0x01,
0x01, 0xF2, 0x0F, 0x34, 0x01,
0x01, 0xEA, 0x10, 0x22, 0x01,
0x01, 0xE2, 0x0C, 0x22, 0x01,
0x01, 0xDA, 0x0D, 0x22, 0x01,
0x01, 0xD2, 0x0E, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0xE0, 0x00, 0xE2, 0x01,
0x01, 0xF8, 0x00, 0xF2, 0x01,
0x02, 0x22, 0x13, 0x20, 0x01,
0x02, 0x1A, 0x14, 0x22, 0x01,
0x02, 0x0A, 0x13, 0x34, 0x01,
0x02, 0x02, 0x14, 0x22, 0x01,
0x01, 0xE2, 0x10, 0x22, 0x01,
0x01, 0xDA, 0x11, 0x22, 0x01,
0x01, 0xD2, 0x12, 0x22, 0x01,
0x01, 0xF8, 0x00, 0xE2, 0x01,
0x02, 0x10, 0x00, 0xF2, 0x01,
0x02, 0x3A, 0x14, 0x20, 0x01,
0x02, 0x32, 0x15, 0x22, 0x01,
0x02, 0x22, 0x14, 0x34, 0x01,
0x02, 0x1A, 0x15, 0x22, 0x01,
0x01, 0xFA, 0x11, 0x22, 0x01,
0x01, 0xF2, 0x12, 0x22, 0x01,
0x01, 0xEA, 0x13, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x10, 0x00, 0xE2, 0x01,
0x02, 0x28, 0x00, 0xF2, 0x01,
0x02, 0x52, 0x18, 0x20, 0x01,
0x02, 0x4A, 0x19, 0x22, 0x01,
0x02, 0x3A, 0x18, 0x34, 0x01,
0x02, 0x32, 0x19, 0x22, 0x01,
0x02, 0x12, 0x15, 0x22, 0x01,
0x02, 0x0A, 0x16, 0x22, 0x01,
0x02, 0x02, 0x17, 0x22, 0x01,
0x02, 0x28, 0x00, 0xE2, 0x01,
0x02, 0x40, 0x00, 0xF2, 0x01,
0x02, 0x6A, 0x19, 0x20, 0x01,
0x02, 0x62, 0x1A, 0x22, 0x01,
0x02, 0x52, 0x19, 0x34, 0x01,
0x02, 0x4A, 0x1A, 0x22, 0x01,
0x02, 0x2A, 0x16, 0x22, 0x01,
0x02, 0x22, 0x17, 0x22, 0x01,
0x02, 0x1A, 0x18, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x40, 0x00, 0xE2, 0x01,
0x02, 0x58, 0x00, 0xF2, 0x01,
0x02, 0x82, 0x1D, 0x20, 0x01,
0x02, 0x7A, 0x1E, 0x22, 0x01,
0x02, 0x6A, 0x1D, 0x34, 0x01,
0x02, 0x62, 0x1E, 0x22, 0x01,
0x02, 0x42, 0x1A, 0x22, 0x01,
0x02, 0x3A, 0x1B, 0x22, 0x01,
0x02, 0x32, 0x1C, 0x22, 0x01,
0x02, 0x58, 0x00, 0xE2, 0x01,
0x02, 0x70, 0x00, 0xF2, 0x01,
0x02, 0x9A, 0x1E, 0x20, 0x01,
0x02, 0x92, 0x1F, 0x22, 0x01,
0x02, 0x82, 0x1E, 0x34, 0x01,
0x02, 0x7A, 0x1F, 0x22, 0x01,
0x02, 0x5A, 0x1B, 0x22, 0x01,
0x02, 0x52, 0x1C, 0x22, 0x01,
0x02, 0x4A, 0x1D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x70, 0x00, 0xE2, 0x01,
0x02, 0x88, 0x00, 0xF2, 0x01,
0x02, 0xB2, 0x22, 0x20, 0x01,
0x02, 0xAA, 0x23, 0x22, 0x01,
0x02, 0x9A, 0x22, 0x34, 0x01,
0x02, 0x92, 0x23, 0x22, 0x01,
0x02, 0x72, 0x1F, 0x22, 0x01,
0x02, 0x6A, 0x20, 0x22, 0x01,
0x02, 0x62, 0x21, 0x22, 0x01,
0x02, 0x88, 0x00, 0xE2, 0x01,
0x02, 0xA0, 0x00, 0xF2, 0x01,
0x02, 0xCA, 0x23, 0x20, 0x01,
0x02, 0xC2, 0x24, 0x22, 0x01,
0x02, 0xB2, 0x23, 0x34, 0x01,
0x02, 0xAA, 0x24, 0x22, 0x01,
0x02, 0x8A, 0x20, 0x22, 0x01,
0x02, 0x82, 0x21, 0x22, 0x01,
0x02, 0x7A, 0x22, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xA0, 0x00, 0xE2, 0x01,
0x02, 0xB8, 0x00, 0xF2, 0x01,
0x01, 0x6A, 0x24, 0x20, 0x01,
0x02, 0xC0, 0x00, 0xE2, 0x01,
0x02, 0xB8, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xF2, 0x01,
0x02, 0xFA, 0x28, 0x20, 0x01,
0x02, 0xF2, 0x29, 0x22, 0x01,
0x02, 0xE2, 0x28, 0x34, 0x01,
0x02, 0xDA, 0x29, 0x22, 0x01,
0x02, 0xBA, 0x25, 0x22, 0x01,
0x02, 0xB2, 0x26, 0x22, 0x01,
0x02, 0xAA, 0x27, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xA2, 0x25, 0x20, 0x01,
0x02, 0xC8, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xC1, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x28, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x20, 0x00, 0xF2, 0x01,
0x03, 0x11, 0x08, 0x20, 0x01,
0x03, 0x12, 0x32, 0x22, 0x41,
0x03, 0x22, 0x32, 0x22, 0x01,
0x03, 0x01, 0x08, 0x34, 0x01,
0x03, 0x02, 0x32, 0x22, 0x41,
0x03, 0x2A, 0x32, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x08, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xF2, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x2E, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x26, 0x20, 0x01,
0xFF, 0xF2, 0x27, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x03, 0x32, 0x28, 0x20, 0x01,
0xFF, 0xF2, 0x29, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x2F, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x2A, 0x20, 0x01,
0xFF, 0xF2, 0x2B, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x30, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x2C, 0x20, 0x01,
0xFF, 0xF2, 0x2D, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x31, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x40, 0x01,
0x02, 0xF1, 0x08, 0x20, 0x09,
0x02, 0xFA, 0x33, 0x20, 0x01,
0x02, 0xF2, 0x33, 0x22, 0x41,
0x02, 0xF1, 0x08, 0x22, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x20, 0x01,
0x02, 0xE8, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x34, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x02, 0xE0, 0x00, 0xE2, 0x01,
0x02, 0xF8, 0x00, 0xC0, 0x01,
0x02, 0xC7, 0xFF, 0x20, 0x01,
0x02, 0xD8, 0x00, 0xE2, 0x01,
0x02, 0xC9, 0x08, 0x20, 0x01,
0x03, 0x00, 0x00, 0xF2, 0x01,
0x01, 0xB2, 0x2A, 0x20, 0x01,
0x03, 0x08, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x28, 0x00, 0xF0, 0x01,
0x02, 0xEA, 0x2B, 0x20, 0x01,
0x03, 0x10, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x09, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x70, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x20, 0x00, 0xF2, 0x01,
0x03, 0x71, 0x08, 0x20, 0x01,
0x03, 0x72, 0x41, 0x22, 0x41,
0x03, 0x22, 0x41, 0x22, 0x01,
0x03, 0x61, 0x08, 0x34, 0x01,
0x03, 0x62, 0x41, 0x22, 0x41,
0x03, 0x2A, 0x41, 0x22, 0x01,
0x03, 0x68, 0x00, 0xF2, 0x01,
0x03, 0x59, 0x08, 0x20, 0x01,
0x03, 0x5A, 0x38, 0x22, 0x41,
0x03, 0x6A, 0x38, 0x22, 0x01,
0x03, 0x49, 0x08, 0x34, 0x01,
0x03, 0x4A, 0x38, 0x22, 0x41,
0x03, 0x72, 0x38, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x68, 0x00, 0xE2, 0x01,
0x03, 0x78, 0x00, 0xF2, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x3D, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x35, 0x20, 0x01,
0xFF, 0xF2, 0x36, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x03, 0x32, 0x37, 0x20, 0x01,
0xFF, 0xF2, 0x38, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x3E, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x39, 0x20, 0x01,
0xFF, 0xF2, 0x3A, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x3F, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x3B, 0x20, 0x01,
0xFF, 0xF2, 0x3C, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0x03, 0x50, 0x00, 0xE2, 0x01,
0x03, 0x60, 0x00, 0xF2, 0x01,
0x03, 0x78, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x34, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x2C, 0x20, 0x01,
0xFF, 0xF2, 0x2D, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x7A, 0x2E, 0x20, 0x01,
0xFF, 0xF2, 0x2F, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x35, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x30, 0x20, 0x01,
0xFF, 0xF2, 0x31, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x36, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x32, 0x20, 0x01,
0xFF, 0xF2, 0x33, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x40, 0xA0, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x37, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x01,
0x03, 0x40, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x40, 0x01,
0x03, 0x41, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x23,
0x03, 0x40, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x40, 0x01,
0x03, 0x51, 0x08, 0x20, 0x09,
0x03, 0x5A, 0x42, 0x20, 0x01,
0x03, 0x52, 0x42, 0x22, 0x41,
0x03, 0x51, 0x08, 0x22, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x23,
0x03, 0x41, 0x08, 0x40, 0x01,
0x03, 0x39, 0x08, 0x20, 0x09,
0x03, 0x42, 0x39, 0x20, 0x01,
0x03, 0x3A, 0x39, 0x22, 0x41,
0x03, 0x39, 0x08, 0x22, 0x01,
0x03, 0x40, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x20, 0x01,
0x03, 0x48, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x43, 0x22, 0x49,
0x03, 0x41, 0x08, 0x20, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x3A, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x03, 0x40, 0x00, 0xE2, 0x01,
0x03, 0x58, 0x00, 0xC0, 0x01,
0x02, 0xCF, 0xFF, 0x20, 0x01,
0x03, 0x38, 0x00, 0xE2, 0x01,
0x03, 0x39, 0x08, 0x20, 0x01,
0x03, 0x28, 0x00, 0xE2, 0x01,
0x03, 0x40, 0x00, 0xC0, 0x01,
0x03, 0x0F, 0xFF, 0x20, 0x01,
0x03, 0x20, 0x00, 0xE2, 0x01,
0x03, 0x11, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x70, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x68, 0x00, 0xF2, 0x01,
0x03, 0xB9, 0x08, 0x20, 0x01,
0x03, 0xBA, 0x47, 0x22, 0x41,
0x03, 0x6A, 0x47, 0x22, 0x01,
0x03, 0xA9, 0x08, 0x34, 0x01,
0x03, 0xAA, 0x47, 0x22, 0x41,
0x03, 0x72, 0x47, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xB0, 0x00, 0xE2, 0x01,
0x03, 0xC0, 0x00, 0xF2, 0x01,
0x03, 0x78, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x43, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x3B, 0x20, 0x01,
0xFF, 0xF2, 0x3C, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x7A, 0x3D, 0x20, 0x01,
0xFF, 0xF2, 0x3E, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x44, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x3F, 0x20, 0x01,
0xFF, 0xF2, 0x40, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x45, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x41, 0x20, 0x01,
0xFF, 0xF2, 0x42, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x46, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x40, 0x01,
0x03, 0x99, 0x08, 0x20, 0x09,
0x03, 0xA2, 0x48, 0x20, 0x01,
0x03, 0x9A, 0x48, 0x22, 0x41,
0x03, 0x99, 0x08, 0x22, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x20, 0x01,
0x03, 0x90, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x49, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x03, 0x88, 0x00, 0xE2, 0x01,
0x03, 0xA0, 0x00, 0xC0, 0x01,
0x03, 0x17, 0xFF, 0x20, 0x01,
0x03, 0x80, 0x00, 0xE2, 0x01,
0x03, 0x81, 0x08, 0x20, 0x01,
0xFF, 0x30, 0x00, 0x02, 0x01,
0x02, 0xD9, 0x08, 0x20, 0x01,
0x03, 0x21, 0x08, 0x20, 0x01,
0xFF, 0x28, 0x00, 0x02, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
@@ -1031,34 +1053,18 @@ ADI_REG_TYPE Program_Data_IC_1[PROGRAM_SIZE_IC_1] = {
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
};
/* DSP Parameter (Coefficient) Data */
#define PARAM_SIZE_IC_1 4096
#define PARAM_ADDR_IC_1 0
ADI_REG_TYPE Param_Data_IC_1[PARAM_SIZE_IC_1] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x6C, 0xAC,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
@@ -2077,12 +2083,6 @@ ADI_REG_TYPE Param_Data_IC_1[PARAM_SIZE_IC_1] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
@@ -2094,7 +2094,7 @@ ADI_REG_TYPE R0_COREREGISTER_IC_1_Default[REG_COREREGISTER_IC_1_BYTE] = {
/* Register Default - IC 1.HWConFiguration */
#define R3_HWCONFIGURATION_IC_1_SIZE 24
ADI_REG_TYPE R3_HWCONFIGURATION_IC_1_Default[R3_HWCONFIGURATION_IC_1_SIZE] = {
0x00, 0x18, 0x08, 0x08, 0x00, 0x00, 0x44, 0x00, 0x44, 0x44, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
0x00, 0x18, 0x0B, 0x08, 0x00, 0x00, 0x44, 0x00, 0x44, 0x44, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
/* Register Default - IC 1.CoreRegister */
@@ -1,7 +1,7 @@
/*
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadio_IC_1_PARAM.h
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadioModificato\DigiRadio_IC_1_PARAM.h
*
* Created: Monday, July 6, 2026 2:41:28 PM
* Created: Friday, August 7, 2026 3:19:11 AM
* Description: DigiRadio:IC 1 parameter RAM definitions.
*
* This software is distributed in the hope that it will be useful,
@@ -20,14 +20,30 @@
#define __DIGIRADIO_IC_1_PARAM_H__
/* Module Beep1 - Beep Sources*/
#define COUNT_BEEP1 3
#define DEVICE_BEEP1 "IC1"
#define ADDR_BEEP1_ENABLE 0
#define FIXPT_BEEP1_ENABLE 0x00000000
#define VALUE_BEEP1_ENABLE SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_BEEP1_ENABLE SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_BEEP1_KICK 1
#define FIXPT_BEEP1_KICK 0x00000000
#define VALUE_BEEP1_KICK SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_BEEP1_KICK SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_BEEP1_BEEP_FREQ 2
#define FIXPT_BEEP1_BEEP_FREQ 0x00006CAB
#define VALUE_BEEP1_BEEP_FREQ SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.00331636677280911)
#define TYPE_BEEP1_BEEP_FREQ SIGMASTUDIOTYPE_FIXPOINT
/* Module Si4674 - Multiple Volume Control*/
#define COUNT_SI4674 2
#define DEVICE_SI4674 "IC1"
#define ADDR_SI4674 0
#define ADDR_SI4674 3
#define FIXPT_SI4674 0x00800000
#define VALUE_SI4674 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SI4674 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_SI4674_1 1
#define ADDR_SI4674_1 4
#define FIXPT_SI4674_1 0x00800000
#define VALUE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT
@@ -35,267 +51,287 @@
/* Module ESP32 - Multiple Volume Control*/
#define COUNT_ESP32 2
#define DEVICE_ESP32 "IC1"
#define ADDR_ESP32 2
#define ADDR_ESP32 5
#define FIXPT_ESP32 0x00800000
#define VALUE_ESP32 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_ESP32 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_ESP32_1 3
#define ADDR_ESP32_1 6
#define FIXPT_ESP32_1 0x00800000
#define VALUE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT
/* Module Single 1 - Single Volume*/
#define COUNT_SINGLE1 1
#define DEVICE_SINGLE1 "IC1"
#define ADDR_SINGLE1 7
#define FIXPT_SINGLE1 0x00800000
#define VALUE_SINGLE1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SINGLE1 SIGMASTUDIOTYPE_FIXPOINT
/* Module S Splitter1 - Single Control Splitter*/
#define COUNT_SSPLITTER1 1
#define DEVICE_SSPLITTER1 "IC1"
#define ADDR_SSPLITTER1 8
#define FIXPT_SSPLITTER1 0x00800000
#define VALUE_SSPLITTER1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SSPLITTER1 SIGMASTUDIOTYPE_FIXPOINT
/* Module St Mixer1 - Stereo Mixer*/
#define COUNT_STMIXER1 2
#define COUNT_STMIXER1 3
#define DEVICE_STMIXER1 "IC1"
#define ADDR_STMIXER1_ST0_VOLUME 4
#define ADDR_STMIXER1_ST0_VOLUME 9
#define FIXPT_STMIXER1_ST0_VOLUME 0x00800000
#define VALUE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_STMIXER1_ST1_VOLUME 5
#define ADDR_STMIXER1_ST1_VOLUME 10
#define FIXPT_STMIXER1_ST1_VOLUME 0x00800000
#define VALUE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_STMIXER1_ST2_VOLUME 11
#define FIXPT_STMIXER1_ST2_VOLUME 0x00800000
#define VALUE_STMIXER1_ST2_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_STMIXER1_ST2_VOLUME SIGMASTUDIOTYPE_FIXPOINT
/* Module Param EQ1 - Parametric EQ*/
#define COUNT_PARAMEQ1 60
#define DEVICE_PARAMEQ1 "IC1"
#define ADDR_PARAMEQ1_ST0_B0 6
#define ADDR_PARAMEQ1_ST0_B0 12
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B0 6
#define ADDR_PARAMEQ1_ST0_B0 12
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B1 7
#define ADDR_PARAMEQ1_ST0_B1 13
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B1 7
#define ADDR_PARAMEQ1_ST0_B1 13
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B2 8
#define ADDR_PARAMEQ1_ST0_B2 14
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B2 8
#define ADDR_PARAMEQ1_ST0_B2 14
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A0 9
#define ADDR_PARAMEQ1_ST0_A0 15
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A0 9
#define ADDR_PARAMEQ1_ST0_A0 15
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A1 10
#define ADDR_PARAMEQ1_ST0_A1 16
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A1 10
#define ADDR_PARAMEQ1_ST0_A1 16
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B0 11
#define ADDR_PARAMEQ1_ST1_B0 17
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B0 11
#define ADDR_PARAMEQ1_ST1_B0 17
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B1 12
#define ADDR_PARAMEQ1_ST1_B1 18
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B1 12
#define ADDR_PARAMEQ1_ST1_B1 18
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B2 13
#define ADDR_PARAMEQ1_ST1_B2 19
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B2 13
#define ADDR_PARAMEQ1_ST1_B2 19
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A0 14
#define ADDR_PARAMEQ1_ST1_A0 20
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A0 14
#define ADDR_PARAMEQ1_ST1_A0 20
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A1 15
#define ADDR_PARAMEQ1_ST1_A1 21
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A1 15
#define ADDR_PARAMEQ1_ST1_A1 21
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B0 16
#define ADDR_PARAMEQ1_ST2_B0 22
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B0 16
#define ADDR_PARAMEQ1_ST2_B0 22
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B1 17
#define ADDR_PARAMEQ1_ST2_B1 23
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B1 17
#define ADDR_PARAMEQ1_ST2_B1 23
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B2 18
#define ADDR_PARAMEQ1_ST2_B2 24
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B2 18
#define ADDR_PARAMEQ1_ST2_B2 24
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A0 19
#define ADDR_PARAMEQ1_ST2_A0 25
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A0 19
#define ADDR_PARAMEQ1_ST2_A0 25
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A1 20
#define ADDR_PARAMEQ1_ST2_A1 26
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A1 20
#define ADDR_PARAMEQ1_ST2_A1 26
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B0 21
#define ADDR_PARAMEQ1_ST3_B0 27
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B0 21
#define ADDR_PARAMEQ1_ST3_B0 27
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B1 22
#define ADDR_PARAMEQ1_ST3_B1 28
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B1 22
#define ADDR_PARAMEQ1_ST3_B1 28
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B2 23
#define ADDR_PARAMEQ1_ST3_B2 29
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B2 23
#define ADDR_PARAMEQ1_ST3_B2 29
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A0 24
#define ADDR_PARAMEQ1_ST3_A0 30
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A0 24
#define ADDR_PARAMEQ1_ST3_A0 30
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A1 25
#define ADDR_PARAMEQ1_ST3_A1 31
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A1 25
#define ADDR_PARAMEQ1_ST3_A1 31
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B0 26
#define ADDR_PARAMEQ1_ST4_B0 32
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B0 26
#define ADDR_PARAMEQ1_ST4_B0 32
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B1 27
#define ADDR_PARAMEQ1_ST4_B1 33
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B1 27
#define ADDR_PARAMEQ1_ST4_B1 33
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B2 28
#define ADDR_PARAMEQ1_ST4_B2 34
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B2 28
#define ADDR_PARAMEQ1_ST4_B2 34
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A0 29
#define ADDR_PARAMEQ1_ST4_A0 35
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A0 29
#define ADDR_PARAMEQ1_ST4_A0 35
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A1 30
#define ADDR_PARAMEQ1_ST4_A1 36
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A1 30
#define ADDR_PARAMEQ1_ST4_A1 36
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B0 31
#define ADDR_PARAMEQ1_ST5_B0 37
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B0 31
#define ADDR_PARAMEQ1_ST5_B0 37
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B1 32
#define ADDR_PARAMEQ1_ST5_B1 38
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B1 32
#define ADDR_PARAMEQ1_ST5_B1 38
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B2 33
#define ADDR_PARAMEQ1_ST5_B2 39
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B2 33
#define ADDR_PARAMEQ1_ST5_B2 39
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A0 34
#define ADDR_PARAMEQ1_ST5_A0 40
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A0 34
#define ADDR_PARAMEQ1_ST5_A0 40
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A1 35
#define ADDR_PARAMEQ1_ST5_A1 41
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A1 35
#define ADDR_PARAMEQ1_ST5_A1 41
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
@@ -303,11 +339,11 @@
/* Module Multiple 1 - Multiple Volume Control*/
#define COUNT_MULTIPLE1 2
#define DEVICE_MULTIPLE1 "IC1"
#define ADDR_MULTIPLE1 36
#define ADDR_MULTIPLE1 42
#define FIXPT_MULTIPLE1 0x00800000
#define VALUE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_MULTIPLE1_1 37
#define ADDR_MULTIPLE1_1 43
#define FIXPT_MULTIPLE1_1 0x00800000
#define VALUE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT
@@ -315,63 +351,63 @@
/* Module Limiter 1 - Limiter*/
#define COUNT_LIMITER1 15
#define DEVICE_LIMITER1 "IC1"
#define ADDR_LIMITER1_S1 38
#define ADDR_LIMITER1_S1 44
#define FIXPT_LIMITER1_S1 0xFFFE2BE3
#define VALUE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
#define TYPE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT1 39
#define ADDR_LIMITER1_INT1 45
#define FIXPT_LIMITER1_INT1 0x0022BE2B
#define VALUE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
#define TYPE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S2 40
#define ADDR_LIMITER1_S2 46
#define FIXPT_LIMITER1_S2 0xFFEAAAAB
#define VALUE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
#define TYPE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT2 41
#define ADDR_LIMITER1_INT2 47
#define FIXPT_LIMITER1_INT2 0x006AAAAA
#define VALUE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
#define TYPE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S3 42
#define ADDR_LIMITER1_S3 48
#define FIXPT_LIMITER1_S3 0xFF9A69A7
#define VALUE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
#define TYPE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT3 43
#define ADDR_LIMITER1_INT3 49
#define FIXPT_LIMITER1_INT3 0x00FDF7DF
#define VALUE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
#define TYPE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S4 44
#define ADDR_LIMITER1_S4 50
#define FIXPT_LIMITER1_S4 0xF83C6C20
#define VALUE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
#define TYPE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT4 45
#define ADDR_LIMITER1_INT4 51
#define FIXPT_LIMITER1_INT4 0x03F5AA22
#define VALUE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
#define TYPE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C1 46
#define ADDR_LIMITER1_C1 52
#define FIXPT_LIMITER1_C1 0x01800000
#define VALUE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
#define TYPE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C2 47
#define ADDR_LIMITER1_C2 53
#define FIXPT_LIMITER1_C2 0x01000000
#define VALUE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
#define TYPE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C3 48
#define ADDR_LIMITER1_C3 54
#define FIXPT_LIMITER1_C3 0x00333333
#define VALUE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
#define TYPE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_THRESHOLD 49
#define ADDR_LIMITER1_THRESHOLD 55
#define FIXPT_LIMITER1_THRESHOLD 0x00800000
#define VALUE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_RMS 50
#define ADDR_LIMITER1_RMS 56
#define FIXPT_LIMITER1_RMS 0x000007DC
#define VALUE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
#define TYPE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_DECAY 51
#define ADDR_LIMITER1_DECAY 57
#define FIXPT_LIMITER1_DECAY 0x00000800
#define VALUE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
#define TYPE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_DECAYCOMPLEMENT 52
#define ADDR_LIMITER1_DECAYCOMPLEMENT 58
#define FIXPT_LIMITER1_DECAYCOMPLEMENT 0x007FF000
#define VALUE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
#define TYPE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
@@ -379,63 +415,63 @@
/* Module Limiter 2 - Limiter*/
#define COUNT_LIMITER2 15
#define DEVICE_LIMITER2 "IC1"
#define ADDR_LIMITER2_S1 53
#define ADDR_LIMITER2_S1 59
#define FIXPT_LIMITER2_S1 0xFFFE2BE3
#define VALUE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
#define TYPE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT1 54
#define ADDR_LIMITER2_INT1 60
#define FIXPT_LIMITER2_INT1 0x0022BE2B
#define VALUE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
#define TYPE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S2 55
#define ADDR_LIMITER2_S2 61
#define FIXPT_LIMITER2_S2 0xFFEAAAAB
#define VALUE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
#define TYPE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT2 56
#define ADDR_LIMITER2_INT2 62
#define FIXPT_LIMITER2_INT2 0x006AAAAA
#define VALUE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
#define TYPE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S3 57
#define ADDR_LIMITER2_S3 63
#define FIXPT_LIMITER2_S3 0xFF9A69A7
#define VALUE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
#define TYPE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT3 58
#define ADDR_LIMITER2_INT3 64
#define FIXPT_LIMITER2_INT3 0x00FDF7DF
#define VALUE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
#define TYPE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S4 59
#define ADDR_LIMITER2_S4 65
#define FIXPT_LIMITER2_S4 0xF83C6C20
#define VALUE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
#define TYPE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT4 60
#define ADDR_LIMITER2_INT4 66
#define FIXPT_LIMITER2_INT4 0x03F5AA22
#define VALUE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
#define TYPE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C1 61
#define ADDR_LIMITER2_C1 67
#define FIXPT_LIMITER2_C1 0x01800000
#define VALUE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
#define TYPE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C2 62
#define ADDR_LIMITER2_C2 68
#define FIXPT_LIMITER2_C2 0x01000000
#define VALUE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
#define TYPE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C3 63
#define ADDR_LIMITER2_C3 69
#define FIXPT_LIMITER2_C3 0x00333333
#define VALUE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
#define TYPE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_THRESHOLD 64
#define ADDR_LIMITER2_THRESHOLD 70
#define FIXPT_LIMITER2_THRESHOLD 0x00800000
#define VALUE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_RMS 65
#define ADDR_LIMITER2_RMS 71
#define FIXPT_LIMITER2_RMS 0x000007DC
#define VALUE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
#define TYPE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_DECAY 66
#define ADDR_LIMITER2_DECAY 72
#define FIXPT_LIMITER2_DECAY 0x00000800
#define VALUE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
#define TYPE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_DECAYCOMPLEMENT 67
#define ADDR_LIMITER2_DECAYCOMPLEMENT 73
#define FIXPT_LIMITER2_DECAYCOMPLEMENT 0x007FF000
#define VALUE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
#define TYPE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
@@ -1,7 +1,7 @@
/*
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadio_IC_1_REG.h
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadioModificato\DigiRadio_IC_1_REG.h
*
* Created: Monday, July 6, 2026 2:41:28 PM
* Created: Friday, August 7, 2026 3:19:11 AM
* Description: DigiRadio:IC 1 control register definitions.
*
* This software is distributed in the hope that it will be useful,
@@ -93,7 +93,7 @@
/* RAMRegister - Registers (IC 1) */
#define REG_RAMREGISTER_IC_1_ADDR 0x81D
#define REG_RAMREGISTER_IC_1_BYTE 1
#define REG_RAMREGISTER_IC_1_VALUE 0x8
#define REG_RAMREGISTER_IC_1_VALUE 0xB
/* SerialOutRegister1 - Registers (IC 1) */
#define REG_SERIALOUTREGISTER1_IC_1_ADDR 0x81E
@@ -286,7 +286,7 @@
#define R13_EXTMEM_SPEED_IC_1_SHIFT 12
/* RAMRegister (IC 1) */
#define R14_RAM_MODULO_IC_1 0x8 /* 1000b [3:0] */
#define R14_RAM_MODULO_IC_1 0xB /* 1011b [3:0] */
#define R14_RAM_MODULO_IC_1_MASK 0xF
#define R14_RAM_MODULO_IC_1_SHIFT 0
@@ -23,6 +23,9 @@
<Link pin="O_C33_A0_P5_out" dir="out" link="Link2" />
<Link pin="O_C33_A0_P6_out" dir="out" link="Link3" />
</Algorithm>
<Algorithm name="BeepAlg1" friendlyname="Beep - variable gain " cell="Beep1 " location="{X=30, Y=459} " Growth="0 " GrowthB="0 " FS="48000">
<Link pin="O_C0_A0_P1_out" dir="out" link="Link15" />
</Algorithm>
<Algorithm name="Gain1940AlgNS1" friendlyname="Gain (no slew) " cell="Si4674 " location="{X=168, Y=5} " Growth="0 " GrowthB="0">
<Link pin="I_C63_A0_P1_in" dir="in" link="Link0" />
<Link pin="O_C63_A0_P2_out" dir="out" link="Link4" />
@@ -39,41 +42,52 @@
<Link pin="I_C75_A1_P1_in" dir="in" link="Link3" />
<Link pin="O_C75_A1_P2_out" dir="out" link="Link7" />
</Algorithm>
<Algorithm name="StereoMixerAlg1" friendlyname="Stereo Mixer " cell="St Mixer1 " location="{X=381, Y=115} " Growth="2 " GrowthB="0">
<Algorithm name="Gain1940AlgNS7" friendlyname="Gain (no slew) " cell="Single 1 " location="{X=188, Y=404} " Growth="0 " GrowthB="0">
<Link pin="I_C4_A0_P1_in" dir="in" link="Link15" />
<Link pin="O_C4_A0_P2_out" dir="out" link="Link16" />
</Algorithm>
<Algorithm name="SingleCtrlSplit1" friendlyname="Splitter Single " cell="S Splitter1 " location="{X=289, Y=440} " Growth="2 " GrowthB="0">
<Link pin="I_C9_A0_P1_in" dir="in" link="Link16" />
<Link pin="O_C9_A0_P2_out" dir="out" link="Link17" />
<Link pin="O_C9_A0_P3_out" dir="out" link="Link18" />
</Algorithm>
<Algorithm name="StereoMixerAlg1" friendlyname="Stereo Mixer " cell="St Mixer1 " location="{X=464, Y=112} " Growth="3 " GrowthB="0">
<Link pin="I_C84_A0_P3_in" dir="in" link="Link4" />
<Link pin="I_C84_A0_P4_in" dir="in" link="Link5" />
<Link pin="I_C84_A0_P5_in" dir="in" link="Link6" />
<Link pin="I_C84_A0_P6_in" dir="in" link="Link7" />
<Link pin="I_C84_A0_P7_in" dir="in" link="Link17" />
<Link pin="I_C84_A0_P8_in" dir="in" link="Link18" />
<Link pin="O_C84_A0_P1_out" dir="out" link="Link8" />
<Link pin="O_C84_A0_P2_out" dir="out" link="Link9" />
</Algorithm>
<Algorithm name="PEQ1Chan1" friendlyname="PEQ1Chan - Double Precision " cell="Param EQ1 " location="{X=502, Y=156} " Growth="2 " GrowthB="0">
<Algorithm name="PEQ1Chan1" friendlyname="PEQ1Chan - Double Precision " cell="Param EQ1 " location="{X=585, Y=153} " Growth="2 " GrowthB="0">
<Link pin="I_C125_A0_P1_in" dir="in" link="Link8" />
<Link pin="I_C125_A0_P3_in" dir="in" link="Link9" />
<Link pin="O_C125_A0_P2_out" dir="out" link="Link10" />
<Link pin="O_C125_A0_P4_out" dir="out" link="Link11" />
</Algorithm>
<Algorithm name="Gain1940AlgNS5" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
<Algorithm name="Gain1940AlgNS5" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=711, Y=85} " Growth="0 " GrowthB="0">
<Link pin="I_C138_A0_P1_in" dir="in" link="Link10" />
<Link pin="O_C138_A0_P2_out" dir="out" link="Link12" />
</Algorithm>
<Algorithm name="Gain1940AlgNS6" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
<Algorithm name="Gain1940AlgNS6" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=711, Y=85} " Growth="0 " GrowthB="0">
<Link pin="I_C138_A1_P1_in" dir="in" link="Link11" />
<Link pin="O_C138_A1_P2_out" dir="out" link="Link13" />
</Algorithm>
<Algorithm name="limiterAlg1" friendlyname="Limiter with Indicator " cell="Limiter 1 " location="{X=761, Y=40} " Growth="0 " GrowthB="0">
<Algorithm name="limiterAlg1" friendlyname="Limiter with Indicator " cell="Limiter 1 " location="{X=844, Y=37} " Growth="0 " GrowthB="0">
<Link pin="I_C169_A0_P1_in" dir="in" link="Link12" />
<Link pin="O_C169_A0_P2_out" dir="out" link="Link15" />
<Link pin="O_C169_A0_P2_out" dir="out" link="Link19" />
</Algorithm>
<Algorithm name="limiterAlg2" friendlyname="Limiter with Indicator " cell="Limiter 2 " location="{X=765, Y=189} " Growth="0 " GrowthB="0">
<Algorithm name="limiterAlg2" friendlyname="Limiter with Indicator " cell="Limiter 2 " location="{X=848, Y=186} " Growth="0 " GrowthB="0">
<Link pin="I_C174_A0_P1_in" dir="in" link="Link13" />
<Link pin="O_C174_A0_P2_out" dir="out" link="Link14" />
</Algorithm>
<Algorithm name="ICSigma100Out2" friendlyname="170x\140x output " cell="Output2 " location="{X=890, Y=227} " Growth="0 " GrowthB="0">
<Algorithm name="ICSigma100Out2" friendlyname="170x\140x output " cell="Output2 " location="{X=1003, Y=202} " Growth="0 " GrowthB="0">
<Link pin="I_C185_A0_P1_in" dir="in" link="Link14" />
</Algorithm>
<Algorithm name="ICSigma100Out1" friendlyname="170x\140x output " cell="Output1 " location="{X=877, Y=57} " Growth="0 " GrowthB="0">
<Link pin="I_C183_A0_P1_in" dir="in" link="Link15" />
<Algorithm name="ICSigma100Out1" friendlyname="170x\140x output " cell="Output1 " location="{X=1009.037, Y=52} " Growth="0 " GrowthB="0">
<Link pin="I_C183_A0_P1_in" dir="in" link="Link19" />
</Algorithm>
</Schematic>
</IC>
@@ -31,6 +31,21 @@ void sigma_studio_bind_i2c(int port, unsigned char addr7);
/** @brief Attach the I2C device handle created by Adau1701Driver. */
void sigma_studio_set_device(void* i2cDevHandle);
/**
* @brief Acquire the process-wide ADAU1701 I2C lock.
*
* Guards a logical multi-transaction operation (e.g. a safeload burst)
* against interleaving from another FreeRTOS task — individual
* i2c_master_transmit calls are already serialised by the ESP-IDF I2C
* driver, but a sequence of several transactions is not atomic without
* this. Callers: Adau1701Driver safeload paths and the SigmaStudio TCP
* bridge (components/net/SigmaStudioTcpServer).
*/
void sigma_studio_lock(void);
/** @brief Release the lock acquired by sigma_studio_lock(). */
void sigma_studio_unlock(void);
/**
* @brief Write a contiguous register/data block to the ADAU1701.
*
@@ -44,6 +59,16 @@ void SIGMA_WRITE_REGISTER_BLOCK(unsigned char devAddress,
unsigned int length,
ADI_REG_TYPE* pData);
/**
* @brief Read a contiguous register/data block from the ADAU1701.
*
* @param reg 16-bit source address in DSP memory map.
* @param data Destination buffer.
* @param length Number of bytes to read.
* @return 0 on success, non-zero on I2C failure.
*/
int sigma_i2c_read(unsigned int reg, unsigned char* data, unsigned int length);
/** Safeload data register base (0x0810..0x0814). */
#define ADAU1701_SAFELOAD_DATA_BASE 0x0810U
/** Safeload address register base (0x0815..0x0819). */
@@ -73,6 +98,22 @@ int sigma_safeload_param(unsigned int paramAddr, int fixpoint);
int sigma_safeload_block(unsigned char count, const unsigned int* paramAddrs,
const int* fixpoints);
/**
* @brief Safeload up to five parameters from raw wire bytes (pass-through).
*
* Used by the SigmaStudio TCP bridge: unlike sigma_safeload_block(), which
* reconstructs the 4-byte payload from a host int, this forwards each
* 4-byte word exactly as received over the network — no endianness
* reinterpretation.
*
* @param count Number of words (1..5).
* @param paramAddrs Parameter RAM addresses.
* @param rawWords count*4 raw bytes, one 4-byte word per address.
* @return 0 on success, non-zero on I2C failure.
*/
int sigma_safeload_raw_block(unsigned char count, const unsigned int* paramAddrs,
const unsigned char* rawWords);
#ifdef __cplusplus
}
#endif
@@ -13,310 +13,332 @@
0x00, 0x20, 0x00, 0xE8, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x28, 0x00, 0xE8, 0x01,
0x00, 0x12, 0x00, 0x20, 0x01,
0x00, 0x30, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x1A, 0x01, 0x20, 0x01,
0x00, 0x02, 0x01, 0xA0, 0x01,
0xFF, 0xE1, 0x07, 0x20, 0x01,
0x00, 0x38, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x22, 0x02, 0x20, 0x01,
0x00, 0x40, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x2A, 0x03, 0x20, 0x01,
0x00, 0x42, 0x02, 0x20, 0x01,
0x00, 0x52, 0x00, 0x22, 0x01,
0x00, 0x58, 0x00, 0xE2, 0x01,
0x00, 0x42, 0x00, 0x20, 0x01,
0x00, 0x59, 0x08, 0x22, 0x41,
0x00, 0x39, 0x08, 0x22, 0x01,
0x00, 0x31, 0x08, 0x22, 0x41,
0x00, 0x48, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x32, 0x04, 0x20, 0x01,
0x00, 0x3A, 0x04, 0x40, 0x01,
0x00, 0x42, 0x05, 0x22, 0x01,
0x00, 0x4A, 0x05, 0x44, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x12, 0x03, 0x20, 0x01,
0x00, 0x60, 0x00, 0xE2, 0x01,
0x01, 0x98, 0x00, 0xE4, 0x01,
0x00, 0x8A, 0x09, 0x20, 0x01,
0x00, 0x82, 0x0A, 0x22, 0x01,
0x00, 0x72, 0x09, 0x34, 0x01,
0x00, 0x6A, 0x0A, 0x22, 0x01,
0x00, 0x62, 0x06, 0x22, 0x01,
0x00, 0x5A, 0x07, 0x22, 0x01,
0x00, 0x52, 0x08, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x1A, 0x04, 0x20, 0x01,
0x00, 0x68, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x22, 0x05, 0x20, 0x01,
0x00, 0x70, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x2A, 0x06, 0x20, 0x01,
0x00, 0x78, 0x00, 0xE2, 0x01,
0x00, 0x90, 0x00, 0xF2, 0x01,
0x00, 0xBA, 0x0E, 0x20, 0x01,
0x00, 0xB2, 0x0F, 0x22, 0x01,
0x00, 0xA2, 0x0E, 0x34, 0x01,
0x00, 0x9A, 0x0F, 0x22, 0x01,
0x00, 0x7A, 0x0B, 0x22, 0x01,
0x00, 0x72, 0x0C, 0x22, 0x01,
0x00, 0x6A, 0x0D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x5A, 0x07, 0x20, 0x01,
0x00, 0x80, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x82, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x88, 0x00, 0xE2, 0x01,
0x00, 0x82, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x90, 0x00, 0xE2, 0x01,
0x00, 0x62, 0x09, 0x20, 0x01,
0x00, 0x6A, 0x09, 0x40, 0x01,
0x00, 0x72, 0x0A, 0x22, 0x01,
0x00, 0x7A, 0x0A, 0x44, 0x01,
0x00, 0x8A, 0x0B, 0x22, 0x01,
0x00, 0x92, 0x0B, 0x44, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0xA8, 0x00, 0xE2, 0x01,
0x00, 0xC0, 0x00, 0xF2, 0x01,
0x00, 0xEA, 0x13, 0x20, 0x01,
0x00, 0xE2, 0x14, 0x22, 0x01,
0x00, 0xD2, 0x13, 0x34, 0x01,
0x00, 0xCA, 0x14, 0x22, 0x01,
0x00, 0xAA, 0x10, 0x22, 0x01,
0x00, 0xA2, 0x11, 0x22, 0x01,
0x00, 0x9A, 0x12, 0x22, 0x01,
0x01, 0xE0, 0x00, 0xE4, 0x01,
0x00, 0xD2, 0x0F, 0x20, 0x01,
0x00, 0xCA, 0x10, 0x22, 0x01,
0x00, 0xBA, 0x0F, 0x34, 0x01,
0x00, 0xB2, 0x10, 0x22, 0x01,
0x00, 0xAA, 0x0C, 0x22, 0x01,
0x00, 0xA2, 0x0D, 0x22, 0x01,
0x00, 0x9A, 0x0E, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0xD8, 0x00, 0xE2, 0x01,
0x00, 0xF0, 0x00, 0xF2, 0x01,
0x01, 0x1A, 0x18, 0x20, 0x01,
0x01, 0x12, 0x19, 0x22, 0x01,
0x01, 0x02, 0x18, 0x34, 0x01,
0x00, 0xFA, 0x19, 0x22, 0x01,
0x00, 0xDA, 0x15, 0x22, 0x01,
0x00, 0xD2, 0x16, 0x22, 0x01,
0x00, 0xCA, 0x17, 0x22, 0x01,
0x00, 0xC0, 0x00, 0xE2, 0x01,
0x00, 0xD8, 0x00, 0xF2, 0x01,
0x01, 0x02, 0x14, 0x20, 0x01,
0x00, 0xFA, 0x15, 0x22, 0x01,
0x00, 0xEA, 0x14, 0x34, 0x01,
0x00, 0xE2, 0x15, 0x22, 0x01,
0x00, 0xC2, 0x11, 0x22, 0x01,
0x00, 0xBA, 0x12, 0x22, 0x01,
0x00, 0xB2, 0x13, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x08, 0x00, 0xE2, 0x01,
0x01, 0x20, 0x00, 0xF2, 0x01,
0x01, 0x4A, 0x1D, 0x20, 0x01,
0x01, 0x42, 0x1E, 0x22, 0x01,
0x01, 0x32, 0x1D, 0x34, 0x01,
0x01, 0x2A, 0x1E, 0x22, 0x01,
0x01, 0x0A, 0x1A, 0x22, 0x01,
0x01, 0x02, 0x1B, 0x22, 0x01,
0x00, 0xFA, 0x1C, 0x22, 0x01,
0x00, 0xF0, 0x00, 0xE2, 0x01,
0x01, 0x08, 0x00, 0xF2, 0x01,
0x01, 0x32, 0x19, 0x20, 0x01,
0x01, 0x2A, 0x1A, 0x22, 0x01,
0x01, 0x1A, 0x19, 0x34, 0x01,
0x01, 0x12, 0x1A, 0x22, 0x01,
0x00, 0xF2, 0x16, 0x22, 0x01,
0x00, 0xEA, 0x17, 0x22, 0x01,
0x00, 0xE2, 0x18, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x38, 0x00, 0xE2, 0x01,
0x01, 0x50, 0x00, 0xF2, 0x01,
0x01, 0x7A, 0x22, 0x20, 0x01,
0x01, 0x72, 0x23, 0x22, 0x01,
0x01, 0x62, 0x22, 0x34, 0x01,
0x01, 0x5A, 0x23, 0x22, 0x01,
0x01, 0x3A, 0x1F, 0x22, 0x01,
0x01, 0x32, 0x20, 0x22, 0x01,
0x01, 0x2A, 0x21, 0x22, 0x01,
0x01, 0x20, 0x00, 0xE2, 0x01,
0x01, 0x38, 0x00, 0xF2, 0x01,
0x01, 0x62, 0x1E, 0x20, 0x01,
0x01, 0x5A, 0x1F, 0x22, 0x01,
0x01, 0x4A, 0x1E, 0x34, 0x01,
0x01, 0x42, 0x1F, 0x22, 0x01,
0x01, 0x22, 0x1B, 0x22, 0x01,
0x01, 0x1A, 0x1C, 0x22, 0x01,
0x01, 0x12, 0x1D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x68, 0x00, 0xE2, 0x01,
0x01, 0x80, 0x00, 0xF2, 0x01,
0x01, 0xC2, 0x09, 0x20, 0x01,
0x01, 0xBA, 0x0A, 0x22, 0x01,
0x01, 0xAA, 0x09, 0x34, 0x01,
0x01, 0xA2, 0x0A, 0x22, 0x01,
0x01, 0x9A, 0x06, 0x22, 0x01,
0x01, 0x92, 0x07, 0x22, 0x01,
0x01, 0x8A, 0x08, 0x22, 0x01,
0x01, 0x50, 0x00, 0xE2, 0x01,
0x01, 0x68, 0x00, 0xF2, 0x01,
0x01, 0x92, 0x23, 0x20, 0x01,
0x01, 0x8A, 0x24, 0x22, 0x01,
0x01, 0x7A, 0x23, 0x34, 0x01,
0x01, 0x72, 0x24, 0x22, 0x01,
0x01, 0x52, 0x20, 0x22, 0x01,
0x01, 0x4A, 0x21, 0x22, 0x01,
0x01, 0x42, 0x22, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0x80, 0x00, 0xE2, 0x01,
0x01, 0x98, 0x00, 0xF2, 0x01,
0x01, 0xC2, 0x28, 0x20, 0x01,
0x01, 0xBA, 0x29, 0x22, 0x01,
0x01, 0xAA, 0x28, 0x34, 0x01,
0x01, 0xA2, 0x29, 0x22, 0x01,
0x01, 0x82, 0x25, 0x22, 0x01,
0x01, 0x7A, 0x26, 0x22, 0x01,
0x01, 0x72, 0x27, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0xB0, 0x00, 0xE2, 0x01,
0x01, 0xC8, 0x00, 0xF2, 0x01,
0x01, 0xF2, 0x0E, 0x20, 0x01,
0x01, 0xEA, 0x0F, 0x22, 0x01,
0x01, 0xDA, 0x0E, 0x34, 0x01,
0x01, 0xD2, 0x0F, 0x22, 0x01,
0x01, 0xB2, 0x0B, 0x22, 0x01,
0x01, 0xAA, 0x0C, 0x22, 0x01,
0x01, 0xA2, 0x0D, 0x22, 0x01,
0x02, 0x0A, 0x0F, 0x20, 0x01,
0x02, 0x02, 0x10, 0x22, 0x01,
0x01, 0xF2, 0x0F, 0x34, 0x01,
0x01, 0xEA, 0x10, 0x22, 0x01,
0x01, 0xE2, 0x0C, 0x22, 0x01,
0x01, 0xDA, 0x0D, 0x22, 0x01,
0x01, 0xD2, 0x0E, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x01, 0xE0, 0x00, 0xE2, 0x01,
0x01, 0xF8, 0x00, 0xF2, 0x01,
0x02, 0x22, 0x13, 0x20, 0x01,
0x02, 0x1A, 0x14, 0x22, 0x01,
0x02, 0x0A, 0x13, 0x34, 0x01,
0x02, 0x02, 0x14, 0x22, 0x01,
0x01, 0xE2, 0x10, 0x22, 0x01,
0x01, 0xDA, 0x11, 0x22, 0x01,
0x01, 0xD2, 0x12, 0x22, 0x01,
0x01, 0xF8, 0x00, 0xE2, 0x01,
0x02, 0x10, 0x00, 0xF2, 0x01,
0x02, 0x3A, 0x14, 0x20, 0x01,
0x02, 0x32, 0x15, 0x22, 0x01,
0x02, 0x22, 0x14, 0x34, 0x01,
0x02, 0x1A, 0x15, 0x22, 0x01,
0x01, 0xFA, 0x11, 0x22, 0x01,
0x01, 0xF2, 0x12, 0x22, 0x01,
0x01, 0xEA, 0x13, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x10, 0x00, 0xE2, 0x01,
0x02, 0x28, 0x00, 0xF2, 0x01,
0x02, 0x52, 0x18, 0x20, 0x01,
0x02, 0x4A, 0x19, 0x22, 0x01,
0x02, 0x3A, 0x18, 0x34, 0x01,
0x02, 0x32, 0x19, 0x22, 0x01,
0x02, 0x12, 0x15, 0x22, 0x01,
0x02, 0x0A, 0x16, 0x22, 0x01,
0x02, 0x02, 0x17, 0x22, 0x01,
0x02, 0x28, 0x00, 0xE2, 0x01,
0x02, 0x40, 0x00, 0xF2, 0x01,
0x02, 0x6A, 0x19, 0x20, 0x01,
0x02, 0x62, 0x1A, 0x22, 0x01,
0x02, 0x52, 0x19, 0x34, 0x01,
0x02, 0x4A, 0x1A, 0x22, 0x01,
0x02, 0x2A, 0x16, 0x22, 0x01,
0x02, 0x22, 0x17, 0x22, 0x01,
0x02, 0x1A, 0x18, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x40, 0x00, 0xE2, 0x01,
0x02, 0x58, 0x00, 0xF2, 0x01,
0x02, 0x82, 0x1D, 0x20, 0x01,
0x02, 0x7A, 0x1E, 0x22, 0x01,
0x02, 0x6A, 0x1D, 0x34, 0x01,
0x02, 0x62, 0x1E, 0x22, 0x01,
0x02, 0x42, 0x1A, 0x22, 0x01,
0x02, 0x3A, 0x1B, 0x22, 0x01,
0x02, 0x32, 0x1C, 0x22, 0x01,
0x02, 0x58, 0x00, 0xE2, 0x01,
0x02, 0x70, 0x00, 0xF2, 0x01,
0x02, 0x9A, 0x1E, 0x20, 0x01,
0x02, 0x92, 0x1F, 0x22, 0x01,
0x02, 0x82, 0x1E, 0x34, 0x01,
0x02, 0x7A, 0x1F, 0x22, 0x01,
0x02, 0x5A, 0x1B, 0x22, 0x01,
0x02, 0x52, 0x1C, 0x22, 0x01,
0x02, 0x4A, 0x1D, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0x70, 0x00, 0xE2, 0x01,
0x02, 0x88, 0x00, 0xF2, 0x01,
0x02, 0xB2, 0x22, 0x20, 0x01,
0x02, 0xAA, 0x23, 0x22, 0x01,
0x02, 0x9A, 0x22, 0x34, 0x01,
0x02, 0x92, 0x23, 0x22, 0x01,
0x02, 0x72, 0x1F, 0x22, 0x01,
0x02, 0x6A, 0x20, 0x22, 0x01,
0x02, 0x62, 0x21, 0x22, 0x01,
0x02, 0x88, 0x00, 0xE2, 0x01,
0x02, 0xA0, 0x00, 0xF2, 0x01,
0x02, 0xCA, 0x23, 0x20, 0x01,
0x02, 0xC2, 0x24, 0x22, 0x01,
0x02, 0xB2, 0x23, 0x34, 0x01,
0x02, 0xAA, 0x24, 0x22, 0x01,
0x02, 0x8A, 0x20, 0x22, 0x01,
0x02, 0x82, 0x21, 0x22, 0x01,
0x02, 0x7A, 0x22, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xA0, 0x00, 0xE2, 0x01,
0x02, 0xB8, 0x00, 0xF2, 0x01,
0x01, 0x6A, 0x24, 0x20, 0x01,
0x02, 0xC0, 0x00, 0xE2, 0x01,
0x02, 0xB8, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xF2, 0x01,
0x02, 0xFA, 0x28, 0x20, 0x01,
0x02, 0xF2, 0x29, 0x22, 0x01,
0x02, 0xE2, 0x28, 0x34, 0x01,
0x02, 0xDA, 0x29, 0x22, 0x01,
0x02, 0xBA, 0x25, 0x22, 0x01,
0x02, 0xB2, 0x26, 0x22, 0x01,
0x02, 0xAA, 0x27, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xA2, 0x25, 0x20, 0x01,
0x02, 0xC8, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xC1, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x28, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x20, 0x00, 0xF2, 0x01,
0x03, 0x11, 0x08, 0x20, 0x01,
0x03, 0x12, 0x32, 0x22, 0x41,
0x03, 0x22, 0x32, 0x22, 0x01,
0x03, 0x01, 0x08, 0x34, 0x01,
0x03, 0x02, 0x32, 0x22, 0x41,
0x03, 0x2A, 0x32, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x08, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xF2, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x2E, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x26, 0x20, 0x01,
0xFF, 0xF2, 0x27, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x03, 0x32, 0x28, 0x20, 0x01,
0xFF, 0xF2, 0x29, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x2F, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x2A, 0x20, 0x01,
0xFF, 0xF2, 0x2B, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x30, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x2C, 0x20, 0x01,
0xFF, 0xF2, 0x2D, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x31, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x40, 0x01,
0x02, 0xF1, 0x08, 0x20, 0x09,
0x02, 0xFA, 0x33, 0x20, 0x01,
0x02, 0xF2, 0x33, 0x22, 0x41,
0x02, 0xF1, 0x08, 0x22, 0x01,
0x02, 0xF8, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xF9, 0x08, 0x20, 0x01,
0x02, 0xE8, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x34, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x02, 0xE0, 0x00, 0xE2, 0x01,
0x02, 0xF8, 0x00, 0xC0, 0x01,
0x02, 0xC7, 0xFF, 0x20, 0x01,
0x02, 0xD8, 0x00, 0xE2, 0x01,
0x02, 0xC9, 0x08, 0x20, 0x01,
0x03, 0x00, 0x00, 0xF2, 0x01,
0x01, 0xB2, 0x2A, 0x20, 0x01,
0x03, 0x08, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x28, 0x00, 0xF0, 0x01,
0x02, 0xEA, 0x2B, 0x20, 0x01,
0x03, 0x10, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x09, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x70, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x20, 0x00, 0xF2, 0x01,
0x03, 0x71, 0x08, 0x20, 0x01,
0x03, 0x72, 0x41, 0x22, 0x41,
0x03, 0x22, 0x41, 0x22, 0x01,
0x03, 0x61, 0x08, 0x34, 0x01,
0x03, 0x62, 0x41, 0x22, 0x41,
0x03, 0x2A, 0x41, 0x22, 0x01,
0x03, 0x68, 0x00, 0xF2, 0x01,
0x03, 0x59, 0x08, 0x20, 0x01,
0x03, 0x5A, 0x38, 0x22, 0x41,
0x03, 0x6A, 0x38, 0x22, 0x01,
0x03, 0x49, 0x08, 0x34, 0x01,
0x03, 0x4A, 0x38, 0x22, 0x41,
0x03, 0x72, 0x38, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x68, 0x00, 0xE2, 0x01,
0x03, 0x78, 0x00, 0xF2, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x3D, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x35, 0x20, 0x01,
0xFF, 0xF2, 0x36, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x03, 0x32, 0x37, 0x20, 0x01,
0xFF, 0xF2, 0x38, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x3E, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x39, 0x20, 0x01,
0xFF, 0xF2, 0x3A, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x3F, 0x40, 0x01,
0x03, 0x31, 0x08, 0x20, 0x09,
0x03, 0x32, 0x3B, 0x20, 0x01,
0xFF, 0xF2, 0x3C, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x23,
0x03, 0x50, 0x00, 0xE2, 0x01,
0x03, 0x60, 0x00, 0xF2, 0x01,
0x03, 0x78, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x34, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x2C, 0x20, 0x01,
0xFF, 0xF2, 0x2D, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x7A, 0x2E, 0x20, 0x01,
0xFF, 0xF2, 0x2F, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x35, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x30, 0x20, 0x01,
0xFF, 0xF2, 0x31, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x36, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x32, 0x20, 0x01,
0xFF, 0xF2, 0x33, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x03, 0x37, 0xFF, 0x20, 0x41,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD7, 0xFF, 0x20, 0x01,
0x02, 0xD0, 0x00, 0xE2, 0x01,
0x02, 0xD0, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x40, 0xA0, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x37, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x01,
0x03, 0x40, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x40, 0x01,
0x03, 0x41, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x23,
0x03, 0x40, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x40, 0x01,
0x03, 0x51, 0x08, 0x20, 0x09,
0x03, 0x5A, 0x42, 0x20, 0x01,
0x03, 0x52, 0x42, 0x22, 0x41,
0x03, 0x51, 0x08, 0x22, 0x01,
0x03, 0x58, 0x00, 0xE2, 0x23,
0x03, 0x41, 0x08, 0x40, 0x01,
0x03, 0x39, 0x08, 0x20, 0x09,
0x03, 0x42, 0x39, 0x20, 0x01,
0x03, 0x3A, 0x39, 0x22, 0x41,
0x03, 0x39, 0x08, 0x22, 0x01,
0x03, 0x40, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x59, 0x08, 0x20, 0x01,
0x03, 0x48, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x43, 0x22, 0x49,
0x03, 0x41, 0x08, 0x20, 0x01,
0x03, 0x30, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x3A, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x03, 0x40, 0x00, 0xE2, 0x01,
0x03, 0x58, 0x00, 0xC0, 0x01,
0x02, 0xCF, 0xFF, 0x20, 0x01,
0x03, 0x38, 0x00, 0xE2, 0x01,
0x03, 0x39, 0x08, 0x20, 0x01,
0x03, 0x28, 0x00, 0xE2, 0x01,
0x03, 0x40, 0x00, 0xC0, 0x01,
0x03, 0x0F, 0xFF, 0x20, 0x01,
0x03, 0x20, 0x00, 0xE2, 0x01,
0x03, 0x11, 0x08, 0x20, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x70, 0x00, 0xF0, 0x01,
0xFF, 0xE9, 0x08, 0x22, 0x01,
0x03, 0x68, 0x00, 0xF2, 0x01,
0x03, 0xB9, 0x08, 0x20, 0x01,
0x03, 0xBA, 0x47, 0x22, 0x41,
0x03, 0x6A, 0x47, 0x22, 0x01,
0x03, 0xA9, 0x08, 0x34, 0x01,
0x03, 0xAA, 0x47, 0x22, 0x41,
0x03, 0x72, 0x47, 0x22, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xB0, 0x00, 0xE2, 0x01,
0x03, 0xC0, 0x00, 0xF2, 0x01,
0x03, 0x78, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x43, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x3B, 0x20, 0x01,
0xFF, 0xF2, 0x3C, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x7A, 0x3D, 0x20, 0x01,
0xFF, 0xF2, 0x3E, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x44, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x3F, 0x20, 0x01,
0xFF, 0xF2, 0x40, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0xFF, 0xF2, 0x45, 0x40, 0x01,
0x03, 0x79, 0x08, 0x20, 0x09,
0x03, 0x7A, 0x41, 0x20, 0x01,
0xFF, 0xF2, 0x42, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x03, 0x7F, 0xFF, 0x20, 0x41,
0xFF, 0xF1, 0x07, 0x22, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x1F, 0xFF, 0x20, 0x01,
0x03, 0x18, 0x00, 0xE2, 0x01,
0x03, 0x18, 0x00, 0xC0, 0x01,
0x00, 0x02, 0x46, 0xA0, 0x01,
0xFF, 0xE7, 0xFF, 0x20, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x40, 0x01,
0xFF, 0xF1, 0x08, 0x20, 0x09,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x40, 0x01,
0x03, 0x99, 0x08, 0x20, 0x09,
0x03, 0xA2, 0x48, 0x20, 0x01,
0x03, 0x9A, 0x48, 0x22, 0x41,
0x03, 0x99, 0x08, 0x22, 0x01,
0x03, 0xA0, 0x00, 0xE2, 0x23,
0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0xA1, 0x08, 0x20, 0x01,
0x03, 0x90, 0x00, 0xE2, 0x01,
0xFF, 0xF2, 0x49, 0x22, 0x49,
0xFF, 0xF1, 0x08, 0x20, 0x01,
0xFF, 0xE9, 0x08, 0x20, 0x25,
0x03, 0x88, 0x00, 0xE2, 0x01,
0x03, 0xA0, 0x00, 0xC0, 0x01,
0x03, 0x17, 0xFF, 0x20, 0x01,
0x03, 0x80, 0x00, 0xE2, 0x01,
0x03, 0x81, 0x08, 0x20, 0x01,
0xFF, 0x30, 0x00, 0x02, 0x01,
0x02, 0xD9, 0x08, 0x20, 0x01,
0x03, 0x21, 0x08, 0x20, 0x01,
0xFF, 0x28, 0x00, 0x02, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
@@ -1003,29 +1025,13 @@
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, /* (2) Param */
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x6C, 0xAC,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00,
@@ -2044,14 +2050,8 @@
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x08, 0x1C, /* (3) IC 1.HWConFiguration */
0x00, 0x18, 0x08, 0x08, 0x00,
0x00, 0x18, 0x0B, 0x08, 0x00,
0x00, 0x44, 0x00, 0x44, 0x44,
0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00,
@@ -1,7 +1,7 @@
/*
* File: defines.h
*
* Created: Monday, July 6, 2026 2:41:28 PM
* Created: Friday, August 7, 2026 3:19:11 AM
* Description: DigiRadio IC default download data definitions.
*
* This software is distributed in the hope that it will be useful,
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,459 @@
/*
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadioTest\DigiRadio_IC_1_PARAM.h
*
* Created: Thursday, August 6, 2026 1:57:25 PM
* Description: DigiRadio:IC 1 parameter RAM definitions.
*
* This software is distributed in the hope that it will be useful,
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* This software may only be used to program products purchased from
* Analog Devices for incorporation by you into audio products that
* are intended for resale to audio product end users. This software
* may not be distributed whole or in any part to third parties.
*
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
*/
#ifndef __DIGIRADIO_IC_1_PARAM_H__
#define __DIGIRADIO_IC_1_PARAM_H__
/* Module Beep1 - Beep Sources*/
#define COUNT_BEEP1 3
#define DEVICE_BEEP1 "IC1"
#define ADDR_BEEP1_ENABLE 0
#define FIXPT_BEEP1_ENABLE 0x00000000
#define VALUE_BEEP1_ENABLE SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_BEEP1_ENABLE SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_BEEP1_KICK 1
#define FIXPT_BEEP1_KICK 0x00000000
#define VALUE_BEEP1_KICK SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_BEEP1_KICK SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_BEEP1_BEEP_FREQ 2
#define FIXPT_BEEP1_BEEP_FREQ 0x00006CAB
#define VALUE_BEEP1_BEEP_FREQ SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.00331636677280911)
#define TYPE_BEEP1_BEEP_FREQ SIGMASTUDIOTYPE_FIXPOINT
/* Module Si4674 - Multiple Volume Control*/
#define COUNT_SI4674 2
#define DEVICE_SI4674 "IC1"
#define ADDR_SI4674 3
#define FIXPT_SI4674 0x00800000
#define VALUE_SI4674 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SI4674 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_SI4674_1 4
#define FIXPT_SI4674_1 0x00800000
#define VALUE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT
/* Module ESP32 - Multiple Volume Control*/
#define COUNT_ESP32 2
#define DEVICE_ESP32 "IC1"
#define ADDR_ESP32 5
#define FIXPT_ESP32 0x00800000
#define VALUE_ESP32 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_ESP32 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_ESP32_1 6
#define FIXPT_ESP32_1 0x00800000
#define VALUE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT
/* Module St Mixer1 - Stereo Mixer*/
#define COUNT_STMIXER1 2
#define DEVICE_STMIXER1 "IC1"
#define ADDR_STMIXER1_ST0_VOLUME 7
#define FIXPT_STMIXER1_ST0_VOLUME 0x00800000
#define VALUE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_STMIXER1_ST1_VOLUME 8
#define FIXPT_STMIXER1_ST1_VOLUME 0x00800000
#define VALUE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT
/* Module Param EQ1 - Parametric EQ*/
#define COUNT_PARAMEQ1 60
#define DEVICE_PARAMEQ1 "IC1"
#define ADDR_PARAMEQ1_ST0_B0 9
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B0 9
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B1 10
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B1 10
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B2 11
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_B2 11
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A0 12
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A0 12
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A1 13
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST0_A1 13
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B0 14
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B0 14
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B1 15
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B1 15
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B2 16
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_B2 16
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A0 17
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A0 17
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A1 18
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST1_A1 18
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B0 19
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B0 19
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B1 20
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B1 20
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B2 21
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_B2 21
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A0 22
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A0 22
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A1 23
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST2_A1 23
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B0 24
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B0 24
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B1 25
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B1 25
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B2 26
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_B2 26
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A0 27
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A0 27
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A1 28
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST3_A1 28
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B0 29
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B0 29
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B1 30
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B1 30
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B2 31
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_B2 31
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A0 32
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A0 32
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A1 33
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST4_A1 33
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B0 34
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B0 34
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B1 35
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B1 35
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B2 36
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_B2 36
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A0 37
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A0 37
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A1 38
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_PARAMEQ1_ST5_A1 38
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
/* Module Multiple 1 - Multiple Volume Control*/
#define COUNT_MULTIPLE1 2
#define DEVICE_MULTIPLE1 "IC1"
#define ADDR_MULTIPLE1 39
#define FIXPT_MULTIPLE1 0x00800000
#define VALUE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_MULTIPLE1_1 40
#define FIXPT_MULTIPLE1_1 0x00800000
#define VALUE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT
/* Module Limiter 1 - Limiter*/
#define COUNT_LIMITER1 15
#define DEVICE_LIMITER1 "IC1"
#define ADDR_LIMITER1_S1 41
#define FIXPT_LIMITER1_S1 0xFFFE2BE3
#define VALUE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
#define TYPE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT1 42
#define FIXPT_LIMITER1_INT1 0x0022BE2B
#define VALUE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
#define TYPE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S2 43
#define FIXPT_LIMITER1_S2 0xFFEAAAAB
#define VALUE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
#define TYPE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT2 44
#define FIXPT_LIMITER1_INT2 0x006AAAAA
#define VALUE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
#define TYPE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S3 45
#define FIXPT_LIMITER1_S3 0xFF9A69A7
#define VALUE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
#define TYPE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT3 46
#define FIXPT_LIMITER1_INT3 0x00FDF7DF
#define VALUE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
#define TYPE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_S4 47
#define FIXPT_LIMITER1_S4 0xF83C6C20
#define VALUE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
#define TYPE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_INT4 48
#define FIXPT_LIMITER1_INT4 0x03F5AA22
#define VALUE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
#define TYPE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C1 49
#define FIXPT_LIMITER1_C1 0x01800000
#define VALUE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
#define TYPE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C2 50
#define FIXPT_LIMITER1_C2 0x01000000
#define VALUE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
#define TYPE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_C3 51
#define FIXPT_LIMITER1_C3 0x00333333
#define VALUE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
#define TYPE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_THRESHOLD 52
#define FIXPT_LIMITER1_THRESHOLD 0x00800000
#define VALUE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_RMS 53
#define FIXPT_LIMITER1_RMS 0x000007DC
#define VALUE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
#define TYPE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_DECAY 54
#define FIXPT_LIMITER1_DECAY 0x00000800
#define VALUE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
#define TYPE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER1_DECAYCOMPLEMENT 55
#define FIXPT_LIMITER1_DECAYCOMPLEMENT 0x007FF000
#define VALUE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
#define TYPE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
/* Module Limiter 2 - Limiter*/
#define COUNT_LIMITER2 15
#define DEVICE_LIMITER2 "IC1"
#define ADDR_LIMITER2_S1 56
#define FIXPT_LIMITER2_S1 0xFFFE2BE3
#define VALUE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
#define TYPE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT1 57
#define FIXPT_LIMITER2_INT1 0x0022BE2B
#define VALUE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
#define TYPE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S2 58
#define FIXPT_LIMITER2_S2 0xFFEAAAAB
#define VALUE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
#define TYPE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT2 59
#define FIXPT_LIMITER2_INT2 0x006AAAAA
#define VALUE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
#define TYPE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S3 60
#define FIXPT_LIMITER2_S3 0xFF9A69A7
#define VALUE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
#define TYPE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT3 61
#define FIXPT_LIMITER2_INT3 0x00FDF7DF
#define VALUE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
#define TYPE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_S4 62
#define FIXPT_LIMITER2_S4 0xF83C6C20
#define VALUE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
#define TYPE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_INT4 63
#define FIXPT_LIMITER2_INT4 0x03F5AA22
#define VALUE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
#define TYPE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C1 64
#define FIXPT_LIMITER2_C1 0x01800000
#define VALUE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
#define TYPE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C2 65
#define FIXPT_LIMITER2_C2 0x01000000
#define VALUE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
#define TYPE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_C3 66
#define FIXPT_LIMITER2_C3 0x00333333
#define VALUE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
#define TYPE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_THRESHOLD 67
#define FIXPT_LIMITER2_THRESHOLD 0x00800000
#define VALUE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
#define TYPE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_RMS 68
#define FIXPT_LIMITER2_RMS 0x000007DC
#define VALUE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
#define TYPE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_DECAY 69
#define FIXPT_LIMITER2_DECAY 0x00000800
#define VALUE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
#define TYPE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT
#define ADDR_LIMITER2_DECAYCOMPLEMENT 70
#define FIXPT_LIMITER2_DECAYCOMPLEMENT 0x007FF000
#define VALUE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
#define TYPE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
#endif
@@ -0,0 +1,565 @@
/*
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadioTest\DigiRadio_IC_1_REG.h
*
* Created: Thursday, August 6, 2026 1:57:25 PM
* Description: DigiRadio:IC 1 control register definitions.
*
* This software is distributed in the hope that it will be useful,
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* This software may only be used to program products purchased from
* Analog Devices for incorporation by you into audio products that
* are intended for resale to audio product end users. This software
* may not be distributed whole or in any part to third parties.
*
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
*/
#ifndef __DIGIRADIO_IC_1_REG_H__
#define __DIGIRADIO_IC_1_REG_H__
/* InterfaceRegister0 - Registers (IC 1) */
#define REG_INTERFACEREGISTER0_IC_1_ADDR 0x800
#define REG_INTERFACEREGISTER0_IC_1_BYTE 4
#define REG_INTERFACEREGISTER0_IC_1_VALUE 0x0
/* InterfaceRegister1 - Registers (IC 1) */
#define REG_INTERFACEREGISTER1_IC_1_ADDR 0x801
#define REG_INTERFACEREGISTER1_IC_1_BYTE 4
#define REG_INTERFACEREGISTER1_IC_1_VALUE 0x0
/* InterfaceRegister2 - Registers (IC 1) */
#define REG_INTERFACEREGISTER2_IC_1_ADDR 0x802
#define REG_INTERFACEREGISTER2_IC_1_BYTE 4
#define REG_INTERFACEREGISTER2_IC_1_VALUE 0x0
/* InterfaceRegister3 - Registers (IC 1) */
#define REG_INTERFACEREGISTER3_IC_1_ADDR 0x803
#define REG_INTERFACEREGISTER3_IC_1_BYTE 4
#define REG_INTERFACEREGISTER3_IC_1_VALUE 0x0
/* InterfaceRegister4 - Registers (IC 1) */
#define REG_INTERFACEREGISTER4_IC_1_ADDR 0x804
#define REG_INTERFACEREGISTER4_IC_1_BYTE 4
#define REG_INTERFACEREGISTER4_IC_1_VALUE 0x0
/* InterfaceRegister5 - Registers (IC 1) */
#define REG_INTERFACEREGISTER5_IC_1_ADDR 0x805
#define REG_INTERFACEREGISTER5_IC_1_BYTE 4
#define REG_INTERFACEREGISTER5_IC_1_VALUE 0x0
/* InterfaceRegister6 - Registers (IC 1) */
#define REG_INTERFACEREGISTER6_IC_1_ADDR 0x806
#define REG_INTERFACEREGISTER6_IC_1_BYTE 4
#define REG_INTERFACEREGISTER6_IC_1_VALUE 0x0
/* InterfaceRegister7 - Registers (IC 1) */
#define REG_INTERFACEREGISTER7_IC_1_ADDR 0x807
#define REG_INTERFACEREGISTER7_IC_1_BYTE 4
#define REG_INTERFACEREGISTER7_IC_1_VALUE 0x0
/* GpioAllRegister - Registers (IC 1) */
#define REG_GPIOALLREGISTER_IC_1_ADDR 0x808
#define REG_GPIOALLREGISTER_IC_1_BYTE 2
#define REG_GPIOALLREGISTER_IC_1_VALUE 0x0
/* Adc0 - Registers (IC 1) */
#define REG_ADC0_IC_1_ADDR 0x809
#define REG_ADC0_IC_1_BYTE 1
#define REG_ADC0_IC_1_VALUE 0x0
/* Adc1 - Registers (IC 1) */
#define REG_ADC1_IC_1_ADDR 0x80A
#define REG_ADC1_IC_1_BYTE 1
#define REG_ADC1_IC_1_VALUE 0x0
/* Adc2 - Registers (IC 1) */
#define REG_ADC2_IC_1_ADDR 0x80B
#define REG_ADC2_IC_1_BYTE 1
#define REG_ADC2_IC_1_VALUE 0x0
/* Adc3 - Registers (IC 1) */
#define REG_ADC3_IC_1_ADDR 0x80C
#define REG_ADC3_IC_1_BYTE 1
#define REG_ADC3_IC_1_VALUE 0x0
/* CoreRegister - Registers (IC 1) */
#define REG_COREREGISTER_IC_1_ADDR 0x81C
#define REG_COREREGISTER_IC_1_BYTE 2
#define REG_COREREGISTER_IC_1_VALUE 0x1C
/* RAMRegister - Registers (IC 1) */
#define REG_RAMREGISTER_IC_1_ADDR 0x81D
#define REG_RAMREGISTER_IC_1_BYTE 1
#define REG_RAMREGISTER_IC_1_VALUE 0x8
/* SerialOutRegister1 - Registers (IC 1) */
#define REG_SERIALOUTREGISTER1_IC_1_ADDR 0x81E
#define REG_SERIALOUTREGISTER1_IC_1_BYTE 2
#define REG_SERIALOUTREGISTER1_IC_1_VALUE 0x800
/* SerialInputRegister - Registers (IC 1) */
#define REG_SERIALINPUTREGISTER_IC_1_ADDR 0x81F
#define REG_SERIALINPUTREGISTER_IC_1_BYTE 1
#define REG_SERIALINPUTREGISTER_IC_1_VALUE 0x0
/* MpCfg0 - Registers (IC 1) */
#define REG_MPCFG0_IC_1_ADDR 0x820
#define REG_MPCFG0_IC_1_BYTE 3
#define REG_MPCFG0_IC_1_VALUE 0x440044
/* MpCfg1 - Registers (IC 1) */
#define REG_MPCFG1_IC_1_ADDR 0x821
#define REG_MPCFG1_IC_1_BYTE 3
#define REG_MPCFG1_IC_1_VALUE 0x440004
/* AnalogPowerDownRegister - Registers (IC 1) */
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_ADDR 0x822
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_BYTE 2
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_VALUE 0x0
/* TestRegister - Registers (IC 1) */
#define REG_TESTREGISTER_IC_1_ADDR 0x823
#define REG_TESTREGISTER_IC_1_BYTE 2
#define REG_TESTREGISTER_IC_1_VALUE 0x0
/* AnalogInterfaceRegister0 - Registers (IC 1) */
#define REG_ANALOGINTERFACEREGISTER0_IC_1_ADDR 0x824
#define REG_ANALOGINTERFACEREGISTER0_IC_1_BYTE 2
#define REG_ANALOGINTERFACEREGISTER0_IC_1_VALUE 0x8000
/* AnalogInterfaceRegister1 - Registers (IC 1) */
#define REG_ANALOGINTERFACEREGISTER1_IC_1_ADDR 0x825
#define REG_ANALOGINTERFACEREGISTER1_IC_1_BYTE 2
#define REG_ANALOGINTERFACEREGISTER1_IC_1_VALUE 0x0
/* AnalogInterfaceRegister2 - Registers (IC 1) */
#define REG_ANALOGINTERFACEREGISTER2_IC_1_ADDR 0x826
#define REG_ANALOGINTERFACEREGISTER2_IC_1_BYTE 2
#define REG_ANALOGINTERFACEREGISTER2_IC_1_VALUE 0x0
/* AnalogInterfaceRegister3 - Registers (IC 1) */
#define REG_ANALOGINTERFACEREGISTER3_IC_1_ADDR 0x827
#define REG_ANALOGINTERFACEREGISTER3_IC_1_BYTE 2
#define REG_ANALOGINTERFACEREGISTER3_IC_1_VALUE 0x1
/*
*
* Control register's field descriptions
*
*/
/* InterfaceRegister0 (IC 1) */
#define R0_INTERFACEREGISTER0_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R0_INTERFACEREGISTER0_IC_1_MASK 0xFFFFFFFF
#define R0_INTERFACEREGISTER0_IC_1_SHIFT 0
/* InterfaceRegister1 (IC 1) */
#define R1_INTERFACEREGISTER1_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R1_INTERFACEREGISTER1_IC_1_MASK 0xFFFFFFFF
#define R1_INTERFACEREGISTER1_IC_1_SHIFT 0
/* InterfaceRegister2 (IC 1) */
#define R2_INTERFACEREGISTER2_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R2_INTERFACEREGISTER2_IC_1_MASK 0xFFFFFFFF
#define R2_INTERFACEREGISTER2_IC_1_SHIFT 0
/* InterfaceRegister3 (IC 1) */
#define R3_INTERFACEREGISTER3_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R3_INTERFACEREGISTER3_IC_1_MASK 0xFFFFFFFF
#define R3_INTERFACEREGISTER3_IC_1_SHIFT 0
/* InterfaceRegister4 (IC 1) */
#define R4_INTERFACEREGISTER4_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R4_INTERFACEREGISTER4_IC_1_MASK 0xFFFFFFFF
#define R4_INTERFACEREGISTER4_IC_1_SHIFT 0
/* InterfaceRegister5 (IC 1) */
#define R5_INTERFACEREGISTER5_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R5_INTERFACEREGISTER5_IC_1_MASK 0xFFFFFFFF
#define R5_INTERFACEREGISTER5_IC_1_SHIFT 0
/* InterfaceRegister6 (IC 1) */
#define R6_INTERFACEREGISTER6_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R6_INTERFACEREGISTER6_IC_1_MASK 0xFFFFFFFF
#define R6_INTERFACEREGISTER6_IC_1_SHIFT 0
/* InterfaceRegister7 (IC 1) */
#define R7_INTERFACEREGISTER7_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
#define R7_INTERFACEREGISTER7_IC_1_MASK 0xFFFFFFFF
#define R7_INTERFACEREGISTER7_IC_1_SHIFT 0
/* GpioAllRegister (IC 1) */
#define R8_PIN0_IC_1 0x0 /* 0b [0] */
#define R8_PIN1_IC_1 0x0 /* 0b [1] */
#define R8_PIN2_IC_1 0x0 /* 0b [2] */
#define R8_PIN3_IC_1 0x0 /* 0b [3] */
#define R8_PIN4_IC_1 0x0 /* 0b [4] */
#define R8_PIN5_IC_1 0x0 /* 0b [5] */
#define R8_PIN6_IC_1 0x0 /* 0b [6] */
#define R8_PIN7_IC_1 0x0 /* 0b [7] */
#define R8_PIN8_IC_1 0x0 /* 0b [8] */
#define R8_PIN9_IC_1 0x0 /* 0b [9] */
#define R8_PIN10_IC_1 0x0 /* 0b [10] */
#define R8_PIN11_IC_1 0x0 /* 0b [11] */
#define R8_PIN0_IC_1_MASK 0x1
#define R8_PIN0_IC_1_SHIFT 0
#define R8_PIN1_IC_1_MASK 0x2
#define R8_PIN1_IC_1_SHIFT 1
#define R8_PIN2_IC_1_MASK 0x4
#define R8_PIN2_IC_1_SHIFT 2
#define R8_PIN3_IC_1_MASK 0x8
#define R8_PIN3_IC_1_SHIFT 3
#define R8_PIN4_IC_1_MASK 0x10
#define R8_PIN4_IC_1_SHIFT 4
#define R8_PIN5_IC_1_MASK 0x20
#define R8_PIN5_IC_1_SHIFT 5
#define R8_PIN6_IC_1_MASK 0x40
#define R8_PIN6_IC_1_SHIFT 6
#define R8_PIN7_IC_1_MASK 0x80
#define R8_PIN7_IC_1_SHIFT 7
#define R8_PIN8_IC_1_MASK 0x100
#define R8_PIN8_IC_1_SHIFT 8
#define R8_PIN9_IC_1_MASK 0x200
#define R8_PIN9_IC_1_SHIFT 9
#define R8_PIN10_IC_1_MASK 0x400
#define R8_PIN10_IC_1_SHIFT 10
#define R8_PIN11_IC_1_MASK 0x800
#define R8_PIN11_IC_1_SHIFT 11
/* Adc0 (IC 1) */
#define R9_ADC0_IC_1 0x00 /* 00000000b [7:0] */
#define R9_ADC0_IC_1_MASK 0xFF
#define R9_ADC0_IC_1_SHIFT 0
/* Adc1 (IC 1) */
#define R10_ADC1_IC_1 0x00 /* 00000000b [7:0] */
#define R10_ADC1_IC_1_MASK 0xFF
#define R10_ADC1_IC_1_SHIFT 0
/* Adc2 (IC 1) */
#define R11_ADC2_IC_1 0x00 /* 00000000b [7:0] */
#define R11_ADC2_IC_1_MASK 0xFF
#define R11_ADC2_IC_1_SHIFT 0
/* Adc3 (IC 1) */
#define R12_ADC3_IC_1 0x00 /* 00000000b [7:0] */
#define R12_ADC3_IC_1_MASK 0xFF
#define R12_ADC3_IC_1_SHIFT 0
/* CoreRegister (IC 1) */
#define R13_PROGRAM_LENGTH_IC_1 0x0 /* 00b [1:0] */
#define R13_REGISTER_ZERO_IC_1 0x1 /* 1b [2] */
#define R13_MUTE_DAC_IC_1 0x1 /* 1b [3] */
#define R13_MUTE_ADC_IC_1 0x1 /* 1b [4] */
#define R13_SAFELOAD_IC_1 0x0 /* 0b [5] */
#define R13_WRITESPI_INTERFACEREG_IC_1 0x0 /* 0b [6] */
#define R13_WRITESPI_GPIO_IC_1 0x0 /* 0b [7] */
#define R13_WRITESPI_ADC_IC_1 0x0 /* 0b [8] */
#define R13_MUTE_SERIALIN_IC_1 0x0 /* 0b [9] */
#define R13_GPIO_DEBOUNCE_IC_1 0x0 /* 00b [11:10] */
#define R13_EXTMEM_SPEED_IC_1 0x0 /* 00b [13:12] */
#define R13_PROGRAM_LENGTH_IC_1_MASK 0x3
#define R13_PROGRAM_LENGTH_IC_1_SHIFT 0
#define R13_REGISTER_ZERO_IC_1_MASK 0x4
#define R13_REGISTER_ZERO_IC_1_SHIFT 2
#define R13_MUTE_DAC_IC_1_MASK 0x8
#define R13_MUTE_DAC_IC_1_SHIFT 3
#define R13_MUTE_ADC_IC_1_MASK 0x10
#define R13_MUTE_ADC_IC_1_SHIFT 4
#define R13_SAFELOAD_IC_1_MASK 0x20
#define R13_SAFELOAD_IC_1_SHIFT 5
#define R13_WRITESPI_INTERFACEREG_IC_1_MASK 0x40
#define R13_WRITESPI_INTERFACEREG_IC_1_SHIFT 6
#define R13_WRITESPI_GPIO_IC_1_MASK 0x80
#define R13_WRITESPI_GPIO_IC_1_SHIFT 7
#define R13_WRITESPI_ADC_IC_1_MASK 0x100
#define R13_WRITESPI_ADC_IC_1_SHIFT 8
#define R13_MUTE_SERIALIN_IC_1_MASK 0x200
#define R13_MUTE_SERIALIN_IC_1_SHIFT 9
#define R13_GPIO_DEBOUNCE_IC_1_MASK 0xC00
#define R13_GPIO_DEBOUNCE_IC_1_SHIFT 10
#define R13_EXTMEM_SPEED_IC_1_MASK 0x3000
#define R13_EXTMEM_SPEED_IC_1_SHIFT 12
/* RAMRegister (IC 1) */
#define R14_RAM_MODULO_IC_1 0x8 /* 1000b [3:0] */
#define R14_RAM_MODULO_IC_1_MASK 0xF
#define R14_RAM_MODULO_IC_1_SHIFT 0
/* SerialOutRegister1 (IC 1) */
#define R15_WORDLENGTH_IC_1 0x0 /* 00b [1:0] */
#define R15_MSB_POS_IC_1 0x0 /* 000b [4:2] */
#define R15_TDM_IC_1 0x0 /* 0b [5] */
#define R15_FRAMESYNC_TYPE_IC_1 0x0 /* 0b [6] */
#define R15_LRCLK_FREQ_IC_1 0x0 /* 00b [8:7] */
#define R15_BCLK_FREQ_IC_1 0x0 /* 00b [10:9] */
#define R15_MASTER_SLAVE_IC_1 0x1 /* 1b [11] */
#define R15_BCLK_POLARITY_IC_1 0x0 /* 0b [12] */
#define R15_LRCLK_POLARITY_IC_1 0x0 /* 0b [13] */
#define R15_WORDLENGTH_IC_1_MASK 0x3
#define R15_WORDLENGTH_IC_1_SHIFT 0
#define R15_MSB_POS_IC_1_MASK 0x1C
#define R15_MSB_POS_IC_1_SHIFT 2
#define R15_TDM_IC_1_MASK 0x20
#define R15_TDM_IC_1_SHIFT 5
#define R15_FRAMESYNC_TYPE_IC_1_MASK 0x40
#define R15_FRAMESYNC_TYPE_IC_1_SHIFT 6
#define R15_LRCLK_FREQ_IC_1_MASK 0x180
#define R15_LRCLK_FREQ_IC_1_SHIFT 7
#define R15_BCLK_FREQ_IC_1_MASK 0x600
#define R15_BCLK_FREQ_IC_1_SHIFT 9
#define R15_MASTER_SLAVE_IC_1_MASK 0x800
#define R15_MASTER_SLAVE_IC_1_SHIFT 11
#define R15_BCLK_POLARITY_IC_1_MASK 0x1000
#define R15_BCLK_POLARITY_IC_1_SHIFT 12
#define R15_LRCLK_POLARITY_IC_1_MASK 0x2000
#define R15_LRCLK_POLARITY_IC_1_SHIFT 13
/* SerialInputRegister (IC 1) */
#define R16_INPUT_MODE_IC_1 0x0 /* 000b [2:0] */
#define R16_BCLK_POLARITY_IC_1 0x0 /* 0b [3] */
#define R16_LRCLK_POLARITY_IC_1 0x0 /* 0b [4] */
#define R16_INPUT_MODE_IC_1_MASK 0x7
#define R16_INPUT_MODE_IC_1_SHIFT 0
#define R16_BCLK_POLARITY_IC_1_MASK 0x8
#define R16_BCLK_POLARITY_IC_1_SHIFT 3
#define R16_LRCLK_POLARITY_IC_1_MASK 0x10
#define R16_LRCLK_POLARITY_IC_1_SHIFT 4
/* MpCfg0 (IC 1) */
#define R17_MFSELECT0_IC_1 0x4 /* 100b [2:0] */
#define R17_MFINVERT0_IC_1 0x0 /* 0b [3] */
#define R17_MFSELECT1_IC_1 0x4 /* 100b [6:4] */
#define R17_MFINVERT1_IC_1 0x0 /* 0b [7] */
#define R17_MFSELECT2_IC_1 0x0 /* 000b [10:8] */
#define R17_MFINVERT2_IC_1 0x0 /* 0b [11] */
#define R17_MFSELECT3_IC_1 0x0 /* 000b [14:12] */
#define R17_MFINVERT3_IC_1 0x0 /* 0b [15] */
#define R17_MFSELECT4_IC_1 0x4 /* 100b [18:16] */
#define R17_MFINVERT4_IC_1 0x0 /* 0b [19] */
#define R17_MFSELECT5_IC_1 0x4 /* 100b [22:20] */
#define R17_MFINVERT5_IC_1 0x0 /* 0b [23] */
#define R17_MFSELECT0_IC_1_MASK 0x7
#define R17_MFSELECT0_IC_1_SHIFT 0
#define R17_MFINVERT0_IC_1_MASK 0x8
#define R17_MFINVERT0_IC_1_SHIFT 3
#define R17_MFSELECT1_IC_1_MASK 0x70
#define R17_MFSELECT1_IC_1_SHIFT 4
#define R17_MFINVERT1_IC_1_MASK 0x80
#define R17_MFINVERT1_IC_1_SHIFT 7
#define R17_MFSELECT2_IC_1_MASK 0x700
#define R17_MFSELECT2_IC_1_SHIFT 8
#define R17_MFINVERT2_IC_1_MASK 0x800
#define R17_MFINVERT2_IC_1_SHIFT 11
#define R17_MFSELECT3_IC_1_MASK 0x7000
#define R17_MFSELECT3_IC_1_SHIFT 12
#define R17_MFINVERT3_IC_1_MASK 0x8000
#define R17_MFINVERT3_IC_1_SHIFT 15
#define R17_MFSELECT4_IC_1_MASK 0x70000
#define R17_MFSELECT4_IC_1_SHIFT 16
#define R17_MFINVERT4_IC_1_MASK 0x80000
#define R17_MFINVERT4_IC_1_SHIFT 19
#define R17_MFSELECT5_IC_1_MASK 0x700000
#define R17_MFSELECT5_IC_1_SHIFT 20
#define R17_MFINVERT5_IC_1_MASK 0x800000
#define R17_MFINVERT5_IC_1_SHIFT 23
/* MpCfg1 (IC 1) */
#define R18_MFSELECT6_IC_1 0x4 /* 100b [2:0] */
#define R18_MFINVERT6_IC_1 0x0 /* 0b [3] */
#define R18_MFSELECT7_IC_1 0x0 /* 000b [6:4] */
#define R18_MFINVERT7_IC_1 0x0 /* 0b [7] */
#define R18_MFSELECT8_IC_1 0x0 /* 000b [10:8] */
#define R18_MFINVERT8_IC_1 0x0 /* 0b [11] */
#define R18_MFSELECT9_IC_1 0x0 /* 000b [14:12] */
#define R18_MFINVERT9_IC_1 0x0 /* 0b [15] */
#define R18_MFSELECT10_IC_1 0x4 /* 100b [18:16] */
#define R18_MFINVERT10_IC_1 0x0 /* 0b [19] */
#define R18_MFSELECT11_IC_1 0x4 /* 100b [22:20] */
#define R18_MFINVERT11_IC_1 0x0 /* 0b [23] */
#define R18_MFSELECT6_IC_1_MASK 0x7
#define R18_MFSELECT6_IC_1_SHIFT 0
#define R18_MFINVERT6_IC_1_MASK 0x8
#define R18_MFINVERT6_IC_1_SHIFT 3
#define R18_MFSELECT7_IC_1_MASK 0x70
#define R18_MFSELECT7_IC_1_SHIFT 4
#define R18_MFINVERT7_IC_1_MASK 0x80
#define R18_MFINVERT7_IC_1_SHIFT 7
#define R18_MFSELECT8_IC_1_MASK 0x700
#define R18_MFSELECT8_IC_1_SHIFT 8
#define R18_MFINVERT8_IC_1_MASK 0x800
#define R18_MFINVERT8_IC_1_SHIFT 11
#define R18_MFSELECT9_IC_1_MASK 0x7000
#define R18_MFSELECT9_IC_1_SHIFT 12
#define R18_MFINVERT9_IC_1_MASK 0x8000
#define R18_MFINVERT9_IC_1_SHIFT 15
#define R18_MFSELECT10_IC_1_MASK 0x70000
#define R18_MFSELECT10_IC_1_SHIFT 16
#define R18_MFINVERT10_IC_1_MASK 0x80000
#define R18_MFINVERT10_IC_1_SHIFT 19
#define R18_MFSELECT11_IC_1_MASK 0x700000
#define R18_MFSELECT11_IC_1_SHIFT 20
#define R18_MFINVERT11_IC_1_MASK 0x800000
#define R18_MFINVERT11_IC_1_SHIFT 23
/* AnalogPowerDownRegister (IC 1) */
#define R19_DAC3_POWERDOWN_IC_1 0x0 /* 0b [0] */
#define R19_DAC2_POWERDOWN_IC_1 0x0 /* 0b [1] */
#define R19_DAC1_POWERDOWN_IC_1 0x0 /* 0b [2] */
#define R19_DAC0_POWERDOWN_IC_1 0x0 /* 0b [3] */
#define R19_DACS_RESET_IC_1 0x0 /* 0b [4] */
#define R19_REF_CORE_POWERDOWN_IC_1 0x0 /* 0b [5] */
#define R19_REF_BUF_POWERDOWN_IC_1 0x0 /* 0b [6] */
#define R19_ADCS_POWERDOWN_IC_1 0x0 /* 0b [7] */
#define R19_AUX_ADC_FILTER_IC_1 0x0 /* 000b [10:8] */
#define R19_DAC3_POWERDOWN_IC_1_MASK 0x1
#define R19_DAC3_POWERDOWN_IC_1_SHIFT 0
#define R19_DAC2_POWERDOWN_IC_1_MASK 0x2
#define R19_DAC2_POWERDOWN_IC_1_SHIFT 1
#define R19_DAC1_POWERDOWN_IC_1_MASK 0x4
#define R19_DAC1_POWERDOWN_IC_1_SHIFT 2
#define R19_DAC0_POWERDOWN_IC_1_MASK 0x8
#define R19_DAC0_POWERDOWN_IC_1_SHIFT 3
#define R19_DACS_RESET_IC_1_MASK 0x10
#define R19_DACS_RESET_IC_1_SHIFT 4
#define R19_REF_CORE_POWERDOWN_IC_1_MASK 0x20
#define R19_REF_CORE_POWERDOWN_IC_1_SHIFT 5
#define R19_REF_BUF_POWERDOWN_IC_1_MASK 0x40
#define R19_REF_BUF_POWERDOWN_IC_1_SHIFT 6
#define R19_ADCS_POWERDOWN_IC_1_MASK 0x80
#define R19_ADCS_POWERDOWN_IC_1_SHIFT 7
#define R19_AUX_ADC_FILTER_IC_1_MASK 0x700
#define R19_AUX_ADC_FILTER_IC_1_SHIFT 8
/* TestRegister (IC 1) */
#define R20_ADCCLOCKDELAY_IC_1 0x0 /* 000b [2:0] */
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1 0x0 /* 0b [3] */
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1 0x0 /* 0b [4] */
#define R20_SPIKEFILT_SEROUT0_1_IC_1 0x0 /* 0b [5] */
#define R20_UNUSED_IC_1 0x00 /* 0000000000b [15:6] */
#define R20_ADCCLOCKDELAY_IC_1_MASK 0x7
#define R20_ADCCLOCKDELAY_IC_1_SHIFT 0
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1_MASK 0x8
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1_SHIFT 3
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1_MASK 0x10
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1_SHIFT 4
#define R20_SPIKEFILT_SEROUT0_1_IC_1_MASK 0x20
#define R20_SPIKEFILT_SEROUT0_1_IC_1_SHIFT 5
#define R20_UNUSED_IC_1_MASK 0xFFC0
#define R20_UNUSED_IC_1_SHIFT 6
/* AnalogInterfaceRegister0 (IC 1) */
#define R21_CHOP_ENB_INT_AMP_IC_1 0x0 /* 0b [0] */
#define R21_PD_INT_AMP_IC_1 0x0 /* 0b [1] */
#define R21_ADC_NON_OVERLAP_ADJ_IC_1 0x0 /* 0b [2] */
#define R21_ADC_I_SEL_IC_1 0x0 /* 00000b [7:3] */
#define R21_ADC_CLK_ADJ_IC_1 0x0 /* 000b [10:8] */
#define R21_LCHOP_PMOS_CTRL_IC_1 0x0 /* 00b [12:11] */
#define R21_LCHOP_AMP_ENB_IC_1 0x0 /* 0b [13] */
#define R21_LSCRAM_ENB_IC_1 0x0 /* 0b [14] */
#define R21_AUX_ADC_EN_IC_1 0x1 /* 1b [15] */
#define R21_CHOP_ENB_INT_AMP_IC_1_MASK 0x1
#define R21_CHOP_ENB_INT_AMP_IC_1_SHIFT 0
#define R21_PD_INT_AMP_IC_1_MASK 0x2
#define R21_PD_INT_AMP_IC_1_SHIFT 1
#define R21_ADC_NON_OVERLAP_ADJ_IC_1_MASK 0x4
#define R21_ADC_NON_OVERLAP_ADJ_IC_1_SHIFT 2
#define R21_ADC_I_SEL_IC_1_MASK 0xF8
#define R21_ADC_I_SEL_IC_1_SHIFT 3
#define R21_ADC_CLK_ADJ_IC_1_MASK 0x700
#define R21_ADC_CLK_ADJ_IC_1_SHIFT 8
#define R21_LCHOP_PMOS_CTRL_IC_1_MASK 0x1800
#define R21_LCHOP_PMOS_CTRL_IC_1_SHIFT 11
#define R21_LCHOP_AMP_ENB_IC_1_MASK 0x2000
#define R21_LCHOP_AMP_ENB_IC_1_SHIFT 13
#define R21_LSCRAM_ENB_IC_1_MASK 0x4000
#define R21_LSCRAM_ENB_IC_1_SHIFT 14
#define R21_AUX_ADC_EN_IC_1_MASK 0x8000
#define R21_AUX_ADC_EN_IC_1_SHIFT 15
/* AnalogInterfaceRegister1 (IC 1) */
#define R22_RSWC_DAC_CTRL_IC_1 0x0 /* 00b [1:0] */
#define R22_RCLK_SEL_PMOS_IC_1 0x0 /* 00b [3:2] */
#define R22_RCLK_SEL_AMP_IC_1 0x0 /* 00b [5:4] */
#define R22_RCHOP_PMOS_CTRL_IC_1 0x0 /* 00b [7:6] */
#define R22_LCLK_SEL_PMOS_IC_1 0x0 /* 00b [9:8] */
#define R22_LSWC_DAC_CTRL_IC_1 0x0 /* 00b [11:10] */
#define R22_CLK_SEL_INT_AMP_IC_1 0x0 /* 00b [13:12] */
#define R22_RSWC_DAC_CTRL_IC_1_MASK 0x3
#define R22_RSWC_DAC_CTRL_IC_1_SHIFT 0
#define R22_RCLK_SEL_PMOS_IC_1_MASK 0xC
#define R22_RCLK_SEL_PMOS_IC_1_SHIFT 2
#define R22_RCLK_SEL_AMP_IC_1_MASK 0x30
#define R22_RCLK_SEL_AMP_IC_1_SHIFT 4
#define R22_RCHOP_PMOS_CTRL_IC_1_MASK 0xC0
#define R22_RCHOP_PMOS_CTRL_IC_1_SHIFT 6
#define R22_LCLK_SEL_PMOS_IC_1_MASK 0x300
#define R22_LCLK_SEL_PMOS_IC_1_SHIFT 8
#define R22_LSWC_DAC_CTRL_IC_1_MASK 0xC00
#define R22_LSWC_DAC_CTRL_IC_1_SHIFT 10
#define R22_CLK_SEL_INT_AMP_IC_1_MASK 0x3000
#define R22_CLK_SEL_INT_AMP_IC_1_SHIFT 12
/* AnalogInterfaceRegister2 (IC 1) */
#define R23_UNUSED_IC_1 0x0 /* 0b [0] */
#define R23_ADC_DITHER_EN_IC_1 0x0 /* 0b [1] */
#define R23_XTAL_ENB_IC_1 0x0 /* 0b [2] */
#define R23_PLL_PD_IC_1 0x0 /* 0b [3] */
#define R23_IBIAS_TO_PIN_IC_1 0x0 /* 0b [4] */
#define R23_IBIAS_ADJ_IC_1 0x0 /* 0000b [8:5] */
#define R23_VREF_TRIM_IC_1 0x0 /* 0000b [12:9] */
#define R23_REF_I_SEL_IC_1 0x0 /* 0b [13] */
#define R23_RSCRAM_ENB_IC_1 0x0 /* 0b [14] */
#define R23_LCHOP_AMP_ENB_IC_1 0x0 /* 0b [15] */
#define R23_UNUSED_IC_1_MASK 0x1
#define R23_UNUSED_IC_1_SHIFT 0
#define R23_ADC_DITHER_EN_IC_1_MASK 0x2
#define R23_ADC_DITHER_EN_IC_1_SHIFT 1
#define R23_XTAL_ENB_IC_1_MASK 0x4
#define R23_XTAL_ENB_IC_1_SHIFT 2
#define R23_PLL_PD_IC_1_MASK 0x8
#define R23_PLL_PD_IC_1_SHIFT 3
#define R23_IBIAS_TO_PIN_IC_1_MASK 0x10
#define R23_IBIAS_TO_PIN_IC_1_SHIFT 4
#define R23_IBIAS_ADJ_IC_1_MASK 0x1E0
#define R23_IBIAS_ADJ_IC_1_SHIFT 5
#define R23_VREF_TRIM_IC_1_MASK 0x1E00
#define R23_VREF_TRIM_IC_1_SHIFT 9
#define R23_REF_I_SEL_IC_1_MASK 0x2000
#define R23_REF_I_SEL_IC_1_SHIFT 13
#define R23_RSCRAM_ENB_IC_1_MASK 0x4000
#define R23_RSCRAM_ENB_IC_1_SHIFT 14
#define R23_LCHOP_AMP_ENB_IC_1_MASK 0x8000
#define R23_LCHOP_AMP_ENB_IC_1_SHIFT 15
/* AnalogInterfaceRegister3 (IC 1) */
#define R24_DAC_OFFSET_IC_1 0x1 /* 01b [1:0] */
#define R24_DAC_DITHER_ENABLE_IC_1 0x0 /* 0b [2] */
#define R24_PCTRL_IC_1 0x0 /* 00b [4:3] */
#define R24_DAC_PCLK_SEL_IC_1 0x0 /* 00b [6:5] */
#define R24_DAC_I_SEL_IC_1 0x0 /* 0000b [10:7] */
#define R24_DAC_CLK_SEL_IC_1 0x0 /* 00b [12:11] */
#define R24_DAC_CHOP_ENB_IC_1 0x0 /* 0b [13] */
#define R24_DAC_DRTZ_IC_1 0x0 /* 00b [15:14] */
#define R24_DAC_OFFSET_IC_1_MASK 0x3
#define R24_DAC_OFFSET_IC_1_SHIFT 0
#define R24_DAC_DITHER_ENABLE_IC_1_MASK 0x4
#define R24_DAC_DITHER_ENABLE_IC_1_SHIFT 2
#define R24_PCTRL_IC_1_MASK 0x18
#define R24_PCTRL_IC_1_SHIFT 3
#define R24_DAC_PCLK_SEL_IC_1_MASK 0x60
#define R24_DAC_PCLK_SEL_IC_1_SHIFT 5
#define R24_DAC_I_SEL_IC_1_MASK 0x780
#define R24_DAC_I_SEL_IC_1_SHIFT 7
#define R24_DAC_CLK_SEL_IC_1_MASK 0x1800
#define R24_DAC_CLK_SEL_IC_1_SHIFT 11
#define R24_DAC_CHOP_ENB_IC_1_MASK 0x2000
#define R24_DAC_CHOP_ENB_IC_1_SHIFT 13
#define R24_DAC_DRTZ_IC_1_MASK 0xC000
#define R24_DAC_DRTZ_IC_1_SHIFT 14
#endif
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- *
* This software is distributed in the hope that it will be useful,
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* This software may only be used to program products purchased from
* Analog Devices for incorporation by you into audio products that
* are intended for resale to audio product end users. This software
* may not be distributed whole or in any part to third parties.
*
* Copyright © 2026 Analog Devices, Inc. All rights reserved.
*/
-->
<!--NetList XML file-->
<NetList>
<IC name=" IC 1 " type="DSPSigma100">
<Schematic>
<Algorithm name="ICSigma100In1" friendlyname="170x\140x input " cell="Input1 " location="{X=-15, Y=104} " Growth="10 " GrowthB="0 " FS="48000">
<Link pin="O_C33_A0_P3_out" dir="out" link="Link0" />
<Link pin="O_C33_A0_P4_out" dir="out" link="Link1" />
<Link pin="O_C33_A0_P5_out" dir="out" link="Link2" />
<Link pin="O_C33_A0_P6_out" dir="out" link="Link3" />
</Algorithm>
<Algorithm name="BeepAlg1" friendlyname="Beep - variable gain " cell="Beep1 " location="{X=933, Y=138} " Growth="0 " GrowthB="0 " FS="48000">
<Link pin="O_C0_A0_P1_out" dir="out" link="Link15" />
</Algorithm>
<Algorithm name="Gain1940AlgNS1" friendlyname="Gain (no slew) " cell="Si4674 " location="{X=168, Y=5} " Growth="0 " GrowthB="0">
<Link pin="I_C63_A0_P1_in" dir="in" link="Link0" />
<Link pin="O_C63_A0_P2_out" dir="out" link="Link4" />
</Algorithm>
<Algorithm name="Gain1940AlgNS2" friendlyname="Gain (no slew) " cell="Si4674 " location="{X=168, Y=5} " Growth="0 " GrowthB="0">
<Link pin="I_C63_A1_P1_in" dir="in" link="Link1" />
<Link pin="O_C63_A1_P2_out" dir="out" link="Link5" />
</Algorithm>
<Algorithm name="Gain1940AlgNS3" friendlyname="Gain (no slew) " cell="ESP32 " location="{X=166, Y=196} " Growth="0 " GrowthB="0">
<Link pin="I_C75_A0_P1_in" dir="in" link="Link2" />
<Link pin="O_C75_A0_P2_out" dir="out" link="Link6" />
</Algorithm>
<Algorithm name="Gain1940AlgNS4" friendlyname="Gain (no slew) " cell="ESP32 " location="{X=166, Y=196} " Growth="0 " GrowthB="0">
<Link pin="I_C75_A1_P1_in" dir="in" link="Link3" />
<Link pin="O_C75_A1_P2_out" dir="out" link="Link7" />
</Algorithm>
<Algorithm name="ICSigma100Out1" friendlyname="170x\140x output " cell="Output1 " location="{X=1127, Y=55} " Growth="0 " GrowthB="0">
<Link pin="I_C183_A0_P1_in" dir="in" link="Link15" />
</Algorithm>
<Algorithm name="StereoMixerAlg1" friendlyname="Stereo Mixer " cell="St Mixer1 " location="{X=381, Y=115} " Growth="2 " GrowthB="0">
<Link pin="I_C84_A0_P3_in" dir="in" link="Link4" />
<Link pin="I_C84_A0_P4_in" dir="in" link="Link5" />
<Link pin="I_C84_A0_P5_in" dir="in" link="Link6" />
<Link pin="I_C84_A0_P6_in" dir="in" link="Link7" />
<Link pin="O_C84_A0_P1_out" dir="out" link="Link8" />
<Link pin="O_C84_A0_P2_out" dir="out" link="Link9" />
</Algorithm>
<Algorithm name="PEQ1Chan1" friendlyname="PEQ1Chan - Double Precision " cell="Param EQ1 " location="{X=502, Y=156} " Growth="2 " GrowthB="0">
<Link pin="I_C125_A0_P1_in" dir="in" link="Link8" />
<Link pin="I_C125_A0_P3_in" dir="in" link="Link9" />
<Link pin="O_C125_A0_P2_out" dir="out" link="Link10" />
<Link pin="O_C125_A0_P4_out" dir="out" link="Link11" />
</Algorithm>
<Algorithm name="Gain1940AlgNS5" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
<Link pin="I_C138_A0_P1_in" dir="in" link="Link10" />
<Link pin="O_C138_A0_P2_out" dir="out" link="Link12" />
</Algorithm>
<Algorithm name="Gain1940AlgNS6" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
<Link pin="I_C138_A1_P1_in" dir="in" link="Link11" />
<Link pin="O_C138_A1_P2_out" dir="out" link="Link13" />
</Algorithm>
<Algorithm name="limiterAlg1" friendlyname="Limiter with Indicator " cell="Limiter 1 " location="{X=761, Y=40} " Growth="0 " GrowthB="0">
<Link pin="I_C169_A0_P1_in" dir="in" link="Link12" />
</Algorithm>
<Algorithm name="limiterAlg2" friendlyname="Limiter with Indicator " cell="Limiter 2 " location="{X=765, Y=189} " Growth="0 " GrowthB="0">
<Link pin="I_C174_A0_P1_in" dir="in" link="Link13" />
<Link pin="O_C174_A0_P2_out" dir="out" link="Link14" />
</Algorithm>
<Algorithm name="ICSigma100Out2" friendlyname="170x\140x output " cell="Output2 " location="{X=890, Y=227} " Growth="0 " GrowthB="0">
<Link pin="I_C185_A0_P1_in" dir="in" link="Link14" />
</Algorithm>
</Schematic>
</IC>
</NetList>
@@ -0,0 +1,5 @@
4,
5122,
4098,
26,
4,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
/*
* File: defines.h
*
* Created: Thursday, August 6, 2026 1:57:25 PM
* Description: DigiRadio IC default download data definitions.
*
* This software is distributed in the hope that it will be useful,
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* This software may only be used to program products purchased from
* Analog Devices for incorporation by you into audio products that
* are intended for resale to audio product end users. This software
* may not be distributed whole or in any part to third parties.
*
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
*/
#ifndef __DEFINES_H__
#define __DEFINES_H__
#define BufferSize_IC_1 9254
#define NumTransactions_IC_1 5
#endif
+2
View File
@@ -9,6 +9,7 @@ idf_component_register(
"src/WifiSsid.cpp"
"src/WifiCredentials.cpp"
"src/WifiProvisionJson.cpp"
"src/WifiScanJson.cpp"
"src/EmbeddedBlobReader.cpp"
"src/TunerJson.cpp"
"src/FrequencyKHz.cpp"
@@ -38,6 +39,7 @@ idf_component_register(
"src/RegisterWrite.cpp"
"src/DspProgramBlob.cpp"
"src/OtaAppDescriptor.cpp"
"src/WebRadioJson.cpp"
INCLUDE_DIRS "include"
)
@@ -90,4 +90,18 @@ namespace core {
[[nodiscard]] std::expected<EnhanceLevel, ParseError> parseEnhanceLevelJson(
std::string_view json);
/**
* @brief parseBeepEnabledJson — parse POST body {"enabled":true|false}.
*
* @dname parseBeepEnabledJson
* @param json Untrusted request body.
* @return Parsed enabled flag, or ParseError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<bool, ParseError> parseBeepEnabledJson(
std::string_view json);
} // namespace core
@@ -14,6 +14,8 @@
#include "core/Bt1035At.hpp"
#include "core/Bt1035PairedDevice.hpp"
#include "core/Bt1035ScannedDevice.hpp"
#include "core/BtSpeakerTarget.hpp"
#include <cstdint>
#include <string>
@@ -39,6 +41,22 @@ struct BluetoothStatus {
std::uint8_t autoReconnect; ///< Power-on reconnect count (0 = off).
};
/**
* @brief BluetoothConnectRequest — POST /api/bluetooth/connect body.
*
* @dname BluetoothConnectRequest
* @return n/a (type)
* @pubstate Parsed by parseBluetoothConnectJson().
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct BluetoothConnectRequest {
std::string mac; ///< 12-char module MAC.
std::string name; ///< Optional label stored when save is true.
bool save; ///< Persist as default speaker on success.
};
/**
* @brief serializeBluetoothStatusJson — serialise BluetoothStatus for HTTP.
*
@@ -94,4 +112,74 @@ struct BluetoothStatus {
[[nodiscard]] std::expected<std::uint8_t, ParseError>
parseBluetoothAutoReconnectJson(std::string_view json);
/**
* @brief serializeBluetoothScanJson — serialise scan result list.
*
* @dname serializeBluetoothScanJson
* @param devices Parsed +SCAN entries.
* @return JSON object with a devices array.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string serializeBluetoothScanJson(
const std::vector<Bt1035ScannedDevice>& devices);
/**
* @brief parseBluetoothConnectJson — validate POST /api/bluetooth/connect.
*
* @dname parseBluetoothConnectJson
* @param json Request body with 12-char \c mac field.
* @return Normalised uppercase MAC on success, or ParseError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<std::string, ParseError>
parseBluetoothConnectJson(std::string_view json);
/**
* @brief parseBluetoothConnectRequest — mac, optional name, save flag.
*
* @dname parseBluetoothConnectRequest
* @param json POST /api/bluetooth/connect body.
* @return Parsed connect request (mac may be empty if invalid).
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] BluetoothConnectRequest parseBluetoothConnectRequest(
std::string_view json);
/**
* @brief serializeBluetoothSpeakerJson — saved default speaker for HTTP.
*
* @dname serializeBluetoothSpeakerJson
* @param target Stored speaker, or nullptr when not configured.
* @return JSON object with configured flag.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string serializeBluetoothSpeakerJson(
const BtSpeakerTarget* target);
/**
* @brief parseBluetoothSpeakerJson — validate POST /api/bluetooth/speaker.
*
* @dname parseBluetoothSpeakerJson
* @param json Request body with mac and optional name.
* @return BtSpeakerTarget on success, or ParseError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<BtSpeakerTarget, ParseError>
parseBluetoothSpeakerJson(std::string_view json);
} // namespace core
@@ -13,6 +13,7 @@
#pragma once
#include "core/Bt1035PairedDevice.hpp"
#include "core/Bt1035ScannedDevice.hpp"
#include "core/ParseError.hpp"
#include <array>
@@ -36,12 +37,14 @@ namespace core {
* @date 2026-07-06
*/
enum class Bt1035AtCommand {
Reset, ///< AT+RESET — software reset (best-effort, not gated on OK).
Ping, ///< AT — link check.
I2sMode, ///< AT+AUXCFG=3 — I2S input from ADAU1701 (mandatory).
I2sSlave48k32, ///< AT+I2SCFG=67 — I2S slave 48 kHz 32-bit (§5.1.4).
I2sSlave48k24, ///< AT+I2SCFG=35 — I2S slave 48 kHz 24-bit (§5.1.4).
PairDiscoverable, ///< AT+PAIR=1 — enter BR/EDR/BLE discoverable mode.
PairHidden, ///< AT+PAIR=0 — leave discoverable mode.
A2dpStat, ///< AT+A2DPSTAT — read A2DP link state.
A2dpEncoder, ///< AT+A2DPENC — read negotiated A2DP codec (§5.3.5).
A2dpDisconnect, ///< AT+A2DPDISC — release current A2DP connection.
QueryName, ///< AT+NAME — read BR/EDR local name (+NAME=).
QueryAutoConn, ///< AT+AUTOCONN — read power-on auto-reconnect count.
@@ -67,6 +70,24 @@ enum class Bt1035A2dpState : std::uint8_t {
Paused = 5,
};
/**
* @brief Bt1035A2dpCodec — negotiated codec from +A2DPENC=Param (§5.3.5).
*
* @dname Bt1035A2dpCodec
* @return n/a (type)
* @pubstate BT1035 chip variant codes (BT806 differs; board uses BT1035).
*
* @author Michele Bigi
* @date 2026-08-07
*/
enum class Bt1035A2dpCodec : std::uint8_t {
Sbc = 1,
Aptx = 2,
AptxHd = 3,
AptxLl = 4,
AptxAdaptive = 5,
};
/**
* @brief Bt1035AtResponseKind — parsed module reply class.
*
@@ -86,8 +107,14 @@ enum class Bt1035AtResponseKind {
/** Number of commands in bootInitSequence(). */
inline constexpr std::size_t kBt1035BootInitCommandCount = 3U;
/** Feasycom programming guide §5.1.4: I2S slave, 48 kHz, 32-bit. */
inline constexpr std::uint8_t kBt1035I2sSlave48k32Param = 67U;
/**
* Feasycom programming guide §5.1.4: I2S slave, 48 kHz, 24-bit — matches the
* ADAU1701 serial output word length (SigmaStudio Hardware Configuration).
* Bit field: BIT[0]=enable(1), BIT[1]=slave(1), BIT[2]=FS 48kHz(0),
* BIT[3]=left justified(0), BIT[4]=data 1-bit delay(0), BIT[5:6]=24-bit(01)
* -> 1 + 2 + 32 = 35.
*/
inline constexpr std::uint8_t kBt1035I2sSlave48k24Param = 35U;
/**
* @brief buildBt1035AtLine — serialise a command with CRLF terminator.
@@ -106,7 +133,7 @@ inline constexpr std::uint8_t kBt1035I2sSlave48k32Param = 67U;
* @brief bootInitSequence — mandatory bring-up commands in order.
*
* @dname bootInitSequence
* @return Ping, I2sMode (AUXCFG=3), I2sSlave48k32 (I2SCFG=67).
* @return Ping, I2sMode (AUXCFG=3), I2sSlave48k24 (I2SCFG=35).
* @pubstate none
*
* @author Michele Bigi
@@ -156,6 +183,33 @@ parseBt1035A2dpStatResponse(std::string_view response);
*/
[[nodiscard]] const char* a2dpStateToken(Bt1035A2dpState state) noexcept;
/**
* @brief parseBt1035A2dpEncoderResponse — extract negotiated codec.
*
* @dname parseBt1035A2dpEncoderResponse
* @param response Full module reply (may include +A2DPENC and OK lines).
* @return Bt1035A2dpCodec on success, or ParseError::MissingField.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<Bt1035A2dpCodec, ParseError>
parseBt1035A2dpEncoderResponse(std::string_view response);
/**
* @brief a2dpCodecToken — serialise negotiated codec for logs/JSON.
*
* @dname a2dpCodecToken
* @param codec Parsed negotiated codec.
* @return Short stable string (e.g. "sbc").
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] const char* a2dpCodecToken(Bt1035A2dpCodec codec) noexcept;
/**
* @brief buildBt1035SetAutoConnLine — AT+AUTOCONN with reconnect count.
*
@@ -226,4 +280,149 @@ parseBt1035AutoConnResponse(std::string_view response);
[[nodiscard]] std::expected<std::vector<Bt1035PairedDevice>, ParseError>
parseBt1035PairedListResponse(std::string_view response);
/**
* @brief buildBt1035StartScanLine — AT+SCAN for nearby BR/EDR devices.
*
* @dname buildBt1035StartScanLine
* @param scanType AT+SCAN Param1; clamped to 1 or 2 (2 only when
* passed exactly, otherwise 1).
* @param scanSeconds BR/EDR inquiry time 1255 (Feasycom Param2).
* @return Full AT line including CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035StartScanLine(std::uint8_t scanType,
std::uint8_t scanSeconds = 0U);
/**
* @brief buildBt1035StopScanLine — stop an active device scan.
*
* @dname buildBt1035StopScanLine
* @return AT+SCAN=0 line with CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035StopScanLine();
/**
* @brief buildBt1035EnablePrintLine — ensure +SCAN events are reported.
*
* @dname buildBt1035EnablePrintLine
* @return AT+PRINT=1 line with CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035EnablePrintLine();
/**
* @brief buildBt1035DisconnectAllLine — release all BT connections.
*
* @dname buildBt1035DisconnectAllLine
* @return AT+DSCA line with CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035DisconnectAllLine();
/**
* @brief buildBt1035DisableAutoLinkLine — disable auto-link on power-up.
*
* @dname buildBt1035DisableAutoLinkLine
* @return AT+LINKCFG=0,0 line with CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035DisableAutoLinkLine();
/**
* @brief buildBt1035ClearPairedListLine — erase all paired records.
*
* @dname buildBt1035ClearPairedListLine
* @return AT+PLIST=0 line with CRLF.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035ClearPairedListLine();
/**
* @brief buildBt1035A2dpConnectLine — connect A2DP to a remote MAC.
*
* @dname buildBt1035A2dpConnectLine
* @param mac 12-char ASCII MAC address (no colons).
* @return AT+A2DPCONN line, or empty when mac invalid.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035A2dpConnectLine(std::string_view mac);
/**
* @brief buildBt1035A2dpAudioLine — start/stop A2DP audio (§5.3.6).
*
* @dname buildBt1035A2dpAudioLine
* @param establish true → AT+A2DPAUDIO=1, false → release audio.
* @return Full AT line including CRLF.
* @pubstate none
*
* Feasycom BT1035: link state Connected (3) is not enough for I2S→sink
* playback; audio starts after A2DPAUDIO=1 and +A2DPSTAT=4 (Streaming).
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string buildBt1035A2dpAudioLine(bool establish);
/**
* @brief isValidBt1035Mac — true for 12 hex digits.
*
* @dname isValidBt1035Mac
* @param mac Candidate MAC from scan or UI.
* @return true when exactly 12 hexadecimal characters.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool isValidBt1035Mac(std::string_view mac) noexcept;
/**
* @brief normalizeBt1035Mac — strip separators and uppercase MAC.
*
* @dname normalizeBt1035Mac
* @param mac MAC with optional colons or dashes.
* @return 12 uppercase hex digits, or empty when invalid length/chars.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string normalizeBt1035Mac(std::string_view mac);
/**
* @brief parseBt1035ScanResponse — parse +SCAN= inquiry results.
*
* @dname parseBt1035ScanResponse
* @param response Full UART payload until +SCAN=E.
* @return Discovered devices, or ParseError when payload malformed.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<std::vector<Bt1035ScannedDevice>, ParseError>
parseBt1035ScanResponse(std::string_view response);
} // namespace core
@@ -0,0 +1,39 @@
/**
* @file Bt1035ScannedDevice.hpp
* @brief One entry from FSC-BT1035 AT+SCAN (+SCAN= lines).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#pragma once
#include <cstdint>
#include <string>
namespace core {
/**
* @brief Bt1035ScannedDevice — nearby remote from +SCAN= events.
*
* @dname Bt1035ScannedDevice
* @return n/a (type)
* @pubstate Plain DTO parsed in the pure core.
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct Bt1035ScannedDevice {
std::uint8_t index; ///< Scan result index from the module.
std::uint8_t addressType; ///< 0/1 LE, 2 BR/EDR (Feasycom manual).
std::string mac; ///< 12-char ASCII MAC without separators.
std::int16_t rssiDbm; ///< RSSI in dBm (-127..-1).
std::string name; ///< Remote friendly name when advertised.
std::string deviceClass; ///< Class of device hex string when present.
};
} // namespace core
@@ -0,0 +1,34 @@
/**
* @file BtSpeakerTarget.hpp
* @brief Saved A2DP speaker target (MAC + optional friendly name).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#pragma once
#include <string>
namespace core {
/**
* @brief BtSpeakerTarget — default BR/EDR speaker for AT+A2DPCONN.
*
* @dname BtSpeakerTarget
* @return n/a (type)
* @pubstate Persisted in NVS via ISecureStore; MAC is 12-char ASCII hex.
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct BtSpeakerTarget {
std::string mac; ///< 12-char BR/EDR MAC (uppercase, no separators).
std::string name; ///< Optional label shown in the web UI.
};
} // namespace core
@@ -27,7 +27,7 @@ namespace core {
struct CompanionChipStatus {
bool si4684Ready; ///< Si4684 HOST_LOAD completed.
bool adau1701Ready; ///< ADAU1701 SigmaStudio download completed.
bool bt1035Ready; ///< BT1035 I2S init (AUXCFG=3 + I2SCFG=67) completed.
bool bt1035Ready; ///< BT1035 I2S init (AUXCFG=3 + I2SCFG=35) completed.
};
} // namespace core
@@ -130,6 +130,21 @@ public:
*/
[[nodiscard]] virtual std::expected<void, DspError> setEqBand(
EqBandIndex band, GainDb gain, FrequencyHz center, float q) = 0;
/**
* @brief setBeepEnabled — gate the SigmaStudio Beep1 tone generator.
*
* @dname setBeepEnabled
* @param enabled true unmutes Beep1, false mutes it.
* @return Ok on success, or DspError.
* @pubstate writes ADAU1701 parameter RAM via safeload. Not part of
* AudioProfile — live-only, never persisted.
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] virtual std::expected<void, DspError> setBeepEnabled(
bool enabled) = 0;
};
} // namespace core
@@ -18,6 +18,7 @@
#pragma once
#include "core/StoreError.hpp"
#include "core/BtSpeakerTarget.hpp"
#include "core/WifiCredentials.hpp"
#include <cstdint>
@@ -210,6 +211,97 @@ public:
*/
[[nodiscard]] virtual std::expected<void, StoreError>
clearLastPresetIndex() = 0;
/**
* @brief hasBtSpeakerTarget — check whether a default speaker MAC is stored.
*
* @dname hasBtSpeakerTarget
* @return true when loadBtSpeakerTarget would succeed.
* @pubstate reads backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] virtual bool hasBtSpeakerTarget() const = 0;
/**
* @brief saveBtSpeakerTarget — persist default A2DP speaker MAC/name.
*
* @dname saveBtSpeakerTarget
* @param target Validated MAC and optional display name.
* @return Ok on success, or StoreError::IoFailed.
* @pubstate writes backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] virtual std::expected<void, StoreError>
saveBtSpeakerTarget(const BtSpeakerTarget& target) = 0;
/**
* @brief loadBtSpeakerTarget — read stored default speaker.
*
* @dname loadBtSpeakerTarget
* @return BtSpeakerTarget on success, or StoreError.
* @pubstate reads backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] virtual std::expected<BtSpeakerTarget, StoreError>
loadBtSpeakerTarget() const = 0;
/**
* @brief clearBtSpeakerTarget — erase stored default speaker.
*
* @dname clearBtSpeakerTarget
* @return Ok on success, or StoreError::IoFailed.
* @pubstate clears backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] virtual std::expected<void, StoreError>
clearBtSpeakerTarget() = 0;
/**
* @brief hasWebRadioConfig — check whether a streaming config is stored.
*
* @dname hasWebRadioConfig
* @return true when loadWebRadioConfigJson would succeed.
* @pubstate reads backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] virtual bool hasWebRadioConfig() const = 0;
/**
* @brief saveWebRadioConfigJson — persist serialised streaming config.
*
* @dname saveWebRadioConfigJson
* @param json Output of core::serializeWebRadioConfigJson().
* @return Ok on success, or StoreError::IoFailed.
* @pubstate writes backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] virtual std::expected<void, StoreError>
saveWebRadioConfigJson(std::string_view json) = 0;
/**
* @brief loadWebRadioConfigJson — read stored streaming config.
*
* @dname loadWebRadioConfigJson
* @return JSON blob on success, or StoreError.
* @pubstate reads backing storage via implementation.
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] virtual std::expected<std::string, StoreError>
loadWebRadioConfigJson() const = 0;
};
} // namespace core
@@ -33,11 +33,11 @@ struct MixerState {
GainDb si4684Right; ///< Si4674 volume control, right channel.
GainDb esp32Left; ///< ESP32 volume control, left channel.
GainDb esp32Right; ///< ESP32 volume control, right channel.
GainDb mixLeft; ///< St Mixer1 left blend level.
GainDb mixRight; ///< St Mixer1 right blend level.
GainDb mixLeft; ///< St Mixer1 ST0 (Si4684) input level.
GainDb mixRight; ///< St Mixer1 ST1 (ESP32) input level.
/**
* @brief factoryDefault — unity gains on all paths (radio-first mix).
* @brief factoryDefault — unity gains on all paths.
*
* @dname factoryDefault
* @return MixerState with 0 dB on every control.
@@ -47,6 +47,18 @@ struct MixerState {
* @date 2026-07-06
*/
[[nodiscard]] static MixerState factoryDefault() noexcept;
/**
* @brief radioFirst — Si4684 path open, ESP32 path muted.
*
* @dname radioFirst
* @return MixerState for FM/DAB tuner audio to the Bose I2S chain.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] static MixerState radioFirst() noexcept;
};
} // namespace core
@@ -58,6 +58,43 @@ struct TunerPlayRequest {
std::uint32_t componentId; ///< Audio component within the service.
};
/**
* @brief TunerScanRequest — parsed POST /api/tuner/scan body.
*
* @dname TunerScanRequest
* @return n/a (type)
* @pubstate Plain DTO filled by parseTunerScanJson at the HTTP boundary.
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct TunerScanRequest {
TunerBand band; ///< FM seek scan or DAB ensemble scan.
std::uint8_t maxSteps; ///< Max FM seeks or DAB ensemble indices to try.
std::string nameFilter; ///< Optional case-insensitive substring (PS/DAB label).
};
/**
* @brief TunerScanResult — outcome of an automatic station search.
*
* @dname TunerScanResult
* @return n/a (type)
* @pubstate Built by TunerService::scanForStation().
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct TunerScanResult {
bool found; ///< True when a station matched criteria.
TunerBand band; ///< Band that was scanned.
std::uint16_t stepsTried; ///< Seek or ensemble attempts performed.
std::optional<FrequencyKHz> fmFrequency; ///< FM centre when found on FM.
std::optional<std::uint8_t> dabFreqIndex; ///< Ensemble index when found on DAB.
std::optional<std::uint32_t> dabServiceId; ///< Started DAB service id.
std::optional<std::uint32_t> dabComponentId; ///< Started DAB component id.
std::optional<BroadcastLabel> stationName; ///< RDS PS or DAB label when known.
};
/**
* @brief serializeTunerStatusJson — serialise a tuner snapshot for GET status.
*
@@ -143,4 +180,31 @@ struct TunerPlayRequest {
[[nodiscard]] std::expected<SeekDirection, ParseError> parseTunerSeekJson(
std::string_view json);
/**
* @brief parseTunerScanJson — validate POST /api/tuner/scan body.
*
* @dname parseTunerScanJson
* @param json Untrusted request body from the HTTP handler.
* @return TunerScanRequest on success, or a ParseError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<TunerScanRequest, ParseError> parseTunerScanJson(
std::string_view json);
/**
* @brief serializeTunerScanJson — serialise automatic scan outcome.
*
* @dname serializeTunerScanJson
* @param result Scan result from TunerService::scanForStation().
* @return JSON object for the HTTP response body.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string serializeTunerScanJson(const TunerScanResult& result);
} // namespace core
@@ -60,6 +60,7 @@ struct TunerStatus {
std::optional<std::uint32_t> dabPlayingServiceId; ///< Last played DAB service.
std::optional<std::uint32_t> dabPlayingComponentId; ///< Last played DAB component.
std::optional<FrequencyKHz> fmFrequency; ///< Tuned FM centre frequency.
std::optional<FrequencyKHz> fmChipReadFrequency; ///< FM_RSQ READFREQ (may lag commanded).
std::optional<std::int8_t> fmRssiDbuV; ///< FM RSSI in dBµV.
std::optional<std::int8_t> fmSnrDb; ///< FM SNR in dB.
std::optional<bool> fmStereo; ///< FM stereo pilot detected.
@@ -0,0 +1,52 @@
/**
* @file WebRadioConfig.hpp
* @brief Internet radio stream configuration (URL + enable flag).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-08
*/
#pragma once
#include <string>
namespace core {
/**
* @brief WebRadioConfig — HTTP MP3 stream URL and on/off state.
*
* @dname WebRadioConfig
* @return n/a (type)
* @pubstate Plain value type; url is a plaintext http:// URL (no secrets).
*
* @author Michele Bigi
* @date 2026-08-08
*/
struct WebRadioConfig {
bool enabled = false; ///< Whether the streaming task should be playing.
std::string url; ///< Plaintext http:// MP3 stream URL.
/**
* @brief factoryDefault — disabled, with a known-good sample URL.
*
* @dname factoryDefault
* @return WebRadioConfig with enabled=false and a placeholder URL.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] static WebRadioConfig factoryDefault()
{
return WebRadioConfig{
.enabled = false,
.url = "http://edge.radiomontecarlo.net/RMC.mp3",
};
}
};
} // namespace core
@@ -0,0 +1,66 @@
/**
* @file WebRadioJson.hpp
* @brief JSON parse/serialise for the web radio streaming API (pure core).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-08
*/
#pragma once
#include "core/ParseError.hpp"
#include "core/WebRadioConfig.hpp"
#include <expected>
#include <string>
#include <string_view>
namespace core {
/**
* @brief parseWebRadioConfigJson — parse POST /api/streaming body.
*
* @dname parseWebRadioConfigJson
* @param json Untrusted request body: {"enabled":bool,"url":string}.
* @return WebRadioConfig on success, or ParseError. Rejects URLs that
* are missing, longer than 200 bytes, or not http://.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] std::expected<WebRadioConfig, ParseError> parseWebRadioConfigJson(
std::string_view json);
/**
* @brief serializeWebRadioConfigJson — serialise for GET /api/streaming.
*
* @dname serializeWebRadioConfigJson
* @param config Current streaming configuration.
* @return JSON object string for the HTTP response body.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] std::string serializeWebRadioConfigJson(
const WebRadioConfig& config);
/**
* @brief serializeWebRadioErrorJson — error response for streaming routes.
*
* @dname serializeWebRadioErrorJson
* @param reason Safe token (never includes secrets).
* @return JSON object with status and reason fields.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] std::string serializeWebRadioErrorJson(std::string_view reason);
} // namespace core
@@ -0,0 +1,50 @@
/**
* @file WifiScanJson.hpp
* @brief JSON serialisation for Wi-Fi scan REST API (pure core).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#pragma once
#include "core/WifiScannedNetwork.hpp"
#include <string>
#include <string_view>
#include <vector>
namespace core {
/**
* @brief serializeWifiScanJson — serialise scan result list.
*
* @dname serializeWifiScanJson
* @param networks Deduped AP records sorted by signal strength.
* @return JSON object with a networks array.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string serializeWifiScanJson(
const std::vector<WifiScannedNetwork>& networks);
/**
* @brief serializeWifiScanErrorJson — serialise a Wi-Fi scan API error.
*
* @dname serializeWifiScanErrorJson
* @param reason Short machine-readable cause.
* @return JSON error object.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string serializeWifiScanErrorJson(std::string_view reason);
} // namespace core
@@ -0,0 +1,37 @@
/**
* @file WifiScannedNetwork.hpp
* @brief One entry from a nearby Wi-Fi access-point scan.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#pragma once
#include <cstdint>
#include <string>
namespace core {
/**
* @brief WifiScannedNetwork — nearby AP from a Wi-Fi scan.
*
* @dname WifiScannedNetwork
* @return n/a (type)
* @pubstate Plain DTO assembled by the net shell after esp_wifi scan.
*
* @author Michele Bigi
* @date 2026-08-05
*/
struct WifiScannedNetwork {
std::string ssid; ///< Broadcast SSID (may be empty when hidden).
std::int16_t rssiDbm; ///< RSSI in dBm.
std::string auth; ///< Short auth token (open, wpa2, wpa3, …).
std::uint8_t channel; ///< Primary channel number.
};
} // namespace core
@@ -54,6 +54,25 @@ namespace {
return end != json.data() + valueStart;
}
[[nodiscard]] bool extractJsonBool(std::string_view json,
std::string_view key,
bool& out)
{
const std::string trueNeedle =
std::string("\"") + std::string(key) + "\":true";
const std::string falseNeedle =
std::string("\"") + std::string(key) + "\":false";
if (json.find(trueNeedle) != std::string_view::npos) {
out = true;
return true;
}
if (json.find(falseNeedle) != std::string_view::npos) {
out = false;
return true;
}
return false;
}
[[nodiscard]] std::expected<GainDb, ParseError> parseGainField(
std::string_view json, std::string_view key)
{
@@ -277,4 +296,17 @@ std::expected<EnhanceLevel, ParseError> parseEnhanceLevelJson(
return EnhanceLevel::tryFromLevel(level);
}
std::expected<bool, ParseError> parseBeepEnabledJson(std::string_view json)
{
if (json.find('{') == std::string_view::npos) {
return std::unexpected(ParseError::InvalidJson);
}
bool enabled = false;
if (!extractJsonBool(json, "enabled", enabled)) {
return std::unexpected(ParseError::MissingField);
}
return enabled;
}
} // namespace core
@@ -14,6 +14,7 @@
#include "core/BluetoothJson.hpp"
#include <cstdlib>
#include <cctype>
#include <sstream>
namespace core {
@@ -95,4 +96,112 @@ parseBluetoothAutoReconnectJson(std::string_view json)
return static_cast<std::uint8_t>(raw);
}
std::string serializeBluetoothScanJson(
const std::vector<Bt1035ScannedDevice>& devices)
{
std::ostringstream out;
out << "{\"devices\":[";
for (std::size_t i = 0; i < devices.size(); ++i) {
if (i > 0U) {
out << ',';
}
const Bt1035ScannedDevice& device = devices[i];
out << "{\"index\":" << static_cast<unsigned>(device.index)
<< ",\"mac\":";
appendJsonString(out, device.mac);
out << ",\"name\":";
appendJsonString(out, device.name);
out << ",\"rssi_dbm\":" << device.rssiDbm << "}";
}
out << "]}";
return out.str();
}
std::expected<std::string, ParseError>
parseBluetoothConnectJson(std::string_view json)
{
if (json.find('{') == std::string_view::npos) {
return std::unexpected(ParseError::InvalidJson);
}
const std::string needle = "\"mac\":\"";
const std::size_t start = json.find(needle);
if (start == std::string_view::npos) {
return std::unexpected(ParseError::MissingField);
}
const std::size_t valueStart = start + needle.size();
const std::size_t valueEnd = json.find('"', valueStart);
if (valueEnd == std::string_view::npos) {
return std::unexpected(ParseError::InvalidJson);
}
const std::string_view mac = json.substr(valueStart, valueEnd - valueStart);
if (!isValidBt1035Mac(mac)) {
return std::unexpected(ParseError::InvalidJson);
}
std::string normalized;
normalized.reserve(12U);
for (const char ch : mac) {
normalized.push_back(
static_cast<char>(std::toupper(static_cast<unsigned char>(ch))));
}
return normalized;
}
BluetoothConnectRequest parseBluetoothConnectRequest(std::string_view json)
{
BluetoothConnectRequest request{
.mac = {},
.name = {},
.save = false,
};
if (auto mac = parseBluetoothConnectJson(json); mac) {
request.mac = std::move(*mac);
}
const std::string nameNeedle = "\"name\":\"";
const std::size_t nameStart = json.find(nameNeedle);
if (nameStart != std::string_view::npos) {
const std::size_t valueStart = nameStart + nameNeedle.size();
const std::size_t valueEnd = json.find('"', valueStart);
if (valueEnd != std::string_view::npos) {
request.name.assign(json.substr(valueStart, valueEnd - valueStart));
}
}
request.save = json.find("\"save\":true") != std::string_view::npos
|| json.find("\"save\": true") != std::string_view::npos;
return request;
}
std::string serializeBluetoothSpeakerJson(const BtSpeakerTarget* target)
{
if (target == nullptr) {
return "{\"configured\":false}";
}
std::ostringstream out;
out << "{\"configured\":true,\"mac\":";
appendJsonString(out, target->mac);
out << ",\"name\":";
appendJsonString(out, target->name);
out << '}';
return out.str();
}
std::expected<BtSpeakerTarget, ParseError>
parseBluetoothSpeakerJson(std::string_view json)
{
const auto mac = parseBluetoothConnectJson(json);
if (!mac) {
return std::unexpected(mac.error());
}
BtSpeakerTarget target{.mac = *mac, .name = {}};
const std::string nameNeedle = "\"name\":\"";
const std::size_t nameStart = json.find(nameNeedle);
if (nameStart != std::string_view::npos) {
const std::size_t valueStart = nameStart + nameNeedle.size();
const std::size_t valueEnd = json.find('"', valueStart);
if (valueEnd != std::string_view::npos) {
target.name.assign(json.substr(valueStart, valueEnd - valueStart));
}
}
return target;
}
} // namespace core
+220 -3
View File
@@ -13,6 +13,7 @@
#include "core/Bt1035At.hpp"
#include <cctype>
#include <cstdlib>
#include <vector>
@@ -99,18 +100,22 @@ namespace {
std::string buildBt1035AtLine(Bt1035AtCommand command)
{
switch (command) {
case Bt1035AtCommand::Reset:
return "AT+RESET\r\n";
case Bt1035AtCommand::Ping:
return "AT\r\n";
case Bt1035AtCommand::I2sMode:
return "AT+AUXCFG=3\r\n";
case Bt1035AtCommand::I2sSlave48k32:
return "AT+I2SCFG=67\r\n";
case Bt1035AtCommand::I2sSlave48k24:
return "AT+I2SCFG=35\r\n";
case Bt1035AtCommand::PairDiscoverable:
return "AT+PAIR=1\r\n";
case Bt1035AtCommand::PairHidden:
return "AT+PAIR=0\r\n";
case Bt1035AtCommand::A2dpStat:
return "AT+A2DPSTAT\r\n";
case Bt1035AtCommand::A2dpEncoder:
return "AT+A2DPENC\r\n";
case Bt1035AtCommand::A2dpDisconnect:
return "AT+A2DPDISC\r\n";
case Bt1035AtCommand::QueryName:
@@ -145,7 +150,7 @@ std::array<Bt1035AtCommand, kBt1035BootInitCommandCount> bootInitSequence() noex
return std::array<Bt1035AtCommand, kBt1035BootInitCommandCount>{
Bt1035AtCommand::Ping,
Bt1035AtCommand::I2sMode,
Bt1035AtCommand::I2sSlave48k32,
Bt1035AtCommand::I2sSlave48k24,
};
}
@@ -206,6 +211,43 @@ const char* a2dpStateToken(Bt1035A2dpState state) noexcept
return "unknown";
}
std::expected<Bt1035A2dpCodec, ParseError>
parseBt1035A2dpEncoderResponse(std::string_view response)
{
constexpr std::string_view kPrefix = "+A2DPENC=";
const std::size_t pos = response.find(kPrefix);
if (pos == std::string_view::npos) {
return std::unexpected(ParseError::MissingField);
}
const std::size_t valueStart = pos + kPrefix.size();
char* end = nullptr;
const unsigned long raw =
std::strtoul(response.data() + valueStart, &end, 10);
if (end == response.data() + valueStart || raw < 1U || raw > 5U) {
return std::unexpected(ParseError::MissingField);
}
return static_cast<Bt1035A2dpCodec>(raw);
}
const char* a2dpCodecToken(Bt1035A2dpCodec codec) noexcept
{
switch (codec) {
case Bt1035A2dpCodec::Sbc:
return "sbc";
case Bt1035A2dpCodec::Aptx:
return "aptx";
case Bt1035A2dpCodec::AptxHd:
return "aptx-hd";
case Bt1035A2dpCodec::AptxLl:
return "aptx-ll";
case Bt1035A2dpCodec::AptxAdaptive:
return "aptx-adaptive";
}
return "unknown";
}
std::expected<std::string, ParseError>
parseBt1035NameResponse(std::string_view response)
{
@@ -286,4 +328,179 @@ parseBt1035PairedListResponse(std::string_view response)
return devices;
}
std::string buildBt1035StartScanLine(std::uint8_t scanType, std::uint8_t scanSeconds)
{
const std::uint8_t type = (scanType == 2U) ? 2U : 1U;
if (scanSeconds == 0U) {
return "AT+SCAN=" + std::to_string(type) + "\r\n";
}
return "AT+SCAN=" + std::to_string(type) + ","
+ std::to_string(scanSeconds) + "\r\n";
}
std::string buildBt1035StopScanLine()
{
return "AT+SCAN=0\r\n";
}
std::string buildBt1035EnablePrintLine()
{
return "AT+PRINT=1\r\n";
}
std::string buildBt1035DisconnectAllLine()
{
return "AT+DSCA\r\n";
}
std::string buildBt1035DisableAutoLinkLine()
{
return "AT+LINKCFG=0,0\r\n";
}
std::string buildBt1035ClearPairedListLine()
{
return "AT+PLIST=0\r\n";
}
bool isValidBt1035Mac(std::string_view mac) noexcept
{
if (mac.size() != 12U) {
return false;
}
for (const char ch : mac) {
if (!std::isxdigit(static_cast<unsigned char>(ch))) {
return false;
}
}
return true;
}
[[nodiscard]] std::string normalizeBt1035Mac(std::string_view mac)
{
std::string normalized;
normalized.reserve(12U);
for (const char ch : mac) {
if (ch == ':' || ch == '-') {
continue;
}
normalized.push_back(
static_cast<char>(std::toupper(static_cast<unsigned char>(ch))));
}
return normalized;
}
std::string buildBt1035A2dpConnectLine(std::string_view mac)
{
if (!isValidBt1035Mac(mac)) {
return {};
}
return std::string("AT+A2DPCONN=") + std::string(mac) + "\r\n";
}
std::string buildBt1035A2dpAudioLine(bool establish)
{
return establish ? "AT+A2DPAUDIO=1\r\n" : "AT+A2DPAUDIO=0\r\n";
}
std::expected<std::vector<Bt1035ScannedDevice>, ParseError>
parseBt1035ScanResponse(std::string_view response)
{
std::vector<Bt1035ScannedDevice> devices;
std::size_t pos = 0;
while ((pos = response.find("+SCAN", pos)) != std::string_view::npos) {
std::size_t start = pos + 5U;
if (start < response.size() && response[start] == '=') {
++start;
}
while (start < response.size()
&& (response[start] == ' ' || response[start] == '\t')) {
++start;
}
const std::size_t end = response.find_first_of("\r\n", start);
const std::string_view line =
end == std::string_view::npos ? response.substr(start)
: response.substr(start, end - start);
pos = end == std::string_view::npos ? response.size() : end + 1U;
if (line.empty() || line == "E" || line.front() == 'E') {
continue;
}
const std::string lineStr(line);
const std::size_t comma1 = lineStr.find(',');
const std::size_t comma2 =
comma1 == std::string_view::npos ? std::string_view::npos
: lineStr.find(',', comma1 + 1U);
const std::size_t comma3 =
comma2 == std::string_view::npos ? std::string_view::npos
: lineStr.find(',', comma2 + 1U);
const std::size_t comma4 =
comma3 == std::string_view::npos ? std::string_view::npos
: lineStr.find(',', comma3 + 1U);
if (comma4 == std::string_view::npos) {
continue;
}
char* endIdx = nullptr;
const unsigned long index =
std::strtoul(lineStr.c_str(), &endIdx, 10);
if (endIdx == lineStr.c_str() || index == 0U) {
continue;
}
const unsigned long addrType =
std::strtoul(lineStr.c_str() + comma1 + 1U, &endIdx, 10);
const std::string mac =
normalizeBt1035Mac(lineStr.substr(comma2 + 1U, comma3 - comma2 - 1U));
if (!isValidBt1035Mac(mac)) {
continue;
}
const long rssi =
std::strtol(lineStr.c_str() + comma3 + 1U, &endIdx, 10);
const unsigned long nameLen =
std::strtoul(lineStr.c_str() + comma4 + 1U, &endIdx, 10);
const std::size_t afterNameLen =
static_cast<std::size_t>(endIdx - lineStr.c_str());
std::string name;
std::string deviceClass;
if (nameLen > 0U) {
if (afterNameLen >= lineStr.size() || lineStr[afterNameLen] != ',') {
continue;
}
const std::size_t nameStart = afterNameLen + 1U;
if (nameStart + nameLen > lineStr.size()) {
continue;
}
name.assign(lineStr.substr(nameStart, nameLen));
const std::size_t afterName = nameStart + nameLen;
if (afterName < lineStr.size() && lineStr[afterName] == ',') {
deviceClass.assign(lineStr.substr(afterName + 1U));
}
} else if (afterNameLen < lineStr.size()
&& lineStr[afterNameLen] == ',') {
const std::size_t classStart = afterNameLen + 1U;
if (classStart < lineStr.size()) {
if (lineStr[classStart] == ',') {
deviceClass.assign(lineStr.substr(classStart + 1U));
} else {
deviceClass.assign(lineStr.substr(classStart));
}
}
}
Bt1035ScannedDevice entry = {};
entry.index = static_cast<std::uint8_t>(index);
entry.addressType = static_cast<std::uint8_t>(addrType);
entry.mac = mac;
entry.rssiDbm = static_cast<std::int16_t>(rssi);
entry.name = std::move(name);
entry.deviceClass = std::move(deviceClass);
devices.push_back(std::move(entry));
}
return devices;
}
} // namespace core
@@ -28,4 +28,18 @@ MixerState MixerState::factoryDefault() noexcept
};
}
MixerState MixerState::radioFirst() noexcept
{
const GainDb unity = GainDb::zero();
const GainDb muted = *GainDb::tryFromDb(GainDb::kMinDb);
return MixerState{
.si4684Left = unity,
.si4684Right = unity,
.esp32Left = muted,
.esp32Right = muted,
.mixLeft = unity,
.mixRight = muted,
};
}
} // namespace core
@@ -246,4 +246,74 @@ std::expected<SeekDirection, ParseError> parseTunerSeekJson(
return std::unexpected(ParseError::InvalidJson);
}
std::expected<TunerScanRequest, ParseError> parseTunerScanJson(
std::string_view json)
{
if (json.find('{') == std::string_view::npos) {
return std::unexpected(ParseError::InvalidJson);
}
const std::string_view band = extractJsonString(json, "band");
if (band.empty()) {
return std::unexpected(ParseError::MissingField);
}
TunerScanRequest req = {};
unsigned long maxSteps = 0U;
if (band == "fm") {
req.band = TunerBand::Fm;
req.maxSteps = 45U;
} else if (band == "dab") {
req.band = TunerBand::Dab;
req.maxSteps = 38U;
} else {
return std::unexpected(ParseError::InvalidJson);
}
if (extractJsonUint(json, "max_steps", maxSteps)) {
if (maxSteps == 0U || maxSteps > 255U) {
return std::unexpected(ParseError::InvalidJson);
}
req.maxSteps = static_cast<std::uint8_t>(maxSteps);
}
const std::string_view name = extractJsonString(json, "name");
if (!name.empty()) {
req.nameFilter.assign(name.begin(), name.end());
for (char& ch : req.nameFilter) {
if (ch >= 'A' && ch <= 'Z') {
ch = static_cast<char>(ch - 'A' + 'a');
}
}
}
return req;
}
std::string serializeTunerScanJson(const TunerScanResult& result)
{
std::ostringstream out;
out << "{\"status\":\"" << (result.found ? "found" : "not_found") << '"'
<< ",\"band\":\"" << bandToken(result.band) << '"'
<< ",\"steps\":" << result.stepsTried;
if (result.fmFrequency) {
out << ",\"frequency_khz\":" << result.fmFrequency->value();
}
if (result.dabFreqIndex) {
out << ",\"freq_index\":" << static_cast<unsigned>(*result.dabFreqIndex);
}
if (result.dabServiceId) {
out << ",\"service_id\":" << *result.dabServiceId;
}
if (result.dabComponentId) {
out << ",\"component_id\":" << *result.dabComponentId;
}
if (result.stationName) {
out << ",\"station_name\":";
appendJsonString(out, result.stationName->value());
}
out << '}';
return out.str();
}
} // namespace core
@@ -0,0 +1,89 @@
/**
* @file WebRadioJson.cpp
* @brief WebRadioJson implementation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-08
*/
#include "core/WebRadioJson.hpp"
namespace core {
namespace {
constexpr std::size_t kMaxUrlLength = 200U;
constexpr std::string_view kHttpPrefix = "http://";
[[nodiscard]] std::string_view extractJsonString(std::string_view json,
std::string_view key)
{
const std::string needle =
std::string("\"") + std::string(key) + "\":\"";
const std::size_t start = json.find(needle);
if (start == std::string_view::npos) {
return {};
}
const std::size_t valueStart = start + needle.size();
const std::size_t valueEnd = json.find('"', valueStart);
if (valueEnd == std::string_view::npos) {
return {};
}
return json.substr(valueStart, valueEnd - valueStart);
}
[[nodiscard]] bool extractJsonBool(std::string_view json,
std::string_view key, bool& out)
{
const std::string trueNeedle =
std::string("\"") + std::string(key) + "\":true";
const std::string falseNeedle =
std::string("\"") + std::string(key) + "\":false";
if (json.find(trueNeedle) != std::string_view::npos) {
out = true;
return true;
}
if (json.find(falseNeedle) != std::string_view::npos) {
out = false;
return true;
}
return false;
}
} // namespace
std::expected<WebRadioConfig, ParseError> parseWebRadioConfigJson(
std::string_view json)
{
bool enabled = false;
if (!extractJsonBool(json, "enabled", enabled)) {
return std::unexpected(ParseError::MissingField);
}
const std::string_view url = extractJsonString(json, "url");
if (url.empty() || url.size() > kMaxUrlLength
|| url.substr(0, kHttpPrefix.size()) != kHttpPrefix) {
return std::unexpected(ParseError::InvalidJson);
}
return WebRadioConfig{.enabled = enabled, .url = std::string(url)};
}
std::string serializeWebRadioConfigJson(const WebRadioConfig& config)
{
return std::string(R"({"enabled":)") + (config.enabled ? "true" : "false")
+ R"(,"url":")" + config.url + "\"}";
}
std::string serializeWebRadioErrorJson(std::string_view reason)
{
return std::string(R"({"status":"error","reason":")")
+ std::string(reason) + "\"}";
}
} // namespace core
@@ -0,0 +1,73 @@
/**
* @file WifiScanJson.cpp
* @brief Wi-Fi scan JSON serialisation implementation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#include "core/WifiScanJson.hpp"
#include <sstream>
namespace core {
namespace {
/**
* @brief appendJsonString — emit a JSON string literal.
*
* @dname appendJsonString
* @param out Output stream.
* @param text Raw UTF-8 text.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
void appendJsonString(std::ostringstream& out, std::string_view text)
{
out << '"';
for (const char ch : text) {
if (ch == '"' || ch == '\\') {
out << '\\';
}
out << ch;
}
out << '"';
}
} // namespace
std::string serializeWifiScanJson(
const std::vector<WifiScannedNetwork>& networks)
{
std::ostringstream out;
out << "{\"networks\":[";
for (std::size_t i = 0; i < networks.size(); ++i) {
if (i > 0U) {
out << ',';
}
const WifiScannedNetwork& network = networks[i];
out << "{\"ssid\":";
appendJsonString(out, network.ssid);
out << ",\"rssi_dbm\":" << network.rssiDbm << ",\"auth\":";
appendJsonString(out, network.auth);
out << ",\"channel\":" << static_cast<unsigned>(network.channel) << '}';
}
out << "]}";
return out.str();
}
std::string serializeWifiScanErrorJson(std::string_view reason)
{
return std::string("{\"status\":\"error\",\"reason\":\"") + std::string(reason)
+ "\"}";
}
} // namespace core
@@ -21,6 +21,7 @@ add_library(digiradio_core STATIC
"${CORE_SRC_DIR}/WifiSsid.cpp"
"${CORE_SRC_DIR}/WifiCredentials.cpp"
"${CORE_SRC_DIR}/WifiProvisionJson.cpp"
"${CORE_SRC_DIR}/WifiScanJson.cpp"
"${CORE_SRC_DIR}/EmbeddedBlobReader.cpp"
"${CORE_SRC_DIR}/TunerJson.cpp"
"${CORE_SRC_DIR}/FrequencyKHz.cpp"
@@ -50,6 +51,7 @@ add_library(digiradio_core STATIC
"${CORE_SRC_DIR}/RegisterWrite.cpp"
"${CORE_SRC_DIR}/DspProgramBlob.cpp"
"${CORE_SRC_DIR}/OtaAppDescriptor.cpp"
"${CORE_SRC_DIR}/WebRadioJson.cpp"
)
target_include_directories(digiradio_core PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
@@ -75,6 +77,10 @@ add_executable(wifi_provision_test wifi_provision_test.cpp)
target_link_libraries(wifi_provision_test PRIVATE digiradio_core)
add_test(NAME wifi_provision_test COMMAND wifi_provision_test)
add_executable(wifi_scan_test wifi_scan_test.cpp)
target_link_libraries(wifi_scan_test PRIVATE digiradio_core)
add_test(NAME wifi_scan_test COMMAND wifi_scan_test)
add_executable(embedded_blob_reader_test embedded_blob_reader_test.cpp)
target_link_libraries(embedded_blob_reader_test PRIVATE digiradio_core)
add_test(NAME embedded_blob_reader_test COMMAND embedded_blob_reader_test)
@@ -101,6 +107,7 @@ add_test(NAME enhancements_design_test COMMAND enhancements_design_test)
add_executable(station_service_test station_service_test.cpp)
target_include_directories(station_service_test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/host_stubs"
"${CMAKE_CURRENT_SOURCE_DIR}/../../services/station/include"
"${CMAKE_CURRENT_SOURCE_DIR}/../../services/tuner/include"
)
@@ -123,8 +130,13 @@ add_executable(broadcast_metadata_test broadcast_metadata_test.cpp)
target_link_libraries(broadcast_metadata_test PRIVATE digiradio_core)
add_test(NAME broadcast_metadata_test COMMAND broadcast_metadata_test)
add_executable(web_radio_json_test web_radio_json_test.cpp)
target_link_libraries(web_radio_json_test PRIVATE digiradio_core)
add_test(NAME web_radio_json_test COMMAND web_radio_json_test)
add_executable(integration_service_test integration_service_test.cpp)
target_include_directories(integration_service_test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/host_stubs"
"${CMAKE_CURRENT_SOURCE_DIR}/../../services/integration/include"
"${CMAKE_CURRENT_SOURCE_DIR}/../../services/station/include"
"${CMAKE_CURRENT_SOURCE_DIR}/../../services/tuner/include"
@@ -72,6 +72,31 @@ namespace {
return EXIT_SUCCESS;
}
[[nodiscard]] int runBeepEnabledJsonTest()
{
const auto onParsed = core::parseBeepEnabledJson(R"({"enabled":true})");
if (!onParsed || *onParsed != true) {
std::cerr << "beep enabled=true parse mismatch\n";
return EXIT_FAILURE;
}
const auto offParsed = core::parseBeepEnabledJson(R"({"enabled":false})");
if (!offParsed || *offParsed != false) {
std::cerr << "beep enabled=false parse mismatch\n";
return EXIT_FAILURE;
}
const auto missing = core::parseBeepEnabledJson(R"({})");
if (missing) {
std::cerr << "beep missing field should fail\n";
return EXIT_FAILURE;
}
const auto malformed = core::parseBeepEnabledJson("not json");
if (malformed) {
std::cerr << "beep malformed body should fail\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
@@ -79,5 +104,8 @@ int main()
if (runRoundTripTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runBeepEnabledJsonTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -13,6 +13,7 @@
#include "core/Bt1035At.hpp"
#include "core/Bt1035PairedDevice.hpp"
#include "core/Bt1035ScannedDevice.hpp"
#include "core/BluetoothJson.hpp"
#include <cstdlib>
@@ -32,7 +33,7 @@ namespace {
std::cerr << "I2S mode must be in init sequence\n";
return EXIT_FAILURE;
}
if (sequence[2U] != core::Bt1035AtCommand::I2sSlave48k32) {
if (sequence[2U] != core::Bt1035AtCommand::I2sSlave48k24) {
std::cerr << "I2SCFG must be in init sequence\n";
return EXIT_FAILURE;
}
@@ -43,9 +44,15 @@ namespace {
return EXIT_FAILURE;
}
const std::string i2sCfg =
core::buildBt1035AtLine(core::Bt1035AtCommand::I2sSlave48k32);
if (i2sCfg != "AT+I2SCFG=67\r\n") {
std::cerr << "I2SCFG=67 command line mismatch\n";
core::buildBt1035AtLine(core::Bt1035AtCommand::I2sSlave48k24);
if (i2sCfg != "AT+I2SCFG=35\r\n") {
std::cerr << "I2SCFG=35 command line mismatch\n";
return EXIT_FAILURE;
}
const std::string reset =
core::buildBt1035AtLine(core::Bt1035AtCommand::Reset);
if (reset != "AT+RESET\r\n") {
std::cerr << "AT+RESET command line mismatch\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
@@ -136,6 +143,115 @@ namespace {
return EXIT_SUCCESS;
}
[[nodiscard]] int runScanConnectTest()
{
if (core::buildBt1035StartScanLine(1U, 20U) != "AT+SCAN=1,20\r\n") {
std::cerr << "SCAN start line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035StartScanLine(1U, 0U) != "AT+SCAN=1\r\n") {
std::cerr << "SCAN default line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035StartScanLine(2U, 15U) != "AT+SCAN=2,15\r\n") {
std::cerr << "BLE SCAN line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035EnablePrintLine() != "AT+PRINT=1\r\n") {
std::cerr << "PRINT line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035DisableAutoLinkLine() != "AT+LINKCFG=0,0\r\n") {
std::cerr << "LINKCFG line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035ClearPairedListLine() != "AT+PLIST=0\r\n") {
std::cerr << "PLIST clear line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035DisconnectAllLine() != "AT+DSCA\r\n") {
std::cerr << "DSCA line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035StopScanLine() != "AT+SCAN=0\r\n") {
std::cerr << "SCAN stop line mismatch\n";
return EXIT_FAILURE;
}
if (!core::isValidBt1035Mac("AABBCCDDEEFF")
|| core::isValidBt1035Mac("bad")) {
std::cerr << "MAC validation failed\n";
return EXIT_FAILURE;
}
if (core::buildBt1035A2dpConnectLine("aabbccddeeff")
!= "AT+A2DPCONN=aabbccddeeff\r\n") {
std::cerr << "A2DPCONN line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035A2dpAudioLine(true) != "AT+A2DPAUDIO=1\r\n") {
std::cerr << "A2DPAUDIO=1 line mismatch\n";
return EXIT_FAILURE;
}
if (core::buildBt1035A2dpAudioLine(false) != "AT+A2DPAUDIO=0\r\n") {
std::cerr << "A2DPAUDIO=0 line mismatch\n";
return EXIT_FAILURE;
}
const auto scanned = core::parseBt1035ScanResponse(
"+SCAN=1,0,112233445566,-55,7,MyPhone,240404\r\n"
"+SCAN=2,0,FFEEDDCCBBAA,-70,0,,240404\r\n"
"+SCAN=E\r\n"
"OK\r\n");
if (!scanned || scanned->size() != 2U || (*scanned)[0U].mac != "112233445566"
|| (*scanned)[0U].name != "MyPhone" || (*scanned)[0U].rssiDbm != -55
|| !(*scanned)[1U].name.empty()) {
std::cerr << "SCAN parse failed\n";
return EXIT_FAILURE;
}
const auto colonMac = core::parseBt1035ScanResponse(
"+SCAN=1,2,AA:BB:CC:DD:EE:FF,-60,4,Test,240404\r\n+SCAN=E\r\n");
if (!colonMac || colonMac->size() != 1U
|| (*colonMac)[0U].mac != "AABBCCDDEEFF") {
std::cerr << "SCAN colon MAC parse failed\n";
return EXIT_FAILURE;
}
const auto userLogScan = core::parseBt1035ScanResponse(
"+DEVSTAT=9\r\n\r\nOK\r\n\r\n"
"+SCAN=1,2,6960BB7EC5AA,-71,8,HY300PRO,1A0114\r\n\r\n"
"+SCAN=2,2,BC87FAE69D6E,-49,0\r\n\r\n"
"+DEVSTAT=1\r\n");
if (!userLogScan || userLogScan->size() != 2U
|| (*userLogScan)[0U].name != "HY300PRO"
|| (*userLogScan)[0U].mac != "6960BB7EC5AA"
|| (*userLogScan)[1U].mac != "BC87FAE69D6E"
|| !(*userLogScan)[1U].name.empty()
|| (*userLogScan)[1U].rssiDbm != -49) {
std::cerr << "SCAN user-log payload parse failed\n";
return EXIT_FAILURE;
}
const auto bareEmptyName = core::parseBt1035ScanResponse(
"+SCAN=2,2,BC87FAE69D6E,-49,0\r\n");
if (!bareEmptyName || bareEmptyName->size() != 1U
|| (*bareEmptyName)[0U].mac != "BC87FAE69D6E"
|| !(*bareEmptyName)[0U].name.empty()
|| !(*bareEmptyName)[0U].deviceClass.empty()) {
std::cerr << "SCAN bare empty-name line parse failed\n";
return EXIT_FAILURE;
}
const std::string scanJson =
core::serializeBluetoothScanJson(*scanned);
if (scanJson.find("\"mac\":\"112233445566\"") == std::string::npos
|| scanJson.find("\"rssi_dbm\":-55") == std::string::npos) {
std::cerr << "scan JSON serialise failed: " << scanJson << '\n';
return EXIT_FAILURE;
}
const auto mac =
core::parseBluetoothConnectJson(R"({"mac":"112233445566"})");
if (!mac || *mac != "112233445566") {
std::cerr << "connect JSON parse failed\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
@@ -149,5 +265,8 @@ int main()
if (runNameAutoConnPairedParseTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runScanConnectTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,21 @@
/**
* @file esp_log.h
* @brief Host-test stand-in for ESP-IDF logging macros.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Not shipped to device firmware: idf.py builds resolve the real ESP-IDF
* esp_log.h first. Only used so services/tuner/src/TunerService.cpp — which
* logs scan progress on real hardware — can also compile against
* components/core/test host tests without pulling in ESP-IDF.
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define ESP_LOGE(tag, fmt, ...) ((void)0)
#define ESP_LOGW(tag, fmt, ...) ((void)0)
#define ESP_LOGI(tag, fmt, ...) ((void)0)
#define ESP_LOGD(tag, fmt, ...) ((void)0)
#define ESP_LOGV(tag, fmt, ...) ((void)0)
@@ -0,0 +1,19 @@
/**
* @file FreeRTOS.h
* @brief Host-test stand-in for the FreeRTOS core header.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Not shipped to device firmware: idf.py builds resolve the real FreeRTOS
* headers first. Only used so services/tuner/src/TunerService.cpp — which
* paces scan steps with vTaskDelay on real hardware — can also compile
* against components/core/test host tests without pulling in FreeRTOS.
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
using TickType_t = unsigned int;
#define pdMS_TO_TICKS(ms) (static_cast<TickType_t>(ms))
@@ -0,0 +1,18 @@
/**
* @file task.h
* @brief Host-test stand-in for the FreeRTOS task API.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Not shipped to device firmware: idf.py builds resolve the real FreeRTOS
* headers first. vTaskDelay is a no-op here — host tests assert scan logic
* against a FakeTuner, not real hardware settle timing.
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "freertos/FreeRTOS.h"
inline void vTaskDelay(TickType_t) {}
@@ -15,6 +15,7 @@
#include "core/FrequencyKHz.hpp"
#include "core/IDsp.hpp"
#include "core/ISecureStore.hpp"
#include "core/BtSpeakerTarget.hpp"
#include "core/StationName.hpp"
#include "core/TunerBand.hpp"
#include "integration/IntegrationService.hpp"
@@ -68,6 +69,12 @@ public:
{
return {};
}
[[nodiscard]] std::expected<void, core::DspError> setBeepEnabled(
bool) override
{
return {};
}
};
class TrackingTuner final : public core::ITuner {
@@ -217,9 +224,60 @@ public:
return {};
}
[[nodiscard]] bool hasBtSpeakerTarget() const override
{
return btSpeaker_.has_value();
}
[[nodiscard]] std::expected<void, core::StoreError> saveBtSpeakerTarget(
const core::BtSpeakerTarget& target) override
{
btSpeaker_ = target;
return {};
}
[[nodiscard]] std::expected<core::BtSpeakerTarget, core::StoreError>
loadBtSpeakerTarget() const override
{
if (!btSpeaker_) {
return std::unexpected(core::StoreError::NotFound);
}
return *btSpeaker_;
}
[[nodiscard]] std::expected<void, core::StoreError> clearBtSpeakerTarget()
override
{
btSpeaker_.reset();
return {};
}
[[nodiscard]] bool hasWebRadioConfig() const override
{
return webRadioJson_.has_value();
}
[[nodiscard]] std::expected<void, core::StoreError>
saveWebRadioConfigJson(std::string_view json) override
{
webRadioJson_ = std::string(json);
return {};
}
[[nodiscard]] std::expected<std::string, core::StoreError>
loadWebRadioConfigJson() const override
{
if (!webRadioJson_) {
return std::unexpected(core::StoreError::NotFound);
}
return *webRadioJson_;
}
private:
std::optional<std::string> stationJson_;
std::optional<std::uint8_t> lastPresetIndex_;
std::optional<core::BtSpeakerTarget> btSpeaker_;
std::optional<std::string> webRadioJson_;
};
[[nodiscard]] core::Station makeFmStation(const char* name, std::uint32_t khz)
@@ -171,9 +171,54 @@ public:
return {};
}
[[nodiscard]] bool hasBtSpeakerTarget() const override
{
return false;
}
[[nodiscard]] std::expected<void, core::StoreError> saveBtSpeakerTarget(
const core::BtSpeakerTarget&) override
{
return {};
}
[[nodiscard]] std::expected<core::BtSpeakerTarget, core::StoreError>
loadBtSpeakerTarget() const override
{
return std::unexpected(core::StoreError::NotFound);
}
[[nodiscard]] std::expected<void, core::StoreError> clearBtSpeakerTarget()
override
{
return {};
}
[[nodiscard]] bool hasWebRadioConfig() const override
{
return webRadioJson_.has_value();
}
[[nodiscard]] std::expected<void, core::StoreError>
saveWebRadioConfigJson(std::string_view json) override
{
webRadioJson_ = std::string(json);
return {};
}
[[nodiscard]] std::expected<std::string, core::StoreError>
loadWebRadioConfigJson() const override
{
if (!webRadioJson_) {
return std::unexpected(core::StoreError::NotFound);
}
return *webRadioJson_;
}
private:
std::optional<std::string> stationJson_;
std::optional<std::uint8_t> lastPresetIndex_;
std::optional<std::string> webRadioJson_;
};
[[nodiscard]] core::Station makeFmStation(const char* name, std::uint32_t khz)
@@ -0,0 +1,73 @@
/**
* @file web_radio_json_test.cpp
* @brief Host tests for WebRadioConfig JSON round-trip.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-08
*/
#include "core/WebRadioJson.hpp"
#include <cstdlib>
#include <iostream>
namespace {
[[nodiscard]] int runRoundTripTest()
{
const core::WebRadioConfig config{
.enabled = true, .url = "http://edge.radiomontecarlo.net/RMC.mp3"};
const std::string json = core::serializeWebRadioConfigJson(config);
const auto parsed = core::parseWebRadioConfigJson(json);
if (!parsed || parsed->enabled != true || parsed->url != config.url) {
std::cerr << "round-trip mismatch\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runValidationTest()
{
const auto missingUrl =
core::parseWebRadioConfigJson(R"({"enabled":false})");
if (missingUrl) {
std::cerr << "missing url should fail\n";
return EXIT_FAILURE;
}
const auto badScheme = core::parseWebRadioConfigJson(
R"({"enabled":true,"url":"https://example.com/x.mp3"})");
if (badScheme) {
std::cerr << "non-http scheme should fail\n";
return EXIT_FAILURE;
}
const auto malformed = core::parseWebRadioConfigJson("not json");
if (malformed) {
std::cerr << "malformed body should fail\n";
return EXIT_FAILURE;
}
const auto ok = core::parseWebRadioConfigJson(
R"({"enabled":false,"url":"http://example.com/x.mp3"})");
if (!ok || ok->enabled != false) {
std::cerr << "valid disabled config should parse\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
{
if (runRoundTripTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runValidationTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,121 @@
/**
* @file wifi_scan_test.cpp
* @brief Host tests for Wi-Fi scan JSON serialisation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#include "core/WifiScanJson.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
namespace {
/**
* @brief expectEqual — assert two strings match.
*
* @dname expectEqual
* @param actual Observed value.
* @param expected Expected value.
* @return true when equal.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool expectEqual(const std::string& actual,
const std::string& expected)
{
if (actual == expected) {
return true;
}
std::cerr << "expected: " << expected << "\nactual: " << actual << '\n';
return false;
}
/**
* @brief runWifiScanSerialiseTest — verify scan list JSON shape.
*
* @dname runWifiScanSerialiseTest
* @return EXIT_SUCCESS or EXIT_FAILURE.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] int runWifiScanSerialiseTest()
{
const std::vector<core::WifiScannedNetwork> networks = {
{
.ssid = "HomeNet",
.rssiDbm = -42,
.auth = "wpa2",
.channel = 6,
},
{
.ssid = "Guest\"WiFi",
.rssiDbm = -71,
.auth = "open",
.channel = 11,
},
};
const std::string json = core::serializeWifiScanJson(networks);
const std::string expected =
R"({"networks":[{"ssid":"HomeNet","rssi_dbm":-42,"auth":"wpa2","channel":6},{"ssid":"Guest\"WiFi","rssi_dbm":-71,"auth":"open","channel":11}]})";
if (!expectEqual(json, expected)) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
/**
* @brief runWifiScanErrorSerialiseTest — verify error JSON.
*
* @dname runWifiScanErrorSerialiseTest
* @return EXIT_SUCCESS or EXIT_FAILURE.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] int runWifiScanErrorSerialiseTest()
{
if (!expectEqual(core::serializeWifiScanErrorJson("scan_failed"),
R"({"status":"error","reason":"scan_failed"})")) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
/**
* @brief main — host test entry point.
*
* @dname main
* @return EXIT_SUCCESS when all tests pass.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
int main()
{
if (runWifiScanSerialiseTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runWifiScanErrorSerialiseTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -220,6 +220,20 @@ public:
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center,
float q);
/**
* @brief setBeepEnabled — gate the SigmaStudio Beep1 tone generator.
*
* @dname setBeepEnabled
* @param enabled true unmutes Beep1, false mutes it.
* @return Ok on success, or Adau1701Error.
* @pubstate writes parameter RAM via safeload (ADDR_BEEP1_ENABLE).
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<void, Adau1701Error> setBeepEnabled(
bool enabled);
private:
[[nodiscard]] std::expected<void, Adau1701Error> ensureBooted() const;
[[nodiscard]] std::expected<void, Adau1701Error> safeloadGain(
@@ -61,6 +61,9 @@ public:
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center,
float q) override;
[[nodiscard]] std::expected<void, core::DspError> setBeepEnabled(
bool enabled) override;
private:
[[nodiscard]] static core::DspError mapError(Adau1701Error error) noexcept;
@@ -27,273 +27,345 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern "C" {
extern "C"
{
} // extern "C"
namespace adau1701 {
namespace {
constexpr char kTag[] = "Adau1701";
constexpr int kI2cPort = 0;
/** Index 0 is the fixed high-pass band (SigmaStudio band 1); not safeloaded. */
constexpr std::uint8_t kFixedHighPassBandIndex = 0U;
} // namespace
Adau1701Driver::Adau1701Driver(Adau1701Pins pins,
core::IDspProgramSource& programSource)
: pins_(pins)
, programSource_(programSource)
, booted_(false)
, i2cBus_(nullptr)
, i2cDev_(nullptr)
namespace adau1701
{
}
Adau1701Driver::~Adau1701Driver()
{
auto* dev = static_cast<i2c_master_dev_handle_t>(i2cDev_);
auto* bus = static_cast<i2c_master_bus_handle_t>(i2cBus_);
if (dev != nullptr) {
i2c_master_bus_rm_device(dev);
namespace
{
constexpr char kTag[] = "Adau1701";
constexpr int kI2cPort = 0;
/** Index 0 is the fixed high-pass band (SigmaStudio band 1); not safeloaded. */
constexpr std::uint8_t kFixedHighPassBandIndex = 0U;
} // namespace
Adau1701Driver::Adau1701Driver(Adau1701Pins pins,
core::IDspProgramSource &programSource)
: pins_(pins), programSource_(programSource), booted_(false), i2cBus_(nullptr), i2cDev_(nullptr)
{
}
if (bus != nullptr) {
i2c_del_master_bus(bus);
}
}
std::expected<void, Adau1701Error> Adau1701Driver::replayProgram(
const core::DspProgram& program)
{
const unsigned char deviceAddr =
static_cast<unsigned char>(pins_.i2cAddr7 << 1);
for (const core::RegisterWrite& write : program.writes()) {
const auto data = write.data();
if (data.empty()) {
return std::unexpected(Adau1701Error::DownloadFailed);
Adau1701Driver::~Adau1701Driver()
{
auto *dev = static_cast<i2c_master_dev_handle_t>(i2cDev_);
auto *bus = static_cast<i2c_master_bus_handle_t>(i2cBus_);
if (dev != nullptr)
{
i2c_master_bus_rm_device(dev);
}
if (bus != nullptr)
{
i2c_del_master_bus(bus);
}
SIGMA_WRITE_REGISTER_BLOCK(
deviceAddr,
write.address(),
static_cast<unsigned int>(data.size()),
const_cast<ADI_REG_TYPE*>(
reinterpret_cast<const ADI_REG_TYPE*>(data.data())));
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::boot()
{
if (booted_) {
std::expected<void, Adau1701Error> Adau1701Driver::replayProgram(
const core::DspProgram &program)
{
const unsigned char deviceAddr =
static_cast<unsigned char>(pins_.i2cAddr7 << 1);
for (const core::RegisterWrite &write : program.writes())
{
const auto data = write.data();
if (data.empty())
{
return std::unexpected(Adau1701Error::DownloadFailed);
}
SIGMA_WRITE_REGISTER_BLOCK(
deviceAddr,
write.address(),
static_cast<unsigned int>(data.size()),
const_cast<ADI_REG_TYPE *>(
reinterpret_cast<const ADI_REG_TYPE *>(data.data())));
}
return {};
}
gpio_config_t resetCfg = {};
resetCfg.pin_bit_mask = 1ULL << pins_.resetGpio;
resetCfg.mode = GPIO_MODE_OUTPUT;
if (gpio_config(&resetCfg) != ESP_OK) {
return std::unexpected(Adau1701Error::ResetFailed);
}
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 1);
vTaskDelay(pdMS_TO_TICKS(10));
i2c_master_bus_config_t busCfg = {};
busCfg.i2c_port = static_cast<i2c_port_num_t>(kI2cPort);
busCfg.sda_io_num = static_cast<gpio_num_t>(pins_.i2cSda);
busCfg.scl_io_num = static_cast<gpio_num_t>(pins_.i2cScl);
busCfg.clk_source = I2C_CLK_SRC_DEFAULT;
busCfg.glitch_ignore_cnt = 7;
busCfg.flags.enable_internal_pullup = true;
i2c_master_bus_handle_t bus = nullptr;
if (i2c_new_master_bus(&busCfg, &bus) != ESP_OK) {
ESP_LOGE(kTag, "i2c_new_master_bus failed");
return std::unexpected(Adau1701Error::I2cInitFailed);
}
i2cBus_ = bus;
i2c_device_config_t devCfg = {};
devCfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
devCfg.device_address = static_cast<uint16_t>(pins_.i2cAddr7);
devCfg.scl_speed_hz = 100000;
i2c_master_dev_handle_t dev = nullptr;
if (i2c_master_bus_add_device(bus, &devCfg, &dev) != ESP_OK) {
ESP_LOGE(kTag, "i2c_master_bus_add_device failed");
return std::unexpected(Adau1701Error::I2cInitFailed);
}
i2cDev_ = dev;
sigma_studio_bind_i2c(kI2cPort, static_cast<unsigned char>(pins_.i2cAddr7));
sigma_studio_set_device(dev);
const auto program = programSource_.loadProgram();
if (!program) {
ESP_LOGE(kTag, "DSP program load failed");
return std::unexpected(Adau1701Error::DownloadFailed);
}
if (auto replay = replayProgram(*program); !replay) {
return replay;
}
booted_ = true;
ESP_LOGI(kTag, "SigmaStudio program loaded");
return {};
}
bool Adau1701Driver::isBooted() const noexcept
{
return booted_;
}
void* Adau1701Driver::i2cBusHandle() const noexcept
{
return i2cBus_;
}
std::expected<void, Adau1701Error> Adau1701Driver::ensureBooted() const
{
if (!booted_) {
return std::unexpected(Adau1701Error::NotBooted);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::safeloadFixpoint(
unsigned paramAddr, std::int32_t fixpoint)
{
if (sigma_safeload_param(paramAddr, fixpoint) != 0) {
return std::unexpected(Adau1701Error::SafeloadFailed);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::safeloadGain(
unsigned paramAddr, core::GainDb gain)
{
return safeloadFixpoint(paramAddr, core::gainDbToLinearFixpoint(gain));
}
std::expected<void, Adau1701Error> Adau1701Driver::setInputVolume(
core::MixSource source, core::GainDb left, core::GainDb right)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
if (auto result = safeloadGain(paramAddrInputLeft(source), left); !result) {
return result;
}
return safeloadGain(paramAddrInputRight(source), right);
}
std::expected<void, Adau1701Error> Adau1701Driver::setMasterVolume(
core::GainDb left, core::GainDb right)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
if (auto result = safeloadGain(static_cast<unsigned>(ADDR_MULTIPLE1), left);
!result) {
return result;
}
return safeloadGain(static_cast<unsigned>(ADDR_MULTIPLE1_1), right);
}
std::expected<void, Adau1701Error> Adau1701Driver::applyMixer(
const core::MixerState& mixer)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
if (auto result = setInputVolume(core::MixSource::Si4684, mixer.si4684Left,
mixer.si4684Right);
!result) {
return result;
}
if (auto result = setInputVolume(core::MixSource::Esp32, mixer.esp32Left,
mixer.esp32Right);
!result) {
return result;
}
if (auto result =
safeloadGain(static_cast<unsigned>(ADDR_STMIXER1_ST0_VOLUME),
mixer.mixLeft);
!result) {
return result;
}
return safeloadGain(static_cast<unsigned>(ADDR_STMIXER1_ST1_VOLUME),
mixer.mixRight);
}
std::expected<void, Adau1701Error> Adau1701Driver::setEqBand(
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center, float q)
{
if (band.value() == kFixedHighPassBandIndex) {
return std::unexpected(Adau1701Error::InvalidParameter);
}
if (auto ready = ensureBooted(); !ready) {
return ready;
}
const core::BiquadCoefficients coeffs =
core::designPeakingEq(center, gain, q);
const auto fixpoints = coeffs.toFixpoint823();
const unsigned baseAddr = paramAddrEqBandBase(band.value());
unsigned addrs[5U];
int values[5U];
for (unsigned i = 0U; i < 5U; ++i) {
addrs[i] = baseAddr + i;
values[i] = fixpoints[i];
}
if (sigma_safeload_block(5U, addrs, values) != 0) {
return std::unexpected(Adau1701Error::SafeloadFailed);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::applyEq(
const core::EqProfile& eq)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
for (std::uint8_t i = 0; i < core::EqBandIndex::kBandCount; ++i) {
if (i == kFixedHighPassBandIndex) {
continue;
std::expected<void, Adau1701Error> Adau1701Driver::boot()
{
if (booted_)
{
return {};
}
const auto index = core::EqBandIndex::tryFromIndex(i);
if (!index) {
gpio_config_t resetCfg = {};
resetCfg.pin_bit_mask = 1ULL << pins_.resetGpio;
resetCfg.mode = GPIO_MODE_OUTPUT;
if (gpio_config(&resetCfg) != ESP_OK)
{
return std::unexpected(Adau1701Error::ResetFailed);
}
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 1);
vTaskDelay(pdMS_TO_TICKS(10));
i2c_master_bus_config_t busCfg = {};
busCfg.i2c_port = static_cast<i2c_port_num_t>(kI2cPort);
busCfg.sda_io_num = static_cast<gpio_num_t>(pins_.i2cSda);
busCfg.scl_io_num = static_cast<gpio_num_t>(pins_.i2cScl);
busCfg.clk_source = I2C_CLK_SRC_DEFAULT;
busCfg.glitch_ignore_cnt = 7;
busCfg.flags.enable_internal_pullup = true;
i2c_master_bus_handle_t bus = nullptr;
if (i2c_new_master_bus(&busCfg, &bus) != ESP_OK)
{
ESP_LOGE(kTag, "i2c_new_master_bus failed");
return std::unexpected(Adau1701Error::I2cInitFailed);
}
i2cBus_ = bus;
i2c_device_config_t devCfg = {};
devCfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
devCfg.device_address = static_cast<uint16_t>(pins_.i2cAddr7);
devCfg.scl_speed_hz = 100000;
i2c_master_dev_handle_t dev = nullptr;
if (i2c_master_bus_add_device(bus, &devCfg, &dev) != ESP_OK)
{
ESP_LOGE(kTag, "i2c_master_bus_add_device failed");
return std::unexpected(Adau1701Error::I2cInitFailed);
}
i2cDev_ = dev;
sigma_studio_bind_i2c(kI2cPort, static_cast<unsigned char>(pins_.i2cAddr7));
sigma_studio_set_device(dev);
const auto program = programSource_.loadProgram();
if (!program)
{
ESP_LOGE(kTag, "DSP program load failed");
return std::unexpected(Adau1701Error::DownloadFailed);
}
ESP_LOGI(kTag,
"DSP program contains %u writes",
static_cast<unsigned>(program->writes().size()));
for (const auto &w : program->writes())
{
ESP_LOGI(kTag,
"ADDR=0x%04X LEN=%u",
static_cast<unsigned>(w.address()),
static_cast<unsigned>(w.data().size()));
}
if (auto replay = replayProgram(*program); !replay)
{
return replay;
}
booted_ = true;
ESP_LOGI(kTag, "SigmaStudio program loaded");
return {};
}
bool Adau1701Driver::isBooted() const noexcept
{
return booted_;
}
void *Adau1701Driver::i2cBusHandle() const noexcept
{
return i2cBus_;
}
std::expected<void, Adau1701Error> Adau1701Driver::ensureBooted() const
{
if (!booted_)
{
return std::unexpected(Adau1701Error::NotBooted);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::safeloadFixpoint(
unsigned paramAddr, std::int32_t fixpoint)
{
sigma_studio_lock();
const int result = sigma_safeload_param(paramAddr, fixpoint);
sigma_studio_unlock();
if (result != 0)
{
return std::unexpected(Adau1701Error::SafeloadFailed);
}
const core::EqBandSettings& band = eq.band(*index);
if (auto result = setEqBand(*index, band.gain, band.center, band.q);
!result) {
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::safeloadGain(
unsigned paramAddr, core::GainDb gain)
{
return safeloadFixpoint(paramAddr, core::gainDbToLinearFixpoint(gain));
}
std::expected<void, Adau1701Error> Adau1701Driver::setInputVolume(
core::MixSource source, core::GainDb left, core::GainDb right)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
if (auto result = safeloadGain(paramAddrInputLeft(source), left); !result)
{
return result;
}
return safeloadGain(paramAddrInputRight(source), right);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::applyProfile(
const core::AudioProfile& profile)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
std::expected<void, Adau1701Error> Adau1701Driver::setMasterVolume(
core::GainDb left, core::GainDb right)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
if (auto result = safeloadGain(static_cast<unsigned>(ADDR_MULTIPLE1), left);
!result)
{
return result;
}
return safeloadGain(static_cast<unsigned>(ADDR_MULTIPLE1_1), right);
}
if (auto result = applyMixer(profile.mixer); !result) {
return result;
std::expected<void, Adau1701Error> Adau1701Driver::applyMixer(
const core::MixerState &mixer)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
if (auto result = setInputVolume(core::MixSource::Si4684, mixer.si4684Left,
mixer.si4684Right);
!result)
{
return result;
}
if (auto result = setInputVolume(core::MixSource::Esp32, mixer.esp32Left,
mixer.esp32Right);
!result)
{
return result;
}
if (auto result =
safeloadGain(static_cast<unsigned>(ADDR_STMIXER1_ST0_VOLUME),
mixer.mixLeft);
!result)
{
return result;
}
return safeloadGain(static_cast<unsigned>(ADDR_STMIXER1_ST1_VOLUME),
mixer.mixRight);
}
if (auto result = applyEq(profile.eq); !result) {
return result;
std::expected<void, Adau1701Error> Adau1701Driver::setEqBand(
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center, float q)
{
if (band.value() == kFixedHighPassBandIndex)
{
return std::unexpected(Adau1701Error::InvalidParameter);
}
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
const core::BiquadCoefficients coeffs =
core::designPeakingEq(center, gain, q);
const auto fixpoints = coeffs.toFixpoint823();
const unsigned baseAddr = paramAddrEqBandBase(band.value());
unsigned addrs[5U];
int values[5U];
for (unsigned i = 0U; i < 5U; ++i)
{
addrs[i] = baseAddr + i;
values[i] = fixpoints[i];
}
sigma_studio_lock();
const int result = sigma_safeload_block(5U, addrs, values);
sigma_studio_unlock();
if (result != 0)
{
return std::unexpected(Adau1701Error::SafeloadFailed);
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::setBeepEnabled(
bool enabled)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
// Beep1 ("Beep - variable gain", ADI Sound Generation toolbox,
// DigiRadio.params) ENABLE/KICK: unity fixpoint 0x00800000 = on,
// exact zero = off. Not continuous gains, so bypass
// safeloadGain/GainDb (whose quietest value is -96 dB, not true
// zero) and write the raw fixpoint. KICK is the cell's trigger
// input (per its own SigmaStudio parameter name); ENABLE alone
// may leave the generator gated shut without it.
const std::int32_t value =
enabled ? core::gainDbToLinearFixpoint(core::GainDb::zero()) : 0;
if (auto result = safeloadFixpoint(
static_cast<unsigned>(ADDR_BEEP1_ENABLE), value);
!result)
{
return result;
}
return safeloadFixpoint(static_cast<unsigned>(ADDR_BEEP1_KICK), value);
}
std::expected<void, Adau1701Error> Adau1701Driver::applyEq(
const core::EqProfile &eq)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
for (std::uint8_t i = 0; i < core::EqBandIndex::kBandCount; ++i)
{
if (i == kFixedHighPassBandIndex)
{
continue;
}
const auto index = core::EqBandIndex::tryFromIndex(i);
if (!index)
{
return std::unexpected(Adau1701Error::SafeloadFailed);
}
const core::EqBandSettings &band = eq.band(*index);
if (auto result = setEqBand(*index, band.gain, band.center, band.q);
!result)
{
return result;
}
}
return {};
}
std::expected<void, Adau1701Error> Adau1701Driver::applyProfile(
const core::AudioProfile &profile)
{
if (auto ready = ensureBooted(); !ready)
{
return ready;
}
if (auto result = applyMixer(profile.mixer); !result)
{
return result;
}
if (auto result = applyEq(profile.eq); !result)
{
return result;
}
return setMasterVolume(profile.masterLeft, profile.masterRight);
}
return setMasterVolume(profile.masterLeft, profile.masterRight);
}
} // namespace adau1701
@@ -89,4 +89,12 @@ std::expected<void, core::DspError> Adau1701Dsp::setEqBand(
return {};
}
std::expected<void, core::DspError> Adau1701Dsp::setBeepEnabled(bool enabled)
{
if (auto result = driver_.setBeepEnabled(enabled); !result) {
return std::unexpected(mapError(result.error()));
}
return {};
}
} // namespace adau1701
@@ -18,7 +18,7 @@
#include "esp_log.h"
#include "esp_partition.h"
#include <vector>
#include <cstdlib>
#include <memory>
namespace adau1701 {
@@ -75,26 +75,60 @@ FlashDspProgramSource::loadProgram()
return std::unexpected(core::DspProgramError::Empty);
}
// Partizione non vuota: alloca il backing store con new(nothrow), cosi'
// un OOM ritorna nullptr invece di abortire (nessuna eccezione).
auto* raw = new (std::nothrow) std::uint8_t[part->size];
// Determine exact blob size by scanning DRAD write records before
// allocating — the full partition (256 KB) exceeds the available heap
// on ESP32-S3, and new(nothrow) still invokes __cxa_allocate_exception
// when exceptions are disabled, causing abort().
// Read DRAD header (12 bytes) to get write_count.
constexpr std::size_t kHdrSize = 12U;
std::uint8_t hdr[kHdrSize] = {};
if (esp_partition_read(part, 0, hdr, kHdrSize) != ESP_OK) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
// Quick magic/version check (full validation done by parseDspProgramBlob).
if (hdr[0] != 'D' || hdr[1] != 'R' || hdr[2] != 'A' || hdr[3] != 'D'
|| static_cast<std::uint16_t>(hdr[4] | (hdr[5] << 8)) != 1U) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
const auto writeCount =
static_cast<std::uint16_t>(hdr[6] | (hdr[7] << 8));
if (writeCount == 0U || writeCount > 32U) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
// Scan write record headers (4 bytes each) to compute total payload size.
std::size_t payloadSize = 0U;
for (std::uint16_t i = 0U; i < writeCount; ++i) {
std::uint8_t rec[4] = {};
if (esp_partition_read(part, kHdrSize + payloadSize, rec, 4U)
!= ESP_OK) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
const auto dataLen =
static_cast<std::uint16_t>(rec[2] | (rec[3] << 8));
if (dataLen == 0U || dataLen > 16384U) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
payloadSize += 4U + dataLen;
}
const std::size_t totalSize = kHdrSize + payloadSize;
// Use malloc — avoids C++ exception machinery entirely (no nothrow workaround).
auto* raw = static_cast<std::uint8_t*>(::malloc(totalSize));
if (raw == nullptr) {
ESP_LOGE(kTag, "dsp buffer alloc failed (%u byte)",
static_cast<unsigned>(part->size));
ESP_LOGE(kTag, "dsp buffer alloc failed (%u bytes)",
static_cast<unsigned>(totalSize));
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
std::unique_ptr<std::uint8_t[]> guard(raw);
std::unique_ptr<std::uint8_t, decltype(&::free)> guard(raw, ::free);
if (esp_partition_read(part, 0, raw, part->size) != ESP_OK) {
if (esp_partition_read(part, 0, raw, totalSize) != ESP_OK) {
return std::unexpected(core::DspProgramError::FlashReadFailed);
}
std::span<const std::uint8_t> view(raw, part->size);
if (partitionLooksEmpty(view)) {
return std::unexpected(core::DspProgramError::Empty);
}
return core::parseDspProgramBlob(view);
return core::parseDspProgramBlob({raw, totalSize});
}
std::expected<void, core::DspProgramError>
@@ -14,10 +14,13 @@
#include "SigmaStudioFW.h"
#include "driver/i2c_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <string.h>
static i2c_master_dev_handle_t s_dev = NULL;
static SemaphoreHandle_t s_lock = NULL;
void sigma_studio_bind_i2c(int port, unsigned char addr7)
{
@@ -28,6 +31,45 @@ void sigma_studio_bind_i2c(int port, unsigned char addr7)
void sigma_studio_set_device(void* i2cDevHandle)
{
s_dev = (i2c_master_dev_handle_t)i2cDevHandle;
if (s_lock == NULL) {
s_lock = xSemaphoreCreateMutex();
}
}
void sigma_studio_lock(void)
{
if (s_lock != NULL) {
xSemaphoreTake(s_lock, portMAX_DELAY);
}
}
void sigma_studio_unlock(void)
{
if (s_lock != NULL) {
xSemaphoreGive(s_lock);
}
}
/*
* The ADAU1701 memory map is word-indexed with a region-dependent word
* width (Param RAM = 4 bytes, Program RAM = 5 bytes, Control regs =
* 2 bytes) — confirmed by DigiRadio's own generated export
* (DigiRadio_IC_1.h: PROGRAM_ADDR_IC_1=1024/PROGRAM_SIZE_IC_1=5120,
* PARAM_ADDR_IC_1=0/PARAM_SIZE_IC_1=4096; DigiRadio_IC_1_REG.h:
* REG_COREREGISTER_IC_1_ADDR=0x81C) and independently by the
* ADAU1701-TCPi-ESP32 reference project's directWrite(). Chunk
* boundaries must land on whole words, or the address advance for the
* next I2C transaction (and the data itself, mid-word) is wrong.
*/
static unsigned int sigmaWordSize(unsigned int address)
{
if (address >= 0x0400U && address <= 0x07FFU) {
return 5U;
}
if (address >= 0x0800U) {
return 2U;
}
return 4U;
}
void SIGMA_WRITE_REGISTER_BLOCK(unsigned char devAddress,
@@ -40,25 +82,45 @@ void SIGMA_WRITE_REGISTER_BLOCK(unsigned char devAddress,
return;
}
enum { kChunk = 64U };
enum { kChunkBytesMax = 64U };
const unsigned int wordSize = sigmaWordSize(address);
const unsigned int wordsPerChunk = kChunkBytesMax / wordSize;
const unsigned int chunkBytes = wordsPerChunk * wordSize;
unsigned int addr = address;
unsigned int remaining = length;
ADI_REG_TYPE* cursor = pData;
while (remaining > 0U) {
const unsigned int chunk =
remaining > kChunk ? kChunk : remaining;
unsigned char buf[2U + 64U];
remaining > chunkBytes ? chunkBytes : remaining;
const unsigned int words = chunk / wordSize;
unsigned char buf[2U + kChunkBytesMax];
buf[0] = (unsigned char)((addr >> 8) & 0xFFU);
buf[1] = (unsigned char)(addr & 0xFFU);
memcpy(buf + 2U, cursor, chunk);
i2c_master_transmit(s_dev, buf, (size_t)(2U + chunk), 1000);
addr += chunk;
addr += words;
cursor += chunk;
remaining -= chunk;
}
}
int sigma_i2c_read(unsigned int reg, unsigned char* data, unsigned int length)
{
if (s_dev == NULL || data == NULL || length == 0U) {
return -1;
}
unsigned char addrBuf[2U];
addrBuf[0] = (unsigned char)((reg >> 8) & 0xFFU);
addrBuf[1] = (unsigned char)(reg & 0xFFU);
const esp_err_t err = i2c_master_transmit_receive(
s_dev, addrBuf, sizeof(addrBuf), data, (size_t)length, 1000);
return err == ESP_OK ? 0 : -1;
}
static int sigma_i2c_write(unsigned int reg, const unsigned char* data,
unsigned char length)
{
@@ -66,7 +128,7 @@ static int sigma_i2c_write(unsigned int reg, const unsigned char* data,
return -1;
}
unsigned char buf[2U + 4U];
unsigned char buf[2U + 5U];
if ((size_t)length + 2U > sizeof(buf)) {
return -1;
}
@@ -78,14 +140,25 @@ static int sigma_i2c_write(unsigned int reg, const unsigned char* data,
return err == ESP_OK ? 0 : -1;
}
/*
* Safeload Data registers are 5 bytes wide (fixed 0x00 qualifier byte +
* full sign-extended 32-bit value, MSB first) — confirmed against the
* ADAU1701-TCPi-ESP32 reference project's safeloadChunk(), which sends
* the identical 5-byte layout. `fixpoint` is a full-range Q8.23
* two's-complement value (core::floatToFixpoint823): the previous
* 4-byte payload here dropped the top (sign) byte entirely, silently
* corrupting every negative coefficient (e.g. biquad a1/b1, routinely
* negative for peaking/shelving EQ bands).
*/
static int sigma_write_fixpoint_reg(unsigned int reg, int fixpoint)
{
unsigned char payload[4U];
unsigned char payload[5U];
payload[0] = 0U;
payload[1] = (unsigned char)((fixpoint >> 16) & 0xFFU);
payload[2] = (unsigned char)((fixpoint >> 8) & 0xFFU);
payload[3] = (unsigned char)(fixpoint & 0xFFU);
return sigma_i2c_write(reg, payload, 4U);
payload[1] = (unsigned char)(((unsigned int)fixpoint >> 24) & 0xFFU);
payload[2] = (unsigned char)(((unsigned int)fixpoint >> 16) & 0xFFU);
payload[3] = (unsigned char)(((unsigned int)fixpoint >> 8) & 0xFFU);
payload[4] = (unsigned char)((unsigned int)fixpoint & 0xFFU);
return sigma_i2c_write(reg, payload, 5U);
}
static int sigma_write_param_addr(unsigned int reg, unsigned int paramAddr)
@@ -133,3 +206,30 @@ int sigma_safeload_block(unsigned char count, const unsigned int* paramAddrs,
return sigma_trigger_safeload();
}
int sigma_safeload_raw_block(unsigned char count, const unsigned int* paramAddrs,
const unsigned char* rawWords)
{
if (count == 0U || count > 5U || paramAddrs == NULL || rawWords == NULL) {
return -1;
}
for (unsigned char i = 0U; i < count; ++i) {
const unsigned int dataReg =
ADAU1701_SAFELOAD_DATA_BASE + (unsigned int)i;
const unsigned int addrReg =
ADAU1701_SAFELOAD_ADDR_BASE + (unsigned int)i;
unsigned char payload[5U];
payload[0] = 0U;
memcpy(payload + 1U, rawWords + ((unsigned int)i * 4U), 4U);
if (sigma_i2c_write(dataReg, payload, 5U) != 0) {
return -1;
}
if (sigma_write_param_addr(addrReg, paramAddrs[i]) != 0) {
return -1;
}
}
return sigma_trigger_safeload();
}
@@ -16,6 +16,7 @@
#include "core/Bt1035At.hpp"
#include <cstdint>
#include <expected>
#include <string>
#include <string_view>
@@ -36,8 +37,6 @@ namespace bt1035 {
struct Bt1035Pins {
int uartTx; ///< ESP32 TX -> module RX.
int uartRx; ///< ESP32 RX <- module TX.
int rtsGpio; ///< RTS (flow control).
int ctsGpio; ///< CTS (flow control).
int resetGpio; ///< Module RESET (active level per schematic).
int sysCtlGpio; ///< SYS_CTL (optional module enable).
};
@@ -48,7 +47,7 @@ struct Bt1035Pins {
* @dname Bt1035Driver
* @return n/a (type)
* @pubstate Owns UART port after boot(). booted_ true after init sequence
* including AT+AUXCFG=3 and AT+I2SCFG=67 (I2S slave from ADAU1701).
* including AT+AUXCFG=3 and AT+I2SCFG=35 (I2S slave from ADAU1701).
*
* @author Michele Bigi
* @date 2026-07-06
@@ -159,6 +158,22 @@ public:
[[nodiscard]] std::expected<core::Bt1035A2dpState, Bt1035Error>
queryA2dpState();
/**
* @brief queryA2dpEncoder — read negotiated A2DP codec (AT+A2DPENC).
*
* @dname queryA2dpEncoder
* @return Parsed codec on success, or Bt1035Error. Failing here while
* queryA2dpState() reports Streaming means the module has a
* link but is not actually encoding audio — a real fault,
* not just a quiet source.
* @pubstate writes UART; parses +A2DPENC from the reply.
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<core::Bt1035A2dpCodec, Bt1035Error>
queryA2dpEncoder();
/**
* @brief disconnectA2dp — release the active A2DP session (AT+A2DPDISC).
*
@@ -236,15 +251,115 @@ public:
[[nodiscard]] std::expected<std::vector<core::Bt1035PairedDevice>, Bt1035Error>
queryPairedList();
/**
* @brief scanNearbyBrEdr — inquiry for nearby A2DP sink devices.
*
* @dname scanNearbyBrEdr
* @param scanSeconds BR/EDR scan duration 1255 (default 20 in service).
* @return Parsed +SCAN entries, or Bt1035Error.
* @pubstate sends AT+SCAN=1,n and collects until +SCAN=E.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<std::vector<core::Bt1035ScannedDevice>, Bt1035Error>
scanNearbyBrEdr(std::uint8_t scanSeconds = 20U);
/**
* @brief stopScan — abort an active AT+SCAN inquiry.
*
* @dname stopScan
* @return Ok on success, or Bt1035Error.
* @pubstate sends AT+SCAN=0.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<void, Bt1035Error> stopScan();
/**
* @brief prepareForOutgoingConnect — disable auto-link before A2DPCONN.
*
* @dname prepareForOutgoingConnect
* @pubstate sends LINKCFG/AUTOCONN off; does not clear PLIST.
*
* @author Michele Bigi
* @date 2026-08-05
*/
void prepareForOutgoingConnect();
/**
* @brief waitForA2dpConnected — poll until A2DP link is up.
*
* @dname waitForA2dpConnected
* @param timeoutMs Maximum wait in milliseconds.
* @return true when Connected/Streaming/Paused, false on timeout.
* @pubstate polls AT+A2DPSTAT.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool waitForA2dpConnected(int timeoutMs);
/**
* @brief startA2dpAudio — send AT+A2DPAUDIO=1 (Feasycom §5.3.6).
*
* @dname startA2dpAudio
* @return Ok on module OK, or Bt1035Error.
* @pubstate writes UART.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<void, Bt1035Error> startA2dpAudio();
/**
* @brief waitForA2dpStreaming — poll until +A2DPSTAT=4 (Streaming).
*
* @dname waitForA2dpStreaming
* @param timeoutMs Maximum wait in milliseconds.
* @return true when Streaming, false on timeout.
* @pubstate polls AT+A2DPSTAT; sends A2DPAUDIO=1 when stuck at Connected.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool waitForA2dpStreaming(int timeoutMs);
/**
* @brief connectA2dp — pair/connect to a remote by MAC (AT+A2DPCONN).
*
* @dname connectA2dp
* @param mac 12-char ASCII MAC from scan results.
* @return Ok on success, or Bt1035Error.
* @pubstate may take several seconds while the module pairs.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<void, Bt1035Error> connectA2dp(
std::string_view mac);
private:
[[nodiscard]] std::expected<void, Bt1035Error> ensureBooted() const;
[[nodiscard]] std::expected<void, Bt1035Error> runInitSequence();
[[nodiscard]] std::expected<std::string, Bt1035Error> transmitAndCollect(
std::string_view commandLine, int timeoutMs = kResponseTimeoutMs);
[[nodiscard]] std::expected<std::string, Bt1035Error> transmitAndCollectUntil(
std::string_view commandLine, std::string_view endMarker, int timeoutMs,
std::uint8_t minScanSeconds = 0U);
[[nodiscard]] std::expected<void, Bt1035Error> transmitAndExpectOk(
std::string_view commandLine);
[[nodiscard]] std::expected<void, Bt1035Error> transmitAndExpectOkLogged(
std::string_view label, std::string_view commandLine);
void prepareForInquiryScan();
[[nodiscard]] bool waitForA2dpIdle(int timeoutMs);
[[nodiscard]] std::expected<std::vector<core::Bt1035ScannedDevice>, Bt1035Error>
runInquiryScan(std::uint8_t scanType, std::uint8_t scanSeconds);
static constexpr int kResponseTimeoutMs = 2000;
static constexpr int kConnectTimeoutMs = 15000;
Bt1035Pins pins_;
bool booted_;
@@ -13,6 +13,7 @@
#include "bt1035/Bt1035Driver.hpp"
#include "core/Bt1035ScannedDevice.hpp"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
@@ -20,6 +21,8 @@
#include "freertos/task.h"
#include <array>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <string_view>
#include <vector>
@@ -30,11 +33,218 @@ namespace {
constexpr char kTag[] = "Bt1035";
constexpr int kUartPort = 2;
constexpr int kBaudRate = 115200;
constexpr int kUartRxBuffer = 512;
constexpr int kUartRxBuffer = 4096;
constexpr int kUartTxBuffer = 256;
constexpr int kResponseTimeoutMs = 2000;
constexpr int kPostResetMs = 500;
constexpr int kPostUartMs = 100;
void flushUartRx(int uartPort) noexcept
{
uart_flush_input(static_cast<uart_port_t>(uartPort));
std::array<char, 256> discard{};
while (uart_read_bytes(static_cast<uart_port_t>(uartPort), discard.data(),
discard.size(), 0)
> 0) {
}
}
constexpr int kBrEdrScanTimeoutMs = 90000;
constexpr int kScanProgressLogMs = 5000;
constexpr int kScanIdleCompleteMs = 4000;
constexpr int kDefaultScanListenSeconds = 25;
constexpr unsigned long kDevStatScanComplete = 1U;
void logAtLine(std::string_view label, std::string_view line) noexcept
{
std::string sanitized;
sanitized.reserve(line.size());
for (const char ch : line) {
if (ch == '\r') {
sanitized.append("\\r");
} else if (ch == '\n') {
sanitized.append("\\n");
} else if (ch >= 0x20 && ch < 0x7F) {
sanitized.push_back(ch);
} else {
sanitized.push_back('.');
}
}
ESP_LOGI(kTag, "%.*s: %s", static_cast<int>(label.size()), label.data(),
sanitized.c_str());
}
[[nodiscard]] bool responseHasScanEnd(std::string_view response) noexcept
{
return response.find("+SCAN=E") != std::string_view::npos
|| response.find("+SCAN= E") != std::string_view::npos
|| response.find("+SCAN=END") != std::string_view::npos
|| response.find("+SCAN= END") != std::string_view::npos;
}
[[nodiscard]] bool responseHasScanEntries(std::string_view response) noexcept
{
return response.find("+SCAN=") != std::string_view::npos
|| response.find("+SCAN =") != std::string_view::npos;
}
[[nodiscard]] bool responseHasDevStatScanComplete(
std::string_view response) noexcept
{
if (!responseHasScanEntries(response)) {
return false;
}
constexpr std::string_view kPrefix = "+DEVSTAT=";
std::size_t pos = 0;
unsigned long lastValue = 0U;
bool found = false;
while ((pos = response.find(kPrefix, pos)) != std::string_view::npos) {
const std::size_t start = pos + kPrefix.size();
char* end = nullptr;
const unsigned long raw =
std::strtoul(response.data() + start, &end, 10);
if (end != response.data() + start) {
lastValue = raw;
found = true;
}
pos = start + 1U;
}
return found && lastValue == kDevStatScanComplete;
}
[[nodiscard]] std::size_t countScanEntries(std::string_view response) noexcept
{
std::size_t count = 0U;
std::size_t pos = 0;
while ((pos = response.find("+SCAN=", pos)) != std::string_view::npos) {
const std::size_t valueStart = pos + 6U;
if (valueStart < response.size() && response[valueStart] != 'E') {
++count;
}
pos = valueStart + 1U;
}
return count;
}
enum class ScanCollectReason {
EndMarker,
DevStatComplete,
IdleAfterScan,
TimeoutPartial,
};
[[nodiscard]] bool scanCollectionShouldStop(std::string_view response,
std::size_t scanEntryCount,
TickType_t lastRxTick,
TickType_t now,
TickType_t started,
std::uint8_t minScanSeconds,
ScanCollectReason* reason) noexcept
{
if (!response.empty() && responseHasScanEnd(response)) {
if (reason != nullptr) {
*reason = ScanCollectReason::EndMarker;
}
return true;
}
const int elapsedMs = static_cast<int>(pdTICKS_TO_MS(now - started));
const int minListenMs =
minScanSeconds > 0U
? static_cast<int>(minScanSeconds) * 1000
: kDefaultScanListenSeconds * 1000;
const bool minListenElapsed = elapsedMs >= minListenMs;
if (minListenElapsed && responseHasDevStatScanComplete(response)) {
if (reason != nullptr) {
*reason = ScanCollectReason::DevStatComplete;
}
return true;
}
if (minListenElapsed && scanEntryCount > 0U && lastRxTick > 0U
&& (now - lastRxTick) >= pdMS_TO_TICKS(kScanIdleCompleteMs)) {
if (reason != nullptr) {
*reason = ScanCollectReason::IdleAfterScan;
}
return true;
}
return false;
}
void logScanRawPayload(std::string_view label, std::string_view response) noexcept
{
if (response.empty()) {
ESP_LOGI(kTag, "%.*s (0 bytes)", static_cast<int>(label.size()),
label.data());
return;
}
std::string sanitized;
sanitized.reserve(std::min(response.size(), std::size_t{512U}));
for (const char ch : response) {
if (ch == '\r') {
sanitized.append("\\r");
} else if (ch == '\n') {
sanitized.append("\\n");
} else if (ch >= 0x20 && ch < 0x7F) {
sanitized.push_back(ch);
} else {
sanitized.push_back('.');
}
}
constexpr std::size_t kChunk = 480U;
if (sanitized.size() <= kChunk) {
ESP_LOGI(kTag, "%.*s (%d bytes): %s",
static_cast<int>(label.size()), label.data(),
static_cast<int>(response.size()), sanitized.c_str());
return;
}
ESP_LOGI(kTag, "%.*s (%d bytes, part 1/%u): %.*s",
static_cast<int>(label.size()), label.data(),
static_cast<int>(response.size()),
static_cast<unsigned>(
(sanitized.size() + kChunk - 1U) / kChunk),
static_cast<int>(kChunk), sanitized.c_str());
for (std::size_t off = kChunk; off < sanitized.size(); off += kChunk) {
const std::size_t len = std::min(kChunk, sanitized.size() - off);
ESP_LOGI(kTag, "%.*s (cont %u): %.*s",
static_cast<int>(label.size()), label.data(),
static_cast<unsigned>(off / kChunk + 1U),
static_cast<int>(len), sanitized.c_str() + off);
}
}
void logScanUartChunk(int received, std::string_view chunk) noexcept
{
const std::string label = "scan UART RX +" + std::to_string(received);
logScanRawPayload(label, chunk);
}
[[nodiscard]] const char* scanCollectReasonLabel(
ScanCollectReason reason) noexcept
{
switch (reason) {
case ScanCollectReason::EndMarker:
return "+SCAN=E/end marker";
case ScanCollectReason::DevStatComplete:
return "+DEVSTAT=1";
case ScanCollectReason::IdleAfterScan:
return "UART idle after +SCAN";
case ScanCollectReason::TimeoutPartial:
return "timeout";
}
return "unknown";
}
void logScanParsedDevice(const core::Bt1035ScannedDevice& device) noexcept
{
ESP_LOGI(kTag,
"scan device[%u]: mac=%s name=%s rssi=%d dBm class=%s addrType=%u",
static_cast<unsigned>(device.index), device.mac.c_str(),
device.name.empty() ? "(no name)" : device.name.c_str(),
static_cast<int>(device.rssiDbm),
device.deviceClass.empty() ? "-" : device.deviceClass.c_str(),
static_cast<unsigned>(device.addressType));
}
} // namespace
Bt1035Driver::Bt1035Driver(Bt1035Pins pins)
@@ -102,6 +312,284 @@ std::expected<std::string, Bt1035Error> Bt1035Driver::transmitAndCollect(
return std::unexpected(Bt1035Error::AtTimeout);
}
std::expected<std::string, Bt1035Error> Bt1035Driver::transmitAndCollectUntil(
std::string_view commandLine, std::string_view endMarker, int timeoutMs,
std::uint8_t minScanSeconds)
{
logAtLine("scan UART TX", commandLine);
const int written = uart_write_bytes(static_cast<uart_port_t>(uartPort_),
commandLine.data(),
commandLine.size());
if (written < 0
|| static_cast<std::size_t>(written) != commandLine.size()) {
ESP_LOGE(kTag, "scan UART TX failed (%d)", written);
return std::unexpected(Bt1035Error::UartInitFailed);
}
std::array<char, 512> buffer{};
std::string accumulated;
accumulated.reserve(8192U);
const TickType_t started = xTaskGetTickCount();
const TickType_t deadline = started + pdMS_TO_TICKS(timeoutMs);
TickType_t lastProgressLog = started;
TickType_t lastRxTick = 0;
std::size_t scanEntryCount = 0U;
ScanCollectReason stopReason = ScanCollectReason::TimeoutPartial;
bool stoppedEarly = false;
ESP_LOGI(kTag, "======== BT scan inquiry begin (min %u s, timeout %d ms) ========",
static_cast<unsigned>(minScanSeconds), timeoutMs);
while (xTaskGetTickCount() < deadline) {
const int received = uart_read_bytes(static_cast<uart_port_t>(uartPort_),
buffer.data(), buffer.size(),
pdMS_TO_TICKS(200));
const TickType_t now = xTaskGetTickCount();
if (received > 0) {
const std::string_view chunk(buffer.data(),
static_cast<std::size_t>(received));
logScanUartChunk(received, chunk);
accumulated.append(buffer.data(), static_cast<std::size_t>(received));
lastRxTick = now;
const std::size_t updatedScanCount =
countScanEntries(accumulated);
if (updatedScanCount > scanEntryCount) {
ESP_LOGI(kTag, "scan +SCAN entry #%u (total %u)",
static_cast<unsigned>(updatedScanCount),
static_cast<unsigned>(updatedScanCount));
scanEntryCount = updatedScanCount;
}
if (!endMarker.empty()
&& accumulated.find(endMarker) != std::string::npos) {
stopReason = ScanCollectReason::EndMarker;
stoppedEarly = true;
ESP_LOGI(kTag, "scan end marker %.*s seen",
static_cast<int>(endMarker.size()), endMarker.data());
break;
}
if (scanCollectionShouldStop(accumulated, scanEntryCount, lastRxTick,
now, started, minScanSeconds,
&stopReason)) {
stoppedEarly = true;
if (stopReason == ScanCollectReason::EndMarker) {
ESP_LOGI(kTag, "scan end marker +SCAN=E seen");
} else if (stopReason == ScanCollectReason::DevStatComplete) {
ESP_LOGI(kTag,
"scan complete: +DEVSTAT=1 after %u +SCAN entr(ies)",
static_cast<unsigned>(scanEntryCount));
}
break;
}
if (core::parseBt1035AtResponse(accumulated)
== core::Bt1035AtResponseKind::Error) {
logScanRawPayload("scan ERROR response", accumulated);
return std::unexpected(Bt1035Error::AtError);
}
} else if (scanEntryCount > 0U
&& scanCollectionShouldStop(accumulated, scanEntryCount,
lastRxTick, now, started,
minScanSeconds, &stopReason)) {
stoppedEarly = true;
if (stopReason == ScanCollectReason::IdleAfterScan) {
ESP_LOGI(kTag,
"scan complete: idle %d ms after last +SCAN (%u entr(ies))",
kScanIdleCompleteMs,
static_cast<unsigned>(scanEntryCount));
}
break;
}
if ((now - lastProgressLog) >= pdMS_TO_TICKS(kScanProgressLogMs)) {
lastProgressLog = now;
const int elapsedMs =
static_cast<int>(pdTICKS_TO_MS(now - started));
const int minListenMs =
minScanSeconds > 0U
? static_cast<int>(minScanSeconds) * 1000
: kDefaultScanListenSeconds * 1000;
ESP_LOGI(kTag,
"scan progress: %d ms, %d bytes rx, %u +SCAN, min listen %d ms",
elapsedMs, static_cast<int>(accumulated.size()),
static_cast<unsigned>(scanEntryCount), minListenMs);
}
}
const int elapsedMs =
static_cast<int>(pdTICKS_TO_MS(xTaskGetTickCount() - started));
if (stoppedEarly) {
ESP_LOGI(kTag, "scan stopped: reason=%s, elapsed=%d ms, %u +SCAN entr(ies)",
scanCollectReasonLabel(stopReason), elapsedMs,
static_cast<unsigned>(scanEntryCount));
} else {
stopReason = ScanCollectReason::TimeoutPartial;
ESP_LOGW(kTag, "scan stopped: reason=%s, elapsed=%d ms, %u +SCAN entr(ies)",
scanCollectReasonLabel(stopReason), elapsedMs,
static_cast<unsigned>(scanEntryCount));
}
logScanRawPayload("scan UART full RX", accumulated);
const bool hasEnd = stoppedEarly
|| (!endMarker.empty()
? accumulated.find(endMarker)
!= std::string::npos
: responseHasScanEnd(accumulated));
if (!hasEnd) {
if (!responseHasScanEntries(accumulated)) {
if (accumulated.find("+A2DPSTAT=2") != std::string_view::npos) {
ESP_LOGW(kTag,
"scan blocked: module auto-connecting A2DP (disable LINKCFG)");
}
if (accumulated.find("+DEVSTAT=") != std::string_view::npos) {
ESP_LOGW(kTag, "scan saw DEVSTAT events but no +SCAN= lines");
}
ESP_LOGW(kTag, "scan timeout (%d bytes rx, no +SCAN entries)",
static_cast<int>(accumulated.size()));
(void)transmitAndCollect(core::buildBt1035StopScanLine(), 1000);
return std::unexpected(Bt1035Error::AtTimeout);
}
ESP_LOGW(kTag, "scan timeout but %d bytes contain +SCAN entries — parsing partial",
static_cast<int>(accumulated.size()));
}
ESP_LOGI(kTag, "======== BT scan inquiry end ========");
return accumulated;
}
std::expected<void, Bt1035Error> Bt1035Driver::transmitAndExpectOkLogged(
std::string_view label, std::string_view commandLine)
{
logAtLine(label, commandLine);
auto collected = transmitAndCollect(commandLine);
if (collected) {
if (!collected->empty()) {
const std::string rxLabel = std::string(label) + " RX";
logScanRawPayload(rxLabel, *collected);
}
ESP_LOGI(kTag, "%.*s: OK", static_cast<int>(label.size()), label.data());
return {};
}
if (collected.error() == Bt1035Error::AtTimeout) {
ESP_LOGW(kTag, "%.*s: timeout (no response)", static_cast<int>(label.size()),
label.data());
} else {
ESP_LOGW(kTag, "%.*s: failed (%d)", static_cast<int>(label.size()),
label.data(), static_cast<int>(collected.error()));
}
return std::unexpected(collected.error());
}
void Bt1035Driver::prepareForInquiryScan()
{
ESP_LOGI(kTag, "======== BT scan prep begin ========");
flushUartRx(uartPort_);
(void)transmitAndExpectOkLogged("scan prep PRINT",
core::buildBt1035EnablePrintLine());
(void)transmitAndExpectOkLogged("scan prep LINKCFG off",
core::buildBt1035DisableAutoLinkLine());
(void)transmitAndExpectOkLogged("scan prep AUTOCONN off",
core::buildBt1035SetAutoConnLine(0U));
(void)transmitAndExpectOkLogged("scan prep PLIST clear",
core::buildBt1035ClearPairedListLine());
(void)transmitAndExpectOkLogged(
"scan prep A2DPDISC",
core::buildBt1035AtLine(core::Bt1035AtCommand::A2dpDisconnect));
(void)transmitAndExpectOkLogged("scan prep DSCA",
core::buildBt1035DisconnectAllLine());
(void)transmitAndExpectOkLogged(
"scan prep PAIR=0",
core::buildBt1035AtLine(core::Bt1035AtCommand::PairHidden));
auto stopScan = transmitAndCollect(core::buildBt1035StopScanLine(), 1000);
if (stopScan) {
logScanRawPayload("scan prep SCAN=0 RX", *stopScan);
ESP_LOGI(kTag, "scan prep SCAN=0: OK");
} else {
ESP_LOGI(kTag, "scan prep SCAN=0: skipped (no active scan)");
}
flushUartRx(uartPort_);
vTaskDelay(pdMS_TO_TICKS(500));
if (!waitForA2dpIdle(10000)) {
ESP_LOGW(kTag, "scan prep: A2DP still busy — continuing anyway");
}
if (auto paired = queryPairedList(); paired && !paired->empty()) {
ESP_LOGI(kTag, "scan prep: %u paired device(s) on module",
static_cast<unsigned>(paired->size()));
for (const core::Bt1035PairedDevice& entry : *paired) {
ESP_LOGI(kTag, "scan prep paired: %s (%s)",
entry.mac.c_str(),
entry.name.empty() ? "(no name)" : entry.name.c_str());
}
}
flushUartRx(uartPort_);
ESP_LOGI(kTag, "======== BT scan prep end ========");
}
bool Bt1035Driver::waitForA2dpIdle(int timeoutMs)
{
const TickType_t deadline =
xTaskGetTickCount() + pdMS_TO_TICKS(timeoutMs);
TickType_t lastDisconnectAttempt = 0;
while (xTaskGetTickCount() < deadline) {
auto state = queryA2dpState();
if (!state) {
ESP_LOGW(kTag, "scan prep: A2DPSTAT query failed");
return false;
}
ESP_LOGI(kTag, "scan prep: A2DPSTAT=%u",
static_cast<unsigned>(static_cast<std::uint8_t>(*state)));
if (*state == core::Bt1035A2dpState::Standby
|| *state == core::Bt1035A2dpState::Unsupported) {
return true;
}
const TickType_t now = xTaskGetTickCount();
if ((now - lastDisconnectAttempt) >= pdMS_TO_TICKS(1500)) {
lastDisconnectAttempt = now;
ESP_LOGW(kTag, "scan prep: A2DP busy — sending A2DPDISC+DSCA");
(void)transmitAndExpectOk(
core::buildBt1035AtLine(core::Bt1035AtCommand::A2dpDisconnect));
(void)transmitAndExpectOk(core::buildBt1035DisconnectAllLine());
flushUartRx(uartPort_);
}
vTaskDelay(pdMS_TO_TICKS(400));
}
return false;
}
std::expected<std::vector<core::Bt1035ScannedDevice>, Bt1035Error>
Bt1035Driver::runInquiryScan(std::uint8_t scanType, std::uint8_t scanSeconds)
{
const std::string startLine =
core::buildBt1035StartScanLine(scanType, scanSeconds);
const int timeoutMs = scanSeconds == 0U
? kBrEdrScanTimeoutMs
: static_cast<int>(scanSeconds) * 1000 + 30000;
flushUartRx(uartPort_);
ESP_LOGI(kTag, "scan inquiry: type=%u seconds=%u timeout=%d ms",
static_cast<unsigned>(scanType),
static_cast<unsigned>(scanSeconds), timeoutMs);
auto response = transmitAndCollectUntil(startLine, {}, timeoutMs, scanSeconds);
if (!response) {
ESP_LOGW(kTag, "scan inquiry failed (type %u)",
static_cast<unsigned>(scanType));
return std::unexpected(response.error());
}
auto parsed = core::parseBt1035ScanResponse(*response);
if (!parsed) {
ESP_LOGW(kTag, "scan parse failed (type %u)",
static_cast<unsigned>(scanType));
return std::unexpected(Bt1035Error::UnexpectedResponse);
}
ESP_LOGI(kTag, "scan parsed %u device(s)",
static_cast<unsigned>(parsed->size()));
for (const core::Bt1035ScannedDevice& device : *parsed) {
logScanParsedDevice(device);
}
return *parsed;
}
std::expected<void, Bt1035Error> Bt1035Driver::transmitAndExpectOk(
std::string_view commandLine)
{
@@ -156,6 +644,25 @@ std::expected<core::Bt1035A2dpState, Bt1035Error> Bt1035Driver::queryA2dpState()
return *parsed;
}
std::expected<core::Bt1035A2dpCodec, Bt1035Error> Bt1035Driver::queryA2dpEncoder()
{
if (auto ready = ensureBooted(); !ready) {
return std::unexpected(ready.error());
}
auto response = transmitAndCollect(
core::buildBt1035AtLine(core::Bt1035AtCommand::A2dpEncoder));
if (!response) {
return std::unexpected(response.error());
}
auto parsed = core::parseBt1035A2dpEncoderResponse(*response);
if (!parsed) {
return std::unexpected(Bt1035Error::UnexpectedResponse);
}
return *parsed;
}
std::expected<void, Bt1035Error> Bt1035Driver::disconnectA2dp()
{
if (auto ready = ensureBooted(); !ready) {
@@ -243,6 +750,133 @@ Bt1035Driver::queryPairedList()
return *parsed;
}
std::expected<std::vector<core::Bt1035ScannedDevice>, Bt1035Error>
Bt1035Driver::scanNearbyBrEdr(std::uint8_t scanSeconds)
{
if (auto ready = ensureBooted(); !ready) {
return std::unexpected(ready.error());
}
ESP_LOGI(kTag, "======== BT scan session begin (%u s) ========",
static_cast<unsigned>(scanSeconds));
prepareForInquiryScan();
auto result = runInquiryScan(1U, scanSeconds);
ESP_LOGI(kTag, "scan post-cleanup: A2DPDISC+DSCA+SCAN=0");
(void)transmitAndExpectOkLogged(
"scan post A2DPDISC",
core::buildBt1035AtLine(core::Bt1035AtCommand::A2dpDisconnect));
(void)transmitAndExpectOkLogged("scan post DSCA",
core::buildBt1035DisconnectAllLine());
(void)transmitAndExpectOkLogged("scan post SCAN=0",
core::buildBt1035StopScanLine());
if (result) {
ESP_LOGI(kTag, "======== BT scan session OK: %u device(s) ========",
static_cast<unsigned>(result->size()));
} else {
ESP_LOGW(kTag, "======== BT scan session FAILED ========");
}
return result;
}
std::expected<void, Bt1035Error> Bt1035Driver::stopScan()
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
return transmitAndExpectOk(core::buildBt1035StopScanLine());
}
void Bt1035Driver::prepareForOutgoingConnect()
{
ESP_LOGI(kTag, "connect prep: LINKCFG/AUTOCONN off");
(void)transmitAndExpectOk(core::buildBt1035DisableAutoLinkLine());
(void)transmitAndExpectOk(core::buildBt1035SetAutoConnLine(0U));
flushUartRx(uartPort_);
}
bool Bt1035Driver::waitForA2dpConnected(int timeoutMs)
{
const TickType_t deadline =
xTaskGetTickCount() + pdMS_TO_TICKS(timeoutMs);
while (xTaskGetTickCount() < deadline) {
auto state = queryA2dpState();
if (!state) {
return false;
}
ESP_LOGI(kTag, "connect wait: A2DPSTAT=%u",
static_cast<unsigned>(static_cast<std::uint8_t>(*state)));
if (*state == core::Bt1035A2dpState::Connected
|| *state == core::Bt1035A2dpState::Streaming
|| *state == core::Bt1035A2dpState::Paused) {
return true;
}
vTaskDelay(pdMS_TO_TICKS(500));
}
return false;
}
std::expected<void, Bt1035Error> Bt1035Driver::startA2dpAudio()
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
ESP_LOGI(kTag, "A2DP audio start (AT+A2DPAUDIO=1)");
return transmitAndExpectOk(core::buildBt1035A2dpAudioLine(true));
}
bool Bt1035Driver::waitForA2dpStreaming(int timeoutMs)
{
const TickType_t deadline =
xTaskGetTickCount() + pdMS_TO_TICKS(timeoutMs);
TickType_t lastAudioStartAttempt = 0;
while (xTaskGetTickCount() < deadline) {
auto state = queryA2dpState();
if (state) {
ESP_LOGI(kTag, "stream wait: A2DPSTAT=%u",
static_cast<unsigned>(static_cast<std::uint8_t>(*state)));
if (*state == core::Bt1035A2dpState::Streaming) {
return true;
}
const TickType_t now = xTaskGetTickCount();
if ((*state == core::Bt1035A2dpState::Connected
|| *state == core::Bt1035A2dpState::Paused)
&& (lastAudioStartAttempt == 0U
|| (now - lastAudioStartAttempt) >= pdMS_TO_TICKS(3000))) {
lastAudioStartAttempt = now;
if (auto started = startA2dpAudio(); !started) {
ESP_LOGW(kTag, "A2DPAUDIO=1 failed (%d)",
static_cast<int>(started.error()));
}
}
}
vTaskDelay(pdMS_TO_TICKS(500));
}
return false;
}
std::expected<void, Bt1035Error> Bt1035Driver::connectA2dp(std::string_view mac)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
prepareForOutgoingConnect();
const std::string line = core::buildBt1035A2dpConnectLine(mac);
if (line.empty()) {
return std::unexpected(Bt1035Error::UnexpectedResponse);
}
(void)stopScan();
auto response = transmitAndCollect(line, kConnectTimeoutMs);
if (!response) {
return std::unexpected(response.error());
}
return {};
}
std::expected<void, Bt1035Error> Bt1035Driver::runInitSequence()
{
for (const core::Bt1035AtCommand command : core::bootInitSequence()) {
@@ -314,13 +948,24 @@ std::expected<void, Bt1035Error> Bt1035Driver::boot()
uart_flush_input(static_cast<uart_port_t>(uartPort_));
vTaskDelay(pdMS_TO_TICKS(kPostUartMs));
// Best-effort: not gated on OK — some Feasycom firmware acks before
// rebooting, some resets silently. Either way, settle and flush before
// the mandatory init sequence below, which IS gated.
(void)transmitAndExpectOk(core::buildBt1035AtLine(core::Bt1035AtCommand::Reset));
vTaskDelay(pdMS_TO_TICKS(kPostResetMs));
uart_flush_input(static_cast<uart_port_t>(uartPort_));
ESP_LOGI(kTag, "AT+RESET sent");
if (auto init = runInitSequence(); !init) {
ESP_LOGE(kTag, "AT init failed");
return init;
}
(void)transmitAndExpectOk(core::buildBt1035DisableAutoLinkLine());
ESP_LOGI(kTag, "auto-link disabled (AT+LINKCFG=0,0)");
booted_ = true;
ESP_LOGI(kTag, "I2S slave mode enabled (AT+AUXCFG=3, AT+I2SCFG=67)");
ESP_LOGI(kTag, "I2S slave mode enabled (AT+AUXCFG=3, AT+I2SCFG=35)");
return {};
}
@@ -106,14 +106,21 @@ public:
* rail ramp before app_main runs.
*
* @dname boot
* @param band DAB or FM application to load.
* @param band DAB or FM application to load.
* @param xtalIbias POWER_UP ARG3 IBIAS, 10 uA steps (AN649 §Command
* 0x01); default matches the values already
* verified live (72 = 720 uA startup bias).
* @param xtalCtun POWER_UP ARG8 CTUN, 0-63 (AN649 §Command 0x01);
* default matches the values already verified live.
* @return Ok on success, or Si4684Error.
* @pubstate writes booted_ and loadedBand_ on success.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> boot(Si4684Band band);
[[nodiscard]] std::expected<void, Si4684Error> boot(
Si4684Band band, std::uint8_t xtalIbias = 72U,
std::uint8_t xtalCtun = 31U);
/**
* @brief isBooted — query whether boot completed successfully.
@@ -387,7 +394,10 @@ private:
[[nodiscard]] std::expected<void, Si4684Error> ensureBand(
Si4684Band band) const;
[[nodiscard]] std::expected<void, Si4684Error> waitCts();
[[nodiscard]] std::expected<void, Si4684Error> waitStc();
[[nodiscard]] std::expected<void, Si4684Error> waitStc(
int maxRetries = 250);
[[nodiscard]] std::expected<bool, Si4684Error> pollStc();
[[nodiscard]] std::expected<void, Si4684Error> clearFmStc();
[[nodiscard]] std::expected<void, Si4684Error> sendCommand(
std::span<const std::uint8_t> bytes);
[[nodiscard]] std::expected<void, Si4684Error> readRaw(
@@ -190,10 +190,25 @@ private:
*/
[[nodiscard]] static core::TunerError mapError(Si4684Error error) noexcept;
/**
* @brief ensureBandLoaded — HOST_LOAD FM/DAB image when band differs.
*
* @dname ensureBandLoaded
* @param band Required application band for the next operation.
* @return Ok when the chip runs the requested image.
* @pubstate may reload firmware (~1 s) and stop an active DAB service.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<void, core::TunerError> ensureBandLoaded(
core::TunerBand band);
Si4684Driver& driver_;
std::uint8_t dabIndex_;
core::FrequencyKHz fmFrequency_;
std::uint8_t volume_;
bool fmBandReady_;
core::RdsMetadataAccumulator rdsMetadata_;
core::DabDynamicLabelAccumulator dabDynamicLabel_;
std::optional<std::uint32_t> lastDabServiceId_;
@@ -18,6 +18,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <vector>
namespace si4684 {
@@ -91,11 +92,11 @@ struct Si4684SysState {
* @date 2026-07-06
*/
struct Si4684FmRsq {
core::FrequencyKHz frequency; ///< Tuned centre frequency in kHz.
std::int8_t rssiDbuV; ///< RSSI in dBµV.
std::int8_t snrDb; ///< SNR in dB.
bool valid; ///< RSQ valid flag from the chip.
bool stereo; ///< Stereo pilot detected.
std::optional<core::FrequencyKHz> frequency; ///< READFREQ when in FM band.
std::int8_t rssiDbuV; ///< RSSI in dBµV.
std::int8_t snrDb; ///< SNR in dB.
bool valid; ///< RSQ valid flag from the chip.
bool stereo; ///< Stereo pilot detected.
};
/**
@@ -35,15 +35,58 @@ constexpr int kCtsPollMs = 2;
constexpr int kCtsRetries = 5000;
constexpr int kStcRetries = 250;
constexpr int kStcPollMs = 20;
/** SPI readRaw: byte 0 is a lead-in; STATUS0 is at index 1 (AN649). */
constexpr std::size_t kSpiReplyLeadIn = 1U;
/** FM_RSQ_STATUS field indices with kSpiReplyLeadIn (AN649 RESP510). */
constexpr std::size_t kFmRsqOffValid = 6U;
constexpr std::size_t kFmRsqOffReadFreq = 7U;
constexpr std::size_t kFmRsqOffRssi = 10U;
constexpr std::size_t kFmRsqOffSnr = 11U;
constexpr std::uint16_t kPropDigitalIoOutputSelect = 0x0200U;
constexpr std::uint16_t kPropDigitalIoOutputFormat = 0x0202U;
constexpr std::uint16_t kPropDigitalIoSampleRate = 0x0201U;
/** I2S slave — ADAU1701 is bus master (AN649 property 0x0200, bit15=0). */
constexpr std::uint16_t kSi4684I2sSlaveSelect = 0x0000U;
/** 24-bit samples in 32-bit I2S slots (SAMPL=0x18, SLOT=0x7, I2S mode). */
constexpr std::uint16_t kSi4684I2sOutputFormat = 0x1870U;
/** 48000 Hz — matches ADAU1701 48 kHz master clock domain. */
constexpr std::uint16_t kSi4684I2sSampleRateHz = 0xBB80U;
constexpr std::uint16_t kPropPinConfigEnable = 0x0800U;
constexpr std::uint16_t kPropAudioVolume = 0x0300U;
constexpr std::uint16_t kPropAudioMute = 0x0301U;
constexpr std::uint16_t kPropAudioOutputConfig = 0x0302U;
/** AN649 AUDIO_OUTPUT_CONFIG bit1 I2SOUTEN — required for I2S to ADAU1701. */
constexpr std::uint16_t kSi4684I2sOutEnable = 0x0002U;
/** Si4684 volume: 0=mute, 63=max (AN649 AUDIO_ANALOG_VOLUME). */
constexpr std::uint8_t kSi4684VolumeMax = 63U;
constexpr std::uint16_t kPropFmRdsConfig = 0x3C02U;
/** AN649 FM valid tune properties (defaults RSSI 17 dBµV, SNR 10 dB). */
constexpr std::uint16_t kPropFmValidRssiThreshold = 0x3202U;
constexpr std::uint16_t kPropFmValidSnrThreshold = 0x3204U;
constexpr std::uint16_t kFmValidRssiThresholdDbuV = 0x0005U;
constexpr std::uint16_t kFmValidSnrThresholdDb = 0x0003U;
/** AN649 FM seek band/spacing (10 kHz units): 87.5107.9 MHz, 100 kHz steps. */
constexpr std::uint16_t kPropFmSeekBandBottom = 0x3100U;
constexpr std::uint16_t kPropFmSeekBandTop = 0x3101U;
constexpr std::uint16_t kPropFmSeekSpacing = 0x3102U;
constexpr std::uint16_t kFmSeekBandBottomChip = 8750U;
constexpr std::uint16_t kFmSeekBandTopChip = 10790U;
constexpr std::uint16_t kFmSeekSpacingChip = 10U;
/** AN851 §"Varactor Tuning Properties" recommended-network table: FM slope/
* intercept for 0x1710/0x1711 (Table, "FM" row: 0xEDB5 / 0x01E3). */
constexpr std::uint16_t kFmTuneFeVarm = 0xEDB5U;
constexpr std::uint16_t kFmTuneFeVarb = 0x01E3U;
constexpr std::uint16_t kFmTuneFeCfgEnable = 0x0001U;
constexpr std::uint16_t kPropDabTuneFeCfg = 0x1712U;
constexpr std::uint16_t kPropFmTuneFeCfg = 0x1712U;
constexpr std::uint16_t kPropDabXpadEnable = 0xB400U;
constexpr std::uint16_t kPropDigitalServiceIntSource = 0x8100U;
/** AN649 INT_CTL_ENABLE / INT_CTL_REPEAT — route STC to INTB until STCACK. */
constexpr std::uint16_t kPropIntCtlEnable = 0x0000U;
constexpr std::uint16_t kPropIntCtlRepeat = 0x0001U;
constexpr std::uint16_t kIntCtlStcEnable = 0x0001U; ///< STCIEN
constexpr std::uint16_t kIntCtlStcRepeat = 0x0001U; ///< STCREP
std::uint16_t readLe16(const std::uint8_t* p)
{
@@ -140,28 +183,83 @@ std::expected<void, Si4684Error> Si4684Driver::waitCts()
return std::unexpected(Si4684Error::CtsTimeout);
}
std::expected<void, Si4684Error> Si4684Driver::waitStc()
std::expected<bool, Si4684Error> Si4684Driver::pollStc()
{
std::array<std::uint8_t, 5> pollTx = {};
std::array<std::uint8_t, 5> pollRx = {};
for (int attempt = 0; attempt < kStcRetries; ++attempt) {
vTaskDelay(pdMS_TO_TICKS(kStcPollMs));
spi_transaction_t txn = {};
txn.length = pollTx.size() * 8U;
txn.tx_buffer = pollTx.data();
txn.rx_buffer = pollRx.data();
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
&txn) != ESP_OK) {
return std::unexpected(Si4684Error::SpiInitFailed);
spi_transaction_t txn = {};
txn.length = pollTx.size() * 8U;
txn.tx_buffer = pollTx.data();
txn.rx_buffer = pollRx.data();
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
&txn) != ESP_OK) {
return std::unexpected(Si4684Error::SpiInitFailed);
}
// STATUS0 STCINT (AN649 D0) is at pollRx[1] after the SPI lead-in byte.
if ((pollRx[1] & 0x01U) != 0U) {
return true;
}
if (pins_.intbGpio >= 0) {
const gpio_num_t intb = static_cast<gpio_num_t>(pins_.intbGpio);
if (gpio_get_level(intb) == 0) {
return true;
}
if ((pollRx[1] & 0x01U) != 0U) {
}
return false;
}
std::expected<void, Si4684Error> Si4684Driver::waitStc(int maxRetries)
{
for (int attempt = 0; attempt < maxRetries; ++attempt) {
vTaskDelay(pdMS_TO_TICKS(kStcPollMs));
auto stc = pollStc();
if (!stc) {
return std::unexpected(stc.error());
}
if (*stc) {
return {};
}
}
// Diagnostic: dump the final poll so a STC timeout is distinguishable
// between "chip replies but STCINT never sets" (register/offset bug)
// and "chip stopped replying" (SPI/CTS problem) without guessing.
std::array<std::uint8_t, 5> pollTx = {};
std::array<std::uint8_t, 5> pollRx = {};
spi_transaction_t txn = {};
txn.length = pollTx.size() * 8U;
txn.tx_buffer = pollTx.data();
txn.rx_buffer = pollRx.data();
const esp_err_t spiResult = spi_device_transmit(
static_cast<spi_device_handle_t>(spiDevice_), &txn);
const int intbLevel = pins_.intbGpio >= 0
? gpio_get_level(static_cast<gpio_num_t>(pins_.intbGpio))
: -1;
ESP_LOGW(kTag,
"STC timeout: last poll spi_err=%d status=%02x %02x %02x %02x "
"%02x INTB=%d",
static_cast<int>(spiResult), pollRx[0], pollRx[1], pollRx[2],
pollRx[3], pollRx[4], intbLevel);
return std::unexpected(Si4684Error::StcTimeout);
}
std::expected<void, Si4684Error> Si4684Driver::clearFmStc()
{
if (auto band = ensureBand(Si4684Band::Fm); !band) {
return band;
}
// AN649 FM_RSQ_STATUS ARG1 STCACK=1 clears a latched STCINT.
const std::uint8_t args[] = {0x01U};
if (auto cmd = writeCommand(Command::FmRsqStatus, args, sizeof(args));
!cmd) {
return cmd;
}
std::array<std::uint8_t, 23> raw = {};
if (auto rd = readRaw(raw); !rd) {
return rd;
}
return {};
}
std::expected<void, Si4684Error> Si4684Driver::sendCommand(
std::span<const std::uint8_t> bytes)
{
@@ -314,7 +412,7 @@ std::expected<Si4684PartInfo, Si4684Error> Si4684Driver::getPartInfo()
}
Si4684PartInfo info = {};
info.chipId = readLe16(raw.data() + 9);
info.chipId = readLe16(raw.data() + 9U);
info.firmwareMajor = fnRaw[5];
info.firmwareMinor = fnRaw[6];
info.firmwareBuild = fnRaw[7];
@@ -341,14 +439,21 @@ std::expected<Si4684SysState, Si4684Error> Si4684Driver::getSysState()
std::expected<void, Si4684Error> Si4684Driver::configureAfterBoot(
Si4684Band band)
{
if (auto stcEn = setProperty(kPropIntCtlEnable, kIntCtlStcEnable); !stcEn) {
return stcEn;
}
if (auto stcRep = setProperty(kPropIntCtlRepeat, kIntCtlStcRepeat); !stcRep) {
return stcRep;
}
if (band == Si4684Band::Dab) {
if (auto plan = installDefaultDabFrequencyPlan(); !plan) {
return plan;
}
// AN851 recommended-network table, "DAB" row: 0xF8A9 / 0x01C6.
static constexpr std::uint16_t kDabProps[][2] = {
{0x0202U, 0x1600U},
{0x1710U, 0xFC4AU},
{0x1711U, 0x00F8U},
{0x1710U, 0xF8A9U},
{0x1711U, 0x01C6U},
{0x8101U, 0x0064U},
{0xB200U, 0x0000U},
{0xB201U, 0x0080U},
@@ -366,32 +471,92 @@ std::expected<void, Si4684Error> Si4684Driver::configureAfterBoot(
if (auto xpad = setProperty(kPropDabXpadEnable, 0x0097U); !xpad) {
return xpad;
}
if (auto dabFe = setProperty(kPropDabTuneFeCfg, 0x0001U); !dabFe) {
ESP_LOGW(kTag, "DAB TUNE_FE_CFG (0x1712) failed");
return dabFe;
}
if (auto dsrv = setProperty(kPropDigitalServiceIntSource, 0x0001U);
!dsrv) {
ESP_LOGW(kTag, "DIGITAL_SERVICE_INT_SOURCE (0x8100) failed");
return dsrv;
}
} else {
// FM varactor cal per hitech95/uGreen DTS (not DAB PE5PVB values).
static constexpr std::uint16_t kFmFeProps[][2] = {
{0x1710U, kFmTuneFeVarm},
{0x1711U, kFmTuneFeVarb},
};
for (const auto& prop : kFmFeProps) {
if (auto set = setProperty(prop[0], prop[1]); !set) {
return set;
}
}
if (auto feCfg = setProperty(kPropFmTuneFeCfg, kFmTuneFeCfgEnable);
!feCfg) {
return feCfg;
}
static constexpr std::uint16_t kFmSeekProps[][2] = {
{kPropFmSeekBandBottom, kFmSeekBandBottomChip},
{kPropFmSeekBandTop, kFmSeekBandTopChip},
{kPropFmSeekSpacing, kFmSeekSpacingChip},
};
for (const auto& prop : kFmSeekProps) {
if (auto set = setProperty(prop[0], prop[1]); !set) {
return set;
}
}
if (auto rds = setProperty(kPropFmRdsConfig, 0x0001U); !rds) {
return rds;
}
// AN649 §0x3202/0x3204: lower seek/tune validity for weak lab antennas.
if (auto rssi = setProperty(kPropFmValidRssiThreshold,
kFmValidRssiThresholdDbuV);
!rssi) {
return rssi;
}
if (auto snr = setProperty(kPropFmValidSnrThreshold,
kFmValidSnrThresholdDb);
!snr) {
return snr;
}
ESP_LOGI(kTag, "FM valid tune: RSSI>=%u dBuV SNR>=%u dB",
static_cast<unsigned>(kFmValidRssiThresholdDbuV),
static_cast<unsigned>(kFmValidSnrThresholdDb));
}
if (auto i2s = setProperty(kPropDigitalIoOutputSelect, 0x8000U); !i2s) {
return i2s;
if (auto i2sRole =
setProperty(kPropDigitalIoOutputSelect, kSi4684I2sSlaveSelect);
!i2sRole) {
return i2sRole;
}
if (auto rate = setProperty(kPropDigitalIoSampleRate, 0xAC44U); !rate) {
if (auto i2sFmt =
setProperty(kPropDigitalIoOutputFormat, kSi4684I2sOutputFormat);
!i2sFmt) {
return i2sFmt;
}
if (auto rate =
setProperty(kPropDigitalIoSampleRate, kSi4684I2sSampleRateHz);
!rate) {
return rate;
}
if (auto pins = setProperty(kPropPinConfigEnable, 0x0003U); !pins) {
return pins;
}
if (auto dabFe = setProperty(kPropDabTuneFeCfg, 0x0001U); !dabFe) {
return dabFe;
if (auto mute = setProperty(kPropAudioMute, 0x0000U); !mute) {
return mute;
}
if (auto dsrv = setProperty(kPropDigitalServiceIntSource, 0x0001U);
!dsrv) {
return dsrv;
if (auto outCfg = setProperty(kPropAudioOutputConfig, kSi4684I2sOutEnable);
!outCfg) {
return outCfg;
}
if (auto vol = setProperty(kPropAudioVolume, kSi4684VolumeMax); !vol) {
return vol;
}
return {};
}
std::expected<void, Si4684Error> Si4684Driver::boot(Si4684Band band)
std::expected<void, Si4684Error> Si4684Driver::boot(
Si4684Band band, std::uint8_t xtalIbias, std::uint8_t xtalCtun)
{
if (booted_ && loadedBand_ == band) {
return {};
@@ -451,12 +616,25 @@ std::expected<void, Si4684Error> Si4684Driver::boot(Si4684Band band)
spiDevice_ = dev;
}
if (pins_.intbGpio >= 0) {
gpio_config_t intCfg = {};
intCfg.pin_bit_mask = 1ULL << pins_.intbGpio;
intCfg.mode = GPIO_MODE_INPUT;
intCfg.pull_up_en = GPIO_PULLUP_ENABLE;
if (gpio_config(&intCfg) != ESP_OK) {
return std::unexpected(Si4684Error::SpiInitFailed);
}
}
if (auto st = writeCommand(Command::GetSysState, nullptr, 0U); !st) {
return st;
}
const std::uint8_t powerUp[] = {
0x17, 0x48, 0x00, 0xf8, 0x24, 0x01, 0x1F, 0x10,
// ARG2=0x17(CLK_MODE=crystal,TR_SIZE), ARG3=IBIAS, ARG4-7=XTAL_FREQ
// 19.2 MHz (0x0124F800), ARG8=CTUN, ARG9=0x10 (fixed bit4=1 per AN649
// §Command 0x01), ARG10-15=0 (AN649 POWER_UP argument table).
std::uint8_t powerUp[] = {
0x17, xtalIbias, 0x00, 0xf8, 0x24, 0x01, xtalCtun, 0x10,
0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
};
if (auto pu = writeCommand(Command::PowerUp, powerUp, sizeof(powerUp));
@@ -517,6 +695,9 @@ std::expected<void, Si4684Error> Si4684Driver::tuneFm(
if (auto band = ensureBand(Si4684Band::Fm); !band) {
return band;
}
if (auto cleared = clearFmStc(); !cleared) {
return cleared;
}
const std::uint16_t chipFreq = kHzToChipFmFreq(frequency.value());
const std::uint8_t args[] = {
0x00U,
@@ -524,13 +705,38 @@ std::expected<void, Si4684Error> Si4684Driver::tuneFm(
static_cast<std::uint8_t>(chipFreq >> 8),
0x00U,
0x00U,
0x00U, // PROG_ID (AN649 ARG6; ignored when DIR_TUNE=0)
};
if (auto cmd = writeCommand(Command::FmTuneFreq, args, sizeof(args));
!cmd) {
return std::unexpected(Si4684Error::TuneFailed);
}
if (auto stc = waitStc(); !stc) {
return stc;
if (auto stc = waitStc(kStcRetries); !stc) {
ESP_LOGW(kTag, "FM tune STC timeout at %u kHz — settling 150 ms",
static_cast<unsigned>(frequency.value()));
vTaskDelay(pdMS_TO_TICKS(150));
} else {
(void)clearFmStc();
}
if (auto rsq = readFmRsq(); rsq) {
const std::uint32_t readKhz =
rsq->frequency ? rsq->frequency->value() : 0U;
if (readKhz != 0U && readKhz != frequency.value()) {
ESP_LOGW(kTag,
"FM tune READFREQ mismatch: want %u kHz got %u kHz",
static_cast<unsigned>(frequency.value()),
static_cast<unsigned>(readKhz));
}
ESP_LOGI(kTag,
"FM tuned %u kHz rssi=%d dBuV snr=%d dB valid=%d readfreq=%u",
static_cast<unsigned>(frequency.value()),
static_cast<int>(rsq->rssiDbuV),
static_cast<int>(rsq->snrDb),
static_cast<int>(rsq->valid),
static_cast<unsigned>(readKhz));
} else {
ESP_LOGI(kTag, "FM tuned %u kHz (RSQ read failed)",
static_cast<unsigned>(frequency.value()));
}
return {};
}
@@ -541,27 +747,54 @@ std::expected<core::FrequencyKHz, Si4684Error> Si4684Driver::seekFm(
if (auto band = ensureBand(Si4684Band::Fm); !band) {
return std::unexpected(band.error());
}
if (auto cleared = clearFmStc(); !cleared) {
return std::unexpected(cleared.error());
}
std::optional<std::uint32_t> prevKhz;
if (auto before = readFmRsq(); before && before->frequency) {
prevKhz = before->frequency->value();
}
const bool seekUp = direction == core::SeekDirection::Up;
const bool wrapBand = wrap == SeekBandWrap::Wrap;
// AN649 FM_SEEK_START: ARG1=tune/injection, ARG2=SEEKUP|WRAP.
const std::uint8_t seekFlags =
static_cast<std::uint8_t>(((seekUp ? 1U : 0U) << 1U)
| (wrapBand ? 1U : 0U));
const std::uint8_t args[] = {
0x10U,
static_cast<std::uint8_t>(((seekUp ? 1U : 0U) << 1U) | (wrapBand ? 1U : 0U)),
0x00U,
seekFlags,
0x00U,
0x00U,
0x00U,
};
if (auto cmd = writeCommand(Command::FmSeekStart, args, sizeof(args));
!cmd) {
ESP_LOGW(kTag, "FM seek command failed (flags=0x%02x)",
static_cast<unsigned>(seekFlags));
return std::unexpected(Si4684Error::TuneFailed);
}
if (auto stc = waitStc(); !stc) {
if (auto stc = waitStc(kStcRetries); !stc) {
ESP_LOGW(kTag, "FM seek STC timeout (flags=0x%02x)",
static_cast<unsigned>(seekFlags));
return std::unexpected(stc.error());
}
(void)clearFmStc();
auto rsq = readFmRsq();
if (!rsq) {
ESP_LOGW(kTag, "FM seek RSQ read failed");
return std::unexpected(rsq.error());
}
return rsq->frequency;
if (!rsq->frequency) {
ESP_LOGW(kTag, "FM seek READFREQ out of band (valid=%d)",
static_cast<int>(rsq->valid));
return std::unexpected(Si4684Error::TuneFailed);
}
if (prevKhz && rsq->frequency->value() == *prevKhz) {
ESP_LOGW(kTag, "FM seek READFREQ unchanged at %u kHz",
static_cast<unsigned>(*prevKhz));
return std::unexpected(Si4684Error::TuneFailed);
}
return *rsq->frequency;
}
std::expected<Si4684FmRsq, Si4684Error> Si4684Driver::readFmRsq()
@@ -579,18 +812,42 @@ std::expected<Si4684FmRsq, Si4684Error> Si4684Driver::readFmRsq()
return std::unexpected(rd.error());
}
const auto khz = chipFmFreqToKHz(readLe16(raw.data() + 6));
if (auto freq = core::FrequencyKHz::tryFromKhz(khz); freq) {
Si4684FmRsq rsq{
*freq,
static_cast<std::int8_t>(raw[8]),
static_cast<std::int8_t>(raw[9]),
(raw[4] & 0x01U) != 0U,
(raw[4] & 0x02U) != 0U,
};
return rsq;
if (raw.size() < kFmRsqOffSnr + 1U) {
return std::unexpected(Si4684Error::ReplyTooShort);
}
return std::unexpected(Si4684Error::CommandFailed);
ESP_LOGI(kTag,
"FM RSQ raw: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x "
"%02x %02x",
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
raw[8], raw[9], raw[10], raw[11]);
const auto khz =
chipFmFreqToKHz(readLe16(raw.data() + kFmRsqOffReadFreq));
const auto freq = core::FrequencyKHz::tryFromKhz(khz);
const bool freqInBand = static_cast<bool>(freq);
const bool chipValid = (raw[kFmRsqOffValid] & 0x01U) != 0U;
if (!freqInBand && khz != 0U) {
ESP_LOGW(kTag,
"FM RSQ out-of-band freq %u kHz (st=%02x %02x %02x %02x "
"freq=%02x %02x rssi=%02x snr=%02x)",
static_cast<unsigned>(khz), raw[kSpiReplyLeadIn],
raw[kSpiReplyLeadIn + 1U],
raw[kSpiReplyLeadIn + 2U], raw[kSpiReplyLeadIn + 3U],
raw[kFmRsqOffReadFreq], raw[kFmRsqOffReadFreq + 1U],
raw[kFmRsqOffRssi], raw[kFmRsqOffSnr]);
} else if (khz == 0U && raw[kSpiReplyLeadIn] == 0U
&& raw[kSpiReplyLeadIn + 1U] == 0U) {
ESP_LOGW(kTag, "FM RSQ empty reply (st=%02x %02x %02x %02x)",
raw[kSpiReplyLeadIn], raw[kSpiReplyLeadIn + 1U],
raw[kSpiReplyLeadIn + 2U], raw[kSpiReplyLeadIn + 3U]);
}
Si4684FmRsq rsq{
freqInBand ? std::optional<core::FrequencyKHz>{*freq} : std::nullopt,
static_cast<std::int8_t>(raw[kFmRsqOffRssi]),
static_cast<std::int8_t>(raw[kFmRsqOffSnr]),
freqInBand && chipValid,
false,
};
return rsq;
}
std::expected<Si4684FmRdsStatus, Si4684Error> Si4684Driver::readFmRds()
@@ -611,10 +868,10 @@ std::expected<Si4684FmRdsStatus, Si4684Error> Si4684Driver::readFmRds()
Si4684FmRdsStatus rds = {};
rds.received = (raw[4] & 0x01U) != 0U;
rds.fifoUsed = raw[10];
rds.blockA = readLe16(raw.data() + 12);
rds.blockB = readLe16(raw.data() + 14);
rds.blockC = readLe16(raw.data() + 16);
rds.blockD = readLe16(raw.data() + 18);
rds.blockA = readLe16(raw.data() + 12U);
rds.blockB = readLe16(raw.data() + 14U);
rds.blockC = readLe16(raw.data() + 16U);
rds.blockD = readLe16(raw.data() + 18U);
return rds;
}
@@ -708,7 +965,7 @@ std::expected<void, Si4684Error> Si4684Driver::tuneDab(std::uint8_t freqIndex)
!cmd) {
return std::unexpected(Si4684Error::TuneFailed);
}
if (auto stc = waitStc(); !stc) {
if (auto stc = waitStc(kStcRetries); !stc) {
return stc;
}
return {};
@@ -13,11 +13,16 @@
#include "si4684/Si4684Tuner.hpp"
#include "esp_log.h"
#include "si4684/Si4684Band.hpp"
namespace si4684 {
namespace {
constexpr char kTag[] = "Si4684Tuner";
constexpr std::uint32_t kFmSeekStepKhz = 100U;
constexpr std::uint32_t kFmBandBottomKhz = 87500U;
constexpr std::uint32_t kFmBandTopKhz = 107900U;
[[nodiscard]] core::FrequencyKHz defaultFmFrequency()
{
@@ -30,7 +35,8 @@ Si4684Tuner::Si4684Tuner(Si4684Driver& driver)
: driver_(driver)
, dabIndex_(0U)
, fmFrequency_(defaultFmFrequency())
, volume_(40U)
, volume_(63U)
, fmBandReady_(false)
{
}
@@ -44,6 +50,9 @@ core::TunerError Si4684Tuner::mapError(Si4684Error error) noexcept
case Si4684Error::TuneFailed:
case Si4684Error::StcTimeout:
return core::TunerError::TuneFailed;
case Si4684Error::CtsTimeout:
case Si4684Error::CommandFailed:
return core::TunerError::HardwareFailed;
default:
return core::TunerError::HardwareFailed;
}
@@ -68,6 +77,58 @@ std::expected<core::TunerBand, core::TunerError> Si4684Tuner::currentBand() cons
: core::TunerBand::Dab;
}
std::expected<void, core::TunerError> Si4684Tuner::ensureBandLoaded(
core::TunerBand band)
{
const Si4684Band hwTarget =
(band == core::TunerBand::Fm) ? Si4684Band::Fm : Si4684Band::Dab;
if (driver_.isBooted() && driver_.loadedBand() == hwTarget) {
if (hwTarget != Si4684Band::Fm || fmBandReady_) {
return {};
}
}
const bool reloading =
!(driver_.isBooted() && driver_.loadedBand() == hwTarget);
if (reloading) {
ESP_LOGI(kTag, "band switch -> %s",
band == core::TunerBand::Fm ? "FM" : "DAB");
}
if (driver_.isBooted() && driver_.loadedBand() == Si4684Band::Dab
&& hwTarget == Si4684Band::Fm && lastDabServiceId_
&& lastDabComponentId_) {
(void)driver_.stopDabService(*lastDabServiceId_, *lastDabComponentId_);
lastDabServiceId_.reset();
lastDabComponentId_.reset();
dabDynamicLabel_.reset();
}
if (auto loaded = boot(band); !loaded) {
return loaded;
}
if (auto vol = driver_.setVolume(volume_); !vol) {
return std::unexpected(mapError(vol.error()));
}
if (band == core::TunerBand::Fm) {
rdsMetadata_.reset();
fmFrequency_ = defaultFmFrequency();
fmBandReady_ = false;
if (auto tuned = driver_.tuneFm(fmFrequency_); !tuned) {
return std::unexpected(mapError(tuned.error()));
}
fmBandReady_ = true;
} else {
fmBandReady_ = false;
dabDynamicLabel_.reset();
lastDabServiceId_.reset();
lastDabComponentId_.reset();
}
return {};
}
std::expected<core::TunerStatus, core::TunerError> Si4684Tuner::readStatus()
{
if (!driver_.isBooted()) {
@@ -106,11 +167,20 @@ std::expected<core::TunerStatus, core::TunerError> Si4684Tuner::readStatus()
status.fmFrequency = fmFrequency_;
if (auto rsq = driver_.readFmRsq(); rsq) {
status.locked = rsq->valid;
status.fmFrequency = rsq->frequency;
status.fmRssiDbuV = rsq->rssiDbuV;
status.fmSnrDb = rsq->snrDb;
status.fmStereo = rsq->stereo;
fmFrequency_ = rsq->frequency;
status.fmChipReadFrequency = rsq->frequency;
// Keep commanded frequency when chip READFREQ is stale (stuck at band
// bottom); adopt READFREQ only when valid or it matches our target.
status.fmFrequency = fmFrequency_;
if (rsq->frequency) {
const std::uint32_t chipKhz = rsq->frequency->value();
if (rsq->valid || chipKhz == fmFrequency_.value()) {
status.fmFrequency = *rsq->frequency;
fmFrequency_ = *rsq->frequency;
}
}
} else {
return std::unexpected(mapError(rsq.error()));
}
@@ -118,7 +188,8 @@ std::expected<core::TunerStatus, core::TunerError> Si4684Tuner::readStatus()
for (int attempt = 0; attempt < 8; ++attempt) {
auto rds = driver_.readFmRds();
if (!rds) {
return std::unexpected(mapError(rds.error()));
ESP_LOGW(kTag, "FM RDS read failed (attempt %d)", attempt);
break;
}
if (!rds->received) {
break;
@@ -140,6 +211,9 @@ std::expected<core::TunerStatus, core::TunerError> Si4684Tuner::readStatus()
std::expected<void, core::TunerError> Si4684Tuner::tuneDab(
std::uint8_t freqIndex)
{
if (auto ready = ensureBandLoaded(core::TunerBand::Dab); !ready) {
return ready;
}
if (auto result = driver_.tuneDab(freqIndex); !result) {
return std::unexpected(mapError(result.error()));
}
@@ -153,14 +227,9 @@ std::expected<void, core::TunerError> Si4684Tuner::tuneDab(
std::expected<void, core::TunerError> Si4684Tuner::tuneFm(
core::FrequencyKHz frequency)
{
if (driver_.loadedBand() == Si4684Band::Dab && lastDabServiceId_
&& lastDabComponentId_) {
(void)driver_.stopDabService(*lastDabServiceId_, *lastDabComponentId_);
lastDabServiceId_.reset();
lastDabComponentId_.reset();
dabDynamicLabel_.reset();
if (auto ready = ensureBandLoaded(core::TunerBand::Fm); !ready) {
return ready;
}
if (auto result = driver_.tuneFm(frequency); !result) {
return std::unexpected(mapError(result.error()));
}
@@ -172,18 +241,55 @@ std::expected<void, core::TunerError> Si4684Tuner::tuneFm(
std::expected<core::FrequencyKHz, core::TunerError> Si4684Tuner::seekFm(
core::SeekDirection direction)
{
const auto result = driver_.seekFm(direction, SeekBandWrap::Wrap);
if (!result) {
return std::unexpected(mapError(result.error()));
if (auto ready = ensureBandLoaded(core::TunerBand::Fm); !ready) {
return std::unexpected(ready.error());
}
fmFrequency_ = *result;
rdsMetadata_.reset();
return *result;
const core::FrequencyKHz before = fmFrequency_;
const auto hw = driver_.seekFm(direction, SeekBandWrap::Wrap);
if (hw && hw->value() != before.value()) {
fmFrequency_ = *hw;
rdsMetadata_.reset();
return *hw;
}
// hitech95/si468x_dab_receiver uses HW seek + INTB STC; when READFREQ does
// not move, step by FM_SEEK_FREQUENCY_SPACING (100 kHz) via FM_TUNE_FREQ.
std::uint32_t nextKhz = before.value();
if (direction == core::SeekDirection::Up) {
nextKhz += kFmSeekStepKhz;
if (nextKhz > kFmBandTopKhz) {
nextKhz = kFmBandBottomKhz;
}
} else {
nextKhz = (nextKhz > kFmBandBottomKhz + kFmSeekStepKhz)
? nextKhz - kFmSeekStepKhz
: kFmBandTopKhz;
}
const auto next = core::FrequencyKHz::tryFromKhz(nextKhz);
if (!next) {
return std::unexpected(core::TunerError::TuneFailed);
}
if (auto stepped = tuneFm(*next); !stepped) {
return std::unexpected(stepped.error());
}
if (auto rsq = driver_.readFmRsq(); rsq && rsq->frequency) {
ESP_LOGI(kTag, "FM software seek %s -> %u kHz (chip READFREQ)",
direction == core::SeekDirection::Up ? "UP" : "DOWN",
static_cast<unsigned>(rsq->frequency->value()));
return *rsq->frequency;
}
ESP_LOGI(kTag, "FM software seek %s -> %u kHz",
direction == core::SeekDirection::Up ? "UP" : "DOWN",
static_cast<unsigned>(nextKhz));
return *next;
}
std::expected<std::vector<core::TunerServiceEntry>, core::TunerError>
Si4684Tuner::listDabServices()
{
if (auto ready = ensureBandLoaded(core::TunerBand::Dab); !ready) {
return std::unexpected(ready.error());
}
if (auto events = driver_.readDabEventStatus(); events) {
if (!events->serviceListReady) {
return std::unexpected(core::TunerError::ServiceListEmpty);
@@ -215,6 +321,9 @@ std::expected<void, core::TunerError> Si4684Tuner::playDabService(
std::uint32_t serviceId,
std::uint32_t componentId)
{
if (auto ready = ensureBandLoaded(core::TunerBand::Dab); !ready) {
return ready;
}
if (auto result = driver_.startDabService(serviceId, componentId); !result) {
return std::unexpected(mapError(result.error()));
}
+3 -1
View File
@@ -4,10 +4,12 @@ idf_component_register(
"src/SoftApHost.cpp"
"src/StaClient.cpp"
"src/SetupWebServer.cpp"
"src/SigmaStudioTcpServer.cpp"
"src/WifiScanner.cpp"
"src/NetBootstrap.cpp"
INCLUDE_DIRS "include"
EMBED_FILES "www/index.html.gz"
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server mdns secure_store tuner audio bluetooth station integration ota bt1035 adau1701
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server mdns lwip secure_store tuner audio bluetooth station integration ota bt1035 adau1701 webradio
)
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
@@ -23,6 +23,7 @@
#include "net/NetError.hpp"
#include "net/NetState.hpp"
#include "net/SetupWebServer.hpp"
#include "net/SigmaStudioTcpServer.hpp"
#include "net/SoftApHost.hpp"
#include "net/StaClient.hpp"
@@ -53,6 +54,10 @@ namespace tuner {
class TunerService;
} // namespace tuner
namespace webradio {
class WebRadioService;
} // namespace webradio
namespace net {
/**
@@ -60,8 +65,9 @@ namespace net {
*
* @dname NetBootstrap
* @return n/a (type)
* @pubstate Owns optional softAp_, optional sta_, and webServer_. Must
* outlive app_main; keep one instance alive for process lifetime.
* @pubstate Owns optional softAp_, optional sta_, webServer_, and
* sigmaStudio_. Must outlive app_main; keep one instance alive
* for process lifetime.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -79,6 +85,7 @@ public:
* @param stations Station preset service for REST routes.
* @param integration Application orchestration for preset recall.
* @param ota Firmware OTA service for POST /api/system/ota.
* @param webRadio Streaming config for GET/POST /api/streaming.
* @param companionChips Boot flags exposed on GET /api/health.
* @param deviceIdentity EEPROM-derived SSID, hostname, and serial.
* @return NetBootstrap on success, or a NetError.
@@ -93,6 +100,7 @@ public:
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity);
@@ -106,12 +114,14 @@ public:
* @param softAp Optional SoftAP mode host.
* @param sta Optional STA client instance.
* @param webServer HTTP server instance.
* @param sigmaStudio SigmaStudio TCP:8086 bridge instance.
* @param state Initial network state.
* @pubstate Transfers ownership of optional network resources.
*/
NetBootstrap(std::optional<SoftApHost> softAp,
std::optional<StaClient> sta,
SetupWebServer webServer,
SigmaStudioTcpServer sigmaStudio,
NetState state);
/**
@@ -119,7 +129,7 @@ public:
*
* @dname NetBootstrap
* @param other Source instance; left empty after the move.
* @pubstate transfers softAp_, sta_, and webServer_ from other.
* @pubstate transfers softAp_, sta_, webServer_, and sigmaStudio_ from other.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -132,7 +142,7 @@ public:
* @dname operator=
* @param other Source instance; left empty after the move.
* @return Reference to this instance.
* @pubstate transfers softAp_, sta_, and webServer_ from other.
* @pubstate transfers softAp_, sta_, webServer_, and sigmaStudio_ from other.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -143,7 +153,7 @@ public:
* @brief ~NetBootstrap — tear down network subsystems.
*
* @dname ~NetBootstrap
* @pubstate destroys softAp_, sta_, and webServer_.
* @pubstate destroys softAp_, sta_, webServer_, and sigmaStudio_.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -166,6 +176,7 @@ private:
std::optional<SoftApHost> softAp_;
std::optional<StaClient> sta_;
SetupWebServer webServer_;
SigmaStudioTcpServer sigmaStudio_;
NetState state_;
};
@@ -37,10 +37,12 @@ enum class NetError {
WifiConfigFailed,
WifiStartFailed,
HttpServerStartFailed,
TcpServerStartFailed,
StaConnectTimeout,
StaConnectFailed,
StoreSaveFailed,
CredentialsNotFound,
WifiScanFailed,
};
} // namespace net
@@ -50,6 +50,10 @@ namespace tuner {
class TunerService;
} // namespace tuner
namespace webradio {
class WebRadioService;
} // namespace webradio
struct httpd_handle;
namespace net {
@@ -73,6 +77,7 @@ struct HttpRouteContext {
station::StationService* stations; ///< Preset list REST routes.
integration::IntegrationService* integration; ///< Preset recall orchestration.
ota::OtaService* ota; ///< Firmware OTA streaming.
webradio::WebRadioService* webRadio; ///< Streaming config REST routes.
core::CompanionChipStatus companionChips; ///< Boot flags for /api/health.
core::DeviceIdentity deviceIdentity; ///< EEPROM-derived unit identity.
};
@@ -152,6 +157,7 @@ public:
* @param stations Station preset service for list REST routes.
* @param integration Application orchestration for preset recall.
* @param ota Firmware OTA service for POST /api/system/ota.
* @param webRadio Streaming config for GET/POST /api/streaming.
* @param companionChips Boot flags for GET /api/health.
* @param deviceIdentity Unit identity for /api/health serialNumber.
* @return Ok on success, or NetError::HttpServerStartFailed.
@@ -167,6 +173,7 @@ public:
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity);
@@ -179,7 +186,6 @@ private:
bluetooth::BluetoothService* bluetooth_;
station::StationService* stations_;
integration::IntegrationService* integration_;
HttpRouteContext routeContext_;
};
} // namespace net
@@ -0,0 +1,133 @@
/**
* @file SigmaStudioTcpServer.hpp
* @brief TCP bridge exposing the ADAU1701 to SigmaStudio's Remote Connection.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Implements the subset of SigmaStudio's TCPi wire protocol needed for
* Connect, Link Compile Download, register read/write, and runtime
* safeload parameter updates, ported from the ADAU1701-TCPi-ESP32
* reference project (https://github.com/rarranzb/ADAU1701-TCPi-ESP32,
* MIT) onto DigiRadio's existing I2C plumbing
* (components/drivers/adau1701/src/SigmaStudioFW.c). A completed Link
* Compile Download is persisted to the `dsp` flash partition via
* adau1701::FlashDspProgramSource::storeBlob(), so it becomes the
* program DigiRadio boots with next time — same mechanism as
* POST /api/dsp/program.
*
* @author Michele Bigi
* @date 2026-08-07
*/
#pragma once
#include "net/NetError.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <expected>
namespace net {
/**
* @brief SigmaStudioTcpServer — port 8086 bridge to the ADAU1701.
*
* @dname SigmaStudioTcpServer
* @return n/a (type)
* @pubstate Owns a listening socket and a FreeRTOS task for the process
* lifetime once started(); stop()/destructor tear both down.
* Requires adau1701::Adau1701Driver::boot() to have already
* bound the I2C device handle via sigma_studio_set_device().
* Only one instance may be started at a time: the accept task
* reads the listen fd from a process-lifetime singleton (see
* activeListenFd() in the .cpp) rather than a captured `this`,
* since instances are constructed as locals and move-relocated
* into NetBootstrap — same rationale as SetupWebServer's
* routeContextStorage().
*
* @author Michele Bigi
* @date 2026-08-07
*/
class SigmaStudioTcpServer {
public:
/**
* @brief SigmaStudioTcpServer — construct an unstarted server.
*
* @dname SigmaStudioTcpServer
* @pubstate clears listenFd_ and task_.
*
* @author Michele Bigi
* @date 2026-08-07
*/
SigmaStudioTcpServer();
/**
* @brief ~SigmaStudioTcpServer — stop the server if running.
*
* @dname ~SigmaStudioTcpServer
* @pubstate deletes task_ and closes listenFd_ when started.
*
* @author Michele Bigi
* @date 2026-08-07
*/
~SigmaStudioTcpServer();
SigmaStudioTcpServer(const SigmaStudioTcpServer&) = delete;
SigmaStudioTcpServer& operator=(const SigmaStudioTcpServer&) = delete;
/**
* @brief SigmaStudioTcpServer — move-construct, transferring ownership.
*
* @dname SigmaStudioTcpServer
* @param other Source server; left stopped after the move.
* @pubstate takes ownership of other.listenFd_ and other.task_.
*
* @author Michele Bigi
* @date 2026-08-07
*/
SigmaStudioTcpServer(SigmaStudioTcpServer&& other) noexcept;
/**
* @brief operator= — move-assign, transferring ownership.
*
* @dname operator=
* @param other Source server; left stopped after the move.
* @return Reference to this instance.
* @pubstate takes ownership of other.listenFd_ and other.task_.
*
* @author Michele Bigi
* @date 2026-08-07
*/
SigmaStudioTcpServer& operator=(SigmaStudioTcpServer&& other) noexcept;
/**
* @brief start — open the listening socket and spawn the server task.
*
* @dname start
* @return Ok on success, or NetError::TcpServerStartFailed.
* @pubstate writes listenFd_ and task_ on success.
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<void, NetError> start();
private:
void stop() noexcept;
/** @brief FreeRTOS task entry: accepts and serves one client at a time. */
static void acceptLoopTask(void* arg);
int listenFd_;
TaskHandle_t task_;
};
} // namespace net
@@ -20,6 +20,8 @@
#include "core/WifiCredentials.hpp"
#include "net/NetError.hpp"
#include "esp_event.h"
#include <expected>
#include <string_view>
@@ -107,6 +109,8 @@ public:
private:
bool connected_;
esp_event_handler_instance_t wifiHandler_;
esp_event_handler_instance_t ipHandler_;
};
} // namespace net
@@ -0,0 +1,50 @@
/**
* @file WifiScanner.hpp
* @brief Blocking Wi-Fi scan wrapper for the setup web UI.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#pragma once
#include "core/WifiScannedNetwork.hpp"
#include "net/NetError.hpp"
#include <expected>
#include <vector>
namespace net {
/**
* @brief WifiScanner — runs esp_wifi scan and maps results to core DTOs.
*
* @dname WifiScanner
* @return n/a (type)
* @pubstate Stateless shell helper; assumes esp_wifi_init() already ran.
*
* @author Michele Bigi
* @date 2026-08-05
*/
class WifiScanner {
public:
/**
* @brief scanNearby — list visible access points sorted by RSSI.
*
* @dname scanNearby
* @return Deduped networks on success, or NetError::WifiScanFailed.
* @pubstate Temporarily switches AP-only mode to APSTA when required.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] static std::expected<std::vector<core::WifiScannedNetwork>,
NetError>
scanNearby();
};
} // namespace net
+26 -7
View File
@@ -27,6 +27,7 @@
#include "secure_store/NvsPlatformInit.hpp"
#include "station/StationService.hpp"
#include "tuner/TunerService.hpp"
#include "webradio/WebRadioService.hpp"
namespace net {
@@ -102,10 +103,12 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner,
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity)
{
esp_netif_create_default_wifi_ap();
esp_netif_create_default_wifi_sta();
SoftApHost softAp(SoftApConfig::forSsid(deviceIdentity.softApSsid()));
if (auto apResult = softAp.start(); !apResult) {
@@ -115,17 +118,23 @@ startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner,
SetupWebServer webServer;
if (auto webResult =
webServer.start(store, NetState::SoftApSetup, tuner, audio,
bluetooth, stations, integration, ota,
bluetooth, stations, integration, ota, webRadio,
companionChips, deviceIdentity);
!webResult) {
return std::unexpected(webResult.error());
}
SigmaStudioTcpServer sigmaStudio;
if (auto sigmaResult = sigmaStudio.start(); !sigmaResult) {
ESP_LOGW(kTag, "SigmaStudio TCP bridge failed to start — continuing "
"without it");
}
ESP_LOGI(kTag, "setup mode ready — SSID %.*s",
static_cast<int>(deviceIdentity.softApSsid().size()),
deviceIdentity.softApSsid().data());
return NetBootstrap(std::move(softAp), std::nullopt, std::move(webServer),
NetState::SoftApSetup);
std::move(sigmaStudio), NetState::SoftApSetup);
}
/**
@@ -146,6 +155,7 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner,
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity)
{
@@ -175,17 +185,23 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner,
SetupWebServer webServer;
if (auto webResult =
webServer.start(store, NetState::StaConnected, tuner, audio,
bluetooth, stations, integration, ota,
bluetooth, stations, integration, ota, webRadio,
companionChips, deviceIdentity);
!webResult) {
return std::unexpected(webResult.error());
}
SigmaStudioTcpServer sigmaStudio;
if (auto sigmaResult = sigmaStudio.start(); !sigmaResult) {
ESP_LOGW(kTag, "SigmaStudio TCP bridge failed to start — continuing "
"without it");
}
ESP_LOGI(kTag, "STA mode ready — hostname %.*s.local",
static_cast<int>(deviceIdentity.hostname().size()),
deviceIdentity.hostname().data());
return NetBootstrap(std::nullopt, std::move(sta), std::move(webServer),
NetState::StaConnected);
std::move(sigmaStudio), NetState::StaConnected);
}
} // namespace
@@ -197,6 +213,7 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity)
{
@@ -210,8 +227,8 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
if (store.hasWifiCredentials()) {
auto staResult = startStaMode(store, tuner, audio, bluetooth, stations,
integration, ota, companionChips,
deviceIdentity);
integration, ota, webRadio,
companionChips, deviceIdentity);
if (staResult) {
return staResult;
}
@@ -223,16 +240,18 @@ NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner,
}
return startSetupMode(store, tuner, audio, bluetooth, stations, integration,
ota, companionChips, deviceIdentity);
ota, webRadio, companionChips, deviceIdentity);
}
NetBootstrap::NetBootstrap(std::optional<SoftApHost> softAp,
std::optional<StaClient> sta,
SetupWebServer webServer,
SigmaStudioTcpServer sigmaStudio,
NetState state)
: softAp_(std::move(softAp))
, sta_(std::move(sta))
, webServer_(std::move(webServer))
, sigmaStudio_(std::move(sigmaStudio))
, state_(state)
{
}
+606 -44
View File
@@ -21,11 +21,9 @@
#include "core/AudioProfile.hpp"
#include "core/AudioProfileJson.hpp"
#include "core/BluetoothJson.hpp"
#include "core/Bt1035ScannedDevice.hpp"
#include "core/CompanionChipStatus.hpp"
#include "core/DspProgramBlob.hpp"
#include "core/FirmwareVersion.hpp"
#include "core/HealthStatus.hpp"
#include "core/HealthStatusJson.hpp"
#include "core/IntegrationError.hpp"
#include "core/ParseError.hpp"
#include "core/SeekDirection.hpp"
@@ -33,6 +31,8 @@
#include "core/StoreError.hpp"
#include "core/TunerJson.hpp"
#include "core/WifiProvisionJson.hpp"
#include "core/WifiScanJson.hpp"
#include "net/WifiScanner.hpp"
#include "tuner/TunerService.hpp"
#include "audio/AudioService.hpp"
#include "bluetooth/BluetoothService.hpp"
@@ -42,10 +42,13 @@
#include "ota/OtaService.hpp"
#include "ota/OtaError.hpp"
#include "core/OtaAppDescriptor.hpp"
#include "core/WebRadioJson.hpp"
#include "bt1035/Bt1035Error.hpp"
#include "webradio/WebRadioService.hpp"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -53,6 +56,8 @@
#include <array>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
@@ -68,6 +73,49 @@ extern const uint8_t index_html_gz_start[] asm(
extern const uint8_t index_html_gz_end[] asm(
"_binary_index_html_gz_end");
/**
* @brief routeContextStorage app-lifetime HTTP handler dependencies.
*
* @dname routeContextStorage
* @return Reference to the singleton route context.
* @pubstate Populated in SetupWebServer::start(); stable across moves so
* esp_http_server user_ctx never goes stale.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] HttpRouteContext& routeContextStorage() noexcept
{
static HttpRouteContext ctx{
.store = nullptr,
.tuner = nullptr,
.audio = nullptr,
.bluetooth = nullptr,
.stations = nullptr,
.integration = nullptr,
.ota = nullptr,
.webRadio = nullptr,
.companionChips = {},
.deviceIdentity = core::DeviceIdentity::unknown(),
};
return ctx;
}
/**
* @brief routeContextReady whether start() populated route dependencies.
*
* @dname routeContextReady
* @return true after SetupWebServer::start() succeeds.
* @pubstate reads routeContextStorage().store.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool routeContextReady() noexcept
{
return routeContextStorage().store != nullptr;
}
/**
* @brief routeContextFrom read handler dependencies from user_ctx.
*
@@ -81,7 +129,10 @@ extern const uint8_t index_html_gz_end[] asm(
*/
[[nodiscard]] HttpRouteContext* routeContextFrom(httpd_req_t* req) noexcept
{
return static_cast<HttpRouteContext*>(req->user_ctx);
if (req != nullptr && req->user_ctx != nullptr) {
return static_cast<HttpRouteContext*>(req->user_ctx);
}
return routeContextReady() ? &routeContextStorage() : nullptr;
}
/**
@@ -216,21 +267,64 @@ template <std::size_t N>
esp_err_t healthGetHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
const core::CompanionChipStatus chips =
ctx != nullptr
? ctx->companionChips
: core::CompanionChipStatus{
.si4684Ready = false,
.adau1701Ready = false,
.bt1035Ready = false,
};
const core::HealthStatus status = core::HealthStatus::ok(
core::FirmwareVersion(kFirmwareVersion), chips,
ctx != nullptr ? ctx->deviceIdentity.serialNumber()
: std::string_view("unknown"));
const std::string json = core::serializeHealthStatusJson(status);
bool si4684Ready = false;
bool adau1701Ready = false;
bool bt1035Ready = false;
const char* serialNumber = "unknown";
if (ctx != nullptr) {
si4684Ready = ctx->companionChips.si4684Ready;
adau1701Ready = ctx->companionChips.adau1701Ready;
bt1035Ready = ctx->companionChips.bt1035Ready;
const std::string_view serial = ctx->deviceIdentity.serialNumber();
if (!serial.empty() && serial.size() <= 32U) {
serialNumber = serial.data();
}
}
char ipAddr[16] = {};
esp_netif_t* staNetif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (staNetif != nullptr) {
esp_netif_ip_info_t ipInfo{};
if (esp_netif_get_ip_info(staNetif, &ipInfo) == ESP_OK
&& ipInfo.ip.addr != 0U) {
std::snprintf(ipAddr, sizeof(ipAddr), IPSTR, IP2STR(&ipInfo.ip));
}
}
if (ipAddr[0] == '\0') {
esp_netif_t* apNetif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
if (apNetif != nullptr) {
esp_netif_ip_info_t ipInfo{};
if (esp_netif_get_ip_info(apNetif, &ipInfo) == ESP_OK
&& ipInfo.ip.addr != 0U) {
std::snprintf(ipAddr, sizeof(ipAddr), IPSTR, IP2STR(&ipInfo.ip));
}
}
}
char json[320];
int jsonLen = 0;
if (ipAddr[0] != '\0') {
jsonLen = std::snprintf(
json, sizeof(json),
R"({"status":"ok","fw":"%s","serialNumber":"%s","ip":"%s","chips":{"si4684":%s,"adau1701":%s,"bt1035":%s}})",
kFirmwareVersion, serialNumber, ipAddr,
si4684Ready ? "true" : "false", adau1701Ready ? "true" : "false",
bt1035Ready ? "true" : "false");
} else {
jsonLen = std::snprintf(
json, sizeof(json),
R"({"status":"ok","fw":"%s","serialNumber":"%s","chips":{"si4684":%s,"adau1701":%s,"bt1035":%s}})",
kFirmwareVersion, serialNumber, si4684Ready ? "true" : "false",
adau1701Ready ? "true" : "false", bt1035Ready ? "true" : "false");
}
if (jsonLen <= 0 || jsonLen >= static_cast<int>(sizeof(json))) {
httpd_resp_set_status(req, "500 Internal Server Error");
return httpd_resp_send(req, nullptr, 0);
}
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
return httpd_resp_send(req, json, static_cast<std::size_t>(jsonLen));
}
/**
@@ -446,6 +540,61 @@ esp_err_t tunerSeekPostHandler(httpd_req_t* req)
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief tunerScanPostHandler automatic FM/DAB station search (test mode).
*
* @dname tunerScanPostHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate uses route context tuner service; may block tens of seconds.
*
* @author Michele Bigi
* @date 2026-08-05
*/
esp_err_t tunerScanPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->tuner == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 512> body{};
if (!readRequestBody(req, body)) {
httpd_resp_set_status(req, "400 Bad Request");
return httpd_resp_send(req, nullptr, 0);
}
const auto parsed =
core::parseTunerScanJson(std::string_view(body.data()));
if (!parsed) {
const std::string json =
core::serializeTunerErrorJson(parseErrorToken(parsed.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
ESP_LOGI(kTag, "tuner scan HTTP band=%s max_steps=%u name='%.*s'",
parsed->band == core::TunerBand::Fm ? "fm" : "dab",
static_cast<unsigned>(parsed->maxSteps),
static_cast<int>(parsed->nameFilter.size()),
parsed->nameFilter.c_str());
auto result = ctx->tuner->scanForStation(*parsed);
if (!result) {
const std::string json =
core::serializeTunerErrorJson(tunerErrorToken(result.error()));
httpd_resp_set_status(req, "409 Conflict");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const std::string json = core::serializeTunerScanJson(*result);
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief audioProfileGetHandler serve GET /api/audio/profile JSON.
*
@@ -608,6 +757,125 @@ esp_err_t audioBassEnhancePostHandler(httpd_req_t* req)
return audioEnhancePostHandler(req, false);
}
/**
* @brief audioBeepPostHandler toggle the ADAU1701 Beep1 tone generator.
*
* @dname audioBeepPostHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate live-only safeload; does not touch AudioProfile or NVS.
*
* @author Michele Bigi
* @date 2026-08-07
*/
esp_err_t audioBeepPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->audio == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 128> body{};
if (!readRequestBody(req, body)) {
httpd_resp_set_status(req, "400 Bad Request");
return httpd_resp_send(req, nullptr, 0);
}
const auto parsed = core::parseBeepEnabledJson(std::string_view(body.data()));
if (!parsed) {
const std::string json =
core::serializeAudioErrorJson(parseErrorToken(parsed.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
if (auto applied = ctx->audio->setBeepEnabled(*parsed); !applied) {
const std::string json = core::serializeAudioErrorJson("dsp_failed");
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const std::string json = core::serializeAudioSavedJson();
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief streamingGetHandler serve GET /api/streaming as JSON.
*
* @dname streamingGetHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate reads route context web radio service snapshot.
*
* @author Michele Bigi
* @date 2026-08-08
*/
esp_err_t streamingGetHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->webRadio == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
const std::string json =
core::serializeWebRadioConfigJson(ctx->webRadio->config());
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief streamingPostHandler accept POST /api/streaming JSON.
*
* @dname streamingPostHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate persists config to NVS and updates the live streaming task
* config; takes effect without a reboot.
*
* @author Michele Bigi
* @date 2026-08-08
*/
esp_err_t streamingPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->webRadio == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 512> body{};
if (!readRequestBody(req, body)) {
httpd_resp_set_status(req, "400 Bad Request");
return httpd_resp_send(req, nullptr, 0);
}
const auto parsed =
core::parseWebRadioConfigJson(std::string_view(body.data()));
if (!parsed) {
const std::string json =
core::serializeWebRadioErrorJson(parseErrorToken(parsed.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
if (auto applied = ctx->webRadio->setConfig(*parsed); !applied) {
const std::string json =
core::serializeWebRadioErrorJson("store_failed");
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const std::string json = core::serializeWebRadioConfigJson(*parsed);
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief indexGetHandler serve gzipped setup page from flash.
* @param req HTTP request handle from esp_http_server.
@@ -699,6 +967,35 @@ esp_err_t wifiPostHandler(httpd_req_t* req)
return ESP_OK;
}
/**
* @brief wifiScanPostHandler list nearby Wi-Fi access points.
*
* @dname wifiScanPostHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate runs esp_wifi scan via WifiScanner.
*
* @author Michele Bigi
* @date 2026-08-05
*/
esp_err_t wifiScanPostHandler(httpd_req_t* req)
{
(void)req;
auto networks = WifiScanner::scanNearby();
if (!networks) {
const std::string json =
core::serializeWifiScanErrorJson("scan_failed");
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const std::string json = core::serializeWifiScanJson(*networks);
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
/**
* @brief dspProgramPostHandler store validated ADAU program blob to flash.
*
@@ -939,6 +1236,191 @@ esp_err_t bluetoothPairedGetHandler(httpd_req_t* req)
return httpd_resp_send(req, json.c_str(), json.size());
}
esp_err_t bluetoothScanPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
ESP_LOGI(kTag, "bluetooth scan HTTP request");
std::uint8_t scanSeconds = 45U;
std::array<char, 128> body{};
if (readRequestBody(req, body)) {
const std::string_view payload(body.data());
const std::string needle = "\"seconds\":";
const std::size_t start = payload.find(needle);
if (start != std::string_view::npos) {
char* end = nullptr;
const unsigned long raw = std::strtoul(
payload.data() + start + needle.size(), &end, 10);
if (end != payload.data() + start + needle.size() && raw >= 1U
&& raw <= 255U) {
scanSeconds = static_cast<std::uint8_t>(raw);
}
}
}
ESP_LOGI(kTag, "bluetooth scan HTTP seconds=%u",
static_cast<unsigned>(scanSeconds));
auto devices = ctx->bluetooth->scanNearby(scanSeconds);
if (!devices) {
ESP_LOGW(kTag, "bluetooth scan failed: %s",
bt1035ErrorToken(devices.error()));
const std::string json =
core::serializeBluetoothErrorJson(bt1035ErrorToken(devices.error()));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
ESP_LOGI(kTag, "bluetooth scan HTTP OK: %u device(s)",
static_cast<unsigned>(devices->size()));
for (const core::Bt1035ScannedDevice& device : *devices) {
ESP_LOGI(kTag, "bluetooth scan HTTP result: mac=%s name=%s rssi=%d",
device.mac.c_str(),
device.name.empty() ? "(no name)" : device.name.c_str(),
static_cast<int>(device.rssiDbm));
}
const std::string json = core::serializeBluetoothScanJson(*devices);
ESP_LOGI(kTag, "bluetooth scan JSON length=%u",
static_cast<unsigned>(json.size()));
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
esp_err_t bluetoothConnectPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 256> body{};
if (!readRequestBody(req, body)) {
httpd_resp_set_status(req, "400 Bad Request");
return httpd_resp_send(req, nullptr, 0);
}
const core::BluetoothConnectRequest request =
core::parseBluetoothConnectRequest(std::string_view(body.data()));
if (request.mac.empty()) {
const std::string json =
core::serializeBluetoothErrorJson("invalid_mac");
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
ESP_LOGI(kTag, "bluetooth connect HTTP mac=%s save=%s",
request.mac.c_str(), request.save ? "yes" : "no");
if (auto result = ctx->bluetooth->connectTo(request); !result) {
const std::string json =
core::serializeBluetoothErrorJson(bt1035ErrorToken(result.error()));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, "{\"status\":\"connected\"}", 24);
}
esp_err_t bluetoothSpeakerGetHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
if (!ctx->bluetooth->hasSavedSpeaker()) {
const std::string json = core::serializeBluetoothSpeakerJson(nullptr);
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const auto target = ctx->bluetooth->loadSavedSpeaker();
if (!target) {
httpd_resp_set_status(req, "500 Internal Server Error");
return httpd_resp_send(req, nullptr, 0);
}
const std::string json = core::serializeBluetoothSpeakerJson(&(*target));
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
esp_err_t bluetoothSpeakerPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 256> body{};
if (!readRequestBody(req, body)) {
httpd_resp_set_status(req, "400 Bad Request");
return httpd_resp_send(req, nullptr, 0);
}
const auto target = core::parseBluetoothSpeakerJson(std::string_view(body.data()));
if (!target) {
const std::string json =
core::serializeBluetoothErrorJson(parseErrorToken(target.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
if (auto saved = ctx->bluetooth->saveSpeaker(*target); !saved) {
httpd_resp_set_status(req, "500 Internal Server Error");
return httpd_resp_send(req, nullptr, 0);
}
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, "{\"status\":\"saved\"}", 18);
}
esp_err_t bluetoothSpeakerDeleteHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
(void)ctx->bluetooth->clearSavedSpeaker();
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, "{\"status\":\"cleared\"}", 20);
}
esp_err_t bluetoothReconnectPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
ESP_LOGI(kTag, "bluetooth reconnect HTTP request");
if (auto result = ctx->bluetooth->reconnectSavedSpeaker(true); !result) {
const std::string json =
core::serializeBluetoothErrorJson(bt1035ErrorToken(result.error()));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, "{\"status\":\"connected\"}", 24);
}
esp_err_t bluetoothAutoReconnectPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
@@ -1124,9 +1606,6 @@ SetupWebServer::SetupWebServer()
, bluetooth_(nullptr)
, stations_(nullptr)
, integration_(nullptr)
, routeContext_{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, core::CompanionChipStatus{false, false, false},
core::DeviceIdentity::unknown()}
{
}
@@ -1139,7 +1618,6 @@ SetupWebServer::SetupWebServer(SetupWebServer&& other) noexcept
, bluetooth_(other.bluetooth_)
, stations_(other.stations_)
, integration_(other.integration_)
, routeContext_(other.routeContext_)
{
other.server_ = nullptr;
other.store_ = nullptr;
@@ -1149,8 +1627,6 @@ SetupWebServer::SetupWebServer(SetupWebServer&& other) noexcept
other.bluetooth_ = nullptr;
other.stations_ = nullptr;
other.integration_ = nullptr;
other.routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, {}, core::DeviceIdentity::unknown()};
}
SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
@@ -1167,7 +1643,6 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
bluetooth_ = other.bluetooth_;
stations_ = other.stations_;
integration_ = other.integration_;
routeContext_ = other.routeContext_;
other.server_ = nullptr;
other.store_ = nullptr;
other.netState_ = NetState::Uninitialized;
@@ -1176,9 +1651,6 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
other.bluetooth_ = nullptr;
other.stations_ = nullptr;
other.integration_ = nullptr;
other.routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, core::CompanionChipStatus{false, false, false},
core::DeviceIdentity::unknown()};
}
return *this;
}
@@ -1189,9 +1661,6 @@ SetupWebServer::~SetupWebServer()
httpd_stop(server_);
server_ = nullptr;
}
routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, core::CompanionChipStatus{false, false, false},
core::DeviceIdentity::unknown()};
}
std::expected<void, NetError> SetupWebServer::start(
@@ -1203,6 +1672,7 @@ std::expected<void, NetError> SetupWebServer::start(
station::StationService& stations,
integration::IntegrationService& integration,
ota::OtaService& ota,
webradio::WebRadioService& webRadio,
core::CompanionChipStatus companionChips,
const core::DeviceIdentity& deviceIdentity)
{
@@ -1217,30 +1687,34 @@ std::expected<void, NetError> SetupWebServer::start(
bluetooth_ = &bluetooth;
stations_ = &stations;
integration_ = &integration;
routeContext_.store = &store;
routeContext_.tuner = &tuner;
routeContext_.audio = &audio;
routeContext_.bluetooth = &bluetooth;
routeContext_.stations = &stations;
routeContext_.integration = &integration;
routeContext_.ota = &ota;
routeContext_.companionChips = companionChips;
routeContext_.deviceIdentity = deviceIdentity;
auto& routeContext = routeContextStorage();
routeContext.store = &store;
routeContext.tuner = &tuner;
routeContext.audio = &audio;
routeContext.bluetooth = &bluetooth;
routeContext.stations = &stations;
routeContext.integration = &integration;
routeContext.ota = &ota;
routeContext.webRadio = &webRadio;
routeContext.companionChips = companionChips;
routeContext.deviceIdentity = deviceIdentity;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 12288;
config.max_open_sockets = 3;
config.max_uri_handlers = 40;
config.server_port = 80;
config.lru_purge_enable = true;
config.recv_wait_timeout = 60;
config.send_wait_timeout = 60;
if (httpd_start(&server_, &config) != ESP_OK) {
ESP_LOGE(kTag, "httpd_start failed");
routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, core::CompanionChipStatus{false, false, false},
core::DeviceIdentity::unknown()};
routeContext.store = nullptr;
return std::unexpected(NetError::HttpServerStartFailed);
}
void* routeCtx = &routeContext_;
void* routeCtx = &routeContext;
const httpd_uri_t healthUri = {
.uri = "/api/health",
@@ -1266,6 +1740,14 @@ std::expected<void, NetError> SetupWebServer::start(
};
httpd_register_uri_handler(server_, &wifiUri);
const httpd_uri_t wifiScanUri = {
.uri = "/api/wifi/scan",
.method = HTTP_POST,
.handler = wifiScanPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &wifiScanUri);
const httpd_uri_t tunerStatusUri = {
.uri = "/api/tuner/status",
.method = HTTP_GET,
@@ -1306,6 +1788,14 @@ std::expected<void, NetError> SetupWebServer::start(
};
httpd_register_uri_handler(server_, &tunerSeekUri);
const httpd_uri_t tunerScanUri = {
.uri = "/api/tuner/scan",
.method = HTTP_POST,
.handler = tunerScanPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &tunerScanUri);
const httpd_uri_t audioProfileGetUri = {
.uri = "/api/audio/profile",
.method = HTTP_GET,
@@ -1346,6 +1836,30 @@ std::expected<void, NetError> SetupWebServer::start(
};
httpd_register_uri_handler(server_, &audioBassEnhanceUri);
const httpd_uri_t audioBeepUri = {
.uri = "/api/audio/beep",
.method = HTTP_POST,
.handler = audioBeepPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &audioBeepUri);
const httpd_uri_t streamingGetUri = {
.uri = "/api/streaming",
.method = HTTP_GET,
.handler = streamingGetHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &streamingGetUri);
const httpd_uri_t streamingPostUri = {
.uri = "/api/streaming",
.method = HTTP_POST,
.handler = streamingPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &streamingPostUri);
const httpd_uri_t dspProgramUri = {
.uri = "/api/dsp/program",
.method = HTTP_POST,
@@ -1402,6 +1916,54 @@ std::expected<void, NetError> SetupWebServer::start(
};
httpd_register_uri_handler(server_, &bluetoothPairedUri);
const httpd_uri_t bluetoothScanUri = {
.uri = "/api/bluetooth/scan",
.method = HTTP_POST,
.handler = bluetoothScanPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothScanUri);
const httpd_uri_t bluetoothConnectUri = {
.uri = "/api/bluetooth/connect",
.method = HTTP_POST,
.handler = bluetoothConnectPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothConnectUri);
const httpd_uri_t bluetoothSpeakerGetUri = {
.uri = "/api/bluetooth/speaker",
.method = HTTP_GET,
.handler = bluetoothSpeakerGetHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothSpeakerGetUri);
const httpd_uri_t bluetoothSpeakerPostUri = {
.uri = "/api/bluetooth/speaker",
.method = HTTP_POST,
.handler = bluetoothSpeakerPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothSpeakerPostUri);
const httpd_uri_t bluetoothSpeakerDeleteUri = {
.uri = "/api/bluetooth/speaker",
.method = HTTP_DELETE,
.handler = bluetoothSpeakerDeleteHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothSpeakerDeleteUri);
const httpd_uri_t bluetoothReconnectUri = {
.uri = "/api/bluetooth/reconnect",
.method = HTTP_POST,
.handler = bluetoothReconnectPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothReconnectUri);
const httpd_uri_t bluetoothAutoReconnectUri = {
.uri = "/api/bluetooth/auto-reconnect",
.method = HTTP_POST,
@@ -0,0 +1,570 @@
/**
* @file SigmaStudioTcpServer.cpp
* @brief SigmaStudioTcpServer implementation.
*
* DigiRadio firmware https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* @author Michele Bigi
* @date 2026-08-07
*/
#include "net/SigmaStudioTcpServer.hpp"
#include "adau1701/FlashDspProgramSource.hpp"
#include "core/DspProgram.hpp"
#include "core/DspProgramBlob.hpp"
#include "core/RegisterWrite.hpp"
#include "SigmaStudioFW.h"
#include "esp_log.h"
#include "lwip/sockets.h"
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <span>
#include <vector>
namespace net {
namespace {
constexpr char kTag[] = "SigmaTcp";
constexpr std::uint16_t kPort = 8086U;
constexpr std::size_t kRecvBufSize = 16U * 1024U;
constexpr std::uint32_t kTaskStackBytes = 8192U;
constexpr UBaseType_t kTaskPriority = 5U;
constexpr std::uint8_t kCtrlWrite = 0x09U;
constexpr std::uint8_t kCtrlReadReq = 0x0AU;
constexpr std::uint8_t kCtrlReadResp = 0x0BU;
constexpr std::uint8_t kChipAddrDsp = 0x01U;
// board::pins::Adau1701Addr (main/board_pins.hpp), duplicated as a literal
// to avoid a net -> main include dependency; keep in sync if the board's
// I2C address ever changes. The reference project accepts both the IC
// index (0x01) and the raw address as chipAddr since it's unclear which
// convention real SigmaStudio uses for a single-IC project — mirrored here.
constexpr std::uint8_t kDspI2cAddr7 = 0x34U;
[[nodiscard]] bool isDspChipAddr(std::uint8_t chipAddr) noexcept
{
return chipAddr == kChipAddrDsp || chipAddr == kDspI2cAddr7;
}
/**
* @brief activeListenFd process-lifetime storage for the listen fd.
*
* @dname activeListenFd
* @return Reference to the singleton listen-fd slot.
* @pubstate Written by SigmaStudioTcpServer::start()/stop(); read by
* acceptLoopTask(). SigmaStudioTcpServer is constructed as a
* local and move-relocated into NetBootstrap (see
* NetBootstrap.cpp), so a `this` pointer captured at start()
* time would go stale once that local's stack frame returns
* same problem SetupWebServer's routeContextStorage() solves.
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::atomic<int>& activeListenFd() noexcept
{
static std::atomic<int> fd{-1};
return fd;
}
constexpr std::uint16_t kProgRamStart = 0x0400U;
constexpr std::uint16_t kProgRamEnd = 0x07FFU;
constexpr std::uint16_t kCtrlRegStart = 0x0800U;
constexpr std::uint16_t kCoreControlReg = 0x081CU;
constexpr std::uint8_t kDspRunBit = 0x04U;
constexpr unsigned kWordBytesParam = 4U;
constexpr unsigned kWordsPerSafeload = 5U;
constexpr std::size_t kWriteHeaderSize = 10U;
constexpr std::size_t kReadReqHeaderSize = 8U;
constexpr std::size_t kReadRespHeaderSize = 9U;
constexpr std::uint16_t kMaxReadBytes = 256U;
constexpr std::size_t kMaxCaptureRegions = 32U; // core::DspProgramBlob kMaxWriteCount
constexpr std::size_t kMaxRegionPayload = 16U * 1024U; // kMaxWritePayload
[[nodiscard]] unsigned wordSizeForAddress(std::uint16_t address) noexcept
{
if (address >= kProgRamStart && address <= kProgRamEnd) {
return 5U;
}
if (address >= kCtrlRegStart) {
return 2U;
}
return 4U;
}
[[nodiscard]] std::uint16_t readBe16(const std::uint8_t* p) noexcept
{
return static_cast<std::uint16_t>((static_cast<std::uint16_t>(p[0]) << 8)
| p[1]);
}
void writeBe16(std::uint8_t* p, std::uint16_t value) noexcept
{
p[0] = static_cast<std::uint8_t>((value >> 8) & 0xFFU);
p[1] = static_cast<std::uint8_t>(value & 0xFFU);
}
/**
* @brief PendingRegion one coalesced contiguous write during a Download.
*/
struct PendingRegion {
std::uint16_t address;
std::vector<std::uint8_t> data;
};
/**
* @brief DownloadCapture coalesces a Link Compile Download for persistence.
*
* Merges contiguous direct (non-safeload) DSP writes into a handful of
* regions (mirroring the ~5-block shape of EmbeddedDspProgramSource), then
* on finish() serialises and stores them via FlashDspProgramSource so the
* downloaded program becomes what DigiRadio boots with next time. Bails
* out (does not persist) if the session doesn't fit the DRAD blob's own
* caps the DSP still runs fine from what was already written live.
*/
class DownloadCapture {
public:
void addWrite(std::uint16_t address, std::span<const std::uint8_t> data)
{
if (overflowed_ || data.empty()) {
return;
}
if (tryExtendLast(address, data)) {
return;
}
if (regions_.size() >= kMaxCaptureRegions) {
abandon();
return;
}
regions_.push_back(PendingRegion{
address, std::vector<std::uint8_t>(data.begin(), data.end())});
}
void finish()
{
if (!overflowed_ && !regions_.empty()) {
persist();
}
regions_.clear();
overflowed_ = false;
}
private:
[[nodiscard]] bool tryExtendLast(std::uint16_t address,
std::span<const std::uint8_t> data)
{
if (regions_.empty()) {
return false;
}
PendingRegion& last = regions_.back();
const unsigned wordSize = wordSizeForAddress(last.address);
const auto lastWords =
static_cast<std::uint32_t>(last.data.size() / wordSize);
const std::uint32_t lastEnd =
static_cast<std::uint32_t>(last.address) + lastWords;
if (lastEnd != address
|| last.data.size() + data.size() > kMaxRegionPayload) {
return false;
}
last.data.insert(last.data.end(), data.begin(), data.end());
return true;
}
void abandon()
{
overflowed_ = true;
regions_.clear();
ESP_LOGW(kTag,
"Download too fragmented to persist as boot program — "
"DSP still runs live, only reboot-persistence is skipped");
}
void persist()
{
std::vector<core::RegisterWrite> writes;
writes.reserve(regions_.size());
for (auto& region : regions_) {
writes.emplace_back(region.address, std::move(region.data));
}
const core::DspProgram program(std::move(writes));
const std::vector<std::uint8_t> blob =
core::serializeDspProgramBlob(program);
if (auto stored = adau1701::FlashDspProgramSource::storeBlob(blob);
!stored) {
ESP_LOGW(kTag, "SigmaStudio download not persisted (flash store failed)");
return;
}
ESP_LOGI(kTag,
"SigmaStudio download persisted as boot program (%u bytes)",
static_cast<unsigned>(blob.size()));
}
std::vector<PendingRegion> regions_;
bool overflowed_ = false;
};
/**
* @brief ConnectionState per-TCP-connection Download tracking.
*/
struct ConnectionState {
DownloadCapture capture;
bool dspRunning = false;
};
void directWrite(std::uint16_t address, std::span<const std::uint8_t> data)
{
if (data.empty()) {
return;
}
sigma_studio_lock();
SIGMA_WRITE_REGISTER_BLOCK(
0U, address, static_cast<unsigned int>(data.size()),
const_cast<ADI_REG_TYPE*>(
reinterpret_cast<const ADI_REG_TYPE*>(data.data())));
sigma_studio_unlock();
}
void safeloadWrite(std::uint16_t address, std::span<const std::uint8_t> data)
{
const auto totalWords =
static_cast<unsigned>(data.size() / kWordBytesParam);
unsigned offset = 0U;
sigma_studio_lock();
while (offset < totalWords) {
const unsigned words =
std::min(totalWords - offset, kWordsPerSafeload);
unsigned addrs[kWordsPerSafeload];
for (unsigned i = 0U; i < words; ++i) {
addrs[i] = static_cast<unsigned>(address) + offset + i;
}
sigma_safeload_raw_block(
static_cast<unsigned char>(words), addrs,
data.data() + static_cast<std::size_t>(offset) * kWordBytesParam);
offset += words;
}
sigma_studio_unlock();
}
void trackDownloadCompletion(std::uint16_t address,
std::span<const std::uint8_t> payload,
ConnectionState& state)
{
if (address != kCoreControlReg || payload.size() < 2U) {
return;
}
const bool wasRunning = state.dspRunning;
state.dspRunning = (payload.back() & kDspRunBit) != 0U;
if (!wasRunning && state.dspRunning) {
state.capture.finish();
}
}
void dispatchWrite(std::uint16_t address, std::span<const std::uint8_t> payload,
std::uint8_t safeload, ConnectionState& state)
{
if (safeload != 0U) {
safeloadWrite(address, payload);
return;
}
if (!state.dspRunning) {
state.capture.addWrite(address, payload);
}
directWrite(address, payload);
trackDownloadCompletion(address, payload, state);
}
[[nodiscard]] std::size_t tryConsumeWrite(const std::uint8_t* p,
std::size_t avail,
ConnectionState& state)
{
if (avail < kWriteHeaderSize) {
return 0U;
}
const std::uint16_t totalLen = readBe16(p + 3U);
if (totalLen < kWriteHeaderSize) {
return 1U; // malformed frame — resync by one byte
}
if (avail < totalLen) {
return 0U;
}
const std::uint8_t safeload = p[1];
const std::uint8_t chipAddr = p[5];
const std::uint16_t dataLen = readBe16(p + 6U);
const std::uint16_t address = readBe16(p + 8U);
const std::uint16_t maxPayload =
static_cast<std::uint16_t>(totalLen - kWriteHeaderSize);
const std::uint16_t safeLen = std::min(dataLen, maxPayload);
if (isDspChipAddr(chipAddr)) {
const std::span<const std::uint8_t> payload(p + kWriteHeaderSize,
safeLen);
dispatchWrite(address, payload, safeload, state);
}
return totalLen;
}
void sendReadResponse(int clientFd, std::uint8_t chipAddr,
std::uint16_t address, std::uint16_t requested)
{
const std::uint16_t length = std::min(requested, kMaxReadBytes);
std::vector<std::uint8_t> data(length);
sigma_studio_lock();
const int result =
length == 0U ? 0 : sigma_i2c_read(address, data.data(), length);
sigma_studio_unlock();
if (result != 0) {
ESP_LOGW(kTag,
"read 0x%04x len=%u chipAddr=0x%02x: sigma_i2c_read failed",
static_cast<unsigned>(address),
static_cast<unsigned>(length),
static_cast<unsigned>(chipAddr));
return;
}
std::vector<std::uint8_t> resp(kReadRespHeaderSize + length);
resp[0] = kCtrlReadResp;
writeBe16(resp.data() + 1U,
static_cast<std::uint16_t>(kReadRespHeaderSize + length));
resp[3] = chipAddr;
writeBe16(resp.data() + 4U, length);
writeBe16(resp.data() + 6U, address);
resp[8] = 0x01U;
std::memcpy(resp.data() + kReadRespHeaderSize, data.data(), length);
const ssize_t sent = send(clientFd, resp.data(), resp.size(), 0);
ESP_LOGI(kTag,
"read 0x%04x len=%u chipAddr=0x%02x: sent %d/%u resp bytes",
static_cast<unsigned>(address), static_cast<unsigned>(length),
static_cast<unsigned>(chipAddr), static_cast<int>(sent),
static_cast<unsigned>(resp.size()));
}
[[nodiscard]] std::size_t tryConsumeRead(const std::uint8_t* p,
std::size_t avail, int clientFd)
{
if (avail < kReadReqHeaderSize) {
return 0U;
}
const std::uint16_t totalLen = readBe16(p + 1U);
if (totalLen < kReadReqHeaderSize) {
return 1U; // malformed frame — resync by one byte
}
if (avail < totalLen) {
return 0U;
}
const std::uint8_t chipAddr = p[3];
const std::uint16_t dataLen = readBe16(p + 4U);
const std::uint16_t address = readBe16(p + 6U);
if (isDspChipAddr(chipAddr)) {
sendReadResponse(clientFd, chipAddr, address, dataLen);
} else {
ESP_LOGW(kTag,
"read req dropped: chipAddr=0x%02x not DSP (want 0x%02x or "
"0x%02x), addr=0x%04x len=%u",
static_cast<unsigned>(chipAddr),
static_cast<unsigned>(kChipAddrDsp),
static_cast<unsigned>(kDspI2cAddr7),
static_cast<unsigned>(address),
static_cast<unsigned>(dataLen));
}
return totalLen;
}
[[nodiscard]] std::size_t processBuffer(std::uint8_t* buf, std::size_t len,
int clientFd, ConnectionState& state)
{
std::size_t pos = 0U;
while (pos < len) {
const std::uint8_t ctrl = buf[pos];
std::size_t consumed = 0U;
if (ctrl == kCtrlWrite) {
consumed = tryConsumeWrite(buf + pos, len - pos, state);
} else if (ctrl == kCtrlReadReq) {
consumed = tryConsumeRead(buf + pos, len - pos, clientFd);
} else {
ESP_LOGW(kTag, "unrecognized ctrl byte 0x%02x — resyncing",
static_cast<unsigned>(ctrl));
consumed = 1U;
}
if (consumed == 0U) {
break;
}
pos += consumed;
}
return pos;
}
/** @brief Diagnostic: hex-dump up to the first 48 bytes of a receive. */
void logRxHexDump(const std::uint8_t* p, std::size_t len)
{
constexpr std::size_t kMaxDump = 48U;
char hex[3U * kMaxDump + 1U];
const std::size_t n = std::min(len, kMaxDump);
for (std::size_t i = 0U; i < n; ++i) {
std::snprintf(hex + i * 3U, 4U, "%02x ", static_cast<unsigned>(p[i]));
}
ESP_LOGI(kTag, "rx %u bytes: %s%s", static_cast<unsigned>(len), hex,
len > kMaxDump ? "..." : "");
}
void serveClient(int clientFd)
{
ConnectionState state;
std::vector<std::uint8_t> buf(kRecvBufSize);
std::size_t len = 0U;
while (true) {
const ssize_t received =
recv(clientFd, buf.data() + len, buf.size() - len, 0);
if (received <= 0) {
break;
}
logRxHexDump(buf.data() + len, static_cast<std::size_t>(received));
len += static_cast<std::size_t>(received);
const std::size_t consumed = processBuffer(buf.data(), len, clientFd, state);
if (consumed > 0U && consumed < len) {
std::memmove(buf.data(), buf.data() + consumed, len - consumed);
}
len = consumed <= len ? len - consumed : 0U;
}
}
} // namespace
SigmaStudioTcpServer::SigmaStudioTcpServer()
: listenFd_(-1)
, task_(nullptr)
{
}
SigmaStudioTcpServer::~SigmaStudioTcpServer()
{
stop();
}
SigmaStudioTcpServer::SigmaStudioTcpServer(SigmaStudioTcpServer&& other) noexcept
: listenFd_(other.listenFd_)
, task_(other.task_)
{
other.listenFd_ = -1;
other.task_ = nullptr;
}
SigmaStudioTcpServer&
SigmaStudioTcpServer::operator=(SigmaStudioTcpServer&& other) noexcept
{
if (this != &other) {
stop();
listenFd_ = other.listenFd_;
task_ = other.task_;
other.listenFd_ = -1;
other.task_ = nullptr;
}
return *this;
}
void SigmaStudioTcpServer::stop() noexcept
{
if (task_ != nullptr) {
vTaskDelete(task_);
task_ = nullptr;
}
activeListenFd().store(-1, std::memory_order_release);
if (listenFd_ >= 0) {
close(listenFd_);
listenFd_ = -1;
}
}
std::expected<void, NetError> SigmaStudioTcpServer::start()
{
if (task_ != nullptr) {
return {};
}
const int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
ESP_LOGE(kTag, "socket() failed");
return std::unexpected(NetError::TcpServerStartFailed);
}
const int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(kPort);
if (bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0
|| listen(fd, 1) != 0) {
ESP_LOGE(kTag, "bind/listen failed");
close(fd);
return std::unexpected(NetError::TcpServerStartFailed);
}
listenFd_ = fd;
activeListenFd().store(fd, std::memory_order_release);
const BaseType_t created =
xTaskCreate(&SigmaStudioTcpServer::acceptLoopTask, "sigma_tcp",
kTaskStackBytes, nullptr, kTaskPriority, &task_);
if (created != pdPASS) {
ESP_LOGE(kTag, "xTaskCreate failed");
activeListenFd().store(-1, std::memory_order_release);
close(listenFd_);
listenFd_ = -1;
task_ = nullptr;
return std::unexpected(NetError::TcpServerStartFailed);
}
ESP_LOGI(kTag, "SigmaStudio TCP bridge listening on port %u",
static_cast<unsigned>(kPort));
return {};
}
void SigmaStudioTcpServer::acceptLoopTask(void* /*arg*/)
{
while (true) {
const int listenFd = activeListenFd().load(std::memory_order_acquire);
sockaddr_in clientAddr{};
socklen_t clientLen = sizeof(clientAddr);
const int clientFd = accept(
listenFd, reinterpret_cast<sockaddr*>(&clientAddr), &clientLen);
if (clientFd < 0) {
ESP_LOGW(kTag, "accept() failed: errno=%d", errno);
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
ESP_LOGI(kTag, "SigmaStudio client connected");
serveClient(clientFd);
ESP_LOGI(kTag, "SigmaStudio client disconnected");
close(clientFd);
}
}
} // namespace net
+8 -1
View File
@@ -18,6 +18,7 @@
#include "net/SoftApHost.hpp"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_log.h"
@@ -69,11 +70,17 @@ std::expected<void, NetError> SoftApHost::start()
return {};
}
if (esp_wifi_set_mode(WIFI_MODE_AP) != ESP_OK) {
if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_set_mode failed");
return std::unexpected(NetError::WifiConfigFailed);
}
wifi_config_t staCfg = {};
if (esp_wifi_set_config(WIFI_IF_STA, &staCfg) != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_set_config STA failed");
return std::unexpected(NetError::WifiConfigFailed);
}
wifi_config_t wifiCfg = {};
const std::string_view ssid = config_.ssid();
std::memcpy(wifiCfg.ap.ssid, ssid.data(), ssid.size());
+137 -12
View File
@@ -36,9 +36,62 @@ namespace {
constexpr char kTag[] = "StaClient";
constexpr int kConnectedBit = BIT0;
constexpr int kFailedBit = BIT1;
constexpr TickType_t kConnectTimeout = pdMS_TO_TICKS(30000);
constexpr int kMaxConnectRetries = 10;
constexpr TickType_t kConnectTimeout = pdMS_TO_TICKS(45000);
constexpr TickType_t kRetryDelay = pdMS_TO_TICKS(800);
EventGroupHandle_t s_wifiEventGroup = nullptr;
int s_connectRetries = 0;
/**
* @brief disconnectReasonString map ESP-IDF Wi-Fi disconnect reason codes.
*
* @dname disconnectReasonString
* @param reason wifi_event_sta_disconnected_t::reason value.
* @return Short English label for logs.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] const char* disconnectReasonString(uint8_t reason) noexcept
{
switch (reason) {
case WIFI_REASON_AUTH_EXPIRE:
return "auth expired (wrong password?)";
case WIFI_REASON_NO_AP_FOUND:
return "no AP found (check SSID / 2.4 GHz)";
case WIFI_REASON_AUTH_FAIL:
return "auth failed (check password)";
case WIFI_REASON_ASSOC_FAIL:
return "association failed";
case WIFI_REASON_HANDSHAKE_TIMEOUT:
return "handshake timeout";
case WIFI_REASON_BEACON_TIMEOUT:
return "beacon timeout (weak signal / PS)";
case WIFI_REASON_CONNECTION_FAIL:
return "connection failed";
default:
return "unknown";
}
}
/**
* @brief applyStaLinkTuning stabilise STA link after connect.
*
* @dname applyStaLinkTuning
* @pubstate Disables PS, forces 20 MHz, disables inactive disconnect.
*
* @author Michele Bigi
* @date 2026-08-05
*/
void applyStaLinkTuning() noexcept
{
esp_wifi_set_ps(WIFI_PS_NONE);
esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT20);
esp_wifi_set_inactive_time(WIFI_IF_STA, 0);
ESP_LOGI(kTag, "STA link tuning applied (PS off, HT20, inactive off)");
}
/**
* @brief wifiEventHandler signal connect success or failure.
@@ -59,29 +112,72 @@ void wifiEventHandler(void* arg,
void* eventData)
{
(void)arg;
(void)eventData;
if (eventBase == WIFI_EVENT && eventId == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (eventBase == WIFI_EVENT
&& eventId == WIFI_EVENT_STA_DISCONNECTED) {
const auto* disc =
static_cast<const wifi_event_sta_disconnected_t*>(eventData);
const uint8_t reason = disc != nullptr ? disc->reason : 0U;
ESP_LOGW(kTag, "STA disconnected (reason %u: %s)",
static_cast<unsigned>(reason),
disconnectReasonString(reason));
if (s_wifiEventGroup != nullptr) {
if (s_connectRetries < kMaxConnectRetries) {
++s_connectRetries;
ESP_LOGI(kTag, "retrying STA connect (%d/%d)",
s_connectRetries, kMaxConnectRetries);
vTaskDelay(kRetryDelay);
esp_wifi_connect();
return;
}
xEventGroupSetBits(s_wifiEventGroup, kFailedBit);
return;
}
ESP_LOGW(kTag, "STA link lost — reconnecting");
esp_wifi_connect();
} else if (eventBase == IP_EVENT && eventId == IP_EVENT_STA_GOT_IP) {
const auto* event =
static_cast<const ip_event_got_ip_t*>(eventData);
if (event != nullptr) {
ESP_LOGI(kTag, "STA IP " IPSTR, IP2STR(&event->ip_info.ip));
}
applyStaLinkTuning();
if (s_wifiEventGroup != nullptr) {
xEventGroupSetBits(s_wifiEventGroup, kConnectedBit);
}
}
}
void unregisterStaHandlers(esp_event_handler_instance_t wifiHandler,
esp_event_handler_instance_t ipHandler) noexcept
{
if (wifiHandler != nullptr) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
wifiHandler);
}
if (ipHandler != nullptr) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
ipHandler);
}
}
} // namespace
StaClient::StaClient()
: connected_(false)
, wifiHandler_(nullptr)
, ipHandler_(nullptr)
{
}
StaClient::~StaClient()
{
unregisterStaHandlers(wifiHandler_, ipHandler_);
wifiHandler_ = nullptr;
ipHandler_ = nullptr;
if (connected_) {
esp_wifi_stop();
connected_ = false;
@@ -90,18 +186,27 @@ StaClient::~StaClient()
StaClient::StaClient(StaClient&& other) noexcept
: connected_(other.connected_)
, wifiHandler_(other.wifiHandler_)
, ipHandler_(other.ipHandler_)
{
other.connected_ = false;
other.wifiHandler_ = nullptr;
other.ipHandler_ = nullptr;
}
StaClient& StaClient::operator=(StaClient&& other) noexcept
{
if (this != &other) {
unregisterStaHandlers(wifiHandler_, ipHandler_);
if (connected_) {
esp_wifi_stop();
}
connected_ = other.connected_;
wifiHandler_ = other.wifiHandler_;
ipHandler_ = other.ipHandler_;
other.connected_ = false;
other.wifiHandler_ = nullptr;
other.ipHandler_ = nullptr;
}
return *this;
}
@@ -122,19 +227,18 @@ StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname
if (s_wifiEventGroup == nullptr) {
return std::unexpected(NetError::StaConnectFailed);
}
s_connectRetries = 0;
esp_event_handler_instance_t instanceAnyId = nullptr;
esp_event_handler_instance_t instanceGotIp = nullptr;
esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifiEventHandler,
nullptr,
&instanceAnyId);
&wifiHandler_);
esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifiEventHandler,
nullptr,
&instanceGotIp);
&ipHandler_);
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
return std::unexpected(NetError::WifiConfigFailed);
@@ -150,15 +254,31 @@ StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname
wifi_config_t wifiCfg = {};
const std::string_view ssid = creds.ssid().value();
const std::size_t ssidCopy =
std::min(ssid.size(), sizeof(wifiCfg.sta.ssid) - 1);
std::min(ssid.size(), sizeof(wifiCfg.sta.ssid) - 1U);
std::memcpy(wifiCfg.sta.ssid, ssid.data(), ssidCopy);
wifiCfg.sta.ssid[ssidCopy] = '\0';
std::size_t pwdLen = 0U;
creds.password().usePlaintext([&](std::string_view pwd) {
pwdLen = pwd.size();
const std::size_t pwdCopy =
std::min(pwd.size(), sizeof(wifiCfg.sta.password) - 1);
std::min(pwd.size(), sizeof(wifiCfg.sta.password) - 1U);
std::memcpy(wifiCfg.sta.password, pwd.data(), pwdCopy);
wifiCfg.sta.password[pwdCopy] = '\0';
});
if (pwdLen == 0U) {
wifiCfg.sta.threshold.authmode = WIFI_AUTH_OPEN;
} else {
wifiCfg.sta.threshold.authmode = WIFI_AUTH_WPA2_WPA3_PSK;
}
wifiCfg.sta.pmf_cfg.capable = true;
wifiCfg.sta.pmf_cfg.required = false;
wifiCfg.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifiCfg.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
wifiCfg.sta.failure_retry_cnt = 3;
wifiCfg.sta.listen_interval = 1;
if (esp_wifi_set_config(WIFI_IF_STA, &wifiCfg) != ESP_OK) {
return std::unexpected(NetError::WifiConfigFailed);
}
@@ -167,20 +287,22 @@ StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname
return std::unexpected(NetError::WifiStartFailed);
}
esp_wifi_set_protocol(WIFI_IF_STA,
WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G
| WIFI_PROTOCOL_11N);
esp_wifi_set_ps(WIFI_PS_NONE);
const EventBits_t bits = xEventGroupWaitBits(s_wifiEventGroup,
kConnectedBit | kFailedBit,
pdTRUE,
pdFALSE,
kConnectTimeout);
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
instanceGotIp);
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
instanceAnyId);
vEventGroupDelete(s_wifiEventGroup);
s_wifiEventGroup = nullptr;
if ((bits & kConnectedBit) != 0) {
applyStaLinkTuning();
connected_ = true;
if (!hostLabel.empty()) {
if (mdns_init() == ESP_OK) {
@@ -192,6 +314,9 @@ StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname
return {};
}
unregisterStaHandlers(wifiHandler_, ipHandler_);
wifiHandler_ = nullptr;
ipHandler_ = nullptr;
esp_wifi_stop();
ESP_LOGW(kTag, "STA connect timed out or failed");
if ((bits & kFailedBit) != 0) {
+220
View File
@@ -0,0 +1,220 @@
/**
* @file WifiScanner.cpp
* @brief WifiScanner implementation.
*
* DigiRadio firmware https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-05
*/
#include "net/WifiScanner.hpp"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <unordered_map>
namespace net {
namespace {
constexpr char kTag[] = "WifiScanner";
/**
* @brief authToken map ESP-IDF auth mode to a short API token.
*
* @dname authToken
* @param auth wifi_ap_record_t::authmode value.
* @return Stable lowercase token for JSON.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] const char* authToken(wifi_auth_mode_t auth) noexcept
{
switch (auth) {
case WIFI_AUTH_OPEN:
return "open";
case WIFI_AUTH_WEP:
return "wep";
case WIFI_AUTH_WPA_PSK:
return "wpa";
case WIFI_AUTH_WPA2_PSK:
return "wpa2";
case WIFI_AUTH_WPA_WPA2_PSK:
return "wpa_wpa2";
case WIFI_AUTH_WPA2_ENTERPRISE:
return "wpa2_enterprise";
case WIFI_AUTH_WPA3_PSK:
return "wpa3";
case WIFI_AUTH_WPA2_WPA3_PSK:
return "wpa2_wpa3";
case WIFI_AUTH_WAPI_PSK:
return "wapi";
default:
return "unknown";
}
}
/**
* @brief ssidFromRecord read a possibly unterminated SSID field.
*
* @dname ssidFromRecord
* @param record Raw ESP-IDF scan entry.
* @return SSID string (empty when hidden).
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::string ssidFromRecord(const wifi_ap_record_t& record)
{
const std::size_t len =
strnlen(reinterpret_cast<const char*>(record.ssid), sizeof(record.ssid));
return std::string(reinterpret_cast<const char*>(record.ssid), len);
}
/**
* @brief ensureStaNetif create default STA netif when missing.
*
* @dname ensureStaNetif
* @return true when STA netif exists or was created.
* @pubstate may call esp_netif_create_default_wifi_sta().
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] bool ensureStaNetif() noexcept
{
if (esp_netif_get_handle_from_ifkey("WIFI_STA_DEF") != nullptr) {
return true;
}
return esp_netif_create_default_wifi_sta() != nullptr;
}
/**
* @brief dedupeBySsid keep strongest RSSI per SSID.
*
* @dname dedupeBySsid
* @param records Raw scan rows from esp_wifi.
* @return Sorted networks, strongest first.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::vector<core::WifiScannedNetwork>
dedupeBySsid(const std::vector<wifi_ap_record_t>& records)
{
std::unordered_map<std::string, core::WifiScannedNetwork> best;
best.reserve(records.size());
for (const wifi_ap_record_t& record : records) {
const std::string ssid = ssidFromRecord(record);
if (ssid.empty()) {
continue;
}
core::WifiScannedNetwork entry = {
.ssid = ssid,
.rssiDbm = record.rssi,
.auth = authToken(record.authmode),
.channel = record.primary,
};
const auto existing = best.find(ssid);
if (existing == best.end() || entry.rssiDbm > existing->second.rssiDbm) {
best.emplace(ssid, std::move(entry));
}
}
std::vector<core::WifiScannedNetwork> networks;
networks.reserve(best.size());
for (auto& item : best) {
networks.push_back(std::move(item.second));
}
std::sort(networks.begin(), networks.end(),
[](const core::WifiScannedNetwork& left,
const core::WifiScannedNetwork& right) {
return left.rssiDbm > right.rssiDbm;
});
return networks;
}
} // namespace
std::expected<std::vector<core::WifiScannedNetwork>, NetError>
WifiScanner::scanNearby()
{
wifi_mode_t mode = WIFI_MODE_NULL;
if (esp_wifi_get_mode(&mode) != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_get_mode failed");
return std::unexpected(NetError::WifiScanFailed);
}
if (mode == WIFI_MODE_AP) {
if (!ensureStaNetif()) {
ESP_LOGE(kTag, "STA netif creation failed");
return std::unexpected(NetError::WifiScanFailed);
}
wifi_config_t staCfg = {};
if (esp_wifi_set_config(WIFI_IF_STA, &staCfg) != ESP_OK) {
ESP_LOGE(kTag, "STA config for scan failed");
return std::unexpected(NetError::WifiScanFailed);
}
if (esp_wifi_set_mode(WIFI_MODE_APSTA) != ESP_OK) {
ESP_LOGE(kTag, "APSTA mode switch failed");
return std::unexpected(NetError::WifiScanFailed);
}
vTaskDelay(pdMS_TO_TICKS(300));
}
(void)esp_wifi_scan_stop();
(void)esp_wifi_clear_ap_list();
wifi_scan_config_t scanCfg = {};
scanCfg.channel = 0;
scanCfg.show_hidden = true;
scanCfg.scan_type = WIFI_SCAN_TYPE_ACTIVE;
scanCfg.scan_time.active.min = 300U;
scanCfg.scan_time.active.max = 1200U;
const esp_err_t scanErr = esp_wifi_scan_start(&scanCfg, true);
if (scanErr != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_scan_start failed (%d)", static_cast<int>(scanErr));
return std::unexpected(NetError::WifiScanFailed);
}
std::uint16_t count = 0U;
if (esp_wifi_scan_get_ap_num(&count) != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_scan_get_ap_num failed");
return std::unexpected(NetError::WifiScanFailed);
}
std::vector<wifi_ap_record_t> records(count);
if (count > 0U
&& esp_wifi_scan_get_ap_records(&count, records.data()) != ESP_OK) {
ESP_LOGE(kTag, "esp_wifi_scan_get_ap_records failed");
return std::unexpected(NetError::WifiScanFailed);
}
records.resize(count);
ESP_LOGI(kTag, "raw AP count %u", static_cast<unsigned>(count));
const auto networks = dedupeBySsid(records);
ESP_LOGI(kTag, "scan found %u unique network(s)",
static_cast<unsigned>(networks.size()));
return networks;
}
} // namespace net
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -122,6 +122,25 @@ public:
[[nodiscard]] std::expected<void, core::StoreError>
clearLastPresetIndex() override;
[[nodiscard]] bool hasBtSpeakerTarget() const override;
[[nodiscard]] std::expected<void, core::StoreError>
saveBtSpeakerTarget(const core::BtSpeakerTarget& target) override;
[[nodiscard]] std::expected<core::BtSpeakerTarget, core::StoreError>
loadBtSpeakerTarget() const override;
[[nodiscard]] std::expected<void, core::StoreError>
clearBtSpeakerTarget() override;
[[nodiscard]] bool hasWebRadioConfig() const override;
[[nodiscard]] std::expected<void, core::StoreError>
saveWebRadioConfigJson(std::string_view json) override;
[[nodiscard]] std::expected<std::string, core::StoreError>
loadWebRadioConfigJson() const override;
};
} // namespace secure_store
@@ -18,6 +18,7 @@
#include "secure_store/NvsSecureStore.hpp"
#include "core/Bt1035At.hpp"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
@@ -33,6 +34,9 @@ constexpr char kSsidKey[] = "wifi_ssid";
constexpr char kPasswordKey[] = "wifi_pwd";
constexpr char kStationListKey[] = "station_list";
constexpr char kLastPresetKey[] = "last_preset";
constexpr char kBtSpeakerMacKey[] = "bt_spk_mac";
constexpr char kBtSpeakerNameKey[] = "bt_spk_name";
constexpr char kWebRadioConfigKey[] = "web_radio_cfg";
constexpr char kTag[] = "NvsSecureStore";
} // namespace
@@ -41,8 +45,15 @@ bool NvsSecureStore::hasWifiCredentials() const
nvs_handle_t handle = 0;
const esp_err_t openErr = nvs_open(kNamespace, NVS_READONLY, &handle);
if (openErr != ESP_OK) {
ESP_LOGW(kTag, "nvs_open failed in hasWifiCredentials (0x%x)",
static_cast<unsigned>(openErr));
if (openErr == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGD(kTag,
"no wifi credentials namespace yet in hasWifiCredentials "
"(0x%x)",
static_cast<unsigned>(openErr));
} else {
ESP_LOGW(kTag, "nvs_open failed in hasWifiCredentials (0x%x)",
static_cast<unsigned>(openErr));
}
return false;
}
@@ -313,4 +324,170 @@ std::expected<void, core::StoreError> NvsSecureStore::clearLastPresetIndex()
return {};
}
bool NvsSecureStore::hasBtSpeakerTarget() const
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
return false;
}
std::size_t macLen = 0;
const esp_err_t err = nvs_get_str(handle, kBtSpeakerMacKey, nullptr, &macLen);
nvs_close(handle);
return err == ESP_OK && macLen > 1U;
}
std::expected<void, core::StoreError>
NvsSecureStore::saveBtSpeakerTarget(const core::BtSpeakerTarget& target)
{
if (!core::isValidBt1035Mac(target.mac)) {
return std::unexpected(core::StoreError::InvalidData);
}
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return std::unexpected(core::StoreError::IoFailed);
}
esp_err_t err = nvs_set_str(handle, kBtSpeakerMacKey, target.mac.c_str());
if (err == ESP_OK) {
err = nvs_set_str(handle, kBtSpeakerNameKey, target.name.c_str());
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
return std::unexpected(core::StoreError::IoFailed);
}
ESP_LOGI(kTag, "BT speaker target saved (%s)", target.mac.c_str());
return {};
}
std::expected<core::BtSpeakerTarget, core::StoreError>
NvsSecureStore::loadBtSpeakerTarget() const
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
return std::unexpected(core::StoreError::NotFound);
}
std::size_t macLen = 0;
if (nvs_get_str(handle, kBtSpeakerMacKey, nullptr, &macLen) != ESP_OK
|| macLen == 0U) {
nvs_close(handle);
return std::unexpected(core::StoreError::NotFound);
}
std::vector<char> macBuf(macLen);
if (nvs_get_str(handle, kBtSpeakerMacKey, macBuf.data(), &macLen) != ESP_OK) {
nvs_close(handle);
return std::unexpected(core::StoreError::IoFailed);
}
std::string name;
std::size_t nameLen = 0;
if (nvs_get_str(handle, kBtSpeakerNameKey, nullptr, &nameLen) == ESP_OK
&& nameLen > 0U) {
std::vector<char> nameBuf(nameLen);
if (nvs_get_str(handle, kBtSpeakerNameKey, nameBuf.data(), &nameLen)
== ESP_OK) {
name.assign(nameBuf.data());
}
}
nvs_close(handle);
const std::string mac = core::normalizeBt1035Mac(macBuf.data());
if (!core::isValidBt1035Mac(mac)) {
return std::unexpected(core::StoreError::InvalidData);
}
return core::BtSpeakerTarget{.mac = mac, .name = std::move(name)};
}
std::expected<void, core::StoreError> NvsSecureStore::clearBtSpeakerTarget()
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return std::unexpected(core::StoreError::IoFailed);
}
esp_err_t err = nvs_erase_key(handle, kBtSpeakerMacKey);
if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) {
err = nvs_erase_key(handle, kBtSpeakerNameKey);
}
if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
return std::unexpected(core::StoreError::IoFailed);
}
ESP_LOGI(kTag, "BT speaker target cleared");
return {};
}
bool NvsSecureStore::hasWebRadioConfig() const
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
return false;
}
std::size_t len = 0;
const esp_err_t err =
nvs_get_str(handle, kWebRadioConfigKey, nullptr, &len);
nvs_close(handle);
return err == ESP_OK && len > 1;
}
std::expected<void, core::StoreError>
NvsSecureStore::saveWebRadioConfigJson(std::string_view json)
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return std::unexpected(core::StoreError::IoFailed);
}
const std::string payload(json);
esp_err_t err = nvs_set_str(handle, kWebRadioConfigKey, payload.c_str());
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
return std::unexpected(core::StoreError::IoFailed);
}
return {};
}
std::expected<std::string, core::StoreError>
NvsSecureStore::loadWebRadioConfigJson() const
{
nvs_handle_t handle = 0;
if (nvs_open(kNamespace, NVS_READONLY, &handle) != ESP_OK) {
return std::unexpected(core::StoreError::NotFound);
}
std::size_t len = 0;
if (nvs_get_str(handle, kWebRadioConfigKey, nullptr, &len) != ESP_OK
|| len == 0) {
nvs_close(handle);
return std::unexpected(core::StoreError::NotFound);
}
std::vector<char> buffer(len);
if (nvs_get_str(handle, kWebRadioConfigKey, buffer.data(), &len)
!= ESP_OK) {
nvs_close(handle);
return std::unexpected(core::StoreError::IoFailed);
}
nvs_close(handle);
return std::string(buffer.data());
}
} // namespace secure_store
@@ -130,6 +130,20 @@ public:
[[nodiscard]] std::expected<void, core::StoreError> setMasterVolume(
core::GainDb left, core::GainDb right, bool persist);
/**
* @brief applyRadioFirstMix route Si4684 to output, mute ESP32 path.
*
* @dname applyRadioFirstMix
* @param persist When true and store is set, write NVS.
* @return Ok on success, or StoreError.
* @pubstate updates profile_.mixer and master; safeloads ADAU1701.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<void, core::StoreError> applyRadioFirstMix(
bool persist);
/**
* @brief setEqBand update one PEQ band and apply live.
*
@@ -179,6 +193,21 @@ public:
[[nodiscard]] std::expected<void, core::StoreError> setBassEnhance(
core::EnhanceLevel level, bool persist);
/**
* @brief setBeepEnabled toggle the ADAU1701 Beep1 tone generator.
*
* @dname setBeepEnabled
* @param enabled true unmutes Beep1, false mutes it.
* @return Ok on success, or DspError.
* @pubstate live-only: not part of AudioProfile, never persisted, does
* not touch profile_.
*
* @author Michele Bigi
* @date 2026-08-07
*/
[[nodiscard]] std::expected<void, core::DspError> setBeepEnabled(
bool enabled);
private:
[[nodiscard]] std::expected<void, core::StoreError> persistProfile() const;
@@ -134,6 +134,25 @@ std::expected<void, core::StoreError> AudioService::setMasterVolume(
return {};
}
std::expected<void, core::StoreError> AudioService::applyRadioFirstMix(
bool persist)
{
const core::GainDb unity = core::GainDb::zero();
profile_.mixer = core::MixerState::radioFirst();
profile_.masterLeft = unity;
profile_.masterRight = unity;
if (auto applied = dsp_.applyMixer(profile_.mixer); !applied) {
return std::unexpected(core::StoreError::IoFailed);
}
if (auto master = dsp_.setMasterVolume(unity, unity); !master) {
return std::unexpected(core::StoreError::IoFailed);
}
if (persist) {
return persistProfile();
}
return {};
}
std::expected<void, core::StoreError> AudioService::setEqBand(
core::EqBandIndex band, core::GainDb gain, core::FrequencyHz center, float q,
bool persist)
@@ -160,4 +179,9 @@ std::expected<void, core::StoreError> AudioService::setBassEnhance(
return applyEffectiveEq(persist);
}
std::expected<void, core::DspError> AudioService::setBeepEnabled(bool enabled)
{
return dsp_.setBeepEnabled(enabled);
}
} // namespace audio
@@ -2,7 +2,7 @@ idf_component_register(
SRCS
"src/BluetoothService.cpp"
INCLUDE_DIRS "include"
REQUIRES core bt1035
REQUIRES core bt1035 freertos
)
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
@@ -15,6 +15,9 @@
#include "bt1035/Bt1035Driver.hpp"
#include "bt1035/Bt1035Error.hpp"
#include "core/BluetoothJson.hpp"
#include "core/BtSpeakerTarget.hpp"
#include "core/ISecureStore.hpp"
#include "core/StoreError.hpp"
#include <expected>
#include <vector>
@@ -22,12 +25,11 @@
namespace bluetooth {
/**
* @brief BluetoothService pairing and A2DP status for the web API.
* @brief BluetoothService pairing, scan, and saved-speaker reconnect.
*
* @dname BluetoothService
* @return n/a (type)
* @pubstate Borrows bt1035::Bt1035Driver for the process lifetime. Tracks
* whether discoverable mode was requested via startPairing().
* @pubstate Borrows bt1035::Bt1035Driver and core::ISecureStore for life.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -35,96 +37,173 @@ namespace bluetooth {
class BluetoothService {
public:
/**
* @brief BluetoothService bind to the BT1035 driver.
* @brief BluetoothService bind driver and store for the process life.
*
* @dname BluetoothService
* @param driver Booted BT1035 driver (must outlive this service).
* @pubstate stores driver reference; pairing inactive initially.
*
* @author Michele Bigi
* @date 2026-07-06
* @param driver BT1035 driver (must outlive this service).
* @param store Secure store for the saved default speaker.
* @pubstate initialises pairingActive_ to false.
*/
explicit BluetoothService(bt1035::Bt1035Driver& driver);
explicit BluetoothService(bt1035::Bt1035Driver& driver,
core::ISecureStore& store);
/**
* @brief refreshStatus read module boot, pairing, and A2DP state.
* @brief refreshStatus read boot/pairing/name/reconnect/A2DP state.
*
* @dname refreshStatus
* @return BluetoothStatus on success, or Bt1035Error from the driver.
* @pubstate queries driver for A2DP state when booted.
*
* @author Michele Bigi
* @date 2026-07-06
* @return BluetoothStatus (booted=false if the module never booted),
* or a Bt1035Error from the first failing query.
* @pubstate queries driver_; reads pairingActive_.
*/
[[nodiscard]] std::expected<core::BluetoothStatus, bt1035::Bt1035Error>
refreshStatus();
/**
* @brief startPairing enter discoverable mode (AT+PAIR=1).
* @brief startPairing enter BT1035 discoverable mode.
*
* @dname startPairing
* @return Ok on success, or Bt1035Error.
* @pubstate sets pairingActive_ on success.
*
* @author Michele Bigi
* @date 2026-07-06
* @return Ok on success, or a Bt1035Error.
* @pubstate sets pairingActive_ true on success.
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> startPairing();
/**
* @brief stopPairing leave discoverable mode (AT+PAIR=0).
* @brief stopPairing leave BT1035 discoverable mode.
*
* @dname stopPairing
* @return Ok on success, or Bt1035Error.
* @pubstate clears pairingActive_ on success.
*
* @author Michele Bigi
* @date 2026-07-06
* @return Ok on success, or a Bt1035Error.
* @pubstate sets pairingActive_ false on success.
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> stopPairing();
/**
* @brief disconnect release the active A2DP link (AT+A2DPDISC).
* @brief disconnect tear down the current A2DP link.
*
* @dname disconnect
* @return Ok on success, or Bt1035Error.
* @pubstate delegates to driver.
*
* @author Michele Bigi
* @date 2026-07-06
* @return Ok on success, or a Bt1035Error.
* @pubstate none
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> disconnect();
/**
* @brief listPaired read paired remotes from the module.
* @brief listPaired read the BT1035's paired-device list.
*
* @dname listPaired
* @return Device list on success, or Bt1035Error.
* @pubstate queries AT+PLIST via the driver.
*
* @author Michele Bigi
* @date 2026-07-07
* @return Paired devices, or a Bt1035Error.
* @pubstate none
*/
[[nodiscard]] std::expected<std::vector<core::Bt1035PairedDevice>,
bt1035::Bt1035Error>
listPaired();
/**
* @brief setAutoReconnect configure power-on reconnect attempts.
* @brief setAutoReconnect set the module's auto-reconnect attempts.
*
* @dname setAutoReconnect
* @param times 0 off, 115 per Feasycom manual.
* @return Ok on success, or Bt1035Error.
* @pubstate writes AT+AUTOCONN via the driver.
*
* @author Michele Bigi
* @date 2026-07-07
* @param times Retry count passed to the BT1035 firmware.
* @return Ok on success, or a Bt1035Error.
* @pubstate none
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> setAutoReconnect(
std::uint8_t times);
/**
* @brief scanNearby classic BT/EDR discovery, cancelling pairing.
*
* @dname scanNearby
* @param scanSeconds Scan duration in seconds.
* @return Discovered devices, or a Bt1035Error.
* @pubstate leaves pairing mode and stops any active scan first; sets
* pairingActive_ false.
*/
[[nodiscard]] std::expected<std::vector<core::Bt1035ScannedDevice>,
bt1035::Bt1035Error>
scanNearby(std::uint8_t scanSeconds = 20U);
/**
* @brief connectTo direct A2DPCONN to a MAC, wait for streaming.
*
* @dname connectTo
* @param mac Target BT1035 MAC (12 hex chars, no separators).
* @return Ok once A2DP reaches Streaming, or a Bt1035Error.
* @pubstate sets pairingActive_ false; blocks up to the connect and
* stream settle timeouts.
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> connectTo(
std::string_view mac);
/**
* @brief connectTo connect by request, optionally saving the target.
*
* @dname connectTo
* @param request Validated MAC/name plus a save flag.
* @return Ok once A2DP reaches Streaming, or a Bt1035Error
* (UnexpectedResponse for an invalid MAC).
* @pubstate sets pairingActive_ false; persists via saveSpeaker() when
* request.save is true (a failed save does not fail connect).
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> connectTo(
const core::BluetoothConnectRequest& request);
/**
* @brief hasSavedSpeaker check whether a default speaker is stored.
*
* @dname hasSavedSpeaker
* @return true when loadSavedSpeaker() would succeed.
* @pubstate reads store_.
*/
[[nodiscard]] bool hasSavedSpeaker() const;
/**
* @brief loadSavedSpeaker read the stored default speaker.
*
* @dname loadSavedSpeaker
* @return BtSpeakerTarget on success, or a StoreError.
* @pubstate reads store_.
*/
[[nodiscard]] std::expected<core::BtSpeakerTarget, core::StoreError>
loadSavedSpeaker() const;
/**
* @brief saveSpeaker persist the default A2DP speaker target.
*
* @dname saveSpeaker
* @param target MAC and optional display name.
* @return Ok on success, or a StoreError.
* @pubstate writes store_.
*/
[[nodiscard]] std::expected<void, core::StoreError> saveSpeaker(
const core::BtSpeakerTarget& target);
/**
* @brief clearSavedSpeaker erase the stored default speaker.
*
* @dname clearSavedSpeaker
* @return Ok on success, or a StoreError.
* @pubstate writes store_.
*/
[[nodiscard]] std::expected<void, core::StoreError> clearSavedSpeaker();
/** @brief Launch background task: A2DPCONN saved MAC, scan fallback. */
void startupReconnect();
/**
* @brief reconnectSavedSpeaker direct MAC connect, optional scan retry.
*
* @dname reconnectSavedSpeaker
* @param scanFallback Run AT+SCAN when direct connect fails.
* @return Ok when A2DP reaches Connected, or Bt1035Error.
* @pubstate may block tens of seconds when scanFallback is true.
*/
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> reconnectSavedSpeaker(
bool scanFallback);
private:
[[nodiscard]] std::expected<void, bt1035::Bt1035Error> connectDirect(
std::string_view mac);
bt1035::Bt1035Driver& driver_;
core::ISecureStore& store_;
bool pairingActive_;
};
@@ -13,10 +13,67 @@
#include "bluetooth/BluetoothService.hpp"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
namespace bluetooth {
BluetoothService::BluetoothService(bt1035::Bt1035Driver& driver)
namespace {
constexpr char kTag[] = "BluetoothSvc";
constexpr int kConnectSettleMs = 30000;
constexpr int kStreamSettleMs = 20000;
constexpr int kAutoReconnectWaitMs = 45000;
[[nodiscard]] bool isA2dpLinked(core::Bt1035A2dpState state) noexcept
{
using core::Bt1035A2dpState;
return state == Bt1035A2dpState::Connected
|| state == Bt1035A2dpState::Streaming
|| state == Bt1035A2dpState::Paused;
}
[[nodiscard]] bool isA2dpLinked(bt1035::Bt1035Driver& driver)
{
if (auto state = driver.queryA2dpState(); state) {
return isA2dpLinked(*state);
}
return false;
}
[[nodiscard]] bool ensureA2dpStreaming(bt1035::Bt1035Driver& driver)
{
if (auto state = driver.queryA2dpState(); state
&& *state == core::Bt1035A2dpState::Streaming) {
ESP_LOGI(kTag, "A2DP already streaming");
return true;
}
if (!driver.waitForA2dpStreaming(kStreamSettleMs)) {
ESP_LOGW(kTag, "A2DP not streaming within %d ms", kStreamSettleMs);
return false;
}
ESP_LOGI(kTag, "A2DP streaming OK");
return true;
}
void savedSpeakerReconnectTask(void* arg)
{
auto* service = static_cast<BluetoothService*>(arg);
ESP_LOGI(kTag, "boot reconnect task started");
if (auto result = service->reconnectSavedSpeaker(true); !result) {
ESP_LOGW(kTag, "boot reconnect failed (%d)",
static_cast<int>(result.error()));
} else {
ESP_LOGI(kTag, "boot reconnect OK");
}
vTaskDelete(nullptr);
}
} // namespace
BluetoothService::BluetoothService(bt1035::Bt1035Driver& driver,
core::ISecureStore& store)
: driver_(driver)
, store_(store)
, pairingActive_(false)
{
}
@@ -91,4 +148,152 @@ std::expected<void, bt1035::Bt1035Error> BluetoothService::setAutoReconnect(
return driver_.setAutoReconnect(times);
}
std::expected<std::vector<core::Bt1035ScannedDevice>, bt1035::Bt1035Error>
BluetoothService::scanNearby(std::uint8_t scanSeconds)
{
pairingActive_ = false;
(void)driver_.leavePairingMode();
(void)driver_.stopScan();
return driver_.scanNearbyBrEdr(scanSeconds);
}
std::expected<void, bt1035::Bt1035Error> BluetoothService::connectDirect(
std::string_view mac)
{
if (auto sent = driver_.connectA2dp(mac); !sent) {
return sent;
}
if (!driver_.waitForA2dpConnected(kConnectSettleMs)) {
ESP_LOGW(kTag, "A2DP connect sent but link not up in %d ms",
kConnectSettleMs);
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
if (!ensureA2dpStreaming(driver_)) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
return {};
}
std::expected<void, bt1035::Bt1035Error> BluetoothService::connectTo(
std::string_view mac)
{
pairingActive_ = false;
return connectDirect(mac);
}
std::expected<void, bt1035::Bt1035Error> BluetoothService::connectTo(
const core::BluetoothConnectRequest& request)
{
if (request.mac.empty() || !core::isValidBt1035Mac(request.mac)) {
return std::unexpected(bt1035::Bt1035Error::UnexpectedResponse);
}
pairingActive_ = false;
if (auto connected = connectDirect(request.mac); !connected) {
return connected;
}
if (request.save) {
core::BtSpeakerTarget target{
.mac = request.mac,
.name = request.name,
};
if (auto saved = saveSpeaker(target); !saved) {
ESP_LOGW(kTag, "connect OK but save speaker failed");
}
}
return {};
}
bool BluetoothService::hasSavedSpeaker() const
{
return store_.hasBtSpeakerTarget();
}
std::expected<core::BtSpeakerTarget, core::StoreError>
BluetoothService::loadSavedSpeaker() const
{
return store_.loadBtSpeakerTarget();
}
std::expected<void, core::StoreError> BluetoothService::saveSpeaker(
const core::BtSpeakerTarget& target)
{
return store_.saveBtSpeakerTarget(target);
}
std::expected<void, core::StoreError> BluetoothService::clearSavedSpeaker()
{
return store_.clearBtSpeakerTarget();
}
void BluetoothService::startupReconnect()
{
if (!hasSavedSpeaker()) {
ESP_LOGI(kTag, "no saved BT speaker — skip boot reconnect");
return;
}
if (xTaskCreate(savedSpeakerReconnectTask, "bt_reconn", 8192, this, 4,
nullptr)
!= pdPASS) {
ESP_LOGW(kTag, "boot reconnect task create failed");
}
}
std::expected<void, bt1035::Bt1035Error> BluetoothService::reconnectSavedSpeaker(
bool scanFallback)
{
const auto target = loadSavedSpeaker();
if (!target) {
return {};
}
ESP_LOGI(kTag, "reconnect saved speaker %s (%s)", target->mac.c_str(),
target->name.empty() ? "(no name)" : target->name.c_str());
if (isA2dpLinked(driver_)) {
ESP_LOGI(kTag, "A2DP already linked — ensure streaming");
if (!ensureA2dpStreaming(driver_)) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
return {};
}
if (auto connected = connectDirect(target->mac); connected) {
return connected;
}
if (isA2dpLinked(driver_)) {
ESP_LOGI(kTag, "A2DP linked after connect attempt");
if (!ensureA2dpStreaming(driver_)) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
return {};
}
ESP_LOGI(kTag, "waiting for module auto-reconnect (%d ms)",
kAutoReconnectWaitMs);
if (driver_.waitForA2dpConnected(kAutoReconnectWaitMs)) {
ESP_LOGI(kTag, "A2DP auto-reconnect linked");
if (!ensureA2dpStreaming(driver_)) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
return {};
}
if (!scanFallback) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
ESP_LOGW(kTag, "direct A2DPCONN failed — retry connect (no scan)");
if (auto retry = connectDirect(target->mac); retry) {
return retry;
}
if (isA2dpLinked(driver_)) {
if (!ensureA2dpStreaming(driver_)) {
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
return {};
}
return std::unexpected(bt1035::Bt1035Error::AtTimeout);
}
} // namespace bluetooth
@@ -2,7 +2,7 @@ idf_component_register(
SRCS
"src/TunerService.cpp"
INCLUDE_DIRS "include"
REQUIRES core
REQUIRES core freertos
)
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
@@ -16,6 +16,7 @@
#include "core/ITuner.hpp"
#include "core/SeekDirection.hpp"
#include "core/TunerError.hpp"
#include "core/TunerJson.hpp"
#include "core/TunerStatus.hpp"
#include <cstdint>
@@ -109,6 +110,20 @@ public:
[[nodiscard]] std::expected<core::FrequencyKHz, core::TunerError> seekFm(
core::SeekDirection direction);
/**
* @brief scanForStation FM seek or DAB ensemble search with optional name.
*
* @dname scanForStation
* @param request Band, step limit, and optional label substring.
* @return Scan outcome; TunerError only on driver failure.
* @pubstate tunes/plays first matching station; updates last tune caches.
*
* @author Michele Bigi
* @date 2026-08-05
*/
[[nodiscard]] std::expected<core::TunerScanResult, core::TunerError>
scanForStation(const core::TunerScanRequest& request);
/**
* @brief listDabServices programmes available on the current ensemble.
*
@@ -13,15 +13,124 @@
#include "tuner/TunerService.hpp"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <algorithm>
#include <cctype>
#include <string>
namespace tuner {
namespace {
constexpr char kTag[] = "TunerSvc";
constexpr int kDabTuneSettleMs = 600;
/** FM scan step size (100 kHz spacing). */
constexpr std::uint32_t kFmScanStepKhz = 100U;
constexpr int kFmTuneSettleMs = 120;
constexpr int kFmNamePollMs = 250;
constexpr int kFmNamePollAttempts = 6;
constexpr std::int8_t kMinFmScanRssiDbuV = 5;
constexpr std::int8_t kMinFmScanSnrDb = 10;
/** Max |READFREQ commanded| for scan hit without RDS lock (kHz). */
constexpr std::uint32_t kFmScanChipFreqSlackKhz = 150U;
constexpr std::uint8_t kMinDabFicQuality = 35U;
/** European FM band top used for scan/seek wrap (107.9 MHz). */
constexpr std::uint32_t kFmScanMaxKhz = 107900U;
/** European FM band bottom used for full-band scan (87.5 MHz). */
constexpr std::uint32_t kFmScanMinKhz = 87500U;
[[nodiscard]] core::FrequencyKHz fmScanStartFrequency(
core::FrequencyKHz current,
std::string_view nameFilter)
{
if (!nameFilter.empty()) {
return current;
}
const std::uint32_t khz = current.value();
if (khz > kFmScanMaxKhz || khz < kFmScanMinKhz) {
return *core::FrequencyKHz::tryFromKhz(kFmScanMinKhz);
}
return current;
}
[[nodiscard]] core::FrequencyKHz defaultFmFrequency()
{
return *core::FrequencyKHz::tryFromKhz(101500U);
}
[[nodiscard]] std::string toLowerAscii(std::string_view text)
{
std::string lowered;
lowered.reserve(text.size());
for (const char ch : text) {
lowered.push_back(static_cast<char>(std::tolower(
static_cast<unsigned char>(ch))));
}
return lowered;
}
[[nodiscard]] bool nameMatchesFilter(const std::optional<core::BroadcastLabel>& label,
std::string_view filterLower)
{
if (filterLower.empty()) {
return true;
}
if (!label) {
return false;
}
const std::string haystack = toLowerAscii(label->value());
return haystack.find(filterLower) != std::string::npos;
}
[[nodiscard]] bool nameMatchesFilter(std::string_view label,
std::string_view filterLower)
{
if (filterLower.empty()) {
return true;
}
const std::string haystack = toLowerAscii(label);
return haystack.find(filterLower) != std::string::npos;
}
[[nodiscard]] bool fmStatusUsableForScan(const core::TunerStatus& status,
std::uint32_t commandedKhz,
bool requireLocked)
{
if (!status.fmRssiDbuV || *status.fmRssiDbuV < kMinFmScanRssiDbuV) {
return false;
}
if (requireLocked) {
return status.locked;
}
if (!status.fmSnrDb || *status.fmSnrDb < kMinFmScanSnrDb) {
return false;
}
if (status.fmChipReadFrequency) {
const std::uint32_t chipKhz = status.fmChipReadFrequency->value();
const std::uint32_t diff =
chipKhz > commandedKhz ? chipKhz - commandedKhz
: commandedKhz - chipKhz;
if (diff > kFmScanChipFreqSlackKhz) {
return false;
}
}
return true;
}
[[nodiscard]] core::TunerScanResult makeScanResult(
const core::TunerScanRequest& request,
std::uint16_t steps,
bool found)
{
core::TunerScanResult result = {};
result.found = found;
result.band = request.band;
result.stepsTried = steps;
return result;
}
} // namespace
TunerService::TunerService(core::ITuner& tuner)
@@ -77,6 +186,160 @@ std::expected<core::FrequencyKHz, core::TunerError> TunerService::seekFm(
return result;
}
std::expected<core::TunerScanResult, core::TunerError>
TunerService::scanForStation(const core::TunerScanRequest& request)
{
const std::string_view nameFilter = request.nameFilter;
if (request.band == core::TunerBand::Fm) {
const core::FrequencyKHz scanStart =
fmScanStartFrequency(lastFmFrequency_, nameFilter);
if (auto tuned = tuneFm(scanStart); !tuned) {
return std::unexpected(tuned.error());
}
const std::uint32_t startFreq = lastFmFrequency_.value();
const bool requireLocked = !nameFilter.empty();
std::uint32_t freqKhz = startFreq;
ESP_LOGI(kTag, "FM scan start from %u kHz (max %u steps, name='%.*s')",
static_cast<unsigned>(startFreq), static_cast<unsigned>(request.maxSteps),
static_cast<int>(nameFilter.size()), nameFilter.data());
for (std::uint16_t step = 1U; step <= request.maxSteps; ++step) {
freqKhz += kFmScanStepKhz;
if (freqKhz > kFmScanMaxKhz) {
freqKhz = kFmScanMinKhz;
}
if (freqKhz == startFreq) {
ESP_LOGI(kTag, "FM scan wrapped to start frequency");
break;
}
const auto next = core::FrequencyKHz::tryFromKhz(freqKhz);
if (!next) {
return std::unexpected(core::TunerError::TuneFailed);
}
if (auto tuned = tuneFm(*next); !tuned) {
ESP_LOGW(kTag, "FM scan step %u tune failed at %u kHz (err=%d)",
static_cast<unsigned>(step), static_cast<unsigned>(freqKhz),
static_cast<int>(tuned.error()));
return std::unexpected(tuned.error());
}
vTaskDelay(pdMS_TO_TICKS(kFmTuneSettleMs));
std::optional<core::BroadcastLabel> stationName;
bool usable = false;
for (int attempt = 0; attempt < kFmNamePollAttempts; ++attempt) {
auto status = refreshStatus();
if (!status) {
return std::unexpected(status.error());
}
if (attempt == 0) {
ESP_LOGI(kTag,
"FM scan step %u at %u kHz: valid=%d rssi=%d dBuV "
"snr=%d dB",
static_cast<unsigned>(step),
static_cast<unsigned>(lastFmFrequency_.value()),
static_cast<int>(status->locked),
status->fmRssiDbuV ? *status->fmRssiDbuV : -128,
status->fmSnrDb ? *status->fmSnrDb : -128);
}
if (!fmStatusUsableForScan(*status, freqKhz, requireLocked)) {
break;
}
usable = true;
stationName = status->fmStationName;
if (nameFilter.empty()
|| nameMatchesFilter(stationName, nameFilter)) {
break;
}
if (attempt + 1 < kFmNamePollAttempts) {
vTaskDelay(pdMS_TO_TICKS(kFmNamePollMs));
}
}
if (!usable) {
continue;
}
if (nameFilter.empty() || nameMatchesFilter(stationName, nameFilter)) {
auto status = refreshStatus();
if (!status) {
return std::unexpected(status.error());
}
core::TunerScanResult result = makeScanResult(request, step, true);
result.fmFrequency = status->fmFrequency;
result.stationName = status->fmStationName;
ESP_LOGI(kTag, "FM scan found %u kHz after %u steps",
static_cast<unsigned>(result.fmFrequency ? result.fmFrequency->value() : 0U),
static_cast<unsigned>(step));
return result;
}
}
ESP_LOGW(kTag, "FM scan: no station found");
return makeScanResult(request, request.maxSteps, false);
}
ESP_LOGI(kTag, "DAB scan start (max %u ensembles, name='%.*s')",
static_cast<unsigned>(request.maxSteps),
static_cast<int>(nameFilter.size()), nameFilter.data());
for (std::uint16_t step = 0U; step < request.maxSteps; ++step) {
const std::uint8_t freqIndex = static_cast<std::uint8_t>(step);
if (auto tuned = tuneDab(freqIndex); !tuned) {
return std::unexpected(tuned.error());
}
vTaskDelay(pdMS_TO_TICKS(kDabTuneSettleMs));
auto status = refreshStatus();
if (!status) {
return std::unexpected(status.error());
}
if (!status->locked || !status->dabFicQuality
|| *status->dabFicQuality < kMinDabFicQuality) {
continue;
}
auto services = listDabServices();
if (!services || services->empty()) {
continue;
}
for (const core::TunerServiceEntry& service : *services) {
const std::string_view label(service.label.data());
if (!nameMatchesFilter(label, nameFilter)) {
continue;
}
if (auto played =
playDabService(service.serviceId, service.componentId);
!played) {
continue;
}
core::TunerScanResult result =
makeScanResult(request, static_cast<std::uint16_t>(step + 1U), true);
result.dabFreqIndex = freqIndex;
result.dabServiceId = service.serviceId;
result.dabComponentId = service.componentId;
if (auto parsed = core::BroadcastLabel::tryFromChipBytes(label);
parsed) {
result.stationName = std::move(*parsed);
}
ESP_LOGI(kTag, "DAB scan found idx=%u svc=%u after %u steps",
static_cast<unsigned>(freqIndex),
static_cast<unsigned>(service.serviceId),
static_cast<unsigned>(step + 1U));
return result;
}
}
ESP_LOGW(kTag, "DAB scan: no station found");
return makeScanResult(request, request.maxSteps, false);
}
std::expected<std::vector<core::TunerServiceEntry>, core::TunerError>
TunerService::listDabServices()
{
@@ -0,0 +1,8 @@
idf_component_register(
SRCS
"src/WebRadioService.cpp"
INCLUDE_DIRS "include"
REQUIRES core freertos
)
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
@@ -0,0 +1,91 @@
/**
* @file WebRadioService.hpp
* @brief Live, persisted config for the internet radio streaming task.
*
* DigiRadio firmware https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-08
*/
#pragma once
#include "core/ISecureStore.hpp"
#include "core/StoreError.hpp"
#include "core/WebRadioConfig.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <expected>
namespace webradio {
/**
* @brief WebRadioService thread-safe streaming config for HTTP + task.
*
* @dname WebRadioService
* @return n/a (type)
* @pubstate Borrows core::ISecureStore for life. Owns a FreeRTOS mutex
* guarding an in-RAM copy of the config; the streaming task in
* main/web_radio_stream.cpp polls config() to react to changes
* without a reboot.
*
* @author Michele Bigi
* @date 2026-08-08
*/
class WebRadioService {
public:
/**
* @brief WebRadioService load persisted config or factory default.
*
* @dname WebRadioService
* @param store Secure store for persistence (must outlive this).
* @pubstate reads store via loadWebRadioConfigJson(); creates mutex_.
*
* @author Michele Bigi
* @date 2026-08-08
*/
explicit WebRadioService(core::ISecureStore& store);
~WebRadioService();
WebRadioService(const WebRadioService&) = delete;
WebRadioService& operator=(const WebRadioService&) = delete;
/**
* @brief config read a thread-safe snapshot of the current config.
*
* @dname config
* @return Copy of the live WebRadioConfig.
* @pubstate locks mutex_ for the copy.
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] core::WebRadioConfig config() const;
/**
* @brief setConfig persist and apply a new streaming config.
*
* @dname setConfig
* @param config Validated config from the HTTP layer.
* @return Ok on success, or a StoreError.
* @pubstate persists via store_ first, then updates the live copy under
* mutex_ so a failed save never desyncs the running task.
*
* @author Michele Bigi
* @date 2026-08-08
*/
[[nodiscard]] std::expected<void, core::StoreError> setConfig(
const core::WebRadioConfig& config);
private:
core::ISecureStore& store_;
SemaphoreHandle_t mutex_;
core::WebRadioConfig config_;
};
} // namespace webradio

Some files were not shown because too many files have changed in this diff Show More