Modified FiveTalk with GPS coordinates, Power has OLED display at outset

This commit is contained in:
John Poole 2026-02-19 21:22:25 -08:00
commit 38b80f97e5
4 changed files with 423 additions and 4 deletions

View file

@ -0,0 +1,30 @@
# Exercise 14: Power (Charging + Visual)
This exercise is intentionally narrow in scope:
- Detect if a battery is present.
- Detect if USB/VBUS power is present.
- Determine if charging is needed.
- Keep charging enabled through AXP2101 PMU settings.
- Flash the PMU charge LED while charging.
- If fully charged, leave LED off (do nothing).
OLED behavior:
- For the first 2 minutes after boot, OLED shows:
- `Exercise 14 Power`
- node name (`NODE_LABEL`)
- time (RTC/system time if available, else uptime)
- charging state and battery stats
- After 2 minutes, it switches to a steady `Power Monitor` header while continuing live stats.
## Meshtastic references used
- `src/Power.cpp`
- charging detection path (`isCharging()`, `isVbusIn()`, battery checks)
- `src/modules/StatusLEDModule.cpp`
- PMU charging LED control via `PMU->setChargingLedMode(...)`
## Build and upload
```bash
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/14_Power
pio run -e ed -t upload
pio device monitor -b 115200
```

View file

@ -0,0 +1,65 @@
; 20260220 Codex
; Exercise 14_Power
[platformio]
default_envs = ed
[env]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
monitor_speed = 115200
lib_deps =
lewisxhe/XPowersLib@0.3.3
Wire
olikraus/U8g2@^2.36.4
build_flags =
-I ../../shared/boards
-I ../../external/microReticulum_Firmware
-D BOARD_MODEL=BOARD_TBEAM_S_V1
-D OLED_SDA=17
-D OLED_SCL=18
-D OLED_ADDR=0x3C
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
[env:amy]
extends = env
upload_port = /dev/ttytAMY
monitor_port = /dev/ttytAMY
build_flags =
${env.build_flags}
-D NODE_LABEL=\"AMY\"
[env:bob]
extends = env
upload_port = /dev/ttytBOB
monitor_port = /dev/ttytBOB
build_flags =
${env.build_flags}
-D NODE_LABEL=\"BOB\"
[env:cy]
extends = env
upload_port = /dev/ttytCY
monitor_port = /dev/ttytCY
build_flags =
${env.build_flags}
-D NODE_LABEL=\"CY\"
[env:dan]
extends = env
upload_port = /dev/ttytDAN
monitor_port = /dev/ttytDAN
build_flags =
${env.build_flags}
-D NODE_LABEL=\"DAN\"
[env:ed]
extends = env
upload_port = /dev/ttytED
monitor_port = /dev/ttytED
build_flags =
${env.build_flags}
-D NODE_LABEL=\"ED\"

View file

@ -0,0 +1,192 @@
// 20260220 Codex
// Exercise 14: Power / Charging Visual Indicator
#include <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <XPowersLib.h>
#include <time.h>
#include "tbeam_supreme_adapter.h"
#ifndef NODE_LABEL
#define NODE_LABEL "POWER"
#endif
#ifndef OLED_SDA
#define OLED_SDA 17
#endif
#ifndef OLED_SCL
#define OLED_SCL 18
#endif
#ifndef OLED_ADDR
#define OLED_ADDR 0x3C
#endif
static XPowersLibInterface *g_pmu = nullptr;
static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE);
static const uint32_t kBlinkIntervalMs = 500;
static const uint32_t kStatusIntervalMs = 1000;
static const uint32_t kSerialIntervalMs = 2000;
static const uint32_t kStartupDisplayMs = 120000;
static bool g_ledOn = false;
static uint32_t g_lastBlinkMs = 0;
static uint32_t g_lastStatusMs = 0;
static uint32_t g_lastSerialMs = 0;
static uint32_t g_bootMs = 0;
static void oledShow(const char *l1,
const char *l2 = nullptr,
const char *l3 = nullptr,
const char *l4 = nullptr,
const char *l5 = nullptr)
{
g_oled.clearBuffer();
g_oled.setFont(u8g2_font_5x8_tf);
if (l1) g_oled.drawUTF8(0, 12, l1);
if (l2) g_oled.drawUTF8(0, 24, l2);
if (l3) g_oled.drawUTF8(0, 36, l3);
if (l4) g_oled.drawUTF8(0, 48, l4);
if (l5) g_oled.drawUTF8(0, 60, l5);
g_oled.sendBuffer();
}
static void setChargeLed(bool on)
{
if (!g_pmu) return;
g_pmu->setChargingLedMode(on ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}
static void setupChargingDefaults()
{
if (!g_pmu) return;
g_pmu->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
g_pmu->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
}
static const char *powerState(bool batteryPresent, bool usbPresent, bool fullyCharged, bool chargingNow)
{
if (!batteryPresent) return "NO BATTERY";
if (!usbPresent) return "USB NOT PRESENT";
if (fullyCharged) return "FULL";
if (chargingNow) return "CHARGING";
return "IDLE";
}
static void formatDisplayTime(char *out, size_t outSize)
{
const time_t now = time(nullptr);
if (now > 1700000000) {
struct tm tmNow;
localtime_r(&now, &tmNow);
snprintf(out, outSize, "Time: %02d:%02d:%02d", tmNow.tm_hour, tmNow.tm_min, tmNow.tm_sec);
return;
}
uint32_t sec = millis() / 1000;
uint32_t hh = sec / 3600;
uint32_t mm = (sec % 3600) / 60;
uint32_t ss = sec % 60;
snprintf(out, outSize, "Uptime: %02lu:%02lu:%02lu", (unsigned long)hh, (unsigned long)mm, (unsigned long)ss);
}
void setup()
{
g_bootMs = millis();
Serial.begin(115200);
delay(1200);
Serial.println("Exercise 14_Power boot");
Wire.begin(OLED_SDA, OLED_SCL);
g_oled.setI2CAddress(OLED_ADDR << 1);
g_oled.begin();
oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "Booting...");
if (!tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial)) {
Serial.println("ERROR: PMU init failed");
oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "PMU init FAILED");
return;
}
setupChargingDefaults();
setChargeLed(false);
Serial.println("PMU init OK");
oledShow("Exercise 14 Power", "Node: " NODE_LABEL, "PMU ready");
}
void loop()
{
if (!g_pmu) {
delay(1000);
return;
}
const bool batteryPresent = g_pmu->isBatteryConnect();
const bool usbPresent = g_pmu->isVbusIn();
const bool chargingNow = g_pmu->isCharging();
const int battPercent = g_pmu->getBatteryPercent();
const float battV = g_pmu->getBattVoltage() / 1000.0f;
const bool fullyCharged = batteryPresent && battPercent >= 100;
const bool shouldCharge = batteryPresent && usbPresent && !fullyCharged;
if (shouldCharge) {
if (millis() - g_lastBlinkMs >= kBlinkIntervalMs) {
g_lastBlinkMs = millis();
g_ledOn = !g_ledOn;
setChargeLed(g_ledOn);
}
} else {
g_ledOn = false;
setChargeLed(false);
}
if (millis() - g_lastStatusMs >= kStatusIntervalMs) {
g_lastStatusMs = millis();
char l1[32];
char l2[32];
char l3[32];
char l4[32];
char l5[32];
const char *state = powerState(batteryPresent, usbPresent, fullyCharged, chargingNow);
const bool startupWindow = (millis() - g_bootMs) < kStartupDisplayMs;
if (startupWindow) {
snprintf(l1, sizeof(l1), "Exercise 14 Power");
} else {
snprintf(l1, sizeof(l1), "Power Monitor");
}
snprintf(l2, sizeof(l2), "Node: %s", NODE_LABEL);
formatDisplayTime(l3, sizeof(l3));
snprintf(l4, sizeof(l4), "State: %s", state);
if (battPercent >= 0) {
snprintf(l5, sizeof(l5), "VBAT:%.3fV %d%%", battV, battPercent);
} else {
snprintf(l5, sizeof(l5), "VBAT:%.3fV pct:?", battV);
}
oledShow(l1, l2, l3, l4, l5);
}
if (millis() - g_lastSerialMs >= kSerialIntervalMs) {
g_lastSerialMs = millis();
Serial.printf("node=%s usb=%u batt=%u charging=%u full=%u led=%u vbatt=%.3fV pct=%d\r\n",
NODE_LABEL,
usbPresent ? 1 : 0,
batteryPresent ? 1 : 0,
chargingNow ? 1 : 0,
fullyCharged ? 1 : 0,
g_ledOn ? 1 : 0,
battV,
battPercent);
}
delay(20);
}