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

Script can not be executed directly!

" else # Get the file location from the ENV hash, read it, and process it through erubis puts Erubis::FastEruby.new(File.read(ENV["PATH_TRANSLATED"])).result(binding()) end ================================================ FILE: logger/logger.erb ================================================ <% require 'cgi' require 'mysql' require 'net/http' require '/path/to/logger_config.rb' class OpenTracker def handlePost line = $stdin.readlines[0] m = line.match(/^imei=([0-9]+)&key=([a-zA-Z0-9]+)&d=(.*)$/) if m.length == 0 exit end return { imei: m[1], key: m[2], d: CGI.unescape(m[3]) } end def handleRequest p = handlePost if p[:key] != $key or p[:imei] != $imei exit end m = p[:d].match(/^([0-9]{2}\/[0-9]{2}\/[0-9]{2}),([0-9]{2}:[0-9]{2}:[0-9]{2}) [0-9]+\[[0-9]+,[0-9]+,([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9\.\-]+),([0-9]+)\]([0-9\.]+),([0-9]),([0-9]+)/) data = { :timestamp => Time.new.strftime("%Y-%m-%d %H:%M:%S"), :lat => m[3], :lon => m[4], :speed => m[5], :altitude => m[6], :heading => m[7], :hdop => m[8], :satellites => m[9], :vehicleBat => m[10], :engineStatus => m[11], :runningTime => m[12], :ip => ENV["REMOTE_ADDR"] } @con = Mysql.new $mysql_server, $mysql_user, $mysql_pass, $mysql_db log(data) forward(p[:d]) end def query(sql) res = @con.query(sql) row = {} if res rowdata = res.fetch_row fields = res.fetch_fields for i in 0...fields.length row[fields[i].name] = rowdata[i] end end row end def log(data) row = query("SELECT * from `log` order by id desc limit 1") distance = sprintf("%.2f",get_distance(row['lat'], row['lon'], data[:lat], data[:lon])) if $detect_engineoff_movement and row['engineStatus'] == 0 and data[:engineStatus] == 0 if distance >= $engineoff_movement_threshold alert('Movement',"Moved $distance metres with engine off!") distance = sprintf("%.2f",distance) last_event = query("select * from `event` order by id desc limit 1") total_distance = sprintf("%.2f",last_event.total_distance + distance) query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-off-moved','#{distance}','#{total_distance}');") end end if row['engineStatus'] == 0 and data[:engineStatus] == 1 time = new Time(data[:timestamp]).strftime("%H:%M") if $detect_enginestart_athome and File.exists?($at_home_file) and File.open($at_home_file,"r").read.strip == 1 alert('Engine','Engine started while at home!') end if $detect_enginestart_overnight and time >= $engine_start_overnight_alert_from and time <= $engine_start_overnight_alert_to alert('Engine','Engine started overnight!') end query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-started','#{distance}','#{distance}');") end if row['engineStatus'] == 1 and data[:engineStatus] == 1 last_event = query("select * from `event` order by id desc limit 1") total_distance = sprintf("%.2f",last_event.moved_total + distance) query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','moved','#{distance}','#{total_distance}');") end if row['engineStatus'] == 1 and engineStatus == 0 last_event = query("select * from `event` order by id desc limit 1") total_distance = sprintf("%.2f",last_event.moved_total + distance) query("insert into `event` (`timestamp`,`event`,`moved`,`moved_total`) values ('#{data[:timestamp]}','engine-stopped','#{distance}','#{total_distance}');") end query("INSERT into `log` (timestamp,lat,lon,speed,altitude,heading,hdop,satellites,vehicleBat,engineStatus,runningTime,ip) values ('#{data[:timestamp]}','#{data[:lat]}','#{data[:lon]}','#{data[:speed]}','#{data[:altitude]}','#{data[:heading]}','#{data[:hdop]}','#{data[:satellites]}','#{data[:vehicleBat]}','#{data[:engineStatus]}','#{data[:runningTime]}','#{data[:ip]}');") end def alert(type, msg) if $send_prowl_alerts msg = CGI.escape(msg) Net::HTTP.get_response(URI("https://prowl.weks.net/publicapi/add?apikey=#{$apikey}&application=Tracker&event=#{type}&description=#{msg}&priority=2")) end end def forward(data) res = Net::HTTP.post_form(URI('http://updates.geolink.io/index.php'), 'imei' => $imei, 'key' => $key, 'd' => data.gsub(/\].*$/,'') ) puts res.body end def get_distance(latitudeFrom, longitudeFrom, latitudeTo, longitudeTo, earthRadius = 6371000) # convert from degrees to radians latFrom = deg2rad(latitudeFrom) lonFrom = deg2rad(longitudeFrom) latTo = deg2rad(latitudeTo) lonTo = deg2rad(longitudeTo) latDelta = latTo - latFrom lonDelta = lonTo - lonFrom angle = 2 * Math::asin(Math::sqrt(Math::sin(latDelta / 2) ** 2) + Math::cos(latFrom) * Math::cos(latTo) * (Math::sin(lonDelta / 2) ** 2)) angle * earthRadius end def deg2rad(deg) deg.to_f * Math::PI / 180 end end ot = OpenTracker.new.handleRequest %> ================================================ FILE: logger/logger_config.rb.example ================================================ # OpenTracker credentials $imei = '1234567890' $key = 'xxxxxxxxx' # MySQL database to log to $mysql_server = 'localhost' $mysql_user = 'tracker' $mysql_password = 'tracker' $mysql_db = 'tracker' # Send alerts to iPhone using Prowl $send_prowl_alerts = true $prowl_apikey = '111111111111111111111111111111111111111' # Detect engine-off movement (not currently reliable) $detect_engineoff_movement = true $engineoff_movement_threshold = 10 # Detect engine start when at home # For this to work, to "/home/user/.at_home" to contain "1" when you are at home, and "0" when not $detect_enginestart_athome = true $at_home_file = "/home/user/.at_home" # Detect engine start overnight $detect_enginestart_overnight = true $engine_start_overnight_alert_from = '00:00' $engine_start_overnight_alert_to = '07:00' ================================================ FILE: logger/logger_schema.sql ================================================ CREATE TABLE `log` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lat` varchar(64) COLLATE utf8_bin NOT NULL, `lon` varchar(64) COLLATE utf8_bin NOT NULL, `speed` varchar(64) COLLATE utf8_bin NOT NULL, `altitude` varchar(64) COLLATE utf8_bin NOT NULL, `heading` varchar(64) COLLATE utf8_bin NOT NULL, `timestamp` datetime NOT NULL, `engineStatus` varchar(64) COLLATE utf8_bin NOT NULL, `deviceStatus` varchar(64) COLLATE utf8_bin NOT NULL, `gsmLevel` varchar(64) COLLATE utf8_bin NOT NULL, `runningTime` bigint(20) unsigned NOT NULL, `vehicleBat` varchar(64) COLLATE utf8_bin NOT NULL, `engineLock` varchar(64) COLLATE utf8_bin NOT NULL, `ip` varchar(16) COLLATE utf8_bin DEFAULT NULL, `runningTimeOffset` bigint(20) unsigned NOT NULL, `hdop` tinyint(1) DEFAULT NULL, `satellites` tinyint(1) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; CREATE TABLE `event` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `timestamp` datetime NOT NULL, `event` varchar(64) COLLATE utf8_bin NOT NULL, `moved` varchar(64) COLLATE utf8_bin NOT NULL, `moved_total` varchar(64) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;