/*----------------------------------------------------------------------------------------------------------------------
* INCLUDES
*/
#include "Particle.h"
#include "cellular_hal.h"
#include <math.h>
#include <HC_SR04.h>
/*----------------------------------------------------------------------------------------------------------------------
* CONSTANTS
*/
#define EVENT_NAME "M"
#define MEASUREMENT_COUNT 100
#define MEASUREMENT_THRESHOLD 10
#define BUFFER_SIZE 256
#define CELL_APN "hologram"
#define CELL_USERNAME ""
#define CELL_PASSWORD ""
#define EEPROM_TIME_ADDRESS 0
#define ELECTRON_PRODUCT_ID ----
#define ELECTRON_PRODUCT_VERSION --
#define DELAY_SETTLE 100 // Milliseconds
#define DELAY_CLOUD 20000 // Milliseconds
#define DELAY_CONNECT_TIME 500 // Milliseconds
#define UPDATE_TIME 300000 // Milliseconds
#define DELAY_MEASUREMENT 20 // Milliseconds
#define NOT_CONNECTED_TIMEOUT 120000 // Milliseconds
#define FAILURE_SLEEP_DURATION 3600 // Seconds
#define PIN_ECHO D5 // Connect HC-SR04 Range finder as follows:
#define PIN_RELAY D3 // GND - GND, 5V - VCC, D4 - Trig, D5 - VoltageDivider.
#define PIN_TRIG D4 // VoltageDivider (470 OHM RESISTORS FROM D5 TO GND AND TO ECHO)
#define RANGE_MAX 400
#define RANGE_MIN 0.5
#define DEBUG 1
#define BAUD_RATE 9600
/*----------------------------------------------------------------------------------------------------------------------
* MACROS
*/
#if DEBUG
# define SERIAL_DEBUG_BEGIN(x) Serial.begin(x)
# define DPRINTF(...) Serial.printf(__VA_ARGS__)
# define DPRINTLN(x) Serial.println(x)
#else // do nothing
# define SERIAL_DEBUG_BEGIN(x)
# define DPRINTF(...)
# define DPRINTLN(x)
#endif
/*----------------------------------------------------------------------------------------------------------------------
* CONFIGURATION
*/
PRODUCT_ID(ELECTRON_PRODUCT_ID); // Product ID
PRODUCT_VERSION(ELECTRON_PRODUCT_VERSION);
STARTUP(cellular_credentials_set(CELL_APN, CELL_USERNAME, CELL_PASSWORD, NULL));
STARTUP(System.enableFeature(FEATURE_RESET_INFO)); // To enable reading why the system was reset.
// SYSTEM_THREAD(ENABLED); // for allowing processes to run in parallel. Default is SYSTEM_THREAD disabled.
SYSTEM_MODE(SEMI_AUTOMATIC); // Cloud connecting at SEMI_AUTOMATIC can provide power savings.
HC_SR04 rangefinder = HC_SR04(PIN_TRIG, PIN_ECHO, RANGE_MIN, RANGE_MAX); // initializes HC_SR04 object
FuelGauge fuel;
/*----------------------------------------------------------------------------------------------------------------------
* GLOBALS
*/
int m_min = -1;
int m_q1 = -1;
int m_med = -1;
int m_q3 = -1;
int m_max = -1;
float m_soc = 0.0;
char publishString[BUFFER_SIZE];
bool published = false;
//bool connectSuccess = false; // used for connection timeout
bool forceConnect = true;
/*======================================================================================================================
* PARTICLE MAIN CYCLE ENTRY POINTS
*/
void setup()
{
initAll();
DPRINTLN("+++ setup ====================================");
takeMeasurements();
slowClock();
sprintf(publishString, "{\"m_min\":%d,\"m_q1\":%d,\"m_med\":%d,\"m_q3\":%d,\"m_max\":%d,\"m_soc\":%f}", m_min, m_q1, m_med, m_q3, m_max, m_soc);
DPRINTF("forceConnect value = %d\n", forceConnect);
if(!forceConnect) // go to sleep without reporting
{
DPRINTLN("setup branch 1; going to sleep without reporting");
unsigned long t_now = Time.now(); // seconds since Jan 1 1970 (UTC)
int Sleep_Duration = getSleepDelay(t_now);
DPRINTF("Going to sleep for %d seconds\n", Sleep_Duration);
DPRINTF("End of no connectivity cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
System.sleep(SLEEP_MODE_SOFTPOWEROFF, Sleep_Duration);
}
Particle.connect();
// connectSuccess = waitFor(Particle.connected, NOT_CONNECTED_TIMEOUT); // System threading needs to be enabled for this to work
waitUntil(Particle.connected);
DPRINTLN("--- setup ====================================");
}
void loop()
{
DPRINTLN("+++ loop *************************************");
// if (connectSuccess)
// {
if (published == false)
{
DPRINTLN("loop branch 1; publishing");
published = Particle.publish(EVENT_NAME, publishString, PRIVATE);
DPRINTF("Published with return value: %d (1 means success)\n", published);
}
else
{
DPRINTLN("loop branch 2; delay, disconnect, write to eeprom, go to sleep");
delay(DELAY_CLOUD);
Particle.disconnect();
unsigned long t_now = Time.now(); // seconds since Jan 1 1970 (UTC)
int Sleep_Duration = getSleepDelay(t_now);
DPRINTF("End of connected cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
DPRINTF("Going to sleep for %d seconds\n", Sleep_Duration);
System.sleep(SLEEP_MODE_SOFTPOWEROFF, Sleep_Duration);
}
// }
// else // connection time is greater than 2 minutes, go to sleep for 1 hour
// {
// DPRINTLN("Going to sleep for one hour, failed to connect to cloud!");
// System.sleep(SLEEP_MODE_SOFTPOWEROFF, FAILURE_SLEEP_DURATION);
// }
DPRINTLN("--- loop *************************************");
}
/*======================================================================================================================
* HELPER FUNCTIONS
*/
///=====================================================================================================================
/// <summary>This function called to initialize everything that is needed</summary>
///=====================================================================================================================
inline void initAll()
{
SERIAL_DEBUG_BEGIN(BAUD_RATE);
DPRINTF("Beginning of cycle; time read: %s\n", Time.format(Time.now(), TIME_FORMAT_DEFAULT).c_str());
pinMode(PIN_RELAY, OUTPUT);
m_soc = fuel.getSoC();
if(!Time.isValid())
{
DPRINTLN("Time is not valid, force a connect.");
forceConnect = true;
}
if(Time.hour(Time.now()+2*60) == 2) // 8pm daily report
{
DPRINTLN("Time is 8pm, force a connect.");
forceConnect = true;
}
}
///=====================================================================================================================
/// <summary>Helper function used in takeMeasurements to turn on the solid state relay</summary>
///=====================================================================================================================
void turnOnRelay() {
digitalWrite(PIN_RELAY,HIGH);
delay(DELAY_SETTLE);
DPRINTLN("The Relay turned on");
}
///=====================================================================================================================
/// <summary>Helper function used in takeMeasurements to turn off the solid state relay</summary>
///=====================================================================================================================
void turnOffRelay() {
digitalWrite(PIN_RELAY,LOW);
delay(DELAY_SETTLE);
DPRINTLN("The Relay turned off");
}
///=====================================================================================================================
/// <summary>
/// This function slows down the clock from 120MHz to 30MHz in order to be more power efficent. This function was
/// written by someone in the particle community (link is on the next line)
/// https://community.particle.io/t/reducing-photon-run-time-power-consumption-howto/18613
/// </summary>
///=====================================================================================================================
void slowClock() {
RCC->CFGR &= ~0xfcf0;
RCC->CFGR |= 0x0090;
SystemCoreClockUpdate();
SysTick_Configuration();
FLASH->ACR &= ~FLASH_ACR_PRFTEN;
DPRINTLN("Clock was slowed down to 30 MHz");
}
///=====================================================================================================================
/// <summary>
/// Helper function for takeMeasurements that sorts an array of length MEASUREMENT_COUNT using the insertion sort
/// algorithm
/// </summary>
/// <param name="myReadings">
/// the array of integers that are being sorted (no need to pass by reference)
/// </param>
///=====================================================================================================================
void sortArray(int myReadings[])
{
int i, j, x;
for(uint16_t i = 1; i < MEASUREMENT_COUNT; i++)
{
x = myReadings[i];
j = i - 1;
while(j >= 0 && (myReadings[j] > x))
{
myReadings[j + 1] = myReadings[j];
j = j - 1;
}
myReadings[j + 1] = x;
}
}
///=====================================================================================================================
/// <summary>
/// This function takes measurements from an ultrasonic sensor and stores the results in 5
/// global variables so that the results are ready to publish
/// </summary>
///=====================================================================================================================
void takeMeasurements()
{
DPRINTLN("+++ takeMeasurements");
turnOnRelay(); // Turns on relay that allows power to flow to sensor
int measurements[MEASUREMENT_COUNT];
for(int i = 0; i < MEASUREMENT_COUNT; i++) // takes MEASUREMENT_COUNT measurements
{
measurements[i] = rangefinder.getDistanceCM();
delay(DELAY_MEASUREMENT);
}
DPRINTLN("Finished Taking Measurements.");
turnOffRelay(); // Stops power from leaking to sensor
sortArray(measurements); // Sorts the measurements in ascending order
// Populates global variables
m_min = measurements[0];
m_q1 = measurements[MEASUREMENT_COUNT / 4 - 1];
m_med = measurements[MEASUREMENT_COUNT / 2 - 1];
m_q3 = measurements[MEASUREMENT_COUNT * 3 / 4 - 1];
m_max = measurements[MEASUREMENT_COUNT - 1];
if(m_q3 <= MEASUREMENT_THRESHOLD) // Threshold reached
{
DPRINTLN("forceConnect set to true, MEASUREMENT_THRESHOLD condition met");
forceConnect = true;
}
DPRINTLN("--- takeMeasurements");
}
///=====================================================================================================================
/// <summary>
/// This function uses the time of day to determine how long to sleep until another measurement is taken. Currently it
/// has been written to operate differently in 3 different time ranges:
/// 8am - 5pm MDT : go to sleep until the next hour
/// 6pm - 7pm MDT : go to sleep until 8pm MDT
/// 8pm - 7am MDT : go to sleep until 8am MDT
/// </summary>
/// <returns>Seconds until wakeup</returns>
///=====================================================================================================================
int getSleepDelay(unsigned long t_unix)
{
DPRINTLN("+++ getSleepDelay");
int t_hour = Time.hour(t_unix); // hour (0-23) in UTC
int r_sec;
if(isInRange(t_hour, 14, 23)) // (14 - 23 UTC) or (8am - 5pm MDT)
{
DPRINTLN("getSleepDelay Branch 1");
r_sec = secUntilNextHour(t_unix);
}
else if(isInRange(t_hour, 0, 1)) // (0 - 1 UTC) or (6pm - 7pm MDT)
{
DPRINTLN("getSleepDelay Branch 2");
r_sec = secUntilNextHour(t_unix);
int r_hours = hoursUntilStart(t_hour, 2); // (2 UTC) or (8pm MDT)
r_sec += (r_hours - 1) * 3600;
}
else // (2 - 13) UTC or (8pm - 7am)
{
DPRINTLN("getSleepDelay Branch 3");
r_sec = secUntilNextHour(t_unix);
int r_hours = hoursUntilStart(t_hour, 14); // (14 UTC) or (8am MDT)
r_sec += (r_hours - 1) * 3600;
}
DPRINTLN("--- getSleepDelay");
return r_sec;
}
///=====================================================================================================================
/// <summary>
/// Helper function for getSleepDelay used to determine how many seconds are remaining until the beginning of the next
/// hour.
/// </summary>
/// <param name="t_unix">unix time that holds how many seconds have elapsed since Jan 1 1970 in UTC timezone</param>
/// <returns>Number of seconds until the beginning of the next hour</returns>
///=====================================================================================================================
int secUntilNextHour(unsigned long t_unix)
{
int t_minute = Time.minute(t_unix);
int t_second = Time.second(t_unix);
int r_minute = 59 - t_minute;
int r_second = 59 - t_second;
return r_minute * 60 + r_second;
}
///=====================================================================================================================
/// <summary>
/// Helper function for getSleepDelay that is used to determin hour many hours need to elapse until the target hour has
/// been reached.
/// </summary>
/// <param name="hour_now">The current hour - expected to be within the range (0 - 23)</param>
/// <param name="hour_target">The target hour - expected to be within the range (0 - 23)</param>
/// <returns>The number of hours needed to elapse until the target hour will be reached</returns>
///=====================================================================================================================
int hoursUntilStart(int hour_now, int hour_target)
{
return (hour_target + 24 - hour_now) % 24;
}
///=====================================================================================================================
/// <summary>
/// Helper function for getSleepDelay that is used to determine if the current hour is within a specified range.
/// </summary>
/// <param name="hour_now">The current hour - expected to be within the range (0 - 23)</param>
/// <param name="lower">The lower hour boundary</param>
/// <param name="upper">The upper hour boundary</param>
/// <returns>True if within range, false if outside of range</returns>
///=====================================================================================================================
bool isInRange(int hour_now, int lower, int upper)
{
if(lower < upper)
{
return (hour_now >= lower) && (hour_now <= upper);
}
return (hour_now >= lower) || (hour_now <= upper); // in case the range wraps around the 23 - 0 hour boundary
}