Repository: geolink/opentracker
Branch: master
Commit: 46a826f8e973
Files: 37
Total size: 157.6 KB
Directory structure:
gitextract_xiaew389/
├── .gitattributes
├── CHANGELOG.md
├── Examples/
│ ├── Bare_minimum_INPUTS_BAT_monitoring/
│ │ └── Bare_minimum_INPUTS_BAT_monitoring.ino
│ ├── Bare_minimum_LED/
│ │ └── Bare_minimum_LED.ino
│ ├── Bare_minimum_Relay/
│ │ └── Bare_minimum_Relay.ino
│ ├── Bare_minimum_VDET/
│ │ └── Bare_minimum_VDET.ino
│ ├── OpenTrackerTest/
│ │ └── OpenTrackerTest.ino
│ └── Test_External_IO_and_LED/
│ └── Test_External_IO_and_LED.ino
├── LICENSE
├── OpenTracker/
│ ├── .gitignore
│ ├── OpenTracker.ino
│ ├── addon.h
│ ├── addon.ino.example
│ ├── data.ino
│ ├── gps.ino
│ ├── gsm.ino
│ ├── led.ino
│ ├── parse.ino
│ ├── reboot.ino
│ ├── settings.ino
│ ├── sms.ino
│ ├── storage.ino
│ └── tracker.h.example
├── README.md
├── daemon/
│ ├── .gitignore
│ ├── README
│ ├── config/
│ │ └── config.rb.example
│ ├── daemon.rb
│ ├── daemon.sh
│ └── schema.sql
└── logger/
├── .gitignore
├── README.md
├── apache-vhost.conf
├── cgi-bin/
│ └── erb-cgi.rb
├── logger.erb
├── logger_config.rb.example
└── logger_schema.sql
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text eol=lf
*.pdf binary
*.exe binary
================================================
FILE: CHANGELOG.md
================================================
Changelog
v1.0
====
v2.0
====
v2.0.1
======
v3.0.1
======
v3.0.2
======
+ Renamed tracker.h to tracker.h.example so that tracker.h can be .gitignored (as it tends to contain credentials)
+ Added ALWAYS_ON config option to always log and post data even when the ignition is off.
+ Added options to additionally log and post the ignition state, battery level and total engine running time.
+ Added tracker.php - a basic PHP script that will log POSTed data to a MySQL database and forward the data on to Geolink.
+ Added a "locate" SMS command that responds to the sender with a google maps link. By default this is in the comgooglemaps:// format which will open in the google maps iOS app, but it can be changed to normal https://maps.google links by setting LOCATE_COMMAND_FORMAT_IOS = 0 in tracker.h.
+ Added GSM_SEND_FAILURES_REBOOT options, if set to >0 this determines the number of consecutive GSM send failures that will trigger a reboot of the device.
v3.0.3
======
+ added SEND_RAW mode to send the data packet over a raw TCP connection in order to minimise bandwidth
+ added example daemon.rb ruby tcp server to accept and log the raw data packets
+ added boolean flags for all elements in the data packet so they can be turned on and off individually
Raw mode drastically reduces the bandwidth used by the OpenTracker.
Example old data transmission using HTTP:
POST /index.php HTTP/1.0\r\nHost: some.server.com \r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: 42\r\nConnection: close\r\n\r\nimei=8634241016201447&key=xxxxxxxxx&d=15/05/10,09:40:40 0[100515,9404700,12.394991,-4.132110,0.02,18.70,346.90,67,15]12.24,0,2520#eof
New data packet using raw mode and only a few select data items:
xxxxxxxxx,12.345968,-1.234517,0.04,12.42,0,2880
273 bytes down to 47 bytes
================================================
FILE: Examples/Bare_minimum_INPUTS_BAT_monitoring/Bare_minimum_INPUTS_BAT_monitoring.ino
================================================
#define DEBUG 1 //enable debug msg, sent to serial port
#define debug_port SerialUSB
#ifdef DEBUG
#define debug_print(x) debug_port.print(x)
#else
#define debug_print(x)
#endif
// Variables will change:
int outputValue;
int sensorValue;
void setup() {
// put your setup code here, to run once:
}
void loop() {
// Read VIN Value
// read the analog in value:
sensorValue = analogRead(AIN_S_INLEVEL);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("VIN = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
// Read IN1 Value
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN1);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("IN1 = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
// Read IN2 Value
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN2);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("IN2 = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
delay(1000);
}
================================================
FILE: Examples/Bare_minimum_LED/Bare_minimum_LED.ino
================================================
#include <avr/dtostrf.h>
void setup() {
//setup led pin
pinMode(PIN_POWER_LED, OUTPUT); // Set LED as Output
digitalWrite(PIN_POWER_LED, LOW); // Set LED initially off
}
void loop() {
// Switch the Power LED
digitalWrite(PIN_POWER_LED, HIGH);
delay(800);
digitalWrite(PIN_POWER_LED, LOW);
delay(800);
}
================================================
FILE: Examples/Bare_minimum_Relay/Bare_minimum_Relay.ino
================================================
void setup() {
// Relay output
pinMode(PIN_C_OUT_1, OUTPUT); // Initialize pin as output
digitalWrite(PIN_C_OUT_1, LOW); // Set PIN LOW
pinMode(PIN_C_OUT_2, OUTPUT); // Initialize pin as output
digitalWrite(PIN_C_OUT_2, LOW); // Set PIN LOW
}
void loop() {
digitalWrite(PIN_C_OUT_1, HIGH); // switch the relay on
digitalWrite(PIN_C_OUT_2, HIGH); // switch the relay on
delay(3000); // wait
digitalWrite(PIN_C_OUT_1, LOW); // switch the relay off
digitalWrite(PIN_C_OUT_2, LOW); // switch the relay off
delay(3000); // wait
}
================================================
FILE: Examples/Bare_minimum_VDET/Bare_minimum_VDET.ino
================================================
#define DEBUG 1 //enable debug msg, sent to serial port
#define debug_port SerialUSB
#ifdef DEBUG
#define debug_print(x) debug_port.print(x)
#else
#define debug_print(x)
#endif
void setup() {
// Ignition detection
pinMode(PIN_S_DETECT, INPUT); // Initialize pin as input
}
void loop() {
// Check If Ignition is on
if (digitalRead(PIN_S_DETECT) == LOW)
debug_print(F("Ignition detected!"));
}
================================================
FILE: Examples/OpenTrackerTest/OpenTrackerTest.ino
================================================
/*
Blink
Turns on an LED on for one second, then off for one second, repeatedly.
This example code is in the public domain.
*/
// Include Scheduler since we want to manage multiple tasks.
#include <Scheduler.h>
// experimental CAN support
#include <due_can.h>
#define TEST_CAN
//Leave defined if you use native port, comment if using programming port
#define Console SerialUSB
#define SerialGPS Serial1
#define SerialGSM Serial2
// the setup routine runs once when you press reset:
void setup() {
// init serial ports before power on to GSM/GPS
SerialGSM.begin(115200);
SerialGPS.begin(9600);
// initialize the digital pin as an output.
pinMode(PIN_POWER_LED, OUTPUT);
digitalWrite(PIN_POWER_LED, HIGH);
pinMode(PIN_C_OUT_1, OUTPUT);
digitalWrite(PIN_C_OUT_1, LOW);
pinMode(PIN_C_OUT_2, OUTPUT);
digitalWrite(PIN_C_OUT_2, LOW);
pinMode(PIN_S_DETECT, INPUT);
// initialize GSM pins
pinMode(PIN_C_PWR_GSM, OUTPUT);
digitalWrite(PIN_C_PWR_GSM, LOW);
pinMode(PIN_C_KILL_GSM, OUTPUT);
digitalWrite(PIN_C_KILL_GSM, LOW);
pinMode(PIN_STATUS_GSM, INPUT);
pinMode(PIN_RING_GSM, INPUT);
// initialize GPS pins
pinMode(PIN_STANDBY_GPS, OUTPUT);
digitalWrite(PIN_STANDBY_GPS, LOW);
pinMode(PIN_RESET_GPS, OUTPUT);
digitalWrite(PIN_RESET_GPS, LOW);
Console.begin(115200);
while (true)
if (millis() > 3000) break;
#ifdef TEST_CAN
Console.print("CAN init...");
CAN.Transceiver.SetRS(PIN_CAN_RS);
CAN.init(CAN_BPS_500K);
Console.print("done!");
//By default there are 7 mailboxes for each device that are RX boxes
//This sets each mailbox to have an open filter that will accept extended
//or standard frames
int filter;
//extended
for (filter = 0; filter < 3; filter++) {
CAN.setRXFilter(filter, 0, 0, true);
}
//standard
for (filter = 3; filter < 7; filter++) {
CAN.setRXFilter(filter, 0, 0, false);
}
#else
delay(100);
gsmPower(1); // force ON
#endif
// Add "loop2" and "loop3" to scheduling.
// "loop" is always started by default.
Scheduler.startLoop(loop2);
Scheduler.startLoop(loop3);
}
void gsmPower(int forceON)
{
Console.print("GSM power...");
if (!digitalRead(PIN_STATUS_GSM))
{
digitalWrite(PIN_C_PWR_GSM, HIGH); //enable
for (int i=0; i<15; i++)
{
delay(100);
if (digitalRead(PIN_STATUS_GSM)) break;
}
if (!digitalRead(PIN_STATUS_GSM))
{
Console.println("GSM Status not going high!");
for(;;);
}
digitalWrite(PIN_C_PWR_GSM, LOW);
delay(100);
}
else if (forceON)
Console.println("GSM Status already high!");
else
{
digitalWrite(PIN_C_PWR_GSM, HIGH); //disable
for (int i=0; i<95; i++)
{
delay(100);
if (!digitalRead(PIN_STATUS_GSM)) break;
}
if (digitalRead(PIN_STATUS_GSM))
{
Console.println("GSM Status not going low!");
for(;;);
}
digitalWrite(PIN_C_PWR_GSM, LOW);
delay(100);
}
if (!digitalRead(PIN_STATUS_GSM))
Console.println("- GSM OFF!");
else
Console.println("- GSM ON!");
}
void gsmLoop()
{
// read from port 1, send to port 0:
if (SerialGSM.available()) {
int inByte = SerialGSM.read();
Console.write(inByte);
}
// read from port 0, send to port 1:
if (Console.available()) {
int inByte = Console.read();
SerialGSM.write(inByte);
if (inByte == '.')
gsmPower(0);
if (inByte == '\'')
{
digitalWrite(PIN_C_KILL_GSM, HIGH);
pinMode(PIN_C_KILL_GSM, OUTPUT);
delay(1000);
digitalWrite(PIN_C_KILL_GSM, LOW);
}
if (inByte == '\\')
{
digitalWrite(PIN_STANDBY_GPS, !digitalRead(PIN_STANDBY_GPS));
}
if (inByte == '|')
{
digitalWrite(PIN_RESET_GPS, HIGH);
pinMode(PIN_RESET_GPS, OUTPUT);
delay(100);
digitalWrite(PIN_RESET_GPS, LOW);
}
}
if (!digitalRead(PIN_RING_GSM))
digitalWrite(PIN_POWER_LED, HIGH); // turn the LED off
else
digitalWrite(PIN_POWER_LED, LOW); // turn the LED on
}
void gpsLoop()
{
// read from port 1, send to port 0:
if (SerialGPS.available()) {
int inByte = SerialGPS.read();
Console.write(inByte);
}
#if 0
// read from port 0, send to port 1:
if (Console.available()) {
int inByte = Console.read();
SerialGPS.write(inByte);
}
#endif
}
void printFrame(CAN_FRAME &frame)
{
Console.print("ID: 0x");
Console.print(frame.id, HEX);
Console.print(" Len: ");
Console.print(frame.length);
Console.print(" Data: 0x");
for (int count = 0; count < frame.length; count++) {
Console.print(frame.data.bytes[count], HEX);
Console.print(" ");
}
Console.print("\r\n");
}
// Task no.1: blink LED with 1 second delay.
void loop()
{
digitalWrite(PIN_POWER_LED, HIGH);
// IMPORTANT:
// When multiple tasks are running 'delay' passes control to
// other tasks while waiting and guarantees they get executed.
delay(500);
digitalWrite(PIN_POWER_LED, LOW);
delay(500);
}
// Task no.2: blink LED with 0.1 second delay.
void loop2()
{
digitalWrite(PIN_C_OUT_1, HIGH); // turn the LED on (HIGH is the voltage level)
digitalWrite(PIN_C_OUT_2, LOW); // turn the LED on (HIGH is the voltage level)
delay(3000); // wait for a second
digitalWrite(PIN_C_OUT_1, LOW); // turn the LED on (HIGH is the voltage level)
digitalWrite(PIN_C_OUT_2, HIGH); // turn the LED on (HIGH is the voltage level)
delay(3000); // wait for a second
}
int sensorValue = 0; // value read from the pot
float outputValue = 0;
// Task no.3: accept commands from Serial port
// 'v' read input voltage
void loop3() {
#ifndef TEST_CAN
gsmLoop();
gpsLoop();
yield();
return;
#endif
#ifdef TEST_CAN
if (CAN.rx_avail()) {
CAN_FRAME incoming;
CAN.get_rx_buff(incoming);
printFrame(incoming);
}
#endif
if (Console.available()) {
char c = Console.read();
#ifdef TEST_CAN
if (c == 'c') {
CAN_FRAME frame;
frame.id = 0x123;
frame.length = 8;
frame.data.low = 0xB8C8A8E8;
frame.data.high = 0x01020304;
frame.extended = 0;
CAN.sendFrame(frame);
}
#endif
if (c == 'v') {
// read the analog in value:
sensorValue = analogRead(AIN_S_INLEVEL);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
Console.print("VIN = " );
Console.print(outputValue);
Console.print("V (");
Console.print(sensorValue);
Console.println(")");
}
if (c == '1') {
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN1);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
Console.print("IN1 = " );
Console.print(outputValue);
Console.print("V (");
Console.print(sensorValue);
Console.println(")");
}
if (c == '2') {
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN2);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
Console.print("IN2 = " );
Console.print(outputValue);
Console.print("V (");
Console.print(sensorValue);
Console.println(")");
}
}
if (digitalRead(PIN_S_DETECT) == LOW)
Console.println("Ignition detected!");
// IMPORTANT:
// We must call 'yield' at a regular basis to pass
// control to other tasks.
yield();
}
================================================
FILE: Examples/Test_External_IO_and_LED/Test_External_IO_and_LED.ino
================================================
//External libraries
#include <avr/dtostrf.h>
#define DEBUG 1 //enable debug msg, sent to serial port
#define debug_port SerialUSB
#ifdef DEBUG
#define debug_print(x) debug_port.print(x)
#else
#define debug_print(x)
#endif
// Variables will change:
int outputValue;
int sensorValue;
void setup() {
// put your setup code here, to run once:
// Ignition detection
pinMode(PIN_S_DETECT, INPUT); // Initialize pin as input
// Relay output
pinMode(PIN_C_OUT_1, OUTPUT); // Initialize pin as output
digitalWrite(PIN_C_OUT_1, LOW); // Set PIN LOW
pinMode(PIN_C_OUT_2, OUTPUT); // Initialize pin as output
digitalWrite(PIN_C_OUT_2, LOW); // Set PIN LOW
//setup led pin
pinMode(PIN_STATUS_GSM, OUTPUT);
digitalWrite(PIN_STATUS_GSM, LOW);
}
void loop() {
// Switch the Power LED
digitalWrite(LED_BUILTIN, HIGH);
// IMPORTANT:
// When multiple tasks are running 'delay' passes control to
// other tasks while waiting and guarantees they get executed.
delay(800);
digitalWrite(LED_BUILTIN, LOW);
delay(800);
// Read VIN Value
// read the analog in value:
sensorValue = analogRead(AIN_S_INLEVEL);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("VIN = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
// Read IN1 Value
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN1);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("IN1 = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
// Read IN2 Value
// read the analog in value:
sensorValue = analogRead(AIN_EXT_IN2);
// map it to the range of the analog out:
outputValue = sensorValue * (242.0f / 22.0f * ANALOG_VREF / 1024.0f);
// print the results to the serial monitor:
debug_print(F("IN2 = " ));
debug_print(outputValue);
debug_print(F("V ("));
debug_print(sensorValue);
debug_print(F(")"));
debug_port.println(" ");
// Check If Ignition is on
if (digitalRead(PIN_S_DETECT) == LOW)
debug_print(F("Ignition detected!"));
}
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
================================================
FILE: OpenTracker/.gitignore
================================================
tracker.h
================================================
FILE: OpenTracker/OpenTracker.ino
================================================
//tracker config
#include "tracker.h"
#include "addon.h"
//External libraries
#include <TinyGPS.h>
#include <DueFlashStorage.h>
//optimization
#define dtostrf(val, width, prec, sout) (void) sprintf(sout, "%" #width "." #prec "f", val)
bool debug_enable = true; // runtime flag to disable debug console
#if DEBUG
#define debug_print(...) do { if(debug_enable) debug_port.println(__VA_ARGS__); } while(0)
#else
#define debug_print(...)
#endif
#if SEND_RAW
#define collect_data(i) collect_all_data_raw(i);
#else
#define collect_data(i) collect_all_data(i);
#endif
// Variables will change:
int ledState = LOW; // ledState used to set the LED
long previousMillis = 0; // will store last time LED was updated
long watchdogMillis = 0; // will store last time modem watchdog was reset
int SEND_DATA = 1;
long time_start, time_stop, time_diff; //count execution time to trigger interval
int interval_count = 0; //current interval count (increased on each data collection and reset after sending)
int sms_check_count = 0; //counter for SMS check (increased on each cycle and reset after check)
char data_current[DATA_LIMIT+1]; //data collected in one go, max 2500 chars
int data_index = 0; //current data index (where last data record stopped)
char time_char[24]; //time attached to every data line
char modem_reply[200]; //data received from modem, max 200 chars
uint32_t logindex = STORAGE_DATA_START;
bool save_config = 0; //flag to save config to flash
bool power_reboot = 0; //flag to reboot everything (used after new settings have been saved)
bool power_cutoff = 0; //flag to cut-off power to avoid deep-discharge (no more operational afterwards)
bool low_power = 0; //flag for low power mode
char lat_current[15];
char lon_current[15];
unsigned long last_time_gps = -1, last_date_gps = 0, last_fix_gps = 0;
int engineRunning = -1;
unsigned long engineRunningTime = 0;
unsigned long engine_start;
DueFlashStorage dueFlashStorage;
int gsm_send_failures = 0;
int gsm_reply_failures = 0;
//settings structure
struct settings {
char apn[64];
char user[20];
char pwd[20];
long interval; //how often to collect data (milli sec, 600000 - 10 mins)
int interval_send; //how many times to collect data before sending (times), sending interval interval*interval_send
char key[12]; //key for connection, will be sent with every data transmission
char sim_pin[5]; //PIN for SIM card
char sms_key[12]; //password for SMS commands
char imei[20]; //IMEI number
byte alarm_on;
char alarm_phone[20]; //alarm phone number
byte queclocator; //flag to use QuecLocator fallback when GPS not available
byte debug; //flag to enable/disable debug console (USB)
byte powersave; //flag to enable/disable low power mode (with engine off)
};
settings config;
//define serial ports
#define gps_port Serial1
#define debug_port SerialUSB
#define gsm_port Serial2
void setup() {
//common hardware initialization
device_init();
//initialize GSM and GPS hardware
gsm_init();
gps_init();
//initialize addon board hardware
addon_init();
//setting debug serial port
debug_port.begin(9600);
delay(2000);
debug_print(F("setup() started"));
//blink software start
blink_start();
settings_load();
//get current log index
#if STORAGE
storage_get_index();
#endif
//GPS setup
gps_setup();
#if DEBUG == 20
debug_gps_terminal();
#endif
//GSM setup
gsm_setup();
#if DEBUG == 10
debug_gsm_terminal();
#endif
// reply to Alarm SMS command
if(config.alarm_on) {
sms_send_msg("Alarm Activated", config.alarm_phone);
}
// make sure we start with empty data
data_reset();
#ifdef KNOWN_APN_LIST
// auto scanning of apn details configuration
int ap = gsm_scan_known_apn();
if (ap) {
save_config = 1; // found good APN, save it as default
}
#endif
#ifdef GSM_USE_NTP_SERVER
// attempt clock update (over data connection)
gsm_ntp_update();
#endif
// setup addon board functionalities
addon_setup();
debug_print(F("setup() completed"));
// ensure SMS command check at power on or reset
sms_check();
// apply runtime debug option (USB console) after setup
if (config.debug == 1)
usb_console_restore();
else
usb_console_disable();
}
void loop() {
if(save_config == 1) {
//config should be saved
settings_save();
save_config = 0;
}
if(power_reboot == 1) {
//reboot unit
reboot();
power_reboot = 0;
}
if (power_cutoff) {
kill_power();
}
// Check if ignition is turned on
int IGNT_STAT = digitalRead(PIN_S_DETECT);
debug_print(F("Ignition status:"));
debug_print(IGNT_STAT);
// detect transitions from engine on and off
if(IGNT_STAT == 0) {
if(engineRunning != 0) {
// engine started
engine_start = millis();
engineRunning = 0;
if(config.alarm_on == 1) {
sms_send_msg("Ignition ON", config.alarm_phone);
}
if(config.powersave == 1) {
// restore full speed for serial communication
cpu_full_speed();
gsm_open();
}
}
} else {
if(engineRunning != 1) {
// engine stopped
if(engineRunning == 0) {
engineRunningTime += (millis() - engine_start);
if(config.alarm_on == 1) {
sms_send_msg("Ignition OFF", config.alarm_phone);
}
// force sending last data
interval_count = config.interval_send;
collect_data(IGNT_STAT);
send_data();
}
engineRunning = 1;
// save power when engine is off
gsm_deactivate(); // ~20mA less
if(config.powersave == 1) {
gsm_close();
cpu_slow_down(); // ~20mA less
}
}
}
if(!ENGINE_RUNNING_LOG_FAST_AS_POSSIBLE || IGNT_STAT != 0 || !SEND_DATA) {
time_stop = millis();
//signed difference is good if less than MAX_LONG
time_diff = time_stop-time_start;
time_diff = config.interval-time_diff;
debug_print(F("Sleeping for:"));
debug_print(time_diff);
debug_print(F("ms"));
if(time_diff < 1000) {
addon_delay(1000); // minimal wait to let addon code execute
} else {
addon_delay(time_diff);
}
} else {
addon_delay(1000); // minimal wait to let addon code execute
}
//start counting time
time_start = millis();
if(ALWAYS_ON || IGNT_STAT == 0) {
if(IGNT_STAT == 0) {
debug_print(F("Ignition is ON!"));
// Insert here only code that should be processed when Ignition is ON
}
//collecting GPS data
collect_data(IGNT_STAT);
send_data();
#if SMS_CHECK_INTERVAL_ENGINE_RUNNING > 0
// perform SMS check
if (++sms_check_count >= SMS_CHECK_INTERVAL_ENGINE_RUNNING) {
sms_check_count = 0;
// facilitate SMS exchange by turning off data session
gsm_deactivate();
sms_check();
}
#endif
} else {
debug_print(F("Ignition is OFF!"));
// Insert here only code that should be processed when Ignition is OFF
#if SMS_CHECK_INTERVAL_COUNT > 0
// perform SMS check
if (++sms_check_count >= SMS_CHECK_INTERVAL_COUNT) {
sms_check_count = 0;
if(config.powersave == 1) {
// restore full speed for serial communication
cpu_full_speed();
gsm_open();
}
sms_check();
if(config.powersave == 1) {
// back to power saving
gsm_close();
cpu_slow_down();
}
}
#endif
}
status_led();
debug_check_input();
addon_loop();
}
void device_init() {
//setup led pin
pinMode(PIN_POWER_LED, OUTPUT);
digitalWrite(PIN_POWER_LED, LOW);
#ifdef PIN_C_REBOOT
pinMode(PIN_C_REBOOT, OUTPUT);
digitalWrite(PIN_C_REBOOT, LOW); //this is required for HW rev 2.3 and earlier
#endif
//setup ignition detection
pinMode(PIN_S_DETECT, INPUT);
// setup relay outputs
pinMode(PIN_C_OUT_1, OUTPUT);
digitalWrite(PIN_C_OUT_1, LOW);
pinMode(PIN_C_OUT_2, OUTPUT);
digitalWrite(PIN_C_OUT_2, LOW);
}
// when DEBUG is defined >= 2 then serial monitor accepts test commands
void debug_check_input() {
#if DEBUG > 1
#warning "Do not use DEBUG=2 in production code!"
if(!debug_enable)
return;
while(debug_port.available()) {
int c = debug_port.read();
debug_port.print(F("debug_check_input() got: "));
debug_port.println((char)c);
switch (c)
{
case 'r':
reboot();
break;
case 'l':
enter_low_power();
addon_delay(15000);
exit_low_power();
break;
case 'd':
storage_dump();
storage_send_logs(0);
break;
case '^':
debug_gsm_terminal();
break;
case '|':
debug_gps_terminal();
break;
}
}
#endif
}
void debug_gsm_terminal()
{
debug_port.print(F("Started GSM terminal"));
for(;;) {
int c = debug_port.read();
if (c == '^') break;
if (c > 0)
gsm_port.write(c);
c = gsm_port.read();
if (c > 0)
debug_port.write(c);
}
debug_port.print(F("Exited GSM terminal"));
}
void debug_gps_terminal()
{
debug_port.print(F("Started GPS terminal"));
for(;;) {
int c = debug_port.read();
if (c == '|') break;
if (c > 0)
gps_port.write(c);
c = gps_port.read();
if (c > 0)
debug_port.write(c);
}
debug_port.print(F("Exited GPS terminal"));
}
================================================
FILE: OpenTracker/addon.h
================================================
// Interface for Add-on board functions
// (define the following symbol in "tracker.h" to provide your own implementation)
#if !ADDON_INTERFACE
void addon_delay(long ms) {
// called for delays that can run addon code
// default implementation just calls status_delay()
void status_delay(long ms);
status_delay(ms);
}
void addon_init() {
// called at the beginning of setup()
// use to configure addon board hardware interface (expansion header)
}
void addon_setup() {
// called at the end of setup()
// use to initialize external hardware on the addon board
}
void addon_loop() {
// called inside main loop() or other waiting loops
}
void addon_event(int event) {
// called on some tracker events (see below list)
}
void addon_sms_command(char *cmd, char *arg, const char *phone) {
// called to handle unknown SMS commands
}
void addon_collect_data() {
// called inside collect_all_data() to append custom data
}
#endif
struct addon_settings;
// event types
enum {
ON_DEVICE_STANDBY, // before going to low power mode
ON_DEVICE_WAKEUP, // after going back to full power mode
ON_DEVICE_KILL, // before killing power consumption (no more operational afterwards)
ON_CLOCK_PAUSE, // before changing system clock
ON_CLOCK_RESUME, // after changing system clock
ON_SETTINGS_DEFAULT, // when loading default settings in volatile memory
ON_SETTINGS_LOAD, // when loading saved settings from volatile memory
ON_SETTINGS_SAVE, // when saving settings to persistent storage
ON_MODEM_REPLY, // after each modem reply (can snoop modem_reply[] buffer)
ON_MODEM_ACTIVATION, // during the first modem GPRS activation
ON_SEND_STARTED, // before initiating a new data connection
ON_SEND_DATA, // before sending a block of data
ON_SEND_COMPLETED, // after the end of successful transmission
ON_SEND_FAILED, // after the end of failed trasmission
ON_RECEIVE_STARTED, // before starting to receive data
ON_RECEIVE_DATA, // after receiving each block of data
ON_RECEIVE_COMPLETED, // after the end of successful reception
ON_RECEIVE_FAILED, // after the end of failed receiption
ON_LOCATION_FIXED, // after GPS acquired fix (valid location)
ON_LOCATION_LOST, // after GPS failed to acquire new fix (invalid location)
};
================================================
FILE: OpenTracker/addon.ino.example
================================================
// Implementation of Audio Add-on board functions
#if !ADDON_INTERFACE
#line 1 "tracker.h"
#error "To use Add-On features you must define ADDON_INTERFACE=1 in tracker.h"
#endif
// -- ADDON INTERFACE FUNCTIONS ---
void addon_delay(long ms) {
// Custom delay function, called when timing is not critical
// and additional code can be executed, for example:
long start = (long)millis();
while ((long)millis() - start < ms) {
addon_loop();
status_delay(100);
}
}
void addon_init() {
// Global state initialization and additional pin configuration
// (e.g. initialize I/O pins on expansion header, on-chip peripherals like
// UART, USART, SPI, I2C, or external I/O expanders)
}
void addon_setup() {
// Additional hardware setup, after modem setup
// (e.g. configure external chips, or send additional modem commands)
}
void addon_loop() {
// Anything you want to execute during the main or other wait loops
}
void addon_sms_command(char *cmd, char *arg, const char *phone) {
// Handle SMS commands not already handled by the main application
}
void addon_collect_data()
{
// Append your own data to send to the server, for example additional sensors
data_append_char('{');
data_field_restart();
// first sensor
data_field_separator(',');
data_append_string("my_sensor1:");
char tmp[40];
unsigned int value = ...;
snprintf(tmp, sizeof(tmp), "%2.2f", value);
data_append_string(tmp);
// second sensor
data_field_separator(',');
data_append_string("my_sensor2:");
...
data_append_char('}');
}
void addon_event(int event) {
// Handle event notifications
switch (event) {
case ON_SETTINGS_DEFAULT:
break;
case ON_SETTINGS_LOAD:
break;
case ON_SETTINGS_SAVE:
break;
case ON_CLOCK_PAUSE:
break;
case ON_CLOCK_RESUME:
break;
case ON_DEVICE_STANDBY:
break;
case ON_DEVICE_WAKEUP:
break;
case ON_MODEM_REPLY:
break;
case ON_MODEM_ACTIVATION:
break;
case ON_SEND_STARTED:
break;
case ON_SEND_DATA:
break;
case ON_SEND_COMPLETED:
break;
case ON_SEND_FAILED:
break;
case ON_RECEIVE_STARTED:
break;
case ON_RECEIVE_DATA:
break;
case ON_RECEIVE_COMPLETED:
break;
case ON_RECEIVE_FAILED:
break;
case ON_LOCATION_FIXED:
break;
case ON_LOCATION_LOST:
break;
default:
break;
}
}
================================================
FILE: OpenTracker/data.ino
================================================
//collect and send GPS data for sending
void data_append_char(char c) {
if (data_index < sizeof(data_current) - 1) {
data_current[data_index++] = c;
data_current[data_index] = 0;
}
}
void data_append_string(const char *str) {
while (*str != 0 && data_index < sizeof(data_current) - 1)
data_current[data_index++] = *str++;
data_current[data_index] = 0;
}
void data_reset() {
// make sure there is always a string terminator
memset(data_current, 0, sizeof(data_current));
data_index = 0;
}
bool data_sep_flag = false;
void data_field_separator(char c) {
if (data_sep_flag)
data_append_char(c);
data_sep_flag = true;
}
void data_field_restart() {
data_sep_flag = false;
}
// url encoding functions
char to_hex(int nibble) {
static const char hex[] = "0123456789abcdef";
return hex[nibble & 15];
}
bool is_url_safe(char c) {
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| c == '-' || c == '_' || c == '.' || c == '~' || c == '!' || c == '*' || c == '\'' || c == '(' || c == ')')
return true;
return false;
}
int url_encoded_strlen(const char* s) {
int len = strlen(s);
int ret = 0;
while (len--) {
ret += is_url_safe(*s++) ? 1 : 3;
}
return ret;
}
// return count of consumed source characters (that fit the buffer after encoding)
int url_encoded_strlcpy(char* dst, int maxlen, const char* src) {
int len = strlen(src);
int count = 0;
while (len > 0 && maxlen > 4) {
char c = *src++;
++count;
--len;
if (is_url_safe(c)) {
*dst++ = c;
--maxlen;
} else {
*dst++ = '%';
*dst++ = to_hex(c >> 4);
*dst++ = to_hex(c & 15);
maxlen -= 3;
}
}
*dst = '\0';
return count;
}
// read and convert analog input voltage
float analog_input_voltage(int pin, int range)
{
float sensorValue = analogRead(pin);
if (range == HIGH)
sensorValue *= 242.0f * ANALOG_VREF / 1023.0f / 22.0f;
if (range == LOW)
sensorValue *= ANALOG_VREF / 1023.0f;
return sensorValue;
}
/**
* This is default collect data function for HTTP
*/
void collect_all_data(int ignitionState) {
debug_print(F("collect_all_data() started"));
data_field_restart();
//get current time and add to this data packet
gsm_get_time();
//attach time to data packet
data_append_string(time_char);
//collect all data
//indicate start of GPS data packet
data_append_char('[');
data_field_restart();
int idx = data_index;
collect_gps_data();
#if GSM_USE_QUECLOCATOR_TIMEOUT > 0
if (config.queclocator == 1 && idx == data_index) // no GPS fix available, use QuecLocator if enabled
{
if (gsm_get_queclocator())
{
debug_print("override missing GPS data with QuecLocator");
// construct GPS data packet
if(DATA_INCLUDE_GPS_DATE) {
data_field_separator(',');
//converting modem date to GPS format
data_append_char(time_char[6]);
data_append_char(time_char[7]);
data_append_char(time_char[3]);
data_append_char(time_char[4]);
data_append_char(time_char[0]);
data_append_char(time_char[1]);
}
if(DATA_INCLUDE_GPS_TIME) {
data_field_separator(',');
//converting modem time to GPS format
data_append_char(time_char[9]);
data_append_char(time_char[10]);
data_append_char(time_char[12]);
data_append_char(time_char[13]);
data_append_char(time_char[15]);
data_append_char(time_char[16]);
data_append_char(time_char[18]);
data_append_char(time_char[19]);
}
if(DATA_INCLUDE_LATITUDE) {
data_field_separator(',');
data_append_string(lat_current);
}
if(DATA_INCLUDE_LONGITUDE) {
data_field_separator(',');
data_append_string(lon_current);
}
if(DATA_INCLUDE_SPEED) {
data_field_separator(',');
}
if(DATA_INCLUDE_ALTITUDE) {
data_field_separator(',');
}
if(DATA_INCLUDE_HEADING) {
data_field_separator(',');
}
if(DATA_INCLUDE_HDOP) {
data_field_separator(',');
}
if(DATA_INCLUDE_SATELLITES) {
data_field_separator(',');
data_append_string("-1"); //-1 means QuecLocator
}
}
}
#endif
//indicate stop of GPS data packet
data_append_char(']');
data_field_restart();
// append battery level to data packet
if(DATA_INCLUDE_BATTERY_LEVEL) {
data_field_separator(',');
float sensorValue = analog_input_voltage(AIN_S_INLEVEL, HIGH);
char batteryLevel[10];
dtostrf(sensorValue,2,2,batteryLevel);
data_append_string(batteryLevel);
}
// ignition state
if(DATA_INCLUDE_IGNITION_STATE) {
data_field_separator(',');
if(ignitionState == -1) {
data_append_char('2'); // backup source
} else if(ignitionState == 0) {
data_append_char('1');
} else {
data_append_char('0');
}
}
// engine running time
if(DATA_INCLUDE_ENGINE_RUNNING_TIME) {
unsigned long currentRunningTime = engineRunningTime;
char runningTimeString[32];
if(engineRunning == 0) {
currentRunningTime += (millis() - engine_start);
}
snprintf(runningTimeString,32,"%ld",(unsigned long) currentRunningTime / 1000);
data_field_separator(',');
data_append_string(runningTimeString);
}
addon_collect_data();
//end of data packet
data_append_char('\n');
debug_print(F("collect_all_data() completed"));
}
/**
* This function collects data for RAW TCP
*/
void collect_all_data_raw(int ignitionState) {
debug_print(F("collect_all_data_raw() started"));
data_field_restart();
if(SEND_RAW_INCLUDE_IMEI) {
data_append_string(config.imei);
}
if(SEND_RAW_INCLUDE_KEY) {
data_field_separator(',');
data_append_string(config.key);
}
if(SEND_RAW_INCLUDE_TIMESTAMP) {
data_field_separator(',');
gsm_get_time();
data_append_string(time_char);
}
collect_gps_data();
// append battery level to data packet
if(DATA_INCLUDE_BATTERY_LEVEL) {
data_field_separator(',');
float sensorValue = analog_input_voltage(AIN_S_INLEVEL, HIGH);
char batteryLevel[10];
dtostrf(sensorValue,2,2,batteryLevel);
data_append_string(batteryLevel);
}
// ignition state
if(DATA_INCLUDE_IGNITION_STATE) {
data_field_separator(',');
if(ignitionState == -1) {
data_append_char('2'); // backup source
} else if(ignitionState == 0) {
data_append_char('1');
} else {
data_append_char('0');
}
}
// engine running time
if(DATA_INCLUDE_ENGINE_RUNNING_TIME) {
data_field_separator(',');
unsigned long currentRunningTime = engineRunningTime;
char runningTimeString[32];
if(engineRunning == 0) {
currentRunningTime += (millis() - engine_start);
}
snprintf(runningTimeString,32,"%ld",(unsigned long) currentRunningTime / 1000);
data_append_string(runningTimeString);
}
//end of data packet
data_append_char('\n');
debug_print(F("collect_all_data_raw() completed"));
}
/**
* This function send collected data using HTTP or TCP
*/
void send_data() {
debug_print(F("send_data() started"));
debug_print(F("Current:"));
debug_print(data_current);
interval_count++;
debug_print(F("Data accumulated:"));
debug_print(interval_count);
// send accumulated data
if (interval_count >= config.interval_send) {
// if data send disabled, use storage
if (!SEND_DATA) {
debug_print(F("Data send is turned off."));
#if STORAGE
storage_save_current(); //in case this fails - data is lost
#endif
} else if (gsm_send_data() != 1) {
debug_print(F("Could not send data."));
#if STORAGE
storage_save_current(); //in case this fails - data is lost
#endif
} else {
debug_print(F("Data sent successfully."));
#if STORAGE
//connection seems ok, send saved data history
storage_send_logs(1); // 0 = dump only, 1 = send data
#endif
}
//reset current data and counter
data_reset();
interval_count -= config.interval_send;
}
debug_print(F("send_data() completed"));
}
================================================
FILE: OpenTracker/gps.ino
================================================
void gps_init() {
debug_print(F("gps_init() started"));
pinMode(PIN_STANDBY_GPS, OUTPUT);
digitalWrite(PIN_STANDBY_GPS, LOW);
pinMode(PIN_RESET_GPS, OUTPUT);
digitalWrite(PIN_RESET_GPS, LOW);
gps_open();
debug_print(F("gps_init() completed"));
}
void gps_open() {
gps_port.begin(9600);
}
void gps_close() {
gps_port.end();
}
void gps_setup() {
debug_print(F("gps_setup() started"));
gps_on();
// read the first 4 lines within timeout
unsigned long t = millis();
for(int i=0; i<4; ++i) {
int c = -1;
while (c != '\n' && (millis() - t < 5000))
if(gps_port.available()) {
c = gps_port.read();
if(DEBUG)
debug_port.write(c);
}
}
if(millis() - t > 5000) debug_print(F("GPS not responding"));
debug_print(F("gps_setup() completed"));
}
void gps_on() {
//turn off GPS
debug_print(F("gps_on() started"));
delay(100);
digitalWrite(PIN_STANDBY_GPS, LOW);
digitalWrite(PIN_RESET_GPS, LOW);
debug_print(F("gps_on() completed"));
}
void gps_off() {
//turn off GPS
debug_print(F("gps_off() started"));
digitalWrite(PIN_STANDBY_GPS, HIGH);
digitalWrite(PIN_RESET_GPS, HIGH);
delay(100);
debug_print(F("gps_off() completed"));
}
void gps_standby() {
// standby GPS
gps_port.print("$PMTK161,0*28\r\n");
}
void gps_wakeup() {
// exit GPS standby
gps_port.print("\r\n");
}
//collect GPS data from serial port
void collect_gps_data() {
int fix = 0;
char tmp[15];
float flat, flon;
unsigned long age_pos, age_time, time_gps, date_gps, speed, course, alt;
unsigned long chars;
unsigned short sentences, failed_checksum;
long timer = millis();
// drain receive buffer (discard old data)
while (gps_port.available() && (signed long)(millis() - timer) < GPS_COLLECT_TIMEOUT * 1000)
gps_port.read();
// use local variable to reset old data
TinyGPS gps;
// looking for valid fix
do {
while (gps_port.available()) {
char c = gps_port.read();
//debug
#ifdef DEBUG
debug_port.print(c);
#endif
if(fix == 1) { //fix already acquired
debug_print(F("GPS already available, breaking"));
break;
}
if(gps.encode(c)) {
// process new gps info here
// time in hhmmsscc, date in ddmmyy
gps.get_datetime(&date_gps, &time_gps, &age_time);
// get latitude and longitude
gps.f_get_position(&flat, &flon, &age_pos);
// check if timestamp and position are current (not from previous attempts)
if(age_time == TinyGPS::GPS_INVALID_AGE || age_time > 1100) {
debug_print(F("Invalid date/time age, retrying."));
continue;
}
if(age_pos == TinyGPS::GPS_INVALID_AGE || age_pos > 1100) {
debug_print(F("Invalid position age, retrying."));
continue;
}
//check if this fix is already received
if((last_time_gps == time_gps) && (last_date_gps == date_gps)) {
debug_print(F("Warning: this fix date/time already logged, retrying"));
continue;
}
#if DATA_INCLUDE_SPEED
float fkmph = gps.f_speed_kmph(); // speed in km/hr
if(fkmph == TinyGPS::GPS_INVALID_F_SPEED) {
debug_print(F("Invalid speed, retrying."));
continue;
}
#endif
#if DATA_INCLUDE_ALTITUDE
float falt = gps.f_altitude(); // +/- altitude in meters
if(falt == TinyGPS::GPS_INVALID_F_ALTITUDE) {
debug_print(F("Invalid altitude, retrying."));
continue;
}
#endif
#if DATA_INCLUDE_HEADING
float fc = gps.f_course(); // course in degrees
if(fc == TinyGPS::GPS_INVALID_F_ANGLE) {
debug_print(F("Invalid course, retrying."));
continue;
}
#endif
#if DATA_INCLUDE_HDOP
long hdop = gps.hdop(); //hdop
if(hdop == TinyGPS::GPS_INVALID_HDOP) {
debug_print(F("Invalid HDOP, retrying."));
continue;
}
#endif
#if DATA_INCLUDE_SATELLITES
long sats = gps.satellites(); //satellites
if(sats == TinyGPS::GPS_INVALID_SATELLITES) {
debug_print(F("Invalid satellites, retrying."));
continue;
}
#endif
debug_print(F("Valid GPS fix received."));
fix = 1;
last_fix_gps = millis();
//update current time var - format 04/12/98,00:35:45+00
// Add 1000000 to ensure the position of the digits
ltoa(date_gps + 1000000, tmp, 10); //1ddmmyy
time_char[0] = tmp[5];
time_char[1] = tmp[6];
time_char[2] = '/';
time_char[3] = tmp[3];
time_char[4] = tmp[4];
time_char[5] = '/';
time_char[6] = tmp[1];
time_char[7] = tmp[2];
time_char[8] = ',';
// Add 1000000 to ensure the position of the digits
ltoa(time_gps + 100000000, tmp, 10); //1hhmmssms
time_char[9] = tmp[1];
time_char[10] = tmp[2];
time_char[11] = ':';
time_char[12] = tmp[3];
time_char[13] = tmp[4];
time_char[14] = ':';
time_char[15] = tmp[5];
time_char[16] = tmp[6];
time_char[17] = '+';
time_char[18] = '0';
time_char[19] = '0';
time_char[20] = '\0';
debug_print(F("Current time set from GPS time:"));
debug_print(time_char);
//set modem time from fresh fix
gsm_set_time();
// construct GPS data packet
if(DATA_INCLUDE_GPS_DATE) {
data_field_separator(',');
//converting date to data packet
ltoa(date_gps + 1000000, tmp, 10);
data_append_string(tmp + 1);
}
if(DATA_INCLUDE_GPS_TIME) {
data_field_separator(',');
//time
ltoa(time_gps + 100000000, tmp, 10);
data_append_string(tmp + 1);
}
if(DATA_INCLUDE_LATITUDE) {
data_field_separator(',');
dtostrf(flat,1,6,tmp);
data_append_string(tmp);
}
if(DATA_INCLUDE_LONGITUDE) {
data_field_separator(',');
dtostrf(flon,1,6,tmp);
data_append_string(tmp);
}
if(DATA_INCLUDE_SPEED) {
data_field_separator(',');
dtostrf(fkmph,1,2,tmp);
data_append_string(tmp);
}
if(DATA_INCLUDE_ALTITUDE) {
data_field_separator(',');
dtostrf(falt,1,2,tmp);
data_append_string(tmp);
}
if(DATA_INCLUDE_HEADING) {
data_field_separator(',');
dtostrf(fc,1,2,tmp);
data_append_string(tmp);
}
if(DATA_INCLUDE_HDOP) {
data_field_separator(',');
ltoa(hdop, tmp, 10);
data_append_string(tmp);
}
if(DATA_INCLUDE_SATELLITES) {
data_field_separator(',');
ltoa(sats, tmp, 10);
data_append_string(tmp);
}
//save last gps data date/time
last_time_gps = time_gps;
last_date_gps = date_gps;
//save current position
dtostrf(flat,1,6,lat_current);
dtostrf(flon,1,6,lon_current);
blink_got_gps();
}
}
if(fix == 1) {
//fix was found
debug_print(F("collect_gps_data(): fix acquired"));
addon_event(ON_LOCATION_FIXED);
break;
} else {
// allow some other processing
addon_delay(5);
}
} while ((signed long)(millis() - timer) < GPS_COLLECT_TIMEOUT * 1000);
gps.stats(&chars, &sentences, &failed_checksum);
debug_print(F("Failed checksums:"));
debug_print(failed_checksum);
if(fix != 1) {
debug_print(F("collect_gps_data(): fix not acquired, given up."));
addon_event(ON_LOCATION_LOST);
}
}
================================================
FILE: OpenTracker/gsm.ino
================================================
//gsm functions
#if MODEM_UG96
#define AT_CONTEXT "AT+QICSGP=1,1,"
#define AT_ACTIVATE "AT+QIACT=1\r"
#define AT_DEACTIVATE "AT+QIDEACT=1\r"
#define AT_CONFIGDNS "AT+QIDNSCFG=1,"
#define AT_LOCALIP "AT+QIACT?\r"
#define AT_OPEN "AT+QIOPEN=1,0,"
#define AT_CLOSE "AT+QICLOSE=0\r"
#define AT_SEND "AT+QISEND=0,"
#define AT_RECEIVE "AT+QIRD=0,"
#define AT_STAT "AT+QISTATE=1,0\r"
#define AT_QUERYACK "AT+QISEND=0,0\r"
#define AT_ACKRESP "+QISEND: "
#define AT_NTP "AT+QNTP=1,"
#else
#define AT_CONTEXT "AT+QIREGAPP="
#define AT_ACTIVATE "AT+QIACT\r"
#define AT_DEACTIVATE "AT+QIDEACT\r"
#define AT_CONFIGDNS "AT+QIDNSCFG="
#define AT_LOCALIP "AT+QILOCIP\r"
#define AT_OPEN "AT+QIOPEN=0,"
#define AT_CLOSE "AT+QICLOSE=0\r"
#define AT_SEND "AT+QISEND=0,"
#define AT_RECEIVE "AT+QIRD=0,1,0,"
#define AT_STAT "AT+QISTATE\r"
#define AT_QUERYACK "AT+QISACK=0\r"
#define AT_ACKRESP "+QISACK: "
#define AT_NTP "AT+QNTP="
#endif
void gsm_init() {
//setup modem pins
debug_print(F("gsm_init() started"));
pinMode(PIN_C_PWR_GSM, OUTPUT);
digitalWrite(PIN_C_PWR_GSM, LOW);
pinMode(PIN_C_KILL_GSM, OUTPUT);
digitalWrite(PIN_C_KILL_GSM, LOW);
pinMode(PIN_STATUS_GSM, INPUT);
pinMode(PIN_RING_GSM, INPUT);
pinMode(PIN_WAKE_GSM, OUTPUT);
digitalWrite(PIN_WAKE_GSM, HIGH);
gsm_open();
debug_print(F("gsm_init() finished"));
}
void gsm_open() {
gsm_port.begin(115200);
}
void gsm_close() {
gsm_port.end();
}
bool gsm_power_status() {
#if MODEM_UG96
// inverted status signal
return digitalRead(PIN_STATUS_GSM) != HIGH;
#else
// help discharge floating pin, by temporarily setting as output low
PIO_Configure(
g_APinDescription[PIN_STATUS_GSM].pPort,
PIO_OUTPUT_0,
g_APinDescription[PIN_STATUS_GSM].ulPin,
g_APinDescription[PIN_STATUS_GSM].ulPinConfiguration);
pinMode(PIN_STATUS_GSM, INPUT);
delay(1);
// read modem power status
return digitalRead(PIN_STATUS_GSM) != LOW;
#endif
}
void gsm_on() {
//turn on the modem
debug_print(F("gsm_on() started"));
int k=0;
for (;;) {
unsigned long t = millis();
if(!gsm_power_status()) { // now off, turn on
digitalWrite(PIN_C_PWR_GSM, HIGH);
while (!gsm_power_status() && (millis() - t < 5000))
delay(100);
digitalWrite(PIN_C_PWR_GSM, LOW);
status_delay(1000);
}
// auto-baudrate
if (gsm_send_at())
break;
debug_print(F("gsm_on(): failed auto-baudrate"));
if (++k >= 5) // max attempts
break;
gsm_off(0);
gsm_off(1);
status_delay(1000);
debug_print(F("gsm_on(): try again"));
debug_print(k);
}
// make sure it's not sleeping
gsm_wakeup();
debug_print(F("gsm_on() finished"));
}
void gsm_off(int emergency) {
//turn off the modem
debug_print(F("gsm_off() started"));
unsigned long t = millis();
if(emergency) {
digitalWrite(PIN_C_KILL_GSM, HIGH);
while (gsm_power_status() && (millis() - t < 5000))
delay(100);
digitalWrite(PIN_C_KILL_GSM, LOW);
status_delay(1000);
}
else
if(gsm_power_status()) { // now on, turn off
#if MODEM_UG96
// 3G modem, normal power down
gsm_port.print("AT+QPOWD=1\r");
gsm_wait_for_reply(1,0);
#else
digitalWrite(PIN_C_PWR_GSM, HIGH);
while (gsm_power_status() && (millis() - t < 5000))
delay(100);
digitalWrite(PIN_C_PWR_GSM, LOW);
#endif
status_delay(1000);
}
gsm_get_reply(1);
debug_print(F("gsm_off() finished"));
}
void gsm_standby() {
// clear wake signal
digitalWrite(PIN_WAKE_GSM, HIGH);
// standby GSM
gsm_port.print("AT+CFUN=4\r");
gsm_wait_for_reply(1,0);
gsm_port.print("AT+QSCLK=1\r");
gsm_wait_for_reply(1,0);
}
void gsm_wakeup() {
// wake GSM
digitalWrite(PIN_WAKE_GSM, LOW);
delay(1000);
gsm_port.print("AT+QSCLK=0\r");
gsm_wait_for_reply(1,0);
gsm_port.print("AT+CFUN=1\r");
gsm_wait_for_reply(1,0);
}
void gsm_setup() {
debug_print(F("gsm_setup() started"));
//turn off modem
gsm_off(1);
//blink modem restart
blink_start();
//turn on modem
gsm_on();
//configure
gsm_config();
debug_print(F("gsm_setup() completed"));
}
void gsm_config() {
//supply PIN code if needed
gsm_set_pin();
// wait up to 1 minute
gsm_wait_modem_ready(60000);
//get GSM IMEI
gsm_get_imei();
//misc GSM startup commands (disable echo)
gsm_startup_cmd();
//set GSM APN
gsm_set_apn();
}
void gsm_wait_modem_ready(int timeout) {
// wait for modem ready (attached to network)
unsigned long t = millis();
do {
int pas = gsm_get_modem_status();
if(pas==0 || pas==3 || pas==4) break;
status_delay(3000);
}
while (millis() - t < timeout);
}
bool gsm_clock_was_set = false;
void gsm_set_time() {
debug_print(F("gsm_set_time() started"));
//setting modems clock from current time var
gsm_port.print("AT+CCLK=\"");
gsm_port.print(time_char);
gsm_port.print("\"\r");
gsm_wait_for_reply(1,0);
gsm_clock_was_set = true;
debug_print(F("gsm_set_time() completed"));
}
void gsm_set_pin() {
debug_print(F("gsm_set_pin() started"));
for (int k=0; k<5; ++k) {
//checking if PIN is set
gsm_port.print("AT+CPIN?");
gsm_port.print("\r");
gsm_wait_for_reply(1,1);
char *tmp = strstr(modem_reply, "SIM PIN");
if(tmp!=NULL) {
debug_print(F("gsm_set_pin(): PIN is required"));
//checking if pin is valid one
if(config.sim_pin[0] == 255) {
debug_print(F("gsm_set_pin(): PIN is not supplied."));
} else {
if(strlen(config.sim_pin) == 4) {
debug_print(F("gsm_set_pin(): PIN supplied, sending to modem."));
gsm_port.print("AT+CPIN=");
gsm_port.print(config.sim_pin);
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
tmp = strstr(modem_reply, "OK");
if(tmp!=NULL) {
debug_print(F("gsm_set_pin(): PIN is accepted"));
} else {
debug_print(F("gsm_set_pin(): PIN is not accepted"));
}
break;
} else {
debug_print(F("gsm_set_pin(): PIN supplied, but has invalid length. Not sending to modem."));
break;
}
}
}
tmp = strstr(modem_reply, "READY");
if(tmp!=NULL) {
debug_print(F("gsm_set_pin(): PIN is not required"));
break;
}
status_delay(2000);
}
debug_print(F("gsm_set_pin() completed"));
}
void gsm_get_time() {
debug_print(F("gsm_get_time() started"));
//clean any serial data
gsm_get_reply(0);
//get time from modem
gsm_port.print("AT+CCLK?");
gsm_port.print("\r");
gsm_wait_for_reply(1,1);
char *tmp = strstr(modem_reply, "+CCLK: \"");
tmp += strlen("+CCLK: \"");
char *tmpval = strtok(tmp, "\"");
//copy data to main time var
if (gsm_clock_was_set)
strlcpy(time_char, tmpval, sizeof(time_char));
debug_print(F("gsm_get_time() result:"));
debug_print(time_char);
debug_print(F("gsm_get_time() completed"));
}
void gsm_startup_cmd() {
debug_print(F("gsm_startup_cmd() started"));
//disable echo for TCP data
gsm_port.print("AT+QISDE=0");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#if MODEM_M95
//set receiving TCP data by command
gsm_port.print("AT+QINDI=1");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
//set multiple socket support
gsm_port.print("AT+QIMUX=1");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#endif
//set SMS as text format
gsm_port.print("AT+CMGF=1");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#if GSM_USE_QUECLOCATOR_TIMEOUT > 0
//set QuectLocator timeout
gsm_port.print("AT+QLOCCFG=\"timeout\",");
gsm_port.print(GSM_USE_QUECLOCATOR_TIMEOUT);
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#endif
debug_print(F("gsm_startup_cmd() completed"));
}
void gsm_get_imei() {
debug_print(F("gsm_get_imei() started"));
//get modem's imei
gsm_port.print("AT+GSN");
gsm_port.print("\r");
status_delay(1000);
gsm_get_reply(1);
//reply data stored to modem_reply
char *tmp = strstr(modem_reply, "AT+GSN\r\r\n");
tmp += strlen("AT+GSN\r\r\n");
char *tmpval = strtok(tmp, "\r");
//copy data to main IMEI var
strlcpy(config.imei, tmpval, sizeof(config.imei));
debug_print(F("gsm_get_imei() result:"));
debug_print(config.imei);
debug_print(F("gsm_get_imei() completed"));
}
int gsm_send_at() {
debug_print(F("gsm_send_at() started"));
int ret = 0;
for (int k=0; k<5; ++k) {
gsm_port.print("ATE1\r");
status_delay(50);
gsm_get_reply(1);
ret = (strstr(modem_reply, "ATE1") != NULL)
&& (strstr(modem_reply, "OK") != NULL);
if (ret) break;
status_delay(1000);
}
debug_print(F("gsm_send_at() completed"));
debug_print(ret);
return ret;
}
int gsm_get_modem_status() {
debug_print(F("gsm_get_modem_status() started"));
gsm_port.print("AT+CPAS");
gsm_port.print("\r");
int pas = -1; // unexpected reply
for (int k=0; k<10; ++k) {
status_delay(50);
gsm_get_reply(0);
char *tmp = strstr(modem_reply, "+CPAS:");
if(tmp!=NULL) {
pas = atoi(tmp+6);
break;
}
}
gsm_wait_for_reply(1,0);
debug_print(F("gsm_get_modem_status() returned: "));
debug_print(pas);
return pas;
}
int gsm_disconnect() {
int ret = 0;
debug_print(F("gsm_disconnect() started"));
#if GSM_DISCONNECT_AFTER_SEND
// option to close data session after each server connection
ret = gsm_deactivate();
#else
//close connection, if previous attempts left it open
gsm_port.print(AT_CLOSE);
gsm_wait_for_reply(MODEM_UG96,0);
//ignore errors (will be taken care during connect)
ret = 1;
#endif
debug_print(F("gsm_disconnect() completed"));
return ret;
}
int gsm_deactivate() {
// disable data session
int ret = 0;
debug_print(F("gsm_deactivate() started"));
//disconnect GSM
gsm_port.print(AT_DEACTIVATE);
gsm_wait_for_reply(MODEM_UG96,0);
#if MODEM_UG96
//check if result contains OK
char *tmp = strstr(modem_reply, "OK");
#else
//check if result contains DEACT OK
char *tmp = strstr(modem_reply, "DEACT OK");
#endif
if(tmp!=NULL)
ret = 1;
debug_print(F("gsm_deactivate() completed"));
return ret;
}
int gsm_set_apn() {
debug_print(F("gsm_set_apn() started"));
//disconnect GSM
gsm_port.print(AT_DEACTIVATE);
gsm_wait_for_reply(MODEM_UG96,0);
addon_event(ON_MODEM_ACTIVATION);
//set all APN data, dns, etc
gsm_port.print(AT_CONTEXT "\"");
gsm_port.print(config.apn);
gsm_port.print("\",\"");
gsm_port.print(config.user);
gsm_port.print("\",\"");
gsm_port.print(config.pwd);
gsm_port.print("\"");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#if MODEM_M95
gsm_port.print("AT+QIDNSIP=1");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#endif
gsm_port.print(AT_ACTIVATE);
// wait for GPRS contex activation (first time)
unsigned long t = millis();
do {
gsm_wait_for_reply(1,0);
if(modem_reply[0] != 0) break;
}
while (millis() - t < 60000);
gsm_port.print(AT_LOCALIP); // diagnostic only
status_delay(500);
gsm_get_reply(0);
gsm_send_at();
gsm_port.print(AT_CONFIGDNS "\"8.8.8.8\"");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
debug_print(F("gsm_set_apn() completed"));
return 1;
}
int gsm_get_connection_status() {
debug_print(F("gsm_get_connection_status() started"));
int ret = -1; //unknown
gsm_get_reply(1); //flush buffer
gsm_port.print(AT_STAT);
gsm_wait_for_reply(1,0);
#if MODEM_UG96
char *tmp = strtok(modem_reply, ",");
if (tmp != NULL && strstr(modem_reply, "+QISTATE:") != NULL) {
for (int k=0; k<5; ++k) {
tmp = strtok(NULL, ",");
}
if (tmp != NULL) {
ret = atoi(tmp);
debug_print(ret);
if (ret == 3)
ret = 1; // already connected
else if (ret > 0)
ret = 2; // previous connection failed, should close
}
gsm_wait_for_reply(1,0); // catch OK
}
else if (strstr(modem_reply, "OK") != NULL)
ret = 0; // ready to connect
// check also data packet connection is active
gsm_get_reply(1); //flush buffer
gsm_port.print("AT+CGACT?\r");
gsm_wait_for_reply(1,1);
tmp = strstr(modem_reply, "+CGACT:");
if(tmp!=NULL) {
tmp = strtok(tmp + 7, ",");
if(tmp!=NULL) {
tmp = strtok(NULL, ",");
if(tmp!=NULL) {
if (atoi(tmp) != 1)
ret = -2; // force deactivation
}
}
}
#else
if (strstr(modem_reply, "OK\r\n") != NULL) {
gsm_wait_for_reply(0,0);
if (strstr(modem_reply, "IP IND") != NULL ||
strstr(modem_reply, "PDP DEACT") != NULL) {
ret = -2; // force deactivation
}
// find socket status
for (int i=0; i<6; ++i) {
gsm_wait_for_reply(0,0);
if (ret == -1 && strstr(modem_reply, "+QISTATE: 0,") != NULL) {
if (strstr(modem_reply, "INITIAL") != NULL ||
strstr(modem_reply, "CLOSE") != NULL)
ret = 0; // ready to connect
if (strstr(modem_reply, "CONNECTED") != NULL)
ret = 1; // already connected
if (strstr(modem_reply, "CONNECTING") != NULL)
ret = 2; // previous connection failed, should close
}
}
gsm_wait_for_reply(1,0); // catch final OK
}
#endif
debug_print(F("gsm_get_connection_status() returned:"));
debug_print(ret);
return ret;
}
int gsm_connect() {
int ret = 0;
debug_print(F("gsm_connect() started"));
//try to connect multiple times
for(int i=0;i<CONNECT_RETRY;i++) {
// connect only when modem is ready
if (gsm_get_modem_status() == 0) {
// check if connected from previous attempts
int ipstat = gsm_get_connection_status();
if (ipstat > 1) {
//close connection, if previous attempts failed
gsm_port.print(AT_CLOSE);
gsm_wait_for_reply(MODEM_UG96,0);
ipstat = 0;
}
if (ipstat < 0) {
//deactivate required
gsm_port.print(AT_DEACTIVATE);
gsm_wait_for_reply(MODEM_UG96,0);
ipstat = 0;
#if MODEM_UG96
gsm_port.print(AT_ACTIVATE);
gsm_wait_for_reply(1,0);
gsm_port.print(AT_CONFIGDNS "\"8.8.8.8\"");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
#endif
}
if (ipstat == 0) {
debug_print(F("Connecting to remote server..."));
debug_print(i);
//open socket connection to remote host
//opening connection
gsm_port.print(AT_OPEN "\"");
gsm_port.print(PROTO);
gsm_port.print("\",\"");
gsm_port.print(HOSTNAME);
gsm_port.print("\",");
#if MODEM_M95
gsm_port.print("\"");
#endif
gsm_port.print(HTTP_PORT);
#if MODEM_M95
gsm_port.print("\"");
#endif
gsm_port.print("\r");
gsm_wait_for_reply(1, 0); // OK sent first
long timer = millis();
if(strstr(modem_reply, "OK")==NULL)
ipstat = 0;
else
do {
gsm_get_reply(1);
#if MODEM_UG96
char *tmp = strstr(modem_reply, "+QIOPEN: 0,");
if(tmp!=NULL) {
tmp += strlen("+QIOPEN: 0,");
if (atoi(tmp)==0)
ipstat = 1;
else
ipstat = 0;
break;
}
#else
if(strstr(modem_reply, "CONNECT OK")!=NULL) {
ipstat = 1;
break;
}
if(strstr(modem_reply, "CONNECT FAIL")!=NULL ||
strstr(modem_reply, "ERROR")!=NULL) {
ipstat = 0;
break;
}
#endif
addon_delay(100);
} while (millis() - timer < CONNECT_TIMEOUT);
}
if(ipstat == 1) {
debug_print(F("Connected to remote server: "));
debug_print(HOSTNAME);
ret = 1;
break;
} else {
debug_print(F("Can not connect to remote server: "));
debug_print(HOSTNAME);
// debug only:
gsm_port.print("AT+CEER\r");
gsm_wait_for_reply(1,0);
}
}
addon_delay(2000); // wait 2s before retrying
}
debug_print(F("gsm_connect() completed"));
return ret;
}
int gsm_validate_tcp() {
int nonacked = 0;
int ret = 0;
debug_print(F("gsm_validate_tcp() started."));
//todo check in the loop if everything delivered
for(int k=0;k<10;k++) {
gsm_port.print(AT_QUERYACK);
gsm_wait_for_reply(1,1);
//todo check if everything is delivered
char *tmp = strstr(modem_reply, AT_ACKRESP);
tmp += strlen(AT_ACKRESP);
//checking how many bytes NON-acked
tmp = strtok(tmp, ", \r\n");
tmp = strtok(NULL, ", \r\n");
tmp = strtok(NULL, ", \r\n");
//non-acked value
nonacked = atoi(tmp);
if(nonacked <= PACKET_SIZE_DELIVERY) {
//all data has been delivered to the server , if not wait and check again
debug_print(F("gsm_validate_tcp() data delivered."));
ret = 1;
break;
} else {
debug_print(F("gsm_validate_tcp() data not yet delivered."));
}
}
debug_print(F("gsm_validate_tcp() completed."));
return ret;
}
int gsm_send_begin(int data_len) {
//sending header packet to remote host
gsm_port.print(AT_SEND);
gsm_port.print(data_len);
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
if (strncmp(modem_reply, "> ", 2) == 0)
return 1; // accepted, can send data
return 0; // error, cannot send data
}
int gsm_send_done() {
gsm_wait_for_reply(1,0);
if (strncmp(modem_reply, "SEND OK", 7) == 0)
return 1; // send successful
return 0; // error
}
#ifdef HTTP_USE_GET
const char HTTP_HEADER0[ ] = //HTTP header line before GET params
"GET " URL "?";
const char HTTP_HEADER1[ ] = //HTTP header line before length
" HTTP/1.0\r\n"
"Host: " HOSTNAME "\r\n"
"Content-length: 0";
#else
const char HTTP_HEADER1[ ] = //HTTP header line before length
"POST " URL " HTTP/1.0\r\n"
"Host: " HOSTNAME "\r\n"
"Content-type: application/x-www-form-urlencoded\r\n"
"Content-length: ";
#endif
const char HTTP_HEADER2[ ] = //HTTP header line after length
"\r\n"
"User-Agent: " HTTP_USER_AGENT "\r\n"
"Connection: close\r\n"
"\r\n";
int gsm_send_http_current() {
//send HTTP request, after connection if fully opened
//this will send Current data
debug_print(F("gsm_send_http(): sending current data."));
debug_print(data_current);
//getting length of data full package
#ifdef HTTP_USE_GET
int http_len = strlen(config.imei)+strlen(config.key);
#else
int http_len = strlen(config.imei)+strlen(config.key)+url_encoded_strlen(data_current);
#endif
http_len += strlen(HTTP_PARAM_IMEI) + strlen(HTTP_PARAM_KEY) + strlen(HTTP_PARAM_DATA) + 5; //imei= &key= &d=
debug_print(F("gsm_send_http(): Length of data packet:"));
debug_print(http_len);
#ifdef HTTP_USE_GET
int tmp_len = strlen(HTTP_HEADER0)+http_len;
#else
//length of header package
char tmp_http_len[7];
itoa(http_len, tmp_http_len, 10);
int tmp_len = strlen(HTTP_HEADER1)+strlen(tmp_http_len)+strlen(HTTP_HEADER2);
#endif
addon_event(ON_SEND_DATA);
if (gsm_get_modem_status() == 4) {
debug_print(F("gsm_send_http(): call interrupted"));
return 0; // abort
}
debug_print(F("gsm_send_http(): Length of header packet:"));
debug_print(tmp_len);
//sending header packet to remote host
if (!gsm_send_begin(tmp_len)) {
debug_print(F("gsm_send_http(): send refused"));
return 0; // abort
}
//sending header
#ifdef HTTP_USE_GET
gsm_port.print(HTTP_HEADER0);
debug_print(F("gsm_send_http(): Sending GET params"));
debug_print(F("gsm_send_http(): Sending IMEI and Key"));
debug_print(config.imei);
// don't disclose the key
#else
gsm_port.print(HTTP_HEADER1);
gsm_port.print(tmp_http_len);
gsm_port.print(HTTP_HEADER2);
if (!gsm_send_done()) {
debug_print(F("gsm_send_http(): send error"));
return 0; // abort
}
//validate header delivery
gsm_validate_tcp();
addon_event(ON_SEND_DATA);
if (gsm_get_modem_status() == 4) {
debug_print(F("gsm_send_http(): call interrupted"));
return 0; // abort
}
debug_print(F("gsm_send_http(): Sending IMEI and Key"));
debug_print(config.imei);
// don't disclose the key
//sending imei and key first
if (!gsm_send_begin(13+strlen(config.imei)+strlen(config.key))) {
debug_print(F("gsm_send_http(): send refused"));
return 0; // abort
}
#endif
gsm_port.print(HTTP_PARAM_IMEI "=");
gsm_port.print(config.imei);
gsm_port.print("&" HTTP_PARAM_KEY "=");
gsm_port.print(config.key);
gsm_port.print("&" HTTP_PARAM_DATA "=");
if (!gsm_send_done()) {
debug_print(F("gsm_send_http(): send error"));
return 0; // abort
}
//validate header delivery
gsm_validate_tcp();
debug_print(F("gsm_send_http(): Sending body"));
int tmp_ret = gsm_send_data_current();
#ifdef HTTP_USE_GET
if (tmp_ret) {
gsm_validate_tcp();
// finish sending headers
tmp_len = strlen(HTTP_HEADER1)+strlen(HTTP_HEADER2);
addon_event(ON_SEND_DATA);
if (gsm_get_modem_status() == 4) {
debug_print(F("gsm_send_http(): call interrupted"));
return 0; // abort
}
debug_print(F("gsm_send_http(): Length of header packet:"));
debug_print(tmp_len);
//sending header packet to remote host
if (!gsm_send_begin(tmp_len)) {
debug_print(F("gsm_send_http(): send refused"));
return 0; // abort
}
gsm_port.print(HTTP_HEADER1);
gsm_port.print(HTTP_HEADER2);
if (!gsm_send_done()) {
debug_print(F("gsm_send_http(): send error"));
return 0; // abort
}
}
#endif
debug_print(F("gsm_send_http(): data sent."));
return tmp_ret;
}
int gsm_send_data_current() {
// avoid large buffer on the stack (not reentrant)
static char buf[PACKET_SIZE];
debug_print(F("gsm_send_data_current(): sending data."));
debug_print(data_current);
int data_len = strlen(data_current);
int chunk_len = 0;
int chunk_pos = 0;
debug_print(F("gsm_send_data_current(): Body packet size:"));
debug_print(data_len);
for(int i=0; i<data_len; ) {
int done = url_encoded_strlcpy(buf, sizeof(buf), &data_current[i]);
i += done;
chunk_len = strlen(buf);
addon_event(ON_SEND_DATA);
if (gsm_get_modem_status() == 4) {
debug_print(F("gsm_send_data_current(): call interrupted"));
return 0; // abort
}
// start chunk
debug_print(F("gsm_send_data_current(): Sending data chunk:"));
debug_print(chunk_pos);
debug_print(F("gsm_send_data_current(): chunk length:"));
debug_print(chunk_len);
//sending chunk
if (!gsm_send_begin(chunk_len)) {
debug_print(F("gsm_send_data_current(): send refused"));
return 0; // abort
}
//sending data
gsm_port.print(buf);
debug_print(buf);
// end chunk
if (!gsm_send_done()) {
debug_print(F("gsm_send_data_current(): send error"));
return 0; // abort
}
chunk_pos += chunk_len;
//validate previous transmission
gsm_validate_tcp();
}
debug_print(F("gsm_send_data_current(): completed"));
return 1;
}
int gsm_send_data() {
int ret_tmp = 0;
//send 2 ATs
gsm_send_at();
//make sure there is no connection
gsm_disconnect();
addon_event(ON_SEND_STARTED);
//opening connection
ret_tmp = gsm_connect();
if(ret_tmp == 1) {
//connection opened, just send data
#if SEND_RAW
ret_tmp = gsm_send_data_current();
#else
// send data, if ok then parse reply
ret_tmp = gsm_send_http_current() && parse_receive_reply();
#endif
}
gsm_disconnect(); // always
if(ret_tmp) {
gsm_send_failures = 0;
addon_event(ON_SEND_COMPLETED);
} else {
debug_print(F("Error, can not send data or no connection."));
gsm_send_failures++;
addon_event(ON_SEND_FAILED);
}
if(GSM_SEND_FAILURES_REBOOT > 0 && gsm_send_failures >= GSM_SEND_FAILURES_REBOOT) {
power_reboot = 1;
}
return ret_tmp;
}
// update and return index to modem_reply buffer
int gsm_read_line(int index = 0) {
char inChar = 0; // Where to store the character read
long last = millis();
do {
if(gsm_port.available()) {
inChar = gsm_port.read(); // always read if available
last = millis();
if(index < sizeof(modem_reply)-1) { // One less than the size of the array
modem_reply[index] = inChar; // Store it
index++; // Increment where to write next
if(index == sizeof(modem_reply)-1 || (inChar == '\n')) { //some data still available, keep it in serial buffer
break;
}
}
}
} while((signed long)(millis() - last) < 10); // allow some inter-character delay
modem_reply[index] = '\0'; // Null terminate the string
return index;
}
// use fullBuffer != 0 if you want to read multiple lines
void gsm_get_reply(int fullBuffer) {
//get reply from the modem
int index = 0, end = 0;
do {
end = gsm_read_line(index);
if (end > index)
index = end;
else
break;
} while(fullBuffer && index < sizeof(modem_reply)-1);
if(index > 0) {
debug_print(F("Modem Reply:"));
debug_print(modem_reply);
addon_event(ON_MODEM_REPLY);
}
}
// use allowOK = 0 if OK comes before the end of the modem reply
void gsm_wait_for_reply(int allowOK, int fullBuffer) {
gsm_wait_for_reply(allowOK, fullBuffer, GSM_MODEM_COMMAND_TIMEOUT);
}
void gsm_wait_for_reply(int allowOK, int fullBuffer, int maxseconds) {
unsigned long timeout = millis();
modem_reply[0] = '\0';
int ret = 0;
//get reply from the modem
int index = 0, end = 0;
do {
if (fullBuffer) //keep past lines
index = end;
else // overwrite
index = 0;
end = gsm_read_line(index);
if(end > index) {
debug_print(F("Modem Line:"));
debug_print(&modem_reply[index]);
addon_event(ON_MODEM_REPLY);
if (gsm_is_final_result(&modem_reply[index], allowOK)) {
ret = 1;
break;
}
} else if ((signed long)(millis() - timeout) > (maxseconds * 1000)) {
break;
} else {
status_delay(50);
}
} while(index < sizeof(modem_reply)-1);
if (ret == 0) {
debug_print(F("Warning: timed out waiting for last modem reply"));
}
if(index > 0) {
debug_print(F("Modem Reply:"));
debug_print(modem_reply);
}
// check that modem is actually alive and sending replies to commands
if (modem_reply[0] == 0) {
debug_print(F("Reply failure count:"));
gsm_reply_failures++;
debug_print(gsm_reply_failures);
} else {
gsm_reply_failures = 0;
}
if (GSM_REPLY_FAILURES_REBOOT > 0 && gsm_reply_failures >= GSM_REPLY_FAILURES_REBOOT) {
reboot(); // reboot immediately
}
}
int gsm_is_final_result(const char* reply, int allowOK) {
int reply_len = strlen(reply);
// debug_print(allowOK);
// debug_print(reply_len);
#define STARTS_WITH(b) ( reply_len >= strlen(b) && strncmp(reply, (b), strlen(b)) == 0)
#define ENDS_WITH(b) ( reply_len >= strlen(b) && strcmp(reply + reply_len - strlen(b), (b)) == 0)
#define CONTAINS(b) ( reply_len >= strlen(b) && strstr(reply, (b)) != NULL)
if(allowOK && ENDS_WITH("\r\nOK\r\n")) {
return true;
}
if(allowOK && STARTS_WITH("OK\r\n")) {
return true;
}
if(STARTS_WITH("+CME ERROR:")) {
return true;
}
if(STARTS_WITH("+CMS ERROR:")) {
return true;
}
if(STARTS_WITH("+QIRD:")) {
return true;
}
if(STARTS_WITH("+QISTATE: ")) {
return true;
}
if(STARTS_WITH("> ")) {
return true;
}
if(STARTS_WITH("ALREADY CONNECT\r\n")) {
return true;
}
if(STARTS_WITH("BUSY\r\n")) {
return true;
}
if(STARTS_WITH("CONNECT\r\n")) {
return true;
}
if(ENDS_WITH("CONNECT OK\r\n")) {
return true;
}
if(ENDS_WITH("CONNECT FAIL\r\n")) {
return true;
}
if(STARTS_WITH("CLOSED\r\n")) {
return true;
}
if(ENDS_WITH("CLOSE OK\r\n")) {
return true;
}
if(STARTS_WITH("DEACT OK\r\n")) {
return true;
}
if(STARTS_WITH("ERROR")) {
return true;
}
if(STARTS_WITH("NO ANSWER\r\n")) {
return true;
}
if(STARTS_WITH("NO CARRIER\r\n")) {
return true;
}
if(STARTS_WITH("NO DIALTONE\r\n")) {
return true;
}
if(STARTS_WITH("SEND OK\r\n")) {
return true;
}
if(STARTS_WITH("SEND FAIL\r\n")) {
return true;
}
if(STARTS_WITH("STATE: ")) {
return true;
}
return false;
}
void gsm_debug() {
gsm_port.print("AT+QLOCKF=?");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+QBAND?");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+CGMR");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+CGMM");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+CGSN");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+CREG?");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+CSQ");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+QENG?");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+COPS?");
gsm_port.print("\r");
status_delay(2000);
gsm_get_reply(0);
gsm_port.print("AT+COPS=?");
gsm_port.print("\r");
status_delay(6000);
gsm_get_reply(0);
}
#ifdef KNOWN_APN_LIST
#if KNOWN_APN_SCAN_MODE < 0 || KNOWN_APN_SCAN_MODE > 2
#error Invalid option KNOWN_APN_SCAN_MODE in tracker.h
#endif
// Use automatic APN configuration
int gsm_scan_known_apn()
{
typedef struct
{
const char *apnname;
const char *user;
const char *pass;
//const char *servicename; // not required, for further expansion and requirement
//const char *value_str; // not required, for further expansion and requirement
} APNSET;
static char apn[sizeof(config.apn)];
static char user[sizeof(config.user)];
static char pwd[sizeof(config.pwd)];
strlcpy(apn, config.apn, sizeof(config.apn));
strlcpy(user, config.user, sizeof(config.user));
strlcpy(pwd, config.pwd, sizeof(config.pwd));
#define KNOWN_APN(apn,usr,pwd,isp,nul) { apn, usr, pwd/*, isp, nul*/ },
static const APNSET apnlist[] =
{
KNOWN_APN_LIST
// last element must be the current APN config
KNOWN_APN(apn, user, pwd, "", NULL)
};
#undef KNOWN_APN
enum { KNOWN_APN_COUNT = sizeof(apnlist)/sizeof(*apnlist) };
int ret = 0;
debug_print(F("gsm_scan_known_apn() started"));
//try to connect multiple times
for (int apn_num = 0; apn_num < KNOWN_APN_COUNT; ++apn_num)
{
debug_port.print(F("Testing APN: "));
debug_print(config.apn);
#if KNOWN_APN_SCAN_MODE < 2
if (gsm_get_connection_status() >= 0)
#endif
{
#if KNOWN_APN_SCAN_MODE > 0
data_current[0] = 0;
ret = gsm_send_data();
#else
ret = 1;
#endif
}
if (ret == 1) {
debug_print(F("gsm_scan_known_apn(): found valid APN settings"));
break;
}
// try next APN on the list
strlcpy(config.apn, apnlist[apn_num].apnname, sizeof(config.apn));
strlcpy(config.user, apnlist[apn_num].user, sizeof(config.user));
strlcpy(config.pwd, apnlist[apn_num].pass, sizeof(config.pwd));
#if KNOWN_APN_SCAN_USE_RESET
//restart GSM with new config
gsm_off(0);
gsm_setup();
#else
// apply new config
gsm_set_apn();
#endif
}
// the last APN in the array is not tested and it's applied only as default
debug_print(F("gsm_scan_known_apn() completed"));
return ret;
}
#endif // KNOWN_APN_LIST
int gsm_get_queclocator()
{
#if GSM_USE_QUECLOCATOR_TIMEOUT > 0
debug_print(F("gsm_get_queclocator() started"));
gsm_port.print("AT+QCELLLOC=1\r");
gsm_wait_for_reply(1,1,GSM_USE_QUECLOCATOR_TIMEOUT);
char* tmp = strstr(modem_reply, "+QCELLLOC:");
if (tmp == NULL)
return 0;
tmp += 10; //strlen("+QCELLLOC:");
char* lon = strtok(tmp, " ,\r");
char* lat = strtok(NULL, " ,\r");
if (lon != NULL && lat != NULL)
{
strlcpy(lon_current, lon, sizeof(lon_current));
strlcpy(lat_current, lat, sizeof(lat_current));
return 1;
}
#endif
debug_print(F("gsm_get_queclocator() completed"));
return 0;
}
#ifdef GSM_USE_NTP_SERVER
void gsm_ntp_update()
{
debug_print(F("gsm_ntp_update() started"));
gsm_port.print(AT_NTP "\"");
gsm_port.print(GSM_USE_NTP_SERVER);
gsm_port.print("\"\r");
gsm_wait_for_reply(1,0); // wait OK
// NOTE: default timeout does not guarantee we get result here
// but receiving it asynchronously later should not be a problem
long last = millis();
while (!gsm_port.available() && (signed long)(millis() - last) < GSM_NTP_COMMAND_TIMEOUT * 1000)
status_led();
gsm_get_reply(1);
char* tmp = strstr(modem_reply, "+QNTP:");
if (tmp != NULL)
tmp = strtok(tmp + 6, " \r");
if (tmp != NULL && *tmp == '0')
gsm_clock_was_set = true;
debug_print(F("gsm_ntp_update() completed"));
}
#endif
================================================
FILE: OpenTracker/led.ino
================================================
//blink led
int led_interval = 1000; //interval at which to blink status led (milliseconds)
void status_led() {
//blink led
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > led_interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if(ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
// set the LED with the ledState of the variable
digitalWrite(PIN_POWER_LED, ledState);
}
}
void status_delay(long ms) {
// delay and update status led
status_led();
if (ms <= 0)
return;
long n = ms & 63;
ms >>= 6;
while (ms--) {
delay(64);
status_led();
}
delay(n);
}
void blink_start() {
//blink start
for(int i = 0; i < 6; i++) {
if(ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
digitalWrite(PIN_POWER_LED, ledState); // set the LED on
delay(200);
}
}
void blink_debug() {
//blink start
for(int i = 0; i < 50; i++) {
if(ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
digitalWrite(PIN_POWER_LED, ledState); // set the LED on
delay(200);
}
}
void blink_got_gps() {
//blink start
for(int i = 0; i < 4; i++) {
if(ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
digitalWrite(PIN_POWER_LED, ledState); // set the LED on
delay(100);
}
}
================================================
FILE: OpenTracker/parse.ino
================================================
//parse remote commands from server
int parse_receive_reply() {
//receive reply from modem and parse it
int ret = 0;
int len = 0;
byte header = 0;
int resp_code = 0;
char *tmp;
char *tmpcmd;
char cmd[100] = ""; //remote commands stored here
debug_print(F("parse_receive_reply() started"));
addon_event(ON_RECEIVE_STARTED);
if (gsm_get_modem_status() == 4) {
debug_print(F("parse_receive_reply(): call interrupted"));
return 0; // abort
}
for(int i=0;i<50;i++) {
#if MODEM_UG96
gsm_get_reply(1); //flush buffer
// query unread length
gsm_port.print(AT_RECEIVE "0\r");
gsm_wait_for_reply(1,0);
tmp = strstr(modem_reply, "+QIRD:");
if(tmp!=NULL) {
tmp = strtok(modem_reply, ",");
for (int k=0; k<2; ++k) {
tmp = strtok(NULL, ",");
}
if (tmp != NULL && atoi(tmp) == 0) {
// no more data to read
if (gsm_get_connection_status() != 1)
break; // exit if no more connected
}
}
#endif
gsm_get_reply(1); //flush buffer
// read server reply
gsm_port.print(AT_RECEIVE "100");
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
//do we have data?
tmp = strstr(modem_reply, "ERROR");
if(tmp!=NULL) {
debug_print(F("No more data available."));
break;
}
tmp = strstr(modem_reply, "+QIRD:");
if(tmp!=NULL) {
#if MODEM_UG96
tmp += strlen("+QIRD:");
#else
tmp = strstr(modem_reply, PROTO ","); //get data length
if(tmp!=NULL)
tmp += strlen(PROTO ",");
#endif
}
if(tmp==NULL) {
// no data yet, keep looking
addon_delay(500);
continue;
}
// read data length
len = atoi(tmp);
debug_print(len);
// read full buffer (data)
gsm_get_reply(1);
if(len==0) {
// no data yet, keep looking
addon_delay(500);
continue;
}
// remove trailing modem response (OK)
if (len < sizeof(modem_reply) - 1)
modem_reply[len] = '\0';
else
debug_print(F("Warning: data exceeds modem receive buffer!"));
debug_print(header);
if (header == 0) {
tmp = strstr(modem_reply, "HTTP/1.");
if(tmp!=NULL) {
debug_print(F("Found response"));
header = 1;
resp_code = atoi(&tmp[9]);
debug_print(resp_code);
#if PARSE_IGNORE_COMMANDS && PARSE_IGNORE_EOF
// optimize and close connection earlier (without reading whole reply)
break;
#endif
} else {
debug_print(F("Not and HTTP response!"));
break;
}
} else if (header == 1) {
// looking for end of headers
tmp = strstr(modem_reply, "\r\n\r\n");
if(tmp!=NULL) {
debug_print(F("End of header found!"));
header = 2;
//all data from this packet and all next packets can be commands
tmp += strlen("\r\n\r\n");
strlcpy(cmd, tmp, sizeof(cmd));
}
} else {
// packet contains only response body
strlcat(cmd, modem_reply, sizeof(cmd));
}
addon_event(ON_RECEIVE_DATA);
if (gsm_get_modem_status() == 4) {
debug_print(F("parse_receive_reply(): call interrupted"));
return 0; // abort
}
}
#if SEND_RAW
debug_print(F("RAW data mode enabled, not checking whether the packet was received or not."));
ret = 1;
#else // HTTP
// any http reply is valid by default
if (header != 0)
ret = 1;
#ifdef PARSE_ACCEPTED_RESPONSE_CODES
#define RESP_CODE(x) && (resp_code != (x))
// apply restrictions to response code
if (1 PARSE_ACCEPTED_RESPONSE_CODES)
ret = 0;
#undef RESP_CODE(x)
#endif
#if !PARSE_IGNORE_EOF
// valid only if "#eof" received
tmp = strstr(cmd, "#eof");
if(tmp==NULL)
ret = 0;
#endif
#if !PARSE_IGNORE_COMMANDS
parse_cmd(cmd);
#endif
#endif // SEND_RAW
if (ret) {
//all data was received by server
debug_print(F("Data was fully received by the server."));
addon_event(ON_RECEIVE_COMPLETED);
} else {
debug_print(F("Data was not received by the server."));
addon_event(ON_RECEIVE_FAILED);
}
debug_print(F("parse_receive_reply() completed"));
return ret;
}
void parse_cmd(char *cmd) {
//parse commands info received from the server
char *tmp;
debug_print(F("parse_cmd() started"));
debug_print(F("Received commands:"));
debug_print(cmd);
//check for settime command (#t:13/01/11,09:43:50+00)
tmp = strstr((cmd), "#t:");
if(tmp!=NULL) {
debug_print(F("Found settime command."));
tmp += 3; //strlen("#t:");
tmp = strtok(tmp, "\r\n"); //all commands end with \n
debug_print(tmp);
if(strlen(tmp) == 20 && tmp[2] == '/' && tmp[5] == '/' && tmp[8] == ','
&& tmp[11] == ':' && tmp[14] == ':' && tmp[17] == '+') {
debug_print(F("Valid time string found."));
//setting current time
strlcpy(time_char, tmp, sizeof(time_char));
gsm_set_time();
}
}
debug_print(F("parse_cmd() completed"));
}
================================================
FILE: OpenTracker/reboot.ino
================================================
void reboot() {
debug_print(F("reboot() started"));
//reset GPS
gps_off();
//emergency power off GSM
gsm_off(1);
//disable USB to allow reboot
//serial monitor on the PC won't work anymore if you don't close it before reset completes
//otherwise, close the serial monitor, detach the USB cable and connect it again
// debug_port.end() does nothing, manually disable USB
UDD_Detach(); // detach from Host
cpu_irq_disable();
rstc_start_software_reset(RSTC);
for (;;)
{
// If we do not keep the watchdog happy and it times out during this wait,
// the reset reason will be wrong when the board starts the next time around.
WDT_Restart(WDT);
}
}
void usb_console_disable() {
cpu_irq_disable();
// debug_port.end() does nothing, manually disable USB serial console
UDD_Detach(); // detach from Host
// de-init procedure (reverses UDD_Init)
otg_freeze_clock();
otg_disable_pad();
otg_disable();
pmc_disable_udpck();
pmc_disable_upll_clock();
pmc_disable_periph_clk(ID_UOTGHS);
NVIC_DisableIRQ((IRQn_Type) ID_UOTGHS);
NVIC_ClearPendingIRQ((IRQn_Type) ID_UOTGHS);
cpu_irq_enable();
}
void usb_console_restore() {
if (!Is_otg_enabled()) {
// re-initialize USB
UDD_Init();
UDD_Attach();
}
}
// override for lower power consumption (wait for interrupt)
void yield(void) {
pmc_enable_sleepmode(0);
}
void cpu_slow_down() {
addon_event(ON_CLOCK_PAUSE);
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // temp disable interrupt
// slow down CPU
pmc_mck_set_prescaler(PMC_MCKR_PRES_CLK_64); // master clock prescaler
// update timer settings
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000);
addon_event(ON_CLOCK_RESUME);
}
void cpu_full_speed() {
addon_event(ON_CLOCK_PAUSE);
// re-init clocks to full speed
SystemInit();
SysTick_Config(SystemCoreClock / 1000);
addon_event(ON_CLOCK_RESUME);
}
void enter_low_power() {
debug_print(F("enter_low_power() started"));
addon_event(ON_DEVICE_STANDBY);
// enter standby/sleep mode
gps_standby();
gps_close();
gsm_standby();
gsm_close();
cpu_slow_down();
debug_print(F("enter_low_power() completed"));
}
void exit_low_power() {
debug_print(F("exit_low_power() started"));
cpu_full_speed();
// enable serial ports
gsm_open();
gsm_wakeup();
gps_open();
gps_wakeup();
addon_event(ON_DEVICE_WAKEUP);
debug_print(F("exit_low_power() completed"));
}
void kill_power() {
debug_print(F("kill_power() called"));
addon_event(ON_DEVICE_KILL);
// save as much power as possible
gps_off();
gps_close();
gsm_off(1);
gsm_close();
usb_console_disable();
// slow down cpu even more
pmc_switch_mainck_to_fastrc(CKGR_MOR_MOSCRCF_4_MHz);
cpu_slow_down();
cpu_irq_disable();
// turn off LED and CAN
digitalWrite(PIN_POWER_LED, HIGH);
pinMode(PIN_CAN_RS, OUTPUT);
digitalWrite(PIN_CAN_RS, HIGH);
// disable peripherals and use wait mode
pmc_set_writeprotect(0);
pmc_disable_all_periph_clk();
pmc_enable_waitmode();
for(;;) // freeze in low power mode
reboot();
}
================================================
FILE: OpenTracker/settings.ino
================================================
void storage_config_fill() {
//fill settings storage space with zeros on very first run
debug_print(F("storage_config_fill() started"));
for(int i = STORAGE_CONFIG_PAGE;i<STORAGE_DATA_START;i++) {
debug_print(F("storage_config_fill(): filling address with 0"));
debug_print(i);
dueFlashStorage.write(i,0);
}
//set first run flag
dueFlashStorage.write(STORAGE_FIRST_RUN_PAGE,1);
debug_print(F("storage_config_fill() finished"));
}
void settings_load() {
//load all settings from EEPROM
byte tmp;
debug_print(F("settings_load() started"));
byte first_run = dueFlashStorage.read(STORAGE_FIRST_RUN_PAGE);
debug_print(F("settings_load(): First run flag:"));
debug_print(first_run);
if(first_run != 1) {
//first run was not set, this is first even run of the board use config from tracker.h
debug_print(F("settings_load(): setting defaults from config"));
config.interval = INTERVAL;
config.interval_send = INTERVAL_SEND;
config.powersave = POWERSAVE;
config.alarm_on = DEFAULT_ALARM_ON;
config.queclocator = QUECLOCATOR;
config.debug = DEBUG ? 1 : 0;
strlcpy(config.key, KEY, sizeof(config.key));
strlcpy(config.sms_key, SMS_KEY, sizeof(config.sms_key));
strlcpy(config.sim_pin, SIM_PIN, sizeof(config.sim_pin));
strlcpy(config.apn, DEFAULT_APN, sizeof(config.apn));
strlcpy(config.user, DEFAULT_USER, sizeof(config.user));
strlcpy(config.pwd, DEFAULT_PASS, sizeof(config.pwd));
strlcpy(config.alarm_phone, DEFAULT_ALARM_SMS, sizeof(config.alarm_phone));
debug_print(F("settings_load(): set config.interval:"));
debug_print(config.interval);
debug_print(config.apn);
addon_event(ON_SETTINGS_DEFAULT);
dueFlashStorage.write(STORAGE_FIRST_RUN_PAGE,1); //set first run flag
settings_save(); //save settings
} else {
debug_print(F("settings_load(): no a first run, loading settings."));
byte* b = dueFlashStorage.readAddress(STORAGE_CONFIG_PAGE); // byte array which is read from flash at adress
memcpy(&config, b, sizeof(settings)); // copy byte array to temporary struct
addon_event(ON_SETTINGS_LOAD);
}
//setting defaults in case nothing loaded
debug_print(F("settings_load(): config.interval:"));
debug_print(config.interval);
if((config.interval == -1) || (config.interval < 0) || (config.interval > 5184000)) {
debug_print(F("settings_load(): interval not found, setting default"));
config.interval = INTERVAL;
debug_print(F("settings_load(): set config.interval:"));
debug_print(config.interval);
}
//interval send
debug_print(F("settings_load(): config.interval_send:"));
debug_print(config.interval_send);
if((config.interval_send == -1) || (config.interval_send < 0) || (config.interval_send > 100)) {
debug_print(F("settings_load(): interval_send not found, setting default"));
config.interval_send = INTERVAL_SEND;
debug_print(F("settings_load(): set config.interval_send:"));
debug_print(config.interval_send);
}
//powersave
debug_print(F("settings_load(): config.powersave:"));
debug_print(config.powersave);
if((config.powersave != 1) && (config.powersave != 0)) {
debug_print(F("settings_load(): powersave not found, setting default"));
config.powersave = POWERSAVE;
debug_print(F("settings_load(): set config.powersave:"));
debug_print(config.powersave);
}
//powersave
debug_print(F("settings_load(): config.debug:"));
debug_print(config.debug);
if((config.debug != 1) && (config.debug != 0)) {
debug_print(F("settings_load(): debug not found, setting default"));
config.debug = DEBUG ? 1 : 0;
debug_print(F("settings_load(): set config.debug:"));
debug_print(config.debug);
}
#if GSM_USE_QUECLOCATOR_TIMEOUT > 0
//queclocator
debug_print(F("settings_load(): config.queclocator:"));
debug_print(config.queclocator);
if((config.queclocator != 1) && (config.queclocator != 0)) {
debug_print(F("settings_load(): queclocator not found, setting default"));
config.queclocator = QUECLOCATOR;
debug_print(F("settings_load(): set config.queclocator:"));
debug_print(config.queclocator);
}
#endif
tmp = config.key[0];
if(tmp == 255) { //this check is not sufficient
debug_print(F("settings_load(): key not found, setting default"));
strlcpy(config.key, KEY, sizeof(config.key));
}
tmp = config.sms_key[0];
if(tmp == 255) { //this check is not sufficient
debug_print("settings_load(): SMS key not found, setting default");
strlcpy(config.sms_key, SMS_KEY, sizeof(config.sms_key));
}
tmp = config.sim_pin[0];
if(tmp == 255) { //this check is not sufficient
debug_print("settings_load(): SIM pin not found, setting default");
strlcpy(config.sim_pin, SIM_PIN, sizeof(config.sim_pin));
}
tmp = config.apn[0];
if(tmp == 255) {
debug_print("settings_load(): APN not set, setting default");
strlcpy(config.apn, DEFAULT_APN, sizeof(config.apn));
}
tmp = config.user[0];
if(tmp == 255) {
debug_print("settings_load(): APN user not set, setting default");
strlcpy(config.user, DEFAULT_USER, sizeof(config.user));
}
tmp = config.pwd[0];
if(tmp == 255) {
debug_print("settings_load(): APN password not set, setting default");
strlcpy(config.pwd, DEFAULT_PASS, sizeof(config.pwd));
}
tmp = config.alarm_phone[0];
if(tmp == 255) {
debug_print("settings_load(): Alarm SMS number not set, setting default");
strlcpy(config.alarm_phone, DEFAULT_ALARM_SMS, sizeof(config.alarm_phone));
}
debug_print(F("settings_load() finished"));
}
void settings_save() {
debug_print(F("settings_save() started"));
//save all settings to flash
byte b2[sizeof(settings)]; // create byte array to store the struct
memcpy(b2, &config, sizeof(settings)); // copy the struct to the byte array
dueFlashStorage.write(STORAGE_CONFIG_PAGE, b2, sizeof(settings)); // write byte array to flash
addon_event(ON_SETTINGS_SAVE);
debug_print(F("settings_save() finished"));
}
================================================
FILE: OpenTracker/sms.ino
================================================
//check SMS
void sms_check() {
int msg_count = 30; // default
char *tmp = NULL, *tmpcmd = NULL;
char phone[32] = "";
char msg[160];
debug_print(F("sms_check() started"));
gsm_get_reply(1); // clear buffer
gsm_port.print("AT+CPMS?\r");
gsm_wait_for_reply(1,1);
tmp = strstr(modem_reply, "+CPMS: ");
if(tmp!=NULL) {
tmp = strtok(tmp + 7, ",\"");
if(tmp!=NULL) {
tmp = strtok(NULL, ",\"");
if(tmp!=NULL) {
tmp = strtok(NULL, ",\"");
if(tmp!=NULL) {
msg_count = atoi(tmp);
debug_print(F("SMS storage total:"));
debug_print(msg_count);
}
}
}
}
for(int i=1;i<=msg_count;i++) {
gsm_get_reply(1); // clear buffer
gsm_port.print("AT+CMGR=");
gsm_port.print(i);
gsm_port.print("\r");
gsm_wait_for_reply(1,1);
//phone info will look like this: +CMGR: "REC READ","+436601601234","","5 12:13:17+04"
//phone will start from ","+ and end with ",
tmp = strstr(modem_reply, "+CMGR:");
if(tmp!=NULL) {
tmp = strstr(modem_reply, "READ\",\"");
if(tmp!=NULL) {
// find start of commands
tmpcmd = strstr(modem_reply, "\r\n#");
// get sender phone number
tmp += 7;
tmp = strtok(tmp, "\",\"");
if(tmp!=NULL) {
strlcpy(phone, tmp, sizeof(phone));
debug_print(F("Phone:"));
debug_print(phone);
}
// find end of commands
tmp = strstr(tmpcmd, "\r\nOK\r\n");
if(tmpcmd!=NULL && tmp!=NULL) {
// make a local copy (since this is coming from modem_reply, it will be overwritten)
*tmp = '\0';
strlcpy(msg, tmpcmd + 3, sizeof(msg));
//next data is probably command till \r
//all data before "," is sms password, the rest is command
debug_print(F("SMS command found"));
debug_print(msg);
sms_cmd(msg, phone);
}
}
debug_print(F("Delete message"));
gsm_get_reply(1); // clear buffer
gsm_port.print("AT+CMGD=");
gsm_port.print(i);
gsm_port.print("\r");
gsm_wait_for_reply(1,0);
}
status_delay(20);
}
debug_print(F("sms_check() completed"));
}
void sms_cmd(char *msg, char *phone) {
char *tmp, *tmp1;
int i=0;
debug_print(F("sms_cmd() started"));
//command separated by "," format: password,command=value
tmp = strtok(msg, ",");
while (tmp != NULL && i < 10) {
if(i == 0) {
bool auth = true;
#if SMS_CHECK_INCLUDE_IMEI
//check IMEI
tmp1 = strtok(tmp, "@");
if(tmp1 != NULL)
tmp1 = strtok(NULL, ",");
if(tmp1 == NULL || strcmp(tmp1, config.imei) != 0)
auth = false;
else
#endif
//checking password
if(strcmp(tmp, config.sms_key) != 0)
auth = false;
if(auth) {
debug_print(F("sms_cmd(): SMS password accepted, executing commands from"));
debug_print(phone);
} else {
debug_print(F("sms_cmd(): SMS password failed, ignoring commands"));
break;
}
} else {
sms_cmd_run(tmp, phone);
}
tmp = strtok(NULL, ",\r\n");
i++;
}
debug_print(F("sms_cmd() completed"));
}
void sms_cmd_run(char *cmd, char *phone) {
char *tmp = NULL;
char *tmpcmd = NULL;
long val;
char msg[160];
debug_print(F("sms_cmd_run() started"));
//checking what command to execute
cmd = strtok_r(cmd, "=", &tmpcmd);
if(cmd != NULL) {
//get argument (if any)
tmp = strtok_r(NULL, ",\r", &tmpcmd);
}
debug_print(cmd);
debug_print(tmp);
//set APN
if(strcmp(cmd,"apn") == 0) {
//setting new APN
debug_print(F("sms_cmd_run(): New APN:"));
debug_print(tmp);
//updating APN in config
strlcpy(config.apn, tmp, sizeof(config.apn));
debug_print(F("New APN configured:"));
debug_print(config.apn);
save_config=1;
power_reboot=1;
//send SMS reply
sms_send_msg("APN saved", phone);
}
else
//APN password
if(strcmp(cmd, "gprspass") == 0) {
//setting new APN pass
debug_print(F("sms_cmd_run(): New APN pass:"));
debug_print(tmp);
//updating in config
strlcpy(config.pwd, tmp, sizeof(config.pwd));
debug_print(F("New APN pass configured:"));
debug_print(config.pwd);
save_config=1;
power_reboot=1;
//send SMS reply
sms_send_msg("APN password saved", phone);
}
else
//APN username
if(strcmp(cmd, "gprsuser") == 0) {
//setting new APN
debug_print(F("sms_cmd_run(): New APN user:"));
debug_print(tmp);
//updating APN in config
strlcpy(config.user, tmp, sizeof(config.user));
debug_print(F("New APN user configured:"));
debug_print(config.user);
save_config=1;
power_reboot=1;
//send SMS reply
sms_send_msg("APN username saved", phone);
}
else
//SMS pass
if(strcmp(cmd, "smspass") == 0) {
//setting new APN
debug_print(F("sms_cmd_run(): New smspass:"));
debug_print(tmp);
//updating APN in config
strlcpy(config.sms_key, tmp, sizeof(config.sms_key));
debug_print(F("New sms_key configured:"));
debug_print(config.sms_key);
save_config=1;
//send SMS reply
sms_send_msg("SMS password saved", phone);
}
else
//PIN
if(strcmp(cmd, "pin") == 0) {
//setting new APN
debug_print(F("sms_cmd_run(): New pin:"));
debug_print(tmp);
//updating pin in config
strlcpy(config.sim_pin, tmp, sizeof(config.sim_pin));
debug_print(F("New sim_pin configured:"));
debug_print(config.sim_pin);
save_config=1;
power_reboot=1;
//send SMS reply
sms_send_msg("SIM pin saved", phone);
}
else
//alarm
if(strcmp(cmd, "alarm") == 0) {
//setting alarm
debug_print(F("sms_cmd_run(): Alarm:"));
debug_print(tmp);
if(strcmp(tmp, "off") == 0) {
config.alarm_on = 0;
} else if(strcmp(tmp, "on") == 0) {
config.alarm_on = 1;
}
//updating alarm phone
strlcpy(config.alarm_phone, phone, sizeof(config.alarm_phone));
debug_print(F("New alarm_phone configured:"));
debug_print(config.alarm_phone);
save_config = 1;
//send SMS reply
if(config.alarm_on) {
sms_send_msg("Alarm set ON", phone);
} else {
sms_send_msg("Alarm set OFF", phone);
}
}
else
//interval
if(strcmp(cmd, "int") == 0) {
//setting new APN
debug_print(F("sms_cmd_run(): New interval:"));
debug_print(tmp);
val = atol(tmp);
if(val > 0) {
//updating interval in config (convert to milliseconds)
config.interval = val*1000;
debug_print(F("New interval configured:"));
debug_print(config.interval);
save_config=1;
//send SMS reply
sms_send_msg("Interval saved", phone);
} else debug_print(F("sms_cmd_run(): invalid value"));
}
else
if(strcmp(cmd, "locate") == 0) {
debug_print(F("sms_cmd_run(): Locate command detected"));
if(LOCATE_COMMAND_FORMAT_IOS) {
snprintf(msg,sizeof(msg),"comgooglemaps://?center=%s,%s",lat_current,lon_current);
} else {
snprintf(msg,sizeof(msg),"https://maps.google.com/maps/place/%s,%s",lat_current,lon_current);
}
sms_send_msg(msg, phone);
}
else
if(strcmp(cmd, "tomtom") == 0) {
debug_print(F("sms_cmd_run(): TomTom command detected"));
snprintf(msg,sizeof(msg),"tomtomhome://geo:lat=%s&long=%s",lat_current,lon_current);
sms_send_msg(msg, phone);
}
else
if(strcmp(cmd, "data") == 0) {
debug_print(F("sms_cmd_run(): Data command detected"));
debug_print(tmp);
if(strcmp(tmp, "off") == 0) {
SEND_DATA = 0;
} else if(strcmp(tmp, "on") == 0) {
SEND_DATA = 1;
}
//send SMS reply
if(SEND_DATA) {
sms_send_msg("Data ON", phone);
} else {
sms_send_msg("Data OFF", phone);
}
}
else
if(strcmp(cmd, "getimei") == 0) {
debug_print(F("sms_cmd_run(): Get IMEI command detected"));
snprintf(msg,sizeof(msg),"IMEI: %s",config.imei);
sms_send_msg(msg, phone);
}
#if GSM_USE_QUECLOCATOR_TIMEOUT > 0
else
if(strcmp(cmd, "queclocator") == 0) {
//setting alarm
debug_print(F("sms_cmd_run(): QuecLocator"));
debug_print(tmp);
if(strcmp(tmp, "off") == 0) {
config.queclocator = 0;
save_config = 1;
} else if(strcmp(tmp, "on") == 0) {
config.queclocator = 1;
save_config = 1;
}
//send SMS reply
if(config.queclocator) {
sms_send_msg("QuecLocator is ON", phone);
} else {
sms_send_msg("QuecLocator is OFF", phone);
}
}
#endif
#if DEBUG
else
if(strcmp(cmd, "debug") == 0) {
debug_print(F("sms_cmd_run(): Debug command detected"));
debug_print(tmp);
if(strcmp(tmp, "off") == 0) {
config.debug = 0;
save_config = 1;
} else if(strcmp(tmp, "on") == 0) {
config.debug = 1;
save_config = 1;
}
//send SMS reply
if (config.debug == 1) {
usb_console_restore();
sms_send_msg("Debug ON", phone);
} else {
sms_send_msg("Debug OFF", phone);
usb_console_disable();
}
}
#endif
else
if(strcmp(cmd, "powersave") == 0) {
debug_print(F("sms_cmd_run(): Powersave command detected"));
debug_print(tmp);
if(strcmp(tmp, "off") == 0) {
config.powersave = 0;
save_config = 1;
} else if(strcmp(tmp, "on") == 0) {
config.powersave = 1;
save_config = 1;
}
//send SMS reply
if (config.powersave == 1) {
sms_send_msg("Powersave ON", phone);
} else {
sms_send_msg("Powersave OFF", phone);
}
}
else
addon_sms_command(cmd, tmp, phone);
debug_print(F("sms_cmd_run() completed"));
}
void sms_send_msg(const char *cmd, const char *phone) {
//send SMS message to number
debug_print(F("sms_send_msg() started"));
gsm_get_reply(1); // clear buffer
debug_print(F("Sending SMS to:"));
debug_print(phone);
debug_print(cmd);
gsm_port.print("AT+CMGS=\"");
gsm_port.print(phone);
gsm_port.print("\"\r");
gsm_wait_for_reply(0,0);
const char *tmp = strstr(modem_reply, ">");
if(tmp!=NULL) {
debug_print(F("Modem replied with >"));
gsm_port.print(cmd);
//sending ctrl+z
gsm_port.print("\x1A");
gsm_wait_for_reply(1,0);
}
debug_print(F("sms_send_msg() completed"));
}
================================================
FILE: OpenTracker/storage.ino
================================================
#define STORAGE_FREE_CHAR 255
void storage_save_current() {
debug_print(F("storage_save_current() started"));
int data_len = strlen(data_current) + 1; // include '\0' as block marker
//check for flash limit
if (logindex + data_len >= STORAGE_DATA_END) {
debug_print(F("storage_save_current(): flash memory is full, starting to overwrite"));
logindex = STORAGE_DATA_START;
}
//saving data_current to flash memory
dueFlashStorage.write(logindex, (byte*)data_current, data_len);
logindex += data_len;
//adding index marker, it will be overwritten with next flash write
dueFlashStorage.write(logindex, STORAGE_FREE_CHAR);
debug_print(F("storage_save_current() ended"));
//storage_dump();
}
void storage_get_index() {
//storage_dump();
debug_print(F("store_get_index() started"));
//scan flash for current log position (new log writes will continue from there)
byte *tmp = dueFlashStorage.readAddress(STORAGE_DATA_START);
byte *tmpend = dueFlashStorage.readAddress(STORAGE_DATA_END);
bool block = false;
while (tmp < tmpend) {
if (!block && (*tmp != STORAGE_FREE_CHAR))
block = true;
else if (block && (*tmp == STORAGE_FREE_CHAR)) {
//found log index
logindex = tmp - FLASH_START;
debug_print(F("store_get_index(): Found log position:"));
break; //we only need first found index
}
++tmp;
}
if (tmp >= tmpend) { // probable corruption, re-initialize
logindex = STORAGE_DATA_START;
dueFlashStorage.write(logindex, STORAGE_FREE_CHAR);
debug_print(F("store_get_index(): Not found, initialize log position:"));
}
debug_print(logindex);
debug_print(F("store_get_index() ended"));
}
void storage_send_logs(int really_send) {
int num_sent = 0;
debug_print(F("storage_send_logs() started"));
//check if some logs were saved
uint32_t sent_position = logindex; //empty set
byte *tmp = dueFlashStorage.readAddress(logindex);
byte *tmpend = dueFlashStorage.readAddress(STORAGE_DATA_END);
while (tmp < tmpend) {
if (*tmp != STORAGE_FREE_CHAR) {
//found sent position
sent_position = tmp - FLASH_START;
debug_print(F("storage_send_logs(): Found log sent position:"));
break; //we only need first found index
}
++tmp;
}
if (tmp >= tmpend) { // re-start from the beginning
tmp = dueFlashStorage.readAddress(STORAGE_DATA_START);
tmpend = dueFlashStorage.readAddress(logindex);
while (tmp < tmpend) {
if (*tmp != STORAGE_FREE_CHAR) {
//found sent position
sent_position = tmp - FLASH_START;
debug_print(F("storage_send_logs(): Found log sent position:"));
break; //we only need first found index
}
++tmp;
}
}
debug_print(sent_position);
if (sent_position != logindex) {
bool err = false;
do {
debug_print(F("storage_send_logs(): Sending data from:"));
debug_print(sent_position);
// read current block
strlcpy(data_current, (char*)dueFlashStorage.readAddress(sent_position), sizeof(data_current));
int data_len = strlen(data_current) + 1; // include string terminator (zero)
debug_print(F("Log data:"));
debug_print(data_current);
if (!really_send) {
sent_position += data_len;
} else {
// send block
if (gsm_send_data() == 1) {
debug_print(F("storage_send_logs(): Success, erase sent data"));
// erase block (after sent)
for (int k=0; k<data_len; ++k) {
dueFlashStorage.write(sent_position + k, STORAGE_FREE_CHAR);
}
sent_position += data_len;
// apply send limit
num_sent++;
if (STORAGE_MAX_SEND_OLD > 0 && num_sent >= STORAGE_MAX_SEND_OLD) {
debug_print(F("storage_send_logs(): reached send limit"));
break;
}
} else {
err = true;
break;
}
}
if (sent_position >= STORAGE_DATA_END) {
debug_print(F("storage_send_logs(): Wrap around sending data"));
sent_position = STORAGE_DATA_START;
}
} while (dueFlashStorage.read(sent_position) != STORAGE_FREE_CHAR);
if (!err) {
debug_print(F("storage_send_logs(): Logs were sent successfully"));
} else {
debug_print(F("storage_send_logs(): Error sending logs"));
}
} else {
debug_print(F("storage_send_logs(): No logs present"));
}
debug_print(F("storage_send_logs() ended"));
}
void storage_dump() {
debug_print(F("storage_dump() started"));
debug_port.print(F("start = "));
debug_print(STORAGE_DATA_START);
debug_port.print(F("end = "));
debug_print(STORAGE_DATA_END);
debug_port.print(F("logindex = "));
debug_print(logindex);
byte *tmp = dueFlashStorage.readAddress(STORAGE_DATA_START);
byte *tmpend = dueFlashStorage.readAddress(STORAGE_DATA_END);
int k=0;
char buf[10];
while (tmp < tmpend) {
if ((k & 31) == 0)
debug_port.println();
snprintf(buf, 10, "%02X", *tmp);
debug_port.print(buf);
++k;
++tmp;
}
debug_port.println();
debug_print(F("storage_dump() ended"));
}
================================================
FILE: OpenTracker/tracker.h.example
================================================
#define ADDON_INTERFACE 0 //non-zero if you have custom addon implementation
//OpenTracker config
#define DEBUG 1 //debug console (0=disabled, 1=send diangostic messages, 2=also accept test commands)
#define ALWAYS_ON 0 //set to 1 to make the tracker log even with the ignition off
//default settings (can be overwritten and stored in EEPRom)
#define INTERVAL 5000 //how often to collect data (milli sec, 600000 - 10 mins)
#define INTERVAL_SEND 4 //how many times to collect data before sending (times), sending interval interval*interval_send (4 default)
#define POWERSAVE 1 //enable power saving mode with engine off (turn off modem data session, slow down CPU)
#define KEY "cSQ88qShwC3" //key for connection, will be sent with every data transmission - max 12 chars
#define DATA_LIMIT 2500 //current data limit, data collected before sending to remote server can not exceed this
#define SMS_KEY "pass" //default password for SMS auth
#define SIM_PIN "" //default SIM PIN (none)
#define QUECLOCATOR 0 //default setting for QuecLocator fallback location provider
#define SMS_CHECK_INCLUDE_IMEI 0 // 0=check only password, 1=also check IMEI (format: #pass@imei,command=value)
#define SMS_CHECK_INTERVAL_COUNT 6 // 0=disable SMS check with engine off, 1-N=check every N cycles of interval time
#define SMS_CHECK_INTERVAL_ENGINE_RUNNING 2 // 0=disable SMS check with engine running, 1-N=check every N cycles of interval time
#define LOCATE_COMMAND_FORMAT_IOS 0 // 1=google maps links will be sent as comgooglemaps://, 0=use format https://maps.google...
#define GSM_USE_QUECLOCATOR_TIMEOUT 10 // 0=don't use QuecLocator, 1-300=timeout for server reply
#define GSM_USE_NTP_SERVER "pool.ntp.org" // use Network Time Protocol server for modem clock update (undefine to disable)
#define GSM_NTP_COMMAND_TIMEOUT 30 // max seconds to wait for NTP synchronization result
#define GSM_MODEM_COMMAND_TIMEOUT 10
#define GSM_SEND_FAILURES_REBOOT 15 // 0=disabled, increase to set the number of GSM data send failures that will trigger a reboot of the opentracker device
#define GSM_REPLY_FAILURES_REBOOT 10 // 0=disabled, increase to set the number of GSM reply failures that will trigger a reboot of the opentracker device
#define GSM_DISCONNECT_AFTER_SEND 0 // 0=keep modem data session active, only close TCP connection (default), 1=close data session after each send
#define GPS_COLLECT_TIMEOUT 4 // max time spent looking for a GPS fix (seconds)
#define ENGINE_RUNNING_LOG_FAST_AS_POSSIBLE 0 // 1=when the engine is running send interval is ignored
#define SEND_RAW 0 // enable to use the new raw tcp send method to minimise data use
#define SEND_RAW_INCLUDE_IMEI 1
#define SEND_RAW_INCLUDE_KEY 1
#define SEND_RAW_INCLUDE_TIMESTAMP 0
#define DATA_INCLUDE_GPS_DATE 1 // enable to include the GPS date in the POST string
#define DATA_INCLUDE_GPS_TIME 1 // enable to include the GPS time in the POST string
#define DATA_INCLUDE_LATITUDE 1 // enable to include latitude
#define DATA_INCLUDE_LONGITUDE 1 // enable to include longitude
#define DATA_INCLUDE_SPEED 1 // enable to include speed (km/h)
#define DATA_INCLUDE_ALTITUDE 1 // enable to include altitude
#define DATA_INCLUDE_HEADING 1 // enable to include heading
#define DATA_INCLUDE_HDOP 1 // enable to include hdop
#define DATA_INCLUDE_SATELLITES 1 // enable to include satellites
#define DATA_INCLUDE_BATTERY_LEVEL 0 // enable to include the battery level in the POST string
#define DATA_INCLUDE_IGNITION_STATE 0 // enable to include the ignition state in the POST string
#define DATA_INCLUDE_ENGINE_RUNNING_TIME 0 // enable to include the engine running time (in seconds) in the POST string
#define PARSE_IGNORE_EOF 0 // 0=consider transmission successful if "eof" included in response body (default)
#define PARSE_IGNORE_COMMANDS 0 // 0=parse commands included in response body (default)
// define this macro to accept only certain HTTP response codes (default is any)
//#define PARSE_ACCEPTED_RESPONSE_CODES RESP_CODE(200)
#define HTTP_USER_AGENT "OpenTracker 3.2"
//#define HTTP_USE_GET // leave commented for POST, uncomment for GET request
#define HTTP_PARAM_IMEI "imei"
#define HTTP_PARAM_KEY "key"
#define HTTP_PARAM_DATA "d"
#define PROTO "TCP"
#define HOSTNAME "updates.geolink.io"
#define HTTP_PORT "80"
#define URL "/index.php"
#define DEFAULT_APN "internet" //default APN
#define DEFAULT_USER "" //default APN user
#define DEFAULT_PASS "" //default APN pass
#define DEFAULT_ALARM_ON 0 //if active SMS will be sent on Ignition ON/OFF
#define DEFAULT_ALARM_SMS "" //default SMS text for alarm
#define PACKET_SIZE 1400 //TCP data chunk size, modem accept max 1460 bytes per send
#define PACKET_SIZE_DELIVERY 3000 //in case modem has this number of bytes undelivered, wait till sending new data (3000 bytes default, max sending TCP buffer is 7300)
#define CONNECT_RETRY 3 //how many times to retry connecting to remote server
#define CONNECT_TIMEOUT 15000 //how much time to wait for each connection attempt
#define STORAGE 1 //save logs in flash storage
#define STORAGE_FIRST_RUN_PAGE 0 //flash index to store flag for first run - very first page
#define STORAGE_CONFIG_PAGE 1 //flash index to store configuration - just after FIRST_RUN page
#define STORAGE_CONFIG_ADDON 512 //flash index to store addon configuration
#define STORAGE_DATA_START 1024 //starting flash index to store logs (first bytes reserved for configuration)
#define STORAGE_DATA_END 131071 //the last possible flash index (max 131072, one last byte must be reserved for marker)
//#define STORAGE_DATA_END 2048 //the last possible flash index (max 131072, one last byte must be reserved for marker)
#define STORAGE_MAX_SEND_OLD 5 //maximum number of old data to send from log
// List of APN configurations to scan at power-on/reset
// (undefine to use the default APN only)
//#define KNOWN_APN_LIST \
//KNOWN_APN("example.apn", "user", "pass", "ISP name", NULL) \
//RECORD FORMAT (ISP and NULL are not used) - INSERT ABOVE
//KNOWN_APN("APN", "USERNAME", "PASSWORD", "ISP", NULL) \
#define KNOWN_APN_SCAN_MODE 1 // 0=check GPRS context only, 2=attempt only server connection, 1=use both in sequence
#define KNOWN_APN_SCAN_USE_RESET 0 // 1=reset modem to change APN, 0=change APN without reset
================================================
FILE: README.md
================================================
# OpenTracker
The first commercial grade, fully open source and 100% Arduino compatible GPS/GLONASS vehicle tracker with a powerful 32-bit ARM Cortex-M3 CPU.
Official website: [https://geolink.io](https://geolink.io)
The new IDE integration files are available at: <https://github.com/geolink/opentracker-arduino-board>
All PCB files are available at: https://github.com/geolink/opentracker-hardware
OpenTracker Wiki: https://github.com/geolink/opentracker/wiki
Stable and tested firmware version: https://github.com/geolink/opentracker/releases
================================================
FILE: daemon/.gitignore
================================================
config/config.rb
================================================
FILE: daemon/README
================================================
OpenTracker data logging daemon
===============================
Set database connection details in config/config.rb
Set the rest of the config in the database, eg:
insert into config (`key`,server,port,uid,gid,include_key,include_timestamp,include_gps_date,include_gps_time,include_latitude,include_longitude,include_speed,include_altitude,include_heading,include_hdop,include_satellites,include_battery_level,include_ignition_state,include_engine_running_time,timestamp_use,show_received,debug,detect_engineoff_movement,engineoff_movement_threshold,detect_enginestart_athome,athome_file,detect_enginestart_overnight,enginestart_overnight_from,enginestart_overnight_to,prowl_api_key,log_journeys,event_alerts) values ('xxxxxxxxxx','127.0.0.1',80,65534,65534,1,0,0,0,1,1,1,1,1,1,1,1,1,1,'server',1,0,1,20,1,'/home/me/at_home',1,'00:00','07:00','',0,0);
================================================
FILE: daemon/config/config.rb.example
================================================
{
:db => {
:adapter => 'mysql',
:user => 'root',
:database => 'tracker',
:password => '',
:host => 'localhost',
:timeout => 60,
:reconnect => true
}
}
================================================
FILE: daemon/daemon.rb
================================================
#!/usr/bin/ruby
require 'socket'
require 'mysql'
require 'process'
require 'time'
require 'sequel'
require 'prowl'
$data_keys = {
'key' => '([0-9a-zA-Z]+)',
'timestamp' => '([0-9]{2}\/[0-9]{2}\/[0-9]{2},[0-9]{2}\:[0-9]{2}\:[0-9]{2})',
'gps_date' => '([0-9]{6})',
'gps_time' => '([0-9]+)',
'latitude' => '([0-9\.\-]+)',
'longitude' => '([0-9\.\-]+)',
'speed' => '([0-9\.\-]+)',
'altitude' => '([0-9\.\-]+)',
'heading' => '([0-9\.\-]+)',
'hdop' => '([0-9]+)',
'satellites' => '([0-9]+)',
'battery_level' => '([0-9\.]+)',
'ignition_state' => '([01])',
'engine_running_time' => '([0-9]+)'
}
class OpenTrackerDaemon
def initialize
config_file = File.dirname(__FILE__) + "/config/config.rb"
if !File.exist? config_file
raise "Config file not found: #{config_file}"
end
config = {}
instance_eval(File.read(config_file)).each do |key, value|
config[key] = value
end
if !config[:db]
raise "Database connection is not defined in config.rb"
end
@db_config = config[:db]
@db = Sequel.connect(@db_config)
load_config
@server = TCPServer.new(@config[:server], @config[:port])
Process::Sys.setuid @config[:uid]
Process::Sys.seteuid @config[:gid]
!@config[:include_key] and puts "WARNING: $include_key == false: authentication disabled!"
if @config[:timestamp_use] == 'gsm' and !@config[:include_timestamp]
raise "Error: $timestamp_use == 'gsm' requires $include_timestamp"
end
if @config[:timestamp_use] == 'gps' and (!@config[:include_gps_date] || !@config[:include_gps_time])
raise "Error: $timestamp_use == 'gps' requires $include_gps_date and $include_gps_time"
end
if !["server","gsm","gps"].include? @config[:timestamp_use]
raise "Error: invalid setting for $timestamp_use; valid settings are server, gsm or gps"
end
end
def reconnect
@db = Sequel.connect(@db_config)
end
def load_config
@config = @db[:config].first
end
def start_server
while (session = @server.accept)
unless session.peeraddr.nil?
Thread.start do
input = session.gets
@config[:show_received] and puts "#{input}"
begin
load_config
rescue Sequel::DatabaseDisconnectError
reconnect
load_config
end
handle_request input, session.peeraddr[2]
session.close
end
end
end
end
def handle_request(req, ipaddr)
if req == nil
return
end
data = parse_data(req)
if data[:key] and data[:key] == @config[:key]
@config[:debug] and puts "key match"
data.delete(:key)
if !data[:ignition_state]
data[:status] = 'ignition off'
else
data[:status] = data[:battery_level] >= @config[:engine_running_voltage] ? 'engine running' : 'position 2'
end
if @config[:timestamp_use] == 'server'
ts = Time.now
elsif @config[:timestamp_use] == 'gsm'
t = data[:timestamp].match /\A([0-9]{2})\/([0-9]{2})\/([0-9]{2}),([0-9]{2}\:[0-9]{2}\:[0-9]{2})\z/
ts = Time.parse "20" + t[1] + "-" + t[2] + "-" + t[3] + " " + t[4]
elsif @config[:timestamp_use] == 'gps'
t1 = data[:gps_date].match(/\A([0-9]{2})([0-9]{2})([0-9]{2})\z/)
t2 = data[:gps_time].match(/\A([0-9]{1,2})([0-9]{2})([0-9]{2})([0-9]{2})\z/)
ts = Time.parse "20" + t1[3] + "-" + t1[2] + "-" + t1[1] + " " + t2[1].rjust(2,'0') + ":" + t2[2] + ":" + t2[3]
end
ts = ts.strftime "%Y-%m-%d %H:%M:%S"
@config[:debug] and puts "timestamp: #{ts}"
last_row = @db[:log].order(Sequel.desc(:id)).limit(1).first
if !last_row[:ignition_state]
last_row[:status] = 'ignition off'
else
last_row[:status] = last_row[:battery_level] >= @config[:engine_running_voltage] ? 'engine running' : 'position 2'
end
last_row_distance = sprintf("%.2f",get_distance(last_row[:latitude], last_row[:longitude], data[:latitude], data[:longitude]))
if last_row[:status] != data[:status]
if @config[:event_alerts]
alert 'Ignition', "#{last_row[:status]} => #{data[:status]}"
end
if ['position 2','engine running'].include?(data[:status])
n = ts.match(/\A[0-9]{4}-[0-9]{2}-[0-9]{2} ([0-9]{2}:[0-9]{2}):[0-9]{2}\z/)
time = n[1]
prefix = data[:status] == 'position 2' ? 'Ignition turned' : 'Engine started'
if @config[:detect_enginestart_athome] and File.exists?(@config[:athome_file]) and File.open(@config[:athome_file],"r").read.strip.to_i == 1
alert 'Engine', prefix + ' while at home!'
end
if @config[:detect_enginestart_overnight] and time >= @config[:enginestart_overnight_from] and time <= @config[:enginestart_overnight_to]
alert 'Engine', prefix + ' overnight!'
end
@db[:event].insert(:timestamp => ts, :event => prefix, :moved => last_row_distance, :moved_total => last_row_distance)
if @config[:log_journeys] and data[:status] == 'engine running'
@db[:journey].insert(:from_timestamp => ts, :from_latitude => data[:latitude], :from_longitude => data[:longitude])
journey = @db[:journey].order(Sequel.desc(:id)).limit(1).first
@db[:journey_step].insert(:journey_id => journey[:id], :timestamp => ts, :latitude => data[:latitude], :longitude => data[:longitude])
end
end
if last_row[:status] == 'engine running'
last_event = @db[:event].order(Sequel.desc(:id)).limit(1).first
total_distance = sprintf("%.2f",last_event[:moved_total].to_f + last_row_distance.to_f)
@db[:event].insert(:timestamp => ts, :event => 'engine-stopped', :moved => last_row_distance, :moved_total => total_distance)
if @config[:log_journeys]
journey = @db[:journey].order(Sequel.desc(:id)).limit(1).first
@db[:journey_step].insert(:journey_id => journey[:id], :timestamp => ts, :latitude => data[:latitude], :longitude => data[:longitude])
@db[:journey].where('id = ?',journey[:id]).update(:to_timestamp => ts, :to_latitude => data[:latitude], :to_longitude => data[:longitude])
end
end
else
if data[:status] == 'engine running'
last_event = @db[:event].order(Sequel.desc(:id)).limit(1).first
last_engineoff = @db[:log].where('ignition_state = ? or battery_level < ?',0,@config[:engine_running_voltage]).order(Sequel.desc(:id)).limit(1).first
total_distance = sprintf("%.2f",get_distance(last_engineoff[:latitude], last_engineoff[:longitude], data[:latitude], data[:longitude]))
if @config[:event_alerts] and total_distance != '0.00'
alert 'Moving', "Moved #{total_distance}m"
end
@db[:event].insert(:timestamp => ts, :event => 'moved', :moved => last_row_distance, :moved_total => total_distance)
if @config[:log_journeys]
journey = @db[:journey].order(Sequel.desc(:id)).limit(1).first
@db[:journey_step].insert(:journey_id => journey[:id], :timestamp => ts, :latitude => data[:latitude], :longitude => data[:longitude])
end
end
end
if @config[:detect_engineoff_movement] and ['ignition off','position 2'].include?(data[:status]) and ['ignition off','position 2'].include?(last_row[:status])
if last_row_distance.to_f >= @config[:engineoff_movement_threshold]
alert 'Movement',"Moved #{last_row_distance} metres with engine off!"
last_event = @db[:event].order(Sequel.desc(:id)).limit(1).first
total_distance = sprintf("%.2f",last_event[:total_distance].to_f + last_row_distance.to_f)
@db[:event].insert(:timestamp => ts, :event => 'engine-off-moved', :moved => last_row_distance, :moved_total => total_distance)
end
end
data.delete(:status)
data[:timestamp] = ts
data[:ip] = ipaddr
@db[:log].insert(data)
else
@config[:debug] and puts "Error: key '#{m[1]}' != #{@config[:key]}"
end
end
def alert(type, msg)
if @config[:prowl_api_key]
Prowl.add({
:apikey => @config[:prowl_api_key],
:application => 'Tracker',
:event => type,
:description => msg
})
end
end
def get_distance(latitudeFrom, longitudeFrom, latitudeTo, longitudeTo, earthRadius = 6371000)
# convert from degrees to radians
latFrom = deg2rad(latitudeFrom)
lonFrom = deg2rad(longitudeFrom)
latTo = deg2rad(latitudeTo)
lonTo = deg2rad(longitudeTo)
latDelta = latTo - latFrom
lonDelta = lonTo - lonFrom
angle = 2 * Math::asin(Math::sqrt(Math::sin(latDelta / 2) ** 2) + Math::cos(latFrom) * Math::cos(latTo) * (Math::sin(lonDelta / 2) ** 2))
angle * earthRadius
end
def deg2rad(deg)
deg.to_f * Math::PI / 180
end
def parse_data(request)
keys = []
regex_seg = []
$data_keys.each do |key, regex|
if @config["include_#{key}".to_sym]
keys.push key
regex_seg.push regex
end
end
@regex = '\A' + regex_seg.join(',')
m = request.match Regexp.new(@regex)
data = {}
if !m.nil?
for i in 0...keys.length
if m[i+1].match /\A[0-9]+\z/
data[keys[i].to_sym] = m[i+1].to_i
elsif m[i+1].match /\A[0-9\.]+\z/
data[keys[i].to_sym] = m[i+1].to_f
else
data[keys[i].to_sym] = m[i+1]
end
if keys[i] == 'speed'
data[:speed] = (data[:speed] * 0.621371).round(2)
elsif keys[i] == 'ignition_state'
data[:ignition_state] = (data[:ignition_state] == 1)
end
end
end
data
end
end
Thread.abort_on_exception = true
ot = OpenTrackerDaemon.new
ot.start_server
================================================
FILE: daemon/daemon.sh
================================================
#!/bin/bash
if [ "`whoami`" != "root" ] ; then
echo "This needs to run as root (but will drop privileges in daemon.rb)"
exit 0
fi
cd "$( dirname "${BASH_SOURCE[0]}" )"
while :
do
./daemon.rb
done
================================================
FILE: daemon/schema.sql
================================================
-- MySQL dump 10.14 Distrib 5.5.44-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: tracker
-- ------------------------------------------------------
-- Server version 5.5.44-MariaDB-1ubuntu0.14.04.1-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `config`
--
DROP TABLE IF EXISTS `config`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `config` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(32) COLLATE utf8_bin NOT NULL,
`server` varchar(16) COLLATE utf8_bin NOT NULL,
`port` int(2) NOT NULL,
`uid` int(2) NOT NULL,
`gid` int(2) NOT NULL,
`include_key` tinyint(1) NOT NULL,
`include_timestamp` tinyint(1) NOT NULL,
`include_gps_date` tinyint(1) NOT NULL,
`include_gps_time` tinyint(1) NOT NULL,
`include_latitude` tinyint(1) NOT NULL,
`include_longitude` tinyint(1) NOT NULL,
`include_speed` tinyint(1) NOT NULL,
`include_altitude` tinyint(1) NOT NULL,
`include_heading` tinyint(1) NOT NULL,
`include_hdop` tinyint(1) NOT NULL,
`include_satellites` tinyint(1) NOT NULL,
`include_battery_level` tinyint(1) NOT NULL,
`include_ignition_state` tinyint(1) NOT NULL,
`include_engine_running_time` tinyint(1) NOT NULL,
`timestamp_use` varchar(16) COLLATE utf8_bin NOT NULL,
`show_received` tinyint(1) NOT NULL,
`debug` tinyint(1) NOT NULL,
`detect_engineoff_movement` tinyint(1) NOT NULL,
`engineoff_movement_threshold` int(2) NOT NULL,
`detect_enginestart_athome` tinyint(1) NOT NULL,
`athome_file` varchar(64) COLLATE utf8_bin NOT NULL,
`detect_enginestart_overnight` tinyint(1) NOT NULL,
`enginestart_overnight_from` varchar(5) COLLATE utf8_bin NOT NULL,
`enginestart_overnight_to` varchar(5) COLLATE utf8_bin NOT NULL,
`prowl_api_key` varchar(40) COLLATE utf8_bin NOT NULL,
`log_journeys` tinyint(1) NOT NULL,
`event_alerts` tinyint(1) NOT NULL,
`engine_running_voltage` decimal(4,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `event`
--
DROP TABLE IF EXISTS `event`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `event` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`event` varchar(64) COLLATE utf8_bin NOT NULL,
`moved` varchar(64) COLLATE utf8_bin NOT NULL,
`moved_total` varchar(64) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `journey`
--
DROP TABLE IF EXISTS `journey`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `journey` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`from_timestamp` datetime NOT NULL,
`from_latitude` decimal(8,6) NOT NULL,
`from_longitude` decimal(8,6) NOT NULL,
`from_place` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`to_timestamp` datetime DEFAULT NULL,
`to_latitude` decimal(8,6) DEFAULT NULL,
`to_longitude` decimal(8,6) DEFAULT NULL,
`to_place` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`routed` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `journey_step`
--
DROP TABLE IF EXISTS `journey_step`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `journey_step` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`journey_id` bigint(20) NOT NULL,
`timestamp` datetime DEFAULT NULL,
`latitude` decimal(8,6) NOT NULL,
`longitude` decimal(8,6) NOT NULL,
`step` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `journey_id_fk` (`journey_id`),
CONSTRAINT `journey_id_fk` FOREIGN KEY (`journey_id`) REFERENCES `journey` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `log`
--
DROP TABLE IF EXISTS `log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`latitude` decimal(8,6) NOT NULL,
`longitude` decimal(8,6) NOT NULL,
`speed` decimal(5,2) unsigned NOT NULL,
`altitude` decimal(6,2) NOT NULL,
`heading` decimal(5,2) NOT NULL,
`hdop` tinyint(1) DEFAULT NULL,
`satellites` tinyint(1) DEFAULT NULL,
`battery_level` decimal(4,2) NOT NULL,
`ignition_state` tinyint(1) unsigned NOT NULL,
`engine_running_time` bigint(20) NOT NULL,
`ip` varchar(16) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2015-09-08 20:06:58
================================================
FILE: logger/.gitignore
================================================
logger_config.rb
================================================
FILE: logger/README.md
================================================
Basic back-end logging system for OpenTracker by github.com/m4rkw
-----------------------------------------------------------------
Features:
- logs to a mysql database
- also stores events such as "engine started", "engine stopped", etc
- can send alerts to ios devices using Prowl
- detects engine-off movement and alerts [1]
- detects engine starts when at home [2]
- detects engine starts overnight
[1] This is not currently very reliable as the gps coords can wander by several
meters in areas of poor signal / sky visibility. Geolink will soon be releasing
an accelerometer module so my hope is that this will work much better with the
new device.
[2] For at-home engine-start detection to work you need to set the contents of a file
to "1" when you are at home and "0" when you are not. An easy way to do this is to
detect the presence of say, your mobile phone, on the home wifi network and log this
accordingly.
================================================
FILE: logger/apache-vhost.conf
================================================
<VirtualHost *:80>
ServerName default
ServerAdmin dev@null
DocumentRoot /var/www/site
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
</VirtualHost>
================================================
FILE: logger/cgi-bin/erb-cgi.rb
================================================
#!/usr/bin/env ruby
require 'erubis'
puts "Content-type: text/html\n\n"
# Check that the script is being executed through a redirect
if ENV["REQUEST_URI"] =~ /^#{ENV["SCRIPT_NAME"]}.*/
puts "<html><body><p>Script can not be executed directly!</p></body></html>"
else
# Get the file location from the ENV hash, read it, and process it through erubis
puts Erubis::FastEruby.new(File.read(ENV["PATH_TRANSLATED"])).result(binding())
end
================================================
FILE: logger/logger.erb
================================================
<%
require 'cgi'
require 'mysql'
require 'net/http'
require '/path/to/logger_config.rb'
class OpenTracker
def handlePost
line = $stdin.readlines[0]
m = line.match(/^imei=([0-9]+)&key=([a-zA-Z0-9]+)&d=(.*)$/)
if m.length == 0
exit
end
return {
imei: m[1],
key: m[2],
d: CGI.unescape(m[3])
}
end
def handleRequest
p = handlePost
if p[:key] != $key or p[:imei] != $imei
exit
end
m = p[:d].match(/^([0-9]{2}\/[0-9]{2}\/[0-9]{2}),([0-9]{2}:[0-9]{2}:[0-9]{2}) [0-9]+\[[0-9]+,[0-9]+,([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9]+)\]([0-9\.]+),([0-9]),([0-9]+)/)
data = {
:timestamp => Time.new.strftime("%Y-%m-%d %H:%M:%S"),
:lat => m[3],
:lon => m[4],
:speed => m[5],
:altitude => m[6],
:heading => m[7],
:hdop => m[8],
:satellites => m[9],
:vehicleBat => m[10],
:engineStatus => m[11],
:runningTime => m[12],
:ip => ENV["REMOTE_ADDR"]
}
@con = Mysql.new $mysql_server, $mysql_user, $mysql_pass, $mysql_db
log(data)
forward(p[:d])
end
def query(sql)
res = @con.query(sql)
row = {}
if res
rowdata = res.fetch_row
fields = res.fetch_fields
for i in 0...fields.length
row[fields[i].name] = rowdata[i]
end
end
row
end
def log(data)
row = query("SELECT * from `log` order by id desc limit 1")
distance = sprintf("%.2f",get_distance(row['lat'], row['lon'], data[:lat], data[:lon]))
if $detect_engineoff_movement and row['engineStatus'] == 0 and data[:engineStatus] == 0
if distance >= $engineoff_movement_threshold
alert('Movement',"Moved $distance metres with engine off!")
distance = sprintf("%.2f",distance)
last_event = query("select * from `event` order by id desc limit 1")
total_distance = sprintf("%.2f",last_event.total_distance + distance)
query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-off-moved','#{distance}','#{total_distance}');")
end
end
if row['engineStatus'] == 0 and data[:engineStatus] == 1
time = new Time(data[:timestamp]).strftime("%H:%M")
if $detect_enginestart_athome and File.exists?($at_home_file) and File.open($at_home_file,"r").read.strip == 1
alert('Engine','Engine started while at home!')
end
if $detect_enginestart_overnight and time >= $engine_start_overnight_alert_from and time <= $engine_start_overnight_alert_to
alert('Engine','Engine started overnight!')
end
query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-started','#{distance}','#{distance}');")
end
if row['engineStatus'] == 1 and data[:engineStatus] == 1
last_event = query("select * from `event` order by id desc limit 1")
total_distance = sprintf("%.2f",last_event.moved_total + distance)
query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','moved','#{distance}','#{total_distance}');")
end
if row['engineStatus'] == 1 and engineStatus == 0
last_event = query("select * from `event` order by id desc limit 1")
total_distance = sprintf("%.2f",last_event.moved_total + distance)
query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-stopped','#{distance}','#{total_distance}');")
end
query("INSERT into `log` (timestamp,lat,lon,speed,altitude,heading,hdop,satellites,vehicleBat,engineStatus,runningTime,ip) values ('#{data[:timestamp]}','#{data[:lat]}','#{data[:lon]}','#{data[:speed]}','#{data[:altitude]}','#{data[:heading]}','#{data[:hdop]}','#{data[:satellites]}','#{data[:vehicleBat]}','#{data[:engineStatus]}','#{data[:runningTime]}','#{data[:ip]}');")
end
def alert(type, msg)
if $send_prowl_alerts
msg = CGI.escape(msg)
Net::HTTP.get_response(URI("https://prowl.weks.net/publicapi/add?apikey=#{$apikey}&application=Tracker&event=#{type}&description=#{msg}&priority=2"))
end
end
def forward(data)
res = Net::HTTP.post_form(URI('http://updates.geolink.io/index.php'),
'imei' => $imei,
'key' => $key,
'd' => data.gsub(/\].*$/,'')
)
puts res.body
end
def get_distance(latitudeFrom, longitudeFrom, latitudeTo, longitudeTo, earthRadius = 6371000)
# convert from degrees to radians
latFrom = deg2rad(latitudeFrom)
lonFrom = deg2rad(longitudeFrom)
latTo = deg2rad(latitudeTo)
lonTo = deg2rad(longitudeTo)
latDelta = latTo - latFrom
lonDelta = lonTo - lonFrom
angle = 2 * Math::asin(Math::sqrt(Math::sin(latDelta / 2) ** 2) + Math::cos(latFrom) * Math::cos(latTo) * (Math::sin(lonDelta / 2) ** 2))
angle * earthRadius
end
def deg2rad(deg)
deg.to_f * Math::PI / 180
end
end
ot = OpenTracker.new.handleRequest
%>
================================================
FILE: logger/logger_config.rb.example
================================================
# OpenTracker credentials
$imei = '1234567890'
$key = 'xxxxxxxxx'
# MySQL database to log to
$mysql_server = 'localhost'
$mysql_user = 'tracker'
$mysql_password = 'tracker'
$mysql_db = 'tracker'
# Send alerts to iPhone using Prowl
$send_prowl_alerts = true
$prowl_apikey = '111111111111111111111111111111111111111'
# Detect engine-off movement (not currently reliable)
$detect_engineoff_movement = true
$engineoff_movement_threshold = 10
# Detect engine start when at home
# For this to work, to "/home/user/.at_home" to contain "1" when you are at home, and "0" when not
$detect_enginestart_athome = true
$at_home_file = "/home/user/.at_home"
# Detect engine start overnight
$detect_enginestart_overnight = true
$engine_start_overnight_alert_from = '00:00'
$engine_start_overnight_alert_to = '07:00'
================================================
FILE: logger/logger_schema.sql
================================================
CREATE TABLE `log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`lat` varchar(64) COLLATE utf8_bin NOT NULL,
`lon` varchar(64) COLLATE utf8_bin NOT NULL,
`speed` varchar(64) COLLATE utf8_bin NOT NULL,
`altitude` varchar(64) COLLATE utf8_bin NOT NULL,
`heading` varchar(64) COLLATE utf8_bin NOT NULL,
`timestamp` datetime NOT NULL,
`engineStatus` varchar(64) COLLATE utf8_bin NOT NULL,
`deviceStatus` varchar(64) COLLATE utf8_bin NOT NULL,
`gsmLevel` varchar(64) COLLATE utf8_bin NOT NULL,
`runningTime` bigint(20) unsigned NOT NULL,
`vehicleBat` varchar(64) COLLATE utf8_bin NOT NULL,
`engineLock` varchar(64) COLLATE utf8_bin NOT NULL,
`ip` varchar(16) COLLATE utf8_bin DEFAULT NULL,
`runningTimeOffset` bigint(20) unsigned NOT NULL,
`hdop` tinyint(1) DEFAULT NULL,
`satellites` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `event` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`event` varchar(64) COLLATE utf8_bin NOT NULL,
`moved` varchar(64) COLLATE utf8_bin NOT NULL,
`moved_total` varchar(64) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
gitextract_xiaew389/
├── .gitattributes
├── CHANGELOG.md
├── Examples/
│ ├── Bare_minimum_INPUTS_BAT_monitoring/
│ │ └── Bare_minimum_INPUTS_BAT_monitoring.ino
│ ├── Bare_minimum_LED/
│ │ └── Bare_minimum_LED.ino
│ ├── Bare_minimum_Relay/
│ │ └── Bare_minimum_Relay.ino
│ ├── Bare_minimum_VDET/
│ │ └── Bare_minimum_VDET.ino
│ ├── OpenTrackerTest/
│ │ └── OpenTrackerTest.ino
│ └── Test_External_IO_and_LED/
│ └── Test_External_IO_and_LED.ino
├── LICENSE
├── OpenTracker/
│ ├── .gitignore
│ ├── OpenTracker.ino
│ ├── addon.h
│ ├── addon.ino.example
│ ├── data.ino
│ ├── gps.ino
│ ├── gsm.ino
│ ├── led.ino
│ ├── parse.ino
│ ├── reboot.ino
│ ├── settings.ino
│ ├── sms.ino
│ ├── storage.ino
│ └── tracker.h.example
├── README.md
├── daemon/
│ ├── .gitignore
│ ├── README
│ ├── config/
│ │ └── config.rb.example
│ ├── daemon.rb
│ ├── daemon.sh
│ └── schema.sql
└── logger/
├── .gitignore
├── README.md
├── apache-vhost.conf
├── cgi-bin/
│ └── erb-cgi.rb
├── logger.erb
├── logger_config.rb.example
└── logger_schema.sql
SYMBOL INDEX (25 symbols across 4 files)
FILE: OpenTracker/addon.h
function addon_delay (line 5) | void addon_delay(long ms) {
function addon_init (line 12) | void addon_init() {
function addon_setup (line 17) | void addon_setup() {
function addon_loop (line 22) | void addon_loop() {
function addon_event (line 26) | void addon_event(int event) {
function addon_sms_command (line 30) | void addon_sms_command(char *cmd, char *arg, const char *phone) {
function addon_collect_data (line 34) | void addon_collect_data() {
type addon_settings (line 40) | struct addon_settings
FILE: daemon/daemon.rb
class OpenTrackerDaemon (line 27) | class OpenTrackerDaemon
method initialize (line 28) | def initialize
method reconnect (line 69) | def reconnect
method load_config (line 73) | def load_config
method start_server (line 77) | def start_server
method handle_request (line 100) | def handle_request(req, ipaddr)
method alert (line 231) | def alert(type, msg)
method get_distance (line 242) | def get_distance(latitudeFrom, longitudeFrom, latitudeTo, longitudeTo,...
method deg2rad (line 257) | def deg2rad(deg)
method parse_data (line 261) | def parse_data(request)
FILE: daemon/schema.sql
type `config` (line 25) | CREATE TABLE `config` (
type `event` (line 71) | CREATE TABLE `event` (
type `journey` (line 88) | CREATE TABLE `journey` (
type `journey_step` (line 110) | CREATE TABLE `journey_step` (
type `log` (line 130) | CREATE TABLE `log` (
FILE: logger/logger_schema.sql
type `log` (line 1) | CREATE TABLE `log` (
type `event` (line 22) | CREATE TABLE `event` (
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (171K chars).
[
{
"path": ".gitattributes",
"chars": 95,
"preview": "# Auto detect text files and perform LF normalization\n* text eol=lf\n\n*.pdf binary\n*.exe binary\n"
},
{
"path": "CHANGELOG.md",
"chars": 1811,
"preview": "Changelog\n\nv1.0\n====\n\nv2.0\n====\n\nv2.0.1\n======\n\nv3.0.1\n======\n\nv3.0.2\n======\n\n+ Renamed tracker.h to tracker.h.example s"
},
{
"path": "Examples/Bare_minimum_INPUTS_BAT_monitoring/Bare_minimum_INPUTS_BAT_monitoring.ino",
"chars": 1771,
"preview": "\n #define DEBUG 1 //enable debug msg, sent to serial port \n #define debug_port SerialUSB\n\n #ifdef DEBUG\n "
},
{
"path": "Examples/Bare_minimum_LED/Bare_minimum_LED.ino",
"chars": 365,
"preview": " #include <avr/dtostrf.h> \n \nvoid setup() { \n \n //setup led pin \n pinMode(PIN_POWER_LED, OUTPUT); // Set LED as "
},
{
"path": "Examples/Bare_minimum_Relay/Bare_minimum_Relay.ino",
"chars": 601,
"preview": "void setup() {\n // Relay output\n pinMode(PIN_C_OUT_1, OUTPUT); // Initialize pin as output\n digitalWrite(PIN_C_"
},
{
"path": "Examples/Bare_minimum_VDET/Bare_minimum_VDET.ino",
"chars": 442,
"preview": " #define DEBUG 1 //enable debug msg, sent to serial port \n #define debug_port SerialUSB\n\n #ifdef DEBUG\n "
},
{
"path": "Examples/OpenTrackerTest/OpenTrackerTest.ino",
"chars": 7714,
"preview": "/*\n Blink\n Turns on an LED on for one second, then off for one second, repeatedly.\n\n This example code is in the publ"
},
{
"path": "Examples/Test_External_IO_and_LED/Test_External_IO_and_LED.ino",
"chars": 2617,
"preview": "\n \n //External libraries\n #include <avr/dtostrf.h>\n\n #define DEBUG 1 //enable debug msg, sent to serial por"
},
{
"path": "LICENSE",
"chars": 18025,
"preview": "GNU GENERAL PUBLIC LICENSE\n Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundati"
},
{
"path": "OpenTracker/.gitignore",
"chars": 10,
"preview": "tracker.h\n"
},
{
"path": "OpenTracker/OpenTracker.ino",
"chars": 9497,
"preview": "//tracker config\n#include \"tracker.h\"\n#include \"addon.h\"\n\n//External libraries\n#include <TinyGPS.h>\n#include <DueFlashSt"
},
{
"path": "OpenTracker/addon.h",
"chars": 2392,
"preview": "// Interface for Add-on board functions\n// (define the following symbol in \"tracker.h\" to provide your own implementatio"
},
{
"path": "OpenTracker/addon.ino.example",
"chars": 2377,
"preview": "// Implementation of Audio Add-on board functions\n#if !ADDON_INTERFACE\n#line 1 \"tracker.h\"\n#error \"To use Add-On feature"
},
{
"path": "OpenTracker/data.ino",
"chars": 8160,
"preview": "//collect and send GPS data for sending\n\nvoid data_append_char(char c) {\n if (data_index < sizeof(data_current) - 1) {\n"
},
{
"path": "OpenTracker/gps.ino",
"chars": 7773,
"preview": "\nvoid gps_init() {\n debug_print(F(\"gps_init() started\"));\n\n pinMode(PIN_STANDBY_GPS, OUTPUT);\n digitalWrite(PIN_STAND"
},
{
"path": "OpenTracker/gsm.ino",
"chars": 32948,
"preview": "//gsm functions\n\n#if MODEM_UG96\n#define AT_CONTEXT \"AT+QICSGP=1,1,\"\n#define AT_ACTIVATE \"AT+QIACT=1\\r\"\n#define AT_DEACTI"
},
{
"path": "OpenTracker/led.ino",
"chars": 1449,
"preview": "\n//blink led\n\nint led_interval = 1000; //interval at which to blink status led (milliseconds)\n\nvoid status_led() {\n //b"
},
{
"path": "OpenTracker/parse.ino",
"chars": 4957,
"preview": "\n//parse remote commands from server\nint parse_receive_reply() {\n //receive reply from modem and parse it\n int ret = 0"
},
{
"path": "OpenTracker/reboot.ino",
"chars": 3113,
"preview": "\nvoid reboot() {\n debug_print(F(\"reboot() started\"));\n\n //reset GPS\n gps_off();\n //emergency power off GSM\n gsm_off"
},
{
"path": "OpenTracker/settings.ino",
"chars": 6060,
"preview": "\nvoid storage_config_fill() {\n //fill settings storage space with zeros on very first run\n debug_print(F(\"storage_conf"
},
{
"path": "OpenTracker/sms.ino",
"chars": 10316,
"preview": "\n//check SMS\nvoid sms_check() {\n int msg_count = 30; // default\n char *tmp = NULL, *tmpcmd = NULL;\n char phone[32] = "
},
{
"path": "OpenTracker/storage.ino",
"chars": 5154,
"preview": "#define STORAGE_FREE_CHAR 255\n\nvoid storage_save_current() {\n debug_print(F(\"storage_save_current() started\"));\n\n int "
},
{
"path": "OpenTracker/tracker.h.example",
"chars": 6763,
"preview": "#define ADDON_INTERFACE 0 //non-zero if you have custom addon implementation\n\n//OpenTracker config\n#define DEBUG 1 "
},
{
"path": "README.md",
"chars": 552,
"preview": "# OpenTracker\nThe first commercial grade, fully open source and 100% Arduino compatible GPS/GLONASS vehicle tracker with"
},
{
"path": "daemon/.gitignore",
"chars": 17,
"preview": "config/config.rb\n"
},
{
"path": "daemon/README",
"chars": 855,
"preview": "OpenTracker data logging daemon\n===============================\n\nSet database connection details in config/config.rb\n\nSe"
},
{
"path": "daemon/config/config.rb.example",
"chars": 202,
"preview": "\n{\n :db => {\n :adapter => 'mysql',\n :user => 'root',\n :database => 'tracker',\n :password => '',\n "
},
{
"path": "daemon/daemon.rb",
"chars": 9850,
"preview": "#!/usr/bin/ruby\n\nrequire 'socket'\nrequire 'mysql'\nrequire 'process'\nrequire 'time'\nrequire 'sequel'\nrequire 'prowl'\n\n$da"
},
{
"path": "daemon/daemon.sh",
"chars": 210,
"preview": "#!/bin/bash\nif [ \"`whoami`\" != \"root\" ] ; then\n echo \"This needs to run as root (but will drop privileges in daemon.r"
},
{
"path": "daemon/schema.sql",
"chars": 6099,
"preview": "-- MySQL dump 10.14 Distrib 5.5.44-MariaDB, for debian-linux-gnu (x86_64)\n--\n-- Host: localhost Database: tracker\n--"
},
{
"path": "logger/.gitignore",
"chars": 17,
"preview": "logger_config.rb\n"
},
{
"path": "logger/README.md",
"chars": 927,
"preview": "Basic back-end logging system for OpenTracker by github.com/m4rkw\n------------------------------------------------------"
},
{
"path": "logger/apache-vhost.conf",
"chars": 158,
"preview": "<VirtualHost *:80>\n ServerName default\n ServerAdmin dev@null\n DocumentRoot /var/www/site\n\n ScriptAlias /cgi-"
},
{
"path": "logger/cgi-bin/erb-cgi.rb",
"chars": 442,
"preview": "#!/usr/bin/env ruby\n\nrequire 'erubis'\n\nputs \"Content-type: text/html\\n\\n\"\n\n# Check that the script is being executed thr"
},
{
"path": "logger/logger.erb",
"chars": 5552,
"preview": "<%\nrequire 'cgi'\nrequire 'mysql'\nrequire 'net/http'\nrequire '/path/to/logger_config.rb'\n\nclass OpenTracker\n def handl"
},
{
"path": "logger/logger_config.rb.example",
"chars": 807,
"preview": "\n# OpenTracker credentials\n$imei = '1234567890'\n$key = 'xxxxxxxxx'\n\n# MySQL database to log to\n$mysql_server = 'localhos"
},
{
"path": "logger/logger_schema.sql",
"chars": 1254,
"preview": "CREATE TABLE `log` (\n `id` int(10) unsigned NOT NULL AUTO_INCREMENT,\n `lat` varchar(64) COLLATE utf8_bin NOT NULL,\n `"
}
]
About this extraction
This page contains the full source code of the geolink/opentracker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (157.6 KB), approximately 44.2k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.