4545 #include < LittleFileSystem.h>
4646 #include < BlockDevice.h>
4747 #include < mbed.h>
48+ #include " rtos/ThisThread.h"
4849 using namespace mbed ;
50+ using namespace std ::chrono; // For chrono types (e.g., milliseconds)
51+ using namespace std ::chrono_literals; // For chrono duration literals (e.g., 100ms)
4952 #define FILESYSTEM_AVAILABLE
5053 #define WATCHDOG_AVAILABLE
5154 #define WATCHDOG_TIMEOUT_SECONDS 30
@@ -92,6 +95,15 @@ static size_t strlcpy(char *dst, const char *src, size_t size) {
9295#define PRODUCT_UID " com.senax.tankalarm112025"
9396#endif
9497
98+ // Power saving configuration for solar-powered installations
99+ #ifndef SOLAR_OUTBOUND_INTERVAL_MINUTES
100+ #define SOLAR_OUTBOUND_INTERVAL_MINUTES 360 // Sync every 6 hours for solar installations
101+ #endif
102+
103+ #ifndef SOLAR_INBOUND_INTERVAL_MINUTES
104+ #define SOLAR_INBOUND_INTERVAL_MINUTES 60 // Check for inbound every hour for solar installations
105+ #endif
106+
95107#ifndef CLIENT_CONFIG_PATH
96108#define CLIENT_CONFIG_PATH " /client_config.json"
97109#endif
@@ -358,6 +370,8 @@ struct ClientConfig {
358370 // Optional clear button configuration
359371 int8_t clearButtonPin; // Pin for physical clear button (-1 = disabled)
360372 bool clearButtonActiveHigh; // true = button active when HIGH, false = active when LOW (with pullup)
373+ // Power saving configuration
374+ bool solarPowered; // true = solar powered (use power saving features), false = grid-tied
361375};
362376
363377struct TankRuntime {
@@ -464,6 +478,7 @@ static bool loadConfigFromFlash(ClientConfig &cfg);
464478static bool saveConfigToFlash (const ClientConfig &cfg);
465479static void printHardwareRequirements (const ClientConfig &cfg);
466480static void initializeNotecard ();
481+ static void configureNotecardHubMode ();
467482static void syncTimeFromNotecard ();
468483static double currentEpoch ();
469484static void scheduleNextDailyReport ();
@@ -658,7 +673,12 @@ void loop() {
658673 }
659674
660675 // Sleep to reduce power consumption between loop iterations
661- delay (100 );
676+ // Use Mbed OS thread sleep for power efficiency - allows CPU to enter low-power states during sleep periods
677+ #if defined(ARDUINO_OPTA) || defined(ARDUINO_ARCH_MBED)
678+ rtos::ThisThread::sleep_for (std::chrono::milliseconds (100 )); // Thread sleep for 100ms - enables power saving
679+ #else
680+ delay (100 ); // Fallback for non-Mbed platforms
681+ #endif
662682}
663683
664684static void initializeStorage () {
@@ -767,6 +787,9 @@ static void createDefaultConfig(ClientConfig &cfg) {
767787 // Clear button defaults (disabled)
768788 cfg.clearButtonPin = -1 ; // -1 = disabled
769789 cfg.clearButtonActiveHigh = false ; // Active LOW with pullup (button connects to GND)
790+
791+ // Power saving defaults (grid-tied, no special power saving)
792+ cfg.solarPowered = false ; // false = grid-tied (default)
770793}
771794
772795static bool loadConfigFromFlash (ClientConfig &cfg) {
@@ -842,6 +865,9 @@ static bool loadConfigFromFlash(ClientConfig &cfg) {
842865 // Load clear button configuration
843866 cfg.clearButtonPin = doc[" clearButtonPin" ].is <int >() ? doc[" clearButtonPin" ].as <int8_t >() : -1 ;
844867 cfg.clearButtonActiveHigh = doc[" clearButtonActiveHigh" ].is <bool >() ? doc[" clearButtonActiveHigh" ].as <bool >() : false ;
868+
869+ // Load power saving configuration
870+ cfg.solarPowered = doc[" solarPowered" ].is <bool >() ? doc[" solarPowered" ].as <bool >() : false ;
845871
846872 cfg.tankCount = doc[" tanks" ].is <JsonArray>() ? min<uint8_t >(doc[" tanks" ].size (), MAX_TANKS) : 0 ;
847873
@@ -978,6 +1004,9 @@ static bool saveConfigToFlash(const ClientConfig &cfg) {
9781004 // Save clear button configuration
9791005 doc[" clearButtonPin" ] = cfg.clearButtonPin ;
9801006 doc[" clearButtonActiveHigh" ] = cfg.clearButtonActiveHigh ;
1007+
1008+ // Save power saving configuration
1009+ doc[" solarPowered" ] = cfg.solarPowered ;
9811010
9821011 JsonArray tanks = doc.createNestedArray (" tanks" );
9831012 for (uint8_t i = 0 ; i < cfg.tankCount ; ++i) {
@@ -1161,6 +1190,44 @@ static void printHardwareRequirements(const ClientConfig &cfg) {
11611190 Serial.println (F (" -----------------------------" ));
11621191}
11631192
1193+ static void configureNotecardHubMode () {
1194+ // Configure Notecard hub mode based on power source
1195+ J *req = notecard.newRequest (" hub.set" );
1196+ if (req) {
1197+ JAddStringToObject (req, " product" , PRODUCT_UID);
1198+
1199+ // Power saving configuration based on power source
1200+ if (gConfig .solarPowered ) {
1201+ // Solar powered: Use periodic mode with extended inbound check to save power
1202+ JAddStringToObject (req, " mode" , " periodic" );
1203+ JAddIntToObject (req, " outbound" , SOLAR_OUTBOUND_INTERVAL_MINUTES); // Sync every 6 hours
1204+ JAddIntToObject (req, " inbound" , SOLAR_INBOUND_INTERVAL_MINUTES); // Check for inbound every hour (power saving)
1205+ } else {
1206+ // Grid-tied: Use continuous mode for faster response times
1207+ JAddStringToObject (req, " mode" , " continuous" );
1208+ // In continuous mode, outbound/inbound are not used - always connected
1209+ }
1210+
1211+ notecard.sendRequest (req);
1212+ }
1213+
1214+ // Disable GPS location tracking for power savings
1215+ // GPS is one of the most power-hungry components on the Notecard and is not used by this application
1216+ req = notecard.newRequest (" card.location.mode" );
1217+ if (req) {
1218+ JAddStringToObject (req, " mode" , " off" );
1219+ notecard.sendRequest (req);
1220+ }
1221+
1222+ // Disable accelerometer motion tracking for power savings
1223+ // The accelerometer is not used by this tank monitoring application
1224+ req = notecard.newRequest (" card.motion.mode" );
1225+ if (req) {
1226+ JAddStringToObject (req, " mode" , " off" );
1227+ notecard.sendRequest (req);
1228+ }
1229+ }
1230+
11641231static void initializeNotecard () {
11651232#ifdef DEBUG_MODE
11661233 notecard.setDebugOutputStream (Serial);
@@ -1173,15 +1240,8 @@ static void initializeNotecard() {
11731240 notecard.sendRequest (req);
11741241 }
11751242
1176- req = notecard.newRequest (" hub.set" );
1177- if (req) {
1178- JAddStringToObject (req, " product" , PRODUCT_UID);
1179- JAddStringToObject (req, " mode" , " periodic" );
1180- JAddIntToObject (req, " outbound" , 360 ); // Sync every 6 hours
1181- JAddIntToObject (req, " inbound" , 10 ); // Check for inbound every 10 minutes
1182- // No route needed - using fleet-based targeting
1183- notecard.sendRequest (req);
1184- }
1243+ // Configure hub mode based on power configuration
1244+ configureNotecardHubMode ();
11851245
11861246 req = notecard.newRequest (" card.uuid" );
11871247 J *rsp = notecard.requestAndResponse (req);
@@ -1381,6 +1441,9 @@ static void reinitializeHardware() {
13811441 gTankState [i].lastReportedInches = -9999 .0f ;
13821442 }
13831443
1444+ // Reconfigure Notecard hub settings (may have changed due to power mode)
1445+ configureNotecardHubMode ();
1446+
13841447 Serial.println (F (" Hardware reinitialized after config update" ));
13851448}
13861449
@@ -1435,6 +1498,15 @@ static void applyConfigUpdate(const JsonDocument &doc) {
14351498 if (doc.containsKey (" clearButtonActiveHigh" )) {
14361499 gConfig .clearButtonActiveHigh = doc[" clearButtonActiveHigh" ].as <bool >();
14371500 }
1501+
1502+ // Handle power saving configuration
1503+ if (doc.containsKey (" solarPowered" )) {
1504+ bool newSolarPowered = doc[" solarPowered" ].as <bool >();
1505+ if (newSolarPowered != gConfig .solarPowered ) {
1506+ gConfig .solarPowered = newSolarPowered;
1507+ hardwareChanged = true ; // Need to reconfigure Notecard hub settings
1508+ }
1509+ }
14381510
14391511 if (doc.containsKey (" tanks" )) {
14401512 hardwareChanged = true ; // Tank configuration affects hardware
0 commit comments