Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 124 additions & 28 deletions src/Power.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ class HasBatteryLevel
virtual int getBatteryPercent() { return -1; }

/**
* The raw voltage of the battery or NAN if unknown
* The raw voltage in mV of the battery or NAN if unknown
*/
virtual uint16_t getBattVoltage() { return 0; }
virtual int16_t getBattVoltage() { return 0; }

/**
* return true if there is a battery installed in this unit
Expand Down Expand Up @@ -240,6 +240,17 @@ class AnalogBatteryLevel : public HasBatteryLevel
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/

bool batIsCharging = false;
bool batIsDischrging = false;
bool batIsConnected = false;
bool isADCFlactuating = false;
int batMeasCount = 0;
int batVoltage = 0; // in mV
uint32_t batMeasTimeLastRead = 0; // in ms
const uint32_t batMeasTimeMinReadingsInterval = 5000; // in ms


virtual int getBatteryPercent() override
{
#if defined(HAS_RAKPROT) && !defined(HAS_PMU)
Expand All @@ -248,14 +259,19 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
#endif

float v = getBattVoltage();
int v = getBattVoltage();
LOG_DEBUG("getBatteryPercent: getBattVoltage: %d", v);
if (v == -1) {return -1;}

if (v < noBatVolt)
if ((float)v < noBatVolt) {
batIsConnected = false;
return -1; // If voltage is super low assume no battery installed
LOG_DEBUG("Low voltage. No bettry installed?");
}

#ifdef NO_BATTERY_LEVEL_ON_CHARGE
// This does not work on a RAK4631 with battery connected
if (v > chargingVolt)
if ((float)v > chargingVolt)
return 0; // While charging we can't report % full on the battery
#endif
/**
Expand Down Expand Up @@ -284,7 +300,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
/**
* The raw voltage of the batteryin millivolts or NAN if unknown
*/
virtual uint16_t getBattVoltage() override
virtual int16_t getBattVoltage() override
{

#if HAS_TELEMETRY && defined(HAS_RAKPROT) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
Expand All @@ -304,33 +320,97 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif

#ifndef BATTERY_SENSE_SAMPLES
#define BATTERY_SENSE_SAMPLES \
15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
#define BATTERY_SENSE_SAMPLES 1500
// Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
// Keep enough (300+300) to fetch several battery chargger PWM cycles/charging phases (on seeed xiao ~300 cycles per phase).
// Will have 2 sets of reading: 1 set is higher readings, 2 is lower readings.
// Will not see difference when BATTERY IS ATTACHED
// If BATTERY IS NOT CONNECTED will have two different sets of measurements
// Need this not only to avoid noise but also to recognize disconnected battery
#endif

#ifdef BATTERY_PIN
// Override variant or default ADC_MULTIPLIER if we have the override pref
float operativeAdcMultiplier =
config.power.adc_multiplier_override > 0 ? config.power.adc_multiplier_override : ADC_MULTIPLIER;
// Do not call analogRead() often.
const uint32_t min_read_interval = 5000;
if (!initial_read_done || !Throttle::isWithinTimespanMs(last_read_time_ms, min_read_interval)) {
last_read_time_ms = millis();

if (!initial_read_done || !Throttle::isWithinTimespanMs(batMeasTimeLastRead, batMeasTimeMinReadingsInterval)) {
batMeasTimeLastRead = millis();
batMeasCount++;

uint32_t raw = 0;
uint32_t raw_prev = 0;
int countRawRaise = 0;
int countRawDown = 0;
uint32_t rawSum = 0;
uint32_t rawAvg = 0;
uint32_t batRawMin = 0;
uint32_t batRawMax = 0;
float batMeasDiffPerc = 0;
float scaled = 0;

batIsCharging = false;
batIsDischrging = false;
isADCFlactuating = false;

adcEnable();
#ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
rawSum = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(rawSum, adc_characs);
scaled *= operativeAdcMultiplier;
#else // block for all other platforms
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
raw += analogRead(BATTERY_PIN);
LOG_DEBUG("batMeasCount: %d", batMeasCount);
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES+10; i++) {
raw = analogRead(BATTERY_PIN);
rawSum += raw;
// LOG_DEBUG("Battery new measurements: %u", (uint32_t)(raw));
// Terminal output will take signifacant time so need adjust number of readings (BATTERY_SENSE_SAMPLES) if debug enabled
if (i < 10) { /* skip first due low accuracy (junk)*/ }
else if (i == 11) {
batRawMin = raw;
batRawMax = raw;
}
else {
if (raw < batRawMin) {
batRawMin = raw;
// LOG_DEBUG("Battery new measurements Min: %u", (uint32_t)(batRawMin));
}
if (raw > batRawMax) {
batRawMax = raw;
// LOG_DEBUG("Battery new measurements Max: %u", (uint32_t)(batRawMax));
}
}
if (i > 11) {
if (raw>raw_prev) {countRawRaise++;} // Voltage going up
if (raw<raw_prev) {countRawDown++;} // Voltage going down
}
raw_prev = raw;
}
raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;

if (batRawMax > batRawMin) {
float diff = batRawMax - batRawMin;
batMeasDiffPerc = (float)diff / (float)batRawMin * 100.0;
if (batMeasDiffPerc > 10) { // ~11.68% for Seeed XIAO
LOG_WARN("Battery ADC readings have high variance: Min=%u Max=%u Diff=%.2f%%. Probably battery is not connected", (uint32_t)(batRawMin), (uint32_t)(batRawMax),batMeasDiffPerc);
batIsConnected = false;
adcDisable();
batVoltage = 0;
isADCFlactuating = true;
return -1;
}
LOG_DEBUG("Battery ADC readings variance: Min=%u Max=%u Diff=%.2f%%", (uint32_t)(batRawMin), (uint32_t)(batRawMax),batMeasDiffPerc);
}

if (countRawRaise - countRawDown > BATTERY_SENSE_SAMPLES/100) { batIsCharging = true; }
else if (countRawDown - countRawRaise > BATTERY_SENSE_SAMPLES/100) { batIsDischrging = true; }
LOG_DEBUG("countRawRaise: %d, countRawDown: %d, batIsCharging: %i, batIsDischrging: %i", countRawRaise, countRawDown, batIsCharging, batIsDischrging);
if (batIsCharging) LOG_DEBUG("batIsCharging: true");
else if (batIsDischrging) LOG_DEBUG("batIsDischrging: true");

rawAvg = rawSum / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * rawAvg;
batIsConnected = true;
#endif
adcDisable();

Expand All @@ -344,10 +424,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
}

// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// (last_read_value));
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, rawAvg, (uint32_t)(scaled), (uint32_t)(last_read_value));
batVoltage = last_read_value;
return last_read_value;
} else {
// LOG_DEBUG ("getBattVoltage: Very short interval between calls. Using cached value: %d", batVoltage);
return batVoltage;
}
return last_read_value;
#endif // BATTERY_PIN
return 0;
}
Expand Down Expand Up @@ -432,7 +515,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
return true;
}
#else
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
virtual bool isBatteryConnect() override {
int batteryPercent = getBatteryPercent();
return (batteryPercent != -1) ? true : batIsConnected;
}
#endif

/// If we see a battery voltage higher than physics allows - assume charger is pumping
Expand All @@ -456,7 +542,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
// if it's not HIGH - check the battery
#endif
#endif
return getBattVoltage() > chargingVolt;
return isADCFlactuating || getBattVoltage() > chargingVolt;
}

/// Assume charging if we have a battery and external power is connected.
Expand Down Expand Up @@ -485,6 +571,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
return isBatteryConnect() && isVbusIn();
#endif
#endif
if (batIsCharging || batIsDischrging) {
return true;
LOG_DEBUG("isCharging: Flag batIsCharging or batIsDischrging is set therefore return true");
}
// by default, we check the battery voltage only
return isVbusIn();
}
Expand All @@ -503,7 +593,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0;


#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT)

Expand Down Expand Up @@ -793,6 +883,7 @@ void Power::shutdown()
void Power::readPowerStatus()
{
int32_t batteryVoltageMv = -1; // Assume unknown
int BattVoltage = batteryLevel->getBattVoltage();
int8_t batteryChargePercent = -1;
OptionalBool usbPowered = OptUnknown;
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
Expand All @@ -802,8 +893,13 @@ void Power::readPowerStatus()
hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse;
usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse;
isChargingNow = batteryLevel->isCharging() ? OptTrue : OptFalse;
if (hasBattery) {
batteryVoltageMv = batteryLevel->getBattVoltage();
LOG_DEBUG("BattVoltage: %d", BattVoltage);
LOG_DEBUG("hasBattery: %d", hasBattery);
LOG_DEBUG("usbPowered: %d", usbPowered);
LOG_DEBUG("isChargingNow: %d", isChargingNow);
if (hasBattery && BattVoltage != -1) {
batteryVoltageMv = BattVoltage;
LOG_DEBUG("batteryVoltageMv: %d", batteryVoltageMv);
// If the AXP192 returns a valid battery percentage, use it
if (batteryLevel->getBatteryPercent() >= 0) {
batteryChargePercent = batteryLevel->getBatteryPercent();
Expand Down Expand Up @@ -1301,7 +1397,7 @@ class LipoBatteryLevel : public HasBatteryLevel
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return max17048->getBusVoltageMv(); }
virtual int16_t getBattVoltage() override { return max17048->getBusVoltageMv(); }

/**
* return true if there is a battery installed in this unit
Expand Down Expand Up @@ -1430,7 +1526,7 @@ class LipoCharger : public HasBatteryLevel
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return bq->getVoltage(); }
virtual int16_t getBattVoltage() override { return bq->getVoltage(); }

/**
* return true if there is a battery installed in this unit
Expand Down Expand Up @@ -1511,7 +1607,7 @@ class meshSolarBatteryLevel : public HasBatteryLevel
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
virtual int16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }

/**
* return true if there is a battery installed in this unit
Expand Down
9 changes: 4 additions & 5 deletions src/modules/Telemetry/DeviceTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#include <OLEDDisplayUi.h>
#include <meshUtils.h>

#define MAGIC_USB_BATTERY_LEVEL 101
#define MAGIC_USB_BATTERY_LEVEL 0 // If no battery don't want mislead by providing full charge level

int32_t DeviceTelemetryModule::runOnce()
{
Expand Down Expand Up @@ -85,6 +85,7 @@ meshtastic_MeshPacket *DeviceTelemetryModule::allocReply()

meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
{
int getBatteryVoltageMv = powerStatus->getBatteryVoltageMv();
meshtastic_Telemetry t = meshtastic_Telemetry_init_zero;
t.which_variant = meshtastic_Telemetry_device_metrics_tag;
t.time = getTime();
Expand All @@ -96,11 +97,9 @@ meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
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->getBatteryChargePercent();
t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent();
t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
t.variant.device_metrics.voltage = (getBatteryVoltageMv == -1) ? 0 : (float)getBatteryVoltageMv/1000;
t.variant.device_metrics.uptime_seconds = getUptimeSeconds();

return t;
Expand Down
46 changes: 46 additions & 0 deletions src/modules/Telemetry/Sensor/DS18B20Sensor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "configuration.h"

#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR

#include "DS18B20Sensor.h"
#include "TelemetrySensor.h"
#include <typeinfo>
#include <DallasTemperature.h>
#include "../mesh/generated/meshtastic/telemetry.pb.h"

DS18B20Sensor::DS18B20Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DS18B20, "DS18B20") {
pin = PIN_WIRE_DS18B20; // Define this pin in your board's variant.h
oneWire = new OneWire(pin);
sensors = new DallasTemperature(oneWire);
sensors->begin();
status = sensors->getDeviceCount() > 0;
}

int32_t DS18B20Sensor::runOnce()
{
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
sensors->requestTemperatures();
status = sensors->getDeviceCount() > 0;
LOG_INFO("DS18B20: pin=%d, device_count=%d", pin, sensors->getDeviceCount());
return initI2CSensor();
}

void DS18B20Sensor::setup() {}

bool DS18B20Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
if (!hasSensor()) return false;

sensors->requestTemperatures();
float tempC = sensors->getTempCByIndex(0);

if(tempC != DEVICE_DISCONNECTED_C) {
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.temperature = tempC;
return true;
}
return false;
}
#endif
22 changes: 22 additions & 0 deletions src/modules/Telemetry/Sensor/DS18B20Sensor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "configuration.h"

#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR

#include "TelemetrySensor.h"
#include <DallasTemperature.h>
#include <OneWire.h>

class DS18B20Sensor : public TelemetrySensor {
private:
OneWire *oneWire;
DallasTemperature *sensors;
uint8_t pin;

public:
DS18B20Sensor();
virtual int32_t runOnce() override;
virtual void setup() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};

#endif
Loading