Files
ESP32_ZACUS/ui_freenove_allinone/include/drivers/board/hardware_manager.h
T
2026-03-11 00:03:58 +01:00

185 lines
6.5 KiB
C++

// hardware_manager.h - WS2812 + microphone + battery helpers for Freenove board.
#pragma once
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <driver/i2s.h>
class HardwareManager {
public:
static constexpr uint8_t kMicWaveformCapacity = 16U;
static constexpr uint8_t kMicSpectrumBinCount = 5U;
enum class LedRuntimeMode : uint8_t {
kPalette = 0,
kBroken,
kTuner,
kSingleRandomBlink,
kDominantBandSingle,
};
struct LedPaletteEntry {
const char* scene_id;
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t brightness;
bool pulse;
};
struct Snapshot {
bool ready = false;
bool ws2812_ready = false;
bool mic_ready = false;
bool battery_ready = false;
bool charging = false;
bool led_manual = false;
uint8_t led_r = 0U;
uint8_t led_g = 0U;
uint8_t led_b = 0U;
uint8_t led_brightness = 0U;
bool led_one_at_a_time = false;
uint8_t mic_level_percent = 0U;
uint16_t mic_peak = 0U;
uint16_t mic_noise_floor = 0U;
uint16_t mic_gain_percent = 100U;
uint16_t mic_freq_hz = 0U;
int16_t mic_pitch_cents = 0;
uint8_t mic_pitch_confidence = 0U;
uint8_t mic_waveform_count = 0U;
uint8_t mic_waveform_head = 0U;
uint8_t mic_waveform[kMicWaveformCapacity] = {0};
uint8_t mic_spectrum[kMicSpectrumBinCount] = {0};
uint16_t mic_spectrum_peak_hz = 0U;
uint16_t battery_mv = 0U;
uint16_t battery_cell_mv = 0U;
uint8_t battery_percent = 0U;
uint8_t last_button = 0U;
bool last_button_long = false;
uint32_t last_button_ms = 0U;
uint32_t button_count = 0U;
char led_mode[24] = "palette";
char scene_id[24] = "SCENE_READY";
};
HardwareManager();
~HardwareManager() = default;
HardwareManager(const HardwareManager&) = delete;
HardwareManager& operator=(const HardwareManager&) = delete;
HardwareManager(HardwareManager&&) = delete;
HardwareManager& operator=(HardwareManager&&) = delete;
bool begin();
void update(uint32_t now_ms);
void noteButton(uint8_t key, bool long_press, uint32_t now_ms);
void setSceneHint(const char* scene_id);
bool setManualLed(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness, bool pulse);
void clearManualLed();
Snapshot snapshot() const;
const Snapshot& snapshotRef() const;
void setMicRuntimeEnabled(bool enabled);
bool micRuntimeEnabled() const;
void setSceneSingleRandomBlink(bool enabled,
uint8_t r,
uint8_t g,
uint8_t b,
uint8_t brightness,
bool dominant_band_sync = false);
private:
bool beginMic();
void endMic();
void updateMic(uint32_t now_ms);
void updateBattery(uint32_t now_ms);
void updateLed(uint32_t now_ms);
bool isBrokenSceneHint() const;
bool isTunerSceneHint() const;
void applyBrokenLedPattern(uint32_t now_ms, uint8_t base_r, uint8_t base_g, uint8_t base_b, uint8_t brightness);
void applyTunerLedPattern(uint32_t now_ms,
uint8_t base_r,
uint8_t base_g,
uint8_t base_b,
uint8_t brightness);
void applySingleRandomBlinkPattern(uint32_t now_ms,
uint8_t base_r,
uint8_t base_g,
uint8_t base_b,
uint8_t brightness);
void applyDominantBandSinglePattern(uint32_t now_ms, uint8_t brightness);
void estimatePitch(uint16_t& freq_hz, int16_t& cents, uint8_t& confidence, uint16_t& peak_for_window);
void estimatePitchFromSamples(const int16_t* samples,
size_t sample_count,
uint16_t& out_freq,
int16_t& out_cents,
uint8_t& out_confidence);
void applyPitchSmoothing(uint32_t now_ms,
uint16_t raw_freq,
int16_t raw_cents,
uint8_t raw_confidence,
uint16_t& smoothed_freq,
int16_t& smoothed_cents,
uint8_t& smoothed_confidence);
void setScenePalette(const char* scene_id);
const LedPaletteEntry* findPaletteForScene(const char* scene_id) const;
uint8_t batteryPercentFromMv(uint16_t cell_mv) const;
static uint8_t clampColor(int value);
static constexpr uint16_t kMicSampleRate = 16000U;
static constexpr uint16_t kMicReadSamples = 256U;
static constexpr uint32_t kMicPeriodMs = 40U;
static constexpr uint32_t kBatteryPeriodMs = 1200U;
static constexpr uint32_t kLedPeriodMs = 33U;
static constexpr uint32_t kButtonFlashMs = 180U;
static constexpr i2s_port_t kMicPort = I2S_NUM_1;
Snapshot snapshot_;
Adafruit_NeoPixel strip_;
bool mic_driver_ready_ = false;
bool led_pulse_ = false;
uint32_t next_mic_ms_ = 0U;
uint32_t next_battery_ms_ = 0U;
uint32_t next_led_ms_ = 0U;
uint32_t button_flash_until_ms_ = 0U;
uint8_t scene_r_ = 0U;
uint8_t scene_g_ = 0U;
uint8_t scene_b_ = 0U;
uint8_t scene_brightness_ = 0U;
bool manual_led_ = false;
bool manual_pulse_ = false;
uint8_t manual_r_ = 0U;
uint8_t manual_g_ = 0U;
uint8_t manual_b_ = 0U;
uint8_t manual_brightness_ = 0U;
bool scene_single_random_blink_ = false;
uint8_t scene_single_blink_r_ = 0U;
uint8_t scene_single_blink_g_ = 0U;
uint8_t scene_single_blink_b_ = 0U;
uint8_t scene_single_blink_brightness_ = 0U;
bool scene_single_blink_dominant_band_ = false;
LedRuntimeMode led_runtime_mode_ = LedRuntimeMode::kPalette;
uint16_t mic_agc_gain_q8_ = 256U;
uint16_t mic_noise_floor_raw_ = 120U;
uint32_t mic_last_signal_ms_ = 0U;
float pitch_confidence_ema_ = 0.0f;
uint8_t pitch_smoothing_count_ = 0U;
uint8_t pitch_smoothing_index_ = 0U;
uint32_t pitch_smoothing_last_ms_ = 0U;
static constexpr uint8_t kPitchSmoothingSamples = 3U;
static constexpr uint16_t kPitchSmoothingStaleMs = 260U;
uint16_t pitch_freq_window_[kPitchSmoothingSamples] = {0U};
int16_t pitch_cents_window_[kPitchSmoothingSamples] = {0};
uint8_t pitch_conf_window_[kPitchSmoothingSamples] = {0U};
bool mic_enabled_runtime_ = true;
// Keep DSP buffers off the loop task stack to avoid canary overflows.
int32_t mic_raw_samples_[kMicReadSamples] = {};
int16_t mic_samples_[kMicReadSamples] = {};
float pitch_centered_[kMicReadSamples] = {};
float pitch_energy_prefix_[kMicReadSamples + 1U] = {};
float pitch_corr_by_lag_[kMicReadSamples + 1U] = {};
};