Should I disable entirely the WDTZero library or disabling the watchdog while moving the files is enough? I sometimes set the WDT_OFF command to my WDZero, but perhaps that is also insufficient.
I posted the file listing of my folder after exporting the compilation. I can only see one bin just 70KB in size, but it felt too small compared to the rest of the files so I do not know which is the one I am meant to upload. Perhaps my MacOS is showing uncompressed all the internal components of that bin you process? This is the full tree for my sketch. All created by the IDE software.
Meland:ControlRiego_0_2_0 me$ ls -laRh
total 120
drwxr-xr-x 6 me staff 192B Jul 26 16:16 .
drwxr-xr-x@ 8 me staff 256B Jul 26 11:25 ..
-rw-r--r--@ 1 me staff 6.0K Jul 26 16:16 .DS_Store
-rw-r--r-- 1 me staff 45K Jul 26 11:25 ControlRiego_0_2_0.ino
-rw-r--r-- 1 me staff 1.0K Jul 26 11:25 arduino_secrets.h
drwxr-xr-x 4 me staff 128B Jul 26 16:16 build
./build:
total 16
drwxr-xr-x 4 me staff 128B Jul 26 16:16 .
drwxr-xr-x 6 me staff 192B Jul 26 16:16 ..
-rw-r--r--@ 1 me staff 6.0K Jul 27 11:29 .DS_Store
drwxr-xr-x 8 me staff 256B Jul 26 16:15 arduino.samd.mkrgsm1400
./build/arduino.samd.mkrgsm1400:
total 5920
drwxr-xr-x 8 me staff 256B Jul 26 16:15 .
drwxr-xr-x 4 me staff 128B Jul 26 16:16 ..
-rwxr-xr-x 1 me staff 69K Jul 26 16:15 ControlRiego_0_2_0.ino.bin
-rwxr-xr-x 1 me staff 1.2M Jul 26 16:15 ControlRiego_0_2_0.ino.elf
-rw-r--r-- 1 me staff 193K Jul 26 16:15 ControlRiego_0_2_0.ino.hex
-rw-r--r-- 1 me staff 1.1M Jul 26 16:15 ControlRiego_0_2_0.ino.map
-rw-r--r-- 1 me staff 77K Jul 26 16:15 ControlRiego_0_2_0.ino.with_bootloader.bin
-rw-r--r-- 1 me staff 210K Jul 26 16:15 ControlRiego_0_2_0.ino.with_bootloader.hex
For the SD based process you need an SD card reader attached to the MKR GSM, right? My apologies if I am saying something stupid, but I feel I am missing something obvious here. I mean, if you can download to the modem FS, then load into RAM, do the size check and integrity check, then reboot into it, wouldn't it be safer to do and requires solely the standard board? Perhaps the external SD card has more read-write cycles available than the internal FS of the modem? Perhaps overusing the internal FS would in the end break the internal modem SO and render the MKR GSM board useless after that? I understand an specific method is not preferable, but there are peculiarities across the MKR family that required specifics for each of them anyway, right?
I am guessing NB1500 does not have a filesystem thus enabling this so specific method is no use for the new generation. Is it? Otherwise, why not take advantage of the filesystem and the OS inside the modem chip? Honest question. I am sure there are more consequences I cannot foresee from my short experience and lacking knowledge.
I am still reading through the library, so please, be patient with my comments and questions. Although I reached far enough to read that for dormant remote devices it is recommended the use of SDU.h as you suggested.
My current sketch relies on SMS messages coming and going, instead of MQTT. It had MQTT implemented on a previous version (I have been tinkering with this for a year and a half already). I am not going to need more than one consumer for the information, so really no benefit to MQTT over SMS. And MQTT required a data connection consuming more power from the battery, DDNS to be kept alive so my server at home is reachable, be careful to resolve the DDNS entry each time it required connection, the trouble of adapting to a forced IP renewal from the ISP, .... So I just installed a GSM dongle (with another SIM from an spare MKR GSM board on the selves) into my Raspberry with SMStools3 and nodered took care of the rest.
For the OTA I was planning on sending an SMS with the OTA command and current valid URL with an expiration timestamp and the MD5 hash, requiring the MKR to refresh the data if download fails or timestamp expires. Then use the modem http download, run the hash at the modem too, then copy to wherever it must be copied into and boot into it.
Still reading. So, please, let me go through it all before. I am sure I am lacking information that is already at the link you provided.
For your reference, this is the sketch in the latest version. Perhaps it could help someone else reading and it not too poorly written. My coding skills have been diminishing over the years, not improving. 
#include "arduino_secrets.h"
#include <MKRGSM.h>
#include <WDTZero.h>
#include <ArduinoLowPower.h>
#include <Arduino_PMIC.h>
#include <Modem.h>
#include <RTCZero.h>
#include <QuickMedianLib.h>
/****************************************************************************/
/* DEFINITIONS SECTION */
/****************************************************************************/
/////////
// LOG //
/////////
/* Log levels */
constexpr int LOG_LEVEL_SILENT = 0;
constexpr int LOG_LEVEL_ERROR = 1;
constexpr int LOG_LEVEL_WARN = 2;
constexpr int LOG_LEVEL_INFO = 3;
constexpr int LOG_LEVEL_TRACE = 4;
/* Log variable */
int LOG_LEVEL = LOG_LEVEL_TRACE;
#define CONSOLE_LOGGING // Enables log messages to Serial console
#ifdef CONSOLE_LOGGING
#define consoleLogging(nivel, mensaje) \
if (nivel <= LOG_LEVEL) { \
Serial.print("LOGLVL "); \
Serial.print(nivel); \
Serial.print(": "); \
Serial.println(mensaje); \
}
#else
#define consoleLogging(nivel, mensaje)
#endif
//////////////////////
// Power management //
//////////////////////
// The IRQ is not called when in deep sleep mode, so this code is useless
volatile unsigned long timeLastBatteryInterrupt = 0;
volatile bool batteryInterruptFired = false;
bool batteryPowered = false;
////////////////////
// Self awareness //
////////////////////
#define watchdogBarkPeriod WDT_SOFTCYCLE2M
constexpr int sketchUpdateWindow = 30000;
constexpr int peripheralStabilizationTime = 10000;
////////////////
// Duty cycle //
////////////////
constexpr int HALF_SECOND = 500;
constexpr int ONE_SECOND = 1000;
constexpr int TWO_SECONDS = 2000;
constexpr int THREE_SECONDS = 3000;
constexpr int FOUR_SECONDS = 4000;
constexpr int FIVE_SECONDS = 5000;
constexpr int TEN_SECONDS = 5000;
constexpr int ONE_MINUTE = 60000;
constexpr int TWO_MINUTES = 120000;
constexpr int FIVE_MINUTES = 300000;
constexpr int TEN_MINUTES = 600000;
constexpr int HALF_HOUR = 1800000;
constexpr int ONE_HOUR = 3600000;
constexpr int onBatteryLoopPeriod = ONE_HOUR;
constexpr int onMainPowerloopPeriod = TEN_MINUTES;
int loopPeriod = onMainPowerloopPeriod;
//////////////////////
// GSM network data //
//////////////////////
char SIM_PIN[5];
const char* controlPhoneNumber[] = SECRET_CONTROL_PHONE_NUMBER;
const char* reportPhoneNumber[] = SECRET_REPORT_PHONE_NUMBER;
constexpr int GSM_CONNECT_WAITING_TIME = 10000;
constexpr int GSM_MAX_CONNECT_ATTEMPTS = 9;
constexpr int SMS_MAXIMUM_LENGTH = 140;
/////////////////////////////////////////////////////////////////
// Sensor attributes //
/////////////////////////////////////////////////////////////////
int sensorSamplingPeriod = FIVE_SECONDS;
int sensorSamplesPerSampling = 5;
// Rain only shallow moisture sensor pin and attributes
#define SENSOR_ON_A1
// Rain only deep moisture sensor pin and attributes
#define SENSOR_ON_A2
// Rain and irrigation deep moisture sensor pin and attributes
#define SENSOR_ON_A3
// Water source pressure sensor pin and attributes
#define SENSOR_ON_A4
// Temperature sensor pin and attributes
#undef SENSOR_ON_A5
// Atributes for all sensors if defined the SENSOR on each PIN
// It meassures higher the drier, lower the wetter
// 100% dry = 0% wet = 575, 0% dry = 100% wet = 308 when powered at 5V
#ifdef SENSOR_ON_A1
#define SENSOR_A1 "M0" //"shallow reference moisture"
constexpr int SENSOR_A1_PIN = A1;
constexpr int SENSOR_A1_MIN_VALUE = 0;
constexpr int SENSOR_A1_MAX_VALUE = 100;
constexpr int SENSOR_A1_MIN_READING = 308;
constexpr int SENSOR_A1_MAX_READING = 575;
#endif
// It meassures higher the drier, lower the wetter
// 100% dry = 0% wet = 575, 0% dry = 100% wet = 308 when powered at 5V
#ifdef SENSOR_ON_A2
#define SENSOR_A2 "M1" //"deep reference moisture"
constexpr int SENSOR_A2_PIN = A2;
constexpr int SENSOR_A2_MIN_VALUE = 0;
constexpr int SENSOR_A2_MAX_VALUE = 100;
constexpr int SENSOR_A2_MIN_READING = 308;
constexpr int SENSOR_A2_MAX_READING = 575;
#endif
// It meassures higher the drier, lower the wetter
// 100% dry = 0% wet = 575, 0% dry = 100% wet = 308 when powered at 5V
#ifdef SENSOR_ON_A3
#define SENSOR_A3 "M2" //"deep undertree moisture"
constexpr int SENSOR_A3_PIN = A3;
constexpr int SENSOR_A3_MIN_VALUE = 0;
constexpr int SENSOR_A3_MAX_VALUE = 100;
constexpr int SENSOR_A3_MIN_READING = 308;
constexpr int SENSOR_A3_MAX_READING = 575;
#endif
// Water source pressure sensor pin and attributes
// Reading 98 = 0 mBar, reading 900 = 5000mBar
// Expected error <100mBar => only dBar make sense, not mBar
#ifdef SENSOR_ON_A4
#define SENSOR_A4 "P0" //"water source pressure"
constexpr int SENSOR_A4_PIN = A4;
constexpr int SENSOR_A4_MIN_VALUE = 0;
constexpr int SENSOR_A4_MAX_VALUE = 5000;
constexpr int SENSOR_A4_MIN_READING = 98;
constexpr int SENSOR_A4_MAX_READING = 900;
#endif
// PENDING CALIBRATION
#ifdef SENSOR_ON_A5
#define SENSOR_A5 "T0" //"temperature"
constexpr int SENSOR_A5_PIN = A5;
constexpr int SENSOR_A5_MIN_VALUE = 0;
constexpr int SENSOR_A5_MAX_VALUE = 5000;
constexpr int SENSOR_A5_MIN_READING = -15;
constexpr int SENSOR_A5_MAX_READING = 65;
#endif
////////////////////
// Reporting data //
////////////////////
#define REPORT_HEADER "TS,M0,M1,M2,P0,T0\n"
#define MAX_REPORT_SAMPLES 4
/****************************************************************************/
/* INSTANCES SECTION */
/****************************************************************************/
////////////////////
// Self awareness //
////////////////////
WDTZero myWatchDog;
///////////////////////////////////
// Instances for Real Time Clock //
///////////////////////////////////
RTCZero rtc;
///////////////////////////////////
// Instances for GSM/GPRS module //
///////////////////////////////////
GSMPIN myPINmanager;
GSM myGsmAccess;
GSM_SMS mySms;
GSMModem myGSMModem;
///////////////////////////////////
// Instances for reporting //
///////////////////////////////////
String manySamplesInCsvString = REPORT_HEADER;
int storedSamplesInCsvString = 0;
/****************************************************************************/
/* FUNCTION DECLARATIONS */
/****************************************************************************/
void initializeSerial();
void initializeWatchdog();
void initializePMIC();
void initializeGSM();
void synchronizeTime();
void initializeSensors();
void initializeReporting();
void myWatchDogShutdown();
void batteryInterrupt();
void restartBoard();
String doATcommandModemGSM(const char* s, unsigned long timeout);
void hardwarepPowerOffModemGSM();
void sendStringSMStoControl(String message, int retries, int period);
void sendStringSMStoReport(String message, int retries, int period);
int ledBlink(int durationMS, int periodMS);
int ledBlinkIdleCPU(int durationMS, int periodMS);
/****************************************************************************/
/* SETUP & LOOP SECTION */
/****************************************************************************/
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
initializeSerial();
consoleLogging(LOG_LEVEL_INFO, "SETUP: Starting update window.");
ledBlink(sketchUpdateWindow, TWO_SECONDS);
consoleLogging(LOG_LEVEL_INFO, "SETUP: Finished update window.");
initializeWatchdog();
initializePMIC();
initializeGSM();
synchronizeTime();
initializeSensors();
initializeReporting();
sendStringSMStoControl("Board finished setup after a reboot or cold restart.", 2, FIVE_SECONDS);
// We know GSM works, so GSM module is throttled down to low power mode while not required
myGsmAccess.lowPowerMode();
digitalWrite(LED_BUILTIN, LOW); // turn the LED off (LOW is the ground voltage)
consoleLogging(LOG_LEVEL_INFO, "SETUP: Done.");
}
void loop() {
consoleLogging(LOG_LEVEL_INFO, "LOOP: Starting a loop.");
digitalWrite(LED_BUILTIN, HIGH);
//////////////////////////////////////////
// Control the duty cycle of activities //
//////////////////////////////////////////
consoleLogging(LOG_LEVEL_INFO, "LOOP: Going to sleep for " + String(loopPeriod) + " ms.");
// Disable watchdog timer
myWatchDog.setup(WDT_OFF);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off (LOW is the ground level)
// Put the board into sleep mode so IRQ can work but ADC and all peripherals are powered off
LowPower.sleep(loopPeriod);
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
// Enable watchdog timer before running the loop
myWatchDog.setup(watchdogBarkPeriod);
consoleLogging(LOG_LEVEL_TRACE, "LOOP: Waking from sleep.");
consoleLogging(LOG_LEVEL_INFO, "LOOP: Starting peripherals stabilization after being powered off because of system sleep.");
//Blink the led while we wait for peripherals
ledBlinkIdleCPU(peripheralStabilizationTime, HALF_SECOND);
consoleLogging(LOG_LEVEL_INFO, "LOOP: Finished peripherals stabilization.");
//////////////////////////////////////
// Power Management Integrated Chip //
//////////////////////////////////////
myWatchDog.clear();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Power Management both for battery control Integrated Chip and power saving modes of board, GSM chip and may be ADC //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
To-Do:
- Poweroff the GSM modem for as long as it makes sense and power up to send many stored SMS
· modem is powered off with the command AT+CPWROFF from SARA-201 docs, but GSM class does have a shutdown so...
· maight be better to just keep using the lowpowermode since idle mode did not deplete the battery too fast
*/
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Backup Battery Voltage is " + String(analogRead(ADC_BATTERY) * (4208 / 1023)) + " mV.");
// System Status Register
consoleLogging(LOG_LEVEL_TRACE, "PMIC: USBmode (UNKNOWN_MODE 0x00, USB_HOST_MODE 0x40, ADAPTER_PORT_MODE 0x80, BOOST_MODE 0xC0) --> 0x" + String(PMIC.USBmode(), 16));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: chargeStatus (NOT_CHARGING 0x00, PRE_CHARGING 0x10, FAST_CHARGING 0x20, CHARGE_TERMINATION_DONE 0x30) --> 0x" + String(PMIC.chargeStatus(), 16));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: isBattConnected --> " + String(PMIC.isBattConnected()));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: isPowerGood --> " + String(PMIC.isPowerGood()));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: isHot --> " + String(PMIC.isHot()));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: canRunOnBattery --> " + String(PMIC.canRunOnBattery()));
// Fault Register
consoleLogging(LOG_LEVEL_TRACE, "PMIC: isWatchdogExpired --> " + String(PMIC.isWatchdogExpired()));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: getChargeFault (NO_CHARGE_FAULT 0x00, INPUT_OVER_VOLTAGE 0x10, THERMAL_SHUTDOWN 0x20, CHARGE_SAFETY_TIME_EXPIRED 0x30) --> 0x" + String(PMIC.getChargeFault(), 16));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: isBatteryInOverVoltage --> " + String(PMIC.isBatteryInOverVoltage()));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: hasBatteryTemperatureFault (NO_TEMPERATURE_FAULT 0x00, LOWER_THRESHOLD_TEMPERATURE_FAULT 0x05, HIGHER_THRESHOLD_TEMPERATURE_FAULT 0x06) --> 0x" + String(PMIC.hasBatteryTemperatureFault(), 16));
/*****************************************************************************************************
PENDING: Figure out if using the IRQ means the loop resumes right after the IRQ call,
when fired while in idle, sleep or deepsleep
or if the system goes back to idle, sleep or deepsleep
--> If it continues inmediately with the loop, it would mess up the schedule of reports
*/
consoleLogging(LOG_LEVEL_INFO, "PMIC: Battery IRQ flag is " + String(batteryInterruptFired));
if (batteryInterruptFired) {
batteryInterruptFired = false; // Reset the flag
consoleLogging(LOG_LEVEL_INFO, "PMIC: Battery IRQ flag changed to " + String(batteryInterruptFired));
}
bool mainSourcePowered = PMIC.isPowerGood();
consoleLogging(LOG_LEVEL_INFO, batteryPowered ? "PMIC: Battery powered" : "Main source powered.");
consoleLogging(LOG_LEVEL_TRACE, "PMIC: batteryPowered = " + String(batteryPowered));
consoleLogging(LOG_LEVEL_TRACE, "PMIC: mainSourcePowered = " + String(mainSourcePowered));
// Check if the board is running on battery but it was not the last loop
if (!batteryPowered && !mainSourcePowered) {
consoleLogging(LOG_LEVEL_ERROR, "PMIC: Lost main power. Running on battery! NO REPORTS WILL BE SENT.");
batteryPowered = true;
myGsmAccess.noLowPowerMode();
ledBlink(TWO_SECONDS, HALF_SECOND); // Wait for the modem to fully come back from lowpowermode
sendStringSMStoControl("Lost main power. Running on battery! NO REPORTS WILL BE SENT.", 10, FIVE_SECONDS);
myGsmAccess.secureShutdown();
loopPeriod = onBatteryLoopPeriod;
}
// Check if the board was running on battery the last loop but not anymore
if (batteryPowered && mainSourcePowered) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Main power source restored. REPORTING WILL BE RESUMED.");
batteryPowered = false;
initializeGSM();
synchronizeTime();
sendStringSMStoControl("Main power source restored. REPORTING WILL BE RESUMED.", 2, FIVE_SECONDS);
myGsmAccess.lowPowerMode();
loopPeriod = onMainPowerloopPeriod;
}
/************************************************************************************************************
When running on battery the reference voltage for sensors at the "connector carrier" board grove connectors
drops below +5v rendering sensor scaling voltage vs reading useless. We cannot report these wrong data.
The reference level drops because the reference is the +5V PIN at the connector carrier power buck. Since
the main power source is not feeding the "connector carrier" power buck though the VIN PIN, the output pins
change voltage.
PENDING:
- We should probably power +5V PIN down to shutdown sensors and prevent damage to those that do not support it.
- If we can use IRQ now in sleep mode, we could sleep forever when batterypowered because all we do is check if
the main power source is recovered
*/
if (batteryPowered) {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Backup Battery Voltage is " + String(analogRead(ADC_BATTERY) * (4208 / 1023)) + " mV.");
consoleLogging(LOG_LEVEL_ERROR, "PMIC: Running on battery. Ending this loop now.");
digitalWrite(LED_BUILTIN, LOW); // turn the LED off (LOW is the ground level)
return;
}
myWatchDog.clear();
///////////////////////////////////////////
// Checking status before doing anything //
///////////////////////////////////////////
// Check if GSM is ready
consoleLogging(LOG_LEVEL_TRACE, "GSM: We do not check GSM status at the loop anymore because we power it on only to report.");
myWatchDog.clear();
///////////////////////////////////////////////
// Finally, let's do what we are meant to do //
///////////////////////////////////////////////
// Variables to store samples
#ifdef SENSOR_ON_A1
int sensorA1Readings[sensorSamplesPerSampling];
#endif
#ifdef SENSOR_ON_A2
int sensorA2Readings[sensorSamplesPerSampling];
#endif
#ifdef SENSOR_ON_A3
int sensorA3Readings[sensorSamplesPerSampling];
#endif
#ifdef SENSOR_ON_A4
int sensorA4Readings[sensorSamplesPerSampling];
#endif
#ifdef SENSOR_ON_A5
int sensorA5Readings[sensorSamplesPerSampling];
#endif
consoleLogging(LOG_LEVEL_INFO, "SAMPLING: Starting taking samples of sensor readings.");
for (int i = 0; i < sensorSamplesPerSampling; i++) {
#ifdef SENSOR_ON_A1
sensorA1Readings[i] = analogRead(SENSOR_A1_PIN);
#endif
#ifdef SENSOR_ON_A2
sensorA2Readings[i] = analogRead(SENSOR_A2_PIN);
#endif
#ifdef SENSOR_ON_A3
sensorA3Readings[i] = analogRead(SENSOR_A3_PIN);
#endif
#ifdef SENSOR_ON_A4
sensorA4Readings[i] = analogRead(SENSOR_A4_PIN);
#endif
#ifdef SENSOR_ON_A5
sensorA5Readings[i] = analogRead(SENSOR_A5_PIN);
#endif
myWatchDog.clear();
ledBlinkIdleCPU(sensorSamplingPeriod, HALF_SECOND);
myWatchDog.clear();
}
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Finished taking samples of sensor readings.");
unsigned long timestamp = rtc.getEpoch();
manySamplesInCsvString += String(timestamp);
consoleLogging(LOG_LEVEL_TRACE, String("SAMPLING: Report string with report header follows.\n") + manySamplesInCsvString);
#ifdef SENSOR_ON_A1
int sensorA1Median = QuickMedian< int >::GetMedian(sensorA1Readings, sensorSamplesPerSampling);
int sensorA1toReport = 100 - map(sensorA1Median, SENSOR_A1_MIN_READING, SENSOR_A1_MAX_READING, SENSOR_A1_MIN_VALUE, SENSOR_A1_MAX_VALUE);
manySamplesInCsvString += "," + String(sensorA1toReport);
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: " + String(SENSOR_A1) + " reports: " + String(sensorA1Median));
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
myWatchDog.clear();
#else
manySamplesInCsvString += ",";
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
#endif
#ifdef SENSOR_ON_A2
int sensorA2Median = QuickMedian< int >::GetMedian(sensorA2Readings, sensorSamplesPerSampling);
int sensorA2toReport = 100 - map(sensorA2Median, SENSOR_A2_MIN_READING, SENSOR_A2_MAX_READING, SENSOR_A2_MIN_VALUE, SENSOR_A2_MAX_VALUE);
manySamplesInCsvString += "," + String(sensorA2toReport);
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: " + String(SENSOR_A2) + " reports: " + String(sensorA2Median));
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
myWatchDog.clear();
#else
manySamplesInCsvString += ",";
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
#endif
#ifdef SENSOR_ON_A3
int sensorA3Median = QuickMedian< int >::GetMedian(sensorA3Readings, sensorSamplesPerSampling);
int sensorA3toReport = 100 - map(sensorA3Median, SENSOR_A3_MIN_READING, SENSOR_A3_MAX_READING, SENSOR_A3_MIN_VALUE, SENSOR_A3_MAX_VALUE);
manySamplesInCsvString += "," + String(sensorA3toReport);
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: " + String(SENSOR_A3) + " reports: " + String(sensorA3Median));
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
myWatchDog.clear();
#else
manySamplesInCsvString += ",";
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
#endif
#ifdef SENSOR_ON_A4
int sensorA4Median = QuickMedian< int >::GetMedian(sensorA4Readings, sensorSamplesPerSampling);
int sensorA4toReport = map(sensorA4Median, SENSOR_A4_MIN_READING, SENSOR_A4_MAX_READING, SENSOR_A4_MIN_VALUE, SENSOR_A4_MAX_VALUE);
manySamplesInCsvString += "," + String(sensorA4toReport);
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: " + String(SENSOR_A4) + " reports: " + String(sensorA4Median));
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
myWatchDog.clear();
#else
manySamplesInCsvString += ",";
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
#endif
#ifdef SENSOR_ON_A5
int sensorA5Median = QuickMedian< int >::GetMedian(sensorA5Readings, sensorSamplesPerSampling);
int sensorA5toReport = map(sensorA5Median, SENSOR_A5_MIN_READING, SENSOR_A5_MAX_READING, SENSOR_A5_MIN_VALUE, SENSOR_A5_MAX_VALUE);
manySamplesInCsvString += "," + String(sensorA5toReport);
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: " + String(SENSOR_A5) + " reports: " + String(sensorA5Median));
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
myWatchDog.clear();
#else
manySamplesInCsvString += ",";
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string:\n" + manySamplesInCsvString);
#endif
manySamplesInCsvString += "\n";
storedSamplesInCsvString++;
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: All readings completed this loop.");
consoleLogging(LOG_LEVEL_INFO, "SAMPLING: Report string:\n" + manySamplesInCsvString);
consoleLogging(LOG_LEVEL_TRACE, "REPORTING: Reports in the string --> " + String(storedSamplesInCsvString));
if (storedSamplesInCsvString >= MAX_REPORT_SAMPLES) {
// If we are going to report, GSM must be enpowered first
consoleLogging(LOG_LEVEL_INFO, "REPORTING: GSM should be in low power mode. Let's feed it properly.");
myGsmAccess.noLowPowerMode();
ledBlink(TWO_SECONDS, HALF_SECOND); // Wait for the modem to fully come back from lowpowermode
consoleLogging(LOG_LEVEL_TRACE, "REPORTING: About to send the report in an SMS.");
// REPORT on SMS
sendStringSMStoReport(manySamplesInCsvString, 3, FIVE_SECONDS);
// We already reported, so GSM must be shutdown to save power
consoleLogging(LOG_LEVEL_INFO, "REPORTING: GSM should be in full power mode. Let it starve to save battery.");
myGsmAccess.lowPowerMode();
// Then the sampling process resumes as if there was no power optimization
manySamplesInCsvString = REPORT_HEADER;
storedSamplesInCsvString = 0;
consoleLogging(LOG_LEVEL_INFO, "REPORTING: SMS sent with combined readings.");
}
consoleLogging(LOG_LEVEL_TRACE, "SAMPLING: Report string at the end of the loop:\n" + manySamplesInCsvString);
consoleLogging(LOG_LEVEL_INFO, "LOOP: All tasks completed this loop.");
digitalWrite(LED_BUILTIN, LOW);
}
/****************************************************************************/
/* FUNCTION DEFINITIONS */
/****************************************************************************/
void initializeSerial() {
Serial.begin(9600);
while (!Serial) {
delay(500);
}
consoleLogging(LOG_LEVEL_INFO, "CONSOLE: Serial USB initialized. Logging to console can now be used.");
}
/*
PENDING: It seems to be OK and forum fellows tell me so,
but still should check if this library is compatible with RTCZero used for PMIC library.
*/
void initializeWatchdog() {
consoleLogging(LOG_LEVEL_INFO, "WATCHDOG: Starting...");
myWatchDog.attachShutdown(myWatchDogShutdown);
myWatchDog.setup(watchdogBarkPeriod);
consoleLogging(LOG_LEVEL_INFO, "WATCHDOG: Initialized!");
}
void initializePMIC() {
///////////////////////////////////////////////////
// Power Management Integrated Chip BQ24195LRGET //
///////////////////////////////////////////////////
/* DO NOT NEED PMIC LIBRARY OR/AND DO NOT UNDERSTAND IT */
// Clear watchdog counter
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Pet the watchdog before starting PMIC.");
myWatchDog.clear(); // Let the dog know we are still in business
consoleLogging(LOG_LEVEL_INFO, "PMIC: Starting...");
// The IRQ is not called when in deep sleep mode, so this code is useless unless sleep mode does allow for IRQs to work
// Available only for MKRGSM1400 and MKRNB1500
#if defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKRNB1500)
// Attach the PMIC IRQ pin
attachInterrupt(digitalPinToInterrupt(PMIC_IRQ_PIN), batteryInterrupt, FALLING);
consoleLogging(LOG_LEVEL_INFO, "PMIC: Just attached IRQ with battery IRQ flag = " + String(batteryInterruptFired));
#endif
// Initialize the power management integrated circuit that charges and uses the battery
if (!PMIC.begin()) {
consoleLogging(LOG_LEVEL_ERROR, "PMIC: Failed to initialize PMIC! Going for infinite sleep.");
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Disabling the watchdog before going to sleep.");
myWatchDog.setup(WDT_OFF);
do {
// Shutdown the GSM module down and set the board into deepsleep mode
myGsmAccess.secureShutdown();
LowPower.deepSleep();
// Somehow we were awaken from infinite sleep, so lets loop.
consoleLogging(LOG_LEVEL_WARN, "PMIC: Awaken from infinite deepsleep due to PMIC initialization error. Lets deepsleep again.");
} while (true);
} else {
// Reset the interrupt variable since the interrupt function is allways called upon setting it
batteryInterruptFired = false;
}
consoleLogging(LOG_LEVEL_TRACE, "PMIC started properly: Battery IRQ flag = " + String(batteryInterruptFired));
myWatchDog.clear(); // Let the dog know we are still in business
// Set the input current limit to 2 A
if (!PMIC.setInputCurrentLimit(2.0)) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error in set input current limit");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Successfully set input current limit");
}
// Set the overload input voltage to 3.88 V
if (!PMIC.setInputVoltageLimit(3.88)) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error in set input voltage limit");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Successfully set input voltage limit");
}
// Set the minimum voltage used to feeding the module embed on Board
if (!PMIC.setMinimumSystemVoltage(3.5)) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error in set minimum system voltage");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Successfully set minimum system voltage");
}
// Set the desired charge voltage to 4.11 V
if (!PMIC.setChargeVoltage(4.2)) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error in set charge voltage");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Successfully set charge voltage");
}
// Set the charge current 500 mA
if (!PMIC.setChargeCurrent(0.500)) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error in set charge current");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Successfully set charge current");
}
// Enable the Charger
if (!PMIC.enableCharge()) {
consoleLogging(LOG_LEVEL_WARN, "PMIC: Error enabling Charge mode");
} else {
consoleLogging(LOG_LEVEL_TRACE, "PMIC: Charge mode enabled");
}
myWatchDog.clear(); // Let the dog know we are still in business
consoleLogging(LOG_LEVEL_INFO, "PMIC: initialization done!");
}
void initializeGSM() {
consoleLogging(LOG_LEVEL_INFO, "GSM: Starting...");
// Wait for the GSM module to start
while (!myGSMModem.begin()) {
consoleLogging(LOG_LEVEL_TRACE, "GSM: Trying to connect to the GSM modem...");
delay(1000);
}
ledBlink(5000, 1000);
consoleLogging(LOG_LEVEL_TRACE, "GSM: Pet the watchdog before checking SIM card status.");
myWatchDog.clear(); // Let the dog know we are still in business
// Check SIM status if enabled
myPINmanager.begin();
int myPINstatus = myPINmanager.isPIN();
if (myPINstatus == 0) {
// SIM does not require PIN
handleSIMwithoutPIN();
} else if (myPINstatus == 1) {
// SIM requires PIN
handleSIMRequiresPIN();
} else if (myPINstatus == -1) {
// PIN is locked
handleSIMLocked("PIN");
} else if (myPINstatus == -2) {
// PUK is locked
handleSIMLocked("PUK");
} else {
// Unknown status
handleUnknownSIMStatus();
}
consoleLogging(LOG_LEVEL_TRACE, "GSM: Pet the watchdog before starting GSM connection.");
myWatchDog.clear(); // Let the dog know we are still in business
// Attempt to connect to GSM
int attemptsConnectGSM = 0;
do {
GSM3_NetworkStatus_t lastCallGsmAccessReturned = myGsmAccess.begin(SIM_PIN);
if (lastCallGsmAccessReturned == ERROR) {
consoleLogging(LOG_LEVEL_ERROR, "GSM: myGsmAccess.begin(SIM_PIN) returned ERROR, but must be network unavailable because the PIN should be OK. If not, it will lock the SIM.");
} else if (lastCallGsmAccessReturned == IDLE) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned IDLE");
} else if (lastCallGsmAccessReturned == CONNECTING) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned CONNECTING");
} else if (lastCallGsmAccessReturned == GSM_READY) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned GSM_READY");
break;
} else if (lastCallGsmAccessReturned == GPRS_READY) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned GPRS_READY");
} else if (lastCallGsmAccessReturned == TRANSPARENT_CONNECTED) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned TRANSPARENT_CONNECTED");
} else if (lastCallGsmAccessReturned == GSM_OFF) {
consoleLogging(LOG_LEVEL_INFO, "GSM: myGsmAccess.begin(SIM_PIN) returned GSM_OFF");
}
attemptsConnectGSM++;
consoleLogging(LOG_LEVEL_INFO, "GSM: [" + String(attemptsConnectGSM) + "/" + String(GSM_MAX_CONNECT_ATTEMPTS) + "] GSM not connected yet.");
// Disable watchdog timer
consoleLogging(LOG_LEVEL_TRACE, "GSM: Disabling the watchdog before going to idle waiting to GSM connections.");
myWatchDog.setup(WDT_OFF);
// Put the board into sleep mode
ledBlinkIdleCPU(GSM_CONNECT_WAITING_TIME, HALF_SECOND);
// Enable watchdog timer
consoleLogging(LOG_LEVEL_TRACE, "GSM: Re-enabling the watchdog after going to idle waiting to GSM connections.");
myWatchDog.setup(watchdogBarkPeriod); // initialize WDT soft counter to 2 minutes interval since GSM is slow to connect
} while (attemptsConnectGSM <= GSM_MAX_CONNECT_ATTEMPTS);
consoleLogging(LOG_LEVEL_INFO, "GSM: Connection loop ended at [" + String(attemptsConnectGSM + 1) + "/" + String(GSM_MAX_CONNECT_ATTEMPTS) + "].");
consoleLogging(LOG_LEVEL_TRACE, "GSM: Pet the watchdog after the GSM connection loop.");
myWatchDog.clear();
// Check if maximum connection attempts were reached
if (attemptsConnectGSM >= GSM_MAX_CONNECT_ATTEMPTS) {
handleMaxConnectionAttempts();
} else {
consoleLogging(LOG_LEVEL_TRACE, "GSM: Pet the watchdog after the GSM initialization.");
myWatchDog.clear(); // Let the dog know we are still in business
consoleLogging(LOG_LEVEL_INFO, "GSM: Initialization done!");
}
}
void synchronizeTime() {
consoleLogging(LOG_LEVEL_TRACE, "TIME: Disabling the watchdog before time synch with the GSM datetime.");
myWatchDog.setup(WDT_OFF);
rtc.begin();
consoleLogging(LOG_LEVEL_INFO, "TIME: Pre synch epoch is " + String(rtc.getEpoch()));
consoleLogging(LOG_LEVEL_INFO, "TIME: Synchronizing date and time with GSM network");
/*********************************************************************************************
THERE IS A GSM.GETTIME() FUNCTION AT MKRGSM LIBRARY, BUT IT RETURNED WRONG DATA IN THE PAST.
*********************************************************************************************/
long gsmTime = myGsmAccess.getTime();
if (gsmTime) {
rtc.setEpoch(gsmTime);
consoleLogging(LOG_LEVEL_INFO, "TIME: Time synchronized with GSM network as epoch " + String(rtc.getEpoch()));
} else {
consoleLogging(LOG_LEVEL_WARN, "TIME: Failed to get date and time from GSM network");
}
consoleLogging(LOG_LEVEL_TRACE, "TIME: Re-enabling the watchdog before time synch with the GSM datetime.");
myWatchDog.setup(watchdogBarkPeriod); // initialize WDT soft counter to 2 minutes interval since GSM is so slow to connect
consoleLogging(LOG_LEVEL_INFO, "TIME: Clock synchronization done!");
}
void initializeReporting() {
manySamplesInCsvString = REPORT_HEADER;
storedSamplesInCsvString = 0;
}
/****************************************************************************/
/* PMIC FUNCTIONS */
/****************************************************************************/
// The IRQ is not called when in deep sleep mode, so this code is useless unless sleep mode allows this IRQ
void batteryInterrupt() {
//Should be quick simple and straightforward, keep track of when the battery stopped charging
consoleLogging(LOG_LEVEL_WARN, "PMIC: Battery interrupt!");
batteryInterruptFired = true; // Set the flag to true to indicate power source change
}
/****************************************************************************/
/* GSM FUNCTIONS */
/****************************************************************************/
String doATcommandModemGSM(const char* s, unsigned long timeout) {
String doATresponse;
doATresponse.reserve(15);
MODEM.send(s);
#ifdef CONSOLE_LOGGING
int doATreturn =
#endif
MODEM.waitForResponse(timeout, &doATresponse);
consoleLogging(LOG_LEVEL_TRACE, "Modem received \"" + String(s) + "\" command, returned \"" + String(doATreturn) + "\" with message \"" + String(doATresponse) + "\"");
return doATresponse;
}
bool getPINbyICCID(const char* iccid, char* pin) {
for (int i = 0; i < numSIMCards; i++) {
if (strcmp(simCards[i].iccid, iccid) == 0) {
strcpy(pin, simCards[i].pin);
return true;
}
}
return false;
}
void handleSIMwithoutPIN() {
consoleLogging(LOG_LEVEL_INFO, "GSM: SIM card reports no PIN required. Lets check if we can enable it.");
// Get ICCID of the SIM
String iccid = myGSMModem.getICCID();
consoleLogging(LOG_LEVEL_TRACE, "GSM: This SIM card ICCID is " + iccid);
// Convert String to char array
char iccidChar[21];
iccid.toCharArray(iccidChar, 21);
// Find the corresponding PIN
if (getPINbyICCID(iccidChar, SIM_PIN)) {
consoleLogging(LOG_LEVEL_INFO, "GSM: This SIM card has a stored PIN of [" + String(SIM_PIN) + "]");
consoleLogging(LOG_LEVEL_TRACE, "GSM: PIN lock will be enabled on this SIM now, if PIN is accepted.");
myPINmanager.switchPIN(SIM_PIN);
} else {
consoleLogging(LOG_LEVEL_ERROR, "GSM: This SIM card (ICCID) has no stored PIN.");
}
}
void handleSIMRequiresPIN() {
consoleLogging(LOG_LEVEL_INFO, "GSM: SIM card reports PIN required. PIN management needed.");
// Get ICCID of the SIM
String iccid = myGSMModem.getICCID();
consoleLogging(LOG_LEVEL_TRACE, "GSM: This SIM card ICCID is " + iccid);
// Convert String to char array
char iccidChar[21];
iccid.toCharArray(iccidChar, 21);
// Find the corresponding PIN
if (!getPINbyICCID(iccidChar, SIM_PIN)) {
handleMissingPIN();
}
consoleLogging(LOG_LEVEL_INFO, "GSM: This SIM card (ICCID) has a stored PIN of " + String(SIM_PIN));
}
void handleSIMLocked(const char* lockType) {
// Set watchdog timer off
myWatchDog.setup(WDT_OFF);
// Loop forever because we do not want to automatically use the PUK or handle locked status.
consoleLogging(LOG_LEVEL_ERROR, "GSM: SIM card reports " + String(lockType) + " is locked.");
// Shutdown the GSM module and set the board into deep sleep mode forever inside a loop
myGsmAccess.secureShutdown();
do {
ledBlinkSOSidleCPU();
LowPower.deepSleep();
// Somehow we were awakened from infinite sleep, so let's loop.
consoleLogging(LOG_LEVEL_WARN, "GSM: Awaken from infinite sleep due to SIM card being " + String(lockType) + " locked. Let's sleep again.");
} while (true);
}
void handleUnknownSIMStatus() {
// Set watchdog timer off
myWatchDog.setup(WDT_OFF);
// Loop forever because we cannot tell what happened.
consoleLogging(LOG_LEVEL_ERROR, "GSM: SIM card reports unknown PIN status.");
// Shutdown the GSM module and set the board into deep sleep mode forever inside a loop
myGsmAccess.secureShutdown();
do {
ledBlinkSOSidleCPU();
LowPower.deepSleep();
// Somehow we were awakened from infinite sleep, so let's loop.
consoleLogging(LOG_LEVEL_WARN, "GSM: Awaken from infinite sleep due to SIM unknown PIN status. Let's sleep again.");
} while (true);
}
void handleMissingPIN() {
consoleLogging(LOG_LEVEL_ERROR, "GSM: SIM card reports PIN required but none found.");
// Shutdown the GSM module and set the board into deep sleep mode forever inside a loop
myWatchDog.setup(WDT_OFF);
myGsmAccess.secureShutdown();
do {
ledBlinkSOSidleCPU();
LowPower.deepSleep();
// Somehow we were awakened from infinite sleep, so let's loop.
consoleLogging(LOG_LEVEL_WARN, "GSM: Awakened from infinite sleep due to SIM card required PIN but none found. Let's sleep again.");
} while (true);
}
void handleIncorrectPIN() {
consoleLogging(LOG_LEVEL_ERROR, "GSM: SIM card reports PIN required but the one found is not accepted.");
// Shutdown the GSM module and set the board into deep sleep mode forever inside a loop
myWatchDog.setup(WDT_OFF);
myGsmAccess.secureShutdown();
do {
ledBlinkSOSidleCPU();
LowPower.deepSleep();
// Somehow we were awakened from infinite sleep, so let's loop.
consoleLogging(LOG_LEVEL_WARN, "GSM: Awakened from infinite sleep due to SIM card required PIN but the chosen one was wrong. Let's sleep again.");
} while (true);
}
void handleMaxConnectionAttempts() {
consoleLogging(LOG_LEVEL_ERROR, "GSM: Maximum connection attempts reached. Going to sleep then restart.");
// Disable watchdog timer
myWatchDog.setup(WDT_OFF);
// Shutdown the GSM module and set the board into deep sleep mode for a loop period in the hope something could change
myGsmAccess.secureShutdown();
ledBlinkSOSidleCPU();
LowPower.deepSleep(loopPeriod);
consoleLogging(LOG_LEVEL_WARN, "GSM: Awoke after a nap after maximum connection attempts were reached. Going to restart.");
restartBoard();
}
/*
// Function to calculate the number of phone numbers
size_t getNumberOfPhoneNumbers(const char* phoneNumbers[]) {
return sizeof(phoneNumbers) / sizeof(phoneNumbers[0]);
}
*/
// Template function to calculate the number of elements in an array at compile time
template<typename T, size_t N>
constexpr size_t getNumberOfPhoneNumbers(const T (&)[N]) {
return N;
}
// Function to calculate the maximum length of phone numbers
size_t getMaxPhoneNumberLength(const char* phoneNumbers[], size_t numPhoneNumbers) {
size_t maxLength = 0;
for (size_t i = 0; i < numPhoneNumbers; ++i) {
size_t length = strlen(phoneNumbers[i]);
if (length > maxLength) {
maxLength = length;
}
}
return maxLength + 1; // Adding 1 for the null terminator
}
void sendStringSMStoControl(String message, int retries, int period) {
// Check if the length is lower than the SMS_MAXIMUM_LENGTH const defined
if (message.length() > SMS_MAXIMUM_LENGTH) {
message = message.substring(0, SMS_MAXIMUM_LENGTH - 1);
consoleLogging(LOG_LEVEL_SILENT, "SMS truncated because of SMS length limit: " + message);
}
message = "[" + myGSMModem.getIMEI() + "] " + message;
// Declare and populate the char array that the sendSMS function needs
char messageArray[SMS_MAXIMUM_LENGTH];
message.toCharArray(messageArray, SMS_MAXIMUM_LENGTH);
// Find the number of phones available
size_t numPhoneNumbers = getNumberOfPhoneNumbers(controlPhoneNumber);
// For each phone number we follow the same process
for (size_t phoneIndex = 0; phoneIndex < numPhoneNumbers; phoneIndex++) {
// Actually send the SMS in a loop until maximum retries have been reached
for (int i = 0; i < retries; i++) {
myWatchDog.clear();
if (mySms.beginSMS(controlPhoneNumber[phoneIndex])) {
mySms.print(messageArray);
if (mySms.endSMS()) {
consoleLogging(LOG_LEVEL_TRACE, "SMS sent: " + message);
break; // SMS sent successfully, let's try the next phone number
} else {
consoleLogging(LOG_LEVEL_WARN, "Error while sending SMS");
}
} else {
consoleLogging(LOG_LEVEL_WARN, "Error starting to send SMS");
}
LowPower.idle(period);
}
}
return;
}
void sendStringSMStoReport(String message, int retries, int period) {
// Check if the length is lower than the SMS_MAXIMUM_LENGTH const defined
if (message.length() > SMS_MAXIMUM_LENGTH) {
message = message.substring(0, SMS_MAXIMUM_LENGTH - 1);
consoleLogging(LOG_LEVEL_SILENT, "SMS truncated because of SMS length limit: " + message);
}
//message = "[" + myGSMModem.getIMEI() + "] " + message;
// Declare and populate the char array that the sendSMS function needs
char messageArray[SMS_MAXIMUM_LENGTH];
message.toCharArray(messageArray, SMS_MAXIMUM_LENGTH);
// Find the number of phones available
size_t numPhoneNumbers = getNumberOfPhoneNumbers(reportPhoneNumber);
// For each phone number we follow the same process
for (size_t phoneIndex = 0; phoneIndex < numPhoneNumbers; phoneIndex++) {
// Actually send the SMS in a loop until maximum retries have been reached
for (int i = 0; i < retries; i++) {
myWatchDog.clear();
if (mySms.beginSMS(reportPhoneNumber[phoneIndex])) {
mySms.print(messageArray);
if (mySms.endSMS()) {
consoleLogging(LOG_LEVEL_TRACE, "SMS sent: " + message);
break; // SMS sent successfully, let's try the next phone number
} else {
consoleLogging(LOG_LEVEL_WARN, "Error while sending SMS");
}
} else {
consoleLogging(LOG_LEVEL_WARN, "Error starting to send SMS");
}
LowPower.idle(period);
}
}
return;
}
/****************************************************************************/
/* Sensor FUNCTIONS */
/****************************************************************************/
void initializeSensors() {
/**************************************************************************
PENDING: Should find a way to compare the watchdog timer with the sampling period.
They cannot be compared straight away, since wdt counts in clycles while sampling period is in miliseconds
// Check if sampling rate is compatible with watchdog timeout
if ((2 * sensorSamplingPeriod) < watchdogBarkPeriod) {
consoleLogging(LOG_LEVEL_ERROR, "Sensor sampling period is too high compared to watchdog timer.");
myWatchDog.setup(WDT_OFF);
sendStringSMS("Shutting down due to the sampling period being too high compared to watchdog timer.", 3, FIVE_SECONDS);
do {
// Put the board into deepsleep mode, sensors can be turned off because this state is inconsistent due to bad programming
LowPower.deepSleep();
// Somehow we were awaken from infinite sleep, so lets loop.
consoleLogging(LOG_LEVEL_WARN, "Awaken from infinite sleep due to ensor sampling period is too high compared to watchdog timer. Lets sleep again.");
} while (true);
}
***************************************************************************/
}
/****************************************************************************/
/* TOOLKIT FUNCTIONS */
/****************************************************************************/
void myWatchDogShutdown() {
// This must be quick since it is kind of an interrupt
consoleLogging(LOG_LEVEL_ERROR, "The watch dog is shutting down the board! ...");
}
void restartBoard() {
// Restart the Arduino board
NVIC_SystemReset();
/* Alternative low level method by Chat-GPT4o
SCB_AIRCR = 0x5FA0004; // Force reset
*/
}
int ledBlinkIdleCPU(int durationMS, int periodMS) {
int initialState = digitalRead(LED_BUILTIN);
for (int i = 0; i < (durationMS / periodMS); i++) {
digitalWrite(LED_BUILTIN, initialState == HIGH ? LOW : HIGH);
LowPower.idle(periodMS / 2);
digitalWrite(LED_BUILTIN, initialState);
LowPower.idle(periodMS / 2);
}
return initialState;
}
int ledBlink(int durationMS, int periodMS) {
int initialState = digitalRead(LED_BUILTIN);
for (int i = 0; i < (durationMS / periodMS); i++) {
digitalWrite(LED_BUILTIN, initialState == HIGH ? LOW : HIGH);
delay(periodMS / 2);
digitalWrite(LED_BUILTIN, initialState);
delay(periodMS / 2);
}
return initialState;
}
void ledBlinkSOSidleCPU() {
int shortBlink = 500; // 250 ms for a short blink
int longBlink = 1500; // 750 ms for a long blink
int pause = 500; // 250 ms between blinks
int letterPause = 1500; // 1000 ms between letters (dots and dashes)
int wordPause = 3000; // 5000 ms between SOS signals
digitalWrite(LED_BUILTIN, LOW);
LowPower.idle(wordPause);
for (int t = 0; t < 5; t++) {
// S: ...
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
LowPower.idle(shortBlink);
digitalWrite(LED_BUILTIN, LOW);
LowPower.idle(pause);
}
LowPower.idle(letterPause);
// O: ---
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
LowPower.idle(longBlink);
digitalWrite(LED_BUILTIN, LOW);
LowPower.idle(pause);
}
LowPower.idle(letterPause);
// S: ...
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
LowPower.idle(shortBlink);
digitalWrite(LED_BUILTIN, LOW);
LowPower.idle(pause);
}
LowPower.idle(wordPause);
}
}
void ledBlinkSOS() {
int shortBlink = 500; // 250 ms for a short blink
int longBlink = 1500; // 750 ms for a long blink
int pause = 500; // 250 ms between blinks
int letterPause = 1500; // 1000 ms between letters (dots and dashes)
int wordPause = 3000; // 5000 ms between SOS signals
digitalWrite(LED_BUILTIN, LOW);
delay(wordPause);
for (int t = 0; t < 5; t++) {
// S: ...
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(shortBlink);
digitalWrite(LED_BUILTIN, LOW);
delay(pause);
}
delay(letterPause);
// O: ---
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(longBlink);
digitalWrite(LED_BUILTIN, LOW);
delay(pause);
}
delay(letterPause);
// S: ...
for (int i = 0; i < 3; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(shortBlink);
digitalWrite(LED_BUILTIN, LOW);
delay(pause);
}
delay(wordPause);
}
}
And the redacted secrets:
#ifndef ARDUINO_SECRETS_H
#define ARDUINO_SECRETS_H
#define SECRET_SIM_APN "isp_apn"
#define SECRET_OPTIONAL_SIM_APN_USERNAME "ISP_username"
#define SECRET_OPTIONAL_SIM_APN_PASSWORD "ISP_password"
#define SECRET_CONTROL_PHONE_NUMBER { "666666666", "666666667" }
#define SECRET_REPORT_PHONE_NUMBER { "6666666668"}
// Structure to store ICCID, PIN and PUK details
typedef struct {
char iccid[20];
char pin[5];
char puk[9];
} SIMCardInfo;
// Your SIM details here
// The SIM card label does not include the leading "1234" digits reported by a call to getICCID()
const SIMCardInfo simCards[] = {
{"1234567890123456789", "1111", "12345678"}, // ICCID, PIN, PUK
{"1234567890123456790", "2222", "12345679"}, // ICCID, PIN, PUK
{"1234567890123456791", "3333", "12345680"}, // ICCID, PIN, PUK
// Add more SIM card detaisl as needed
};
const int numSIMCards = sizeof(simCards) / sizeof(simCards[0]);
#endif