mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 14:12:05 +00:00
Add MAX17048 lipo fuel gauge (#4851)
* Initial commit * Update MAX17048Sensor.cpp * Update EnvironmentTelemetry.cpp --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
parent
1129c92974
commit
40b3dbaa70
@ -136,6 +136,7 @@ lib_deps =
|
|||||||
adafruit/Adafruit MCP9808 Library@^2.0.0
|
adafruit/Adafruit MCP9808 Library@^2.0.0
|
||||||
adafruit/Adafruit INA260 Library@^1.5.0
|
adafruit/Adafruit INA260 Library@^1.5.0
|
||||||
adafruit/Adafruit INA219@^1.2.0
|
adafruit/Adafruit INA219@^1.2.0
|
||||||
|
adafruit/Adafruit MAX1704X@^1.0.3
|
||||||
adafruit/Adafruit SHTC3 Library@^1.0.0
|
adafruit/Adafruit SHTC3 Library@^1.0.0
|
||||||
adafruit/Adafruit LPS2X@^2.0.4
|
adafruit/Adafruit LPS2X@^2.0.4
|
||||||
adafruit/Adafruit SHT31 Library@^2.2.2
|
adafruit/Adafruit SHT31 Library@^2.2.2
|
||||||
|
119
src/Power.cpp
119
src/Power.cpp
@ -77,6 +77,15 @@ INA219Sensor ina219Sensor;
|
|||||||
INA3221Sensor ina3221Sensor;
|
INA3221Sensor ina3221Sensor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||||
|
#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
|
||||||
|
#include <utility>
|
||||||
|
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
|
||||||
|
#if HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)
|
||||||
|
MAX17048Sensor max17048Sensor;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||||
RAK9154Sensor rak9154Sensor;
|
RAK9154Sensor rak9154Sensor;
|
||||||
#endif
|
#endif
|
||||||
@ -167,6 +176,7 @@ static void adcDisable()
|
|||||||
*/
|
*/
|
||||||
class AnalogBatteryLevel : public HasBatteryLevel
|
class AnalogBatteryLevel : public HasBatteryLevel
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||||
*/
|
*/
|
||||||
@ -553,7 +563,12 @@ bool Power::analogInit()
|
|||||||
*/
|
*/
|
||||||
bool Power::setup()
|
bool Power::setup()
|
||||||
{
|
{
|
||||||
bool found = axpChipInit() || analogInit();
|
// initialise one power sensor (only)
|
||||||
|
bool found = axpChipInit();
|
||||||
|
if (!found)
|
||||||
|
found = lipoInit();
|
||||||
|
if (!found)
|
||||||
|
found = analogInit();
|
||||||
|
|
||||||
#ifdef NRF_APM
|
#ifdef NRF_APM
|
||||||
found = true;
|
found = true;
|
||||||
@ -1045,3 +1060,105 @@ bool Power::axpChipInit()
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class for an I2C MAX17048 Lipo battery sensor. If there is no
|
||||||
|
* I2C sensor present, the class falls back to analog battery sensing
|
||||||
|
*/
|
||||||
|
class LipoBatteryLevel : public AnalogBatteryLevel
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
MAX17048Singleton *max17048 = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Init the I2C MAX17048 Lipo battery level sensor
|
||||||
|
*/
|
||||||
|
bool runOnce()
|
||||||
|
{
|
||||||
|
if (max17048 == nullptr) {
|
||||||
|
max17048 = MAX17048Singleton::GetInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to start if the sensor has been detected
|
||||||
|
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].first != 0) {
|
||||||
|
return max17048->runOnce(nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].second);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||||
|
*/
|
||||||
|
virtual int getBatteryPercent() override
|
||||||
|
{
|
||||||
|
if (!max17048->isInitialised())
|
||||||
|
return AnalogBatteryLevel::getBatteryPercent();
|
||||||
|
return max17048->getBusBatteryPercent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||||
|
*/
|
||||||
|
virtual uint16_t getBattVoltage() override
|
||||||
|
{
|
||||||
|
if (!max17048->isInitialised())
|
||||||
|
return AnalogBatteryLevel::getBattVoltage();
|
||||||
|
return max17048->getBusVoltageMv();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is a battery installed in this unit
|
||||||
|
*/
|
||||||
|
virtual bool isBatteryConnect() override
|
||||||
|
{
|
||||||
|
if (!max17048->isInitialised())
|
||||||
|
return AnalogBatteryLevel::isBatteryConnect();
|
||||||
|
return max17048->isBatteryConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if there is an external power source detected
|
||||||
|
*/
|
||||||
|
virtual bool isVbusIn() override
|
||||||
|
{
|
||||||
|
if (!max17048->isInitialised())
|
||||||
|
return AnalogBatteryLevel::isVbusIn();
|
||||||
|
return max17048->isExternallyPowered();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return true if the battery is currently charging
|
||||||
|
*/
|
||||||
|
virtual bool isCharging() override
|
||||||
|
{
|
||||||
|
if (!max17048->isInitialised())
|
||||||
|
return AnalogBatteryLevel::isCharging();
|
||||||
|
return max17048->isBatteryCharging();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LipoBatteryLevel lipoLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the Lipo battery level sensor
|
||||||
|
*/
|
||||||
|
bool Power::lipoInit()
|
||||||
|
{
|
||||||
|
bool result = lipoLevel.runOnce();
|
||||||
|
LOG_DEBUG("Power::lipoInit lipo sensor is %s\n", result ? "ready" : "not ready yet");
|
||||||
|
batteryLevel = &lipoLevel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
/**
|
||||||
|
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
|
||||||
|
*/
|
||||||
|
bool Power::lipoInit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
@ -122,6 +122,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define INA_ADDR_ALTERNATE 0x41
|
#define INA_ADDR_ALTERNATE 0x41
|
||||||
#define INA_ADDR_WAVESHARE_UPS 0x43
|
#define INA_ADDR_WAVESHARE_UPS 0x43
|
||||||
#define INA3221_ADDR 0x42
|
#define INA3221_ADDR 0x42
|
||||||
|
#define MAX1704X_ADDR 0x36
|
||||||
#define QMC6310_ADDR 0x1C
|
#define QMC6310_ADDR 0x1C
|
||||||
#define QMI8658_ADDR 0x6B
|
#define QMI8658_ADDR 0x6B
|
||||||
#define QMC5883L_ADDR 0x0D
|
#define QMC5883L_ADDR 0x0D
|
||||||
|
@ -28,6 +28,7 @@ class ScanI2C
|
|||||||
INA260,
|
INA260,
|
||||||
INA219,
|
INA219,
|
||||||
INA3221,
|
INA3221,
|
||||||
|
MAX17048,
|
||||||
MCP9808,
|
MCP9808,
|
||||||
SHT31,
|
SHT31,
|
||||||
SHT4X,
|
SHT4X,
|
||||||
|
@ -396,6 +396,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n");
|
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n");
|
||||||
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n");
|
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n");
|
||||||
SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n");
|
SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n");
|
||||||
|
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
|
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
|
||||||
|
@ -555,6 +555,7 @@ void setup()
|
|||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260)
|
||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219)
|
||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221)
|
||||||
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048)
|
||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
|
||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
|
||||||
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31)
|
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31)
|
||||||
|
@ -83,7 +83,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// moduleConfig.telemetry.environment_measurement_enabled = 1;
|
// moduleConfig.telemetry.environment_measurement_enabled = 1;
|
||||||
// moduleConfig.telemetry.environment_screen_enabled = 1;
|
// moduleConfig.telemetry.environment_screen_enabled = 1;
|
||||||
// moduleConfig.telemetry.environment_update_interval = 15;
|
// moduleConfig.telemetry.environment_update_interval = 15;
|
||||||
|
|
||||||
if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
||||||
@ -144,6 +144,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
|||||||
result = mlx90632Sensor.runOnce();
|
result = mlx90632Sensor.runOnce();
|
||||||
if (nau7802Sensor.hasSensor())
|
if (nau7802Sensor.hasSensor())
|
||||||
result = nau7802Sensor.runOnce();
|
result = nau7802Sensor.runOnce();
|
||||||
|
if (max17048Sensor.hasSensor())
|
||||||
|
result = max17048Sensor.runOnce();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -156,6 +158,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
|||||||
result = bme680Sensor.runTrigger();
|
result = bme680Sensor.runTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
if (((lastSentToMesh == 0) ||
|
if (((lastSentToMesh == 0) ||
|
||||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||||
moduleConfig.telemetry.environment_update_interval,
|
moduleConfig.telemetry.environment_update_interval,
|
||||||
@ -397,6 +400,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
|||||||
m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
|
m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (max17048Sensor.hasSensor()) {
|
||||||
|
valid = valid && max17048Sensor.getMetrics(m);
|
||||||
|
hasSensor = true;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
return valid && hasSensor;
|
return valid && hasSensor;
|
||||||
@ -587,6 +594,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
|
|||||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
if (max17048Sensor.hasSensor()) {
|
||||||
|
result = max17048Sensor.handleAdminMessage(mp, request, response);
|
||||||
|
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@ int32_t PowerTelemetryModule::runOnce()
|
|||||||
result = ina260Sensor.runOnce();
|
result = ina260Sensor.runOnce();
|
||||||
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized())
|
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized())
|
||||||
result = ina3221Sensor.runOnce();
|
result = ina3221Sensor.runOnce();
|
||||||
|
if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized())
|
||||||
|
result = max17048Sensor.runOnce();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
#else
|
#else
|
||||||
@ -128,19 +130,23 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C";
|
|
||||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||||
if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) {
|
if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) {
|
||||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||||
"Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " +
|
"Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) +
|
||||||
String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||||
|
}
|
||||||
|
if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) {
|
||||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||||
"Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " +
|
"Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) +
|
||||||
String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||||
|
}
|
||||||
|
if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) {
|
||||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||||
"Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " +
|
"Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) +
|
||||||
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +156,8 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m
|
|||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
const char *sender = getSenderShortName(mp);
|
const char *sender = getSenderShortName(mp);
|
||||||
|
|
||||||
LOG_INFO("(Received from %s): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, "
|
LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, "
|
||||||
"ch3_voltage=%f, ch3_current=%f\n",
|
"ch3_voltage=%.1f, ch3_current=%.1f\n",
|
||||||
sender, t->variant.power_metrics.ch1_voltage, t->variant.power_metrics.ch1_current,
|
sender, t->variant.power_metrics.ch1_voltage, t->variant.power_metrics.ch1_current,
|
||||||
t->variant.power_metrics.ch2_voltage, t->variant.power_metrics.ch2_current, t->variant.power_metrics.ch3_voltage,
|
t->variant.power_metrics.ch2_voltage, t->variant.power_metrics.ch2_current, t->variant.power_metrics.ch3_voltage,
|
||||||
t->variant.power_metrics.ch3_current);
|
t->variant.power_metrics.ch3_current);
|
||||||
@ -172,12 +178,7 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
|||||||
m->time = getTime();
|
m->time = getTime();
|
||||||
m->which_variant = meshtastic_Telemetry_power_metrics_tag;
|
m->which_variant = meshtastic_Telemetry_power_metrics_tag;
|
||||||
|
|
||||||
m->variant.power_metrics.ch1_voltage = 0;
|
m->variant.power_metrics = meshtastic_PowerMetrics_init_zero;
|
||||||
m->variant.power_metrics.ch1_current = 0;
|
|
||||||
m->variant.power_metrics.ch2_voltage = 0;
|
|
||||||
m->variant.power_metrics.ch2_current = 0;
|
|
||||||
m->variant.power_metrics.ch3_voltage = 0;
|
|
||||||
m->variant.power_metrics.ch3_current = 0;
|
|
||||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||||
if (ina219Sensor.hasSensor())
|
if (ina219Sensor.hasSensor())
|
||||||
valid = ina219Sensor.getMetrics(m);
|
valid = ina219Sensor.getMetrics(m);
|
||||||
@ -185,6 +186,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
|||||||
valid = ina260Sensor.getMetrics(m);
|
valid = ina260Sensor.getMetrics(m);
|
||||||
if (ina3221Sensor.hasSensor())
|
if (ina3221Sensor.hasSensor())
|
||||||
valid = ina3221Sensor.getMetrics(m);
|
valid = ina3221Sensor.getMetrics(m);
|
||||||
|
if (max17048Sensor.hasSensor())
|
||||||
|
valid = max17048Sensor.getMetrics(m);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
|
176
src/modules/Telemetry/Sensor/MAX17048Sensor.cpp
Normal file
176
src/modules/Telemetry/Sensor/MAX17048Sensor.cpp
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
#include "MAX17048Sensor.h"
|
||||||
|
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||||
|
|
||||||
|
MAX17048Singleton *MAX17048Singleton::GetInstance()
|
||||||
|
{
|
||||||
|
if (pinstance == nullptr) {
|
||||||
|
pinstance = new MAX17048Singleton();
|
||||||
|
}
|
||||||
|
return pinstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX17048Singleton::MAX17048Singleton() {}
|
||||||
|
|
||||||
|
MAX17048Singleton::~MAX17048Singleton() {}
|
||||||
|
|
||||||
|
MAX17048Singleton *MAX17048Singleton::pinstance{nullptr};
|
||||||
|
|
||||||
|
bool MAX17048Singleton::runOnce(TwoWire *theWire)
|
||||||
|
{
|
||||||
|
initialized = begin(theWire);
|
||||||
|
LOG_DEBUG("MAX17048Sensor::runOnce %s\n", initialized ? "began ok" : "begin failed");
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX17048Singleton::isBatteryCharging()
|
||||||
|
{
|
||||||
|
float volts = cellVoltage();
|
||||||
|
if (isnan(volts)) {
|
||||||
|
LOG_DEBUG("MAX17048Sensor::isBatteryCharging is not connected\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX17048ChargeSample sample;
|
||||||
|
sample.chargeRate = chargeRate(); // charge/discharge rate in percent/hr
|
||||||
|
sample.cellPercent = cellPercent(); // state of charge in percent 0 to 100
|
||||||
|
chargeSamples.push(sample); // save a sample into a fifo buffer
|
||||||
|
|
||||||
|
// Keep the fifo buffer trimmed
|
||||||
|
while (chargeSamples.size() > MAX17048_CHARGING_SAMPLES)
|
||||||
|
chargeSamples.pop();
|
||||||
|
|
||||||
|
// Based on the past n samples, is the lipo charging, discharging or idle
|
||||||
|
if (chargeSamples.front().chargeRate > MAX17048_CHARGING_MINIMUM_RATE &&
|
||||||
|
chargeSamples.back().chargeRate > MAX17048_CHARGING_MINIMUM_RATE) {
|
||||||
|
if (chargeSamples.front().cellPercent > chargeSamples.back().cellPercent)
|
||||||
|
chargeState = MAX17048ChargeState::EXPORT;
|
||||||
|
else if (chargeSamples.front().cellPercent < chargeSamples.back().cellPercent)
|
||||||
|
chargeState = MAX17048ChargeState::IMPORT;
|
||||||
|
else
|
||||||
|
chargeState = MAX17048ChargeState::IDLE;
|
||||||
|
} else {
|
||||||
|
chargeState = MAX17048ChargeState::IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("MAX17048Sensor::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", chargeLabels[chargeState], volts,
|
||||||
|
sample.cellPercent, sample.chargeRate);
|
||||||
|
return chargeState == MAX17048ChargeState::IMPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t MAX17048Singleton::getBusVoltageMv()
|
||||||
|
{
|
||||||
|
float volts = cellVoltage();
|
||||||
|
if (isnan(volts)) {
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getBusVoltageMv is not connected\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getBusVoltageMv %.3fmV\n", volts);
|
||||||
|
return (uint16_t)(volts * 1000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MAX17048Singleton::getBusBatteryPercent()
|
||||||
|
{
|
||||||
|
float soc = cellPercent();
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getBusBatteryPercent %.1f%%\n", soc);
|
||||||
|
return clamp(static_cast<uint8_t>(round(soc)), static_cast<uint8_t>(0), static_cast<uint8_t>(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t MAX17048Singleton::getTimeToGoSecs()
|
||||||
|
{
|
||||||
|
float rate = chargeRate(); // charge/discharge rate in percent/hr
|
||||||
|
float soc = cellPercent(); // state of charge in percent 0 to 100
|
||||||
|
soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100%
|
||||||
|
float ttg = ((100.0f - soc) / rate) * 3600.0f; // calculate seconds to charge/discharge
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getTimeToGoSecs %.0f seconds\n", ttg);
|
||||||
|
return (uint16_t)ttg;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX17048Singleton::isBatteryConnected()
|
||||||
|
{
|
||||||
|
float volts = cellVoltage();
|
||||||
|
if (isnan(volts)) {
|
||||||
|
LOG_DEBUG("MAX17048Sensor::isBatteryConnected is not connected\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a valid voltage is returned, then battery must be connected
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MAX17048Singleton::isExternallyPowered()
|
||||||
|
{
|
||||||
|
float volts = cellVoltage();
|
||||||
|
if (isnan(volts)) {
|
||||||
|
// if the battery is not connected then there must be external power
|
||||||
|
LOG_DEBUG("MAX17048Sensor::isExternallyPowered battery is\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// if the bus voltage is over MAX17048_BUS_POWER_VOLTS, then the external power
|
||||||
|
// is assumed to be connected
|
||||||
|
LOG_DEBUG("MAX17048Sensor::isExternallyPowered %s connected\n", volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not");
|
||||||
|
return volts >= MAX17048_BUS_POWER_VOLTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY))
|
||||||
|
|
||||||
|
MAX17048Sensor::MAX17048Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MAX17048, "MAX17048") {}
|
||||||
|
|
||||||
|
int32_t MAX17048Sensor::runOnce()
|
||||||
|
{
|
||||||
|
if (isInitialized()) {
|
||||||
|
LOG_INFO("Init sensor: %s is already initialised\n", sensorName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Init sensor: %s\n", sensorName);
|
||||||
|
if (!hasSensor()) {
|
||||||
|
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a singleton instance and initialise the max17048
|
||||||
|
if (max17048 == nullptr) {
|
||||||
|
max17048 = MAX17048Singleton::GetInstance();
|
||||||
|
}
|
||||||
|
status = max17048->runOnce(nodeTelemetrySensorsMap[sensorType].second);
|
||||||
|
return initI2CSensor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX17048Sensor::setup() {}
|
||||||
|
|
||||||
|
bool MAX17048Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getMetrics id: %i\n", measurement->which_variant);
|
||||||
|
|
||||||
|
float volts = max17048->cellVoltage();
|
||||||
|
if (isnan(volts)) {
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getMetrics battery is not connected\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float rate = max17048->chargeRate(); // charge/discharge rate in percent/hr
|
||||||
|
float soc = max17048->cellPercent(); // state of charge in percent 0 to 100
|
||||||
|
soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100%
|
||||||
|
float ttg = (100.0f - soc) / rate; // calculate hours to charge/discharge
|
||||||
|
|
||||||
|
LOG_DEBUG("MAX17048Sensor::getMetrics volts: %.3fV soc: %.1f%% ttg: %.1f hours\n", volts, soc, ttg);
|
||||||
|
if ((int)measurement->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||||
|
measurement->variant.power_metrics.has_ch1_voltage = true;
|
||||||
|
measurement->variant.power_metrics.ch1_voltage = volts;
|
||||||
|
} else if ((int)measurement->which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||||
|
measurement->variant.device_metrics.has_battery_level = true;
|
||||||
|
measurement->variant.device_metrics.has_voltage = true;
|
||||||
|
measurement->variant.device_metrics.battery_level = static_cast<uint32_t>(round(soc));
|
||||||
|
measurement->variant.device_metrics.voltage = volts;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t MAX17048Sensor::getBusVoltageMv()
|
||||||
|
{
|
||||||
|
return max17048->getBusVoltageMv();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
110
src/modules/Telemetry/Sensor/MAX17048Sensor.h
Normal file
110
src/modules/Telemetry/Sensor/MAX17048Sensor.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef MAX17048_SENSOR_H
|
||||||
|
#define MAX17048_SENSOR_H
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||||
|
|
||||||
|
// Samples to store in a buffer to determine if the battery is charging or discharging
|
||||||
|
#define MAX17048_CHARGING_SAMPLES 3
|
||||||
|
|
||||||
|
// Threshold to determine if the battery is on charge, in percent/hour
|
||||||
|
#define MAX17048_CHARGING_MINIMUM_RATE 1.0f
|
||||||
|
|
||||||
|
// Threshold to determine if the board has bus power
|
||||||
|
#define MAX17048_BUS_POWER_VOLTS 4.195f
|
||||||
|
|
||||||
|
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||||
|
#include "TelemetrySensor.h"
|
||||||
|
#include "VoltageSensor.h"
|
||||||
|
|
||||||
|
#include "meshUtils.h"
|
||||||
|
#include <Adafruit_MAX1704X.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
struct MAX17048ChargeSample {
|
||||||
|
float cellPercent;
|
||||||
|
float chargeRate;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum MAX17048ChargeState { IDLE, EXPORT, IMPORT };
|
||||||
|
|
||||||
|
// Singleton wrapper for the Adafruit_MAX17048 class
|
||||||
|
class MAX17048Singleton : public Adafruit_MAX17048
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static MAX17048Singleton *pinstance;
|
||||||
|
bool initialized = false;
|
||||||
|
std::queue<MAX17048ChargeSample> chargeSamples;
|
||||||
|
MAX17048ChargeState chargeState = IDLE;
|
||||||
|
const String chargeLabels[3] = {F("idle"), F("export"), F("import")};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MAX17048Singleton();
|
||||||
|
~MAX17048Singleton();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create a singleton instance (not thread safe)
|
||||||
|
static MAX17048Singleton *GetInstance();
|
||||||
|
|
||||||
|
// Singletons should not be cloneable.
|
||||||
|
MAX17048Singleton(MAX17048Singleton &other) = delete;
|
||||||
|
|
||||||
|
// Singletons should not be assignable.
|
||||||
|
void operator=(const MAX17048Singleton &) = delete;
|
||||||
|
|
||||||
|
// Initialise the sensor (not thread safe)
|
||||||
|
virtual bool runOnce(TwoWire *theWire = &Wire);
|
||||||
|
|
||||||
|
// Get the current bus voltage
|
||||||
|
uint16_t getBusVoltageMv();
|
||||||
|
|
||||||
|
// Get the state of charge in percent 0 to 100
|
||||||
|
uint8_t getBusBatteryPercent();
|
||||||
|
|
||||||
|
// Calculate the seconds to charge/discharge
|
||||||
|
uint16_t getTimeToGoSecs();
|
||||||
|
|
||||||
|
// Returns true if the battery sensor has started
|
||||||
|
inline virtual bool isInitialised() { return initialized; };
|
||||||
|
|
||||||
|
// Returns true if the battery is currently on charge (not thread safe)
|
||||||
|
bool isBatteryCharging();
|
||||||
|
|
||||||
|
// Returns true if a battery is actually connected
|
||||||
|
bool isBatteryConnected();
|
||||||
|
|
||||||
|
// Returns true if there is bus or external power connected
|
||||||
|
bool isExternallyPowered();
|
||||||
|
};
|
||||||
|
|
||||||
|
#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY))
|
||||||
|
|
||||||
|
class MAX17048Sensor : public TelemetrySensor, VoltageSensor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
MAX17048Singleton *max17048 = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void setup() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MAX17048Sensor();
|
||||||
|
|
||||||
|
// Initialise the sensor
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
|
// Get the current bus voltage and state of charge
|
||||||
|
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||||
|
|
||||||
|
// Get the current bus voltage
|
||||||
|
virtual uint16_t getBusVoltageMv() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -48,6 +48,11 @@ extern INA219Sensor ina219Sensor;
|
|||||||
extern INA3221Sensor ina3221Sensor;
|
extern INA3221Sensor ina3221Sensor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||||
|
#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
|
||||||
|
extern MAX17048Sensor max17048Sensor;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
|
||||||
#include "../variants/rak2560/RAK9154Sensor.h"
|
#include "../variants/rak2560/RAK9154Sensor.h"
|
||||||
extern RAK9154Sensor rak9154Sensor;
|
extern RAK9154Sensor rak9154Sensor;
|
||||||
@ -82,6 +87,8 @@ class Power : private concurrency::OSThread
|
|||||||
bool axpChipInit();
|
bool axpChipInit();
|
||||||
/// Setup a simple ADC input based battery sensor
|
/// Setup a simple ADC input based battery sensor
|
||||||
bool analogInit();
|
bool analogInit();
|
||||||
|
/// Setup a Lipo battery level sensor
|
||||||
|
bool lipoInit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// open circuit voltage lookup table
|
// open circuit voltage lookup table
|
||||||
|
Loading…
Reference in New Issue
Block a user