Working on SD and netowrking
This commit is contained in:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user