@@ -202,6 +202,8 @@ static bool gNotecardAvailable = true;
202202#define NOTECARD_FAILURE_THRESHOLD 5
203203#define NOTECARD_RETRY_INTERVAL 60000UL // Retry after 60 seconds
204204
205+ static const size_t DAILY_NOTE_PAYLOAD_LIMIT = 960U ;
206+
205207// Forward declarations
206208static void initializeStorage ();
207209static void ensureConfigLoaded ();
@@ -225,6 +227,8 @@ static void sendDailyReport();
225227static void publishNote (const char *fileName, const JsonDocument &doc, bool syncNow);
226228static void ensureTimeSync ();
227229static void updateDailyScheduleIfNeeded ();
230+ static bool checkNotecardHealth ();
231+ static bool appendDailyTank (DynamicJsonDocument &doc, JsonArray &array, uint8_t tankIndex, size_t payloadLimit);
228232
229233void setup () {
230234 Serial.begin (115200 );
@@ -1247,36 +1251,96 @@ static void sendAlarm(uint8_t idx, const char *alarmType, float inches) {
12471251}
12481252
12491253static void sendDailyReport () {
1250- DynamicJsonDocument doc (2048 );
1251- JsonArray tanks = doc.createNestedArray (" tanks" );
1252-
1254+ uint8_t eligibleIndices[MAX_TANKS];
1255+ uint8_t eligibleCount = 0 ;
12531256 for (uint8_t i = 0 ; i < gConfig .tankCount ; ++i) {
1254- const TankConfig &cfg = gConfig .tanks [i];
1255- TankRuntime &state = gTankState [i];
1256- if (!cfg.enableDailyReport ) {
1257- continue ;
1257+ if (gConfig .tanks [i].enableDailyReport ) {
1258+ eligibleIndices[eligibleCount++] = i;
12581259 }
1259- JsonObject t = tanks.createNestedObject ();
1260- t[" label" ] = cfg.name ;
1261- t[" tank" ] = cfg.tankNumber ;
1262- t[" levelInches" ] = state.currentInches ;
1263- t[" percent" ] = (cfg.heightInches > 0 .1f ) ? (state.currentInches / cfg.heightInches * 100 .0f ) : 0 .0f ;
1264- t[" high" ] = cfg.highAlarmInches ;
1265- t[" low" ] = cfg.lowAlarmInches ;
1266- state.lastDailySentInches = state.currentInches ;
12671260 }
12681261
1269- if (tanks. size () == 0 ) {
1262+ if (eligibleCount == 0 ) {
12701263 return ;
12711264 }
12721265
1273- doc[" client" ] = gDeviceUID ;
1274- doc[" site" ] = gConfig .siteName ;
1275- doc[" email" ] = gConfig .dailyEmail ;
1276- doc[" time" ] = currentEpoch ();
1266+ double reportEpoch = currentEpoch ();
1267+ size_t tankCursor = 0 ;
1268+ uint8_t part = 0 ;
1269+ bool queuedAny = false ;
1270+
1271+ while (tankCursor < eligibleCount) {
1272+ DynamicJsonDocument doc (1024 );
1273+ doc[" client" ] = gDeviceUID ;
1274+ doc[" site" ] = gConfig .siteName ;
1275+ doc[" email" ] = gConfig .dailyEmail ;
1276+ doc[" time" ] = reportEpoch;
1277+ doc[" part" ] = static_cast <uint8_t >(part + 1 );
1278+
1279+ JsonArray tanks = doc.createNestedArray (" tanks" );
1280+ bool addedTank = false ;
1281+
1282+ while (tankCursor < eligibleCount) {
1283+ uint8_t tankIndex = eligibleIndices[tankCursor];
1284+ if (appendDailyTank (doc, tanks, tankIndex, DAILY_NOTE_PAYLOAD_LIMIT)) {
1285+ ++tankCursor;
1286+ addedTank = true ;
1287+ } else {
1288+ if (!addedTank) {
1289+ // Allow a single large entry with minimal headroom so it still publishes.
1290+ if (appendDailyTank (doc, tanks, tankIndex, DAILY_NOTE_PAYLOAD_LIMIT + 48U )) {
1291+ ++tankCursor;
1292+ addedTank = true ;
1293+ } else {
1294+ Serial.println (F (" Daily report entry skipped; payload still exceeds limit" ));
1295+ ++tankCursor;
1296+ }
1297+ }
1298+ break ;
1299+ }
1300+ }
1301+
1302+ if (!addedTank) {
1303+ continue ;
1304+ }
12771305
1278- publishNote (DAILY_FILE, doc, true );
1279- Serial.println (F (" Daily report queued" ));
1306+ doc[" more" ] = (tankCursor < eligibleCount);
1307+ bool syncNow = (tankCursor >= eligibleCount);
1308+ publishNote (DAILY_FILE, doc, syncNow);
1309+ queuedAny = true ;
1310+ ++part;
1311+ }
1312+
1313+ if (queuedAny) {
1314+ Serial.println (F (" Daily report queued" ));
1315+ }
1316+ }
1317+
1318+ static bool appendDailyTank (DynamicJsonDocument &doc, JsonArray &array, uint8_t tankIndex, size_t payloadLimit) {
1319+ if (tankIndex >= gConfig .tankCount ) {
1320+ return false ;
1321+ }
1322+
1323+ const TankConfig &cfg = gConfig .tanks [tankIndex];
1324+ TankRuntime &state = gTankState [tankIndex];
1325+
1326+ JsonObject t = array.createNestedObject ();
1327+ t[" label" ] = cfg.name ;
1328+ t[" tank" ] = cfg.tankNumber ;
1329+ t[" levelInches" ] = state.currentInches ;
1330+ t[" percent" ] = (cfg.heightInches > 0 .1f ) ? (state.currentInches / cfg.heightInches * 100 .0f ) : 0 .0f ;
1331+ t[" high" ] = cfg.highAlarmInches ;
1332+ t[" low" ] = cfg.lowAlarmInches ;
1333+
1334+ if (measureJson (doc) > payloadLimit) {
1335+ size_t currentSize = array.size ();
1336+ if (currentSize > 0 ) {
1337+ array.remove (currentSize - 1 );
1338+ }
1339+ return false ;
1340+ }
1341+
1342+ state.lastDailySentInches = state.currentInches ;
1343+ return true ;
12801344}
12811345
12821346static void publishNote (const char *fileName, const JsonDocument &doc, bool syncNow) {
0 commit comments