diff --git a/exercises/00_usb_radio_check/src/main.cpp b/exercises/00_usb_radio_check/src/main.cpp index 108ba16..9b73f00 100644 --- a/exercises/00_usb_radio_check/src/main.cpp +++ b/exercises/00_usb_radio_check/src/main.cpp @@ -18,11 +18,27 @@ #define LORA_CR 5 #endif +/** + * This sketch is intended to be used as a quick test of the LoRa radio on the + * T-Beam Supreme board, to verify that the radio is functional and can be used + * in a USB-connected application. + * It will attempt to initialize the radio, and then repeatedly transmit a test + * frame and call startReceive() to verify that the radio is responsive. + * Note that this sketch is not intended to be a full test of the radio's + * functionality, but rather a quick check that the radio can be initialized + * and used without errors. If you are seeing -706 or -707 errors, it likely means + * that the radio is not starting up correctly, which can be caused by incorrect + * pin connections or power issues. If you are seeing other errors, it may indicate + * a different issue with the radio or the code. + */ // SX1262 on T-Beam Supreme (tbeam-s3-core pinout) SX1262 radio = new Module(LORA_CS, LORA_DIO1, LORA_RESET, LORA_BUSY); int state; // = radio.begin(915.0, 125.0, 7, 5, 0x12, 14); +/* + @brief Setup function. Initializes the radio and prints the result to the serial console. +*/ void setup() { Serial.begin(115200); delay(2000); // give USB time to enumerate @@ -42,7 +58,10 @@ void setup() { } - +/* + @brief Loop function. Transmits a test frame and calls startReceive() to verify that + the radio is responsive. Repeats every second. +*/ void loop() { static uint32_t counter = 0; Serial.printf("alive %lu\n", counter++); diff --git a/exercises/09_GPS_Time/src/main.cpp b/exercises/09_GPS_Time/src/main.cpp index c8fd2a7..6556989 100644 --- a/exercises/09_GPS_Time/src/main.cpp +++ b/exercises/09_GPS_Time/src/main.cpp @@ -33,12 +33,12 @@ static const uint32_t kSerialDelayMs = 5000; static const uint32_t kMinuteMs = 60000; static const uint32_t kGpsDiagnosticLogMs = 15000; -static const char* kGpsLogDir = "/gpsdiag"; -static const char* kGpsLogPath = "/gpsdiag/current.log"; -static const char* kBuildDate = __DATE__; -static const char* kBuildTime = __TIME__; +static const char *kGpsLogDir = "/gpsdiag"; +static const char *kGpsLogPath = "/gpsdiag/current.log"; +static const char *kBuildDate = __DATE__; +static const char *kBuildTime = __TIME__; -static XPowersLibInterface* g_pmu = nullptr; +static XPowersLibInterface *g_pmu = nullptr; static StartupSdManager g_sd(Serial); static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, /* reset=*/U8X8_PIN_NONE); static HardwareSerial g_gpsSerial(1); @@ -74,7 +74,8 @@ static uint8_t g_rawLogGsvCount = 0; static uint8_t g_rawLogRmcCount = 0; static uint8_t g_rawLogPubxCount = 0; -enum class GpsModuleKind : uint8_t { +enum class GpsModuleKind : uint8_t +{ UNKNOWN = 0, L76K, UBLOX @@ -88,7 +89,8 @@ static const GpsModuleKind kExpectedGpsModule = GpsModuleKind::L76K; static const GpsModuleKind kExpectedGpsModule = GpsModuleKind::UNKNOWN; #endif -struct RtcDateTime { +struct RtcDateTime +{ uint16_t year; uint8_t month; uint8_t day; @@ -97,7 +99,8 @@ struct RtcDateTime { uint8_t second; }; -struct GpsState { +struct GpsState +{ GpsModuleKind module = kExpectedGpsModule; bool sawAnySentence = false; @@ -131,22 +134,28 @@ static uint8_t displayedSatsInView(); static bool displayHasFreshUtc(); static String formatRtcNow(); -static bool ensureGpsLogDirectory() { - if (!g_spiffsReady) { +static bool ensureGpsLogDirectory() +{ + if (!g_spiffsReady) + { return false; } - if (SPIFFS.exists(kGpsLogDir)) { + if (SPIFFS.exists(kGpsLogDir)) + { return true; } return SPIFFS.mkdir(kGpsLogDir); } -static bool gpsDiagAppendLine(const char* line) { - if (!g_spiffsReady || !line) { +static bool gpsDiagAppendLine(const char *line) +{ + if (!g_spiffsReady || !line) + { return false; } File file = SPIFFS.open(kGpsLogPath, FILE_APPEND); - if (!file) { + if (!file) + { return false; } file.print(line); @@ -155,14 +164,17 @@ static bool gpsDiagAppendLine(const char* line) { return true; } -static void formatGpsSnapshot(char* out, size_t outSize, const char* event) { - if (!out || outSize == 0) { +static void formatGpsSnapshot(char *out, size_t outSize, const char *event) +{ + if (!out || outSize == 0) + { return; } const uint8_t sats = bestSatelliteCount(); - const char* ev = event ? event : "sample"; - if (g_gps.hasValidUtc) { + const char *ev = event ? event : "sample"; + if (g_gps.hasValidUtc) + { snprintf(out, outSize, "ms=%lu event=%s module=%s nmea=%s sats_used=%u sats_view=%u sats_best=%u utc=%04u-%02u-%02uT%02u:%02u:%02u rx=%d tx=%d baud=%lu", @@ -182,7 +194,9 @@ static void formatGpsSnapshot(char* out, size_t outSize, const char* event) { g_gpsRxPin, g_gpsTxPin, (unsigned long)g_gpsBaud); - } else { + } + else + { String rtc = formatRtcNow(); snprintf(out, outSize, @@ -201,37 +215,51 @@ static void formatGpsSnapshot(char* out, size_t outSize, const char* event) { } } -static void appendGpsSnapshot(const char* event) { +static void appendGpsSnapshot(const char *event) +{ char line[256]; formatGpsSnapshot(line, sizeof(line), event); (void)gpsDiagAppendLine(line); } -static String buildStampShort() { +static String buildStampShort() +{ char buf[32]; snprintf(buf, sizeof(buf), "%s %.5s", kBuildDate, kBuildTime); return String(buf); } -static void maybeLogRawSentence(const char* type, const char* sentence) { - if (!type || !sentence || !g_spiffsReady) { +static void maybeLogRawSentence(const char *type, const char *sentence) +{ + if (!type || !sentence || !g_spiffsReady) + { return; } - uint8_t* counter = nullptr; - if (strcmp(type, "GGA") == 0) { + uint8_t *counter = nullptr; + if (strcmp(type, "GGA") == 0) + { counter = &g_rawLogGgaCount; - } else if (strcmp(type, "GSA") == 0) { + } + else if (strcmp(type, "GSA") == 0) + { counter = &g_rawLogGsaCount; - } else if (strcmp(type, "GSV") == 0) { + } + else if (strcmp(type, "GSV") == 0) + { counter = &g_rawLogGsvCount; - } else if (strcmp(type, "RMC") == 0) { + } + else if (strcmp(type, "RMC") == 0) + { counter = &g_rawLogRmcCount; - } else if (strcmp(type, "PUBX") == 0) { + } + else if (strcmp(type, "PUBX") == 0) + { counter = &g_rawLogPubxCount; } - if (!counter || *counter >= 12) { + if (!counter || *counter >= 12) + { return; } (*counter)++; @@ -247,17 +275,21 @@ static void maybeLogRawSentence(const char* type, const char* sentence) { (void)gpsDiagAppendLine(line); } -static void clearGpsSerialInput() { +static void clearGpsSerialInput() +{ g_gpsLineLen = 0; - while (g_gpsSerial.available() > 0) { + while (g_gpsSerial.available() > 0) + { (void)g_gpsSerial.read(); } } -static void ubxChecksum(uint8_t* message, size_t length) { +static void ubxChecksum(uint8_t *message, size_t length) +{ uint8_t ckA = 0; uint8_t ckB = 0; - for (size_t i = 2; i < length - 2; ++i) { + for (size_t i = 2; i < length - 2; ++i) + { ckA = (uint8_t)((ckA + message[i]) & 0xFF); ckB = (uint8_t)((ckB + ckA) & 0xFF); } @@ -265,13 +297,15 @@ static void ubxChecksum(uint8_t* message, size_t length) { message[length - 1] = ckB; } -static size_t makeUbxPacket(uint8_t* out, +static size_t makeUbxPacket(uint8_t *out, size_t outSize, uint8_t classId, uint8_t msgId, - const uint8_t* payload, - uint16_t payloadSize) { - if (!out || outSize < (size_t)payloadSize + 8U) { + const uint8_t *payload, + uint16_t payloadSize) +{ + if (!out || outSize < (size_t)payloadSize + 8U) + { return 0; } out[0] = 0xB5; @@ -280,7 +314,8 @@ static size_t makeUbxPacket(uint8_t* out, out[3] = msgId; out[4] = (uint8_t)(payloadSize & 0xFF); out[5] = (uint8_t)((payloadSize >> 8) & 0xFF); - for (uint16_t i = 0; i < payloadSize; ++i) { + for (uint16_t i = 0; i < payloadSize; ++i) + { out[6 + i] = payload ? payload[i] : 0; } out[6 + payloadSize] = 0; @@ -289,99 +324,116 @@ static size_t makeUbxPacket(uint8_t* out, return (size_t)payloadSize + 8U; } -static bool waitForUbxAck(uint8_t classId, uint8_t msgId, uint32_t waitMs) { +static bool waitForUbxAck(uint8_t classId, uint8_t msgId, uint32_t waitMs) +{ uint8_t ack[10] = {0xB5, 0x62, 0x05, 0x01, 0x02, 0x00, classId, msgId, 0x00, 0x00}; ubxChecksum(ack, sizeof(ack)); uint8_t ackPos = 0; uint32_t deadline = millis() + waitMs; - while ((int32_t)(deadline - millis()) > 0) { - if (g_gpsSerial.available() <= 0) { + while ((int32_t)(deadline - millis()) > 0) + { + if (g_gpsSerial.available() <= 0) + { delay(2); continue; } uint8_t b = (uint8_t)g_gpsSerial.read(); - if (b == ack[ackPos]) { + if (b == ack[ackPos]) + { ackPos++; - if (ackPos == sizeof(ack)) { + if (ackPos == sizeof(ack)) + { return true; } - } else { + } + else + { ackPos = (b == ack[0]) ? 1 : 0; } } return false; } -static int waitForUbxPayload(uint8_t* buffer, +static int waitForUbxPayload(uint8_t *buffer, uint16_t bufferSize, uint8_t classId, uint8_t msgId, - uint32_t waitMs) { + uint32_t waitMs) +{ uint16_t framePos = 0; uint16_t needRead = 0; uint32_t deadline = millis() + waitMs; - while ((int32_t)(deadline - millis()) > 0) { - if (g_gpsSerial.available() <= 0) { + while ((int32_t)(deadline - millis()) > 0) + { + if (g_gpsSerial.available() <= 0) + { delay(2); continue; } int c = g_gpsSerial.read(); - switch (framePos) { - case 0: - framePos = (c == 0xB5) ? 1 : 0; - break; - case 1: - framePos = (c == 0x62) ? 2 : 0; - break; - case 2: - framePos = (c == classId) ? 3 : 0; - break; - case 3: - framePos = (c == msgId) ? 4 : 0; - break; - case 4: - needRead = (uint16_t)c; - framePos = 5; - break; - case 5: - needRead |= (uint16_t)(c << 8); - if (needRead == 0 || needRead >= bufferSize) { - framePos = 0; - break; - } - if (g_gpsSerial.readBytes(buffer, needRead) != needRead) { - framePos = 0; - break; - } - if (g_gpsSerial.available() >= 2) { - (void)g_gpsSerial.read(); - (void)g_gpsSerial.read(); - } - return (int)needRead; - default: + switch (framePos) + { + case 0: + framePos = (c == 0xB5) ? 1 : 0; + break; + case 1: + framePos = (c == 0x62) ? 2 : 0; + break; + case 2: + framePos = (c == classId) ? 3 : 0; + break; + case 3: + framePos = (c == msgId) ? 4 : 0; + break; + case 4: + needRead = (uint16_t)c; + framePos = 5; + break; + case 5: + needRead |= (uint16_t)(c << 8); + if (needRead == 0 || needRead >= bufferSize) + { framePos = 0; break; + } + if (g_gpsSerial.readBytes(buffer, needRead) != needRead) + { + framePos = 0; + break; + } + if (g_gpsSerial.available() >= 2) + { + (void)g_gpsSerial.read(); + (void)g_gpsSerial.read(); + } + return (int)needRead; + default: + framePos = 0; + break; } } return 0; } -static bool detectUbloxM10() { +static bool detectUbloxM10() +{ uint8_t packet[8]; uint8_t payload[256] = {0}; size_t len = makeUbxPacket(packet, sizeof(packet), 0x0A, 0x04, nullptr, 0); - if (len == 0) { + if (len == 0) + { return false; } clearGpsSerialInput(); g_gpsSerial.write(packet, len); int payloadLen = waitForUbxPayload(payload, sizeof(payload), 0x0A, 0x04, 1200); - if (payloadLen < 40) { + if (payloadLen < 40) + { appendGpsSnapshot("ubx_monver_timeout"); return false; } @@ -392,16 +444,20 @@ static bool detectUbloxM10() { snprintf(line, sizeof(line), "ms=%lu event=ubx_monver hw=%s", (unsigned long)millis(), hwVersion); (void)gpsDiagAppendLine(line); - if (strncmp(hwVersion, "000A0000", 8) == 0) { + if (strncmp(hwVersion, "000A0000", 8) == 0) + { return true; } - for (int pos = 40; pos + 30 <= payloadLen; pos += 30) { - if (strncmp((const char*)(payload + pos), "PROTVER=", 8) == 0) { - int prot = atoi((const char*)(payload + pos + 8)); + for (int pos = 40; pos + 30 <= payloadLen; pos += 30) + { + if (strncmp((const char *)(payload + pos), "PROTVER=", 8) == 0) + { + int prot = atoi((const char *)(payload + pos + 8)); snprintf(line, sizeof(line), "ms=%lu event=ubx_monver prot=%d", (unsigned long)millis(), prot); (void)gpsDiagAppendLine(line); - if (prot >= 27) { + if (prot >= 27) + { return true; } } @@ -412,13 +468,15 @@ static bool detectUbloxM10() { static bool sendUbxValset(uint8_t classId, uint8_t msgId, - const uint8_t* payload, + const uint8_t *payload, uint16_t payloadLen, uint32_t ackMs, - const char* eventName) { + const char *eventName) +{ uint8_t packet[96]; size_t len = makeUbxPacket(packet, sizeof(packet), classId, msgId, payload, payloadLen); - if (len == 0) { + if (len == 0) + { return false; } clearGpsSerialInput(); @@ -436,7 +494,8 @@ static bool sendUbxValset(uint8_t classId, return ok; } -static bool configureUbloxM10() { +static bool configureUbloxM10() +{ static const uint8_t kValsetDisableTxtRam[] = {0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03}; static const uint8_t kValsetDisableTxtBbr[] = {0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03}; static const uint8_t kValsetEnableNmeaRam[] = {0x00, 0x01, 0x00, 0x00, 0xbb, 0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01}; @@ -453,15 +512,18 @@ static bool configureUbloxM10() { return ok; } -static void maybeConfigureUblox() { - if (g_ubloxConfigAttempted || kExpectedGpsModule != GpsModuleKind::UBLOX) { +static void maybeConfigureUblox() +{ + if (g_ubloxConfigAttempted || kExpectedGpsModule != GpsModuleKind::UBLOX) + { return; } g_ubloxConfigAttempted = true; appendGpsSnapshot("ubx_config_attempt"); g_ubloxIsM10 = detectUbloxM10(); - if (!g_ubloxIsM10) { + if (!g_ubloxIsM10) + { appendGpsSnapshot("ubx_non_m10_or_unknown"); return; } @@ -469,7 +531,8 @@ static void maybeConfigureUblox() { g_ubloxConfigured = configureUbloxM10(); } -static void logf(const char* fmt, ...) { +static void logf(const char *fmt, ...) +{ char msg[220]; va_list args; va_start(args, fmt); @@ -478,35 +541,45 @@ static void logf(const char* fmt, ...) { Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg); } -static void oledShowLines(const char* l1, - const char* l2 = nullptr, - const char* l3 = nullptr, - const char* l4 = nullptr, - const char* l5 = nullptr) { +static void oledShowLines(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); + 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 uint8_t fromBcd(uint8_t b) { +static uint8_t fromBcd(uint8_t b) +{ return ((b >> 4U) * 10U) + (b & 0x0FU); } -static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) { +static bool rtcRead(RtcDateTime &out, bool &lowVoltageFlag) +{ Wire1.beginTransmission(RTC_I2C_ADDR); Wire1.write(0x02); - if (Wire1.endTransmission(false) != 0) { + if (Wire1.endTransmission(false) != 0) + { return false; } const uint8_t need = 7; uint8_t got = Wire1.requestFrom((int)RTC_I2C_ADDR, (int)need); - if (got != need) { + if (got != need) + { return false; } @@ -514,7 +587,7 @@ static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) { uint8_t min = Wire1.read(); uint8_t hour = Wire1.read(); uint8_t day = Wire1.read(); - (void)Wire1.read(); // weekday + (void)Wire1.read(); // weekday uint8_t month = Wire1.read(); uint8_t year = Wire1.read(); @@ -530,10 +603,12 @@ static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) { return true; } -static String formatRtcNow() { +static String formatRtcNow() +{ RtcDateTime now{}; bool lowV = false; - if (!rtcRead(now, lowV)) { + if (!rtcRead(now, lowV)) + { return "RTC: read failed"; } @@ -551,99 +626,124 @@ static String formatRtcNow() { return String(buf); } -static String gpsModuleToString(GpsModuleKind kind) { - if (kind == GpsModuleKind::L76K) { +static String gpsModuleToString(GpsModuleKind kind) +{ + if (kind == GpsModuleKind::L76K) + { return "L76K"; } - if (kind == GpsModuleKind::UBLOX) { + if (kind == GpsModuleKind::UBLOX) + { return "UBLOX"; } return "Unknown"; } -static bool parseUInt2(const char* s, uint8_t& out) { - if (!s || !isdigit((unsigned char)s[0]) || !isdigit((unsigned char)s[1])) { +static bool parseUInt2(const char *s, uint8_t &out) +{ + if (!s || !isdigit((unsigned char)s[0]) || !isdigit((unsigned char)s[1])) + { return false; } out = (uint8_t)((s[0] - '0') * 10 + (s[1] - '0')); return true; } -static void detectModuleFromText(const char* text) { - if (!text || text[0] == '\0') { +static void detectModuleFromText(const char *text) +{ + if (!text || text[0] == '\0') + { return; } String t(text); t.toUpperCase(); - if (t.indexOf("L76K") >= 0) { + if (t.indexOf("L76K") >= 0) + { g_gps.module = GpsModuleKind::L76K; return; } - if (t.indexOf("QUECTEL") >= 0 || t.indexOf("2021-10-20") >= 0 || t.indexOf("2021/10/20") >= 0) { + if (t.indexOf("QUECTEL") >= 0 || t.indexOf("2021-10-20") >= 0 || t.indexOf("2021/10/20") >= 0) + { g_gps.module = GpsModuleKind::L76K; } } -static void parseGga(char* fields[], int count) { - if (count <= 7) { +static void parseGga(char *fields[], int count) +{ + if (count <= 7) + { return; } int sats = atoi(fields[7]); - if (sats >= 0 && sats <= 255) { + if (sats >= 0 && sats <= 255) + { g_gps.satsUsed = (uint8_t)sats; - if ((uint8_t)sats > g_gps.satsUsedWindowMax) { + if ((uint8_t)sats > g_gps.satsUsedWindowMax) + { g_gps.satsUsedWindowMax = (uint8_t)sats; } g_gps.satsUsedWindowMs = millis(); } } -static void parseGsv(char* fields[], int count) { - if (count <= 3) { +static void parseGsv(char *fields[], int count) +{ + if (count <= 3) + { return; } int sats = atoi(fields[3]); - if (sats >= 0 && sats <= 255) { + if (sats >= 0 && sats <= 255) + { g_gps.satsInView = (uint8_t)sats; - if ((uint8_t)sats > g_gps.satsInViewWindowMax) { + if ((uint8_t)sats > g_gps.satsInViewWindowMax) + { g_gps.satsInViewWindowMax = (uint8_t)sats; } g_gps.satsInViewWindowMs = millis(); } } -static void parseGsa(char* fields[], int count) { - if (count <= 3) { +static void parseGsa(char *fields[], int count) +{ + if (count <= 3) + { return; } } -static void parseRmc(char* fields[], int count) { - if (count <= 9) { +static void parseRmc(char *fields[], int count) +{ + if (count <= 9) + { return; } - const char* utc = fields[1]; - const char* status = fields[2]; - const char* date = fields[9]; + const char *utc = fields[1]; + const char *status = fields[2]; + const char *date = fields[9]; - if (!status || status[0] != 'A') { + if (!status || status[0] != 'A') + { return; } - if (!utc || strlen(utc) < 6 || !date || strlen(date) < 6) { + if (!utc || strlen(utc) < 6 || !date || strlen(date) < 6) + { return; } uint8_t hh = 0, mm = 0, ss = 0; uint8_t dd = 0, mo = 0, yy = 0; - if (!parseUInt2(utc + 0, hh) || !parseUInt2(utc + 2, mm) || !parseUInt2(utc + 4, ss)) { + if (!parseUInt2(utc + 0, hh) || !parseUInt2(utc + 2, mm) || !parseUInt2(utc + 4, ss)) + { return; } - if (!parseUInt2(date + 0, dd) || !parseUInt2(date + 2, mo) || !parseUInt2(date + 4, yy)) { + if (!parseUInt2(date + 0, dd) || !parseUInt2(date + 2, mo) || !parseUInt2(date + 4, yy)) + { return; } @@ -657,24 +757,30 @@ static void parseRmc(char* fields[], int count) { g_gps.utcFixMs = millis(); } -static void parseTxt(char* fields[], int count) { - if (count <= 4) { +static void parseTxt(char *fields[], int count) +{ + if (count <= 4) + { return; } detectModuleFromText(fields[4]); } -static int splitCsvPreserveEmpty(char* line, char* fields[], int maxFields) { - if (!line || !fields || maxFields <= 0) { +static int splitCsvPreserveEmpty(char *line, char *fields[], int maxFields) +{ + if (!line || !fields || maxFields <= 0) + { return 0; } int count = 0; - char* p = line; + char *p = line; fields[count++] = p; - while (*p && count < maxFields) { - if (*p == ',') { + while (*p && count < maxFields) + { + if (*p == ',') + { *p = '\0'; fields[count++] = p + 1; } @@ -684,8 +790,10 @@ static int splitCsvPreserveEmpty(char* line, char* fields[], int maxFields) { return count; } -static void processNmeaLine(char* line) { - if (!line || line[0] != '$') { +static void processNmeaLine(char *line) +{ + if (!line || line[0] != '$') + { return; } g_gps.sawAnySentence = true; @@ -693,51 +801,69 @@ static void processNmeaLine(char* line) { strncpy(rawLine, line, sizeof(rawLine) - 1); rawLine[sizeof(rawLine) - 1] = '\0'; - char* star = strchr(line, '*'); - if (star) { + char *star = strchr(line, '*'); + if (star) + { *star = '\0'; } - char* fields[24] = {0}; + char *fields[24] = {0}; int count = splitCsvPreserveEmpty(line, fields, 24); - if (count <= 0 || !fields[0]) { + if (count <= 0 || !fields[0]) + { return; } - const char* header = fields[0]; - if (strcmp(header, "$PUBX") == 0) { + const char *header = fields[0]; + if (strcmp(header, "$PUBX") == 0) + { g_gps.module = GpsModuleKind::UBLOX; maybeLogRawSentence("PUBX", rawLine); return; } size_t n = strlen(header); - if (n < 6) { + if (n < 6) + { return; } - const char* type = header + (n - 3); + const char *type = header + (n - 3); maybeLogRawSentence(type, rawLine); - if (strcmp(type, "GGA") == 0) { + if (strcmp(type, "GGA") == 0) + { parseGga(fields, count); - } else if (strcmp(type, "GSA") == 0) { + } + else if (strcmp(type, "GSA") == 0) + { parseGsa(fields, count); - } else if (strcmp(type, "GSV") == 0) { + } + else if (strcmp(type, "GSV") == 0) + { parseGsv(fields, count); - } else if (strcmp(type, "RMC") == 0) { + } + else if (strcmp(type, "RMC") == 0) + { parseRmc(fields, count); - } else if (strcmp(type, "TXT") == 0) { + } + else if (strcmp(type, "TXT") == 0) + { parseTxt(fields, count); } } -static void pollGpsSerial() { - while (g_gpsSerial.available() > 0) { +static void pollGpsSerial() +{ + while (g_gpsSerial.available() > 0) + { char c = (char)g_gpsSerial.read(); - if (c == '\r') { + if (c == '\r') + { continue; } - if (c == '\n') { - if (g_gpsLineLen > 0) { + if (c == '\n') + { + if (g_gpsLineLen > 0) + { g_gpsLine[g_gpsLineLen] = '\0'; processNmeaLine(g_gpsLine); g_gpsLineLen = 0; @@ -745,15 +871,19 @@ static void pollGpsSerial() { continue; } - if (g_gpsLineLen + 1 < sizeof(g_gpsLine)) { + if (g_gpsLineLen + 1 < sizeof(g_gpsLine)) + { g_gpsLine[g_gpsLineLen++] = c; - } else { + } + else + { g_gpsLineLen = 0; } } } -static void showGpsLogHelp() { +static void showGpsLogHelp() +{ Serial.println("Command list:"); Serial.println(" help - show command menu"); Serial.println(" stat - show current GPS log file info"); @@ -762,15 +892,18 @@ static void showGpsLogHelp() { Serial.println(" clear - erase current GPS log"); } -static void gpsLogStat() { +static void gpsLogStat() +{ Serial.printf("SPIFFS: %s\r\n", g_spiffsReady ? "ready" : "not ready"); Serial.printf("Path: %s\r\n", kGpsLogPath); - if (!g_spiffsReady || !SPIFFS.exists(kGpsLogPath)) { + if (!g_spiffsReady || !SPIFFS.exists(kGpsLogPath)) + { Serial.println("Current GPS log does not exist"); return; } File file = SPIFFS.open(kGpsLogPath, FILE_READ); - if (!file) { + if (!file) + { Serial.println("Unable to open current GPS log"); return; } @@ -782,51 +915,63 @@ static void gpsLogStat() { (unsigned)(SPIFFS.totalBytes() - SPIFFS.usedBytes())); } -static void gpsLogList() { - if (!g_spiffsReady) { +static void gpsLogList() +{ + if (!g_spiffsReady) + { Serial.println("SPIFFS not ready"); return; } File dir = SPIFFS.open(kGpsLogDir); - if (!dir || !dir.isDirectory()) { + if (!dir || !dir.isDirectory()) + { Serial.printf("Unable to open %s\r\n", kGpsLogDir); return; } Serial.printf("Files in %s:\r\n", kGpsLogDir); File file = dir.openNextFile(); - while (file) { + while (file) + { Serial.printf(" %s (%u bytes)\r\n", file.name(), (unsigned)file.size()); file = dir.openNextFile(); } } -static void gpsLogRead() { - if (!g_spiffsReady || !SPIFFS.exists(kGpsLogPath)) { +static void gpsLogRead() +{ + if (!g_spiffsReady || !SPIFFS.exists(kGpsLogPath)) + { Serial.println("Current GPS log is not available"); return; } File file = SPIFFS.open(kGpsLogPath, FILE_READ); - if (!file) { + if (!file) + { Serial.println("Unable to read current GPS log"); return; } Serial.printf("Reading %s:\r\n", kGpsLogPath); - while (file.available()) { + while (file.available()) + { Serial.write(file.read()); } - if (file.size() > 0) { + if (file.size() > 0) + { Serial.println(); } file.close(); } -static void gpsLogClear() { - if (!g_spiffsReady) { +static void gpsLogClear() +{ + if (!g_spiffsReady) + { Serial.println("SPIFFS not ready"); return; } File file = SPIFFS.open(kGpsLogPath, FILE_WRITE); - if (!file) { + if (!file) + { Serial.println("Unable to clear current GPS log"); return; } @@ -834,50 +979,72 @@ static void gpsLogClear() { Serial.printf("Cleared %s\r\n", kGpsLogPath); } // Process a command received on the serial console. -static void processSerialCommand(const char* line) { - if (!line || line[0] == '\0') { +static void processSerialCommand(const char *line) +{ + if (!line || line[0] == '\0') + { return; } // Echo the command back to the console for clarity and posterity. Serial.printf("-->%s\r\n", line); - if (strcasecmp(line, "help") == 0) { + if (strcasecmp(line, "help") == 0) + { showGpsLogHelp(); - } else if (strcasecmp(line, "stat") == 0) { + } + else if (strcasecmp(line, "stat") == 0) + { gpsLogStat(); - } else if (strcasecmp(line, "list") == 0) { + } + else if (strcasecmp(line, "list") == 0) + { gpsLogList(); - } else if (strcasecmp(line, "read") == 0) { + } + else if (strcasecmp(line, "read") == 0) + { gpsLogRead(); - } else if (strcasecmp(line, "clear") == 0) { + } + else if (strcasecmp(line, "clear") == 0) + { gpsLogClear(); - } else { + } + else + { Serial.println("Unknown command (help for list)"); } } -static void pollSerialConsole() { - while (Serial.available() > 0) { +static void pollSerialConsole() +{ + while (Serial.available() > 0) + { int c = Serial.read(); - if (c < 0) { + if (c < 0) + { continue; } - if (c == '\r' || c == '\n') { - if (g_serialLineLen > 0) { + if (c == '\r' || c == '\n') + { + if (g_serialLineLen > 0) + { g_serialLine[g_serialLineLen] = '\0'; processSerialCommand(g_serialLine); g_serialLineLen = 0; } continue; } - if (g_serialLineLen + 1 < sizeof(g_serialLine)) { + if (g_serialLineLen + 1 < sizeof(g_serialLine)) + { g_serialLine[g_serialLineLen++] = (char)c; - } else { + } + else + { g_serialLineLen = 0; } } } -static void startGpsUart(uint32_t baud, int rxPin, int txPin) { +static void startGpsUart(uint32_t baud, int rxPin, int txPin) +{ g_gpsSerial.end(); delay(20); g_gpsSerial.setRxBufferSize(1024); @@ -887,15 +1054,19 @@ static void startGpsUart(uint32_t baud, int rxPin, int txPin) { g_gpsTxPin = txPin; } -static bool collectGpsTraffic(uint32_t windowMs, bool updateSd) { +static bool collectGpsTraffic(uint32_t windowMs, bool updateSd) +{ uint32_t start = millis(); bool sawBytes = false; - while ((uint32_t)(millis() - start) < windowMs) { - if (g_gpsSerial.available() > 0) { + while ((uint32_t)(millis() - start) < windowMs) + { + if (g_gpsSerial.available() > 0) + { sawBytes = true; } pollGpsSerial(); - if (updateSd) { + if (updateSd) + { g_sd.update(); } delay(2); @@ -903,10 +1074,12 @@ static bool collectGpsTraffic(uint32_t windowMs, bool updateSd) { return sawBytes || g_gps.sawAnySentence; } -static bool probeGpsAtBaud(uint32_t baud, int rxPin, int txPin) { +static bool probeGpsAtBaud(uint32_t baud, int rxPin, int txPin) +{ startGpsUart(baud, rxPin, txPin); logf("Probing GPS at %lu baud on RX=%d TX=%d...", (unsigned long)baud, rxPin, txPin); - if (collectGpsTraffic(700, true)) { + if (collectGpsTraffic(700, true)) + { return true; } @@ -921,23 +1094,28 @@ static bool probeGpsAtBaud(uint32_t baud, int rxPin, int txPin) { return collectGpsTraffic(1200, true); } -static void initialGpsProbe() { +static void initialGpsProbe() +{ const uint32_t bauds[] = {GPS_BAUD, 115200, 38400, 57600, 19200}; int pinCandidates[2][2] = { {GPS_RX_PIN, GPS_TX_PIN}, - {34, 12}, // Legacy T-Beam UBLOX mapping. + {34, 12}, // Legacy T-Beam UBLOX mapping. }; size_t pinCount = 1; if (kExpectedGpsModule == GpsModuleKind::UBLOX && - !(GPS_RX_PIN == 34 && GPS_TX_PIN == 12)) { + !(GPS_RX_PIN == 34 && GPS_TX_PIN == 12)) + { pinCount = 2; } - for (size_t p = 0; p < pinCount; ++p) { + for (size_t p = 0; p < pinCount; ++p) + { int rxPin = pinCandidates[p][0]; int txPin = pinCandidates[p][1]; - for (size_t i = 0; i < sizeof(bauds) / sizeof(bauds[0]); ++i) { - if (probeGpsAtBaud(bauds[i], rxPin, txPin)) { + for (size_t i = 0; i < sizeof(bauds) / sizeof(bauds[0]); ++i) + { + if (probeGpsAtBaud(bauds[i], rxPin, txPin)) + { logf("GPS traffic detected at %lu baud on RX=%d TX=%d", (unsigned long)g_gpsBaud, g_gpsRxPin, g_gpsTxPin); return; @@ -947,24 +1125,30 @@ static void initialGpsProbe() { logf("No GPS traffic detected during startup probe"); } -static uint32_t startupProbeWindowMs() { +static uint32_t startupProbeWindowMs() +{ return (kExpectedGpsModule == GpsModuleKind::UBLOX) ? 45000U : 20000U; } -static GpsModuleKind activeGpsModule() { - if (g_gps.module != GpsModuleKind::UNKNOWN) { +static GpsModuleKind activeGpsModule() +{ + if (g_gps.module != GpsModuleKind::UNKNOWN) + { return g_gps.module; } return kExpectedGpsModule; } -static uint8_t bestSatelliteCount() { +static uint8_t bestSatelliteCount() +{ uint32_t now = millis(); - if ((uint32_t)(now - g_gps.satsUsedWindowMs) > kSatelliteWindowMs) { + if ((uint32_t)(now - g_gps.satsUsedWindowMs) > kSatelliteWindowMs) + { g_gps.satsUsedWindowMax = g_gps.satsUsed; } - if ((uint32_t)(now - g_gps.satsInViewWindowMs) > kSatelliteWindowMs) { + if ((uint32_t)(now - g_gps.satsInViewWindowMs) > kSatelliteWindowMs) + { g_gps.satsInViewWindowMax = g_gps.satsInView; } @@ -973,33 +1157,41 @@ static uint8_t bestSatelliteCount() { return (used > inView) ? used : inView; } -static uint8_t displayedSatsUsed() { - if ((uint32_t)(millis() - g_gps.satsUsedWindowMs) > kFixFreshMs) { +static uint8_t displayedSatsUsed() +{ + if ((uint32_t)(millis() - g_gps.satsUsedWindowMs) > kFixFreshMs) + { return 0; } return g_gps.satsUsed; } -static uint8_t displayedSatsInView() { - if ((uint32_t)(millis() - g_gps.satsInViewWindowMs) > kFixFreshMs) { +static uint8_t displayedSatsInView() +{ + if ((uint32_t)(millis() - g_gps.satsInViewWindowMs) > kFixFreshMs) + { return 0; } return g_gps.satsInView; } -static bool displayHasFreshUtc() { +static bool displayHasFreshUtc() +{ return g_gps.hasValidUtc && (uint32_t)(millis() - g_gps.utcFixMs) <= kFixFreshMs; } -static bool isUnsupportedGpsMode() { +static bool isUnsupportedGpsMode() +{ GpsModuleKind active = activeGpsModule(); - if (kExpectedGpsModule == GpsModuleKind::UNKNOWN || active == GpsModuleKind::UNKNOWN) { + if (kExpectedGpsModule == GpsModuleKind::UNKNOWN || active == GpsModuleKind::UNKNOWN) + { return false; } return active != kExpectedGpsModule; } -static void reportStatusToSerial() { +static void reportStatusToSerial() +{ uint8_t satsUsed = displayedSatsUsed(); uint8_t satsView = displayedSatsInView(); logf("GPS module active: %s", gpsModuleToString(activeGpsModule()).c_str()); @@ -1013,8 +1205,10 @@ static void reportStatusToSerial() { appendGpsSnapshot("status"); } -static void maybeAnnounceGpsTransitions() { - if (isUnsupportedGpsMode()) { +static void maybeAnnounceGpsTransitions() +{ + if (isUnsupportedGpsMode()) + { return; } @@ -1024,7 +1218,8 @@ static void maybeAnnounceGpsTransitions() { bool hasSats = sats > 0; bool hasUtc = displayHasFreshUtc(); - if (!g_satellitesAcquiredAnnounced && !g_prevHadSatellites && hasSats) { + if (!g_satellitesAcquiredAnnounced && !g_prevHadSatellites && hasSats) + { String rtc = formatRtcNow(); char l2[28]; char l3[28]; @@ -1036,7 +1231,8 @@ static void maybeAnnounceGpsTransitions() { g_satellitesAcquiredAnnounced = true; } - if (!g_timeAcquiredAnnounced && !g_prevHadValidUtc && hasUtc) { + if (!g_timeAcquiredAnnounced && !g_prevHadValidUtc && hasUtc) + { char line2[40]; char line3[28]; char line4[28]; @@ -1061,8 +1257,10 @@ static void maybeAnnounceGpsTransitions() { g_prevHadValidUtc = hasUtc; } -static void drawMinuteStatus() { - if (isUnsupportedGpsMode()) { +static void drawMinuteStatus() +{ + if (isUnsupportedGpsMode()) + { oledShowLines("GPS module mismatch", ("Expected: " + gpsModuleToString(kExpectedGpsModule)).c_str(), ("Detected: " + gpsModuleToString(activeGpsModule())).c_str(), @@ -1075,7 +1273,8 @@ static void drawMinuteStatus() { uint8_t satsUsed = displayedSatsUsed(); uint8_t satsView = displayedSatsInView(); - if (displayHasFreshUtc()) { + if (displayHasFreshUtc()) + { char line2[40]; char line3[28]; char line4[28]; @@ -1096,7 +1295,8 @@ static void drawMinuteStatus() { } String rtc = formatRtcNow(); - if (satsUsed > 0 || satsView > 0) { + if (satsUsed > 0 || satsView > 0) + { char line2[28]; char line3[28]; snprintf(line2, sizeof(line2), "Used: %u", (unsigned)satsUsed); @@ -1106,21 +1306,26 @@ static void drawMinuteStatus() { (unsigned)satsUsed, (unsigned)satsView, rtc.c_str()); - } else { + } + else + { oledShowLines("Unable to acquire", "satellites", "Take me outside so I", "can see satellites", rtc.c_str()); logf("Unable to acquire satellites. %s", rtc.c_str()); } } -static bool shouldRefreshDisplay() { +static bool shouldRefreshDisplay() +{ uint32_t now = millis(); - if (g_lastDisplayRefreshMs != 0 && (uint32_t)(now - g_lastDisplayRefreshMs) < kDisplayRefreshMinMs) { + if (g_lastDisplayRefreshMs != 0 && (uint32_t)(now - g_lastDisplayRefreshMs) < kDisplayRefreshMinMs) + { return false; } uint8_t satsUsed = displayedSatsUsed(); uint8_t satsView = displayedSatsInView(); bool hasUtc = displayHasFreshUtc(); - if (!g_haveLastDrawnState) { + if (!g_haveLastDrawnState) + { return true; } return satsUsed != g_lastDrawnSatsUsed || @@ -1128,7 +1333,8 @@ static bool shouldRefreshDisplay() { hasUtc != g_lastDrawnValidUtc; } -static void markDisplayStateDrawn() { +static void markDisplayStateDrawn() +{ g_lastDrawnSatsUsed = displayedSatsUsed(); g_lastDrawnSatsView = displayedSatsInView(); g_lastDrawnValidUtc = displayHasFreshUtc(); @@ -1136,7 +1342,8 @@ static void markDisplayStateDrawn() { g_lastDisplayRefreshMs = millis(); } -void setup() { +void setup() +{ Serial.begin(115200); delay(kSerialDelayMs); @@ -1145,25 +1352,34 @@ void setup() { Serial.println("=================================================="); Serial.printf("Build: %s %s\r\n", kBuildDate, kBuildTime); - if (!tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial)) { + if (!tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial)) + { logf("PMU init failed"); } // SPI Flash File System ("SPIFFS") is used for logging GPS diagnostics, - // which may be helpful for analyzing GPS behavior in different - //environments and over time. + // which may be helpful for analyzing GPS behavior in different + // environments and over time. g_spiffsReady = SPIFFS.begin(true); - if (!g_spiffsReady) { + if (!g_spiffsReady) + { logf("SPIFFS mount failed"); - } else if (!ensureGpsLogDirectory()) { + } + else if (!ensureGpsLogDirectory()) + { logf("GPS log directory create/open failed"); - } else { + } + else + { File file = SPIFFS.open(kGpsLogPath, FILE_WRITE); - if (file) { + if (file) + { file.println("Exercise 09 GPS diagnostics"); file.printf("Build: %s %s\r\n", kBuildDate, kBuildTime); file.close(); - } else { + } + else + { logf("GPS log file open failed: %s", kGpsLogPath); } } @@ -1174,10 +1390,11 @@ void setup() { g_oled.begin(); String buildStamp = buildStampShort(); oledShowLines("09_GPS_Time", buildStamp.c_str(), "Booting..."); - // The GPS startup probe may take a while, - //especially for a cold start. Log some + // The GPS startup probe may take a while, + // especially for a cold start. Log some SdWatcherConfig sdCfg{}; - if (!g_sd.begin(sdCfg, nullptr)) { + if (!g_sd.begin(sdCfg, nullptr)) + { logf("SD startup manager begin() failed"); } @@ -1200,24 +1417,28 @@ void setup() { oledShowLines("GPS startup probe", "Checking satellites", "and GPS time..."); uint32_t probeWindowMs = startupProbeWindowMs(); - if (kExpectedGpsModule == GpsModuleKind::UBLOX) { + if (kExpectedGpsModule == GpsModuleKind::UBLOX) + { logf("UBLOX startup window: %lu ms (allowing cold start acquisition)", (unsigned long)probeWindowMs); } uint32_t probeStart = millis(); uint32_t lastProbeUiMs = 0; - while ((uint32_t)(millis() - probeStart) < probeWindowMs) { + while ((uint32_t)(millis() - probeStart) < probeWindowMs) + { pollSerialConsole(); pollGpsSerial(); g_sd.update(); uint32_t now = millis(); - if ((uint32_t)(now - g_lastGpsDiagnosticLogMs) >= kGpsDiagnosticLogMs) { + if ((uint32_t)(now - g_lastGpsDiagnosticLogMs) >= kGpsDiagnosticLogMs) + { g_lastGpsDiagnosticLogMs = now; appendGpsSnapshot("startup_wait"); } - if ((uint32_t)(now - lastProbeUiMs) >= 1000) { + if ((uint32_t)(now - lastProbeUiMs) >= 1000) + { lastProbeUiMs = now; char l3[28]; char l4[30]; @@ -1239,22 +1460,26 @@ void setup() { g_lastGpsDiagnosticLogMs = millis(); } -void loop() { +void loop() +{ pollSerialConsole(); pollGpsSerial(); g_sd.update(); maybeAnnounceGpsTransitions(); uint32_t now = millis(); - if (shouldRefreshDisplay()) { + if (shouldRefreshDisplay()) + { drawMinuteStatus(); markDisplayStateDrawn(); } - if ((uint32_t)(now - g_lastGpsDiagnosticLogMs) >= kGpsDiagnosticLogMs) { + if ((uint32_t)(now - g_lastGpsDiagnosticLogMs) >= kGpsDiagnosticLogMs) + { g_lastGpsDiagnosticLogMs = now; appendGpsSnapshot("periodic"); } - if ((uint32_t)(now - g_lastMinuteReportMs) >= kMinuteMs) { + if ((uint32_t)(now - g_lastMinuteReportMs) >= kMinuteMs) + { g_lastMinuteReportMs = now; drawMinuteStatus(); markDisplayStateDrawn(); diff --git a/lib/tbeam_clock/src/TBeamClock.cpp b/lib/tbeam_clock/src/TBeamClock.cpp index 326401f..98ede4e 100644 --- a/lib/tbeam_clock/src/TBeamClock.cpp +++ b/lib/tbeam_clock/src/TBeamClock.cpp @@ -8,284 +8,333 @@ #define OLED_SCL 18 #endif -namespace tbeam { +namespace tbeam +{ -TBeamClock::TBeamClock(TwoWire& wire) : wire_(wire) {} + TBeamClock::TBeamClock(TwoWire &wire) : wire_(wire) {} -bool TBeamClock::begin(const ClockConfig& config) { - config_ = config; - clearError(); - - if (config_.sda < 0) { - config_.sda = OLED_SDA; - } - if (config_.scl < 0) { - config_.scl = OLED_SCL; - } - - if (config_.beginWire) { - wire_.begin(config_.sda, config_.scl); - } - - DateTime dt{}; - bool lowVoltage = false; - ready_ = readRtc(dt, lowVoltage); - if (!ready_) { - setError("RTC read failed"); - valid_ = false; - return false; - } - - lowVoltage_ = lowVoltage; - lastRtc_ = dt; - valid_ = !lowVoltage && isValidDateTime(dt); - lastEpoch_ = valid_ ? toEpochSeconds(dt) : 0; - if (lowVoltage) { - setError("RTC low-voltage flag set"); - } else if (!valid_) { - setError("RTC date/time invalid"); - } - return true; -} - -void TBeamClock::update() { - DateTime dt{}; - bool lowVoltage = false; - if (!readRtc(dt, lowVoltage)) { - ready_ = false; - valid_ = false; - setError("RTC read failed"); - return; - } - - ready_ = true; - lowVoltage_ = lowVoltage; - lastRtc_ = dt; - valid_ = !lowVoltage && isValidDateTime(dt); - lastEpoch_ = valid_ ? toEpochSeconds(dt) : 0; - if (valid_) { + bool TBeamClock::begin(const ClockConfig &config) + { + config_ = config; clearError(); - } else if (lowVoltage) { - setError("RTC low-voltage flag set"); - } else { - setError("RTC date/time invalid"); - } -} -bool TBeamClock::readRtc(DateTime& out, bool& lowVoltageFlag) const { - wire_.beginTransmission(config_.rtcAddress); - wire_.write(0x02); - if (wire_.endTransmission(false) != 0) { - return false; + if (config_.sda < 0) + { + config_.sda = OLED_SDA; + } + if (config_.scl < 0) + { + config_.scl = OLED_SCL; + } + + if (config_.beginWire) + { + wire_.begin(config_.sda, config_.scl); + } + + DateTime dt{}; + bool lowVoltage = false; + ready_ = readRtc(dt, lowVoltage); + if (!ready_) + { + setError("RTC read failed"); + valid_ = false; + return false; + } + + lowVoltage_ = lowVoltage; + lastRtc_ = dt; + valid_ = !lowVoltage && isValidDateTime(dt); + lastEpoch_ = valid_ ? toEpochSeconds(dt) : 0; + if (lowVoltage) + { + setError("RTC low-voltage flag set"); + } + else if (!valid_) + { + setError("RTC date/time invalid"); + } + return true; } - const uint8_t need = 7; - const uint8_t got = wire_.requestFrom((int)config_.rtcAddress, (int)need); - if (got != need) { - return false; + void TBeamClock::update() + { + DateTime dt{}; + bool lowVoltage = false; + if (!readRtc(dt, lowVoltage)) + { + ready_ = false; + valid_ = false; + setError("RTC read failed"); + return; + } + + ready_ = true; + lowVoltage_ = lowVoltage; + lastRtc_ = dt; + valid_ = !lowVoltage && isValidDateTime(dt); + lastEpoch_ = valid_ ? toEpochSeconds(dt) : 0; + if (valid_) + { + clearError(); + } + else if (lowVoltage) + { + setError("RTC low-voltage flag set"); + } + else + { + setError("RTC date/time invalid"); + } } - const uint8_t sec = wire_.read(); - const uint8_t min = wire_.read(); - const uint8_t hour = wire_.read(); - const uint8_t day = wire_.read(); - const uint8_t weekday = wire_.read(); - const uint8_t month = wire_.read(); - const uint8_t year = wire_.read(); + bool TBeamClock::readRtc(DateTime &out, bool &lowVoltageFlag) const + { + wire_.beginTransmission(config_.rtcAddress); + wire_.write(0x02); + if (wire_.endTransmission(false) != 0) + { + return false; + } - lowVoltageFlag = (sec & 0x80U) != 0; - out.second = fromBcd(sec & 0x7FU); - out.minute = fromBcd(min & 0x7FU); - out.hour = fromBcd(hour & 0x3FU); - out.day = fromBcd(day & 0x3FU); - out.weekday = fromBcd(weekday & 0x07U); - out.month = fromBcd(month & 0x1FU); - const uint8_t yy = fromBcd(year); - out.year = (month & 0x80U) ? (1900U + yy) : (2000U + yy); - return true; -} + const uint8_t need = 7; + const uint8_t got = wire_.requestFrom((int)config_.rtcAddress, (int)need); + if (got != need) + { + return false; + } -bool TBeamClock::readValidRtc(DateTime& out, int64_t* epochOut) const { - bool lowVoltage = false; - if (!readRtc(out, lowVoltage) || lowVoltage || !isValidDateTime(out)) { - return false; - } - if (epochOut) { - *epochOut = toEpochSeconds(out); - } - return true; -} + const uint8_t sec = wire_.read(); + const uint8_t min = wire_.read(); + const uint8_t hour = wire_.read(); + const uint8_t day = wire_.read(); + const uint8_t weekday = wire_.read(); + const uint8_t month = wire_.read(); + const uint8_t year = wire_.read(); -bool TBeamClock::writeRtc(const DateTime& dt) const { - if (!isValidDateTime(dt)) { - return false; + lowVoltageFlag = (sec & 0x80U) != 0; + out.second = fromBcd(sec & 0x7FU); + out.minute = fromBcd(min & 0x7FU); + out.hour = fromBcd(hour & 0x3FU); + out.day = fromBcd(day & 0x3FU); + out.weekday = fromBcd(weekday & 0x07U); + out.month = fromBcd(month & 0x1FU); + const uint8_t yy = fromBcd(year); + out.year = (month & 0x80U) ? (1900U + yy) : (2000U + yy); + return true; } - wire_.beginTransmission(config_.rtcAddress); - wire_.write(0x02); - wire_.write(toBcd(dt.second) & 0x7FU); - wire_.write(toBcd(dt.minute) & 0x7FU); - wire_.write(toBcd(dt.hour) & 0x3FU); - wire_.write(toBcd(dt.day) & 0x3FU); - wire_.write(toBcd(dt.weekday) & 0x07U); - - uint8_t monthReg = toBcd(dt.month) & 0x1FU; - if (dt.year < 2000U) { - monthReg |= 0x80U; - } - wire_.write(monthReg); - wire_.write(toBcd((uint8_t)(dt.year % 100U))); - return wire_.endTransmission() == 0; -} - -bool TBeamClock::isValidDateTime(const DateTime& dt) { - if (dt.year < 2000U || dt.year > 2099U) return false; - if (dt.month < 1U || dt.month > 12U) return false; - if (dt.day < 1U || dt.day > daysInMonth(dt.year, dt.month)) return false; - if (dt.hour > 23U || dt.minute > 59U || dt.second > 59U) return false; - return true; -} - -int64_t TBeamClock::toEpochSeconds(const DateTime& dt) { - const int64_t days = daysFromCivil((int)dt.year, dt.month, dt.day); - return days * 86400LL + (int64_t)dt.hour * 3600LL + (int64_t)dt.minute * 60LL + (int64_t)dt.second; -} - -bool TBeamClock::fromEpochSeconds(int64_t seconds, DateTime& out) { - if (seconds < 0) { - return false; + bool TBeamClock::readValidRtc(DateTime &out, int64_t *epochOut) const + { + bool lowVoltage = false; + if (!readRtc(out, lowVoltage) || lowVoltage || !isValidDateTime(out)) + { + return false; + } + if (epochOut) + { + *epochOut = toEpochSeconds(out); + } + return true; } - int64_t days = seconds / 86400LL; - int64_t remainder = seconds % 86400LL; - if (remainder < 0) { - remainder += 86400LL; - days -= 1; + bool TBeamClock::writeRtc(const DateTime &dt) const + { + if (!isValidDateTime(dt)) + { + return false; + } + + wire_.beginTransmission(config_.rtcAddress); + wire_.write(0x02); + wire_.write(toBcd(dt.second) & 0x7FU); + wire_.write(toBcd(dt.minute) & 0x7FU); + wire_.write(toBcd(dt.hour) & 0x3FU); + wire_.write(toBcd(dt.day) & 0x3FU); + wire_.write(toBcd(dt.weekday) & 0x07U); + + uint8_t monthReg = toBcd(dt.month) & 0x1FU; + if (dt.year < 2000U) + { + monthReg |= 0x80U; + } + wire_.write(monthReg); + wire_.write(toBcd((uint8_t)(dt.year % 100U))); + return wire_.endTransmission() == 0; } - out.hour = (uint8_t)(remainder / 3600LL); - remainder %= 3600LL; - out.minute = (uint8_t)(remainder / 60LL); - out.second = (uint8_t)(remainder % 60LL); - - days += 719468; - const int era = (days >= 0 ? days : days - 146096) / 146097; - const unsigned doe = (unsigned)(days - era * 146097); - const unsigned yoe = (doe - doe / 1460U + doe / 36524U - doe / 146096U) / 365U; - int year = (int)yoe + era * 400; - const unsigned doy = doe - (365U * yoe + yoe / 4U - yoe / 100U); - const unsigned mp = (5U * doy + 2U) / 153U; - const unsigned day = doy - (153U * mp + 2U) / 5U + 1U; - const unsigned month = mp + (mp < 10U ? 3U : (unsigned)-9); - year += (month <= 2U); - - out.year = (uint16_t)year; - out.month = (uint8_t)month; - out.day = (uint8_t)day; - out.weekday = 0; - return isValidDateTime(out); -} - -void TBeamClock::formatIsoUtc(const DateTime& dt, char* out, size_t outSize) { - snprintf(out, - outSize, - "%04u-%02u-%02uT%02u:%02u:%02uZ", - (unsigned)dt.year, - (unsigned)dt.month, - (unsigned)dt.day, - (unsigned)dt.hour, - (unsigned)dt.minute, - (unsigned)dt.second); -} - -void TBeamClock::formatCompactUtc(const DateTime& dt, char* out, size_t outSize) { - snprintf(out, - outSize, - "%04u%02u%02u_%02u%02u%02u", - (unsigned)dt.year, - (unsigned)dt.month, - (unsigned)dt.day, - (unsigned)dt.hour, - (unsigned)dt.minute, - (unsigned)dt.second); -} - -void TBeamClock::makeRunId(const DateTime& dt, const char* boardId, char* out, size_t outSize) { - snprintf(out, - outSize, - "%04u%02u%02u_%02u%02u%02u_%s", - (unsigned)dt.year, - (unsigned)dt.month, - (unsigned)dt.day, - (unsigned)dt.hour, - (unsigned)dt.minute, - (unsigned)dt.second, - boardId ? boardId : "NODE"); -} - -bool TBeamClock::parseDateTime(const char* text, DateTime& out) { - if (!text) { - return false; - } - int y = 0; - int mo = 0; - int d = 0; - int h = 0; - int mi = 0; - int s = 0; - if (sscanf(text, "%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6 && - sscanf(text, "%d-%d-%dT%d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6) { - return false; + bool TBeamClock::isValidDateTime(const DateTime &dt) + { + if (dt.year < 2000U || dt.year > 2099U) + return false; + if (dt.month < 1U || dt.month > 12U) + return false; + if (dt.day < 1U || dt.day > daysInMonth(dt.year, dt.month)) + return false; + if (dt.hour > 23U || dt.minute > 59U || dt.second > 59U) + return false; + return true; } - out.year = (uint16_t)y; - out.month = (uint8_t)mo; - out.day = (uint8_t)d; - out.hour = (uint8_t)h; - out.minute = (uint8_t)mi; - out.second = (uint8_t)s; - out.weekday = 0; - return isValidDateTime(out); -} - -uint8_t TBeamClock::toBcd(uint8_t value) { - return (uint8_t)(((value / 10U) << 4U) | (value % 10U)); -} - -uint8_t TBeamClock::fromBcd(uint8_t value) { - return (uint8_t)(((value >> 4U) * 10U) + (value & 0x0FU)); -} - -bool TBeamClock::isLeapYear(uint16_t year) { - return ((year % 4U) == 0U && (year % 100U) != 0U) || ((year % 400U) == 0U); -} - -uint8_t TBeamClock::daysInMonth(uint16_t year, uint8_t month) { - static const uint8_t kDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - if (month == 2U) { - return (uint8_t)(isLeapYear(year) ? 29U : 28U); + int64_t TBeamClock::toEpochSeconds(const DateTime &dt) + { + const int64_t days = daysFromCivil((int)dt.year, dt.month, dt.day); + return days * 86400LL + (int64_t)dt.hour * 3600LL + (int64_t)dt.minute * 60LL + (int64_t)dt.second; } - if (month >= 1U && month <= 12U) { - return kDays[month - 1U]; + + bool TBeamClock::fromEpochSeconds(int64_t seconds, DateTime &out) + { + if (seconds < 0) + { + return false; + } + + int64_t days = seconds / 86400LL; + int64_t remainder = seconds % 86400LL; + if (remainder < 0) + { + remainder += 86400LL; + days -= 1; + } + + out.hour = (uint8_t)(remainder / 3600LL); + remainder %= 3600LL; + out.minute = (uint8_t)(remainder / 60LL); + out.second = (uint8_t)(remainder % 60LL); + + days += 719468; + const int era = (days >= 0 ? days : days - 146096) / 146097; + const unsigned doe = (unsigned)(days - era * 146097); + const unsigned yoe = (doe - doe / 1460U + doe / 36524U - doe / 146096U) / 365U; + int year = (int)yoe + era * 400; + const unsigned doy = doe - (365U * yoe + yoe / 4U - yoe / 100U); + const unsigned mp = (5U * doy + 2U) / 153U; + const unsigned day = doy - (153U * mp + 2U) / 5U + 1U; + const unsigned month = mp + (mp < 10U ? 3U : (unsigned)-9); + year += (month <= 2U); + + out.year = (uint16_t)year; + out.month = (uint8_t)month; + out.day = (uint8_t)day; + out.weekday = 0; + return isValidDateTime(out); } - return 0; -} -int64_t TBeamClock::daysFromCivil(int year, unsigned month, unsigned day) { - year -= (month <= 2U); - const int era = (year >= 0 ? year : year - 399) / 400; - const unsigned yoe = (unsigned)(year - era * 400); - const unsigned doy = (153U * (month + (month > 2U ? (unsigned)-3 : 9U)) + 2U) / 5U + day - 1U; - const unsigned doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; - return era * 146097 + (int)doe - 719468; -} + void TBeamClock::formatIsoUtc(const DateTime &dt, char *out, size_t outSize) + { + snprintf(out, + outSize, + "%04u-%02u-%02uT%02u:%02u:%02uZ", + (unsigned)dt.year, + (unsigned)dt.month, + (unsigned)dt.day, + (unsigned)dt.hour, + (unsigned)dt.minute, + (unsigned)dt.second); + } -void TBeamClock::setError(const char* message) const { - strlcpy(lastError_, message ? message : "", sizeof(lastError_)); -} + void TBeamClock::formatCompactUtc(const DateTime &dt, char *out, size_t outSize) + { + snprintf(out, + outSize, + "%04u%02u%02u_%02u%02u%02u", + (unsigned)dt.year, + (unsigned)dt.month, + (unsigned)dt.day, + (unsigned)dt.hour, + (unsigned)dt.minute, + (unsigned)dt.second); + } -void TBeamClock::clearError() const { - lastError_[0] = '\0'; -} + void TBeamClock::makeRunId(const DateTime &dt, const char *boardId, char *out, size_t outSize) + { + snprintf(out, + outSize, + "%04u%02u%02u_%02u%02u%02u_%s", + (unsigned)dt.year, + (unsigned)dt.month, + (unsigned)dt.day, + (unsigned)dt.hour, + (unsigned)dt.minute, + (unsigned)dt.second, + boardId ? boardId : "NODE"); + } -} // namespace tbeam + bool TBeamClock::parseDateTime(const char *text, DateTime &out) + { + if (!text) + { + return false; + } + int y = 0; + int mo = 0; + int d = 0; + int h = 0; + int mi = 0; + int s = 0; + if (sscanf(text, "%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6 && + sscanf(text, "%d-%d-%dT%d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6) + { + return false; + } + + out.year = (uint16_t)y; + out.month = (uint8_t)mo; + out.day = (uint8_t)d; + out.hour = (uint8_t)h; + out.minute = (uint8_t)mi; + out.second = (uint8_t)s; + out.weekday = 0; + return isValidDateTime(out); + } + + uint8_t TBeamClock::toBcd(uint8_t value) + { + return (uint8_t)(((value / 10U) << 4U) | (value % 10U)); + } + + uint8_t TBeamClock::fromBcd(uint8_t value) + { + return (uint8_t)(((value >> 4U) * 10U) + (value & 0x0FU)); + } + + bool TBeamClock::isLeapYear(uint16_t year) + { + return ((year % 4U) == 0U && (year % 100U) != 0U) || ((year % 400U) == 0U); + } + + uint8_t TBeamClock::daysInMonth(uint16_t year, uint8_t month) + { + static const uint8_t kDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (month == 2U) + { + return (uint8_t)(isLeapYear(year) ? 29U : 28U); + } + if (month >= 1U && month <= 12U) + { + return kDays[month - 1U]; + } + return 0; + } + + int64_t TBeamClock::daysFromCivil(int year, unsigned month, unsigned day) + { + year -= (month <= 2U); + const int era = (year >= 0 ? year : year - 399) / 400; + const unsigned yoe = (unsigned)(year - era * 400); + const unsigned doy = (153U * (month + (month > 2U ? (unsigned)-3 : 9U)) + 2U) / 5U + day - 1U; + const unsigned doe = yoe * 365U + yoe / 4U - yoe / 100U + doy; + return era * 146097 + (int)doe - 719468; + } + + void TBeamClock::setError(const char *message) const + { + strlcpy(lastError_, message ? message : "", sizeof(lastError_)); + } + + void TBeamClock::clearError() const + { + lastError_[0] = '\0'; + } + +} // namespace tbeam diff --git a/lib/tbeam_clock/src/TBeamClock.h b/lib/tbeam_clock/src/TBeamClock.h index ad85b88..35c7683 100644 --- a/lib/tbeam_clock/src/TBeamClock.h +++ b/lib/tbeam_clock/src/TBeamClock.h @@ -3,69 +3,73 @@ #include #include -namespace tbeam { +namespace tbeam +{ -struct DateTime { - uint16_t year = 0; - uint8_t month = 0; - uint8_t day = 0; - uint8_t hour = 0; - uint8_t minute = 0; - uint8_t second = 0; - uint8_t weekday = 0; -}; + struct DateTime + { + uint16_t year = 0; + uint8_t month = 0; + uint8_t day = 0; + uint8_t hour = 0; + uint8_t minute = 0; + uint8_t second = 0; + uint8_t weekday = 0; + }; -struct ClockConfig { - uint8_t rtcAddress = 0x51; - int sda = -1; - int scl = -1; - bool beginWire = true; -}; + struct ClockConfig + { + uint8_t rtcAddress = 0x51; + int sda = -1; + int scl = -1; + bool beginWire = true; + }; -class TBeamClock { - public: - explicit TBeamClock(TwoWire& wire = Wire1); + class TBeamClock + { + public: + explicit TBeamClock(TwoWire &wire = Wire1); - bool begin(const ClockConfig& config = ClockConfig{}); - void update(); + bool begin(const ClockConfig &config = ClockConfig{}); + void update(); - bool readRtc(DateTime& out, bool& lowVoltageFlag) const; - bool readValidRtc(DateTime& out, int64_t* epochOut = nullptr) const; - bool writeRtc(const DateTime& dt) const; + bool readRtc(DateTime &out, bool &lowVoltageFlag) const; + bool readValidRtc(DateTime &out, int64_t *epochOut = nullptr) const; + bool writeRtc(const DateTime &dt) const; - bool ready() const { return ready_; } - bool valid() const { return valid_; } - bool lowVoltage() const { return lowVoltage_; } - const DateTime& lastRtc() const { return lastRtc_; } - int64_t lastEpoch() const { return lastEpoch_; } - const char* lastError() const { return lastError_; } + bool ready() const { return ready_; } + bool valid() const { return valid_; } + bool lowVoltage() const { return lowVoltage_; } + const DateTime &lastRtc() const { return lastRtc_; } + int64_t lastEpoch() const { return lastEpoch_; } + const char *lastError() const { return lastError_; } - static bool isValidDateTime(const DateTime& dt); - static int64_t toEpochSeconds(const DateTime& dt); - static bool fromEpochSeconds(int64_t seconds, DateTime& out); - static void formatIsoUtc(const DateTime& dt, char* out, size_t outSize); - static void formatCompactUtc(const DateTime& dt, char* out, size_t outSize); - static void makeRunId(const DateTime& dt, const char* boardId, char* out, size_t outSize); - static bool parseDateTime(const char* text, DateTime& out); + static bool isValidDateTime(const DateTime &dt); + static int64_t toEpochSeconds(const DateTime &dt); + static bool fromEpochSeconds(int64_t seconds, DateTime &out); + static void formatIsoUtc(const DateTime &dt, char *out, size_t outSize); + static void formatCompactUtc(const DateTime &dt, char *out, size_t outSize); + static void makeRunId(const DateTime &dt, const char *boardId, char *out, size_t outSize); + static bool parseDateTime(const char *text, DateTime &out); - private: - static uint8_t toBcd(uint8_t value); - static uint8_t fromBcd(uint8_t value); - static bool isLeapYear(uint16_t year); - static uint8_t daysInMonth(uint16_t year, uint8_t month); - static int64_t daysFromCivil(int year, unsigned month, unsigned day); + private: + static uint8_t toBcd(uint8_t value); + static uint8_t fromBcd(uint8_t value); + static bool isLeapYear(uint16_t year); + static uint8_t daysInMonth(uint16_t year, uint8_t month); + static int64_t daysFromCivil(int year, unsigned month, unsigned day); - void setError(const char* message) const; - void clearError() const; + void setError(const char *message) const; + void clearError() const; - TwoWire& wire_; - ClockConfig config_{}; - bool ready_ = false; - bool valid_ = false; - bool lowVoltage_ = false; - DateTime lastRtc_{}; - int64_t lastEpoch_ = 0; - mutable char lastError_[128] = {}; -}; + TwoWire &wire_; + ClockConfig config_{}; + bool ready_ = false; + bool valid_ = false; + bool lowVoltage_ = false; + DateTime lastRtc_{}; + int64_t lastEpoch_ = 0; + mutable char lastError_[128] = {}; + }; -} // namespace tbeam +} // namespace tbeam diff --git a/lib/tbeam_logger/src/TBeamLogger.cpp b/lib/tbeam_logger/src/TBeamLogger.cpp index 0e3c104..f7bbf21 100644 --- a/lib/tbeam_logger/src/TBeamLogger.cpp +++ b/lib/tbeam_logger/src/TBeamLogger.cpp @@ -1,78 +1,98 @@ #include "TBeamLogger.h" -namespace tbeam { +namespace tbeam +{ -bool TBeamLogger::begin(Print& serial, TBeamStorage* storage, const LoggerConfig& config) { - serial_ = &serial; - storage_ = storage; - config_ = config; - lastFlushMs_ = millis(); - return true; -} - -void TBeamLogger::update() { - if (!config_.autoFlush || !storage_) { - return; + bool TBeamLogger::begin(Print &serial, TBeamStorage *storage, const LoggerConfig &config) + { + serial_ = &serial; + storage_ = storage; + config_ = config; + lastFlushMs_ = millis(); + return true; } - const uint32_t now = millis(); - if ((uint32_t)(now - lastFlushMs_) >= config_.flushIntervalMs) { - storage_->flush(); - lastFlushMs_ = now; + + void TBeamLogger::update() + { + if (!config_.autoFlush || !storage_) + { + return; + } + const uint32_t now = millis(); + if ((uint32_t)(now - lastFlushMs_) >= config_.flushIntervalMs) + { + storage_->flush(); + lastFlushMs_ = now; + } } -} -bool TBeamLogger::openLog(const char* path) { - return storage_ && storage_->openLog(path); -} - -bool TBeamLogger::openUniqueLog(const char* prefix, const char* extension) { - if (!storage_) { - return false; + bool TBeamLogger::openLog(const char *path) + { + return storage_ && storage_->openLog(path); } - char path[128]; - if (!storage_->makeUniqueLogPath(prefix, extension, path, sizeof(path))) { - return false; + + bool TBeamLogger::openUniqueLog(const char *prefix, const char *extension) + { + if (!storage_) + { + return false; + } + char path[128]; + if (!storage_->makeUniqueLogPath(prefix, extension, path, sizeof(path))) + { + return false; + } + return storage_->openLog(path); } - return storage_->openLog(path); -} -const char* TBeamLogger::currentLogPath() const { - return storage_ ? storage_->currentLogPath() : ""; -} - -bool TBeamLogger::storageReady() const { - return storage_ && storage_->ready() && storage_->isLogOpen(); -} - -void TBeamLogger::flush() { - if (storage_) { - storage_->flush(); + const char *TBeamLogger::currentLogPath() const + { + return storage_ ? storage_->currentLogPath() : ""; } - if (serial_) { - serial_->flush(); - } -} -void TBeamLogger::closeLog() { - if (storage_) { - storage_->closeLog(); + bool TBeamLogger::storageReady() const + { + return storage_ && storage_->ready() && storage_->isLogOpen(); } -} -size_t TBeamLogger::write(uint8_t value) { - return write(&value, 1); -} - -size_t TBeamLogger::write(const uint8_t* buffer, size_t size) { - size_t serialWrote = 0; - size_t storageWrote = 0; - if (config_.echoSerial && serial_) { - serialWrote = serial_->write(buffer, size); + void TBeamLogger::flush() + { + if (storage_) + { + storage_->flush(); + } + if (serial_) + { + serial_->flush(); + } } - if (config_.echoStorage && storage_ && storage_->isLogOpen()) { - storageWrote = storage_->write(buffer, size); - } - return storageWrote > 0 ? storageWrote : serialWrote; -} -} // namespace tbeam + void TBeamLogger::closeLog() + { + if (storage_) + { + storage_->closeLog(); + } + } + + size_t TBeamLogger::write(uint8_t value) + { + return write(&value, 1); + } + + size_t TBeamLogger::write(const uint8_t *buffer, size_t size) + { + size_t serialWrote = 0; + size_t storageWrote = 0; + if (config_.echoSerial && serial_) + { + serialWrote = serial_->write(buffer, size); + } + if (config_.echoStorage && storage_ && storage_->isLogOpen()) + { + storageWrote = storage_->write(buffer, size); + } + return storageWrote > 0 ? storageWrote : serialWrote; + } + +} // namespace tbeam