Skip to content

Commit 879c1ee

Browse files
committed
Add battery charge support (backend)
1 parent ffbc90a commit 879c1ee

6 files changed

Lines changed: 131 additions & 36 deletions

File tree

src/powermonitor.cpp

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,94 @@
11
#include "powermonitor.h"
22
#include <ArduinoLog.h>
33
#include "configuration.h"
4-
#include <INA219.h>
4+
#include <array>
5+
#include <utility>
6+
#include "settings.h"
57

6-
APB::PowerMonitor &APB::PowerMonitor::Instance = *new APB::PowerMonitor();
78

8-
struct APB::PowerMonitor::Private {
9-
INA219 ina219{APB_INA1219_ADDRESS};
10-
PowerMonitor::Status status;
11-
Task loopTask;
12-
};
9+
float lipoBatteryCharge(uint8_t cells, float voltage);
1310

14-
APB::PowerMonitor::PowerMonitor() : d{std::make_shared<Private>()}{
11+
APB::PowerMonitor &APB::PowerMonitor::Instance = *new APB::PowerMonitor();
1512

13+
APB::PowerMonitor::PowerMonitor() {
1614
}
1715

1816
APB::PowerMonitor::~PowerMonitor() {
19-
20-
}
21-
22-
APB::PowerMonitor::Status APB::PowerMonitor::status() const {
23-
return d->status;
2417
}
2518

2619
void APB::PowerMonitor::setup(Scheduler &scheduler) {
27-
d->status.initialised = d->ina219.begin();
28-
if(d->status.initialised) {
20+
_status.initialised = _ina219.begin();
21+
if(_status.initialised) {
2922
Log.infoln("Powermonitor initialised: INA219 with address 0x%x", APB_INA1219_ADDRESS);
3023

3124

32-
bool gainValid = d->ina219.setGain(APB_POWER_INA219_GAIN);
33-
bool voltageRangeValid = d->ina219.setBusVoltageRange(APB_POWER_INA219_VOLTAGE_RANGE);
34-
bool shuntValid = d->ina219.setMaxCurrentShunt(APB_POWER_MAX_CURRENT_AMPS, APB_POWER_SHUNT_OHMS);
25+
bool gainValid = _ina219.setGain(APB_POWER_INA219_GAIN);
26+
bool voltageRangeValid = _ina219.setBusVoltageRange(APB_POWER_INA219_VOLTAGE_RANGE);
27+
bool shuntValid = _ina219.setMaxCurrentShunt(APB_POWER_MAX_CURRENT_AMPS, APB_POWER_SHUNT_OHMS);
3528

3629
Log.infoln("Powermonitor settings: valid=%d, %d milliamp max (%d amps), shunt resistor: %d milliohms",
3730
shuntValid && voltageRangeValid && gainValid,
38-
static_cast<int>(d->ina219.getMaxCurrent() * 1000.0),
39-
static_cast<int>(d->ina219.getMaxCurrent()),
40-
static_cast<int>(d->ina219.getShunt() * 1000.0)
31+
static_cast<int>(_ina219.getMaxCurrent() * 1000.0),
32+
static_cast<int>(_ina219.getMaxCurrent()),
33+
static_cast<int>(_ina219.getShunt() * 1000.0)
4134
);
4235

43-
d->loopTask.set(1000, TASK_FOREVER, [this](){
44-
d->status.busVoltage = d->ina219.getBusVoltage();
45-
d->status.current = d->ina219.getCurrent();
46-
d->status.power = d->ina219.getPower();
47-
d->status.shuntVoltage = d->ina219.getShuntVoltage();
48-
if(d->status.power == 0 && d->status.current == 0) {
49-
Log.warningln("Powermonitor: Reporting power as 0. INA status: %d", d->ina219.isConnected());
36+
_loopTask.set(1000, TASK_FOREVER, [this](){
37+
_status.busVoltage = _ina219.getBusVoltage();
38+
_status.current = _ina219.getCurrent();
39+
_status.power = _ina219.getPower();
40+
_status.shuntVoltage = _ina219.getShuntVoltage();
41+
setCharge();
42+
if(_status.power == 0 && _status.current == 0) {
43+
Log.warningln("Powermonitor: Reporting power as 0. INA status: %d", _ina219.isConnected());
5044
}
5145
});
52-
scheduler.addTask(d->loopTask);
53-
d->loopTask.enable();
46+
scheduler.addTask(_loopTask);
47+
_loopTask.enable();
5448
} else {
5549
Log.errorln("Powermonitor failed to initialise INA219 with address 0x%x", APB_INA1219_ADDRESS);
5650
}
51+
}
52+
53+
void APB::PowerMonitor::setCharge() {
54+
switch (Settings::Instance.powerSource()) {
55+
case LipoBattery3C:
56+
_status.charge = lipoBatteryCharge(3, _status.busVoltage);
57+
break;
58+
default:
59+
_status.charge = 100.0f;
60+
}
61+
}
62+
63+
64+
float lipoBatteryCharge(uint8_t cells, float voltage) {
65+
float singleCellVoltage = voltage / static_cast<float>(cells);
66+
using RefPair = std::pair<float, float>;
67+
static const std::array<RefPair, 20> chargeMap{{
68+
{95, 4.15},
69+
{90, 4.11},
70+
{85, 4.08},
71+
{80, 4.02},
72+
{75, 3.98},
73+
{70, 3.95},
74+
{65, 3.91},
75+
{60, 3.87},
76+
{55, 3.85},
77+
{50, 3.84},
78+
{45, 3.82},
79+
{40, 3.8},
80+
{35, 3.79},
81+
{30, 3.77},
82+
{25, 3.75},
83+
{20, 3.73},
84+
{15, 3.71},
85+
{10, 3.69},
86+
{ 5, 3.61},
87+
{ 0, 3.27},
88+
}};
89+
const RefPair *found = std::find_if(chargeMap.begin(), chargeMap.end(), [&singleCellVoltage](const RefPair &el){ return singleCellVoltage > el.second; });
90+
if(found == chargeMap.end()) {
91+
return 100.0;
92+
}
93+
return found->first;
5794
}

src/powermonitor.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <TaskSchedulerDeclarations.h>
77

88
#include "configuration.h"
9+
#include <INA219.h>
910

1011
namespace APB {
1112

@@ -22,13 +23,20 @@ class PowerMonitor {
2223
float busVoltage = 0;
2324
float current = 0;
2425
float power = 0;
25-
26+
float charge = 0;
2627
};
27-
Status status() const;
28+
enum PowerSource {
29+
AC = 0,
30+
LipoBattery3C = 1,
31+
};
32+
Status status() const { return _status; }
2833
private:
29-
struct Private;
30-
friend struct Private;
31-
std::shared_ptr<Private> d;
34+
INA219 _ina219{APB_INA1219_ADDRESS};
35+
PowerMonitor::Status _status;
36+
Task _loopTask;
37+
PowerSource _powerSource = AC;
38+
39+
void setCharge();
3240
};
3341
}
3442

src/settings.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@
99
#define APB_KEY_VERSION "version"
1010

1111
#define APB_KEY_STATUS_LED_DUTY "status_led_duty"
12+
#define APB_KEY_POWER_SOURCE_TYPE "power_src_type"
1213

1314
#define LOG_SCOPE "APB::Configuration - "
1415

1516
using namespace std::placeholders;
1617

1718
APB::Settings &APB::Settings::Instance = *new APB::Settings();
1819

20+
const std::unordered_map<APB::PowerMonitor::PowerSource, const char*> APB::Settings::PowerSourcesNames = {
21+
{PowerMonitor::AC, "AC"},
22+
{PowerMonitor::LipoBattery3C, "lipo_3c"},
23+
};
24+
25+
1926

2027
APB::Settings::Settings() :
2128
#ifdef APB_DEFAULT_HOSTNAME
@@ -44,12 +51,15 @@ void APB::Settings::load() {
4451
return;
4552
}
4653
_statusLedDuty = prefs.getFloat(APB_KEY_STATUS_LED_DUTY, 1);
54+
_powerSource = static_cast<PowerMonitor::PowerSource>(prefs.getUShort(APB_KEY_POWER_SOURCE_TYPE, static_cast<uint16_t>(PowerMonitor::AC)));
4755
wifiSettings.load();
4856
Log.infoln(LOG_SCOPE "Preferences loaded");
4957
}
5058

5159
void APB::Settings::loadDefaults() {
5260
wifiSettings.loadDefaults();
61+
_powerSource = PowerMonitor::AC;
62+
_statusLedDuty = 1.0;
5363
}
5464

5565

@@ -59,5 +69,14 @@ void APB::Settings::save() {
5969
prefs.putUShort(APB_KEY_VERSION, APB_PREFS_VERSION);
6070
wifiSettings.save();
6171
prefs.putFloat(APB_KEY_STATUS_LED_DUTY, _statusLedDuty);
72+
prefs.putUShort(APB_KEY_POWER_SOURCE_TYPE, static_cast<uint16_t>(_powerSource));
6273
Log.infoln(LOG_SCOPE "Preferences saved");
6374
}
75+
76+
APB::PowerMonitor::PowerSource APB::Settings::powerSource() const {
77+
return _powerSource;
78+
}
79+
80+
void APB::Settings::setPowerSource(PowerMonitor::PowerSource powerSource) {
81+
this->_powerSource = powerSource;
82+
}

src/settings.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
#include "configuration.h"
55
#include <WString.h>
66
#include "wifisettings.h"
7-
7+
#include "powermonitor.h"
8+
#include <unordered_map>
89

910
namespace APB {
1011
class Settings {
@@ -18,10 +19,15 @@ class Settings {
1819

1920
float statusLedDuty() const { return _statusLedDuty; };
2021
void setStatusLedDuty(float duty) { _statusLedDuty = duty; }
22+
PowerMonitor::PowerSource powerSource() const;
23+
void setPowerSource(PowerMonitor::PowerSource powerSource);
24+
25+
static const std::unordered_map<PowerMonitor::PowerSource, const char*> PowerSourcesNames;
2126
private:
2227
Preferences prefs;
2328
GuLinux::WiFiSettings wifiSettings;
2429
float _statusLedDuty;
30+
PowerMonitor::PowerSource _powerSource;
2531
void loadDefaults();
2632
};
2733
}

src/webserver.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "metricsresponse.h"
1010
#include <esp_system.h>
1111
#include <LittleFS.h>
12+
#include <map>
1213
#include "utils.h"
1314

1415
#define LOG_SCOPE "APB::WebServer "
@@ -40,6 +41,7 @@ void APB::WebServer::setup() {
4041
onJsonRequest("/api/config/accessPoint", std::bind(&WiFiManager::onConfigAccessPoint, &WiFiManager::Instance, _1, _2), HTTP_POST | HTTP_DELETE);
4142
onJsonRequest("/api/config/station", std::bind(&WiFiManager::onConfigStation, &WiFiManager::Instance, _1, _2), HTTP_POST | HTTP_DELETE);
4243
onJsonRequest("/api/config/statusLedDuty", std::bind(&WebServer::onConfigStatusLedDuty, this, _1, _2), HTTP_POST);
44+
onJsonRequest("/api/config/powerSourceType", std::bind(&WebServer::onConfigPowerSourceType, this, _1, _2), HTTP_POST);
4345
server.on("/api/metrics", HTTP_GET, std::bind(&WebServer::onGetMetrics, this, _1));
4446
server.on("/api/config/write", HTTP_POST, std::bind(&WebServer::onPostWriteConfig, this, _1));
4547
server.on("/api/config", HTTP_GET, std::bind(&WebServer::onGetConfig, this, _1));
@@ -107,6 +109,7 @@ void APB::WebServer::onGetConfig(AsyncWebServerRequest *request) {
107109
JsonObject rootObject = response.root().to<JsonObject>();
108110
WiFiManager::Instance.onGetConfig(rootObject);
109111
rootObject["ledDuty"] = Settings::Instance.statusLedDuty();
112+
rootObject["powerSourceType"] = Settings::PowerSourcesNames.at(Settings::Instance.powerSource());
110113
}
111114

112115
void APB::WebServer::onGetHistory(AsyncWebServerRequest *request) {
@@ -171,6 +174,7 @@ void APB::WebServer::populatePowerStatus(JsonObject powerStatus) {
171174
powerStatus["current"] = PowerMonitor::Instance.status().current;
172175
powerStatus["power"] = PowerMonitor::Instance.status().power;
173176
powerStatus["shuntVoltage"] = PowerMonitor::Instance.status().shuntVoltage;
177+
powerStatus["charge"] = PowerMonitor::Instance.status().charge;
174178
}
175179

176180

@@ -320,6 +324,26 @@ void APB::WebServer::onConfigStatusLedDuty(AsyncWebServerRequest *request, JsonV
320324
response.root()["duty"] = StatusLed::Instance.duty();
321325
}
322326

327+
void APB::WebServer::onConfigPowerSourceType(AsyncWebServerRequest *request, JsonVariant &json) {
328+
Validation validation{request, json};
329+
std::forward_list<String> choices;
330+
std::map<String, PowerMonitor::PowerSource> mapping;
331+
std::for_each(
332+
Settings::PowerSourcesNames.begin(),
333+
Settings::PowerSourcesNames.end(),
334+
[&choices, &mapping](const auto &item){
335+
choices.push_front(item.second);
336+
mapping[item.second] = item.first;
337+
}
338+
);
339+
if(validation.required<String>("powerSourceType")
340+
.choice("powerSourceType", choices)
341+
.invalid()) return;
342+
Settings::Instance.setPowerSource(mapping.at(json["powerSourceType"]));
343+
JsonResponse response(request);
344+
response.root()["powerSourceType"] = Settings::PowerSourcesNames.at(Settings::Instance.powerSource());
345+
}
346+
323347
void APB::WebServer::onJsonRequest(const char *path, ArJsonRequestHandlerFunction f, WebRequestMethodComposite method) {
324348
auto handler = new AsyncCallbackJsonWebHandler(path, f);
325349
handler->setMethod(method);

src/webserver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class WebServer {
4040
void onRestart(AsyncWebServerRequest *request);
4141
void onPostSetHeater(AsyncWebServerRequest *request, JsonVariant &json);
4242
void onConfigStatusLedDuty(AsyncWebServerRequest *request, JsonVariant &json);
43+
void onConfigPowerSourceType(AsyncWebServerRequest *request, JsonVariant &json);
4344

4445

4546
void populateHeatersStatus(JsonArray heatersStatus);

0 commit comments

Comments
 (0)