Skip to content

Commit 2c4374a

Browse files
authored
Merge pull request #128 from SenaxInc/copilot/add-solar-power-saving-options
Add solar power mode with deep sleep and disable unused Notecard peripherals
2 parents d521ccb + 133fb86 commit 2c4374a

File tree

2 files changed

+91
-10
lines changed

2 files changed

+91
-10
lines changed

TankAlarm-112025-Client-BluesOpta/TankAlarm-112025-Client-BluesOpta.ino

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@
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

363377
struct TankRuntime {
@@ -464,6 +478,7 @@ static bool loadConfigFromFlash(ClientConfig &cfg);
464478
static bool saveConfigToFlash(const ClientConfig &cfg);
465479
static void printHardwareRequirements(const ClientConfig &cfg);
466480
static void initializeNotecard();
481+
static void configureNotecardHubMode();
467482
static void syncTimeFromNotecard();
468483
static double currentEpoch();
469484
static 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

664684
static 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

772795
static 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+
11641231
static 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

TankAlarm-112025-Server-BluesOpta/TankAlarm-112025-Server-BluesOpta.ino

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,14 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
748748
<label class="field"><span>Daily Report Email Recipient</span><input id="dailyEmail" type="email"></label>
749749
</div>
750750
751+
<h3>Power Configuration</h3>
752+
<div class="form-grid">
753+
<label class="field" style="display: flex; align-items: center; gap: 8px; grid-column: 1 / -1;">
754+
<input type="checkbox" id="solarPowered" style="width: auto;">
755+
<span>Solar Powered<span class="tooltip-icon" tabindex="0" data-tooltip="Enable power saving features for solar-powered installations. Uses periodic mode with 60-minute inbound check intervals and deep sleep routines. When disabled (grid-tied), uses continuous mode for faster response times.">?</span></span>
756+
</label>
757+
</div>
758+
751759
<h3>Sensors</h3>
752760
<div id="sensorsContainer"></div>
753761
@@ -1394,6 +1402,7 @@ static const char CONFIG_GENERATOR_HTML[] PROGMEM = R"HTML(
13941402
reportHour: reportHour,
13951403
reportMinute: reportMinute,
13961404
dailyEmail: document.getElementById('dailyEmail').value.trim(),
1405+
solarPowered: document.getElementById('solarPowered').checked,
13971406
tanks: []
13981407
};
13991408

0 commit comments

Comments
 (0)