diff --git a/.gitignore b/.gitignore index b651be9..63ffc6b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ openstratos openstratosRoot utesting Makefile +data +config.h # http://www.gnu.org/software/automake @@ -48,7 +50,8 @@ Makefile .deps/ .dirstamp *~ +config.h # Eclipse files .project -.cproject \ No newline at end of file +.cproject diff --git a/.travis.yml b/.travis.yml index 1b624c1..909c545 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,23 @@ language: cpp compiler: gcc +sudo: required + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - autoconf + - automake + - m4 + - gcc-4.9 + - g++-4.9 before_install: - git submodule update --init --recursive > /dev/null - - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - - sudo apt-get update -qq + - export CXX="g++-4.9" install: - - sudo apt-get install -qq autoconf automake m4 gcc-4.9 g++-4.9 > /dev/null - - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.9 90 - "bash -ex ./install-wiringpi.sh" script: @@ -26,8 +35,6 @@ notifications: email: recipients: - iban.eguia@opendeusto.es - - jordan.aranda@me.com - - aritzbilbao@deusto.es - eneko.cruz@opendeusto.es on_success: change on_failure: always diff --git a/DCO.txt b/DCO.txt new file mode 100644 index 0000000..a404c0d --- /dev/null +++ b/DCO.txt @@ -0,0 +1,25 @@ +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(1) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(2) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(3) The contribution was provided directly to me by some other + person who certified (1), (2) or (3) and I have not modified + it. + +(4) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/Makefile.am b/Makefile.am index 617a2f8..de5b549 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,11 +1,7 @@ bin_PROGRAMS = openstratos -openstratos_SOURCES = openstratos.cc camera/Camera.cc gps/GPS.cc serial/Serial.cc battery/Battery.cc temperature/Temperature.cc -openstratos_LDADD = -lwiringPi -openstratos_CPPFLAGS = -std=c++11 -Wno-unused-result +openstratos_SOURCES = openstratos.cc utils.cc threads.cc camera/Camera.cc gps/GPS.cc serial/Serial.cc logger/Logger.cc gsm/GSM.cc adc/MCP3424.cc adc/ADC.cc +openstratos_CPPFLAGS = -std=c++14 -EXTRA_PROGRAMS = openstratosRoot utesting -openstratosRoot_SOURCES = openstratos-root.cc camera/Camera.cc gps/GPS.cc serial/Serial.cc battery/Battery.cc temperature/Temperature.cc -openstratosRoot_CPPFLAGS = -std=c++11 -Wno-unused-result - -utesting_SOURCES = testing/testing.cc camera/Camera.cc gps/GPS.cc serial/Serial.cc battery/Battery.cc temperature/Temperature.cc -utesting_CPPFLAGS = -std=c++11 -Itesting/bandit -Wno-unused-result -DOS_TESTING +EXTRA_PROGRAMS = utesting +utesting_SOURCES = testing/testing.cc camera/Camera.cc gps/GPS.cc serial/Serial.cc logger/Logger.cc +utesting_CPPFLAGS = -std=c++14 -Itesting/bandit -Wno-unused-result -DOS_TESTING diff --git a/README b/README index 80f7c34..0d24b7b 100644 --- a/README +++ b/README @@ -2,4 +2,122 @@ [![Build Status](https://travis-ci.org/OpenStratos/server.svg?branch=develop)](https://travis-ci.org/OpenStratos/server) -Server implemented in C++11. It will be in charge of the management of the balloon. +Server implemented in C++14. It will be in charge of the management of the balloon. It will +communicate via SMS to the provided phone. It will initialize all needed components, track and log +position and software messages, detect launch, burst and landing and send the landing position via +SMS. + +## Requirements ## + +The software needs a working instalation of WiringPi and a RaspiCam for the tests. It will perform +some basic tests if no Raspberry Pi is being used. It works with Adafruit Fona module, even though +it should work with other GSM modules. It also uses the Adafruit Ultimate GPS module. The following +software is needed to compile OpenStratos (apart from the WiringPi library): + +* build-essential +* g++ +* m4 +* automake +* autoconf + +## Compiling ## + +For compilation, a *build.sh* script is provided, that should be run as is. It will compile the +tests of OpenStratos and run them. After that, the main program can be compiled using ```make```. +Optional configuration arguments can be passed. The first optional configuration argument is the +*NO_SMS* flag. This prevents actual SMSs being sent, even if they are simulated. This way no charges +will be applied. For using this argument the directory should be cleaned with ```make clean``` and +then pass the *NO_SMS* flag to the configure script: + +``` +./configure CPPFLAGS="-DNO_SMS" +``` + +After that the usual ```make``` will compile the software. Note that the test do not send SMSs. The software itself comes with two built-in simulation modes: + +### Normal Simulation ### + +In this mode, a simple simulation is made, with a length of about 45 minutes. It will run through +all the main stages of the program. For using this mode the directory should be cleaned with +```make clean``` and then pass the *SIM* flag to the configure script: + +``` +./configure CPPFLAGS="-DSIM" +``` + +It can be combined with the *NO_SMS* flag: + +``` +./configure CPPFLAGS="-DSIM -DNO_SMS" +``` + +After that, the software can be compiled using ```make```. + +### Realistic Simulation ### + +In this mode, a complete realistic simulation is made, that will last for about 5 hours. It will +realistically simulate the times in a 35 km height balloon. It will be similar to the normal +simulation, the only change will be in the timing. For using this mode the directory should be +cleaned with ```make clean``` and then pass the *REAL_SIM* flag to the configure script: + +``` +./configure CPPFLAGS="-DREAL_SIM" +``` + +It can be combined with the *NO_SMS* flag: + +``` +./configure CPPFLAGS="-DREAL_SIM -DNO_SMS" +``` + +After that, the software can be compiled using ```make```. The result of combining the two +simulation flags is undetermined. They should not be combined. + +### Debug Mode ### + +The software has a small debug mode, that prints from *stdio* some logs that occur before the log +file is created. It also enables serial logging, that will log everything that happens in the +serial. This has a moderate overhead and should not be used in production. GSM and GPS loggers log everythong they send and receive. This is only needed to debug if something goes wrong with the +serial. For using this mode the directory should be cleaned with ```make clean``` and then pass the +*DEBUG* flag to the configure script: + +``` +./configure CPPFLAGS="-DDEBUG" +``` + +It can be combined with the *NO_SMS* flag or/and one of the simulation flags: + +``` +./configure CPPFLAGS="-DREAL_SIM -DNO_SMS -DDEBUG" +./configure CPPFLAGS="-DSIM -DDEBUG" +./configure CPPFLAGS="-DNO_SMS -DDEBUG" +``` + +### No power off mode ### + +For some testing there is no need to reboot or shut down the system in failures. For this, the +software provides the no power off mode, that will prevent the software from shutting the +Raspberry Pi down. For using this mode the directory should be cleaned with ```make clean``` and +then pass the *NO_POWER_OFF* flag to the configure script: + +``` +./configure CPPFLAGS="-DNO_POWER_OFF" +``` + +It can be combined with the *NO_SMS* flag, the *DEBUG* flag or/and one of the simulation flags: + +``` +./configure CPPFLAGS="-DREAL_SIM -DNO_SMS -DDEBUG -DNO_POWER_OFF" +./configure CPPFLAGS="-DSIM -DDEBUG -DNO_POWER_OFF" +./configure CPPFLAGS="-DNO_SMS -DDEBUG -DNO_POWER_OFF" + +## License ## + +This software is licensed under the GNU General Public License version 3. You can use, copy, modify +the software as long as all the derivative work is published under the same license. A copy of the license can be found in the *[COPYING](COPYING)* file of the repository where a detailed explanation +of the copying rights can be found. + +## Contributing guidelines ## + +The guidelines to contribute to this repository can be found in the +[contribution guidelines](contributing.md) file of the repository. diff --git a/adc/ADC.cc b/adc/ADC.cc new file mode 100644 index 0000000..bc03559 --- /dev/null +++ b/adc/ADC.cc @@ -0,0 +1 @@ +#include "adc/ADC.h" \ No newline at end of file diff --git a/adc/ADC.h b/adc/ADC.h new file mode 100644 index 0000000..d330504 --- /dev/null +++ b/adc/ADC.h @@ -0,0 +1,4 @@ +#ifndef ADC_ADC_H_ +#define ADC_ADC_H_ + +#endif \ No newline at end of file diff --git a/adc/MCP3424.cc b/adc/MCP3424.cc new file mode 100644 index 0000000..393b7a5 --- /dev/null +++ b/adc/MCP3424.cc @@ -0,0 +1,11 @@ +#include "adc/MCP3424.h" +#include + +using namespace os; + +MCP3424::MCP3424(int i2c_address, int gain, int res) +{ + this->gain = gain; + this->res = res; + this->fd = wiringPiI2CSetup(i2c_address); +} diff --git a/adc/MCP3424.h b/adc/MCP3424.h new file mode 100644 index 0000000..e75ba01 --- /dev/null +++ b/adc/MCP3424.h @@ -0,0 +1,24 @@ +#ifndef ADC_MCP3424_H_ +#define ADC_MCP3424_H_ + +namespace os { + + class MCP3424 + { + private: + int fd; + int gain; + int res; + + inline int get_divisor() {return 1 << (this->gain + 2*this->res);} + + public: + MCP3424(int i2c_address, int gain, int res); + MCP3424(MCP3424& copy) = delete; + ~MCP3424() = default; + + double read_channel(int channel); + }; +} + +#endif // ADC_MCP3424_H_ diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 86f4f99..0000000 --- a/autogen.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -aclocal --install -I m4 && - autoreconf && - automake --add-missing --copy && - ./configure "$@" \ No newline at end of file diff --git a/battery/Battery.cc b/battery/Battery.cc deleted file mode 100644 index 37748cd..0000000 --- a/battery/Battery.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include "battery/Battery.h" - -#include -#include -#include - -#include - -#include "constants.h" - -using namespace std; -using namespace os; - -Battery::~Battery() -{ - if (this->reading) - this->stop_reading(); -} - -Battery::Battery(const int address) -{ - this->reading = false; - this->stopped = true; - #ifndef OS_TESTING - int fh = wiringPiI2CSetup(address); - if (fh != -1) - { - this->address = address; - this->filehandle = fh; - } - else - { - // TODO Log error - //printf("An error ocurred initializing I2C Battery module\n"); - } - #endif -} - -void Battery::start_reading() -{ - if ( ! this->reading) - { - this->reading = true; - this->stopped = false; - thread t(&Battery::read_battery, this); - t.detach(); - } -} - -void Battery::stop_reading() -{ - this->reading = false; - while( ! this->stopped); -} - -void Battery::read_battery() -{ - while (this->reading) - { - #ifndef OS_TESTING - int value = wiringPiI2CRead(this->filehandle); - #else - int value = 32768*(0.5*(BAT_MAX+BAT_MIN))*BAT_R2/(BAT_R1+BAT_R2)/5; - #endif - - float voltage5 = value * 5.0 / 32768; // 2^15 - - this->battery = volt_to_percent(voltage5*(BAT_R1+BAT_R2)/BAT_R2); - - this_thread::sleep_for(chrono::milliseconds(50)); - } - this->stopped = true; -} diff --git a/battery/Battery.h b/battery/Battery.h deleted file mode 100644 index be549c6..0000000 --- a/battery/Battery.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef BATTERY_BATTERY_H_ -#define BATTERY_BATTERY_H_ - -#include - -#include "constants.h" -using namespace std; - -namespace os { - class Battery - { - private: - int address; - int filehandle; - float battery; - atomic_bool reading; - atomic_bool stopped; - - void read_battery(); - public: - Battery(const int address); - ~Battery(); - Battery(Battery& copy) = delete; - - float get_battery() {return this->battery;} - void start_reading(); - void stop_reading(); - bool is_reading() {return this->reading;} - }; - - inline float volt_to_percent(float voltage) {return (voltage-BAT_MIN)/(BAT_MAX-BAT_MIN)*100;} -} - -#endif // BATTERY_BATTERY_H_ diff --git a/build.sh b/build.sh index 91822e5..b7471b0 100755 --- a/build.sh +++ b/build.sh @@ -7,5 +7,12 @@ autoconf ./configure make utesting +mkdir data +mkdir data/logs +mkdir data/logs/GPS +mkdir data/logs/camera +mkdir data/logs/main +mkdir data/video + printf "\n\n----- Starting unit tests -----\n\n" -./utesting \ No newline at end of file +./utesting diff --git a/camera/Camera.cc b/camera/Camera.cc index c4b5810..3d66d5a 100644 --- a/camera/Camera.cc +++ b/camera/Camera.cc @@ -6,6 +6,14 @@ #include #include +#include +#include +#include + +#include "config.h" +#include "constants.h" +#include "gps/GPS.h" + using namespace os; using namespace std; @@ -15,44 +23,200 @@ Camera& Camera::get_instance() return instance; } +Camera::Camera() +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + this->logger = new Logger("data/logs/camera/Camera."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "Camera"); +} + Camera::~Camera() { - if (this->recording) this->stop(); + this->logger->log("Shuting down..."); + if (this->recording) + { + this->logger->log("Stopping video recording..."); + if ( ! this->stop()) + this->logger->log("Error soping video recording."); + else + this->logger->log("Video recording stopped."); + } + this->logger->log("Shut down finished"); + delete this->logger; } void Camera::record_thread(int time) { + this->logger->log("Recording thread started."); this_thread::sleep_for(chrono::milliseconds(time)); this->recording = false; + this->logger->log("Finished recording thread."); } -void Camera::record(int time) +bool Camera::record(int time) { + if (time != 0) this->logger->log("Recording for "+to_string(time/1000)+" seconds..."); if ( ! this->recording) { - string command = "raspivid -o os_video.h264 -t " + to_string(time) + " &"; - #ifndef RASPIVID + this->logger->log("Not already recording, creating command..."); + string filename = time > 0 ? "data/video/test.h264" : "data/video/video-"+ + to_string(get_file_count("data/video/")) +".h264"; + #ifdef OS_TESTING + filename = "data/video/test.h264"; + #endif + string command = "raspivid -n -o "+ filename +" -t " + to_string(time) + " -w "+ to_string(VIDEO_WIDTH) +" -h " + + to_string(VIDEO_HEIGHT) +" -b "+ to_string(VIDEO_BITRATE*1000000) + + " -fps "+ to_string(VIDEO_FPS) +" -co "+ to_string(VIDEO_CONTRAST) + + " -ex "+ VIDEO_EXPOSURE +" -br "+ to_string(VIDEO_BRIGHTNESS) +" &"; + this->logger->log("Video command: '"+command+"'"); + + #ifndef RASPICAM + this->logger->log("Test mode, video recording simulated."); command = ""; #endif - system(command.c_str()); + int st = system(command.c_str()); this->recording = true; - if (time > 0) + bool result = st == 0; + + if (result && time > 0) { + this->logger->log("Starting recording thread..."); thread t(&Camera::record_thread, this, time); t.detach(); } + + if (result) this->logger->log("Video recording correctly started."); + else this->logger->log("Error starting video recording."); + + return result; } } -void Camera::record() +bool Camera::record() { - this->record(0); + this->logger->log("Recording indefinitely..."); + return this->record(0); } -void Camera::stop() +bool Camera::take_picture(const string& exif) { - system("pkill raspivid"); - this->recording = false; + bool was_recording = this->recording; + if (was_recording) this->logger->log("Recording video, stopping..."); + if (was_recording && ! this->stop()) return false; + this->logger->log("Video recording stopped."); + + string filename = "data/img/img-"+ to_string(get_file_count("data/img/")) +".jpg"; + #ifdef OS_TESTING + filename = "data/img/test.jpg"; + #endif + + string exif_command = exif != "" ? " -x "+ exif : ""; + + string command = "raspistill -n -o "+ filename +" " + (PHOTO_RAW ? "-r" : "") + " -w "+ to_string(PHOTO_WIDTH) + +" -h "+ to_string(PHOTO_HEIGHT) +" -q "+ to_string(PHOTO_QUALITY) + +" -co "+ to_string(PHOTO_CONTRAST) +" -br "+ to_string(PHOTO_BRIGHTNESS) + +" -ex "+ PHOTO_EXPOSURE + exif_command; + + this->logger->log("Picture command: '"+command+"'"); + + #ifndef RASPICAM + this->logger->log("Test mode, picture taking simulated."); + command = ""; + #endif + + int st = system(command.c_str()); + bool result = st == 0; + + if (result) this->logger->log("Picture taken correctly."); + else this->logger->log("Error taking picture."); + + if (was_recording) this->logger->log("Video recording was active before taking picture. Resuming..."); + if (was_recording && ! this->record()) + { + this->logger->log("Error resuming video recording."); + return false; + } + this->logger->log("Video recording resumed."); + + return result; +} + +bool Camera::take_picture() +{ + return Camera::take_picture(""); +} + +bool Camera::stop() +{ + this->logger->log("Stopping video recording..."); + #ifdef RASPICAM + if (system("pkill raspivid") == 0) + { + this->logger->log("Video recording stopped correctly."); + this->recording = false; + return true; + } + this->logger->log("Error stopping video recording."); + + if ( ! this->is_really_recording()) + { + this->logger->log("Warning: video was already stopped."); + this->recording = false; + return true; + } + return false; + #else + this->logger->log("Test mode. Video recording stop simulated."); + this->recording = false; + return true; + #endif +} + +bool Camera::is_really_recording() const +{ + return (0 == system("pidof -x raspivid > /dev/null")); +} + +int os::get_file_count(const string& path) +{ + DIR *dp; + int i = 0; + struct dirent *ep; + dp = opendir(path.c_str()); + + while (ep = readdir(dp)) i++; + (void) closedir(dp); + + return i-2; +} + +const string os::generate_exif_data() +{ + string exif; + while (GPS::get_instance().get_PDOP() > 5) + this_thread::sleep_for(1s); + + double gps_lat = GPS::get_instance().get_latitude(); + double gps_lon = GPS::get_instance().get_longitude(); + double gps_alt = GPS::get_instance().get_altitude(); + uint_fast8_t gps_sat = GPS::get_instance().get_satellites(); + float gps_pdop = GPS::get_instance().get_PDOP(); + euc_vec gps_velocity = GPS::get_instance().get_velocity(); + + exif += "GPSLatitudeRef="+to_string(gps_lat > 0 ? 'N' : 'S'); + exif += " GPSLatitude="+to_string(abs((int) gps_lat*1000000))+"/1000000,0/1,0/1"; + exif += " GPSLongitudeRef="+to_string(gps_lon > 0 ? 'E' : 'W'); + exif += " GPSLongitude="+to_string(abs((int) gps_lon*1000000))+"/1000000,0/1,0/1"; + exif += " GPSAltitudeRef=0 GPSAltitude="+to_string(gps_alt); + exif += " GPSSatellites="+to_string(gps_sat); + exif += " GPSDOP="+to_string(gps_pdop); + exif += " GPSSpeedRef=K GPSSpeed="+to_string(gps_velocity.speed*3.6); + exif += " GPSTrackRef=T GPSTrack="+to_string(gps_velocity.course); + exif += " GPSDifferential=0"; } diff --git a/camera/Camera.h b/camera/Camera.h index 1619de6..b4cdb29 100644 --- a/camera/Camera.h +++ b/camera/Camera.h @@ -1,25 +1,38 @@ #ifndef CAMERA_CAMERA_H_ #define CAMERA_CAMERA_H_ +#include + +#include "logger/Logger.h" + +using namespace std; + namespace os { class Camera { private: - Camera() = default; - ~Camera(); - void record_thread(int time); + Logger* logger; bool recording = false; + Camera(); + void record_thread(int time); + bool is_really_recording() const; public: Camera(Camera& copy) = delete; + ~Camera(); static Camera& get_instance(); - void record(int time); - void record(); - void stop(); + bool record(int time); + bool record(); + bool take_picture(const string& exif); + bool take_picture(); + bool stop(); bool is_recording() const {return this->recording;} }; + + int get_file_count(const string& path); + const string generate_exif_data(); } #endif // CAMERA_CAMERA_H_ diff --git a/config.h b/config.h deleted file mode 100644 index 308566b..0000000 --- a/config.h +++ /dev/null @@ -1,86 +0,0 @@ -/* config.h. Generated from config.h.in by configure. */ -/* config.h.in. Generated from configure.ac by autoheader. */ - -/* Define to 1 if you have the `alarm' function. */ -#define HAVE_ALARM 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the `pthread' library (-lpthread). */ -#define HAVE_LIBPTHREAD 1 - -/* Define to 1 if you have the `wiringPi' library (-lwiringPi). */ -#define HAVE_LIBWIRINGPI 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the `stpcpy' function. */ -#define HAVE_STPCPY 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the `strstr' function. */ -#define HAVE_STRSTR 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Name of package */ -#define PACKAGE "openstratos" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "https://openstratos.org/bugtracker" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "OpenStratos" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "OpenStratos Alpha-1-dev" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "openstratos" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "Alpha-1-dev" - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to 1 if you can safely include both and . */ -#define TIME_WITH_SYS_TIME 1 - -/* Version number of package */ -#define VERSION "Alpha-1-dev" - -/* Define to `__inline__' or `__inline' if that's what the C compiler - calls it, or to nothing if 'inline' is not supported under any name. */ -#ifndef __cplusplus -/* #undef inline */ -#endif - -/* Define to `unsigned int' if does not define. */ -/* #undef size_t */ diff --git a/configure.ac b/configure.ac index 9244376..d35f714 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.68]) -AC_INIT(OpenStratos, Alpha-1-dev, https://openstratos.org/bugtracker) -AC_CONFIG_SRCDIR([openstratos.h]) +AC_INIT(OpenStratos, 1.0, https://github.com/OpenStratos/server/issues) +AC_CONFIG_SRCDIR([openstratos.cc]) AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_HEADERS([config.h]) @@ -12,11 +12,13 @@ AC_PROG_CXX AC_PROG_CC AC_PROG_INSTALL AC_PROG_MAKE_SET -AC_CHECK_PROG(RASPIVID, raspivid, yes) +AC_CHECK_PROG([HAVE_RASPICAM], [raspivid], [yes]) +AS_IF([test "x$HAVE_RASPICAM" = xyes], + [AC_DEFINE([RASPICAM], [1], [Define to 1 if you have raspicam available.])]) # Checks for libraries. AC_CHECK_LIB([pthread], [pthread_create]) -AC_CHECK_LIB([wiringPi], [wiringPiSetupSys]) +AC_CHECK_LIB([wiringPi], [wiringPiSetup]) # Checks for header files. AC_CHECK_HEADERS([string.h]) @@ -30,7 +32,6 @@ AC_TYPE_SIZE_T # Checks for library functions. AC_FUNC_ERROR_AT_LINE -AC_FUNC_MKTIME AC_CHECK_FUNCS([stpcpy strstr]) AC_CONFIG_FILES([Makefile]) diff --git a/constants.h b/constants.h index 9aaaef1..d94956a 100644 --- a/constants.h +++ b/constants.h @@ -1,17 +1,42 @@ #ifndef CONSTANTS_H_ #define CONSTANTS_H_ - #define ERR_OK 0 - #define ERR_CHK 1 - #define ERR_INT 2 - #define ERR_PWD 3 + #define FLIGHT_LENGTH 4.4333 // Hours - #define TEMP_VIN 5 - #define TEMP_R 500 + #define BAT_GSM_MAX 4.2 + #define BAT_GSM_MIN 3.7 + #define BAT_MAIN_MAX 8.4*2660/(2660+7420) // Measured Ohms in voltage divider + #define BAT_MAIN_MIN 7.4*BAT_MAIN_MAX/8.4 - #define BAT_MAX 8.4 - #define BAT_MIN 7.4 - #define BAT_R1 3300 - #define BAT_R2 4700 + #define VIDEO_WIDTH 1920 + #define VIDEO_HEIGHT 1080 + #define VIDEO_BITRATE 17 //Mbps + #define VIDEO_FPS 30 + #define VIDEO_CONTRAST 50 + #define VIDEO_BRIGHTNESS 50 + #define VIDEO_EXPOSURE "antishake" + #define PHOTO_WIDTH 2592 + #define PHOTO_HEIGHT 1944 + #define PHOTO_QUALITY 90 + #define PHOTO_RAW true + #define PHOTO_CONTRAST 50 + #define PHOTO_BRIGHTNESS 50 + #define PHOTO_EXPOSURE "antishake" + + #define GPS_UART "/dev/ttyAMA0" + #define GPS_ENABLE_GPIO 2 + #define GPS_BAUDRATE 9600 + #define GPS_ENDL "\r\n" + + #define GSM_LOC_SERV "gprs-service.com" + #define GSM_UART "/dev/ttyUSB0" + #define GSM_PWR_GPIO 7 + #define GSM_STATUS_GPIO 21 + #define GSM_BAUDRATE 9600 + #define GSM_ENDL "\r\n" + + #define SMS_PHONE "" + + #define STATE_FILE "data/last_state.txt" #endif // CONSTANTS_H_ diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..5d8d7ca --- /dev/null +++ b/contributing.md @@ -0,0 +1,88 @@ +# Contributing to OpenStratos # + +OpenStratos is an open source project developed by university students, and accepts contributions from the community. These contributions are made in the form of Issues or [Pull Requests](https://help.github.com/articles/using-pull-requests/) on the [OpenStratos repositories](https://github.com/OpenStratos) on GitHub. + +Issues are a quick way to point out a bug. If you find a bug or documentation error in OpenStratos then please check a few things first: + +1. It is not already an open issue +2. The issue has already been fixed (check the develop branch, or look for closed issues) +3. Can you solve the issue yourself? + +Reporting issues is helpful but an even better approach is to send a pull Request, which is done by "forking" the main repository and committing to your own copy. This will require you to use the version control system called Git. + +The bug tracker is located at [openstratos.org](https://openstratos.org/bugtracker/). Please post your issues there. + +## Guidelines ## + +Before we look into how, here are the guidelines. If your Pull Requests fail +to pass these guidelines it will be declined and you will need to re-submit +when you’ve made the changes. This might sound a bit tough, but it is required +for us to maintain quality of the code-base. + + +### Code style + +All C++ code must meet the [Google C++ style guide](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html). All the Python code must meet the [Google Python Style Guide](https://google-styleguide.googlecode.com/svn/trunk/pyguide.html). This makes certain that all code is the same format as the existing code and means it will be as readable as possible. + +### Compatibility + +OpenStratos must be compatible with C++11. This means that it must use all the benefits of C++11 as possible. In the case of Python, it must be Python 3.4+ compatible. We do not support Python 2.x. + +### Branching + +OpenStratos uses the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-model/) branching model which requires all pull requests to be sent to the "develop" branch. This is where the next planned version will be developed. The "master" branch will always contain the latest stable version and is kept clean so a "hotfix" (e.g: an emergency security patch) can be applied to master to create a new version, without worrying about other features holding it up. For this reason all commits need to be made to "develop" and any sent to "master" will be closed automatically. If you have multiple changes to submit, please place all changes into their own branch on your fork. + +One thing at a time: A pull request should only contain one change. That does not mean only one commit, but one change - however many commits it took. The reason for this is that if you change X and Y but send a pull request for both at the same time, we might really want X but disagree with Y, meaning we cannot merge the request. Using the Git-Flow branching model you can create new branches for both of these features and send two requests. + +### Signing + +You must sign your work, certifying that you either wrote the work or otherwise have the right to pass it on to an open source project. Git makes this trivial as you merely have to use `--signoff` on your commits to your OpenStratos fork. + +`git commit --signoff` + +or simply + +`git commit -s` + +This will sign your commits with the information setup in your git config, e.g. + +`Signed-off-by: Razican ` + +If you are using [Tower](http://www.git-tower.com/) there is a "Sign-Off" checkbox in the commit window. You could even alias git commit to use the `-s` flag so you don’t have to think about it. + +By signing your work in this manner, you certify to a "Developer's Certificate of Origin". The current version of this certificate is in the `DCO.txt` file in the root of this repository. + +## How-to Guide + +There are two ways to make changes, the easy way and the hard way. Either way you will need to [join GitHub](https://github.com/join). + +Easy way GitHub allows in-line editing of files for making simple typo changes and quick-fixes. This is not the best way as you are unable to test if the code works. If you do this you could be introducing syntax errors, etc, but for a Git-phobic user this is good for a quick-fix. + +Hard way The best way to contribute is to "clone" your fork of OpenStratos to your development area. That sounds like some jargon, but "forking" on GitHub means "making a copy of that repo to your account" and "cloning" means "copying that code to your environment so you can work on it". + +1. Set up Git (Windows, Mac & Linux) +2. Go to the OpenStratos repo you want to contribute to. +3. Fork it +4. Clone your OpenStratos repo. +5. Checkout the "develop" branch. +6. Create a new branch for your changes. At this point you are ready to start making changes. +6. Fix existing bugs on the Issue tracker after taking a look to see nobody else is working on them. +7. Commit the files. +8. Push your branch to your fork. +9. [Send a pull request](https://help.github.com/articles/using-pull-requests/) + +The core developers will now be alerted about the change and at least one of the team will respond. If your change fails to meet the guidelines it will be bounced, or feedback will be provided to help you improve it. + +Once the core developer handling your pull request is happy with it they will merge it into develop and your patch will be part of the next release. + +### Keeping your fork up-to-date + +Unlike systems like Subversion, Git can have multiple remotes. A remote is the name for a URL of a Git repository. By default your fork will have a remote named "origin" which points to your fork, but you can add another remote named "upstream" which points to the OpenStratos repository. This is a read-only remote but you can pull from this develop branch to update your own. + +If you are using command-line you can do the following: + +1. `git remote add upstream {OpenStratos repo}` +2. `git pull upstream develop` +3. `git push origin develop` + +Now your fork is up to date. This should be done regularly, or before you send a pull request at least. diff --git a/gps/GPS.cc b/gps/GPS.cc index 2180789..3d14fc7 100644 --- a/gps/GPS.cc +++ b/gps/GPS.cc @@ -1,12 +1,20 @@ #include "gps/GPS.h" +#include "constants.h" #include #include #include #include +#include +#include + +#include + +#include #include "constants.h" #include "serial/Serial.h" +#include "logger/Logger.h" using namespace std; @@ -20,37 +28,194 @@ GPS& GPS::get_instance() GPS::~GPS() { - this->serial.close(); + if ( ! this->stopped) + { + this->logger->log("Stopping GPS thread..."); + this->should_stop = true; + while ( ! this->stopped) this_thread::sleep_for(1ms); + this->logger->log("GPS thread stopped"); + } + + if (this->serial->is_open()) + { + this->logger->log("Closing serial interface..."); + this->serial->close(); + this->logger->log("Serial interface closed."); + this->logger->log("Deallocating serial..."); + delete this->serial; + this->logger->log("Serial deallocated"); + + this->logger->log("Deallocating frame logger..."); + delete this->frame_logger; + this->logger->log("Frame logger deallocated"); + } + this->logger->log("Turning off GPS..."); + this->turn_off(); + this->logger->log("GPS off."); + delete this->logger; } -void GPS::initialize(const string& serial_URL) +bool GPS::initialize() { - this->serial.initialize(serial_URL, 9600, "\r\n", bind(&GPS::parse, this, placeholders::_1)); + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + this->logger = new Logger("data/logs/GPS/GPS."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "GPS"); + + this->frame_logger = new Logger("data/logs/GPS/GPSFrames."+ to_string(now->tm_year+1900) +"-"+ + to_string(now->tm_mon) +"-"+ to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ + to_string(now->tm_min) +"-"+ to_string(now->tm_sec) +".log", "GPSFrame"); + + this->should_stop = false; + this->stopped = true; #ifndef OS_TESTING - this->serial.send_frame("$PMTK220,100*2F"); - this->serial.send_frame("$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"); + pinMode(GPS_ENABLE_GPIO, OUTPUT); + + this->logger->log("Turning GPS on..."); + this->turn_on(); + this->logger->log("GPS on."); #endif + + this->logger->log("Starting serial connection..."); + this->serial = new Serial(GPS_UART, GPS_BAUDRATE, "GPS"); + if ( ! this->serial->is_open()) { + this->logger->log("GPS serial error."); + return false; + } + this->logger->log("Serial connection started."); + + this->logger->log("Starting GPS frame thread..."); + thread t(&GPS::gps_thread, this); + t.detach(); + this->logger->log("GPS frame thread running."); + + #ifndef OS_TESTING + this->logger->log("Sending configuration frames..."); + this->serial->println("$PMTK220,100*2F"); + this->frame_logger->log("Sent: $PMTK220,100*2F"); + this->serial->println("$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"); + this->frame_logger->log("Sent: $PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"); + this->logger->log("Configuration frames sent."); + #endif + + return true; } -uint_fast8_t GPS::parse(const string& frame) +bool GPS::turn_on() const { - string frame_type = frame.substr(1, frame.find_first_of(',')-1); + if (digitalRead(GPS_ENABLE_GPIO) == LOW) + { + digitalWrite(GPS_ENABLE_GPIO, HIGH); + return true; + } + else + { + this->logger->log("Error: Turning on GPS but GPS already on."); + return false; + } +} - if (frame_type == "GPGGA") +bool GPS::turn_off() const +{ + if (digitalRead(GPS_ENABLE_GPIO) == HIGH) { - this->parse_GGA(frame); + digitalWrite(GPS_ENABLE_GPIO, LOW); + return true; } - else if (frame_type == "GPGSA") + else { - this->parse_GSA(frame); + this->logger->log("Error: Turning off GPS but GPS already off."); + return false; } - else if (frame_type == "GPRMC") +} + +void GPS::gps_thread() +{ + this->stopped = false; + string response; + + while( ! this->should_stop) { - this->parse_RMC(frame); + #ifndef OS_TESTING + int available = this->serial->available(); + + if (available > 0) + { + for (int i = 0; i < available; i++) + { + char c = this->serial->read_char(); + response += c; + if (response[response.length()-2] == '\r' && c == '\n') + { + response = response.substr(0, response.length()-2); + + if (response.at(0) == '$') + { + this->parse(response); + } + response = ""; + this_thread::sleep_for(50ms); + } + } + } + else if (available == 0) + { + this_thread::sleep_for(50ms); + } + else if (available < 0) + { + this->logger->log("Error: Serial available < 0."); + } + #else + this_thread::sleep_for(50ms); + #endif } + this->logger->log("Should-stop flag noticed."); + this->stopped = true; +} + +bool GPS::is_valid(string frame) +{ + regex frame_regex("\\$[A-Z][0-9A-Z\\.,-]*\\*[0-9A-F]{1,2}"); + if ( ! regex_match(frame, frame_regex)) return false; - return ERR_OK; + uint_fast8_t checksum = 0; + for (char c : frame) + { + if (c == '$') continue; + if (c == '*') break; + + checksum ^= c; + } + uint_fast8_t frame_cs = stoi(frame.substr(frame.rfind('*')+1, frame.length()-frame.rfind('*')-1), 0, 16); + + return checksum == frame_cs; +} + +void GPS::parse(const string& frame) +{ + if (frame.length() > 1 && is_valid(frame)) + { + this->frame_logger->log(frame); + string frame_type = frame.substr(1, frame.find_first_of(',')-1); + + if (frame_type == "GPGGA") + { + this->parse_GGA(frame); + } + else if (frame_type == "GPGSA") + { + this->parse_GSA(frame); + } + else if (frame_type == "GPRMC") + { + this->parse_RMC(frame); + } + } } void GPS::parse_GGA(const string& frame) @@ -60,8 +225,7 @@ void GPS::parse_GGA(const string& frame) vector s_data; // We put all fields in a vector - while(getline(ss, data, ',')) - s_data.push_back(data); + while(getline(ss, data, ',')) s_data.push_back(data); // Is the data valid? this->active = s_data[6] > "0"; @@ -105,6 +269,7 @@ void GPS::parse_GSA(const string& frame) if (this->active) { // Update DOP + this->pdop = stof(s_data[15]); this->hdop = stof(s_data[16]); this->vdop = stof(s_data[17].substr(0, s_data[17].find_first_of('*'))); } @@ -130,7 +295,7 @@ void GPS::parse_RMC(const string& frame) this->time.tm_sec = stoi(s_data[1].substr(4, 2)); this->time.tm_mday = stoi(s_data[9].substr(0, 2)); - this->time.tm_mon = stoi(s_data[9].substr(2, 2)); + this->time.tm_mon = stoi(s_data[9].substr(2, 2))-1; this->time.tm_year = stoi(s_data[9].substr(4, 2))+100; // Update latitude diff --git a/gps/GPS.h b/gps/GPS.h index 5e44fea..3dac520 100644 --- a/gps/GPS.h +++ b/gps/GPS.h @@ -4,8 +4,10 @@ #include #include +#include #include "serial/Serial.h" +#include "logger/Logger.h" using namespace std; @@ -20,7 +22,12 @@ namespace os { class GPS { private: - Serial serial; + Serial* serial; + Logger* logger; + Logger* frame_logger; + + atomic_bool should_stop; + atomic_bool stopped; tm time; bool active; @@ -28,12 +35,14 @@ namespace os { double latitude; double longitude; double altitude; + float pdop; float hdop; float vdop; euc_vec velocity; GPS() = default; - ~GPS(); + + void gps_thread(); void parse_GGA(const string& frame); void parse_GSA(const string& frame); @@ -41,20 +50,25 @@ namespace os { public: GPS(GPS& copy) = delete; + ~GPS(); static GPS& get_instance(); + static bool is_valid(string frame); + + tm get_time() const {return this->time;} + bool is_fixed() const {return this->active;} + uint_fast8_t get_satellites() const {return this->satellites;} + double get_latitude() const {return this->latitude;} + double get_longitude() const {return this->longitude;} + double get_altitude() const {return this->altitude;} + float get_PDOP() const {return this->pdop;} + float get_HDOP() const {return this->hdop;} + float get_VDOP() const {return this->vdop;} + euc_vec get_velocity() const {return this->velocity;} - tm* get_time() {return &this->time;} - bool is_active() {return this->active;} - uint_fast8_t get_satellites() {return this->satellites;} - double get_latitude() {return this->latitude;} - double get_longitude() {return this->longitude;} - double get_altitude() {return this->altitude;} - float get_HDOP() {return this->hdop;} - float get_VDOP() {return this->vdop;} - euc_vec* get_velocity() {return &this->velocity;} - - void initialize(const string& serial_URL); - uint_fast8_t parse(const string& frame); + bool initialize(); + bool turn_on() const; + bool turn_off() const; + void parse(const string& frame); }; } diff --git a/gsm/GSM.cc b/gsm/GSM.cc new file mode 100644 index 0000000..14d97fe --- /dev/null +++ b/gsm/GSM.cc @@ -0,0 +1,424 @@ +#include "gsm/GSM.h" +#include "constants.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +using namespace std; +using namespace os; + +GSM& GSM::get_instance() +{ + static GSM instance; + return instance; +} + +GSM::~GSM() +{ + if (this->serial->is_open()) + { + this->logger->log("Closing serial interface..."); + this->serial->close(); + this->logger->log("Serial interface closed."); + this->logger->log("Deallocating serial..."); + delete this->serial; + this->logger->log("Serial deallocated"); + + this->logger->log("Deallocating command logger..."); + delete this->command_logger; + } + + if (this->get_status()) + { + this->logger->log("Shutting down..."); + this->turn_off(); + this->logger->log("Shut down finished."); + } + delete this->logger; +} + +bool GSM::initialize() +{ + this->occupied = false; + + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + this->logger = new Logger("data/logs/GSM/GSM."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "GSM"); + + this->command_logger = new Logger("data/logs/GSM/GSMCommands."+ to_string(now->tm_year+1900) +"-"+ + to_string(now->tm_mon) +"-"+ to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ + to_string(now->tm_min) +"-"+ to_string(now->tm_sec) +".log", "GSMCommand"); + + pinMode(GSM_PWR_GPIO, OUTPUT); + digitalWrite(GSM_PWR_GPIO, HIGH); + pinMode(GSM_STATUS_GPIO, INPUT); + + this->logger->log("Rebooting module for stability."); + this->turn_off(); + this->logger->log("Module off. Sleeping 3 seconds before turning it on..."); + this_thread::sleep_for(3s); + + this->logger->log("Turning module on..."); + this->turn_on(); + if (this->get_status()) + { + this->logger->log("Status checked. Module is on."); + } + else + { + this->logger->log("Error: Status checked. Module is off. Finishing initialization."); + return false; + } + this->logger->log("Sleeping 3 seconds to let it turn completely on..."); + this_thread::sleep_for(3s); + + this->occupied = true; + this->logger->log("Starting serial connection..."); + this->serial = new Serial(GSM_UART, GSM_BAUDRATE, "GSM"); + if ( ! this->serial->is_open()) + { + this->logger->log("GSM serial error."); + this->occupied = false; + return false; + } + this->logger->log("Serial connection started."); + + this->logger->log("Deleting possible serial characters..."); + this->serial->flush(); + + this->logger->log("Checking OK initialization (3 times)..."); + if (this->send_command_read("AT") != "OK") + this->logger->log("Not initialized."); + this_thread::sleep_for(100ms); + + if (this->send_command_read("AT") != "OK") + this->logger->log("Not initialized."); + this_thread::sleep_for(100ms); + + if (this->send_command_read("AT") != "OK") + { + this->logger->log("Error on initialization."); + this->occupied = false; + return false; + } + this_thread::sleep_for(100ms); + this->logger->log("Initialization OK."); + this->occupied = false; + + return true; +} + +bool GSM::send_SMS(const string& message, const string& number) +{ + while (this->occupied) this_thread::sleep_for(10ms); + this->occupied = true; + + this->logger->log("Sending SMS: \""+message+"\" ("+ to_string(message.length()) +" characters) to number "+number+"."); + if (message.length() > 160) + { + this->logger->log("Error: SMS has more than 10 characters"); + } + else + { + #ifndef NO_SMS + if (this->send_command_read("AT+CMGF=1") != "OK") + { + this->logger->log("Error sending SMS on 'AT+CMGD=1' response."); + this->occupied = false; + return false; + } + + if (this->send_command_read("AT+CMGS=\""+number+"\"") != "> ") + { + this->logger->log("Error sending SMS on 'AT+CMGS' response."); + this->occupied = false; + return false; + } + + this->serial->println(message); + this->command_logger->log("Sent: '"+message+"'"); + + for (int i = 0; i <= std::count(message.begin(), message.end(), '\n'); i++) + this->command_logger->log("Received: '"+this->serial->read_line()+"'"); // Eat message echo + + this->serial->println(); + this->serial->read_line(); // Eat prompt + this->serial->write('\x1A'); + this->serial->read_line(60); // Eat prompt (timeout 60 seconds) + + // Read +CMGS response + string response = this->serial->read_line(); + this->command_logger->log("Received: '"+response+"'"); + if (response.find("+CMGS") == string::npos) + { + this->logger->log("Error sending SMS. Could not read '+CMGS'."); + this->occupied = false; + return false; + } + this->serial->read_line(); // Eat new line + + // Read OK (timeout 10 seconds) + response = this->serial->read_line(10); + this->command_logger->log("Received: '"+response+"'"); + if (response != "OK") + { + this->logger->log("Error sending SMS. Could not read 'OK'."); + this->occupied = false; + return false; + } + #else + this_thread::sleep_for(5s); + #endif + } + this->occupied = false; + + this->logger->log("SMS sent."); + return true; +} + +bool GSM::get_location(double& latitude, double& longitude) +{ + while (this->occupied) this_thread::sleep_for(10ms); + this->occupied = true; + + if (this->send_command_read("AT+CMGF=1") != "OK") + { + this->logger->log("Error getting location on 'AT+CMGD=1' response."); + this->occupied = false; + return false; + } + + if (this->send_command_read("AT+CGATT=1") != "OK") + { + this->logger->log("Error getting location on 'AT+CGATT=1' response."); + if (this->send_command_read("AT+SAPBR=0,1") != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return false; + } + + if (this->send_command_read("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"") != "OK") + { + this->logger->log("Error getting location on 'AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\"' response."); + if (this->send_command_read("AT+SAPBR=0,1") != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return false; + } + + if (this->send_command_read("AT+SAPBR=3,1,\"APN\",\""+string(GSM_LOC_SERV)+"\"") != "OK") + { + this->logger->log("Error getting location on 'AT+SAPBR=3,1,\"APN\",\""+string(GSM_LOC_SERV)+"\"' response."); + if (this->send_command_read("AT+SAPBR=0,1") != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return false; + } + + this->serial->println("AT+SAPBR=1,1"); + this->command_logger->log("Sent: 'AT+SAPBR=1,1'"); + this->serial->read_line(2); // Eat message echo + + string response = this->serial->read_line(); + this->command_logger->log("Received: '"+ response +"'"); + + if (response != "OK") + { + this->logger->log("Error getting location on 'AT+SAPBR=1,1' response."); + if (this->send_command_read("AT+SAPBR=0,1") != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return false; + } + + this->serial->println("AT+CIPGSMLOC=1,1"); + this->serial->read_line(10); // Eat message echo + response = this->serial->read_line(); + this->command_logger->log("Received: '"+ response +"'"); + + stringstream ss(response); + string data; + vector s_data; + + // We put all fields in a vector + while(getline(ss, data, ',')) s_data.push_back(data); + + latitude = stod(s_data[2]); + longitude = stod(s_data[1]); + + this->serial->read_line(); // Eat new line + response = this->serial->read_line(); + this->command_logger->log("Received: '"+ response +"'"); + if (response == "ERROR" || response != "OK") + { + this->logger->log("Error getting location on 'AT+CIPGSMLOC=1,1' response."); + if (this->send_command_read("AT+SAPBR=0,1") != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return false; + } + + this->serial->println("AT+SAPBR=0,1"); + this->serial->read_line(2); // Eat message echo + response = this->serial->read_line(); + this->command_logger->log("Received: '"+ response +"'"); + if (response != "OK") + this->logger->log("Error turning GPRS down."); + else + this->logger->log("GPRS off."); + + this->occupied = false; + return true; +} + +bool GSM::get_status() const +{ + return digitalRead(GSM_STATUS_GPIO) == HIGH; +} + +bool GSM::get_battery_status(double& main_bat_percentage, double& gsm_bat_percentage) +{ + while (this->occupied) this_thread::sleep_for(10ms); + this->occupied = true; + + this->logger->log("Checking Battery status."); + if (this->get_status()) + { + string gsm_response = this->send_command_read("AT+CBC"); + this->serial->read_line(); // Eat new line + this->serial->read_line(); // Eat OK + string adc_response = this->send_command_read("AT+CADC?"); + this->serial->read_line(); // Eat new line + this->serial->read_line(); // Eat OK + while (adc_response != "" && adc_response.substr(0, 6) != "+CADC:") + adc_response = this->serial->read_line(); + + if (gsm_response.substr(0, 5) == "+CBC:" && adc_response.substr(0, 6) == "+CADC:") + { + stringstream gsm_ss(gsm_response); + string data; + vector gsm_data; + + while(getline(gsm_ss, data, ',')) gsm_data.push_back(data); + + int gsm_bat_voltage = stoi(gsm_data[2]); + int main_bat_voltage = stoi(adc_response.substr(9, 4)); + gsm_bat_percentage = (gsm_bat_voltage/1000.0-BAT_GSM_MIN)/(BAT_GSM_MAX-BAT_GSM_MIN); + main_bat_percentage = (main_bat_voltage/1000.0-BAT_MAIN_MIN)/(BAT_MAIN_MAX-BAT_MAIN_MIN); + + this->occupied = false; + return true; + } + } + else + { + this->logger->log("Error: module is off."); + } + this->occupied = false; + return false; +} + +bool GSM::has_connectivity() +{ + while (this->occupied) this_thread::sleep_for(10ms); + this->occupied = true; + string response = this->send_command_read("AT+CREG?"); + this->serial->read_line(); // Eat new line + this->serial->read_line(); // Eat OK + this->occupied = false; + + return response == "+CREG: 0,1" || response == "+CREG: 0,5"; +} + +bool GSM::turn_on() const +{ + if ( ! this->get_status()) + { + this->logger->log("Turning GSM on..."); + + digitalWrite(GSM_PWR_GPIO, LOW); + this_thread::sleep_for(2s); + digitalWrite(GSM_PWR_GPIO, HIGH); + + this_thread::sleep_for(3s); + + this->logger->log("GSM on."); + return true; + } + else + { + this->logger->log("Error: Turning on GSM but GSM already on."); + return false; + } +} + +bool GSM::turn_off() const +{ + if (this->get_status()) + { + this->logger->log("Turning GSM off..."); + + digitalWrite(GSM_PWR_GPIO, LOW); + this_thread::sleep_for(2s); + digitalWrite(GSM_PWR_GPIO, HIGH); + + this_thread::sleep_for(3s); + + this->logger->log("GSM off."); + return true; + } + else + { + this->logger->log("Error: Turning off GSM but GSM already off."); + return false; + } +} + +const string GSM::send_command_read(const string& command) const +{ + this->command_logger->log("Sent: '"+command+"'"); + this->serial->flush(); + this->serial->println(command); + string response = this->serial->read_line(); + // Trimming + string ltrim = response.erase(0, response.find_first_not_of("\r\n\t")); + response = ltrim.erase(ltrim.find_last_not_of("\r\n\t")+1); + + if (response == command) // Sent command + { + response = this->serial->read_line(); + // Trimming + string ltrim = response.erase(0, response.find_first_not_of("\r\n\t")); + response = ltrim.erase(ltrim.find_last_not_of("\r\n\t")+1); + } + + this->command_logger->log("Received: '"+response+"'"); + return response; +} diff --git a/gsm/GSM.h b/gsm/GSM.h new file mode 100644 index 0000000..a489278 --- /dev/null +++ b/gsm/GSM.h @@ -0,0 +1,44 @@ +#ifndef GSM_GMS_H_ +#define GSM_GSM_H_ + +#include +#include + +#include "serial/Serial.h" +#include "logger/Logger.h" + +using namespace std; + +namespace os { + + class GSM + { + private: + Serial* serial; + Logger* logger; + Logger* command_logger; + + int fh; + atomic_bool occupied; + + GSM() = default; + + const string send_command_read(const string& command) const; + bool init_GPRS() const; + bool tear_down_GPRS() const; + public: + GSM(GSM& copy) = delete; + ~GSM(); + static GSM& get_instance(); + + bool initialize(); + bool send_SMS(const string& message, const string& number); + bool get_location(double& latitude, double& longitude); + bool get_status() const; + bool get_battery_status(double& main_bat_percentage, double& gsm_bat_percentage); + bool has_connectivity(); + bool turn_on() const; + bool turn_off() const; + }; +} +#endif diff --git a/install-wiringpi.sh b/install-wiringpi.sh index 9026a01..89ceca6 100644 --- a/install-wiringpi.sh +++ b/install-wiringpi.sh @@ -3,5 +3,5 @@ printf "Installing WiringPi\n" git clone https://github.com/OpenStratos/WiringPi.git > /dev/null cd WiringPi -./build > /dev/null -cd .. \ No newline at end of file +sudo ./build > /dev/null +cd .. diff --git a/logger/Logger.cc b/logger/Logger.cc new file mode 100644 index 0000000..3d905ad --- /dev/null +++ b/logger/Logger.cc @@ -0,0 +1,34 @@ +#include "logger/Logger.h" + +#include +#include + +#include + +using namespace std; +using namespace os; + +Logger::Logger(const string& path, const string& prefix) +{ + this->log_stream.open(path); + this->log_prefix = prefix; + this->log("Logging started."); +} + +Logger::~Logger() +{ + this->log_stream.close(); +} + +void Logger::log(const string& message) +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + this->log_stream << "[" << log_prefix << "] - " << setfill('0') << setw(2) << now->tm_mon << "/" << + setfill('0') << setw(2) << now->tm_mday << "/" << (now->tm_year+1900) << " " << + setfill('0') << setw(2) << now->tm_hour << ":" << setfill('0') << setw(2) << now->tm_min << ":" << + setfill('0') << setw(2) << now->tm_sec << "." << setfill('0') << setw(6) << timer.tv_usec << + " - " << message << endl; +} diff --git a/logger/Logger.h b/logger/Logger.h new file mode 100644 index 0000000..0c56b9d --- /dev/null +++ b/logger/Logger.h @@ -0,0 +1,26 @@ +#ifndef LOGGER_LOGGER_H_ +#define LOGGER_LOGGER_H_ + +#include +#include + +using namespace std; + +namespace os { + + class Logger + { + private: + ofstream log_stream; + string log_prefix; + public: + Logger() = delete; + Logger(Logger& copy) = delete; + ~Logger(); + + Logger(const string& path, const string& prefix); + void log(const string& message); + }; +} + +#endif // LOGGER_LOGGER_H_ diff --git a/openstratos-root.cc b/openstratos-root.cc deleted file mode 100644 index fab1860..0000000 --- a/openstratos-root.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include "openstratos.h" - -int main(void) -{ - cout << "[OpenStratos][ROOT] Starting" << endl; - cout << "[OpenStratos][ROOT] Starting checks (not really)" << endl; - return 0; -} \ No newline at end of file diff --git a/openstratos.cc b/openstratos.cc index 0e6c886..7be98ce 100644 --- a/openstratos.cc +++ b/openstratos.cc @@ -2,7 +2,1218 @@ int main(void) { - cout << "[OpenStratos] Starting" << endl; - cout << "[OpenStratos] Starting checks (not really)" << endl; + #ifdef DEBUG + #ifdef SIM + cout << "[OpenStratos] Simulation." << endl; + #endif + + #ifdef REAL_SIM + cout << "[OpenStratos] Realistic simulation." << endl; + #endif + + cout << "[OpenStratos] Starting..." << endl; + #endif + + if ( ! file_exists(STATE_FILE)) + { + #ifdef DEBUG + cout << "[OpenStratos] No state file. Starting main logic..." << endl; + #endif + main_logic(); + } + else + { + #ifdef DEBUG + cout << "[OpenStratos] State file found. Starting safe mode..." << endl; + #endif + safe_mode(); + } + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #endif + return 0; -} \ No newline at end of file +} + +void os::main_logic() +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm* now = gmtime(&timer.tv_sec); + + #ifdef DEBUG + cout << "[OpenStratos] Current time: " << setfill('0') << setw(2) << now->tm_hour << ":" << + setfill('0') << setw(2) << now->tm_min << ":" << setfill('0') << setw(2) << now->tm_sec << + " UTC of " << setfill('0') << setw(2) << now->tm_mon << "/" << + setfill('0') << setw(2) << now->tm_mday << "/" << (now->tm_year+1900) << endl; + #endif + + check_or_create("data"); + State state = set_state(INITIALIZING); + + check_or_create("data/logs"); + check_or_create("data/logs/main"); + check_or_create("data/logs/system"); + check_or_create("data/logs/camera"); + check_or_create("data/logs/GPS"); + check_or_create("data/logs/GSM"); + + #ifdef DEBUG + cout << "[OpenStratos] Starting logger..." << endl; + #endif + + Logger logger("data/logs/main/OpenStratos."+ to_string(now->tm_year+1900) +"-"+ + to_string(now->tm_mon) +"-"+ to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ + to_string(now->tm_min) +"-"+ to_string(now->tm_sec) +".log", "OpenStratos"); + + #ifdef DEBUG + cout << "[OpenStratos] Logger started." << endl; + #endif + + logger.log("Starting system thread..."); + thread system_thread(&system_thread_fn, ref(state)); + logger.log("System thread started."); + + initialize(&logger, now); + + logger.log("Starting battery thread..."); + thread battery_thread(&battery_thread_fn, ref(state)); + logger.log("Battery thread started."); + + logger.log("Starting picture thread..."); + thread picture_thread(&picture_thread_fn, ref(state)); + logger.log("Picture thread started."); + + state = set_state(ACQUIRING_FIX); + logger.log("State changed to "+ state_to_string(state) +"."); + + main_while(&logger, &state); + + logger.log("Joining threads..."); + picture_thread.join(); + battery_thread.join(); + system_thread.join(); + logger.log("Threads joined."); + + shut_down(&logger); +} + +void os::safe_mode() +{ + State last_state = get_last_state(); + State state = set_state(SAFE_MODE); + Logger* logger; + int count = 0; + double latitude = 0, longitude = 0; + + check_or_create("data/logs"); + check_or_create("data/logs/main"); + check_or_create("data/logs/system"); + check_or_create("data/logs/camera"); + check_or_create("data/logs/GPS"); + check_or_create("data/logs/GSM"); + + if (last_state > ACQUIRING_FIX) + { + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm* now = gmtime(&timer.tv_sec); + + logger = new Logger("data/logs/main/OpenStratos."+ to_string(now->tm_year+1900) +"-"+ + to_string(now->tm_mon) +"-"+ to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ + to_string(now->tm_min) +"-"+ to_string(now->tm_sec) +".log", "OpenStratos"); + } + + switch (last_state) + { + case INITIALIZING: + case ACQUIRING_FIX: + remove(STATE_FILE); + #ifndef NO_POWER_OFF + sync(); + reboot(RB_AUTOBOOT); + #else + exit(0); + #endif + break; + case FIX_ACQUIRED: // It could be that the SMS was sent but the state didn't change + case WAITING_LAUNCH: + case GOING_UP: + case GOING_DOWN: + case LANDED: + logger->log("Initializing WiringPi..."); + wiringPiSetup(); + logger->log("WiringPi initialized."); + + logger->log("Initializing GPS..."); + while ( ! GPS::get_instance().initialize() && ++count < 5) + logger->log("GPS initialization error."); + + if (count < 5) + { + logger->log("GPS initialized."); + count = 0; + logger->log("Waiting for GPS fix..."); + while ( ! GPS::get_instance().is_fixed() && ++count < 100) + this_thread::sleep_for(10s); + + if (count == 100) + { + logger->log("Not getting fix. Going to recovery mode."); + delete logger; + #ifndef NO_POWER_OFF + sync(); + reboot(RB_AUTOBOOT); + #else + exit(1); + #endif + } + + logger->log("GPS fix acquired."); + this_thread::sleep_for(1min); + state = (state == LANDED) ? LANDED : get_real_state(); + + logger->log("Initializing GSM..."); + if ( ! GSM::get_instance().initialize()) + { + logger->log("GSM initialization error. Going to recovery mode."); + delete logger; + #ifndef NO_POWER_OFF + sync(); + reboot(RB_AUTOBOOT); + #else + exit(1); + #endif + } + logger->log("GSM initialized."); + + if (state != LANDED) + { + logger->log("Trying to start recording..."); + if (Camera::get_instance().record()) + logger->log("Recording."); + else + logger->log("Error starting recording"); + } + + main_while(logger, &state); + shut_down(logger); + } + else + { + logger->log("Error initializing GPS. Going to recovery mode."); + delete logger; + #ifndef NO_POWER_OFF + sync(); + reboot(RB_AUTOBOOT); + #else + exit(1); + #endif + } + break; + case SHUT_DOWN: + shut_down(logger); + break; + case SAFE_MODE: + logger->log("Recovery mode"); + + logger->log("Initializing WiringPi..."); + wiringPiSetup(); + logger->log("WiringPi initialized."); + + logger->log("Initializing GSM..."); + while ( ! GSM::get_instance().initialize()) + logger->log("GSM initialization error."); + logger->log("GSM initialized"); + logger->log("Waiting for GSM connectivity..."); + while ( ! GSM::get_instance().has_connectivity()) this_thread::sleep_for(5s); + logger->log("GSM connected."); + + logger->log("Sending mayday messages..."); + for (count = 0; count < 10;) + { + this_thread::sleep_for(20s); + + GSM::get_instance().get_location(latitude, longitude); + GSM::get_instance().send_SMS("MAYDAY\r\nLat: "+ to_string(latitude) +"\r\n"+ + "Lon: "+ to_string(longitude), SMS_PHONE) && ++count; + } + logger->log("Mayday messages sent."); + + logger->log("Initializing GPS..."); + count = 0; + while ( ! GPS::get_instance().initialize() && ++count < 5) + logger->log("GPS initialization error."); + + if (count < 5) + { + logger->log("GPS initialized."); + count = 0; + logger->log("Waiting for GPS fix..."); + while ( ! GPS::get_instance().is_fixed() && ++count < 100) + this_thread::sleep_for(10s); + + if (count == 100) + { + logger->log("Not getting fix."); + shut_down(logger); + } + + logger->log("GPS fix acquired."); + this_thread::sleep_for(1min); + state = (state == LANDED) ? LANDED : get_real_state(); + + main_while(logger, &state); + shut_down(logger); + } + else + { + shut_down(logger); + } + } + + if (logger) delete logger; +} + +void os::main_while(Logger* logger, State* state) +{ + double launch_altitude; + + while (*state != SHUT_DOWN) + { + if (*state == ACQUIRING_FIX) + { + aquire_fix(logger); + *state = set_state(FIX_ACQUIRED); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else if (*state == FIX_ACQUIRED) + { + this_thread::sleep_for(2min); + logger->log("Sleeping 2 minutes for fix stabilization."); + + start_recording(logger); + send_init_sms(logger); + *state = set_state(WAITING_LAUNCH); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else if (*state == WAITING_LAUNCH) + { + wait_launch(logger, launch_altitude); + *state = set_state(GOING_UP); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else if (*state == GOING_UP) + { + go_up(logger, launch_altitude); + *state = set_state(GOING_DOWN); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else if (*state == GOING_DOWN) + { + go_down(logger); + *state = set_state(LANDED); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else if (*state == LANDED) + { + land(logger); + *state = set_state(SHUT_DOWN); + logger->log("State changed to "+ state_to_string(*state) +"."); + } + else + { + #ifndef NO_POWER_OFF + sync(); + reboot(RB_AUTOBOOT); + #else + exit(1); + #endif + } + } +} + +void os::initialize(Logger* logger, tm* now) +{ + check_or_create("data/video", logger); + check_or_create("data/img", logger); + + float available_disk_space = get_available_disk_space(); + + logger->log("Available disk space: " + to_string(available_disk_space/1073741824) + " GiB"); + if (available_disk_space < (FLIGHT_LENGTH*1.25+0.5)*7549747200) + { + logger->log("Error: Not enough disk space."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + + logger->log("Disk space enough for about " + to_string(available_disk_space/7549747200) + + " hours of fullHD video."); + + logger->log("Initializing WiringPi..."); + wiringPiSetup(); + logger->log("WiringPi initialized."); + + logger->log("Initializing GPS..."); + if ( ! GPS::get_instance().initialize()) + { + logger->log("GPS initialization error."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + logger->log("GPS initialized."); + + logger->log("Initializing GSM..."); + if ( ! GSM::get_instance().initialize()) + { + logger->log("GSM initialization error."); + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + logger->log("GSM initialized."); + + logger->log("Checking batteries..."); + double main_battery, gsm_battery; + if ( ! GSM::get_instance().get_battery_status(main_battery, gsm_battery) && + ! GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + { + logger->log("Error checking batteries."); + + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + + logger->log("Batteries checked => Main battery: "+ (main_battery > -1 ? to_string(main_battery*100)+"%" : "disconnected") + + " - GSM battery: "+ to_string(gsm_battery*100) +"%"); + + if ((main_battery < 0.9 && main_battery > -1) || gsm_battery < 0.85) + { + logger->log("Error: Not enough battery."); + + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + + logger->log("Waiting for GSM connectivity..."); + while ( ! GSM::get_instance().has_connectivity()) + this_thread::sleep_for(1s); + + logger->log("GSM connected."); + + logger->log("Testing camera recording..."); + #ifndef RASPICAM + logger->log("Error: No raspivid found. Is this a Raspberry?"); + exit(1); + #endif + logger->log("Recording 10 seconds as test..."); + if ( ! Camera::get_instance().record(10000)) + { + logger->log("Error starting recording"); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + this_thread::sleep_for(11s); + if (file_exists("data/video/test.h264")) + { + logger->log("Camera test OK."); + + logger->log("Removing test file..."); + if (remove("data/video/test.h264")) + logger->log("Error removing test file."); + else + logger->log("Test file removed."); + } + else + { + logger->log("Test recording error."); + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } +} + +void os::aquire_fix(Logger* logger) +{ + while ( ! GPS::get_instance().is_fixed()) + this_thread::sleep_for(1s); + + logger->log("GPS fix acquired, waiting for date change."); + this_thread::sleep_for(2s); + + struct timezone tz = {0, 0}; + tm gps_time = GPS::get_instance().get_time(); + struct timeval tv = {timegm(&gps_time), 0}; + settimeofday(&tv, &tz); + + logger->log("System date change."); +} + +void os::start_recording(Logger* logger) +{ + logger->log("Starting video recording..."); + if ( ! Camera::get_instance().record()) + { + logger->log("Error starting recording"); + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + logger->log("Recording started."); +} + +void os::send_init_sms(Logger* logger) +{ + double main_battery = 0, gsm_battery = 0; + bool bat_status = false; + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Sending initialization SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Init: OK\r\nAlt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()) + + "\r\nWaiting Launch", SMS_PHONE)) + { + logger->log("Error sending initialization SMS."); + + logger->log("Stoping video recording."); + if (Camera::get_instance().stop()) + { + logger->log("Recording stopped."); + } + else + { + logger->log("Error stopping recording."); + } + + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + #ifndef NO_POWER_OFF + sync(); + reboot(RB_POWER_OFF); + #else + exit(1); + #endif + } + logger->log("Initialization SMS sent."); +} + +void os::wait_launch(Logger* logger, double& launch_altitude) +{ + logger->log("Waiting for launch..."); + launch_altitude = GPS::get_instance().get_altitude(); + #ifdef SIM + this_thread::sleep_for(2min); + #endif + #ifdef REAL_SIM + this_thread::sleep_for(10min); + #endif + + while ( ! has_launched(launch_altitude)) + this_thread::sleep_for(1s); + + logger->log("Balloon launched."); +} + +void os::go_up(Logger* logger, double launch_altitude) +{ + double main_battery = 0, gsm_battery = 0; + bool bat_status = false; + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Trying to send launch confirmation SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Launch\r\nAlt: "+ to_string((int) launch_altitude) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending launch confirmation SMS."); + } + else + { + logger->log("Launch confirmation SMS sent."); + } + + double maximum_altitude = 0; + double current_altitude = GPS::get_instance().get_altitude(); + + #if !defined SIM && !defined REAL_SIM + while (current_altitude = GPS::get_instance().get_altitude() < 1200) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + this_thread::sleep_for(2s); + } + #else + this_thread::sleep_for(124s); + #endif + logger->log("1.2 km mark."); + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Trying to send \"going up\" SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Alt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE) && + // Second attempt + ! GSM::get_instance().send_SMS( + "Alt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending \"going up\" SMS."); + } + else + { + logger->log("\"Going up\" SMS sent."); + } + + logger->log("Turning off GSM..."); + GSM::get_instance().turn_off(); + logger->log("GSM off."); + + bool bursted = false; + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("5 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1357s); + logger->log("5 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 5000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("5 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("10 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1786s); + logger->log("10 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 10000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("10 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("15 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1786s); + logger->log("15 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 15000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("15 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("20 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1786s); + logger->log("20 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 20000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("20 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("25 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1786s); + logger->log("25 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 25000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("25 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + logger->log("30 km mark passed going up."); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1786s); + logger->log("30 km mark passed going up."); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 30000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("30 km mark passed going up."); + else return; + #endif + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1740s); + #else + while ( ! (bursted = has_bursted(maximum_altitude)) && + (current_altitude = GPS::get_instance().get_altitude()) < 35000) + { + if (current_altitude > maximum_altitude) maximum_altitude = current_altitude; + } + if ( ! bursted) logger->log("35 km mark passed going up."); + else return; + #endif + + while ( ! has_bursted(maximum_altitude)) + { + if ((current_altitude = GPS::get_instance().get_altitude()) > maximum_altitude) + maximum_altitude = current_altitude; + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + + logger->log("Balloon burst at about "+ to_string((int) maximum_altitude) +" m."); +} + +void os::go_down(Logger* logger) +{ + double main_battery = 0, gsm_battery = 0; + bool bat_status = false; + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(1min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(317s); + #else + while (GPS::get_instance().get_altitude() > 25000) + { + this_thread::sleep_for(5s); + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + + logger->log("25 km mark passed going down."); + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(1min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(684s); + #else + while (GPS::get_instance().get_altitude() > 15000) + { + this_thread::sleep_for(5s); + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + + logger->log("15 km mark passed going down."); + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(2min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(1450s); + #else + while (GPS::get_instance().get_altitude() > 5000) + { + this_thread::sleep_for(5s); + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + + logger->log("5 km mark passed going down."); + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(1min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(650s); + #else + while (GPS::get_instance().get_altitude() > 2000) + { + this_thread::sleep_for(5s); + + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + logger->log("2 km mark passed going down."); + + logger->log("Turning on GSM..."); + GSM::get_instance().turn_on(); + + logger->log("Waiting for GSM connectivity..."); + int count = 0; + while ( ! GSM::get_instance().has_connectivity()) + { + if (count == 20) break; + this_thread::sleep_for(1s); + ++count; + } + if (count == 20) + { + logger->log("No connectivity, waiting for 1.2 km mark or landing."); + } + else + { + logger->log("GSM connected."); + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Trying to send first SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Alt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending first SMS."); + } + else + { + logger->log("First SMS sent."); + } + + bat_status = false; + } + + bool landed = false; + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(1min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(183s); + #else + while (GPS::get_instance().get_altitude() > 1200 && ! (landed = has_landed())) + { + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + + if ( ! landed) + { + logger->log("1.2 km mark passed going down."); + + count = 0; + while ( ! GSM::get_instance().has_connectivity()) + { + if (count == 20) break; + this_thread::sleep_for(1s); + ++count; + } + if (count == 20) + { + logger->log("No connectivity, waiting for 500 m mark or landing."); + } + else + { + logger->log("GSM connected."); + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Trying to send second SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Alt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending second SMS."); + } + else + { + logger->log("Second SMS sent."); + } + + bat_status = false; + } + } + + #if defined SIM && !defined REAL_SIM + this_thread::sleep_for(1min); + #elif defined REAL_SIM && !defined SIM + this_thread::sleep_for(117s); + #else + while (GPS::get_instance().get_altitude() > 500 && ! (landed = has_landed())) + { + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + #endif + + if ( ! landed) + { + logger->log("500 m mark passed going down."); + + count = 0; + while ( ! GSM::get_instance().has_connectivity()) + { + if (count > 15) break; + this_thread::sleep_for(1s); + ++count; + } + if ( ! GSM::get_instance().has_connectivity()) + { + logger->log("No connectivity, waiting for landing."); + } + else + { + logger->log("GSM connected."); + + logger->log("Getting battery values..."); + if (bat_status = GSM::get_instance().get_battery_status(main_battery, gsm_battery)) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + + logger->log("Trying to send third SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Alt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending third SMS."); + } + else + { + logger->log("Third SMS sent."); + } + } + } + + while ( ! has_landed()) + { + if (get_available_disk_space() < 2000000000) + { + logger->log("Not enough disk space. Stopping video..."); + Camera::get_instance().stop(); + } + } + logger->log("Landed."); +} + +void os::land(Logger* logger) +{ + logger->log("Stopping video..."); + if ( ! Camera::get_instance().stop()) + logger->log("Error stopping video."); + else + logger->log("Video stopped."); + + logger->log("Waiting 1 minute before sending landed SMS..."); + this_thread::sleep_for(1min); + + double main_battery = 0, gsm_battery = 0; + bool bat_status = false; + + logger->log("Getting battery values..."); + if (bat_status = (GSM::get_instance().get_battery_status(main_battery, gsm_battery) || + GSM::get_instance().get_battery_status(main_battery, gsm_battery))) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Sending landed SMS..."); + if ( ! GSM::get_instance().send_SMS( + "Landed\r\nAlt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE)) + { + logger->log("Error sending landed SMS. Trying again in 10 minutes..."); + } + else + { + logger->log("Landed SMS sent. Sending backup SMS in 10 minutes..."); + } + + this_thread::sleep_for(10min); + + logger->log("Getting battery values..."); + if (bat_status = (GSM::get_instance().get_battery_status(main_battery, gsm_battery) || + GSM::get_instance().get_battery_status(main_battery, gsm_battery))) + logger->log("Battery status received."); + else + logger->log("Error getting battery status."); + + logger->log("Sending second landed SMS..."); + while (( ! GSM::get_instance().send_SMS( + "Landed\r\nAlt: "+ to_string((int) GPS::get_instance().get_altitude()) + + " m\r\nLat: "+ to_string(GPS::get_instance().get_latitude()) +"\r\n"+ + "Lon: "+ to_string(GPS::get_instance().get_longitude()) +"\r\n"+ + (bat_status ? "Main bat: "+ to_string((int) (main_battery*100)) +"%\r\n"+ + "GSM bat: "+ to_string((int) (gsm_battery*100)) +"%\r\n" : "Bat: ERR\r\n") + + "Fix: "+ (GPS::get_instance().is_fixed() ? "OK" : "ERR") + + "\r\nSat: "+ to_string(GPS::get_instance().get_satellites()), SMS_PHONE) || + ! GPS::get_instance().is_fixed()) && + (main_battery >= 0 || main_battery < -1) && gsm_battery >= 0) + { + logger->log("Error sending second SMS or GPS without fix, trying again in 5 minutes."); + this_thread::sleep_for(5min); + GSM::get_instance().get_battery_status(main_battery, gsm_battery); + } + + if ((main_battery < 0 && main_battery > -1) || gsm_battery < 0) + { + logger->log("Not enough battery."); + logger->log("Main battery: "+ to_string(main_battery*100) + + "% - GSM battery: "+ to_string(gsm_battery*100) +"%"); + } + else + { + logger->log("Second SMS sent."); + } +} + +void os::shut_down(Logger* logger) +{ + logger->log("Shutting down..."); + + logger->log("Turning GSM off..."); + if (GSM::get_instance().turn_off()) + logger->log("GSM off."); + else + logger->log("Error turning GSM off."); + + logger->log("Turning GPS off..."); + if (GPS::get_instance().turn_off()) + logger->log("GPS off."); + else + logger->log("Error turning GPS off."); + + logger->log("Powering off..."); +} diff --git a/openstratos.h b/openstratos.h index edbd55e..deac4ad 100644 --- a/openstratos.h +++ b/openstratos.h @@ -1,10 +1,47 @@ #ifndef OPENSTRATOS_H_ #define OPENSTRATOS_H_ -#include "config.hpp" +#include +#include +#ifdef DEBUG + #include +#endif -#include +#include +#ifndef NO_POWER_OFF + #include + #include +#endif + +#include + +#include "config.h" +#include "constants.h" +#include "utils.h" +#include "threads.h" +#include "logger/Logger.h" +#include "gps/GPS.h" +#include "camera/Camera.h" +#include "gsm/GSM.h" + +namespace os +{ + void main_logic(); + void safe_mode(); + void main_while(Logger* logger, State* state); + + void initialize(Logger* logger, tm* now); + void aquire_fix(Logger* logger); + void start_recording(Logger* logger); + void send_init_sms(Logger* logger); + void wait_launch(Logger* logger, double& launch_altitude); + void go_up(Logger* logger, double launch_altitude); + void go_down(Logger* logger); + void land(Logger* logger); + void shut_down(Logger* logger); +} using namespace std; +using namespace os; -#endif // OPENSTRATOS_H_ \ No newline at end of file +#endif // OPENSTRATOS_H_ diff --git a/serial/Serial.cc b/serial/Serial.cc index d3dc457..146012e 100644 --- a/serial/Serial.cc +++ b/serial/Serial.cc @@ -3,116 +3,188 @@ #include #include -#include #include #include -#include + +#include #include +#include "constants.h" +#include "gps/GPS.h" +#ifdef DEBUG + #include "logger/Logger.h" +#endif + using namespace std; using namespace os; -void Serial::initialize(const string& url, int baud, const string endl, function) +Serial::Serial(const string& url, int baud_rate, const string& log_path) { - this->listener = listener; - this->endl = endl; - #ifndef OS_TESTING - this->fd = serialOpen(url.c_str(), baud); + this->open = false; + + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + #ifdef DEBUG + this->logger = new Logger("data/logs/"+log_path+"/Serial."+ to_string(now->tm_year+1900) +"-"+ + to_string(now->tm_mon) +"-"+ to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ + to_string(now->tm_min) +"-"+ to_string(now->tm_sec) +".log", "Serial"); #endif - this->open = true; - this->stopped = false; - thread t(&Serial::serial_thread, this); - t.detach(); + #ifndef OS_TESTING + this->fd = serialOpen(url.c_str(), baud_rate); + + #ifdef DEBUG + if (this->fd == -1) this->logger->log("Error: connection fd is -1."); + else this->open = true; + #else + if (this->fd != -1) this->open = true; + #endif + #endif } Serial::~Serial() { - this->close(); + if (this->open) + this->close(); + + #ifdef DEBUG + delete this->logger; + #endif } -void Serial::serial_thread() +void Serial::println(const string& str) const { - string frame; - int endl_pos = 0; + serialPuts(this->fd, (str+"\r\n").c_str()); - while(this->open) - { - #ifndef OS_TESTING - int available = serialDataAvail(this->fd); - - if (available > 0) - { - for (int i = 0; i < available; i++) - { - char c = serialGetchar(this->fd); - frame += c; - - if (c == this->endl[endl_pos]) ++endl_pos; - if (endl_pos == this->endl.length()) - { - frame = frame.substr(0, frame.length()-endl.length()); + #ifdef DEBUG + this->logger->log("Sent: '"+str+"\\r\\n'"); + #endif +} - if (this->is_valid(frame)) this->listener(frame); - // TODO decide what to do in case of non valid frame +void Serial::println() const +{ + serialPuts(this->fd, "\r\n"); - frame = ""; - endl_pos = 0; - this_thread::sleep_for(chrono::milliseconds(50)); - } - } - } - else if (available == 0) - { - this_thread::sleep_for(chrono::milliseconds(25)); - } - else if (available < 0) - { - // TODO log error - } - #endif - } - this->stopped = true; + #ifdef DEBUG + this->logger->log("Sent: '\\r\\n'"); + #endif } -uint_fast8_t Serial::send_frame(string frame) +void Serial::write(unsigned char c) const { - if (this->is_valid(frame)) - { - frame += endl; - serialPuts(this->fd, frame.c_str()); - } - else - { - // TODO log error - } + serialPutchar(this->fd, c); + + #ifdef DEBUG + this->logger->log("Sent char: '"+string(1, c)+"'"); + #endif } void Serial::close() { - this->open = false; - while( ! this->stopped); - #ifndef OS_TESTING - serialClose(this->fd); + if (this->open) { + serialClose(this->fd); + this->open = false; + } #endif } -bool Serial::is_valid(string frame) +bool Serial::is_open() const +{ + return this->open; +} + +int Serial::available() const { - regex frame_regex("\\$[A-Z][0-9A-Z\\.,-]*\\*[0-9A-F]{1,2}"); - if ( ! regex_match(frame, frame_regex)) return false; + return serialDataAvail(this->fd); +} + +char Serial::read_char() const +{ + return serialGetchar(this->fd); +} + +const string Serial::read_line() const +{ + return this->read_line(1); +} + +const string Serial::read_line(double timeout) const +{ + string response = ""; + string logstr = ""; + bool rfound = false, endl_found = false; + int available = 0; + + #ifndef OS_TESTING + struct timeval t1, t2; + double elapsed_time = 0; + gettimeofday(&t1, NULL); + + while ( ! endl_found) + { + gettimeofday(&t2, NULL); + elapsed_time = (t2.tv_sec - t1.tv_sec); + elapsed_time += (t2.tv_usec - t1.tv_usec) / 1000000.0; + + if (elapsed_time > timeout) + { + #ifdef DEBUG + this->logger->log("Error: Serial timeout. ("+to_string(timeout)+" s)"); + #endif + + break; + } + + while (available = serialDataAvail(this->fd) > 0) + { + char c = serialGetchar(this->fd); - uint_fast8_t checksum = 0; - for (char c : frame) - { - if (c == '$') continue; - if (c == '*') break; + if (c == '\r') logstr += "\\r"; + else if (c == '\n') logstr += "\\n"; + else logstr += c; - checksum ^= c; - } - uint_fast8_t frame_cs = stoi(frame.substr(frame.rfind('*')+1, frame.length()-frame.rfind('*')-1), 0, 16); + if (c == '\r') + { + rfound = true; + continue; + } + else if (c == '\n') + { + if (rfound) + { + endl_found = true; + break; + } + } + + rfound = false; + response += c; + } + + if (available < 0) + { + #ifdef DEBUG + this->logger->log("Error: Serial available < 0."); + #endif - return checksum == frame_cs; + break; + } + this_thread::sleep_for(1ms); + } + #endif + + #ifdef DEBUG + this->logger->log("Received: '"+logstr+"'"); + #endif + + return response; +} + +void Serial::flush() const +{ + serialFlush(this->fd); } diff --git a/serial/Serial.h b/serial/Serial.h index bd8fc73..0323a31 100644 --- a/serial/Serial.h +++ b/serial/Serial.h @@ -3,9 +3,12 @@ #include -#include #include -#include + +#include "constants.h" +#ifdef DEBUG + #include "logger/Logger.h" +#endif using namespace std; namespace os { @@ -13,21 +16,29 @@ namespace os { { private: int fd; - function listener; - atomic_bool open; - atomic_bool stopped; - string endl; + bool open; + + #ifdef DEBUG + Logger* logger; + #endif - void serial_thread(); + void gps_thread(); public: - Serial() = default; + Serial(const string& url, int baud_rate, const string& log_path); Serial(Serial& copy) = delete; ~Serial(); - uint_fast8_t send_frame(string frame); + void println(const string& str) const; + void println() const; + void write(unsigned char c) const; void close(); - bool is_valid(string frame); - void initialize(const string& serial_URL, int baud, const string endl, function); + bool is_open() const; + char read_char() const; + int available() const; + const string read_line() const; + const string read_line(double timeout) const; + bool read_only(const string& only) const; + void flush() const; }; } diff --git a/startup.sh b/startup.sh new file mode 100755 index 0000000..fdfb25c --- /dev/null +++ b/startup.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +echo "[`date`] Stopping SSH daemon..." >> /root/control.log +/etc/init.d/ssh stop + +echo "[`date`] Starting OpenStratos..." >> /root/control.log +/root/openstratos >> /root/control.log 2>&1 & +sleep 5 + +while true; do + (pgrep openstratos && sleep 60) || + (echo "[`date`] Process not running, restarting..." >> /root/control.log; + shutdown -r now; sleep 5) +done diff --git a/temperature/Temperature.cc b/temperature/Temperature.cc deleted file mode 100644 index 310c312..0000000 --- a/temperature/Temperature.cc +++ /dev/null @@ -1,72 +0,0 @@ -#include "temperature/Temperature.h" - -#include -#include -#include - -#include - -#include "constants.h" - -using namespace std; -using namespace os; - -Temperature::~Temperature() -{ - if (this->reading) - this->stop_reading(); -} - -Temperature::Temperature(const int address) -{ - this->reading = false; - this->stopped = true; - #ifndef OS_TESTING - int fh = wiringPiI2CSetup(address); - if (fh != -1) - { - this->address = address; - this->filehandle = fh; - } - else - { - // TODO Log error - //printf("An error ocurred initializing I2C Temperature module\n"); - } - #endif -} - -void Temperature::start_reading() -{ - if ( ! this->reading) - { - this->reading = true; - this->stopped = false; - thread t(&Temperature::read_temperature, this); - t.detach(); - } -} - -void Temperature::stop_reading() -{ - this->reading = false; - while( ! this->stopped); -} - -void Temperature::read_temperature() -{ - while (this->reading) - { - #ifndef OS_TESTING - int value = wiringPiI2CRead(this->filehandle); - #else - int value = 16000; - #endif - - float voltage = value * 5 / 32768; // 2^15 - this->temperature = r_to_c(TEMP_R * (TEMP_VIN / voltage - 1)); - - this_thread::sleep_for(chrono::milliseconds(50)); - } - this->stopped = true; -} diff --git a/temperature/Temperature.h b/temperature/Temperature.h deleted file mode 100644 index 3d943dd..0000000 --- a/temperature/Temperature.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TEMPERATURE_TEMPERATURE_H_ -#define TEMPERATURE_TEMPERATURE_H_ - -#include -using namespace std; - -namespace os { - class Temperature - { - private: - int address; - int filehandle; - float temperature; - atomic_bool reading; - atomic_bool stopped; - - void read_temperature(); - public: - Temperature(const int address); - ~Temperature(); - Temperature(Temperature& copy) = delete; - - int get_temperature() {return this->temperature;} - void start_reading(); - void stop_reading(); - bool is_reading() {return this->reading;} - }; -} - -inline float r_to_c(float r) -{ - float value = r - 1000; - return (value / 3.91 + value * value / 100000); -} - -#endif // TEMPERATURE_TEMPERATURE_H_ diff --git a/testing/bandit b/testing/bandit index cd1e581..76f1e76 160000 --- a/testing/bandit +++ b/testing/bandit @@ -1 +1 @@ -Subproject commit cd1e5816bb817a5e7e1d1a03a02f0f7a68a101c8 +Subproject commit 76f1e76d9842d4bee1742ee2b01fdcceed3421d4 diff --git a/testing/battery_test.cc b/testing/battery_test.cc deleted file mode 100644 index a06a9f7..0000000 --- a/testing/battery_test.cc +++ /dev/null @@ -1,19 +0,0 @@ -describe("Battery", [](){ - - it("voltage to percentage calculator test", [&](){ - AssertThat(volt_to_percent(8.4), Is().EqualToWithDelta(100, 0.01)); - AssertThat(volt_to_percent(7.4), Is().EqualToWithDelta(0, 0.01)); - AssertThat(volt_to_percent(7.75), Is().EqualToWithDelta(35, 0.01)); - AssertThat(volt_to_percent(8), Is().EqualToWithDelta(60, 0.01)); - }); - - it("reading test", [&](){ - Battery bat(80); - - bat.start_reading(); - this_thread::sleep_for(chrono::milliseconds(75)); - - AssertThat(bat.get_battery(), Is().EqualToWithDelta(50, 0.05)); - bat.stop_reading(); - }); -}); diff --git a/testing/camera_test.cc b/testing/camera_test.cc index 81f7540..ed4e12a 100644 --- a/testing/camera_test.cc +++ b/testing/camera_test.cc @@ -1,19 +1,63 @@ describe("Camera", [](){ it("recording test", [&](){ - Camera::get_instance().record(200); + AssertThat(Camera::get_instance().record(2000), Equals(true)); AssertThat(Camera::get_instance().is_recording(), Equals(true)); - this_thread::sleep_for(chrono::milliseconds(250)); + this_thread::sleep_for(2.5s); AssertThat(Camera::get_instance().is_recording(), Equals(false)); + + #ifdef RASPICAM + remove("data/video/test.h264"); + #endif }); it("recording and stopping test", [&](){ - Camera::get_instance().record(); + AssertThat(Camera::get_instance().record(), Equals(true)); AssertThat(Camera::get_instance().is_recording(), Equals(true)); - this_thread::sleep_for(chrono::milliseconds(200)); - Camera::get_instance().stop(); + this_thread::sleep_for(2s); + AssertThat(Camera::get_instance().stop(), Equals(true)); AssertThat(Camera::get_instance().is_recording(), Equals(false)); + + #ifdef RASPICAM + remove("data/video/test.h264"); + #endif + }); + + it("picture taking test", [&](){ + AssertThat(Camera::get_instance().take_picture(), Equals(true)); + + #ifdef RASPICAM + remove("data/img/test.jpg"); + #endif }); + + #ifdef RASPICAM + it("video file creation test", [&](){ + AssertThat(Camera::get_instance().record(10000), Equals(true)); + this_thread::sleep_for(chrono::seconds(11)); + + struct stat buf; + int result = stat("data/video/test.h264", &buf); + + AssertThat(result, Equals(0)); + remove("data/video/test.h264"); + }); + #else + it_skip("video file creation test", [&](){}); + #endif + + #ifdef RASPICAM + it("picture file creation test", [&](){ + int start_file_count = get_file_count("data/img/"); + AssertThat(Camera::get_instance().take_picture(), Equals(true)); + int end_file_count = get_file_count("data/img/"); + + AssertThat(end_file_count, Equals(start_file_count+1)); + remove(("data/img/img-"+ to_string(get_file_count("data/img/")-1) +".jpg").c_str()); + }); + #else + it_skip("picture file creation test", [&](){}); + #endif }); diff --git a/testing/core_test.cc b/testing/core_test.cc deleted file mode 100644 index e9d712d..0000000 --- a/testing/core_test.cc +++ /dev/null @@ -1,3 +0,0 @@ -describe("Core test", [](){ - -}); diff --git a/testing/gps_test.cc b/testing/gps_test.cc index edbd80d..e647605 100644 --- a/testing/gps_test.cc +++ b/testing/gps_test.cc @@ -1,7 +1,7 @@ describe("GPS", [](){ before_each([&](){ - GPS::get_instance().initialize(""); + GPS::get_instance().initialize(); }); it("Knots to m/s conversion test", [&](){ @@ -10,15 +10,25 @@ describe("GPS", [](){ AssertThat(kt_to_mps(0), Equals(0)); }); + it("frame validity test", [&](){ + string valid = "$REPORT,0,23,185213184,1421782514,1,4140.7276,-0404.8853,73,52,43*1E"; + string valid2 = "$PMTK226,3,30*4"; + string not_valid = "$REPORT,0,23,185213184,1421782514,1,4140.7276,-0404.8853,73,52,43*14"; + + AssertThat(GPS::is_valid(valid), Equals(true)); + AssertThat(GPS::is_valid(valid2), Equals(true)); + AssertThat(GPS::is_valid(not_valid), Equals(false)); + }); + it("GGA frame parser test", [&](){ GPS::get_instance().parse("$GPGGA,151025,2011.3454,N,12020.2464,W,1,05,1.53,20134.13,M,20103.45,M,,*56"); - AssertThat(GPS::get_instance().is_active(), Equals(true)); + AssertThat(GPS::get_instance().is_fixed(), Equals(true)); - tm* gps_time = GPS::get_instance().get_time(); - AssertThat(gps_time->tm_hour, Equals(15)); - AssertThat(gps_time->tm_min, Equals(10)); - AssertThat(gps_time->tm_sec, Equals(25)); + tm gps_time = GPS::get_instance().get_time(); + AssertThat(gps_time.tm_hour, Equals(15)); + AssertThat(gps_time.tm_min, Equals(10)); + AssertThat(gps_time.tm_sec, Equals(25)); AssertThat(GPS::get_instance().get_satellites(), Equals(5)); AssertThat(GPS::get_instance().get_latitude(), Is().EqualToWithDelta(20.18909, 0.00001)); @@ -30,9 +40,9 @@ describe("GPS", [](){ it("GGA frame parser pass test", [&](){ - int hour = GPS::get_instance().get_time()->tm_hour; - int min = GPS::get_instance().get_time()->tm_min; - int sec = GPS::get_instance().get_time()->tm_sec; + int hour = GPS::get_instance().get_time().tm_hour; + int min = GPS::get_instance().get_time().tm_min; + int sec = GPS::get_instance().get_time().tm_sec; int satellites = GPS::get_instance().get_satellites(); double latitude = GPS::get_instance().get_latitude(); @@ -42,11 +52,11 @@ describe("GPS", [](){ GPS::get_instance().parse("$GPGGA,170810,2316.3654,S,12225.2464,E,0,07,1.89,18647.15,M,18640.35,M,,*53"); - AssertThat(GPS::get_instance().is_active(), Equals(false)); + AssertThat(GPS::get_instance().is_fixed(), Equals(false)); - AssertThat(GPS::get_instance().get_time()->tm_hour, Equals(hour)); - AssertThat(GPS::get_instance().get_time()->tm_min, Equals(min)); - AssertThat(GPS::get_instance().get_time()->tm_sec, Equals(sec)); + AssertThat(GPS::get_instance().get_time().tm_hour, Equals(hour)); + AssertThat(GPS::get_instance().get_time().tm_min, Equals(min)); + AssertThat(GPS::get_instance().get_time().tm_sec, Equals(sec)); AssertThat(GPS::get_instance().get_satellites(), Equals(satellites)); AssertThat(GPS::get_instance().get_latitude(), Equals(latitude)); @@ -58,7 +68,7 @@ describe("GPS", [](){ it("GSA frame parser test", [&](){ GPS::get_instance().parse("$GPGSA,A,3,,,,,,16,18,,22,24,,,3.6,2.1,2.2*3C"); - AssertThat(GPS::get_instance().is_active(), Equals(true)); + AssertThat(GPS::get_instance().is_fixed(), Equals(true)); AssertThat(GPS::get_instance().get_HDOP(), Is().EqualToWithDelta(2.1, 0.0005)); AssertThat(GPS::get_instance().get_VDOP(), Is().EqualToWithDelta(2.2, 0.0005)); }); @@ -69,7 +79,7 @@ describe("GPS", [](){ GPS::get_instance().parse("$GPGSA,A,1,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*36"); - AssertThat(GPS::get_instance().is_active(), Equals(false)); + AssertThat(GPS::get_instance().is_fixed(), Equals(false)); AssertThat(GPS::get_instance().get_HDOP(), Equals(hdop)); AssertThat(GPS::get_instance().get_VDOP(), Equals(vdop)); }); @@ -77,52 +87,55 @@ describe("GPS", [](){ it("RMC frame parser test", [&](){ GPS::get_instance().parse("$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68"); - AssertThat(GPS::get_instance().is_active(), Equals(true)); + AssertThat(GPS::get_instance().is_fixed(), Equals(true)); - tm* gps_time = GPS::get_instance().get_time(); + tm gps_time = GPS::get_instance().get_time(); - AssertThat(gps_time->tm_mday, Equals(19)); - AssertThat(gps_time->tm_mon, Equals(11)); - AssertThat(gps_time->tm_year, Equals(194)); - AssertThat(gps_time->tm_hour, Equals(22)); - AssertThat(gps_time->tm_min, Equals(54)); - AssertThat(gps_time->tm_sec, Equals(46)); + AssertThat(gps_time.tm_mday, Equals(19)); + AssertThat(gps_time.tm_mon, Equals(10)); + AssertThat(gps_time.tm_year, Equals(194)); + AssertThat(gps_time.tm_hour, Equals(22)); + AssertThat(gps_time.tm_min, Equals(54)); + AssertThat(gps_time.tm_sec, Equals(46)); AssertThat(GPS::get_instance().get_latitude(), Is().EqualToWithDelta(49.27417, 0.00001)); AssertThat(GPS::get_instance().get_longitude(), Is().EqualToWithDelta(-123.18533, 0.00001)); - AssertThat(GPS::get_instance().get_velocity()->speed, Is().EqualToWithDelta(0.25722, 0.00001)); - AssertThat(GPS::get_instance().get_velocity()->course, Is().EqualToWithDelta(54.7, 0.001)); + AssertThat(GPS::get_instance().get_velocity().speed, Is().EqualToWithDelta(0.25722, 0.00001)); + AssertThat(GPS::get_instance().get_velocity().course, Is().EqualToWithDelta(54.7, 0.001)); }); it("RMC frame parser pass test", [&](){ - int hour = GPS::get_instance().get_time()->tm_hour; - int min = GPS::get_instance().get_time()->tm_min; - int sec = GPS::get_instance().get_time()->tm_sec; - int mday = GPS::get_instance().get_time()->tm_mday; - int mon = GPS::get_instance().get_time()->tm_mon; - int year = GPS::get_instance().get_time()->tm_year; + tm gps_time = GPS::get_instance().get_time(); + int hour = gps_time.tm_hour; + int min = gps_time.tm_min; + int sec = gps_time.tm_sec; + int mday = gps_time.tm_mday; + int mon = gps_time.tm_mon; + int year = gps_time.tm_year; double latitude = GPS::get_instance().get_latitude(); double longitude = GPS::get_instance().get_longitude(); - float speed = GPS::get_instance().get_velocity()->speed; - float course = GPS::get_instance().get_velocity()->course; + float speed = GPS::get_instance().get_velocity().speed; + float course = GPS::get_instance().get_velocity().course; GPS::get_instance().parse("$GPRMC,081836,V,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*75"); - AssertThat(GPS::get_instance().is_active(), Equals(false)); + AssertThat(GPS::get_instance().is_fixed(), Equals(false)); + + tm new_gps_time = GPS::get_instance().get_time(); - AssertThat(GPS::get_instance().get_time()->tm_hour, Equals(hour)); - AssertThat(GPS::get_instance().get_time()->tm_min, Equals(min)); - AssertThat(GPS::get_instance().get_time()->tm_sec, Equals(sec)); - AssertThat(GPS::get_instance().get_time()->tm_mday, Equals(mday)); - AssertThat(GPS::get_instance().get_time()->tm_mon, Equals(mon)); - AssertThat(GPS::get_instance().get_time()->tm_year, Equals(year)); + AssertThat(new_gps_time.tm_hour, Equals(hour)); + AssertThat(new_gps_time.tm_min, Equals(min)); + AssertThat(new_gps_time.tm_sec, Equals(sec)); + AssertThat(new_gps_time.tm_mday, Equals(mday)); + AssertThat(new_gps_time.tm_mon, Equals(mon)); + AssertThat(new_gps_time.tm_year, Equals(year)); AssertThat(GPS::get_instance().get_latitude(), Equals(latitude)); AssertThat(GPS::get_instance().get_longitude(), Equals(longitude)); - AssertThat(GPS::get_instance().get_velocity()->speed, Equals(speed)); - AssertThat(GPS::get_instance().get_velocity()->course, Equals(course)); + AssertThat(GPS::get_instance().get_velocity().speed, Equals(speed)); + AssertThat(GPS::get_instance().get_velocity().course, Equals(course)); }); }); diff --git a/testing/serial_test.cc b/testing/serial_test.cc deleted file mode 100644 index bf761d0..0000000 --- a/testing/serial_test.cc +++ /dev/null @@ -1,17 +0,0 @@ -describe("Serial", [](){ - - it("frame validity test", [&](){ - string valid = "$REPORT,0,23,185213184,1421782514,1,4140.7276,-0404.8853,73,52,43*1E"; - string valid2 = "$PMTK226,3,30*4"; - string not_valid = "$REPORT,0,23,185213184,1421782514,1,4140.7276,-0404.8853,73,52,43*14"; - - Serial serial; - serial.initialize("", 9600, "\n", NULL); - - AssertThat(serial.is_valid(valid), Equals(true)); - AssertThat(serial.is_valid(valid2), Equals(true)); - AssertThat(serial.is_valid(not_valid), Equals(false)); - - serial.close(); - }); -}); diff --git a/testing/temperature_test.cc b/testing/temperature_test.cc deleted file mode 100644 index 81b9028..0000000 --- a/testing/temperature_test.cc +++ /dev/null @@ -1,30 +0,0 @@ -describe("Temperature", [&](){ - - it("read & stop test", [&]() { - Temperature temp(20); - temp.start_reading(); - AssertThat(temp.is_reading(), Equals(true)); - - this_thread::sleep_for(chrono::milliseconds(100)); - - temp.stop_reading(); - AssertThat(temp.is_reading(), Equals(false)); - }); - - it("resistor to temperature conversion test", [&]() { - - AssertThat(r_to_c(1155.4), Is().EqualToWithDelta(40, 0.05)); - AssertThat(r_to_c(1116.9), Is().EqualToWithDelta(30, 0.05)); - AssertThat(r_to_c(1077.9), Is().EqualToWithDelta(20, 0.05)); - AssertThat(r_to_c(1039), Is().EqualToWithDelta(10, 0.05)); - AssertThat(r_to_c(1000), Is().EqualToWithDelta(0, 0.05)); - AssertThat(r_to_c(960.9), Is().EqualToWithDelta(-10, 0.05)); - AssertThat(r_to_c(921.6), Is().EqualToWithDelta(-20, 0.05)); - AssertThat(r_to_c(882.2), Is().EqualToWithDelta(-30, 0.05)); - AssertThat(r_to_c(842.7), Is().EqualToWithDelta(-40, 0.05)); - AssertThat(r_to_c(803.1), Is().EqualToWithDelta(-50, 0.05)); - AssertThat(r_to_c(763.3), Is().EqualToWithDelta(-60, 0.05)); - AssertThat(r_to_c(723.3), Is().EqualToWithDelta(-70, 0.05)); - AssertThat(r_to_c(683.3), Is().EqualToWithDelta(-80, 0.05)); - }); -}); diff --git a/testing/testing.cc b/testing/testing.cc index 36dcfa0..712b2d1 100644 --- a/testing/testing.cc +++ b/testing/testing.cc @@ -1,18 +1,20 @@ #include -#include + +#include #include -#include "serial/Serial.h" +#include "config.h" +#include "constants.h" + #include "camera/Camera.h" -#include "temperature/Temperature.h" #include "gps/GPS.h" -#include "battery/Battery.h" using namespace bandit; using namespace os; using namespace std; +inline bool file_exists(const string& name); int main(int argc, char* argv[]) { @@ -20,11 +22,30 @@ int main(int argc, char* argv[]) } go_bandit([](){ + if ( ! file_exists("data")) + mkdir("data", 0755); + + if ( ! file_exists("data/logs")) + { + mkdir("data/logs", 0755); + mkdir("data/logs/main", 0755); + mkdir("data/logs/camera", 0755); + mkdir("data/logs/GPS", 0755); + mkdir("data/logs/GSM", 0755); + } + + if ( ! file_exists("data/video")) + mkdir("data/video", 0755); + + if ( ! file_exists("data/img")) + mkdir("data/img", 0755); - #include "core_test.cc" #include "camera_test.cc" - #include "serial_test.cc" #include "gps_test.cc" - #include "temperature_test.cc" - #include "battery_test.cc" }); + +inline bool file_exists(const string& name) +{ + struct stat buffer; + return stat(name.c_str(), &buffer) == 0; +} diff --git a/threads.cc b/threads.cc new file mode 100644 index 0000000..940c635 --- /dev/null +++ b/threads.cc @@ -0,0 +1,153 @@ +#include "threads.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "logger/Logger.h" +#include "camera/Camera.h" +#include "gsm/GSM.h" + +using namespace std; +using namespace os; + +void os::system_thread_fn(State& state) +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + Logger cpu_logger("data/logs/system/CPU."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "CPU"); + + Logger ram_logger("data/logs/system/RAM."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "RAM"); + + Logger temp_logger("data/logs/system/Temp."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "Temp"); + + FILE *gpu_temp_process, *cpu_command_process; + char gpu_response[11]; + char cpu_command[100]; + struct sysinfo info; + + while (state != SHUT_DOWN) + { + ifstream cpu_temp_file("/sys/class/thermal/thermal_zone0/temp"); + string cpu_temp_str((istreambuf_iterator(cpu_temp_file)), + istreambuf_iterator()); + cpu_temp_file.close(); + + gpu_temp_process = popen("/opt/vc/bin/vcgencmd measure_temp", "r"); + fgets(gpu_response, 11, gpu_temp_process); + pclose(gpu_temp_process); + + temp_logger.log("CPU: "+to_string(stoi(cpu_temp_str)/1000.0)+" GPU: "+ + string(gpu_response).substr(5, 4)); + + cpu_command_process = popen("grep 'cpu ' /proc/stat", "r"); + fgets(cpu_command, 100, cpu_command_process); + pclose(cpu_command_process); + + const string cpu_command_str = string(cpu_command); + stringstream ss(cpu_command_str); + string data; + vector s_data; + + // We put all fields in a vector + while(getline(ss, data, ' ')) s_data.push_back(data); + + // Note that s_data[1] is "" + cpu_logger.log(to_string((stof(s_data[2])+stof(s_data[4]))/ + (stof(s_data[2])+stof(s_data[4])+stof(s_data[5])))); + + sysinfo(&info); + ram_logger.log(to_string(((double) info.freeram)/info.totalram)); + + this_thread::sleep_for(30s); + } +} + +void os::picture_thread_fn(State& state) +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + Logger logger("data/logs/camera/Pictures."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "Pictures"); + + logger.log("Waiting for launch..."); + + while (state != GOING_UP) + { + this_thread::sleep_for(10s); + } + + logger.log("Launched, waiting 2 minutes for first picture..."); + this_thread::sleep_for(2min); + + while (state == GOING_UP) + { + logger.log("Taking picture..."); + + if ( ! Camera::get_instance().take_picture(generate_exif_data())) + { + logger.log("Error taking picture. Trying again in 30 seconds..."); + } + else + { + logger.log("Picture taken correctly. Next picture in 30 seconds..."); + } + + this_thread::sleep_for(30s); + logger.log("Taking picture..."); + + if ( ! Camera::get_instance().take_picture(generate_exif_data())) + { + logger.log("Error taking picture. Next picture in 4 minutes..."); + } + else + { + logger.log("Picture taken correctly. Next picture in 4 minutes..."); + } + + this_thread::sleep_for(4min); + } + logger.log("Going down, no more pictures are being taken, picture thread is closing."); +} + +void os::battery_thread_fn(State& state) +{ + struct timeval timer; + gettimeofday(&timer, NULL); + struct tm * now = gmtime(&timer.tv_sec); + + Logger logger("data/logs/GSM/Battery."+ to_string(now->tm_year+1900) +"-"+ to_string(now->tm_mon) +"-"+ + to_string(now->tm_mday) +"."+ to_string(now->tm_hour) +"-"+ to_string(now->tm_min) +"-"+ + to_string(now->tm_sec) +".log", "Battery"); + + double main_battery, gsm_battery; + + while (state != SHUT_DOWN) + { + if (GSM::get_instance().get_status()) + { + GSM::get_instance().get_battery_status(main_battery, gsm_battery); + logger.log("Main: "+ to_string(main_battery)); + logger.log("GSM: "+ to_string(gsm_battery)); + } + + this_thread::sleep_for(3min); + } +} diff --git a/threads.h b/threads.h new file mode 100644 index 0000000..b1136ba --- /dev/null +++ b/threads.h @@ -0,0 +1,12 @@ +#ifndef THREADS_H_ +#define THREADS_H_ + +#include "utils.h" + +namespace os { + void system_thread_fn(State& state); + void picture_thread_fn(State& state); + void battery_thread_fn(State& state); +} + +#endif // THREADS_H_ diff --git a/utils.cc b/utils.cc new file mode 100644 index 0000000..8491e07 --- /dev/null +++ b/utils.cc @@ -0,0 +1,109 @@ +#include "utils.h" + +#include +#ifdef DEBUG + #include +#endif + +#include +#include + +#include "constants.h" + +using namespace std; +using namespace os; + +void os::check_or_create(const string& path, Logger* logger) +{ + if ( ! file_exists(path)) + { + if (logger != NULL) + logger->log("No '"+path+"' directory, creating..."); + #ifdef DEBUG + else + cout << "[OpenStratos] No '"+path+"' directory, creating..." << endl; + #endif + if (mkdir(path.c_str(), 0755) != 0) + { + if (logger != NULL) + logger->log("Error creating '"+path+"' directory."); + #ifdef DEBUG + else + cout << "[OpenStratos] Error creating '"+path+"' directory." << endl; + #endif + + sync(); + reboot(RB_POWER_OFF); + } + else + { + if (logger != NULL) + logger->log("'"+path+"' directory created."); + #ifdef DEBUG + else + cout << "[OpenStratos] '"+path+"' directory created." << endl; + #endif + } + } +} + +State os::set_state(State new_state) +{ + ofstream state_file(STATE_FILE); + state_file << state_to_string(new_state); + state_file.close(); + + return new_state; +} + +State os::get_last_state() +{ + ifstream state_file(STATE_FILE); + string state_str((istreambuf_iterator(state_file)), + istreambuf_iterator()); + state_file.close(); + + if (state_str == "INITIALIZING") return INITIALIZING; + if (state_str == "ACQUIRING_FIX") return ACQUIRING_FIX; + if (state_str == "FIX_ACQUIRED") return FIX_ACQUIRED; + if (state_str == "WAITING_LAUNCH") return WAITING_LAUNCH; + if (state_str == "GOING_UP") return GOING_UP; + if (state_str == "GOING_DOWN") return GOING_DOWN; + if (state_str == "LANDED") return LANDED; + if (state_str == "SHUT_DOWN") return SHUT_DOWN; + + return SAFE_MODE; +} + +const string os::state_to_string(State state) +{ + switch (state) + { + case INITIALIZING: + return "INITIALIZING"; + break; + case ACQUIRING_FIX: + return "ACQUIRING_FIX"; + break; + case FIX_ACQUIRED: + return "FIX_ACQUIRED"; + break; + case WAITING_LAUNCH: + return "WAITING_LAUNCH"; + break; + case GOING_UP: + return "GOING_UP"; + break; + case GOING_DOWN: + return "GOING_DOWN"; + break; + case LANDED: + return "LANDED"; + break; + case SHUT_DOWN: + return "SHUT_DOWN"; + break; + case SAFE_MODE: + return "SAFE_MODE"; + } +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..2080550 --- /dev/null +++ b/utils.h @@ -0,0 +1,110 @@ +#ifndef UTILS_H_ +#define UTILS_H_ + +#include + +#include +#include + +#include +#include + +#include "constants.h" +#include "logger/Logger.h" +#include "gps/GPS.h" + +namespace os { + enum State { + INITIALIZING, + ACQUIRING_FIX, + FIX_ACQUIRED, + WAITING_LAUNCH, + GOING_UP, + GOING_DOWN, + LANDED, + SHUT_DOWN, + SAFE_MODE, + }; + + void check_or_create(const string& path, Logger* logger = NULL); + + inline bool file_exists(const string& name) + { + struct stat buffer; + return stat(name.c_str(), &buffer) == 0; + } + + inline float get_available_disk_space() + { + struct statvfs fs; + statvfs("data", &fs); + + return ((float) fs.f_bsize)*fs.f_bavail; + } + + State set_state(State new_state); + State get_last_state(); + const string state_to_string(State state); + + inline State get_real_state() + { + double start_alt = GPS::get_instance().get_altitude(); + this_thread::sleep_for(5s); + double end_alt = GPS::get_instance().get_altitude(); + + if (end_alt - start_alt < -10) return set_state(GOING_DOWN); + else if (end_alt - start_alt > 5) return set_state(GOING_UP); + else if (end_alt > 8000) return set_state(GOING_DOWN); + else return set_state(LANDED); + } + + inline bool has_launched(double launch_altitude) + { + for (int i = 0; ! GPS::get_instance().is_fixed() && i < 10; ++i); + if ( ! GPS::get_instance().is_fixed()) return false; + + double first_altitude = GPS::get_instance().get_altitude(); + if (first_altitude > launch_altitude + 100) return true; + + this_thread::sleep_for(5s); + double second_altitude = GPS::get_instance().get_altitude(); + + #if defined SIM || defined REAL_SIM + return true; + #else + return second_altitude > first_altitude + 10; + #endif + } + + inline bool has_bursted(double maximum_altitude) + { + for (int i = 0; ! GPS::get_instance().is_fixed() && i < 10; ++i); + if ( ! GPS::get_instance().is_fixed()) return false; + + double first_altitude = GPS::get_instance().get_altitude(); + if (first_altitude < maximum_altitude - 1000) return true; + + this_thread::sleep_for(6s); + double second_altitude = GPS::get_instance().get_altitude(); + + #if defined SIM || defined REAL_SIM + return true; + #else + return second_altitude < first_altitude - 10; + #endif + } + + inline bool has_landed() + { + for (int i = 0; ! GPS::get_instance().is_fixed() && i < 10; ++i); + if ( ! GPS::get_instance().is_fixed()) return false; + + double first_altitude = GPS::get_instance().get_altitude(); + this_thread::sleep_for(5s); + double second_altitude = GPS::get_instance().get_altitude(); + + return abs(first_altitude-second_altitude) < 5; + } +} + +#endif // UTILS_H_