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