kopia lustrzana https://github.com/ogre/habdec
amateur.sondehub.org support. Requires C++17
rodzic
4ec86fbad9
commit
eec808885f
|
@ -67,7 +67,7 @@ You need to build FFTW with float (single precission) support. Also, enable neon
|
||||||
#### Windows
|
#### Windows
|
||||||
Download http://fftw.org/fftw-3.3.8.tar.gz and unpack.
|
Download http://fftw.org/fftw-3.3.8.tar.gz and unpack.
|
||||||
|
|
||||||
cmake -D BUILD_SHARED_LIBS=0 -D ENABLE_FLOAT=1 -D CMAKE_INSTALL_PREFIX=d:\dev\c_libs\fftw\3.3.8\install -D CMAKE_BUILD_TYPE=RELEASE -G "NMake Makefiles" ..\src
|
cmake -D BUILD_SHARED_LIBS=0 -D ENABLE_FLOAT=1 -D CMAKE_INSTALL_PREFIX=d:\dev\c_libs\fftw\3.3.8\install -D CMAKE_BUILD_TYPE=Release -G "NMake Makefiles" ..\src
|
||||||
nmake
|
nmake
|
||||||
nmake install
|
nmake install
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ If you managed to build or install dependencies, you're ready do build habdec
|
||||||
cd habdec
|
cd habdec
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake cmake -D BOOST_ROOT=/path/to/boost_1.68 -D FFTW_ROOT=/path/to/fftwf/install -DCMAKE_BUILD_TYPE=RELEASE ../code
|
cmake -D BOOST_ROOT=/path/to/boost_1.68 -D FFTW_ROOT=/path/to/fftwf/install -DCMAKE_BUILD_TYPE=Release ../code
|
||||||
make -j 4
|
make -j 4
|
||||||
make install
|
make install
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
![alt text](./webClientScreenshot.png)
|
![alt text](./webClientScreenshot.png)
|
||||||
|
|
||||||
Habdec is a C++11 software to decode RTTY telemetry from High Altitude Balloon and upload it to [UKHAS Habitat](http://habitat.habhub.org/)
|
Habdec is a C++17 software to decode RTTY telemetry from High Altitude Balloons and upload it to [UKHAS Habitat](http://habitat.habhub.org/) and [sondehub.org](https://amateur.sondehub.org)
|
||||||
|
|
||||||
Some facts:
|
Some facts:
|
||||||
- builds and runs on Windows/Linux and x64/RaspberryPI/OdroixXU4 platforms
|
- builds and runs on Windows/Linux and x64/RaspberryPI/OdroixXU4 platforms
|
||||||
|
@ -35,7 +35,7 @@ To get habdec you can download precompiled exec or build it from source.
|
||||||
There's also raspberryPi 3 image with ready to use binary and build env. Go to [Wiki](https://github.com/ogre/habdec/wiki)
|
There's also raspberryPi 3 image with ready to use binary and build env. Go to [Wiki](https://github.com/ogre/habdec/wiki)
|
||||||
|
|
||||||
#### Building from source:
|
#### Building from source:
|
||||||
To build habdec you need a C++11 compiler and CMake version 3.8.2
|
To build habdec you need a C++17 compiler and CMake version 3.8.2
|
||||||
You also need to build or install some dependencies:
|
You also need to build or install some dependencies:
|
||||||
- basic decoder: [FFTW](http://www.fftw.org/)
|
- basic decoder: [FFTW](http://www.fftw.org/)
|
||||||
- websocket server: [SoapySDR](https://github.com/pothosware/SoapySDR), [boost 1.68](https://www.boost.org/) (any version with boost-beast)
|
- websocket server: [SoapySDR](https://github.com/pothosware/SoapySDR), [boost 1.68](https://www.boost.org/) (any version with boost-beast)
|
||||||
|
@ -75,6 +75,7 @@ CLI opts:
|
||||||
--sentence_cmd arg Call external command with sentence as parameter
|
--sentence_cmd arg Call external command with sentence as parameter
|
||||||
--flights [=arg(=0)] List Habitat flights
|
--flights [=arg(=0)] List Habitat flights
|
||||||
--payload arg Configure for Payload ID
|
--payload arg Configure for Payload ID
|
||||||
|
--sondehub arg (=https://api.v2.sondehub.org) sondehub API url
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples:
|
### Examples:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required( VERSION 3.8.2 )
|
cmake_minimum_required( VERSION 3.8.2 )
|
||||||
|
|
||||||
project (habdec)
|
project (habdec)
|
||||||
set ( CMAKE_CXX_STANDARD 11 )
|
set ( CMAKE_CXX_STANDARD 17 )
|
||||||
|
|
||||||
# set ( CMAKE_BUILD_TYPE "Debug" )
|
# set ( CMAKE_BUILD_TYPE "Debug" )
|
||||||
# set ( CMAKE_BUILD_TYPE "Release" )
|
# set ( CMAKE_BUILD_TYPE "Release" )
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -45,13 +45,15 @@ int HttpRequest(
|
||||||
|
|
||||||
http::request<http::string_body> req; //{_verb, target, 10/*version*/};
|
http::request<http::string_body> req; //{_verb, target, 10/*version*/};
|
||||||
req.set(http::field::host, host);
|
req.set(http::field::host, host);
|
||||||
req.set(http::field::user_agent, "habdec");
|
req.set(http::field::user_agent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0");
|
||||||
req.version(10); // 11 ?
|
req.version(10); // 11 ?
|
||||||
req.target(target);
|
req.target(target);
|
||||||
if(i_verb == HTTP_VERB::kGet)
|
if(i_verb == HTTP_VERB::kGet)
|
||||||
req.method( http::verb::get );
|
req.method( http::verb::get );
|
||||||
else if(i_verb == HTTP_VERB::kPost)
|
else if(i_verb == HTTP_VERB::kPost)
|
||||||
req.method( http::verb::post );
|
req.method( http::verb::post );
|
||||||
|
else if(i_verb == HTTP_VERB::kPut)
|
||||||
|
req.method( http::verb::put );
|
||||||
if( i_content_type != "" )
|
if( i_content_type != "" )
|
||||||
req.set(http::field::content_type, i_content_type);
|
req.set(http::field::content_type, i_content_type);
|
||||||
if( i_body != "" )
|
if( i_body != "" )
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace habdec
|
namespace habdec
|
||||||
{
|
{
|
||||||
|
|
||||||
enum class HTTP_VERB { kUnknown = 0, kGet, kPost };
|
enum class HTTP_VERB { kUnknown = 0, kGet, kPost, kPut };
|
||||||
|
|
||||||
int HttpRequest(
|
int HttpRequest(
|
||||||
const std::string host,
|
const std::string host,
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,200 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <regex>
|
||||||
|
#include <tuple>
|
||||||
|
#include <optional>
|
||||||
|
// #include <chrono>
|
||||||
|
// #include <ctime>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "common/date.h"
|
||||||
|
|
||||||
|
#include "sentence_parse.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::vector<std::string> split_str(const std::string& text, char sep)
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::size_t start = 0, end = 0;
|
||||||
|
while ((end = text.find(sep, start)) != std::string::npos)
|
||||||
|
{
|
||||||
|
tokens.push_back(text.substr(start, end - start));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
tokens.push_back(text.substr(start));
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace habdec
|
||||||
|
{
|
||||||
|
|
||||||
|
// input time in any of formats:
|
||||||
|
// 123456
|
||||||
|
// 12:34:56
|
||||||
|
// 12_34_56
|
||||||
|
// separator can by ay non-digit character
|
||||||
|
// or format without seconds:
|
||||||
|
// 1234
|
||||||
|
// 12:34
|
||||||
|
// ...
|
||||||
|
// or seconds with fractional
|
||||||
|
// 12:34:56.7
|
||||||
|
// return tuple hour,minute,seconds
|
||||||
|
std::optional< std::tuple<int,int,float> >
|
||||||
|
parse_sentence_time(std::string i_time_str)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
// cout<<"PARSE "<<i_time_str<<endl;
|
||||||
|
|
||||||
|
regex rex{ R"_((\d\d)\D?(\d\d)\D?(\d\d(\.\d+)?)?)_" };
|
||||||
|
smatch match;
|
||||||
|
regex_match(i_time_str, match, rex);
|
||||||
|
|
||||||
|
if(!match.size())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int hours = stoi(match[1]);
|
||||||
|
int minutes = stoi(match[2]);
|
||||||
|
float seconds = 0;
|
||||||
|
if(string(match[3]).size())
|
||||||
|
seconds = stof(match[3]);
|
||||||
|
|
||||||
|
return { std::tuple<int,int,float>(hours,minutes,seconds) };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// given H:M:S time from telemetry string, add current system date to construct full UTC timestamp
|
||||||
|
// if current system time is late/ahead of telemetry time, and we are near midnight => correct day
|
||||||
|
std::string timestamp_from_HMS(int i_hour, int i_minute, float i_second)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
using namespace chrono;
|
||||||
|
using namespace date;
|
||||||
|
|
||||||
|
auto sys_now = system_clock::now();
|
||||||
|
auto sys_YMD = year_month_day(floor<days>(sys_now));
|
||||||
|
auto sys_HMS = make_time(sys_now - floor<days>(sys_now) );
|
||||||
|
|
||||||
|
const bool is_inside_window =
|
||||||
|
i_hour == 23 || i_hour == 0;
|
||||||
|
|
||||||
|
if(is_inside_window) {
|
||||||
|
if(i_hour == 23 && sys_HMS.hours().count() == 0) // system time is ahead of input time
|
||||||
|
sys_YMD = year_month_day( floor<days>(sys_now) - days(1) );
|
||||||
|
else if(i_hour == 0 && sys_HMS.hours().count() == 23) // system time is late of input time
|
||||||
|
sys_YMD = year_month_day( floor<days>(sys_now) + days(1) );
|
||||||
|
}
|
||||||
|
|
||||||
|
stringstream ss;
|
||||||
|
ss << sys_YMD << "T"
|
||||||
|
<< setfill('0') << setw(2) << i_hour << ":"
|
||||||
|
<< setfill('0') << setw(2) << i_minute << ":"
|
||||||
|
<< setfill('0') << setw(2) << i_second << "Z";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// get one coordinate string in decimal dd.dddd or nmea format ddmm.mmmm
|
||||||
|
// return float in decimal degrees
|
||||||
|
float parse_gps_pos(const std::string lat_or_lon)
|
||||||
|
{
|
||||||
|
//NMEA DDMM.MMMM: 5205.5857, 02112.7309
|
||||||
|
//decimal: 52.1234, 21.34 (or 121.345)
|
||||||
|
std::string coord(lat_or_lon);
|
||||||
|
|
||||||
|
// positive or negative ?
|
||||||
|
float sign = 1.0;
|
||||||
|
if(lat_or_lon.at(0) == '-') {
|
||||||
|
sign = -1.0;
|
||||||
|
coord = lat_or_lon.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if this NMEA DDMM.MMMM or usual decimal DD.DDDD
|
||||||
|
size_t decimal_pos = coord.find('.');
|
||||||
|
|
||||||
|
if(decimal_pos == 2 || decimal_pos == 3)
|
||||||
|
return stof(coord); // usual decimal
|
||||||
|
|
||||||
|
// this is NMEA format
|
||||||
|
if(decimal_pos == 4) { // latitude
|
||||||
|
float lat = stof(coord);
|
||||||
|
const float degs = trunc(lat / 100);
|
||||||
|
const float mins = lat - 100.0f * degs;
|
||||||
|
lat = degs + mins / 60.0f;
|
||||||
|
return sign * lat;
|
||||||
|
}
|
||||||
|
if(decimal_pos == 5) { // longitude
|
||||||
|
float lon = stof(coord);
|
||||||
|
const float degs = trunc(lon / 100);
|
||||||
|
const float mins = lon - 100.0f * degs;
|
||||||
|
lon = degs + mins / 60.0f;
|
||||||
|
return sign * lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<sondehub::MinTelemetry> parse_sentence(const std::string& sentence_without_crc)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
using namespace habdec;
|
||||||
|
|
||||||
|
auto tokens = split_str(sentence_without_crc, ',');
|
||||||
|
|
||||||
|
if(tokens.size() < 6)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
//remove leading $$$ from callsign
|
||||||
|
string callsign = tokens[0];
|
||||||
|
size_t dollar = callsign.find('$');
|
||||||
|
if(dollar != string::npos) {
|
||||||
|
while(callsign.at(dollar) == '$' && dollar < callsign.size())
|
||||||
|
dollar++;
|
||||||
|
callsign = callsign.substr(dollar);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int id = stoi(tokens[1]);
|
||||||
|
const string hms_time_str = tokens[2];
|
||||||
|
const string lat_str = tokens[3];
|
||||||
|
const string lon_str = tokens[4];
|
||||||
|
const float alt = stof(tokens[5]);
|
||||||
|
|
||||||
|
// lat / lon
|
||||||
|
//
|
||||||
|
const float lat = parse_gps_pos(lat_str);
|
||||||
|
const float lon = parse_gps_pos(lon_str);
|
||||||
|
|
||||||
|
if(!lat && !lon)
|
||||||
|
return {}; //no gps fix ?
|
||||||
|
|
||||||
|
// timestamp
|
||||||
|
//
|
||||||
|
std::optional< std::tuple<int,int,float> > hms = parse_sentence_time(hms_time_str);
|
||||||
|
if(!hms) {
|
||||||
|
cout<<"Failed parsing time string: "<<hms_time_str<<endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _t = hms.value();
|
||||||
|
const string timestamp = timestamp_from_HMS( get<0>(_t), get<1>(_t), get<2>(_t) );
|
||||||
|
|
||||||
|
sondehub::MinTelemetry res;
|
||||||
|
res.payload_callsign = callsign;
|
||||||
|
res.datetime = timestamp;
|
||||||
|
res.frame = id;
|
||||||
|
res.lat = lat;
|
||||||
|
res.lon = lon;
|
||||||
|
res.alt = alt;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "sondehub/sondehub_uploader.h"
|
||||||
|
|
||||||
|
namespace habdec
|
||||||
|
{
|
||||||
|
|
||||||
|
// input time in any of formats:
|
||||||
|
// 123456
|
||||||
|
// 12:34:56
|
||||||
|
// 12_34_56
|
||||||
|
// separator can by ay non-digit character
|
||||||
|
// or format without seconds:
|
||||||
|
// 1234
|
||||||
|
// 12:34
|
||||||
|
// ...
|
||||||
|
// or seconds with fractional
|
||||||
|
// 12:34:56.7
|
||||||
|
// return tuple hour,minute,seconds
|
||||||
|
std::optional< std::tuple<int,int,float> >
|
||||||
|
parse_sentence_time(std::string i_time_str);
|
||||||
|
|
||||||
|
|
||||||
|
// given H:M:S time from telemetry string, add current system date to construct full UTC timestamp
|
||||||
|
// if current system time is late/ahead of telemetry time, and we are near midnight => correct day
|
||||||
|
std::string timestamp_from_HMS(int i_hour, int i_minute, float i_second);
|
||||||
|
|
||||||
|
|
||||||
|
// get one coordinate string in decimal dd.dddd or nmea format ddmm.mmmm
|
||||||
|
// return float in decimal degrees
|
||||||
|
float parse_gps_pos(const std::string lat_or_lon);
|
||||||
|
|
||||||
|
std::optional<sondehub::MinTelemetry> parse_sentence(const std::string& sentence_without_crc);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "utc_now_iso.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include "date.h"
|
||||||
|
|
||||||
|
std::string utc_now_iso()
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
using namespace chrono;
|
||||||
|
using namespace date;
|
||||||
|
|
||||||
|
auto sys_now = system_clock::now();
|
||||||
|
auto sys_YMD = year_month_day(floor<days>(sys_now));
|
||||||
|
auto sys_HMS = make_time(sys_now - floor<days>(sys_now) );
|
||||||
|
|
||||||
|
stringstream ss;
|
||||||
|
ss<<sys_YMD<<"T"<<sys_HMS<<"Z";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
std::string utc_now_iso();
|
|
@ -129,6 +129,7 @@ std::string HabitatUploadSentence(
|
||||||
string sentence_json = SentenceToHabJson(sentence_b64, i_listener_callsign);
|
string sentence_json = SentenceToHabJson(sentence_b64, i_listener_callsign);
|
||||||
|
|
||||||
string result;
|
string result;
|
||||||
|
// return result;
|
||||||
|
|
||||||
int retries_left = 5;
|
int retries_left = 5;
|
||||||
while( retries_left-- )
|
while( retries_left-- )
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
#include "sondehub_uploader.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include "common/utc_now_iso.h"
|
||||||
|
#include "common/git_repo_sha1.h"
|
||||||
|
#include "common/json.hpp"
|
||||||
|
#include <cpr/cpr.h>
|
||||||
|
|
||||||
|
namespace sondehub
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
void SondeHubUploader::push(const MinTelemetry& t)
|
||||||
|
{
|
||||||
|
if(api_endpoint_ == "" || uploader_callsign_ == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> _lock(queue_mtx_);
|
||||||
|
queue_.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t SondeHubUploader::size() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _lock(queue_mtx_);
|
||||||
|
return queue_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SondeHubUploader::upload()
|
||||||
|
{
|
||||||
|
if(api_endpoint_ == "" || uploader_callsign_ == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
processing_queue_.clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> _lock(queue_mtx_);
|
||||||
|
if(!queue_.size())
|
||||||
|
return;
|
||||||
|
queue_.swap(processing_queue_);
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const string utc_now_str = utc_now_iso();
|
||||||
|
stringstream s;
|
||||||
|
s<<"[";
|
||||||
|
|
||||||
|
for(auto& t : processing_queue_) {
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json tele_json;
|
||||||
|
|
||||||
|
tele_json["uploader_callsign"] = uploader_callsign_;
|
||||||
|
tele_json["software_version"] = string(g_GIT_SHA1).substr(0,7);
|
||||||
|
tele_json["time_received"] = t.time_received;
|
||||||
|
tele_json["upload_time"] = utc_now_str;
|
||||||
|
tele_json["payload_callsign"] = t.payload_callsign;
|
||||||
|
tele_json["datetime"] = t.datetime;
|
||||||
|
tele_json["frame"] = t.frame;
|
||||||
|
tele_json["lat"] = t.lat;
|
||||||
|
tele_json["lon"] = t.lon;
|
||||||
|
tele_json["alt"] = static_cast<int>(t.alt);
|
||||||
|
|
||||||
|
s<<tele_json<<",";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
string payload{s.str()};
|
||||||
|
payload.at(payload.size()-1) = ']';
|
||||||
|
|
||||||
|
// cout<<payload<<endl;
|
||||||
|
// cout<<"uploading to "<<api_endpoint_<<endl;
|
||||||
|
// return;
|
||||||
|
|
||||||
|
cpr::Response r = cpr::Put(
|
||||||
|
cpr::Url{api_endpoint_},
|
||||||
|
cpr::Body{payload},
|
||||||
|
cpr::Header{
|
||||||
|
{"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0"},
|
||||||
|
{"Content-Type", "application/json"}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if(r.status_code != 200) {
|
||||||
|
cout<<"sondehub upload error:\n";
|
||||||
|
cout<<r.status_code<<endl; // 200
|
||||||
|
cout<<r.header["content-type"]<<endl; // application/json; charset=utf-8
|
||||||
|
cout<<r.text<<endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace sondehub
|
||||||
|
{
|
||||||
|
|
||||||
|
// minimal telemetry
|
||||||
|
struct MinTelemetry
|
||||||
|
{
|
||||||
|
std::string payload_callsign;
|
||||||
|
std::string time_received;
|
||||||
|
std::string datetime;
|
||||||
|
int frame;
|
||||||
|
float lat;
|
||||||
|
float lon;
|
||||||
|
float alt;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SondeHubUploader
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<MinTelemetry> queue_;
|
||||||
|
mutable std::mutex queue_mtx_;
|
||||||
|
|
||||||
|
std::vector<MinTelemetry> processing_queue_;
|
||||||
|
|
||||||
|
std::string api_endpoint_;
|
||||||
|
std::string uploader_callsign_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SondeHubUploader(const std::string& api_endpoint, const std::string& uploader_callsign) : api_endpoint_(api_endpoint), uploader_callsign_(uploader_callsign) {};
|
||||||
|
void push(const MinTelemetry&);
|
||||||
|
void upload();
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,12 @@ find_package(SoapySDR REQUIRED) # this reverts CMAKE_CXX_STANDARD to '11'
|
||||||
include_directories( ${SoapySDR_INCLUDE_DIRS} )
|
include_directories( ${SoapySDR_INCLUDE_DIRS} )
|
||||||
|
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git
|
||||||
|
GIT_TAG 871ed52d350214a034f6ef8a3b8f51c5ce1bd400) # The commit hash for 1.9.0. Replace with the latest from: https://github.com/libcpr/cpr/releases
|
||||||
|
FetchContent_MakeAvailable(cpr)
|
||||||
|
|
||||||
|
|
||||||
set( CMAKE_CXX_FLAGS " -O3 " )
|
set( CMAKE_CXX_FLAGS " -O3 " )
|
||||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||||
# set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow" )
|
# set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow" )
|
||||||
|
@ -53,8 +59,12 @@ set ( websocketServer_src
|
||||||
CompressedVector.h CompressedVector.cpp
|
CompressedVector.h CompressedVector.cpp
|
||||||
${CMAKE_SOURCE_DIR}/habitat/habitat_upload.cpp
|
${CMAKE_SOURCE_DIR}/habitat/habitat_upload.cpp
|
||||||
${CMAKE_SOURCE_DIR}/habitat/habitat_list_flights.cpp
|
${CMAKE_SOURCE_DIR}/habitat/habitat_list_flights.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/sondehub/sondehub_uploader.cpp
|
||||||
${CMAKE_SOURCE_DIR}/common/http_request.cpp
|
${CMAKE_SOURCE_DIR}/common/http_request.cpp
|
||||||
${CMAKE_SOURCE_DIR}/common/GpsDistance.cpp
|
${CMAKE_SOURCE_DIR}/common/GpsDistance.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/common/sentence_parse.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/common/utc_now_iso.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/Decoder/CRC.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,6 +77,7 @@ target_link_libraries( habdecWebsocketServer
|
||||||
Decoder
|
Decoder
|
||||||
IQSource
|
IQSource
|
||||||
${Boost_LIBRARIES}
|
${Boost_LIBRARIES}
|
||||||
|
cpr::cpr
|
||||||
${PlatformSpecificLinking} )
|
${PlatformSpecificLinking} )
|
||||||
|
|
||||||
install ( TARGETS habdecWebsocketServer DESTINATION ${CMAKE_INSTALL_PREFIX} )
|
install ( TARGETS habdecWebsocketServer DESTINATION ${CMAKE_INSTALL_PREFIX} )
|
||||||
|
|
|
@ -104,8 +104,11 @@ public:
|
||||||
std::string coord_format_lat_ = "dd.dddd"; // encoding of lat/lon coords: dd.dddd or ddmm.mmmm
|
std::string coord_format_lat_ = "dd.dddd"; // encoding of lat/lon coords: dd.dddd or ddmm.mmmm
|
||||||
std::string coord_format_lon_ = "dd.dddd"; // encoding of lat/lon coords: dd.dddd or ddmm.mmmm
|
std::string coord_format_lon_ = "dd.dddd"; // encoding of lat/lon coords: dd.dddd or ddmm.mmmm
|
||||||
std::string ssdv_dir_ = ".";
|
std::string ssdv_dir_ = ".";
|
||||||
// int datasize_ = 1;
|
std::string test_parse_sentence_ = "";
|
||||||
TransportDataType transport_data_type_ = TransportDataType::kChar;
|
std::string sondehub_ = "https://api.v2.sondehub.org/";
|
||||||
|
std::string iqfile_ = "";
|
||||||
|
float iqfile_sampling_rate_ = 0;
|
||||||
|
TransportDataType transport_data_type_ = TransportDataType::kFloat;
|
||||||
|
|
||||||
|
|
||||||
bool operator==(const PARAMS& rhs) const
|
bool operator==(const PARAMS& rhs) const
|
||||||
|
@ -176,8 +179,7 @@ public:
|
||||||
oFile<<"sentence_cmd = "<<GLOBALS::get().par_.sentence_cmd_<<endl;
|
oFile<<"sentence_cmd = "<<GLOBALS::get().par_.sentence_cmd_<<endl;
|
||||||
oFile<<"payload = "<<GLOBALS::get().par_.habitat_payload_<<endl;
|
oFile<<"payload = "<<GLOBALS::get().par_.habitat_payload_<<endl;
|
||||||
oFile<<"no_exit = "<<GLOBALS::get().par_.no_exit_<<endl;
|
oFile<<"no_exit = "<<GLOBALS::get().par_.no_exit_<<endl;
|
||||||
oFile <<"nmea = "<< ( GLOBALS::get().par_.coord_format_lat_ == "ddmm.mmmm"
|
oFile<<"sondehub = "<<GLOBALS::get().par_.sondehub_<<endl;
|
||||||
&& GLOBALS::get().par_.coord_format_lon_ == "ddmm.mmmm" )<<endl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (exception& e) {
|
catch (exception& e) {
|
||||||
|
@ -214,8 +216,8 @@ public:
|
||||||
cout<<"\tlowpass: "<<GLOBALS::get().par_.lowpass_bw_Hz_<<endl;
|
cout<<"\tlowpass: "<<GLOBALS::get().par_.lowpass_bw_Hz_<<endl;
|
||||||
cout<<"\tlp_trans: "<<GLOBALS::get().par_.lowpass_tr_<<endl;
|
cout<<"\tlp_trans: "<<GLOBALS::get().par_.lowpass_tr_<<endl;
|
||||||
cout<<"\tno_exit: "<<GLOBALS::get().par_.no_exit_<<endl;
|
cout<<"\tno_exit: "<<GLOBALS::get().par_.no_exit_<<endl;
|
||||||
cout<<"\tnmea = "<< ( GLOBALS::get().par_.coord_format_lat_ == "ddmm.mmmm"
|
cout<<"\tsondehub: "<<GLOBALS::get().par_.sondehub_<<endl;
|
||||||
&& GLOBALS::get().par_.coord_format_lon_ == "ddmm.mmmm" )<<endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,14 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include <SoapySDR/Device.hpp>
|
#include <SoapySDR/Device.hpp>
|
||||||
#include <SoapySDR/Formats.hpp>
|
#include <SoapySDR/Formats.hpp>
|
||||||
#include <SoapySDR/Version.hpp>
|
#include <SoapySDR/Version.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#include "IQSource/IQSource_File.h"
|
||||||
#include "IQSource/IQSource_SoapySDR.h"
|
#include "IQSource/IQSource_SoapySDR.h"
|
||||||
#include "Decoder/Decoder.h"
|
#include "Decoder/Decoder.h"
|
||||||
#include "common/console_colors.h"
|
#include "common/console_colors.h"
|
||||||
|
@ -41,12 +44,19 @@
|
||||||
#include "ws_server.h"
|
#include "ws_server.h"
|
||||||
#include "common/git_repo_sha1.h"
|
#include "common/git_repo_sha1.h"
|
||||||
#include "Base64.h"
|
#include "Base64.h"
|
||||||
|
#include "common/sentence_parse.h"
|
||||||
|
#include "common/http_request.h"
|
||||||
|
#include "common/date.h"
|
||||||
|
#include "common/utc_now_iso.h"
|
||||||
|
#include "common/json.hpp"
|
||||||
|
#include "Decoder/CRC.h"
|
||||||
|
#include "sondehub/sondehub_uploader.h"
|
||||||
|
#include <cpr/cpr.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::string> split_string(const std::string& text, char sep)
|
std::vector<std::string> split_str(const std::string& text, char sep)
|
||||||
{
|
{
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
std::size_t start = 0, end = 0;
|
std::size_t start = 0, end = 0;
|
||||||
|
@ -60,33 +70,6 @@ std::vector<std::string> split_string(const std::string& text, char sep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SentenceToPosition(const std::string i_snt,
|
|
||||||
float &lat, float &lon, float &alt,
|
|
||||||
const std::string coord_format_lat, const std::string coord_format_lon
|
|
||||||
)
|
|
||||||
{
|
|
||||||
// "CALLSIGN,123,15:41:24,44.32800,-74.14427,00491,0,0,12,30.7,0.0,0.001,20.2,958*6BC9"
|
|
||||||
std::vector<std::string> tokens = split_string(i_snt, ',');
|
|
||||||
lat = std::stof(tokens[3]);
|
|
||||||
lon = std::stof(tokens[4]);
|
|
||||||
alt = std::stof(tokens[5]);
|
|
||||||
|
|
||||||
if(coord_format_lat == "ddmm.mmmm")
|
|
||||||
{
|
|
||||||
const float degs = trunc(lat / 100);
|
|
||||||
const float mins = lat - 100.0f * degs;
|
|
||||||
lat = degs + mins / 60.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(coord_format_lon == "ddmm.mmmm")
|
|
||||||
{
|
|
||||||
const float degs = trunc(lon / 100);
|
|
||||||
const float mins = lon - 100.0f * degs;
|
|
||||||
lon = degs + mins / 60.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PrintDevicesList(const SoapySDR::KwargsList& device_list)
|
void PrintDevicesList(const SoapySDR::KwargsList& device_list)
|
||||||
{
|
{
|
||||||
int DEV_NUM = 0;
|
int DEV_NUM = 0;
|
||||||
|
@ -301,31 +284,63 @@ void DECODER_THREAD()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SentenceCallback(std::string callsign, std::string data, std::string crc, std::shared_ptr<WebsocketServer> p_ws)
|
void SentenceCallback( std::string callsign, std::string data, std::string crc,
|
||||||
|
std::shared_ptr<WebsocketServer> p_ws,
|
||||||
|
std::shared_ptr<sondehub::SondeHubUploader> p_sondehub_uploader)
|
||||||
|
// sondehub::SondeHubUploader& sondehub_uploader)
|
||||||
{
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
const string sentence_no_crc = callsign + "," + data;
|
||||||
|
|
||||||
|
auto maybe_telemetry = habdec::parse_sentence(sentence_no_crc);
|
||||||
|
sondehub::MinTelemetry t;
|
||||||
|
if(maybe_telemetry) {
|
||||||
|
t = maybe_telemetry.value();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cout<<"Failed parsing sentence "<<sentence_no_crc<<endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t.time_received = utc_now_iso();
|
||||||
|
|
||||||
|
// sondehub upload
|
||||||
|
p_sondehub_uploader->push(t);
|
||||||
|
|
||||||
using Ms = std::chrono::milliseconds;
|
using Ms = std::chrono::milliseconds;
|
||||||
|
|
||||||
auto& stats = GLOBALS::get().stats_;
|
auto& stats = GLOBALS::get().stats_;
|
||||||
stats.last_sentence_timestamp_ = std::chrono::steady_clock::now();
|
stats.last_sentence_timestamp_ = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
const string sentence = callsign + "," + data + "*" + crc;
|
const string sentence = callsign + "," + data + "*" + crc;
|
||||||
const int sentence_number = stoi( data.substr(0, data.find(',')) );
|
|
||||||
|
|
||||||
// notify all websocket clients
|
// notify all websocket clients
|
||||||
if(p_ws)
|
if(p_ws)
|
||||||
{
|
{
|
||||||
|
// sentence
|
||||||
auto p_sentence_msg = std::make_shared<HabdecMessage>();
|
auto p_sentence_msg = std::make_shared<HabdecMessage>();
|
||||||
p_sentence_msg->to_all_clients_ = true;
|
p_sentence_msg->to_all_clients_ = true;
|
||||||
p_sentence_msg->data_stream_<<"cmd::info:sentence="<<sentence;
|
p_sentence_msg->data_stream_<<"cmd::info:sentence="<<sentence;
|
||||||
p_ws->sessions_send(p_sentence_msg);
|
p_ws->sessions_send(p_sentence_msg);
|
||||||
|
|
||||||
|
// tracking telemetry
|
||||||
|
stringstream s;
|
||||||
|
auto p_tele_msg = std::make_shared<HabdecMessage>();
|
||||||
|
p_tele_msg->to_all_clients_ = true;
|
||||||
|
p_tele_msg->data_stream_ << "cmd::info:tracking_telemetry=";
|
||||||
|
p_tele_msg->data_stream_ << t.payload_callsign << ","
|
||||||
|
<< t.datetime << ","
|
||||||
|
<< t.lat << ","
|
||||||
|
<< t.lon << ","
|
||||||
|
<< t.alt;
|
||||||
|
p_ws->sessions_send(p_tele_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// register in globals
|
// register in globals
|
||||||
if( GLOBALS::get().sentences_map_mtx_.try_lock_for(Ms(1000)) )
|
if( GLOBALS::get().sentences_map_mtx_.try_lock_for(Ms(1000)) )
|
||||||
{
|
{
|
||||||
GLOBALS::get().sentences_map_[sentence_number] = sentence;
|
GLOBALS::get().sentences_map_[t.frame] = sentence;
|
||||||
GLOBALS::get().stats_.num_ok_ = GLOBALS::get().sentences_map_.size();
|
GLOBALS::get().stats_.num_ok_ = GLOBALS::get().sentences_map_.size();
|
||||||
GLOBALS::get().sentences_map_mtx_.unlock();
|
GLOBALS::get().sentences_map_mtx_.unlock();
|
||||||
}
|
}
|
||||||
|
@ -357,15 +372,10 @@ void SentenceCallback(std::string callsign, std::string data, std::string crc, s
|
||||||
// print distance and elevation
|
// print distance and elevation
|
||||||
if( GLOBALS::get().par_.station_lat_ )
|
if( GLOBALS::get().par_.station_lat_ )
|
||||||
{
|
{
|
||||||
float lat, lon, alt;
|
|
||||||
SentenceToPosition( sentence, lat, lon, alt,
|
|
||||||
GLOBALS::get().par_.coord_format_lat_,
|
|
||||||
GLOBALS::get().par_.coord_format_lon_);
|
|
||||||
|
|
||||||
stats.D_ = habdec::CalcGpsDistance(
|
stats.D_ = habdec::CalcGpsDistance(
|
||||||
GLOBALS::get().par_.station_lat_, GLOBALS::get().par_.station_lon_,
|
GLOBALS::get().par_.station_lat_, GLOBALS::get().par_.station_lon_,
|
||||||
GLOBALS::get().par_.station_alt_,
|
GLOBALS::get().par_.station_alt_,
|
||||||
lat, lon, alt );
|
t.lat, t.lon, t.alt );
|
||||||
|
|
||||||
stats.dist_max_ = max(stats.dist_max_, stats.D_.dist_line_);
|
stats.dist_max_ = max(stats.dist_max_, stats.D_.dist_line_);
|
||||||
stats.elev_min_ = min(stats.elev_min_, stats.D_.elevation_);
|
stats.elev_min_ = min(stats.elev_min_, stats.D_.elevation_);
|
||||||
|
@ -388,6 +398,48 @@ void SentenceCallback(std::string callsign, std::string data, std::string crc, s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Test_SentenceGenerator(
|
||||||
|
std::shared_ptr<WebsocketServer> p_ws_server,
|
||||||
|
// sondehub::SondeHubUploader& sondehub_uploader
|
||||||
|
std::shared_ptr<sondehub::SondeHubUploader> p_sondehub_uploader
|
||||||
|
)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
using namespace chrono;
|
||||||
|
using namespace date;
|
||||||
|
|
||||||
|
|
||||||
|
static int sentence_id = 0;
|
||||||
|
while(1) {
|
||||||
|
sentence_id++;
|
||||||
|
this_thread::sleep_for(( duration<double, milli>(6 * 1000) ));
|
||||||
|
|
||||||
|
auto sys_now = system_clock::now();
|
||||||
|
auto sys_HMS = make_time(sys_now - floor<days>(sys_now) );
|
||||||
|
|
||||||
|
stringstream ss;
|
||||||
|
ss << sentence_id;
|
||||||
|
ss << "," << sys_HMS;
|
||||||
|
if(sentence_id%2)
|
||||||
|
ss << ",52.5,21.25";
|
||||||
|
else
|
||||||
|
ss << ",5230.1,02115.1"; // NMEA
|
||||||
|
ss << ","<<int(5000 + sentence_id*00);
|
||||||
|
ss << ",-25.3";
|
||||||
|
|
||||||
|
string data{ ss.str() };
|
||||||
|
|
||||||
|
string callsign = GLOBALS::get().par_.station_callsign_ + "-test";
|
||||||
|
|
||||||
|
cout<<callsign<<","<<data<<endl;
|
||||||
|
|
||||||
|
SentenceCallback( callsign, data, habdec::CRC(callsign + string(",") + data),
|
||||||
|
p_ws_server,
|
||||||
|
p_sondehub_uploader );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -420,39 +472,48 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
auto& G = GLOBALS::get();
|
auto& G = GLOBALS::get();
|
||||||
|
|
||||||
// setup SoapySDR device
|
// setup SoapySDR device or iqfile
|
||||||
SoapySDR::Kwargs device;
|
SoapySDR::Kwargs device;
|
||||||
while(true)
|
if(G.par_.iqfile_ != "")
|
||||||
{
|
{
|
||||||
bool ok = false;
|
G.p_iq_source_.reset( new habdec::IQSource_File<float> );
|
||||||
try {
|
G.p_iq_source_->setOption("file_string", &G.par_.iqfile_);
|
||||||
ok = SetupDevice(device);
|
double sr = G.par_.iqfile_sampling_rate_;
|
||||||
} catch(std::runtime_error& er) {
|
G.p_iq_source_->setOption("sampling_rate_double", &sr);
|
||||||
cout<<C_RED<<"Failed opening device: "
|
bool iq_loop = true;
|
||||||
<<C_MAGENTA<<er.what()<<C_OFF<<endl;
|
G.p_iq_source_->setOption("loop_bool", &iq_loop);
|
||||||
}
|
G.p_iq_source_->init();
|
||||||
|
cout<<"Reading IQ samples from "<<G.par_.iqfile_<<" at "<<sr<<endl;
|
||||||
if( ok )
|
G.p_iq_source_->start();
|
||||||
{
|
}
|
||||||
break;
|
else
|
||||||
}
|
{
|
||||||
else
|
while(true) {
|
||||||
{
|
bool ok = false;
|
||||||
if( G.par_.no_exit_ )
|
try {
|
||||||
{
|
ok = SetupDevice(device);
|
||||||
cout<<C_RED<<"Failed Device Setup. Retry."<<C_OFF<<endl;
|
} catch(std::runtime_error& er) {
|
||||||
std::this_thread::sleep_for( ( std::chrono::duration<double, std::milli>(3000) ));
|
cout<<C_RED<<"Failed opening device: "
|
||||||
continue;
|
<<C_MAGENTA<<er.what()<<C_OFF<<endl;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if( ok ) {
|
||||||
cout<<C_RED<<"Failed Device Setup. EXIT."<<C_OFF<<endl;
|
break;
|
||||||
return 1;
|
}
|
||||||
|
else {
|
||||||
|
if( G.par_.no_exit_ ) {
|
||||||
|
cout<<C_RED<<"Failed Device Setup. Retry."<<C_OFF<<endl;
|
||||||
|
std::this_thread::sleep_for( ( std::chrono::duration<double, std::milli>(3000) ));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cout<<C_RED<<"Failed Device Setup. EXIT."<<C_OFF<<endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// station info
|
// station info
|
||||||
if( G.par_.station_callsign_ != "" )
|
if( G.par_.station_callsign_ != "" )
|
||||||
{
|
{
|
||||||
|
@ -460,12 +521,48 @@ int main(int argc, char** argv)
|
||||||
device["driver"] + " - habdec" );
|
device["driver"] + " - habdec" );
|
||||||
|
|
||||||
if( G.par_.station_lat_
|
if( G.par_.station_lat_
|
||||||
&& G.par_.station_lon_ )
|
&& G.par_.station_lon_ ) {
|
||||||
habdec::habitat::UploadStationTelemetry(
|
|
||||||
G.par_.station_callsign_,
|
// habitat
|
||||||
G.par_.station_lat_, G.par_.station_lon_,
|
cout<<"Uploading station info to HabHub."<<endl;
|
||||||
G.par_.station_alt_, 0, false
|
habdec::habitat::UploadStationTelemetry(
|
||||||
);
|
G.par_.station_callsign_,
|
||||||
|
G.par_.station_lat_, G.par_.station_lon_,
|
||||||
|
G.par_.station_alt_, 0, false
|
||||||
|
);
|
||||||
|
|
||||||
|
// sondehub
|
||||||
|
using json = nlohmann::json;
|
||||||
|
json sh_json;
|
||||||
|
sh_json["software_name"] = "habdec";
|
||||||
|
sh_json["software_version"] = string(g_GIT_SHA1).substr(0,7);
|
||||||
|
sh_json["uploader_callsign"] = G.par_.station_callsign_;
|
||||||
|
sh_json["uploader_position"] = vector<float>{
|
||||||
|
G.par_.station_lat_, G.par_.station_lon_, G.par_.station_alt_};
|
||||||
|
sh_json["uploader_radio"] = device["driver"];
|
||||||
|
sh_json["mobile"] = false;
|
||||||
|
|
||||||
|
cout<<"Uploading station info to SondeHub."<<endl;
|
||||||
|
// cout<<sh_json<<endl;
|
||||||
|
stringstream s;
|
||||||
|
s<<sh_json;
|
||||||
|
|
||||||
|
cpr::Response r = cpr::Put(
|
||||||
|
cpr::Url{G.par_.sondehub_ + "/amateur/listeners"},
|
||||||
|
cpr::Body{s.str()},
|
||||||
|
cpr::Header{
|
||||||
|
{"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0"},
|
||||||
|
{"Content-Type", "application/json"}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if(r.status_code != 200) {
|
||||||
|
cout<<"sondehub station info upload error:\n";
|
||||||
|
cout<<r.status_code<<endl; // 200
|
||||||
|
cout<<r.header["content-type"]<<endl; // application/json; charset=utf-8
|
||||||
|
cout<<r.text<<endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial options from globals
|
// initial options from globals
|
||||||
|
@ -493,13 +590,16 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
|
|
||||||
// websocket server
|
// websocket server
|
||||||
shared_ptr<WebsocketServer> p_ws_server = make_shared<WebsocketServer>(
|
auto p_ws_server = make_shared<WebsocketServer>(G.par_.command_host_ , G.par_.command_port_);
|
||||||
G.par_.command_host_ , G.par_.command_port_);
|
|
||||||
|
// sondehub uploader
|
||||||
|
auto p_sondehub_uploader = make_shared<sondehub::SondeHubUploader>(G.par_.sondehub_ + "/amateur/telemetry", G.par_.station_callsign_);
|
||||||
|
|
||||||
|
|
||||||
DECODER.sentence_callback_ =
|
DECODER.sentence_callback_ =
|
||||||
[p_ws_server](string callsign, string data, string crc)
|
[p_ws_server, p_sondehub_uploader](string callsign, string data, string crc)
|
||||||
{
|
{
|
||||||
async(launch::async, SentenceCallback, callsign, data, crc, p_ws_server);
|
async(launch::async, SentenceCallback, callsign, data, crc, p_ws_server, p_sondehub_uploader);
|
||||||
};
|
};
|
||||||
|
|
||||||
DECODER.character_callback_ =
|
DECODER.character_callback_ =
|
||||||
|
@ -535,16 +635,32 @@ int main(int argc, char** argv)
|
||||||
//
|
//
|
||||||
unordered_set<thread*> threads;
|
unordered_set<thread*> threads;
|
||||||
|
|
||||||
// start websocket
|
// websocket server
|
||||||
threads.emplace( new thread(
|
threads.emplace( new thread(
|
||||||
[p_ws_server]() { (*p_ws_server)(); }
|
[p_ws_server]() { (*p_ws_server)(); }
|
||||||
) );
|
) );
|
||||||
|
|
||||||
// start decoder
|
// decoder
|
||||||
threads.emplace( new thread(
|
threads.emplace( new thread(
|
||||||
DECODER_THREAD
|
DECODER_THREAD
|
||||||
|
// [p_ws_server, p_sondehub_uploader]() {
|
||||||
|
// Test_SentenceGenerator(p_ws_server, p_sondehub_uploader);
|
||||||
|
// }
|
||||||
) );
|
) );
|
||||||
|
|
||||||
|
// sondehub uploader
|
||||||
|
if(G.par_.station_callsign_ != "" && G.par_.sondehub_ != "") {
|
||||||
|
cout<<"Uploading telemetry to sondehub "<<G.par_.sondehub_<<endl;
|
||||||
|
threads.emplace( new thread(
|
||||||
|
[p_sondehub_uploader]() {
|
||||||
|
while(1) {
|
||||||
|
this_thread::sleep_for(( std::chrono::duration<double, std::milli>(15 * 1000) ));
|
||||||
|
p_sondehub_uploader->upload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for(auto t : threads)
|
for(auto t : threads)
|
||||||
t->join();
|
t->join();
|
||||||
|
|
|
@ -108,10 +108,12 @@ void prog_opts(int ac, char* av[])
|
||||||
|
|
||||||
("flights", po::value<int>()->implicit_value(0), "List Habitat flights")
|
("flights", po::value<int>()->implicit_value(0), "List Habitat flights")
|
||||||
("payload", po::value<string>(), "Configure for Payload ID")
|
("payload", po::value<string>(), "Configure for Payload ID")
|
||||||
("nmea", po::value<bool>(), "assume NMEA lat/lon format: ddmm.mmmm")
|
|
||||||
|
|
||||||
("ssdv_dir", po::value<string>()->default_value(GLOBALS::get().par_.ssdv_dir_), "SSDV directory.")
|
("ssdv_dir", po::value<string>()->default_value(GLOBALS::get().par_.ssdv_dir_), "SSDV directory.")
|
||||||
|
|
||||||
|
("sondehub", po::value<string>()->default_value("https://api.v2.sondehub.org"), "sondehub API url")
|
||||||
|
("iqfile", po::value< std::vector<string> >()->multitoken(), "iqfile and it's sampling_rate")
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
po::options_description cli_options("Command Line Interface options");
|
po::options_description cli_options("Command Line Interface options");
|
||||||
|
@ -276,11 +278,6 @@ void prog_opts(int ac, char* av[])
|
||||||
cout<<flight.second<<endl;
|
cout<<flight.second<<endl;
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
if (vm.count("nmea") && vm["nmea"].as<bool>())
|
|
||||||
{
|
|
||||||
GLOBALS::get().par_.coord_format_lat_ = "ddmm.mmmm";
|
|
||||||
GLOBALS::get().par_.coord_format_lon_ = "ddmm.mmmm";
|
|
||||||
}
|
|
||||||
if (vm.count("latlon"))
|
if (vm.count("latlon"))
|
||||||
{
|
{
|
||||||
vector<float> latlon_vec = vm["latlon"].as< vector<float> >();
|
vector<float> latlon_vec = vm["latlon"].as< vector<float> >();
|
||||||
|
@ -300,6 +297,22 @@ void prog_opts(int ac, char* av[])
|
||||||
{
|
{
|
||||||
GLOBALS::get().par_.ssdv_dir_ = vm["ssdv_dir"].as<string>();
|
GLOBALS::get().par_.ssdv_dir_ = vm["ssdv_dir"].as<string>();
|
||||||
}
|
}
|
||||||
|
if (vm.count("sondehub"))
|
||||||
|
{
|
||||||
|
GLOBALS::get().par_.sondehub_ = vm["sondehub"].as<string>();
|
||||||
|
}
|
||||||
|
if (vm.count("iqfile"))
|
||||||
|
{
|
||||||
|
vector<string> file_and_sr = vm["iqfile"].as< vector<string> >();
|
||||||
|
if( file_and_sr.size() != 2 )
|
||||||
|
{
|
||||||
|
cout<<C_RED<<"--iqfile option needs 2 args"<<C_OFF<<endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
GLOBALS::get().par_.iqfile_ = file_and_sr[0];
|
||||||
|
GLOBALS::get().par_.iqfile_sampling_rate_ = stof(file_and_sr[1]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(exception& e)
|
catch(exception& e)
|
||||||
{
|
{
|
||||||
|
|
Ładowanie…
Reference in New Issue