Working on SD and netowrking

This commit is contained in:
2026-08-09 20:34:44 -06:00
parent 0014e8697f
commit e06b429db9
4 changed files with 730 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
#pragma once
// ============================================================================
// Audio logging interface (see docs/audio_logging.md for the full design).
//
// THIS FILE IS A SKELETON. The chunk-pool / queue / file logic in
// data_logger.cpp is stubbed with TODOs; only the WAV metadata helpers are
// implemented. The interface below is the contract the future Node
// collection task (producer) and the StorageTask (consumer) are written
// against.
// ============================================================================
#include <Arduino.h>
#include <FS.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "storage_config.h"
// ----------------------------------------------------------------------------
// WAV (RIFF) metadata helpers - fully implemented in data_logger.cpp.
// ----------------------------------------------------------------------------
// 44-byte PCM WAVE header. Layout and field meaning are documented in
// docs/audio_logging.md section 3.
struct WavHeader
{
uint8_t riff[4]; // "RIFF"
uint32_t riffSize; // file size - 8
uint8_t wave[4]; // "WAVE"
uint8_t fmt[4]; // "fmt "
uint32_t fmtChunkSize; // 16 (PCM)
uint16_t audioFormat; // 1 (PCM)
uint16_t numChannels; // STORAGE_AUDIO_CHANNELS
uint32_t sampleRate; // STORAGE_AUDIO_SAMPLE_RATE_HZ
uint32_t byteRate; // sampleRate * blockAlign
uint16_t blockAlign; // numChannels * (bitsPerSample / 8)
uint16_t bitsPerSample; // STORAGE_AUDIO_BITS
uint8_t data[4]; // "data"
uint32_t dataSize; // file size - 44
} __attribute__((packed));
static_assert(sizeof(WavHeader) == 44, "WAV header must be exactly 44 bytes");
// Fills the header for a PCM stream of the given channels/rate/depth.
// dataSize is typically 0 at file open and corrected on finalize.
void buildWavHeader(WavHeader& header,
uint16_t numChannels,
uint32_t sampleRate,
uint16_t bitsPerSample,
uint32_t dataSize);
// Returns the two little-endian values to patch at offsets 4 and 40 when a
// file is closed/rotated: riffSize = fileSize - 8, dataSize = fileSize - 44.
void finalizeWavHeader(uint32_t fileSize,
uint32_t& riffSizeOut,
uint32_t& dataSizeOut);
// ----------------------------------------------------------------------------
// Chunk pool
// ----------------------------------------------------------------------------
// One slot of the write pool. Producers borrow a chunk, fill `data[0..length)`
// with interleaved PCM (40 channels, frame order, 80 bytes/frame), and submit
// it. Chunks must be submitted in strict stream order.
struct AudioChunk
{
uint8_t* data; // DMA-capable buffer owned by the pool
uint32_t capacity; // STORAGE_LOG_CHUNK_SIZE
uint32_t length; // valid bytes, multiple of the WAV frame size
uint32_t sequence; // monotonic order (diagnostics only)
};
// ----------------------------------------------------------------------------
// DataLogger
// ----------------------------------------------------------------------------
// Producer/consumer bridge between the Node collection task (core 1) and the
// StorageTask (core 0). Owns the chunk pool, the two FreeRTOS queues, and the
// open WAV file. Only the consumer touches the card.
class DataLogger
{
public:
DataLogger();
// Lifecycle --------------------------------------------------------------
// Allocates the pool (MALLOC_CAP_DMA), creates the queues, creates
// STORAGE_LOG_DIR. Does not open a session file.
bool begin(fs::FS& files);
// Finalizes and closes the current session file, frees the pool/queues.
void end();
// Session control --------------------------------------------------------
// Creates the next rec_<uptimeSeconds>_<n>.wav under STORAGE_LOG_DIR,
// writes the 44-byte WAV header. false if the card is full or unwritable.
bool openSession();
// Finalizes (patches RIFF/data sizes) and closes the current file.
bool closeSession();
// Closes/opens when bytesThisFile >= STORAGE_LOG_ROTATE_BYTES. Called by
// the consumer on every loop; must be a no-op when idle.
bool rotateIfNeeded();
// Producer API (Node collection task, core 1) ----------------------------
// Pops a free chunk, or on timeout drops the oldest queued chunk (Section
// 8) and returns it so the producer can keep streaming. nullptr only if
// there is nothing to drop (empty pool + empty queue).
AudioChunk* acquireChunk(TickType_t timeout);
// Returns a filled chunk to the write queue. Must be called in order.
void submitChunk(AudioChunk* chunk);
// Consumer API (StorageTask, core 0) --------------------------------------
// Blocks up to `timeout` for the next filled chunk. nullptr on timeout
// (lets the consumer pulse for stats even when idle).
AudioChunk* nextChunk(TickType_t timeout);
// Appends chunk->data[0..length) to the WAV data section with one
// file.write(), updates the byte/chunk counters and the bps window.
// false on card failure (fatal per design.md).
bool writeChunk(AudioChunk* chunk);
// Returns a chunk to the free pool after it has been written or dropped.
void releaseChunk(AudioChunk* chunk);
// Stats -------------------------------------------------------------------
uint32_t bytesWritten() const;
uint32_t chunksWritten() const;
uint32_t droppedChunks() const;
uint32_t writeSpeedBps() const;
bool overflowing() const;
private:
// Pops the oldest unwritten chunk off filledQ without writing it,
// increments droppedChunks, raises the warning. Returns it to the caller
// (used by acquireChunk on timeout).
AudioChunk* dropOldestChunk();
// Serial (rate-limited) + LED (STORAGE_WARN_LED_GPIO) overflow indication.
void setOverflowWarning(bool overflowing);
fs::FS* files; // SD backend, injected by begin()
AudioChunk* pool; // STORAGE_LOG_POOL_SIZE chunks
QueueHandle_t freeQ; // empty chunks
QueueHandle_t filledQ; // chunks waiting to be written
File file; // open session file
char filePath[96];
uint32_t sessionSeq; // rotation counter within this boot
uint64_t bytesThisFile;
uint64_t totalBytes;
uint32_t totalChunks;
uint32_t totalDropped;
uint32_t lastDropPrintMs;
// bps metering window (writeChunk is the only writer)
uint32_t windowBytes;
uint32_t windowStartMs;
uint32_t bps;
bool active;
bool warning;
};