Modified FiveTalk with GPS coordinates, Power has OLED display at outset
This commit is contained in:
parent
61cf7e5191
commit
38b80f97e5
4 changed files with 423 additions and 4 deletions
|
|
@ -120,14 +120,21 @@ struct DateTime {
|
|||
struct GpsState {
|
||||
bool sawAnySentence = false;
|
||||
bool hasValidUtc = false;
|
||||
bool hasValidPosition = false;
|
||||
bool hasValidAltitude = false;
|
||||
uint8_t satsUsed = 0;
|
||||
uint8_t satsInView = 0;
|
||||
uint32_t lastUtcMs = 0;
|
||||
DateTime utc{};
|
||||
double latitudeDeg = 0.0;
|
||||
double longitudeDeg = 0.0;
|
||||
float altitudeM = 0.0f;
|
||||
};
|
||||
|
||||
static GpsState g_gps;
|
||||
|
||||
static void parsePayloadCoords(const char* msg, char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen);
|
||||
|
||||
enum class AppPhase : uint8_t {
|
||||
WAIT_SD = 0,
|
||||
WAIT_DISCIPLINE,
|
||||
|
|
@ -320,11 +327,46 @@ static bool parseUInt2(const char* s, uint8_t& out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool parseNmeaCoordToDecimal(const char* raw, const char* hemi, bool isLat, double& outDeg) {
|
||||
if (!raw || !hemi || raw[0] == '\0' || hemi[0] == '\0') return false;
|
||||
|
||||
int degDigits = isLat ? 2 : 3;
|
||||
size_t n = strlen(raw);
|
||||
if (n <= (size_t)degDigits + 2) return false;
|
||||
|
||||
for (int i = 0; i < degDigits; ++i) {
|
||||
if (!isdigit((unsigned char)raw[i])) return false;
|
||||
}
|
||||
|
||||
char degBuf[4] = {0};
|
||||
memcpy(degBuf, raw, degDigits);
|
||||
int deg = atoi(degBuf);
|
||||
|
||||
const char* minPtr = raw + degDigits;
|
||||
double minutes = atof(minPtr);
|
||||
if (minutes < 0.0 || minutes >= 60.0) return false;
|
||||
|
||||
double dec = (double)deg + (minutes / 60.0);
|
||||
char h = (char)toupper((unsigned char)hemi[0]);
|
||||
if (h == 'S' || h == 'W') {
|
||||
dec = -dec;
|
||||
} else if (h != 'N' && h != 'E') {
|
||||
return false;
|
||||
}
|
||||
|
||||
outDeg = dec;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void parseRmc(char* fields[], int count) {
|
||||
if (count <= 9) return;
|
||||
|
||||
const char* utc = fields[1];
|
||||
const char* status = fields[2];
|
||||
const char* latRaw = (count > 3) ? fields[3] : nullptr;
|
||||
const char* latHem = (count > 4) ? fields[4] : nullptr;
|
||||
const char* lonRaw = (count > 5) ? fields[5] : nullptr;
|
||||
const char* lonHem = (count > 6) ? fields[6] : nullptr;
|
||||
const char* date = fields[9];
|
||||
|
||||
if (!status || status[0] != 'A') return;
|
||||
|
|
@ -343,12 +385,39 @@ static void parseRmc(char* fields[], int count) {
|
|||
g_gps.utc.year = (uint16_t)(2000U + yy);
|
||||
g_gps.hasValidUtc = true;
|
||||
g_gps.lastUtcMs = millis();
|
||||
|
||||
double lat = 0.0;
|
||||
double lon = 0.0;
|
||||
if (parseNmeaCoordToDecimal(latRaw, latHem, true, lat) &&
|
||||
parseNmeaCoordToDecimal(lonRaw, lonHem, false, lon)) {
|
||||
g_gps.latitudeDeg = lat;
|
||||
g_gps.longitudeDeg = lon;
|
||||
g_gps.hasValidPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void parseGga(char* fields[], int count) {
|
||||
if (count <= 7) return;
|
||||
const char* latRaw = (count > 2) ? fields[2] : nullptr;
|
||||
const char* latHem = (count > 3) ? fields[3] : nullptr;
|
||||
const char* lonRaw = (count > 4) ? fields[4] : nullptr;
|
||||
const char* lonHem = (count > 5) ? fields[5] : nullptr;
|
||||
int sats = atoi(fields[7]);
|
||||
if (sats >= 0 && sats <= 255) g_gps.satsUsed = (uint8_t)sats;
|
||||
|
||||
if (count > 9 && fields[9] && fields[9][0] != '\0') {
|
||||
g_gps.altitudeM = (float)atof(fields[9]);
|
||||
g_gps.hasValidAltitude = true;
|
||||
}
|
||||
|
||||
double lat = 0.0;
|
||||
double lon = 0.0;
|
||||
if (parseNmeaCoordToDecimal(latRaw, latHem, true, lat) &&
|
||||
parseNmeaCoordToDecimal(lonRaw, lonHem, false, lon)) {
|
||||
g_gps.latitudeDeg = lat;
|
||||
g_gps.longitudeDeg = lon;
|
||||
g_gps.hasValidPosition = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void parseGsv(char* fields[], int count) {
|
||||
|
|
@ -632,6 +701,20 @@ static bool openSessionLogs() {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void gpsFieldStrings(char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen) {
|
||||
if (latOut && latLen > 0) latOut[0] = '\0';
|
||||
if (lonOut && lonLen > 0) lonOut[0] = '\0';
|
||||
if (altOut && altLen > 0) altOut[0] = '\0';
|
||||
|
||||
if (g_gps.hasValidPosition) {
|
||||
if (latOut && latLen > 0) snprintf(latOut, latLen, "%.6f", g_gps.latitudeDeg);
|
||||
if (lonOut && lonLen > 0) snprintf(lonOut, lonLen, "%.6f", g_gps.longitudeDeg);
|
||||
}
|
||||
if (g_gps.hasValidAltitude) {
|
||||
if (altOut && altLen > 0) snprintf(altOut, altLen, "%.2f", g_gps.altitudeM);
|
||||
}
|
||||
}
|
||||
|
||||
static void writeSentLog(int64_t epoch, const DateTime& dt) {
|
||||
if (!g_sessionReady || !g_sentFile) return;
|
||||
|
||||
|
|
@ -642,11 +725,17 @@ static void writeSentLog(int64_t epoch, const DateTime& dt) {
|
|||
char human[32];
|
||||
formatUtcHuman(dt, human, sizeof(human));
|
||||
|
||||
g_sentFile.printf("epoch=%lld\tutc=%s\tunit=%s\tmsg=%s\ttx_count=%lu\tbatt_present=%u\tbatt_v=%.3f\n",
|
||||
char lat[24], lon[24], alt[24];
|
||||
gpsFieldStrings(lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
|
||||
|
||||
g_sentFile.printf("epoch=%lld\tutc=%s\tunit=%s\tmsg=%s\tlat=%s\tlon=%s\talt_m=%s\ttx_count=%lu\tbatt_present=%u\tbatt_v=%.3f\n",
|
||||
(long long)epoch,
|
||||
human,
|
||||
NODE_SHORT,
|
||||
NODE_SHORT,
|
||||
lat,
|
||||
lon,
|
||||
alt,
|
||||
(unsigned long)g_txCount,
|
||||
battPresent ? 1U : 0U,
|
||||
battV);
|
||||
|
|
@ -663,11 +752,17 @@ static void writeRecvLog(int64_t epoch, const DateTime& dt, const char* msg, flo
|
|||
char human[32];
|
||||
formatUtcHuman(dt, human, sizeof(human));
|
||||
|
||||
g_recvFile.printf("epoch=%lld\tutc=%s\tunit=%s\trx_msg=%s\trssi=%.1f\tsnr=%.1f\tbatt_present=%u\tbatt_v=%.3f\n",
|
||||
char lat[24], lon[24], alt[24];
|
||||
parsePayloadCoords(msg, lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
|
||||
|
||||
g_recvFile.printf("epoch=%lld\tutc=%s\tunit=%s\trx_msg=%s\trx_lat=%s\trx_lon=%s\trx_alt_m=%s\trssi=%.1f\tsnr=%.1f\tbatt_present=%u\tbatt_v=%.3f\n",
|
||||
(long long)epoch,
|
||||
human,
|
||||
NODE_SHORT,
|
||||
msg ? msg : "",
|
||||
lat,
|
||||
lon,
|
||||
alt,
|
||||
rssi,
|
||||
snr,
|
||||
battPresent ? 1U : 0U,
|
||||
|
|
@ -675,6 +770,41 @@ static void writeRecvLog(int64_t epoch, const DateTime& dt, const char* msg, flo
|
|||
g_recvFile.flush();
|
||||
}
|
||||
|
||||
static void buildTxPayload(char* out, size_t outLen) {
|
||||
if (!out || outLen == 0) return;
|
||||
out[0] = '\0';
|
||||
|
||||
char lat[24], lon[24], alt[24];
|
||||
gpsFieldStrings(lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
|
||||
snprintf(out, outLen, "%s,%s,%s,%s", NODE_SHORT, lat, lon, alt);
|
||||
}
|
||||
|
||||
static void parsePayloadCoords(const char* msg, char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen) {
|
||||
if (latOut && latLen > 0) latOut[0] = '\0';
|
||||
if (lonOut && lonLen > 0) lonOut[0] = '\0';
|
||||
if (altOut && altLen > 0) altOut[0] = '\0';
|
||||
if (!msg || msg[0] == '\0') return;
|
||||
|
||||
char buf[128];
|
||||
size_t n = strlen(msg);
|
||||
if (n >= sizeof(buf)) n = sizeof(buf) - 1;
|
||||
memcpy(buf, msg, n);
|
||||
buf[n] = '\0';
|
||||
|
||||
char* saveptr = nullptr;
|
||||
char* token = strtok_r(buf, ",", &saveptr); // unit label
|
||||
(void)token;
|
||||
|
||||
token = strtok_r(nullptr, ",", &saveptr); // lat
|
||||
if (token && latOut && latLen > 0) snprintf(latOut, latLen, "%s", token);
|
||||
|
||||
token = strtok_r(nullptr, ",", &saveptr); // lon
|
||||
if (token && lonOut && lonLen > 0) snprintf(lonOut, lonLen, "%s", token);
|
||||
|
||||
token = strtok_r(nullptr, ",", &saveptr); // alt
|
||||
if (token && altOut && altLen > 0) snprintf(altOut, altLen, "%s", token);
|
||||
}
|
||||
|
||||
static void onLoRaDio1Rise() {
|
||||
g_rxFlag = true;
|
||||
}
|
||||
|
|
@ -725,11 +855,13 @@ static void runTxScheduler() {
|
|||
g_rxFlag = false;
|
||||
g_radio.clearDio1Action();
|
||||
|
||||
int tx = g_radio.transmit(NODE_SHORT);
|
||||
char payload[96];
|
||||
buildTxPayload(payload, sizeof(payload));
|
||||
int tx = g_radio.transmit(payload);
|
||||
if (tx == RADIOLIB_ERR_NONE) {
|
||||
g_txCount++;
|
||||
writeSentLog(epoch, now);
|
||||
logf("TX %s count=%lu", NODE_SHORT, (unsigned long)g_txCount);
|
||||
logf("TX %s count=%lu payload=%s", NODE_SHORT, (unsigned long)g_txCount, payload);
|
||||
} else {
|
||||
logf("TX failed code=%d", tx);
|
||||
}
|
||||
|
|
|
|||
30
exercises/14_Power/README.md
Normal file
30
exercises/14_Power/README.md
Normal 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
|
||||
```
|
||||
65
exercises/14_Power/platformio.ini
Normal file
65
exercises/14_Power/platformio.ini
Normal 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\"
|
||||
192
exercises/14_Power/src/main.cpp
Normal file
192
exercises/14_Power/src/main.cpp
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue