diff --git a/src/PowerStatus.h b/src/PowerStatus.h index fe4543e08d2..f3864da5319 100644 --- a/src/PowerStatus.h +++ b/src/PowerStatus.h @@ -11,6 +11,11 @@ namespace meshtastic */ enum OptionalBool { OptFalse = 0, OptTrue = 1, OptUnknown = 2 }; +/// Coarse charge/power state, derived from PowerStatus's own fields. +/// Explicitly numbered, with Unknown=0 as the default/unset value, so this can be wire-mapped to a future +/// protobuf enum (e.g. an admin/telemetry is_charging-style field) without the numbering shifting under it. +enum class PowerState { Unknown = 0, Discharging = 1, Charging = 2, Charged = 3, Critical = 4 }; + /// Describes the state of the Power system. class PowerStatus : public Status { @@ -56,6 +61,18 @@ class PowerStatus : public Status bool getIsCharging() const { return isCharging == OptTrue; } + /// PowerState::Charged means "powered and battery reads full" - StatusLEDModule keys its solid-LED + /// state off this value directly. Returns Unknown before the first real reading arrives (e.g. cold + /// boot, before Power::readPowerStatus() has run) rather than guessing from all-default fields. + PowerState getPowerState() const + { + if (!isInitialized()) + return PowerState::Unknown; + if (getHasUSB() || getIsCharging()) + return getBatteryChargePercent() >= 100 ? PowerState::Charged : PowerState::Charging; + return getBatteryChargePercent() > 5 ? PowerState::Discharging : PowerState::Critical; + } + int getBatteryVoltageMv() const { return batteryVoltageMv; } /** diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 5c6f849423d..4d49dfd58fd 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -48,18 +48,7 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { case STATUS_TYPE_POWER: { - if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) { - power_state = charging; - if (powerStatus->getBatteryChargePercent() >= 100) { - power_state = charged; - } - } else { - if (powerStatus->getBatteryChargePercent() > 5) { - power_state = discharging; - } else { - power_state = critical; - } - } + power_state = powerStatus->getPowerState(); break; } case STATUS_TYPE_BLUETOOTH: { @@ -111,13 +100,13 @@ int32_t StatusLEDModule::runOnce() { my_interval = 1000; - if (power_state == charging) { + if (power_state == meshtastic::PowerState::Charging) { #ifndef POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING CHARGE_LED_state = !CHARGE_LED_state; #endif - } else if (power_state == charged) { + } else if (power_state == meshtastic::PowerState::Charged) { CHARGE_LED_state = LED_STATE_ON; - } else if (power_state == critical) { + } else if (power_state == meshtastic::PowerState::Critical) { if (POWER_LED_starttime + 30000 < millis() && !doing_fast_blink) { doing_fast_blink = true; POWER_LED_starttime = millis(); @@ -138,9 +127,10 @@ int32_t StatusLEDModule::runOnce() CHARGE_LED_state = LED_STATE_OFF; #endif } -// If we want a LED to be dedicated to the simple hearbeat, we can use that instead of the charge LED + // If we want a LED to be dedicated to the simple hearbeat, we can use that instead of the charge LED + const bool notCharging = power_state != meshtastic::PowerState::Charging && power_state != meshtastic::PowerState::Charged; #if defined(LED_HEARTBEAT) - if (power_state != charging && power_state != charged && !doing_fast_blink && !config.device.led_heartbeat_disabled) { + if (notCharging && !doing_fast_blink && !config.device.led_heartbeat_disabled) { if (HEARTBEAT_LED_state == LED_STATE_ON) { HEARTBEAT_LED_state = LED_STATE_OFF; my_interval = 999; @@ -154,7 +144,7 @@ int32_t StatusLEDModule::runOnce() digitalWrite(LED_HEARTBEAT, HEARTBEAT_LED_state); } #else - if (power_state != charging && power_state != charged && !doing_fast_blink && !config.device.led_heartbeat_disabled) { + if (notCharging && !doing_fast_blink && !config.device.led_heartbeat_disabled) { if (CHARGE_LED_state == LED_STATE_ON) { CHARGE_LED_state = LED_STATE_OFF; my_interval = 999; @@ -219,7 +209,7 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_POWER #ifdef LED_POWER_CRITICAL if (LED_POWER != LED_POWER_CRITICAL) { - if (power_state == critical) { + if (power_state == meshtastic::PowerState::Critical) { digitalWrite(LED_POWER, 0); digitalWrite(LED_POWER_CRITICAL, CHARGE_LED_state); } else { diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index e9e148eaf0d..7d27d2c74f4 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -90,9 +90,7 @@ class StatusLEDModule : private concurrency::OSThread uint32_t LORA_LED_starttime = 0; #endif - enum PowerState { discharging, charging, charged, critical }; - - PowerState power_state = discharging; + meshtastic::PowerState power_state = meshtastic::PowerState::Unknown; enum BLEState { unpaired, pairing, connected }; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 06e22029652..12f864d167b 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -102,9 +102,8 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry() t.variant.device_metrics.has_channel_utilization = true; t.variant.device_metrics.has_uptime_seconds = true; t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent(); - t.variant.device_metrics.battery_level = (!powerStatus->getHasBattery() || powerStatus->getIsCharging()) - ? MAGIC_USB_BATTERY_LEVEL - : powerStatus->getBatteryChargePercent(); + t.variant.device_metrics.battery_level = + !powerStatus->getHasBattery() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent(); t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent(); // Only populate voltage when we actually have a battery reading. Previously this assigned // -0.001 (from -1 mV / 1000) whenever the ADC returned -1, leaking a sentinel onto the wire