Full Code of geolink/opentracker for AI

master 46a826f8e973 cached
37 files
157.6 KB
44.2k tokens
25 symbols
1 requests
Download .txt
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;
Download .txt
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
Download .txt
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.

Copied to clipboard!