#include "Power.h" #include #include #include #include #include using tt::hal::power::PowerDevice; static const auto LOGGER = tt::Logger("JcPower"); namespace { constexpr adc_unit_t ADC_UNIT = ADC_UNIT_2; constexpr adc_channel_t ADC_CHANNEL = ADC_CHANNEL_4; // matches ADC2 CH4 used in brookesia config constexpr adc_atten_t ADC_ATTEN = ADC_ATTEN_DB_12; constexpr int32_t UPPER_RESISTOR_OHM = 85'000; // per brookesia settings constexpr int32_t LOWER_RESISTOR_OHM = 100'000; // per brookesia settings class JcPower final : public PowerDevice { public: JcPower() : chargeEstimator(3.3f, 4.2f) {} ~JcPower() override { deinit(); } std::string getName() const override { return "JC Power"; } std::string getDescription() const override { return "Battery voltage via ADC"; } bool supportsMetric(MetricType type) const override { switch (type) { using enum MetricType; case BatteryVoltage: case ChargeLevel: return true; default: return false; } } bool getMetric(MetricType type, MetricData& data) override { if (!ensureInit()) { return false; } uint32_t batteryMv = 0; if (!readBatteryMilliVolt(batteryMv)) { return false; } switch (type) { case MetricType::BatteryVoltage: data.valueAsUint32 = batteryMv; return true; case MetricType::ChargeLevel: data.valueAsUint8 = chargeEstimator.estimateCharge(batteryMv); return true; default: return false; } } private: bool ensureInit() { if (initialized) { return true; } adc_oneshot_unit_init_cfg_t init_cfg = { .unit_id = ADC_UNIT, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE, }; if (adc_oneshot_new_unit(&init_cfg, &adcHandle) != ESP_OK) { LOGGER.error("ADC unit init failed"); return false; } adc_oneshot_chan_cfg_t chan_cfg = { .atten = ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; if (adc_oneshot_config_channel(adcHandle, ADC_CHANNEL, &chan_cfg) != ESP_OK) { LOGGER.error("ADC channel config failed"); adc_oneshot_del_unit(adcHandle); adcHandle = nullptr; return false; } calibrated = tryInitCalibration(); initialized = true; return true; } bool tryInitCalibration() { #if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED adc_cali_line_fitting_config_t cali_config = { .unit_id = ADC_UNIT, .atten = ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; if (adc_cali_create_scheme_line_fitting(&cali_config, &caliHandle) == ESP_OK) { calScheme = CaliScheme::Line; LOGGER.info("ADC calibration (line fitting) enabled"); return true; } #endif #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED adc_cali_curve_fitting_config_t curve_cfg = { .unit_id = ADC_UNIT, .chan = ADC_CHANNEL, .atten = ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; if (adc_cali_create_scheme_curve_fitting(&curve_cfg, &caliHandle) == ESP_OK) { calScheme = CaliScheme::Curve; LOGGER.info("ADC calibration (curve fitting) enabled"); return true; } #endif LOGGER.warn("ADC calibration not available, using raw scaling"); return false; } bool readBatteryMilliVolt(uint32_t& outMv) { int raw = 0; if (adc_oneshot_read(adcHandle, ADC_CHANNEL, &raw) != ESP_OK) { LOGGER.error("ADC read failed"); return false; } int mv = 0; if (calibrated && adc_cali_raw_to_voltage(caliHandle, raw, &mv) == ESP_OK) { // ok } else { // Fallback: approximate assuming 12-bit full scale 3.3V mv = (raw * 3300) / 4095; } const int64_t numerator = static_cast(UPPER_RESISTOR_OHM + LOWER_RESISTOR_OHM) * mv; const int64_t denominator = LOWER_RESISTOR_OHM; outMv = static_cast(numerator / denominator); return true; } void deinit() { if (adcHandle) { adc_oneshot_del_unit(adcHandle); adcHandle = nullptr; } if (caliHandle) { if (calScheme == CaliScheme::Line) { #if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED adc_cali_delete_scheme_line_fitting(caliHandle); #endif #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED } else if (calScheme == CaliScheme::Curve) { adc_cali_delete_scheme_curve_fitting(caliHandle); #endif } caliHandle = nullptr; calibrated = false; } } enum class CaliScheme { None, Line, Curve }; bool initialized = false; bool calibrated = false; CaliScheme calScheme = CaliScheme::None; adc_oneshot_unit_handle_t adcHandle = nullptr; adc_cali_handle_t caliHandle = nullptr; ChargeFromVoltage chargeEstimator; }; } // namespace std::shared_ptr createPower() { return std::make_shared(); }