@@ -1,267 +1,5 @@ | |||
## Ignore Visual Studio temporary files, build results, and | |||
## files generated by popular Visual Studio add-ons. | |||
# User-specific files | |||
*.suo | |||
*.user | |||
*.userosscache | |||
*.sln.docstates | |||
# User-specific files (MonoDevelop/Xamarin Studio) | |||
*.userprefs | |||
# Build results | |||
[Dd]ebug/ | |||
[Dd]ebugPublic/ | |||
[Rr]elease/ | |||
[Rr]eleases/ | |||
x64/ | |||
x86/ | |||
bld/ | |||
[Bb]in/ | |||
[Oo]bj/ | |||
[Ll]og/ | |||
# Visual Studio 2015 cache/options directory | |||
.vs/ | |||
# Uncomment if you have tasks that create the project's static files in wwwroot | |||
#wwwroot/ | |||
# MSTest test Results | |||
[Tt]est[Rr]esult*/ | |||
[Bb]uild[Ll]og.* | |||
# NUNIT | |||
*.VisualState.xml | |||
TestResult.xml | |||
# Build Results of an ATL Project | |||
[Dd]ebugPS/ | |||
[Rr]eleasePS/ | |||
dlldata.c | |||
# DNX | |||
project.lock.json | |||
project.fragment.lock.json | |||
artifacts/ | |||
*_i.c | |||
*_p.c | |||
*_i.h | |||
*.ilk | |||
*.meta | |||
*.obj | |||
*.pch | |||
*.pdb | |||
*.pgc | |||
*.pgd | |||
*.rsp | |||
*.sbr | |||
*.tlb | |||
*.tli | |||
*.tlh | |||
*.tmp | |||
*.tmp_proj | |||
*.log | |||
*.vspscc | |||
*.vssscc | |||
.builds | |||
*.pidb | |||
*.svclog | |||
*.scc | |||
# Chutzpah Test files | |||
_Chutzpah* | |||
# Visual C++ cache files | |||
ipch/ | |||
*.aps | |||
*.ncb | |||
*.opendb | |||
*.opensdf | |||
*.sdf | |||
*.cachefile | |||
*.VC.db | |||
*.VC.VC.opendb | |||
# Visual Studio profiler | |||
*.psess | |||
*.vsp | |||
*.vspx | |||
*.sap | |||
# TFS 2012 Local Workspace | |||
$tf/ | |||
# Guidance Automation Toolkit | |||
*.gpState | |||
# ReSharper is a .NET coding add-in | |||
_ReSharper*/ | |||
*.[Rr]e[Ss]harper | |||
*.DotSettings.user | |||
# JustCode is a .NET coding add-in | |||
.JustCode | |||
# TeamCity is a build add-in | |||
_TeamCity* | |||
# DotCover is a Code Coverage Tool | |||
*.dotCover | |||
# NCrunch | |||
_NCrunch_* | |||
.*crunch*.local.xml | |||
nCrunchTemp_* | |||
# MightyMoose | |||
*.mm.* | |||
AutoTest.Net/ | |||
# Web workbench (sass) | |||
.sass-cache/ | |||
# Installshield output folder | |||
[Ee]xpress/ | |||
# DocProject is a documentation generator add-in | |||
DocProject/buildhelp/ | |||
DocProject/Help/*.HxT | |||
DocProject/Help/*.HxC | |||
DocProject/Help/*.hhc | |||
DocProject/Help/*.hhk | |||
DocProject/Help/*.hhp | |||
DocProject/Help/Html2 | |||
DocProject/Help/html | |||
# Click-Once directory | |||
publish/ | |||
# Publish Web Output | |||
*.[Pp]ublish.xml | |||
*.azurePubxml | |||
# TODO: Comment the next line if you want to checkin your web deploy settings | |||
# but database connection strings (with potential passwords) will be unencrypted | |||
#*.pubxml | |||
*.publishproj | |||
# Microsoft Azure Web App publish settings. Comment the next line if you want to | |||
# checkin your Azure Web App publish settings, but sensitive information contained | |||
# in these scripts will be unencrypted | |||
PublishScripts/ | |||
# NuGet Packages | |||
*.nupkg | |||
# The packages folder can be ignored because of Package Restore | |||
**/packages/* | |||
# except build/, which is used as an MSBuild target. | |||
!**/packages/build/ | |||
# Uncomment if necessary however generally it will be regenerated when needed | |||
#!**/packages/repositories.config | |||
# NuGet v3's project.json files produces more ignoreable files | |||
*.nuget.props | |||
*.nuget.targets | |||
# Microsoft Azure Build Output | |||
csx/ | |||
*.build.csdef | |||
# Microsoft Azure Emulator | |||
ecf/ | |||
rcf/ | |||
# Windows Store app package directories and files | |||
AppPackages/ | |||
BundleArtifacts/ | |||
Package.StoreAssociation.xml | |||
_pkginfo.txt | |||
# Visual Studio cache files | |||
# files ending in .cache can be ignored | |||
*.[Cc]ache | |||
# but keep track of directories ending in .cache | |||
!*.[Cc]ache/ | |||
# Others | |||
ClientBin/ | |||
~$* | |||
*~ | |||
*.dbmdl | |||
*.dbproj.schemaview | |||
*.jfm | |||
*.pfx | |||
*.publishsettings | |||
node_modules/ | |||
orleans.codegen.cs | |||
# Since there are multiple workflows, uncomment next line to ignore bower_components | |||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) | |||
#bower_components/ | |||
# RIA/Silverlight projects | |||
Generated_Code/ | |||
# Backup & report files from converting an old project file | |||
# to a newer Visual Studio version. Backup files are not needed, | |||
# because we have git ;-) | |||
_UpgradeReport_Files/ | |||
Backup*/ | |||
UpgradeLog*.XML | |||
UpgradeLog*.htm | |||
# SQL Server files | |||
*.mdf | |||
*.ldf | |||
# Business Intelligence projects | |||
*.rdl.data | |||
*.bim.layout | |||
*.bim_*.settings | |||
# Microsoft Fakes | |||
FakesAssemblies/ | |||
# GhostDoc plugin setting file | |||
*.GhostDoc.xml | |||
# Node.js Tools for Visual Studio | |||
.ntvs_analysis.dat | |||
# Visual Studio 6 build log | |||
*.plg | |||
# Visual Studio 6 workspace options file | |||
*.opt | |||
# Visual Studio LightSwitch build output | |||
**/*.HTMLClient/GeneratedArtifacts | |||
**/*.DesktopClient/GeneratedArtifacts | |||
**/*.DesktopClient/ModelManifest.xml | |||
**/*.Server/GeneratedArtifacts | |||
**/*.Server/ModelManifest.xml | |||
_Pvt_Extensions | |||
# Paket dependency manager | |||
.paket/paket.exe | |||
paket-files/ | |||
# FAKE - F# Make | |||
.fake/ | |||
# JetBrains Rider | |||
.idea/ | |||
*.sln.iml | |||
# CodeRush | |||
.cr/ | |||
# Python Tools for Visual Studio (PTVS) | |||
__pycache__/ | |||
*.pyc | |||
#VisualMicro | |||
__vm/ | |||
#Line endings unifier | |||
.leu | |||
#Visual Studio code | |||
.vscode/ |
@@ -0,0 +1,22 @@ | |||
{ | |||
"configurations": [ | |||
{ | |||
"name": "Win32", | |||
"includePath": [ | |||
"${workspaceFolder}/**", | |||
"${workspaceFolder}/../libraries/SIM808", | |||
"${workspaceFolder}/../libraries/uDS3231", | |||
"${workspaceFolder}/../libraries/E24", | |||
"${workspaceFolder}/../libraries/Low-Power", | |||
"${workspaceFolder}/../libraries/ArduinoLog", | |||
"${config:arduino.path}/tools/**", | |||
"${config:arduino.path}/hardware/arduino/avr/**", | |||
"${config:arduino.path}/hardware/tools/avr/avr/include/**" | |||
], | |||
"intelliSenseMode": "clang-x64", | |||
"cStandard": "c11", | |||
"cppStandard": "c++11" | |||
} | |||
], | |||
"version": 4 | |||
} |
@@ -0,0 +1,50 @@ | |||
#pragma once | |||
#include "Debug.h" | |||
#include "Alerts.h" | |||
#include "Config.h" | |||
#include "Rtc.h" | |||
#define LOGGER_NAME "Alerts" | |||
namespace alerts { | |||
uint8_t getTriggered(PositionEntryMetadata &metadata) { | |||
config_t* config = &config::main::value; | |||
uint8_t active = 0; | |||
if (metadata.batteryLevel <= config->alertBatteryLevel1) bitSet(active, ALERT_BATTERY_LEVEL_1); | |||
if (metadata.batteryLevel <= config->alertBatteryLevel2) bitSet(active, ALERT_BATTERY_LEVEL_2); | |||
if (metadata.temperature == ALERT_SUSPICIOUS_RTC_TEMPERATURE) bitSet(active, ALERT_RTC_TEMPERATURE_FAILURE); | |||
if (!rtc::isAccurate()) bitSet(active, ALERT_RTC_CLOCK_FAILURE); | |||
return config->activeAlerts ^ active; | |||
} | |||
void add(uint8_t mask) { | |||
config_t* config = &config::main::value; | |||
uint8_t active = config->activeAlerts; | |||
active |= mask; | |||
if (config->activeAlerts == active) return; //save a write to eeprom if there is no change | |||
config->activeAlerts = active; | |||
config::main::save(); | |||
} | |||
void clear(PositionEntryMetadata &metadata) { | |||
config_t* config = &config::main::value; | |||
uint8_t clearMask = 0; | |||
uint8_t active = config->activeAlerts; | |||
if (metadata.batteryLevel >= config->alertBatteryLevelClear) clearMask |= _BV(ALERT_BATTERY_LEVEL_1) | _BV(ALERT_BATTERY_LEVEL_2); | |||
if (metadata.temperature != ALERT_SUSPICIOUS_RTC_TEMPERATURE) bitSet(clearMask, ALERT_RTC_TEMPERATURE_FAILURE); | |||
if (rtc::isAccurate()) bitSet(clearMask, ALERT_RTC_CLOCK_FAILURE); | |||
active &= ~clearMask; | |||
if (config->activeAlerts == active) return; //save a write to eeprom if there is no change | |||
config->activeAlerts = active; | |||
config::main::save(); | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#include "Positions.h" | |||
#define ALERT_BATTERY_LEVEL_1 0 | |||
#define ALERT_BATTERY_LEVEL_2 1 | |||
#define ALERT_RTC_TEMPERATURE_FAILURE 2 | |||
#define ALERT_RTC_CLOCK_FAILURE 3 | |||
namespace alerts { | |||
uint8_t getTriggered(PositionEntryMetadata &metadata); | |||
void add(uint8_t mask); | |||
void clear(PositionEntryMetadata &metadata); | |||
} |
@@ -0,0 +1,86 @@ | |||
#include "Config.h" | |||
#include "Debug.h" | |||
#include "Hardware.h" | |||
#define LOGGER_NAME "Config" | |||
namespace config { | |||
namespace main { | |||
config_t value; | |||
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, %d, %d, %d, %B, %s", value.seed, value.version, value.firstEntry, value.lastEntry, value.alertBatteryLevel1, value.alertBatteryLevel2, value.alertBatteryLevelClear, value.activeAlerts, value.contactPhone); | |||
#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 | |||
/*strcpy_P(value.version, PSTR(VERSION)); | |||
value.alertBatteryLevel1 = CONFIG_DEFAULT_BATTERY_ALERT_LEVEL1; | |||
value.alertBatteryLevel2 = CONFIG_DEFAULT_BATTERY_ALERT_LEVEL2; | |||
value.alertBatteryLevelClear = CONFIG_DEFAULT_BATTERY_ALERT_CLEAR; | |||
value.activeAlerts = CONFIG_DEFAULT_ACTIVE_ALERTS; | |||
strcpy_P(config.contactPhone, PSTR(CONFIG_DEFAULT_CONTACT_PHONE));*/ | |||
} | |||
void write() { | |||
NOTICE_FORMAT("write", "%d, %s, %d, %d, %d, %d, %d, %B, %s", value.seed, value.version, value.firstEntry, value.lastEntry, value.alertBatteryLevel1, value.alertBatteryLevel2, value.alertBatteryLevelClear, value.activeAlerts, value.contactPhone); | |||
#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 setup() { | |||
details::read(); | |||
//details::write(); | |||
} | |||
void save() { | |||
details::write(); | |||
} | |||
void reset() { | |||
VERBOSE("reset"); | |||
config_t config = {}; | |||
config.seed = CONFIG_SEED; | |||
strcpy_P(config.version, PSTR(VERSION)); | |||
config.firstEntry = config.lastEntry = 0xFFFF; | |||
config.alertBatteryLevel1 = CONFIG_DEFAULT_BATTERY_ALERT_LEVEL1; | |||
config.alertBatteryLevel2 = CONFIG_DEFAULT_BATTERY_ALERT_LEVEL2; | |||
config.alertBatteryLevelClear = CONFIG_DEFAULT_BATTERY_ALERT_CLEAR; | |||
config.activeAlerts = CONFIG_DEFAULT_ACTIVE_ALERTS; | |||
strcpy_P(config.contactPhone, PSTR(CONFIG_DEFAULT_CONTACT_PHONE)); | |||
#if BACKUP_ENABLE_NETWORK | |||
config.network.saveThreshold = POSITIONS_CONFIG_NET_DEFAULT_SAVE_THRESHOLD; | |||
config.network.lastSavedEntry = 0xFFFF; | |||
strcpy_P(config.network.apn, PSTR(POSITIONS_CONFIG_NET_DEFAULT_APN)); | |||
strcpy_P(config.network.url, PSTR(POSITIONS_CONFIG_NET_DEFAULT_URL)); | |||
#endif | |||
value = config; | |||
save(); | |||
} | |||
} | |||
} |
@@ -14,7 +14,7 @@ | |||
#define CONFIG_ADDR 0 | |||
#define CONFIG_RESERVED_SIZE 128 | |||
#define CONFIG_SEED 13 | |||
#define VERSION "1.00" | |||
#define VERSION "1.10" | |||
#define SLEEP_TIMING_TIME(hours, minutes) hours * 60 + minutes | |||
@@ -26,8 +26,15 @@ | |||
Hard coded value for default sleep time between position acquisitions. | |||
Exprimed in seconds | |||
*/ | |||
#define CONFIG_DEFAULT_BATTERY_ALERT_LEVEL1 45 | |||
#define CONFIG_DEFAULT_BATTERY_ALERT_LEVEL2 38 | |||
#define CONFIG_DEFAULT_BATTERY_ALERT_CLEAR 60 | |||
#define CONFIG_DEFAULT_ACTIVE_ALERTS 0 | |||
#define CONFIG_DEFAULT_CONTACT_PHONE "+642568452" | |||
#define SLEEP_DEFAULT_TIME_SECONDS 1800 | |||
#define SLEEP_DEFAULT_STOPPED_THRESHOLD 5 | |||
#define SLEEP_DEFAULT_STOPPED_THRESHOLD 5 | |||
#define SLEEP_DEFAULT_PAUSING_TIME_SECONDS 270 | |||
#define SLEEP_TIMING_MIN SLEEP_TIMING_TIME(0, 0) | |||
@@ -43,6 +50,14 @@ | |||
#define NETWORK_DEFAULT_NO_NETWORK_QUALITY_THRESHOLD 8 | |||
#define NETWORK_DEFAULT_NO_NETWORK_TRIES 5 | |||
#define ALERTS_ON_SERIAL_IF_AVAILABLE 1 | |||
/** | |||
\def ALERTS_ON_SERIAL_IF_AVAILABLE | |||
Display alerts on serial when connected rather than sending an SMS. | |||
Useful for debugging purpose and avoid costs related to SMS sending. | |||
*/ | |||
#define ALERT_SUSPICIOUS_RTC_TEMPERATURE 0 | |||
#pragma endregion | |||
struct sleepTimings_t { | |||
@@ -53,14 +68,21 @@ struct sleepTimings_t { | |||
}; | |||
struct config_t { | |||
uint8_t seed; | |||
char version[5]; | |||
uint16_t firstEntry; | |||
uint16_t lastEntry; | |||
uint8_t seed; //sizeof = 1 | |||
char version[5]; //sizeof = 5 | |||
uint16_t firstEntry; //sizeof = 2 | |||
uint16_t lastEntry; //sizeof = 2 | |||
#if BACKUP_ENABLE_NETWORK | |||
networkConfig_t network; | |||
networkConfig_t network; //sizeof = 73 | |||
#else | |||
char reserved[73]; | |||
#endif | |||
}; | |||
uint8_t alertBatteryLevel1; //sizeof = 1 | |||
uint8_t alertBatteryLevel2; //sizeof = 1 | |||
uint8_t alertBatteryLevelClear; //sizeof = 1 | |||
uint8_t activeAlerts; //sizeof = 1 | |||
char contactPhone[15]; //sizeof = 15 | |||
}; //sizeof = 29 + 73 = 102 | |||
namespace config { | |||
@@ -0,0 +1,155 @@ | |||
#include "Core.h" | |||
#include "Config.h" | |||
#include "Flash.h" | |||
#include "Alerts.h" | |||
#define LOGGER_NAME "Core" | |||
#define SMS_BUFFER_SIZE 140 | |||
#define NO_ALERTS_NOTIFIED 0 | |||
using namespace utils; | |||
namespace core { | |||
uint16_t sleepTime = SLEEP_DEFAULT_TIME_SECONDS; | |||
uint8_t stoppedInARow = SLEEP_DEFAULT_STOPPED_THRESHOLD - 1; | |||
namespace details { | |||
void appendToSmsBuffer(char * buffer, const char * fmt, ...) { | |||
va_list args; | |||
va_start(args, fmt); | |||
size_t bufferLeft = SMS_BUFFER_SIZE - strlen(buffer); | |||
char * p = buffer + strlen(buffer); | |||
vsnprintf_P(p, bufferLeft, fmt, args); | |||
va_end(args); | |||
} | |||
} | |||
void main() { | |||
bool forceBackup = false; | |||
bool acquired = false; | |||
PositionEntryMetadata metadata; | |||
positions::prepareBackup(); | |||
acquired = positions::acquire(metadata); | |||
if (acquired) { | |||
positions::appendLast(metadata); | |||
forceBackup = updateSleepTime(); | |||
gps::preserveCurrentCoordinates(); | |||
} | |||
alerts::clear(metadata); | |||
alerts::add(notifyFailures(metadata)); | |||
positions::doBackup(forceBackup); | |||
if (acquired) updateRtcTime(); | |||
mainunit::deepSleep(sleepTime); | |||
} | |||
uint8_t notifyFailures(PositionEntryMetadata &metadata) { | |||
SIM808RegistrationStatus networkStatus; | |||
char buffer[SMS_BUFFER_SIZE]; | |||
const __FlashStringHelper * backupFailureString = F(" Backup battery failure ?"); | |||
bool notified = false; | |||
uint8_t triggered = alerts::getTriggered(metadata); | |||
if (!triggered) return NO_ALERTS_NOTIFIED; | |||
NOTICE_FORMAT("notifyFailures", "triggered : %B", triggered); | |||
network::powerOn(); | |||
networkStatus = network::waitForRegistered(NETWORK_DEFAULT_TOTAL_TIMEOUT_MS); | |||
if (network::isAvailable(networkStatus.stat)) { | |||
strncpy_P(buffer, PSTR("Alerts !"), SMS_BUFFER_SIZE); | |||
if (bitRead(triggered, ALERT_BATTERY_LEVEL_1) || bitRead(triggered, ALERT_BATTERY_LEVEL_2)) { | |||
details::appendToSmsBuffer(buffer, PSTR("\n- Battery at %d%%."), metadata.batteryLevel); | |||
} | |||
if (bitRead(triggered, ALERT_RTC_TEMPERATURE_FAILURE)) { | |||
details::appendToSmsBuffer(buffer, PSTR("\n- Temperature is %dC.%S"), static_cast<uint16_t>(metadata.temperature * 100), backupFailureString); | |||
} | |||
if (bitRead(triggered, ALERT_RTC_CLOCK_FAILURE)) { | |||
details::appendToSmsBuffer(buffer, PSTR("\n- RTC was stopped.%S"), backupFailureString); | |||
} | |||
#if ALERTS_ON_SERIAL_IF_AVAILABLE | |||
if(Serial) { | |||
NOTICE_FORMAT("notifyFailure", "%s", buffer); | |||
notified = true; | |||
} | |||
else { | |||
#endif | |||
notified = network::sendSms(buffer); | |||
#if ALERTS_ON_SERIAL_IF_AVAILABLE | |||
} | |||
#endif | |||
if (!notified) NOTICE_MSG("notifyFailure", "SMS not sent !"); | |||
} | |||
network::powerOff(); | |||
return notified ? triggered : NO_ALERTS_NOTIFIED; //If not notified, the alerts state should not be persisted (so we can retry to notify them) | |||
} | |||
void updateRtcTime() { | |||
tmElements_t time; | |||
gps::getTime(time); | |||
rtc::setTime(time); | |||
} | |||
bool updateSleepTime() { | |||
bool goingLongSleep = false; | |||
uint8_t velocity = gps::getVelocity(); | |||
sleepTime = mapSleepTime(velocity); | |||
if (velocity < SLEEP_TIMING_MIN_MOVING_VELOCITY) { | |||
float distance = gps::getDistanceFromPrevious(); //did we missed positions because we were sleeping ? | |||
if (distance > GPS_DEFAULT_MISSED_POSITION_GAP_KM) stoppedInARow = 0; | |||
else stoppedInARow = min(stoppedInARow + 1, SLEEP_DEFAULT_STOPPED_THRESHOLD + 1); //avoid overflow on REALLY long stops | |||
if (stoppedInARow < SLEEP_DEFAULT_STOPPED_THRESHOLD) { | |||
sleepTime = SLEEP_DEFAULT_PAUSING_TIME_SECONDS; | |||
} | |||
else if (stoppedInARow == SLEEP_DEFAULT_STOPPED_THRESHOLD) goingLongSleep = true; | |||
} | |||
else stoppedInARow = 0; | |||
NOTICE_FORMAT("updateSleepTime", "%dkmh => %d seconds", velocity, sleepTime); | |||
return goingLongSleep; | |||
} | |||
uint16_t mapSleepTime(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 = 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; | |||
result = timing.seconds; | |||
break; | |||
} | |||
VERBOSE_FORMAT("mapSleepTime", "%d,%d", velocity, result); | |||
return result; | |||
} | |||
} |
@@ -14,6 +14,9 @@ namespace core { | |||
extern uint16_t sleepTime; | |||
void main(); | |||
void updateRtcTime(); | |||
bool updateSleepTime(); | |||
uint16_t mapSleepTime(uint8_t velocity); | |||
uint8_t notifyFailures(PositionEntryMetadata &metadata); | |||
} |
@@ -2,6 +2,7 @@ | |||
#include "Flash.h" | |||
#include "Positions.h" | |||
#include "Core.h" | |||
#include "Alerts.h" | |||
#define LOGGER_NAME "Debug" | |||
@@ -29,7 +30,8 @@ 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(NOTIFY_FAILURES, "[F] Notify failures"); | |||
MENU_ENTRY(CLEAR_ALERTS, "[A] Clear alerts"); | |||
MENU_ENTRY(SLEEP_DEEP, "[s] Deep sleep for 10s"); | |||
MENU_ENTRY(QUESTION, "?"); | |||
@@ -51,7 +53,8 @@ const PROGMEM uint8_t commandIdMapping[] = { | |||
'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), | |||
'F', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::NOTIFY_FAILURES), | |||
'A', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::CLEAR_ALERTS), | |||
's', static_cast<uint8_t>(debug::GPSTRACKER_DEBUG_COMMAND::SLEEP_DEEP), | |||
}; | |||
@@ -88,7 +91,10 @@ const char * const MENU_ENTRIES[] PROGMEM = { | |||
MENU_EEPROM_BACKUP_ENTRIES, | |||
MENU_SEPARATOR, | |||
MENU_SLEEP, | |||
MENU_NOTIFY_FAILURES, | |||
MENU_CLEAR_ALERTS, | |||
MENU_SEPARATOR, | |||
MENU_SLEEP_DEEP, | |||
MENU_QUESTION | |||
@@ -106,7 +112,7 @@ 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); | |||
} | |||
} | |||
@@ -122,7 +128,7 @@ namespace debug { | |||
size_t mappingArraySize = flash::getArraySize(commandIdMapping); | |||
char commandId; | |||
for (uint8_t i = 0; i < mappingArraySize; i += 2) { | |||
for (uint8_t i = 0; i < mappingArraySize; i += 2) { | |||
commandId = pgm_read_byte_near(commandIdMapping + i); | |||
if (commandId == id) return static_cast<GPSTRACKER_DEBUG_COMMAND>(pgm_read_byte_near(commandIdMapping + i + 1)); | |||
} | |||
@@ -135,7 +141,7 @@ namespace debug { | |||
size_t menuSize = flash::getArraySize(MENU_ENTRIES); | |||
uint8_t intermediate_timeout = 50; | |||
do { | |||
do { | |||
for (uint8_t i = 0; i < menuSize; i++) { | |||
Serial.println(reinterpret_cast<const __FlashStringHelper *>(pgm_read_word_near(&MENU_ENTRIES[i]))); | |||
} | |||
@@ -153,7 +159,7 @@ namespace debug { | |||
command = parseCommand(Serial.read()); | |||
while (Serial.available()) Serial.read(); //flushing input | |||
} while (command == GPSTRACKER_DEBUG_COMMAND::NONE); | |||
return command; | |||
} | |||
@@ -183,7 +189,7 @@ namespace debug { | |||
tmElements_t time; | |||
rtc::getTime(time); | |||
NOTICE_FORMAT("getAndDisplayRtcTime", "%d/%d/%d %d:%d:%d", tmYearToCalendar(time.Year), time.Month, time.Day, time.Hour, time.Minute, time.Second); | |||
NOTICE_FORMAT("getAndDisplayRtcTime", "%d/%d/%d %d:%d:%d %t %d", tmYearToCalendar(time.Year), time.Month, time.Day, time.Hour, time.Minute, time.Second, rtc::isAccurate(), (uint16_t)(rtc::getTemperature() * 1000)); | |||
} | |||
void setRtcTime() { | |||
@@ -259,4 +265,30 @@ namespace debug { | |||
for(int i = 0; i < 10; i++) positions::appendLast(metadata); | |||
} | |||
void notifyFailures() { | |||
PositionEntryMetadata metadata = { | |||
1, //all battery alerts should goes on with this | |||
3800, //doesn't matter | |||
ALERT_SUSPICIOUS_RTC_TEMPERATURE, | |||
0, | |||
SIM808_GPS_STATUS::OFF | |||
}; | |||
uint8_t alerts = core::notifyFailures(metadata); | |||
NOTICE_FORMAT("notifyFailures", "result : %B", alerts); | |||
alerts::add(alerts); | |||
} | |||
void clearAlerts() { | |||
PositionEntryMetadata metadata = { | |||
100, //all battery alerts should goes off with this | |||
3800, //doesn't matter | |||
10, | |||
0, | |||
SIM808_GPS_STATUS::OFF | |||
}; | |||
alerts::clear(metadata); | |||
} | |||
} |
@@ -31,7 +31,8 @@ namespace debug { | |||
EEPROM_GET_ENTRIES, | |||
EEPROM_ADD_ENTRY, | |||
EEPROM_BACKUP_ENTRIES, | |||
SLEEP, | |||
NOTIFY_FAILURES, | |||
CLEAR_ALERTS, | |||
SLEEP_DEEP | |||
}; | |||
@@ -55,5 +56,6 @@ namespace debug { | |||
void getAndDisplayEepromLastPosition(); | |||
void addLastPositionToEeprom(); | |||
void notifyFailures(); | |||
void clearAlerts(); | |||
} |
@@ -80,8 +80,11 @@ void loop() { | |||
case debug::GPSTRACKER_DEBUG_COMMAND::EEPROM_BACKUP_ENTRIES: | |||
positions::doBackup(true); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::SLEEP: | |||
mainunit::sleep(period_t::SLEEP_8S); | |||
case debug::GPSTRACKER_DEBUG_COMMAND::NOTIFY_FAILURES: | |||
debug::notifyFailures(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::CLEAR_ALERTS: | |||
debug::clearAlerts(); | |||
break; | |||
case debug::GPSTRACKER_DEBUG_COMMAND::SLEEP_DEEP: | |||
mainunit::deepSleep(10); |
@@ -1,78 +0,0 @@ | |||
#include "Config.h" | |||
#include "Debug.h" | |||
#include "Hardware.h" | |||
#define LOGGER_NAME "Config" | |||
namespace config { | |||
namespace main { | |||
config_t value; | |||
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 setup() { | |||
details::read(); | |||
//details::write(); | |||
} | |||
void save() { | |||
details::write(); | |||
} | |||
void reset() { | |||
VERBOSE("reset"); | |||
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 | |||
}; | |||
value = config; | |||
save(); | |||
} | |||
} | |||
} |
@@ -1,77 +0,0 @@ | |||
#include "Core.h" | |||
#include "Config.h" | |||
#include "Flash.h" | |||
#define LOGGER_NAME "Core" | |||
using namespace utils; | |||
namespace core { | |||
uint16_t sleepTime = SLEEP_DEFAULT_TIME_SECONDS; | |||
uint8_t stoppedInARow = SLEEP_DEFAULT_STOPPED_THRESHOLD - 1; | |||
void main() { | |||
bool forceBackup = false; | |||
positions::prepareBackup(); | |||
PositionEntryMetadata metadata; | |||
if (positions::acquire(metadata)) { | |||
positions::appendLast(metadata); | |||
forceBackup = updateSleepTime(); | |||
gps::preserveCurrentCoordinates(); | |||
} | |||
positions::doBackup(forceBackup); | |||
mainunit::deepSleep(sleepTime); | |||
} | |||
bool updateSleepTime() { | |||
uint8_t velocity = gps::getVelocity(); | |||
uint16_t result = mapSleepTime(velocity); | |||
bool goingLongSleep = false; | |||
if (velocity < SLEEP_TIMING_MIN_MOVING_VELOCITY) { | |||
float distance = gps::getDistanceFromPrevious(); //did we missed positions because we were sleeping ? | |||
if (distance > GPS_DEFAULT_MISSED_POSITION_GAP_KM) stoppedInARow = 0; | |||
else stoppedInARow = min(stoppedInARow + 1, SLEEP_DEFAULT_STOPPED_THRESHOLD + 1); //avoid overflow on REALLY long stops | |||
if (stoppedInARow < SLEEP_DEFAULT_STOPPED_THRESHOLD) { | |||
result = SLEEP_DEFAULT_PAUSING_TIME_SECONDS; | |||
} | |||
else if (stoppedInARow == SLEEP_DEFAULT_STOPPED_THRESHOLD) goingLongSleep = true; | |||
} | |||
else stoppedInARow = 0; | |||
sleepTime = result; | |||
NOTICE_FORMAT("updateSleepTime", "%dkmh => %d seconds", velocity, sleepTime); | |||
return goingLongSleep; | |||
} | |||
uint16_t mapSleepTime(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 = 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; | |||
result = timing.seconds; | |||
break; | |||
} | |||
VERBOSE_FORMAT("computeSleepTime", "%d,%d", velocity, result); | |||
return result; | |||
} | |||
} |
@@ -1,13 +0,0 @@ | |||
#pragma once | |||
#include "Hardware.h" | |||
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(); | |||
} |
@@ -18,6 +18,8 @@ namespace hardware { | |||
namespace sim808 { | |||
SoftwareSerial simSerial = SoftwareSerial(SIM_TX, SIM_RX); | |||
SIM808 device = SIM808(SIM_RST, SIM_PWR, SIM_STATUS); | |||
uint8_t networkPoweredCount = 0; | |||
uint8_t gpsPoweredCount = 0; | |||
void powerOn() { | |||
VERBOSE("powerOn"); | |||
@@ -25,14 +27,19 @@ namespace hardware { | |||
if (!poweredOn) return; | |||
device.init(); | |||
networkPoweredCount = gpsPoweredCount = 0; | |||
} | |||
void powerOff() { | |||
VERBOSE("powerOff"); | |||
device.powerOnOff(false); | |||
networkPoweredCount = gpsPoweredCount = 0; | |||
} | |||
void powerOffIfUnused() { | |||
//does not rely on count for safety | |||
//if there is a bug somewhere, the device will consume more battery, | |||
//but will not fail due to an over aggressive battery saving strategy | |||
bool gpsPowered = false; | |||
if ((!device.getGpsPowerState(&gpsPowered) || !gpsPowered) && | |||
@@ -49,13 +56,30 @@ namespace hardware { | |||
} | |||
void gpsPowerOn() { | |||
if(gpsPoweredCount) { | |||
gpsPoweredCount++; | |||
return; | |||
} | |||
VERBOSE("gpsPowerOn"); | |||
powerOn(); | |||
if(!networkPoweredCount) { | |||
//SIM808 turns phone on by default but we don't need it for gps only | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::MINIMUM); | |||
} | |||
device.enableGps(); | |||
} | |||
void gpsPowerOff() { | |||
if (!device.powered()) return; | |||
if (!device.powered()) { | |||
networkPoweredCount = gpsPoweredCount = 0; //just to be sure counts == 0 | |||
return; | |||
} | |||
if(gpsPoweredCount > 1) { | |||
gpsPoweredCount--; | |||
return; | |||
} | |||
VERBOSE("gpsPowerOff"); | |||
device.disableGps(); | |||
@@ -63,18 +87,30 @@ namespace hardware { | |||
} | |||
void networkPowerOn() { | |||
if(networkPoweredCount) { | |||
networkPoweredCount++; | |||
return; | |||
} | |||
VERBOSE("networkPowerOn"); | |||
powerOn(); | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::FULL); | |||
} | |||
void networkPowerOff() { | |||
if (!device.powered()) return; | |||
if (!device.powered()) { | |||
networkPoweredCount = gpsPoweredCount = 0; //just to be sure counts == 0 | |||
return; | |||
} | |||
if(networkPoweredCount > 1) { | |||
networkPoweredCount--; | |||
return; | |||
} | |||
VERBOSE("networkPowerOff"); | |||
device.disableGprs(); | |||
device.setPhoneFunctionality(SIM808_PHONE_FUNCTIONALITY::MINIMUM); | |||
powerOffIfUnused(); | |||
} | |||
} | |||
@@ -87,32 +123,37 @@ namespace hardware { | |||
uint8_t poweredCount = 0; | |||
void powerOn() { | |||
if (!poweredCount) { | |||
VERBOSE("powerOn"); | |||
digitalWrite(I2C_PWR, HIGH); | |||
pinMode(I2C_PWR, OUTPUT); | |||
Wire.begin(); | |||
if(poweredCount) { | |||
poweredCount++; | |||
return; | |||
} | |||
poweredCount++; | |||
VERBOSE("powerOn"); | |||
digitalWrite(I2C_PWR, HIGH); | |||
pinMode(I2C_PWR, OUTPUT); | |||
Wire.begin(); | |||
poweredCount = 1; | |||
} | |||
void powerOff(bool forced = false) { | |||
if (poweredCount == 1 || forced) { | |||
VERBOSE("powerOff"); | |||
pinMode(I2C_PWR, INPUT); | |||
digitalWrite(I2C_PWR, LOW); | |||
if(poweredCount > 1 && !forced) { | |||
poweredCount--; | |||
return; | |||
} | |||
VERBOSE("powerOff"); | |||
pinMode(I2C_PWR, INPUT); | |||
digitalWrite(I2C_PWR, LOW); | |||
//turn off i2c | |||
TWCR &= ~(bit(TWEN) | bit(TWIE) | bit(TWEA)); | |||
//turn off i2c | |||
TWCR &= ~(bit(TWEN) | bit(TWIE) | bit(TWEA)); | |||
//disable i2c internal pull ups | |||
digitalWrite(A4, LOW); | |||
digitalWrite(A5, LOW); | |||
} | |||
//disable i2c internal pull ups | |||
digitalWrite(A4, LOW); | |||
digitalWrite(A5, LOW); | |||
poweredCount--; | |||
poweredCount = 0; | |||
} | |||
} | |||
} |
@@ -11,7 +11,7 @@ namespace mainunit { | |||
void prepareSleep() { | |||
hardware::sim808::simSerial.end(); //avoid woke up by SoftwareSerial interrupt | |||
delay(5); //ensure message have been printed out | |||
delay(5); //ensure log messages have been printed out | |||
} | |||
void wokeUp() { | |||
@@ -35,14 +35,6 @@ namespace mainunit { | |||
attachInterrupt(digitalPinToInterrupt(RTC_WAKE), interrupt, FALLING); | |||
} | |||
void sleep(period_t period) { | |||
NOTICE_FORMAT("sleep", "Period : %d", period); | |||
details::prepareSleep(); | |||
LowPower.powerDown(period, ADC_OFF, BOD_OFF); | |||
details::wokeUp(); | |||
} | |||
void deepSleep(uint16_t seconds) { | |||
NOTICE_FORMAT("deepSleep", "%d seconds", seconds); | |||
interruptIn(seconds); |
@@ -4,6 +4,5 @@ | |||
#include <LowPower.h> | |||
namespace mainunit { | |||
void sleep(period_t period); | |||
void deepSleep(uint16_t seconds); | |||
} |
@@ -1,55 +1,76 @@ | |||
#include "Config.h" | |||
#if BACKUP_ENABLE_NETWORK | |||
#include "Debug.h" | |||
#include "Network.h" | |||
#include "Hardware.h" | |||
#include "MainUnit.h" | |||
#include "Rtc.h" | |||
#define LOGGER_NAME "Network" | |||
namespace network { | |||
timestamp_t _poweredOnTime; | |||
void powerOn() { | |||
hardware::sim808::networkPowerOn(); | |||
_poweredOnTime = rtc::getTime(); | |||
} | |||
void powerOff() { | |||
hardware::sim808::networkPowerOff(); | |||
_poweredOnTime = 0; | |||
} | |||
__attribute__((__optimize__("O2"))) | |||
SIM808RegistrationStatus waitForRegistered(uint32_t timeout) { | |||
SIM808RegistrationStatus waitForRegistered(uint32_t timeout, bool relativeToPowerOnTime = true) { | |||
SIM808RegistrationStatus currentStatus; | |||
SIM808SignalQualityReport report; | |||
uint8_t noReliableNetwork = 0; | |||
if (relativeToPowerOnTime) timeout -= (rtc::getTime() - _poweredOnTime) * 1000; | |||
currentStatus = hardware::sim808::device.getNetworkRegistrationStatus(); | |||
report = hardware::sim808::device.getSignalQuality(); | |||
do { | |||
currentStatus = hardware::sim808::device.getNetworkRegistrationStatus(); | |||
if (isAvailable(currentStatus.stat)) break; | |||
report = hardware::sim808::device.getSignalQuality(); | |||
NOTICE_FORMAT("waitForRegistered", "%d, [%d %ddBm]", currentStatus.stat, report.ssri, report.attenuation); | |||
if (report.ssri < NETWORK_DEFAULT_NO_NETWORK_QUALITY_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 | |||
break; //after a while, no network really means no network. Bailing out | |||
} | |||
mainunit::deepSleep(NETWORK_DEFAULT_INTERMEDIATE_TIMEOUT_MS / 1000); | |||
timeout -= NETWORK_DEFAULT_INTERMEDIATE_TIMEOUT_MS; | |||
currentStatus = hardware::sim808::device.getNetworkRegistrationStatus(); | |||
report = hardware::sim808::device.getSignalQuality(); | |||
} while (timeout > 1); | |||
report = hardware::sim808::device.getSignalQuality(); | |||
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; | |||
return static_cast<int8_t>(state) & | |||
(static_cast<int8_t>(SIM808_NETWORK_REGISTRATION_STATE::REGISTERED) | static_cast<int8_t>(SIM808_NETWORK_REGISTRATION_STATE::ROAMING)) | |||
!= 0; | |||
} | |||
bool enableGprs() { | |||
return hardware::sim808::device.enableGprs(config::main::value.network.apn); | |||
} | |||
} | |||
#endif | |||
bool sendSms(const char * msg) { | |||
const char * phoneNumber = config::main::value.contactPhone; | |||
NOTICE_FORMAT("sendSms", "%s, %s", phoneNumber, msg); | |||
return hardware::sim808::device.sendSms(phoneNumber, msg); | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
#include "Hardware.h" | |||
namespace network { | |||
void powerOn(); | |||
void powerOff(); | |||
SIM808RegistrationStatus waitForRegistered(uint32_t timeout, bool relativeToPowerOnTime = true); | |||
bool isAvailable(SIM808_NETWORK_REGISTRATION_STATE state); | |||
bool enableGprs(); | |||
bool sendSms(const char * msg); | |||
} |
@@ -26,16 +26,15 @@ namespace positions { | |||
bool NetworkPositionsBackup::appendPosition(PositionEntry &entry) { | |||
char buffer[BUFFER_SIZE]; | |||
snprintf(buffer, BUFFER_SIZE, "%d,%d,%d,%d,%d,%d,%d,", | |||
snprintf_P(buffer, BUFFER_SIZE, PSTR("%d,%d,%d,%d,%d,%d,%d,%s"), | |||
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), | |||
entry.metadata.timeToFix); | |||
strcat(buffer, entry.position); | |||
entry.metadata.timeToFix, | |||
entry.position); | |||
NOTICE_FORMAT("appendPosition", "Sending : %s", buffer); | |||
uint16_t responseCode = hardware::sim808::device.httpPost( | |||
@@ -53,15 +52,14 @@ namespace positions { | |||
//__attribute__((__optimize__("O2"))) | |||
void NetworkPositionsBackup::appendPositions() { | |||
uint16_t currentEntryIndex = config::main::value.network.lastSavedEntry + 1; | |||
uint32_t networkTimeout = 0; | |||
PositionEntry currentEntry; | |||
SIM808RegistrationStatus networkStatus; | |||
network::powerOn(); | |||
networkTimeout = NETWORK_DEFAULT_TOTAL_TIMEOUT_MS; | |||
if (_prepareTime > 0) networkTimeout -= (rtc::getTime() - _prepareTime) * 1000; | |||
//avoid edge case where if 0, whole set of positions will be sent again | |||
if (!positions::count(config::main::value.network.lastSavedEntry)) return; | |||
networkStatus = network::waitForRegistered(networkTimeout); | |||
network::powerOn(); | |||
networkStatus = network::waitForRegistered(NETWORK_DEFAULT_TOTAL_TIMEOUT_MS); | |||
if (!network::isAvailable(networkStatus.stat) || !network::enableGprs()) { | |||
networkUnavailableInARow = min(networkUnavailableInARow + 1, POSITIONS_CONFIG_NET_DEFAULT_UNAVAILABLE_NETWORK_POSTPONE_THRESHOLD + 1); //avoid increment overflow | |||
@@ -90,20 +88,13 @@ namespace positions { | |||
network::powerOff(); | |||
} | |||
void NetworkPositionsBackup::setup() { | |||
NOTICE("setup"); | |||
} | |||
void NetworkPositionsBackup::setup() {} | |||
void NetworkPositionsBackup::prepare() { | |||
NOTICE("prepare"); | |||
if (!isBackupNeeded(true)) { | |||
_prepareTime = 0; | |||
return; | |||
} | |||
if (!isBackupNeeded(true)) return; | |||
network::powerOn(); | |||
_prepareTime = rtc::getTime(); | |||
} | |||
void NetworkPositionsBackup::backup(bool force) { |
@@ -10,8 +10,6 @@ namespace positions { | |||
class NetworkPositionsBackup : public PositionsBackup { | |||
private: | |||
timestamp_t _prepareTime; | |||
bool isBackupNeeded(bool forPrepare); | |||
bool appendPosition(PositionEntry &entry); | |||
void appendPositions(); |
@@ -9,8 +9,8 @@ | |||
struct networkConfig_t { | |||
uint8_t saveThreshold; | |||
uint16_t lastSavedEntry; | |||
char apn[20]; | |||
char url[50]; | |||
}; | |||
uint8_t saveThreshold; //sizeof = 1 | |||
uint16_t lastSavedEntry; //sizeof = 2 | |||
char apn[20]; //sizeof = 20 | |||
char url[50]; //sizeof = 50 | |||
}; //sizeof = 73 |
@@ -4,7 +4,7 @@ | |||
#include "Gps.h" | |||
#if BACKUP_ENABLE_SDCARD || BACKUP_ENABLE_NETWORK | |||
#define BACKUPS_ENABLED BACKUP_ENABLE_SDCARD + BACKUP_ENABLE_NETWORK | |||
#define BACKUPS_ENABLED (BACKUP_ENABLE_SDCARD + BACKUP_ENABLE_NETWORK) | |||
#endif | |||
#if BACKUP_ENABLE_SDCARD | |||
@@ -21,8 +21,10 @@ | |||
#define ENTRIES_ADDR CONFIG_RESERVED_SIZE | |||
namespace positions { | |||
#ifdef BACKUPS_ENABLED | |||
#if BACKUPS_ENABLED > 1 | |||
backup::PositionsBackup **_backups; | |||
#elif BACKUPS_ENABLED == 1 | |||
backup::PositionsBackup * _backup; | |||
#endif | |||
namespace details { | |||
@@ -36,21 +38,37 @@ namespace positions { | |||
void setup() { | |||
details::maxEntryIndex = (E24_MAX_ADDRESS(hardware::i2c::eeprom.getSize()) - ENTRIES_ADDR) / ENTRY_RESERVED_SIZE; | |||
#ifdef BACKUPS_ENABLED | |||
#if BACKUPS_ENABLED > 0 | |||
backup::PositionsBackup * backup = NULL; | |||
#if BACKUPS_ENABLED > 1 | |||
uint8_t backupIdx = 0; | |||
_backups = new backup::PositionsBackup*[BACKUPS_ENABLED]; | |||
#endif //BACKUPS_ENABLED > 1 | |||
#if BACKUP_ENABLE_SDCARD | |||
_backups[backupIdx] = new backup::sd::SdPositionsBackup(); | |||
_backups[backupIdx]->setup(); | |||
backup = new backup::sd::SdPositionsBackup(); | |||
backup->setup(); | |||
#if BACKUPS_ENABLED > 1 | |||
_backups[backupIdx] = backup; | |||
backupIdx++; | |||
#endif | |||
#endif //BACKUPS_ENABLED > 1 | |||
#endif //BACKUP_ENABLE_SDCARD | |||
#if BACKUP_ENABLE_NETWORK | |||
_backups[backupIdx] = new backup::net::NetworkPositionsBackup(); | |||
_backups[backupIdx]->setup(); | |||
backup = new backup::net::NetworkPositionsBackup(); | |||
backup->setup(); | |||
#if BACKUPS_ENABLED > 1 | |||
_backups[backupIdx] = backup; | |||
backupIdx++; | |||
#endif | |||
#endif | |||
#endif //BACKUPS_ENABLED > 1 | |||
#endif //BACKUP_ENABLE_NETWORK | |||
#if BACKUPS_ENABLED == 1 | |||
_backup = backup; | |||
#endif //BACKUPS_ENABLED == 1 | |||
#endif //BACKUPS_ENABLED > 0 | |||
} | |||
bool acquire(PositionEntryMetadata &metadata) { | |||
@@ -61,19 +79,13 @@ namespace positions { | |||
gps::powerOn(); | |||
before = rtc::getTime(); | |||
SIM808_GPS_STATUS gpsStatus = gps::acquireCurrentPosition(GPS_DEFAULT_TOTAL_TIMEOUT_MS); | |||
uint16_t timeToFix = rtc::getTime() - before; | |||
SIM808ChargingStatus battery = hardware::sim808::device.getChargingState(); | |||
gps::powerOff(); | |||
bool acquired = gpsStatus >= SIM808_GPS_STATUS::FIX; //prety useless wins 14 bytes on the hex size rather than return gpStatus >= ... | |||
NOTICE_FORMAT("acquire", "Status : %d", gpsStatus); | |||
if (gpsStatus < SIM808_GPS_STATUS::FIX) return false; | |||
uint16_t timeToFix = rtc::getTime() - before; | |||
tmElements_t time; | |||
gps::getTime(time); | |||
rtc::setTime(time); | |||
metadata = { | |||
battery.level, | |||
battery.voltage, | |||
@@ -82,7 +94,7 @@ namespace positions { | |||
gpsStatus | |||
}; | |||
return true; | |||
return acquired; | |||
} | |||
void appendLast(const PositionEntryMetadata &metadata) { | |||
@@ -101,7 +113,7 @@ namespace positions { | |||
hardware::i2c::powerOn(); | |||
hardware::i2c::eeprom.writeBlock(entryAddress, entry); | |||
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); | |||
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->lastEntry++; | |||
if (config->lastEntry > details::maxEntryIndex) config->lastEntry = 0; | |||
@@ -118,19 +130,19 @@ namespace positions { | |||
uint16_t entryAddress = details::getEntryAddress(index); | |||
if (entryAddress == -1) return false; | |||
VERBOSE_FORMAT("get", "Reading entry n°%d @ %X", index, entryAddress); | |||
VERBOSE_FORMAT("get", "Reading entry n�%d @ %X", index, entryAddress); | |||
hardware::i2c::powerOn(); | |||
hardware::i2c::eeprom.readBlock(entryAddress, entry); | |||
hardware::i2c::powerOff(); | |||
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); | |||
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::main::value.lastEntry) return false; | |||
if (index == details::maxEntryIndex) index = 0; //could use a modulo but easier to understand that way | |||
else index++; | |||
@@ -147,18 +159,22 @@ namespace positions { | |||
} | |||
void prepareBackup() { | |||
#ifdef BACKUPS_ENABLED | |||
#if BACKUPS_ENABLED > 1 | |||
for (int i = 0; i < BACKUPS_ENABLED; i++) { | |||
_backups[i]->prepare(); | |||
} | |||
#elif BACKUPS_ENABLED == 1 | |||
_backup->prepare(); | |||
#endif | |||
} | |||
void doBackup(bool force) { | |||
#ifdef BACKUPS_ENABLED | |||
#if BACKUPS_ENABLED > 1 | |||
for (int i = 0; i < BACKUPS_ENABLED; i++) { | |||
_backups[i]->backup(force); | |||
} | |||
#elif BACKUPS_ENABLED == 1 | |||
_backup->backup(force); | |||
#endif | |||
} | |||
} |
@@ -11,7 +11,7 @@ | |||
using namespace utils; | |||
namespace rtc { | |||
void setup() { | |||
VERBOSE("setup"); | |||
hardware::i2c::powerOn(); | |||
@@ -19,8 +19,6 @@ namespace rtc { | |||
RTC.control(DS3231_A1_INT_ENABLE, DS3231_OFF); //Alarm 1 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) | |||
} | |||
float getTemperature() { |
@@ -0,0 +1,26 @@ | |||
{ | |||
"folders": [ | |||
{ | |||
"path": "." | |||
}, | |||
{ | |||
"path": "../libraries/SIM808" | |||
}, | |||
{ | |||
"path": "../libraries/uDS3231" | |||
}, | |||
{ | |||
"path": "../libraries/E24" | |||
}, | |||
{ | |||
"path": "../libraries/Low-Power" | |||
}, | |||
{ | |||
"path": "../libraries/ArduinoLog" | |||
} | |||
], | |||
"settings": { | |||
"C_Cpp.intelliSenseEngineFallback": "Disabled", | |||
"C_Cpp.intelliSenseEngine": "Tag Parser" | |||
} | |||
} |