@@ -5,53 +5,74 @@ | |||
#define LOGGER_NAME "Config" | |||
namespace config { | |||
Config value; | |||
namespace main { | |||
namespace details { | |||
config_t value; | |||
void read() { | |||
VERBOSE("read"); | |||
hardware::i2c::powerOn(); | |||
hardware::i2c::eeprom.readBlock(CONFIG_ADDR, value); | |||
if (!String(CONFIG_SEED).equals(value.seed)) reset(); | |||
hardware::i2c::powerOff(); | |||
namespace details { | |||
void read() { | |||
VERBOSE("read"); | |||
hardware::i2c::powerOn(); | |||
hardware::i2c::eeprom.readBlock(CONFIG_ADDR, value); | |||
if (CONFIG_SEED != value.seed) reset(); //todo : reset network if seed for network is not right | |||
hardware::i2c::powerOff(); | |||
NOTICE_FORMAT("read", "%d, %s, %d, %d", value.seed, value.version, value.firstEntry, value.lastEntry); | |||
#if BACKUP_ENABLE_NETWORK | |||
NOTICE_FORMAT("read", "%d, %d, %s, %s", value.network.saveThreshold, value.network.lastSavedEntry, value.network.apn, value.network.url); | |||
//networkConfig_t c = { | |||
// POSITIONS_CONFIG_NET_DEFAULT_SAVE_THRESHOLD, | |||
// 0xFFFF, | |||
// POSITIONS_CONFIG_NET_DEFAULT_APN, | |||
// POSITIONS_CONFIG_NET_DEFAULT_URL, | |||
//}; | |||
//value.network = c; | |||
#endif | |||
} | |||
void write() { | |||
NOTICE_FORMAT("write", "%d, %s, %d, %d", value.seed, value.version, value.firstEntry, value.lastEntry); | |||
#if BACKUP_ENABLE_NETWORK | |||
NOTICE_FORMAT("write", "%d, %d, %s, %s", value.network.saveThreshold, value.network.lastSavedEntry, value.network.apn, value.network.url); | |||
#endif | |||
hardware::i2c::powerOn(); | |||
int written = hardware::i2c::eeprom.writeBlock(CONFIG_ADDR, value); | |||
hardware::i2c::powerOff(); | |||
} | |||
} | |||
void write() { | |||
VERBOSE_FORMAT("write", "%s, %s, %s, %d, %d", value.seed, value.version, value.apn, value.firstEntry, value.lastEntry); | |||
void setup() { | |||
details::read(); | |||
//details::write(); | |||
} | |||
hardware::i2c::powerOn(); | |||
int written = hardware::i2c::eeprom.writeBlock(CONFIG_ADDR, value); | |||
hardware::i2c::powerOff(); | |||
void save() { | |||
details::write(); | |||
} | |||
} | |||
Config get() { | |||
if (value.seed[0] == '\0') details::read(); | |||
void reset() { | |||
VERBOSE("reset"); | |||
VERBOSE_FORMAT("get", "%s, %s, %s, %d, %d", value.seed, value.version, value.apn, value.firstEntry, value.lastEntry); | |||
return value; | |||
} | |||
config_t config = { | |||
CONFIG_SEED, | |||
VERSION, | |||
0xFFFF, | |||
0xFFFF, | |||
#if BACKUP_ENABLE_NETWORK | |||
{ | |||
POSITIONS_CONFIG_NET_DEFAULT_SAVE_THRESHOLD, | |||
0xFFFF, | |||
POSITIONS_CONFIG_NET_DEFAULT_APN, | |||
POSITIONS_CONFIG_NET_DEFAULT_URL, | |||
}, | |||
#endif | |||
}; | |||
void set(Config config) { | |||
value = config; | |||
details::write(); | |||
} | |||
value = config; | |||
save(); | |||
} | |||
void reset() { | |||
VERBOSE("reset"); | |||
Config config = { | |||
CONFIG_SEED, | |||
VERSION, | |||
"Vodafone", //TODO : read from SD | |||
0xFFFF, | |||
0xFFFF, | |||
}; | |||
value = config; | |||
details::write(); | |||
} | |||
} |
@@ -2,44 +2,85 @@ | |||
#include <Arduino.h> | |||
#define BACKUP_ENABLE_SDCARD 0 | |||
#define BACKUP_ENABLE_NETWORK 1 | |||
#if BACKUP_ENABLE_NETWORK | |||
#include "NetworkPositionsConfig.h" | |||
#endif | |||
#define SIM808_BAUDRATE 4800 | |||
#define CONFIG_ADDR 0 | |||
#define CONFIG_RESERVED_SIZE 128 | |||
#define CONFIG_SEED 13 | |||
#define VERSION "1.00" | |||
#pragma region Default configuration values | |||
#define MENU_TIMEOUT 10000 | |||
/** | |||
\def SLEEP_DEFAULT_TIME_SECONDS | |||
Hard coded value for default sleep time between position acquisitions. | |||
Exprimed in seconds | |||
*/ | |||
#define SLEEP_DEFAULT_TIME_SECONDS 1800 | |||
#define SLEEP_DEFAULT_INCREASE_THRESHOLD 3 | |||
#define GPS_DEFAULT_INTERMEDIATE_TIMEOUT_MS 10000 | |||
#define GPS_DEFAULT_TOTAL_TIMEOUT_MS 180000 | |||
#define NETWORK_DEFAULT_INTERMEDIATE_TIMEOUT_MS 6000 | |||
#define NETWORK_DEFAULT_TOTAL_TIMEOUT_MS 180000 | |||
#define NETWORK_DEFAULT_NO_NETWORK_QUALIRY_THRESHOLD 8 | |||
#define NETWORK_DEFAULT_NO_NETWORK_TRIES 5 | |||
#pragma endregion | |||
#define SLEEP_TIMING_TIME(hours, minutes) hours * 60 + minutes | |||
#define SLEEP_TIMING_MIN SLEEP_TIMING_TIME(0, 0) | |||
#define SLEEP_TIMING_MAX SLEEP_TIMING_TIME(23, 59) | |||
struct sleepTimings_t { | |||
uint8_t speed; | |||
uint16_t timeMin; | |||
uint16_t timeMax; | |||
uint16_t seconds; | |||
}; | |||
struct Config { | |||
char seed[5]; | |||
struct config_t { | |||
uint8_t seed; | |||
char version[5]; | |||
char apn[20]; | |||
uint16_t firstEntry; | |||
uint16_t lastEntry; | |||
#if BACKUP_ENABLE_NETWORK | |||
networkConfig_t network; | |||
#endif | |||
}; | |||
#define CONFIG_ADDR 0 | |||
#define CONFIG_SEED "UIYA" | |||
#define VERSION "1.00" | |||
#define SLEEP_DEFAULT_TIME_SECONDS 1800 | |||
#define GPS_DEFAULT_INTERMEDIATE_TIMEOUT_MS 10000 | |||
#define GPS_DEFAULT_TOTAL_TIMEOUT_MS 180000 | |||
namespace config { | |||
static const sleepTimings_t defaultSleepTimings[] PROGMEM = { | |||
{ 5, SLEEP_DEFAULT_TIME_SECONDS }, | |||
{ 10, 1200 }, | |||
{ 20, 600 }, | |||
{ 30, 540 }, | |||
{ 50, 480 }, | |||
{ 80, 240 }, | |||
{ 100, 210 }, | |||
{ 180, 180 }, | |||
{ 3, SLEEP_TIMING_TIME(16, 00), SLEEP_TIMING_TIME(19, 59), 3600 }, | |||
{ 3, SLEEP_TIMING_TIME(20, 00), SLEEP_TIMING_MAX, SLEEP_DEFAULT_TIME_SECONDS }, | |||
{ 3, SLEEP_TIMING_MIN, SLEEP_TIMING_TIME(8, 29), SLEEP_DEFAULT_TIME_SECONDS }, | |||
{ 3, SLEEP_TIMING_TIME(8, 30), SLEEP_TIMING_TIME(15, 59), 10800 }, | |||
{ 5, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 900 }, | |||
{ 20, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 600 }, | |||
{ 30, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 540 }, | |||
{ 50, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 480 }, | |||
{ 80, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 240 }, | |||
{ 100, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 210 }, | |||
{ 180, SLEEP_TIMING_MIN, SLEEP_TIMING_MAX, 180 }, | |||
}; | |||
Config get(); | |||
void set(Config config); | |||
namespace main { | |||
extern config_t value; | |||
void reset(); | |||
void setup(); | |||
void save(); | |||
void reset(); | |||
} | |||
} |
@@ -8,38 +8,57 @@ using namespace utils; | |||
namespace core { | |||
uint16_t sleepTime = SLEEP_DEFAULT_TIME_SECONDS;; | |||
uint8_t increaseInARow = 0; | |||
void main() { | |||
VERBOSE("main"); | |||
PositionEntryMetadata metadata; | |||
if (positions::acquire(metadata)) { | |||
positions::appendLast(metadata); | |||
setSleepTime(); | |||
updateSleepTime(gps::getVelocity()); | |||
} | |||
if (positions::needsToSend()) { | |||
positions::send(); | |||
} | |||
positions::doBackup(); | |||
mainunit::deepSleep(sleepTime); | |||
} | |||
void setSleepTime() { | |||
setSleepTime(gps::getVelocity()); | |||
void updateSleepTime(uint8_t velocity) { | |||
uint16_t result = computeSleepTime(velocity); | |||
if (result > sleepTime) { | |||
increaseInARow++; | |||
if (increaseInARow < SLEEP_DEFAULT_INCREASE_THRESHOLD) return; | |||
} | |||
else increaseInARow = 0; | |||
sleepTime = result; | |||
NOTICE_FORMAT("updateSleepTime", "%dkmh => %d seconds", velocity, sleepTime); | |||
} | |||
void setSleepTime(uint8_t velocity) { | |||
sleepTime = SLEEP_DEFAULT_TIME_SECONDS; | |||
uint16_t computeSleepTime(uint8_t velocity) { | |||
uint16_t result; | |||
uint16_t currentTime = 0xFFFF; | |||
if (rtc::isAccurate()) { | |||
tmElements_t time; | |||
rtc::getTime(time); | |||
currentTime = SLEEP_TIMING_TIME(time.Hour, time.Minute); | |||
} | |||
for (uint8_t i = 0; i < flash::getArraySize(config::defaultSleepTimings); i++) { | |||
sleepTimings_t timing; | |||
flash::read(&config::defaultSleepTimings[i], timing); | |||
if (velocity > timing.speed) continue; | |||
if (currentTime != 0xFFFF && (currentTime < timing.timeMin || currentTime > timing.timeMax)) continue; | |||
sleepTime = timing.seconds; | |||
result = timing.seconds; | |||
break; | |||
} | |||
VERBOSE_FORMAT("setSleepTime", "%d", sleepTime); | |||
VERBOSE_FORMAT("computeSleepTime", "%d,%d", velocity, result); | |||
return result; | |||
} | |||
} |
@@ -14,6 +14,6 @@ namespace core { | |||
extern uint16_t sleepTime; | |||
void main(); | |||
void setSleepTime(uint8_t velocity); | |||
void setSleepTime(); | |||
void updateSleepTime(uint8_t velocity); | |||
uint16_t computeSleepTime(uint8_t velocity); | |||
} |
@@ -1,15 +1,16 @@ | |||
#include "Debug.h" | |||
#include "Flash.h" | |||
#include "Positions.h" | |||
#include "Core.h" | |||
#define LOGGER_NAME "Debug" | |||
#define MENU_ENTRY(name, text) const char MENU_##name[] PROGMEM = text | |||
const char FAKE_GPS_ENTRY[] PROGMEM = "1,1,20170924184842.000,49.454862,1.144537,71.900,67.99,172.6,1,,1.3,2.2,1.8,,11,7,,,37,,"; | |||
const char FAKE_GPS_ENTRY[] PROGMEM = "1,1,20170924184842.000,49.454862,1.144537,71.900,2.70,172.6,1,,1.3,2.2,1.8,,11,7,,,37,,"; | |||
MENU_ENTRY(HEADER, "-- Debug Menu --"); | |||
MENU_ENTRY(SEPARATOR, "----"); | |||
MENU_ENTRY(HEADER, "========================\n-- Menu --"); | |||
MENU_ENTRY(SEPARATOR, "----"); | |||
MENU_ENTRY(RUN, "[R] Run"); | |||
MENU_ENTRY(RUN_ONCE, "[r] Run once"); | |||
@@ -21,13 +22,13 @@ MENU_ENTRY(GPS_GET, "[L] Get GPS position"); | |||
MENU_ENTRY(GPS_SET, "[l] Set last GPS position"); | |||
MENU_ENTRY(RTC_SET, "[T] Get RTC time"); | |||
MENU_ENTRY(RTC_GET, "[t] Set RTC time"); | |||
MENU_ENTRY(SD_WRITE_TEST, "[W] Write to test file"); | |||
MENU_ENTRY(EEPROM_GET_CONFIG, "[C] Get EEPROM config"); | |||
MENU_ENTRY(EEPROM_RESET_CONFIG, "[c] Reset EEPROM config"); | |||
MENU_ENTRY(EEPROM_GET_CONTENT, "[E] Get EEPROM content"); | |||
MENU_ENTRY(EEPROM_GET_ENTRIES, "[P] Get EEPROM entries"); | |||
MENU_ENTRY(EEPROM_GET_LAST_ENTRY, "[p] Get EEPROM last entry"); | |||
MENU_ENTRY(EEPROM_ADD_ENTRY, "[a] Add last entry to EEPROM"); | |||
MENU_ENTRY(EEPROM_BACKUP_ENTRIES, "[B] Backup EEPROM entries"); | |||
MENU_ENTRY(SLEEP, "[S] Sleep for 8s"); | |||
MENU_ENTRY(SLEEP_DEEP, "[s] Deep sleep for 10s"); | |||
MENU_ENTRY(QUESTION, "?"); | |||
@@ -43,13 +44,13 @@ const PROGMEM uint8_t commandIdMapping[] = { | |||
'l', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::GPS_SET), | |||
'T', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::RTC_GET), | |||
't', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::RTC_SET), | |||
'W', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::SD_WRITE_TEST), | |||
'C', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_GET_CONFIG), | |||
'c', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_RESET_CONFIG), | |||
'E', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_GET_CONTENT), | |||
'P', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_GET_ENTRIES), | |||
'p', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_GET_LAST_ENTRY), | |||
'a', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_ADD_ENTRY), | |||
'B', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_BACKUP_ENTRIES), | |||
'S', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::SLEEP), | |||
's', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::SLEEP_DEEP), | |||
}; | |||
@@ -78,17 +79,13 @@ const char * const MENU_ENTRIES[] PROGMEM = { | |||
MENU_SEPARATOR, | |||
MENU_SD_WRITE_TEST, | |||
MENU_SEPARATOR, | |||
MENU_EEPROM_GET_CONFIG, | |||
MENU_EEPROM_RESET_CONFIG, | |||
MENU_EEPROM_GET_CONTENT, | |||
MENU_EEPROM_GET_ENTRIES, | |||
MENU_EEPROM_GET_LAST_ENTRY, | |||
MENU_EEPROM_ADD_ENTRY, | |||
MENU_EEPROM_BACKUP_ENTRIES, | |||
MENU_SEPARATOR, | |||
MENU_SLEEP, | |||
@@ -109,20 +106,18 @@ namespace debug { | |||
namespace details { | |||
inline void displayPosition(PositionEntry entry) { | |||
Log.notice(F("%d%%, %dmV, %f°C, %ds %d, %s\n"), entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
Log.notice(F("%d%%, %dmV, %f°C, %ds, %d, %s\n"), entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
} | |||
} | |||
void waitForSerial() { | |||
while (!Serial); | |||
Serial.begin(DEBUG_SERIAL_SPEED); | |||
Serial.println("Starting !"); | |||
} | |||
int freeRam() { | |||
return freeRam2(); | |||
} | |||
void displayFreeRam() { | |||
Log.notice(F("RAM: %d\n"), freeRam()); | |||
} | |||
GPSTRACKER_DEBUG_COMMAND parseCommand(char id) { | |||
size_t mappingArraySize = flash::getArraySize(commandIdMapping); | |||
char commandId; | |||
@@ -135,20 +130,28 @@ namespace debug { | |||
return GPSTRACKER_DEBUG_COMMAND::NONE; | |||
} | |||
GPSTRACKER_DEBUG_COMMAND menu() { | |||
if (!Serial) return GPSTRACKER_DEBUG_COMMAND::RUN; | |||
GPSTRACKER_DEBUG_COMMAND menu(uint16_t timeout) { | |||
GPSTRACKER_DEBUG_COMMAND command; | |||
size_t menuSize = flash::getArraySize(MENU_ENTRIES); | |||
uint8_t intermediate_timeout = 50; | |||
do { | |||
for (uint8_t i = 0; i < menuSize; i++) { | |||
Serial.println(reinterpret_cast<const __FlashStringHelper *>(pgm_read_word_near(&MENU_ENTRIES[i]))); | |||
} | |||
while (!Serial.available()); | |||
while (!Serial.available()) { | |||
if (timeout > 0) { | |||
delay(intermediate_timeout); | |||
timeout -= intermediate_timeout; | |||
if (timeout <= 0) { | |||
NOTICE_MSG("menu", "Timeout expired."); | |||
return GPSTRACKER_DEBUG_COMMAND::RUN; | |||
} | |||
} | |||
} | |||
command = parseCommand(Serial.read()); | |||
while (Serial.available()) Serial.read(); | |||
while (Serial.available()) Serial.read(); //flushing input | |||
} while (command == GPSTRACKER_DEBUG_COMMAND::NONE); | |||
return command; | |||
@@ -164,6 +167,8 @@ namespace debug { | |||
strlcpy_P(gps::lastPosition, FAKE_GPS_ENTRY, GPS_POSITION_SIZE); | |||
NOTICE_FORMAT("setFakeGpsPosition", "Last position set to : %s", gps::lastPosition); | |||
NOTICE_FORMAT("setFakeGpsPosition", "Speed : %d", gps::getVelocity()); | |||
NOTICE_FORMAT("setFakeGpsPosition", "Sleep time : %d", core::computeSleepTime(gps::getVelocity())); | |||
} | |||
void getAndDisplayBattery() { | |||
@@ -181,8 +186,26 @@ namespace debug { | |||
NOTICE_FORMAT("getAndDisplayRtcTime", "%d/%d/%d %d:%d:%d", tmYearToCalendar(time.Year), time.Month, time.Day, time.Hour, time.Minute, time.Second); | |||
} | |||
void setRtcTime() { | |||
tmElements_t time; | |||
gps::getTime(time); | |||
rtc::setTime(time); | |||
} | |||
void getAndDisplaySleepTimes() { | |||
size_t arraySize = flash::getArraySize(config::defaultSleepTimings); | |||
sleepTimings_t maxSpeedTiming; | |||
utils::flash::read(&config::defaultSleepTimings[arraySize - 1], maxSpeedTiming); | |||
for (int i = 0; i <= maxSpeedTiming.speed; i++) { | |||
core::computeSleepTime(i); | |||
} | |||
NOTICE_MSG("getAndDisplaySleepTimes", "Done"); | |||
} | |||
void getAndDisplayEepromConfig() { | |||
config::get(); | |||
config::main::setup(); //forcing read again | |||
} | |||
void getAndDisplayEepromContent() { | |||
@@ -202,7 +225,7 @@ namespace debug { | |||
} | |||
void getAndDisplayEepromPositions() { | |||
uint16_t currentEntryIndex = config::get().firstEntry; | |||
uint16_t currentEntryIndex = config::main::value.firstEntry; | |||
PositionEntry currentEntry; | |||
hardware::i2c::powerOn(); | |||
@@ -214,11 +237,11 @@ namespace debug { | |||
} | |||
void getAndDisplayEepromLastPosition() { | |||
uint16_t lastEntryIndex = config::get().lastEntry; | |||
uint16_t lastEntryIndex = config::main::value.lastEntry; | |||
PositionEntry lastEntry; | |||
positions::get(lastEntryIndex, lastEntry); | |||
details::displayPosition(lastEntry); | |||
if(positions::get(lastEntryIndex, lastEntry)) details::displayPosition(lastEntry); | |||
else Log.notice(F("No position recorded\n")); | |||
} | |||
void addLastPositionToEeprom() { | |||
@@ -234,14 +257,6 @@ namespace debug { | |||
SIM808_GPS_STATUS::OFF | |||
}; | |||
positions::appendLast(metadata); | |||
} | |||
void setRtcTime() { | |||
tmElements_t time; | |||
gps::getTime(time); | |||
rtc::setTime(time); | |||
NOTICE_MSG("setRtcTime", "Done"); | |||
for(int i = 0; i < 10; i++) positions::appendLast(metadata); | |||
} | |||
} |
@@ -1,67 +1,44 @@ | |||
#pragma once | |||
#include <Arduino.h> | |||
#include <ArduinoLog.h> | |||
#include "Config.h" | |||
#include "Logging.h" | |||
#include "Core.h" | |||
#include "Hardware.h" | |||
#include "Gps.h" | |||
#include "Rtc.h" | |||
#define LOG(level, f) Log.level(F("[" LOGGER_NAME "::" f "]\n")) | |||
#define LOG_MSG(level, f, msg) Log.level(F("[" LOGGER_NAME "::" f "] " msg "\n")) | |||
#define LOG_FORMAT(level, f, msg, ...) Log.level(F("[" LOGGER_NAME "::" f "] " msg "\n"), __VA_ARGS__) | |||
#ifdef _DEBUG | |||
#define VERBOSE(f) LOG(verbose, f) | |||
#define VERBOSE_MSG(f, msg) LOG_MSG(verbose, f, msg) | |||
#define VERBOSE_FORMAT(f, msg, ...) LOG_FORMAT(verbose, f, msg, __VA_ARGS__) | |||
#else | |||
#define DISABLE_LOGGING 1 //TODO : does nothing if not included before ArduinoLog.h | |||
#define VERBOSE(f) | |||
#define VERBOSE_MSG(f, msg) | |||
#define VERBOSE_FORMAT(f, msg, ...) | |||
#endif | |||
#define NOTICE(f) LOG(notice, f) | |||
#define NOTICE_MSG(f, msg) LOG_MSG(notice, f, msg) | |||
#define NOTICE_FORMAT(f, msg, ...) LOG_FORMAT(notice, f, msg, __VA_ARGS__) | |||
#define DEBUG_SERIAL_SPEED 115200 | |||
namespace debug { | |||
enum class GPSTRACKER_DEBUG_COMMAND : uint8_t { | |||
NONE = 0, | |||
RUN = 1, | |||
ONCE = 2, | |||
RAM = 3, | |||
BATTERY = 4, | |||
GPS_ON = 5, | |||
GPS_OFF = 6, | |||
GPS_GET = 7, | |||
GPS_SET = 8, | |||
RTC_GET = 11, | |||
RTC_SET = 12, | |||
SD_WRITE_TEST = 13, | |||
EEPROM_GET_CONFIG = 14, | |||
EEPROM_RESET_CONFIG = 15, | |||
EEPROM_GET_CONTENT = 16, | |||
EEPROM_GET_LAST_ENTRY = 17, | |||
EEPROM_GET_ENTRIES = 18, | |||
EEPROM_ADD_ENTRY = 19, | |||
SLEEP = 20, | |||
SLEEP_DEEP = 21 | |||
NONE, | |||
RUN, | |||
ONCE, | |||
RAM, | |||
BATTERY, | |||
GPS_ON, | |||
GPS_OFF, | |||
GPS_GET, | |||
GPS_SET, | |||
RTC_GET, | |||
RTC_SET, | |||
EEPROM_GET_CONFIG, | |||
EEPROM_RESET_CONFIG, | |||
EEPROM_GET_CONTENT, | |||
EEPROM_GET_LAST_ENTRY, | |||
EEPROM_GET_ENTRIES, | |||
EEPROM_ADD_ENTRY, | |||
EEPROM_BACKUP_ENTRIES, | |||
SLEEP, | |||
SLEEP_DEEP | |||
}; | |||
void waitForSerial(); | |||
int freeRam(); | |||
void displayFreeRam(); | |||
GPSTRACKER_DEBUG_COMMAND menu(); | |||
GPSTRACKER_DEBUG_COMMAND menu(uint16_t timeout); | |||
void getAndDisplayBattery(); | |||
void getAndDisplayGpsPosition(); | |||
@@ -70,12 +47,13 @@ namespace debug { | |||
void getAndDisplayRtcTime(); | |||
void setRtcTime(); | |||
void getAndDisplaySleepTimes(); | |||
void getAndDisplayEepromConfig(); | |||
void getAndDisplayEepromContent(); | |||
void getAndDisplayEepromPositions(); | |||
void getAndDisplayEepromLastPosition(); | |||
void addLastPositionToEeprom(); | |||
inline void displayFreeRam() { Serial.println(freeRam()); } | |||
} |
@@ -25,13 +25,14 @@ namespace gps { | |||
char lastPosition[GPS_POSITION_SIZE]; | |||
SIM808_GPS_STATUS lastStatus; | |||
SIM808_GPS_STATUS acquireCurrentPosition(uint16_t timeout) { | |||
SIM808_GPS_STATUS acquireCurrentPosition(uint32_t timeout) { | |||
SIM808_GPS_STATUS currentStatus = SIM808_GPS_STATUS::OFF; | |||
do { | |||
currentStatus = hardware::sim808::device.getGpsStatus(); | |||
if (currentStatus > SIM808_GPS_STATUS::NO_FIX) break; | |||
if (currentStatus > SIM808_GPS_STATUS::FIX) break; //if we have an accurate fix, break right now | |||
NOTICE_FORMAT("acquireCurrentPosition", "%d", currentStatus); | |||
mainunit::deepSleep(GPS_DEFAULT_INTERMEDIATE_TIMEOUT_MS / 1000); | |||
timeout -= GPS_DEFAULT_INTERMEDIATE_TIMEOUT_MS; | |||
} while (timeout > 1); | |||
@@ -41,12 +42,13 @@ namespace gps { | |||
hardware::sim808::device.getGpsPosition(lastPosition); | |||
} | |||
NOTICE_FORMAT("acquireCurrentPosition", "%d", currentStatus); | |||
return currentStatus; | |||
} | |||
uint8_t getVelocity() { | |||
uint8_t velocity; | |||
hardware::sim808::device.getGpsField(lastPosition, SIM808_GPS_FIELD::SPEED, &velocity); | |||
if (!hardware::sim808::device.getGpsField(lastPosition, SIM808_GPS_FIELD::SPEED, &velocity)) velocity = 0; | |||
VERBOSE_FORMAT("getVelocity", "%d", velocity); | |||
@@ -67,6 +69,6 @@ namespace gps { | |||
time.Minute = details::parseSubstring(buffer, timeStr + TIME_MINUTE_OFFSET, 2); | |||
time.Second = details::parseSubstring(buffer, timeStr + TIME_SECOND_OFFSET, 2); | |||
VERBOSE_FORMAT("getTime", "%d/%d/%d %d:%d:%d", tmYearToCalendar(time.Year), time.Month, time.Day, time.Hour, time.Minute, time.Second); | |||
NOTICE_FORMAT("getTime", "%d/%d/%d %d:%d:%d", tmYearToCalendar(time.Year), time.Month, time.Day, time.Hour, time.Minute, time.Second); | |||
} | |||
} |
@@ -15,7 +15,7 @@ namespace gps { | |||
inline void powerOn() { hardware::sim808::gpsPowerOn(); } | |||
inline void powerOff() { hardware::sim808::gpsPowerOff(); } | |||
SIM808_GPS_STATUS acquireCurrentPosition(uint16_t timeout); | |||
SIM808_GPS_STATUS acquireCurrentPosition(uint32_t timeout); | |||
uint8_t getVelocity(); | |||
void getTime(tmElements_t &time); |
@@ -1,7 +1,6 @@ | |||
#pragma once | |||
#include <Arduino.h> | |||
#include <SoftwareSerial.h> | |||
#include <SIM808.h> | |||
#include "Debug.h" | |||
@@ -1,24 +1,32 @@ | |||
#include "GpsTracker.h" | |||
#include "Positions.h" | |||
#if _DEBUG | |||
#define MENU_DEFAULT_TIMEOUT 0 | |||
#else | |||
#define MENU_DEFAULT_TIMEOUT 10000 | |||
#endif | |||
bool bypassMenu = false; | |||
uint16_t menuTimeout = MENU_DEFAULT_TIMEOUT; | |||
void setup() { | |||
#ifdef _DEBUG | |||
debug::waitForSerial(); | |||
Log.begin(LOG_LEVEL_VERBOSE, &Serial); | |||
#else | |||
if(Serial) Log.begin(LOG_LEVEL_NOTICE, &Serial); | |||
#endif | |||
logging::setup(); | |||
config::main::setup(); | |||
rtc::setup(); | |||
hardware::sim808::setup(); | |||
positions::setup(); | |||
} | |||
void loop() { | |||
debug::GPSTRACKER_DEBUG_COMMAND command = debug::GPSTRACKER_DEBUG_COMMAND::RUN; | |||
if(!bypassMenu) command = debug::menu(); | |||
if (Serial && !bypassMenu) command = debug::menu(menuTimeout); | |||
if (command == debug::GPSTRACKER_DEBUG_COMMAND::RUN) bypassMenu = true; | |||
else menuTimeout = 0; //disable timeout once a command has been entered | |||
bypassMenu = command == debug::GPSTRACKER_DEBUG_COMMAND::RUN; | |||
@@ -55,7 +63,7 @@ void loop() { | |||
debug::getAndDisplayEepromConfig(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_RESET_CONFIG: | |||
config::reset(); | |||
config::main::reset(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_GET_CONTENT: | |||
debug::getAndDisplayEepromContent(); | |||
@@ -69,14 +77,16 @@ void loop() { | |||
case debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_ADD_ENTRY: | |||
debug::addLastPositionToEeprom(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_BACKUP_ENTRIES: | |||
positions::doBackup(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::SLEEP: | |||
mainunit::sleep(period_t::SLEEP_8S); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::SLEEP_DEEP: | |||
mainunit::deepSleep(10); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::SD_WRITE_TEST: | |||
default: | |||
Serial.println(F("Unsupported command !")); | |||
NOTICE_MSG("loop", "Unsupported"); | |||
} | |||
} |
@@ -1,3 +1,4 @@ | |||
#include "Config.h" | |||
#include "Hardware.h" | |||
#include "Pins.h" | |||
#include "Debug.h" | |||
@@ -41,12 +42,10 @@ namespace hardware { | |||
} | |||
void setup() { | |||
VERBOSE("setup"); | |||
device.powerOnOff(true); | |||
simSerial.begin(4800); | |||
NOTICE("setup"); | |||
simSerial.begin(SIM808_BAUDRATE); | |||
device.begin(simSerial); | |||
device.powerOnOff(false); | |||
powerOff(); //ensure powerOff on start | |||
} | |||
void gpsPowerOn() { | |||
@@ -56,7 +55,7 @@ namespace hardware { | |||
} | |||
void gpsPowerOff() { | |||
VERBOSE("gpsPowerOff"); | |||
NOTICE("gpsPowerOff"); | |||
device.disableGps(); | |||
powerOffIfUnused(); | |||
} | |||
@@ -65,13 +64,12 @@ namespace hardware { | |||
VERBOSE("networkPowerOn"); | |||
powerOn(); | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::FULL); | |||
device.enableGprs(config::get().apn); | |||
} | |||
void networkPowerOff() { | |||
VERBOSE("networkPowerOff"); | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::MINIMUM); | |||
device.disableGprs(); | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::MINIMUM); | |||
powerOffIfUnused(); | |||
} | |||
@@ -82,11 +80,8 @@ namespace hardware { | |||
namespace i2c { | |||
E24 eeprom = E24(E24Size_t::E24_512K); | |||
uint8_t poweredCount = 0; | |||
//inline void powered() { digitalRead(I2C_PWR) == HIGH; } //TODO = replace enum with just reading the output pin ? | |||
void powerOn() { | |||
if (!poweredCount) { | |||
VERBOSE("powerOn"); | |||
@@ -1,11 +1,13 @@ | |||
#pragma once | |||
#include <SoftwareSerial.h> | |||
#include <SIM808.h> | |||
#include <E24.h> | |||
namespace hardware { | |||
namespace sim808 { | |||
extern SoftwareSerial simSerial; | |||
extern SIM808 device; | |||
void powerOn(); | |||
@@ -0,0 +1,18 @@ | |||
#include "Logging.h" | |||
namespace logging { | |||
void setup() { | |||
#if _DEBUG | |||
while (!Serial); | |||
#endif | |||
if (Serial) { | |||
Serial.begin(LOG_SERIAL_SPEED); | |||
Log.begin(LOG_LEVEL, &Serial); | |||
Log.notice(F("Starting...\n")); | |||
} | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
#pragma once | |||
#include "Config.h" | |||
//#define DISABLE_LOGGING 1 | |||
#include <ArduinoLog.h> | |||
#define LOG_SERIAL_SPEED 115200 | |||
#if _DEBUG | |||
#define LOG_LEVEL LOG_LEVEL_VERBOSE | |||
#else | |||
#define LOG_LEVEL LOG_LEVEL_NOTICE | |||
#endif | |||
#define LOG(level, f) Log.level(F("[" LOGGER_NAME "::" f "]\n")) | |||
#define LOG_MSG(level, f, msg) Log.level(F("[" LOGGER_NAME "::" f "] " msg "\n")) | |||
#define LOG_FORMAT(level, f, msg, ...) Log.level(F("[" LOGGER_NAME "::" f "] " msg "\n"), __VA_ARGS__) | |||
#if _DEBUG | |||
#define VERBOSE(f) LOG(verbose, f) | |||
#define VERBOSE_MSG(f, msg) LOG_MSG(verbose, f, msg) | |||
#define VERBOSE_FORMAT(f, msg, ...) LOG_FORMAT(verbose, f, msg, __VA_ARGS__) | |||
#else | |||
#define VERBOSE(f) | |||
#define VERBOSE_MSG(f, msg) | |||
#define VERBOSE_FORMAT(f, msg, ...) | |||
#endif | |||
#define NOTICE(f) LOG(notice, f) | |||
#define NOTICE_MSG(f, msg) LOG_MSG(notice, f, msg) | |||
#define NOTICE_FORMAT(f, msg, ...) LOG_FORMAT(notice, f, msg, __VA_ARGS__) | |||
namespace logging { | |||
void setup(); | |||
} |
@@ -7,6 +7,23 @@ | |||
namespace mainunit { | |||
namespace details { | |||
void prepareSleep() { | |||
hardware::sim808::simSerial.end(); //avoid woke up by SoftwareSerial interrupt | |||
delay(5); //ensure message have been printed out | |||
} | |||
void wokeUp() { | |||
tmElements_t wokeUpTime; | |||
rtc::getTime(wokeUpTime); | |||
VERBOSE_FORMAT("wokeUp", "%d:%d:%d", wokeUpTime.Hour, wokeUpTime.Minute, wokeUpTime.Second); | |||
hardware::sim808::simSerial.listen(); | |||
} | |||
} | |||
void interrupt() { | |||
detachInterrupt(digitalPinToInterrupt(RTC_WAKE)); | |||
} | |||
@@ -19,18 +36,18 @@ namespace mainunit { | |||
} | |||
void sleep(period_t period) { | |||
NOTICE_FORMAT("sleep", "Sleeping for period : %d", period); | |||
delay(5); | |||
NOTICE_FORMAT("sleep", "Period : %d", period); | |||
details::prepareSleep(); | |||
LowPower.powerDown(period, ADC_OFF, BOD_OFF); | |||
NOTICE_MSG("sleep", "Woke up"); | |||
details::wokeUp(); | |||
} | |||
void deepSleep(uint16_t seconds) { | |||
NOTICE_FORMAT("deepSleep", "Deep sleeping for %d seconds", seconds); | |||
NOTICE_FORMAT("deepSleep", "%d seconds", seconds); | |||
interruptIn(seconds); | |||
delay(5); | |||
details::prepareSleep(); | |||
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); | |||
NOTICE_MSG("deepSleep", "Woke up"); | |||
details::wokeUp(); | |||
} | |||
} |
@@ -1,8 +1,52 @@ | |||
#include "Config.h" | |||
#if BACKUP_ENABLE_NETWORK | |||
#include "Debug.h" | |||
#include "Network.h" | |||
#include "Hardware.h" | |||
#include "MainUnit.h" | |||
#define LOGGER_NAME "Network" | |||
namespace network { | |||
SIM808RegistrationStatus waitForRegistered(uint32_t timeout) { | |||
VERBOSE("waitForRegistered"); | |||
SIM808RegistrationStatus currentStatus; | |||
uint8_t noReliableNetwork = 0; | |||
do { | |||
currentStatus = hardware::sim808::device.getNetworkRegistrationStatus(); | |||
if (isAvailable(currentStatus.stat)) break; | |||
SIM808SignalQualityReport report = hardware::sim808::device.getSignalQuality(); | |||
NOTICE_FORMAT("waitForRegistered", "%d, [%d %ddBm]", currentStatus.stat, report.ssri, report.attenuation); | |||
if (report.ssri < NETWORK_DEFAULT_NO_NETWORK_QUALIRY_THRESHOLD) noReliableNetwork++; | |||
else noReliableNetwork = 0; | |||
if (noReliableNetwork > NETWORK_DEFAULT_NO_NETWORK_TRIES) { | |||
NOTICE_MSG("waitForRegistered", "No reliable signal"); | |||
break; //after a while, not network really means no network. Bailing out | |||
} | |||
mainunit::deepSleep(NETWORK_DEFAULT_INTERMEDIATE_TIMEOUT_MS / 1000); | |||
timeout -= NETWORK_DEFAULT_INTERMEDIATE_TIMEOUT_MS; | |||
} while (timeout > 1); | |||
NOTICE_FORMAT("waitForRegistered", "%d, [%d %ddBm]", currentStatus.stat, report.ssri, report.attenuation); | |||
return currentStatus; | |||
} | |||
bool isAvailable(SIM808_NETWORK_REGISTRATION_STATE state) { | |||
return state == SIM808_NETWORK_REGISTRATION_STATE::REGISTERED || | |||
state == SIM808_NETWORK_REGISTRATION_STATE::ROAMING; | |||
} | |||
bool enableGprs() { | |||
return hardware::sim808::device.enableGprs(config::main::value.network.apn); | |||
} | |||
} | |||
} | |||
#endif |
@@ -6,4 +6,8 @@ namespace network { | |||
inline void powerOn() { hardware::sim808::networkPowerOn(); } | |||
inline void powerOff() { hardware::sim808::networkPowerOff(); } | |||
SIM808RegistrationStatus waitForRegistered(uint32_t timeout); | |||
bool isAvailable(SIM808_NETWORK_REGISTRATION_STATE state); | |||
bool enableGprs(); | |||
} |
@@ -0,0 +1,93 @@ | |||
#pragma once | |||
#include "Config.h" | |||
#if BACKUP_ENABLE_NETWORK | |||
#include "NetworkPositionsBackup.h" | |||
#include "Debug.h" | |||
#include "Positions.h" | |||
#include "Hardware.h" | |||
#include "Network.h" | |||
#define LOGGER_NAME "Positions::backup::network" | |||
#define BUFFER_SIZE 170 | |||
namespace positions { | |||
namespace backup { | |||
namespace net { | |||
namespace details { | |||
bool isBackupNeeded() { | |||
config_t *config = &config::main::value; | |||
return (config->network.lastSavedEntry == 0xFFFF && config ->lastEntry != 0xFFFF) || | |||
positions::count(config->network.lastSavedEntry) > config->network.saveThreshold; | |||
} | |||
bool appendPosition(PositionEntry &entry) { | |||
char buffer[BUFFER_SIZE]; | |||
snprintf(buffer, BUFFER_SIZE, "%d,%d,%d,%d,%d,%d,", | |||
debug::freeRam(), | |||
hardware::sim808::device.getSignalQuality().attenuation, | |||
entry.metadata.batteryLevel, | |||
entry.metadata.batteryVoltage, | |||
static_cast<uint16_t>(entry.metadata.temperature * 100), | |||
static_cast<uint8_t>(entry.metadata.status)); | |||
strcat(buffer, entry.position); | |||
NOTICE_FORMAT("appendPosition", "Sending : %s", buffer); | |||
uint16_t responseCode = hardware::sim808::device.httpPost( | |||
config::main::value.network.url, | |||
F("text/csv"), | |||
buffer, | |||
buffer, | |||
BUFFER_SIZE | |||
) == POSITIONS_CONFIG_NET_DEFAULT_EXPECTED_RESPONSE; | |||
NOTICE_FORMAT("appendPosition", "Response : %d", responseCode); | |||
return responseCode; | |||
} | |||
void appendPositions() { | |||
uint16_t currentEntryIndex = config::main::value.network.lastSavedEntry + 1; | |||
PositionEntry currentEntry; | |||
SIM808RegistrationStatus networkStatus; | |||
network::powerOn(); | |||
networkStatus = network::waitForRegistered(NETWORK_DEFAULT_TOTAL_TIMEOUT_MS); | |||
if (!network::isAvailable(networkStatus.stat)) NOTICE_MSG("appendPositions", "network unavailable"); | |||
else if (!network::enableGprs()) NOTICE_MSG("appendPositions", "gprs unavailable"); | |||
else { | |||
hardware::i2c::powerOn(); | |||
do { | |||
if (!positions::get(currentEntryIndex, currentEntry)) break; | |||
if (!appendPosition(currentEntry)) break; | |||
config::main::value.network.lastSavedEntry = currentEntryIndex; | |||
config::main::save(); | |||
} while (positions::moveNext(currentEntryIndex)); | |||
hardware::i2c::powerOff(); | |||
} | |||
network::powerOff(); | |||
} | |||
} | |||
void NetworkPositionsBackup::setup() { | |||
NOTICE("setup"); | |||
} | |||
void NetworkPositionsBackup::backup() { | |||
NOTICE("backup"); | |||
if (!details::isBackupNeeded()) return; | |||
details::appendPositions(); | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,18 @@ | |||
#pragma once | |||
#include "PositionsBackup.h" | |||
namespace positions { | |||
namespace backup { | |||
namespace net { | |||
class NetworkPositionsBackup : public PositionsBackup { | |||
private: | |||
public: | |||
void setup(); | |||
void backup(); | |||
}; | |||
} | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#define POSITIONS_CONFIG_NET_DEFAULT_SAVE_THRESHOLD 10 | |||
#define POSITIONS_CONFIG_NET_DEFAULT_APN "Vodafone" | |||
#define POSITIONS_CONFIG_NET_DEFAULT_URL "http://yourserver.com/endpoint" | |||
#define POSITIONS_CONFIG_NET_DEFAULT_EXPECTED_RESPONSE 201 | |||
struct networkConfig_t { | |||
uint8_t saveThreshold; | |||
uint16_t lastSavedEntry; | |||
char apn[20]; | |||
char url[50]; | |||
}; |
@@ -1,26 +1,60 @@ | |||
#include "Positions.h" | |||
#include "Debug.h" | |||
#include "Config.h" | |||
#include "Debug.h" | |||
#include "Positions.h" | |||
#include "Gps.h" | |||
#include "Network.h" | |||
#if BACKUP_ENABLE_SDCARD || BACKUP_ENABLE_NETWORK | |||
#define BACKUPS_ENABLED BACKUP_ENABLE_SDCARD + BACKUP_ENABLE_NETWORK | |||
#endif | |||
#if BACKUP_ENABLE_SDCARD | |||
#include "SdPositionsBackup.h" | |||
#endif | |||
#if BACKUP_ENABLE_NETWORK | |||
#include "NetworkPositionsBackup.h" | |||
#endif | |||
#define LOGGER_NAME "Positions" | |||
#define ENTRY_RESERVED_SIZE 128 | |||
#define ENTRIES_ADDR ENTRY_RESERVED_SIZE | |||
#define ENTRIES_ADDR CONFIG_RESERVED_SIZE | |||
namespace positions { | |||
#ifdef BACKUPS_ENABLED | |||
backup::PositionsBackup **_backups; | |||
#endif | |||
uint16_t _maxEntryIndex = (E24_MAX_ADDRESS(hardware::i2c::eeprom.getSize()) - ENTRIES_ADDR) / ENTRY_RESERVED_SIZE; | |||
namespace details { | |||
uint16_t maxEntryIndex = 0; | |||
uint16_t getEntryAddress(uint16_t index) { | |||
if (index > _maxEntryIndex) return -1; | |||
return ENTRIES_ADDR + (ENTRY_RESERVED_SIZE * index); | |||
uint16_t getEntryAddress(uint16_t index) { | |||
if (index > maxEntryIndex) return -1; | |||
return ENTRIES_ADDR + (ENTRY_RESERVED_SIZE * index); | |||
} | |||
} | |||
void setup() { | |||
details::maxEntryIndex = (E24_MAX_ADDRESS(hardware::i2c::eeprom.getSize()) - ENTRIES_ADDR) / ENTRY_RESERVED_SIZE; | |||
#ifdef BACKUPS_ENABLED | |||
uint8_t backupIdx = 0; | |||
_backups = new backup::PositionsBackup*[BACKUPS_ENABLED]; | |||
#if BACKUP_ENABLE_SDCARD | |||
_backups[backupIdx] = new backup::sd::SdPositionsBackup(); | |||
_backups[backupIdx]->setup(); | |||
backupIdx++; | |||
#endif | |||
#if BACKUP_ENABLE_NETWORK | |||
_backups[backupIdx] = new backup::net::NetworkPositionsBackup(); | |||
_backups[backupIdx]->setup(); | |||
backupIdx++; | |||
#endif | |||
#endif | |||
} | |||
bool acquire(PositionEntryMetadata &metadata) { | |||
VERBOSE("acquire"); | |||
NOTICE("acquire"); | |||
timestamp_t before; | |||
@@ -30,6 +64,8 @@ namespace positions { | |||
SIM808ChargingStatus battery = hardware::sim808::device.getChargingState(); | |||
gps::powerOff(); | |||
NOTICE_FORMAT("acquire", "Status : %d", gpsStatus); | |||
if (gpsStatus < SIM808_GPS_STATUS::FIX) return false; | |||
uint16_t timeToFix = rtc::getTime() - before; | |||
@@ -52,29 +88,34 @@ namespace positions { | |||
void appendLast(const PositionEntryMetadata &metadata) { | |||
VERBOSE("appendLast"); | |||
uint16_t entryIndex; | |||
uint16_t entryAddress; | |||
PositionEntry entry = { metadata }; | |||
strlcpy(entry.position, gps::lastPosition, POSITION_SIZE); | |||
hardware::i2c::powerOn(); | |||
Config config = config::get(); | |||
config_t* config = &config::main::value; | |||
entryIndex = config->lastEntry + 1; | |||
config.lastEntry++; | |||
if (config.lastEntry > _maxEntryIndex) config.lastEntry = 0; | |||
if (config.lastEntry == config.firstEntry) config.firstEntry++; | |||
if (config.firstEntry > _maxEntryIndex) config.firstEntry = 0; | |||
entryAddress = details::getEntryAddress(entryIndex); | |||
entryAddress = getEntryAddress(config.lastEntry); | |||
hardware::i2c::powerOn(); | |||
hardware::i2c::eeprom.writeBlock(entryAddress, entry); | |||
VERBOSE_FORMAT("appendLast", "Written to EEPROM @ %X : [%d%% @ %dmV] [%f°C] [TTF : %d, Status : %d, Position : %s]", entryAddress, entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
NOTICE_FORMAT("appendLast", "Saved @ %X : [%d%% @ %dmV] [%f°C] [TTF : %d, Status : %d, Position : %s]", entryAddress, entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
config::set(config); | |||
config->lastEntry++; | |||
if (config->lastEntry > details::maxEntryIndex) config->lastEntry = 0; | |||
if (config->lastEntry == config->firstEntry) config->firstEntry++; | |||
if (config->firstEntry > details::maxEntryIndex) config->firstEntry = 0; | |||
config::main::save(); | |||
hardware::i2c::powerOff(); | |||
} | |||
bool get(uint16_t index, PositionEntry &entry) { | |||
uint16_t entryAddress = getEntryAddress(index); | |||
VERBOSE("get"); | |||
uint16_t entryAddress = details::getEntryAddress(index); | |||
if (entryAddress == -1) return false; | |||
VERBOSE_FORMAT("get", "Reading entry n°%d @ %X", index, entryAddress); | |||
@@ -83,24 +124,31 @@ namespace positions { | |||
hardware::i2c::eeprom.readBlock(entryAddress, entry); | |||
hardware::i2c::powerOff(); | |||
VERBOSE_FORMAT("get", "Read from EEPROM @ %X : [%d%% @ %dmV] [%f°C] [%d, %s]", entryAddress, entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
NOTICE_FORMAT("get", "Read from EEPROM @ %X : [%d%% @ %dmV] [%f°C] [TTF : %d, Status : %d, Position : %s]", entryAddress, entry.metadata.batteryLevel, entry.metadata.batteryVoltage, entry.metadata.temperature, entry.metadata.timeToFix, entry.metadata.status, entry.position); | |||
return true; | |||
} | |||
bool moveNext(uint16_t &index) { | |||
if (index == config::get().lastEntry) return false; | |||
if (index == config::main::value.lastEntry) return false; | |||
if (index == _maxEntryIndex) index = 0; //could use a modulo but easier to understand that way | |||
if (index == details::maxEntryIndex) index = 0; //could use a modulo but easier to understand that way | |||
else index++; | |||
return true; | |||
} | |||
bool needsToSend() { | |||
return false; | |||
} | |||
uint16_t count(uint16_t fromIndex) { | |||
config_t *config = &config::main::value; | |||
if (config->lastEntry < config->firstEntry) { config->lastEntry += details::maxEntryIndex; } | |||
void send() { | |||
return config->lastEntry - fromIndex; | |||
} | |||
void doBackup() { | |||
#ifdef BACKUPS_ENABLED | |||
for (int i = 0; i < BACKUPS_ENABLED; i++) { | |||
_backups[i]->backup(); | |||
} | |||
#endif | |||
} | |||
} |
@@ -18,12 +18,14 @@ struct PositionEntry { | |||
}; //sizeof = 125 | |||
namespace positions { | |||
void setup(); | |||
bool acquire(PositionEntryMetadata &metadata); | |||
void appendLast(const PositionEntryMetadata &metadata); | |||
bool get(uint16_t index, PositionEntry &entry); | |||
bool moveNext(uint16_t &index); | |||
uint16_t count(uint16_t fromIndex); | |||
bool needsToSend(); | |||
void send(); | |||
void doBackup(); | |||
} |
@@ -0,0 +1,9 @@ | |||
#include "PositionsBackup.h" | |||
namespace positions { | |||
namespace backup { | |||
PositionsBackup::~PositionsBackup() {} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
namespace positions { | |||
namespace backup { | |||
class PositionsBackup { | |||
public: | |||
~PositionsBackup(); | |||
virtual void setup()=0; | |||
virtual void backup()=0; | |||
}; | |||
} | |||
} |
@@ -17,7 +17,7 @@ namespace rtc { | |||
hardware::i2c::powerOn(); | |||
RTC.control(DS3231_12H, DS3231_OFF); //24 hours clock | |||
RTC.control(DS3231_A1_INT_ENABLE, DS3231_OFF); //Alarm 1 OFF | |||
RTC.control(DS3231_INT_ENABLE, DS3231_ON); //INTCN OFF | |||
RTC.control(DS3231_INT_ENABLE, DS3231_ON); //INTCN ON | |||
hardware::i2c::powerOff(); | |||
//TODO : check wether the osc has been halted (meaning the battery could be dead) | |||
@@ -31,6 +31,14 @@ namespace rtc { | |||
return temperature; | |||
} | |||
bool isAccurate() { | |||
hardware::i2c::powerOn(); | |||
bool accurate = RTC.status(DS3231_HALTED_FLAG) == DS3231_OFF; | |||
hardware::i2c::powerOff(); | |||
return accurate; | |||
} | |||
timestamp_t getTime() { | |||
tmElements_t time; | |||
getTime(time); | |||
@@ -50,6 +58,7 @@ namespace rtc { | |||
hardware::i2c::powerOn(); | |||
RTC.writeTime(time); | |||
RTC.control(DS3231_HALTED_FLAG, DS3231_OFF); | |||
hardware::i2c::powerOff(); | |||
} | |||
@@ -7,6 +7,7 @@ namespace rtc { | |||
float getTemperature(); | |||
bool isAccurate(); | |||
timestamp_t getTime(); | |||
void getTime(tmElements_t &time); | |||
void setTime(const tmElements_t &time); | |||
@@ -0,0 +1,18 @@ | |||
#include "Config.h" | |||
#if BACKUP_ENABLE_SDCARD | |||
#include "SdCard.h" | |||
namespace hardware { | |||
namespace sdcard { | |||
SdFat filesystem; | |||
bool available = false; | |||
void setup() { | |||
available = filesystem.begin(SD_SS); | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,14 @@ | |||
#pragma once | |||
#include <SdFat.h> | |||
#include "Pins.h" | |||
namespace hardware { | |||
namespace sdcard { | |||
extern SdFat filesystem; | |||
extern bool available; | |||
void setup(); | |||
} | |||
} |
@@ -0,0 +1,119 @@ | |||
#include "SdPositionsBackup.h" | |||
#include "SdPositionsConfig.h" | |||
#include "SdCard.h" | |||
#include "Hardware.h" | |||
#include "Config.h" | |||
#include "Positions.h" | |||
#include "Debug.h" | |||
#define LOGGER_NAME "Positions::backup::sd" | |||
#if BACKUP_ENABLE_SDCARD | |||
namespace positions { | |||
namespace backup { | |||
namespace sd { | |||
namespace details { | |||
bool isBackupNeeded(SdPositionConfig_t &sdConfig) { | |||
Config_t referenceConfig = config::main::value; | |||
sdConfig = config::backup::sd::get(); | |||
return sdConfig.lastSavedEntry == 0xFFFF || | |||
positions::count(sdConfig.lastSavedEntry) > sdConfig.saveThreshold; | |||
} | |||
void getPositionsFileName(uint16_t fileIndex, char *buffer) { | |||
sprintf(buffer, POSITIONS_FILENAME, fileIndex); | |||
} | |||
void ensurePositionsFolder() { | |||
char positionsFolder[] = POSITIONS_FOLDER; | |||
hardware::sdcard::filesystem.chdir(); | |||
if (!hardware::sdcard::filesystem.exists(positionsFolder)) { | |||
hardware::sdcard::filesystem.mkdir(positionsFolder, true); | |||
} | |||
hardware::sdcard::filesystem.chdir(positionsFolder); | |||
} | |||
void selectFile(SdPositionConfig_t &sdConfig, File &file) { | |||
char positionFileName[POSITIONS_FILENAME_LENGTH]; | |||
if (sdConfig.fileRecords >= sdConfig.maxRecordsPerFile) { | |||
if (file.isOpen()) { | |||
file.close(); | |||
config::backup::sd::set(sdConfig); | |||
} | |||
sdConfig.fileIndex++; | |||
sdConfig.filePosition = 0; | |||
sdConfig.fileRecords = 0; | |||
} | |||
if (!file.isOpen()) { | |||
ensurePositionsFolder(); | |||
getPositionsFileName(sdConfig.fileIndex, positionFileName); | |||
file.open(positionFileName, O_RDWR | O_CREAT); | |||
} | |||
} | |||
void appendPosition(File &file, SdPositionConfig_t &sdConfig, PositionEntry &entry) { | |||
VERBOSE("appendPosition"); | |||
const char fieldTerminator = ','; | |||
file.printField(entry.metadata.batteryLevel, fieldTerminator); | |||
file.printField(entry.metadata.batteryVoltage, fieldTerminator); | |||
file.printField(entry.metadata.temperature, fieldTerminator); | |||
file.printField(entry.metadata.timeToFix, fieldTerminator); | |||
file.printField(static_cast<uint8_t>(entry.metadata.status), fieldTerminator); | |||
file.println(entry.position); | |||
sdConfig.filePosition = file.position(); | |||
} | |||
void appendPositions(SdPositionConfig_t &sdConfig) { | |||
VERBOSE("appendPositions"); | |||
uint16_t currentEntryIndex = sdConfig.lastSavedEntry + 1; | |||
PositionEntry currentEntry; | |||
File file; | |||
hardware::i2c::powerOn(); | |||
do { | |||
if (!positions::get(currentEntryIndex, currentEntry)) break; | |||
selectFile(sdConfig, file); | |||
appendPosition(file, sdConfig, currentEntry); | |||
sdConfig.lastSavedEntry = currentEntryIndex; | |||
} while (positions::moveNext(currentEntryIndex)); | |||
hardware::i2c::powerOff(); | |||
if (file.isOpen()) file.close(); | |||
config::backup::sd::set(sdConfig); | |||
} | |||
} | |||
void SdPositionsBackup::setup() { | |||
hardware::sdcard::setup(); | |||
} | |||
void SdPositionsBackup::backup() { | |||
VERBOSE("backup"); | |||
if (!hardware::sdcard::available) { | |||
VERBOSE_MSG("backup", "not available"); | |||
return; | |||
} | |||
SdPositionConfig_t sdConfig; | |||
if (!details::isBackupNeeded(sdConfig)) return; | |||
details::appendPositions(sdConfig); | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,18 @@ | |||
#pragma once | |||
#include "PositionsBackup.h" | |||
namespace positions { | |||
namespace backup { | |||
namespace sd { | |||
class SdPositionsBackup : public PositionsBackup { | |||
private: | |||
public: | |||
void setup(); | |||
void backup(); | |||
}; | |||
} | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
#include "SdPositionsConfig.h" | |||
#include "SdCard.h" | |||
#include "Debug.h" | |||
#define LOGGER_NAME "Config::backup::sd" | |||
#if BACKUP_ENABLE_SDCARD | |||
namespace config { | |||
namespace backup { | |||
namespace sd { | |||
SdPositionConfig_t value; | |||
File configFile; | |||
namespace details { | |||
void ensureOpened() { | |||
if (!configFile.isOpen()) { | |||
hardware::sdcard::filesystem.chdir(); | |||
configFile.open(POSITIONS_CONFIG_FILENAME, O_RDWR | O_CREAT); | |||
} | |||
configFile.rewind(); | |||
} | |||
void read() { | |||
VERBOSE("read"); | |||
ensureOpened(); | |||
configFile.read((void*)&value, sizeof(value)); | |||
if (value.seed != POSITIONS_CONFIG_SEED) reset(); | |||
} | |||
void write() { | |||
VERBOSE("write"); | |||
ensureOpened(); | |||
configFile.write((void*)&value, sizeof(value)); | |||
} | |||
} | |||
SdPositionConfig_t get() { | |||
if (value.seed != POSITIONS_CONFIG_SEED) details::read(); | |||
return value; | |||
} | |||
void set(SdPositionConfig_t config) { | |||
value = config; | |||
details::write(); | |||
} | |||
void reset() { | |||
VERBOSE("reset"); | |||
SdPositionConfig_t config = { | |||
POSITIONS_CONFIG_SEED, | |||
POSITIONS_CONFIG_DEFAULT_SAVE_THRESHOLD, | |||
POSITIONS_CONFIG_DEFAULT_MAX_RECORDS_PER_FILE, | |||
0xFFFF, | |||
0, | |||
0, | |||
0 | |||
}; | |||
value = config; | |||
details::write(); | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,35 @@ | |||
#pragma once | |||
#include <Arduino.h> | |||
#define POSITIONS_FOLDER "positions" | |||
#define POSITIONS_FILENAME "positions-%05d.csv" | |||
#define POSITIONS_FILENAME_LENGTH 19 | |||
#define POSITIONS_CONFIG_FILENAME "positions.config" | |||
#define POSITIONS_CONFIG_SEED 45 | |||
#define POSITIONS_CONFIG_DEFAULT_SAVE_THRESHOLD 10 | |||
#define POSITIONS_CONFIG_DEFAULT_MAX_RECORDS_PER_FILE 5 | |||
struct SdPositionConfig_t { | |||
uint8_t seed; | |||
uint8_t saveThreshold; | |||
uint8_t maxRecordsPerFile; | |||
uint16_t lastSavedEntry; | |||
uint16_t fileIndex; | |||
uint32_t filePosition; | |||
size_t fileRecords; | |||
}; | |||
namespace config { | |||
namespace backup { | |||
namespace sd { | |||
void setup(); | |||
SdPositionConfig_t get(); | |||
void set(SdPositionConfig_t config); | |||
void reset(); | |||
} | |||
} | |||
} |