dl-fldigi/src/dl_fldigi/hbtint.cxx

393 wiersze
9.3 KiB
C++

/*
* Copyright (C) 2011 Daniel Richman
* License: GNU GPL 3
*
* hbtint.cxx: habitat integration (see habitat-cpp-connector)
*/
#include "dl_fldigi/hbtint.h"
#include <string>
#include <sstream>
#include <FL/Fl.H>
#include "configuration.h"
#include "debug.h"
#include "fl_digi.h"
#include "trx.h"
#include "jsoncpp.h"
#include "habitat/EZ.h"
#include "habitat/UKHASExtractor.h"
#include "dl_fldigi/dl_fldigi.h"
#include "dl_fldigi/version.h"
#include "dl_fldigi/location.h"
#include "dl_fldigi/flights.h"
using namespace std;
namespace dl_fldigi {
namespace hbtint {
static EZ::cURLGlobal *cgl;
DExtractorManager *extrmgr;
DUploaderThread *uthr;
static habitat::UKHASExtractor *ukhas;
static EZ::Mutex rig_mutex;
static time_t rig_freq_updated, rig_mode_updated;
static long long rig_freq;
static string rig_mode;
void init()
{
cgl = new EZ::cURLGlobal();
uthr = new DUploaderThread();
extrmgr = new DExtractorManager(*uthr);
ukhas = new habitat::UKHASExtractor();
extrmgr->add(*ukhas);
}
void start()
{
uthr->start();
}
void cleanup()
{
delete extrmgr;
delete ukhas;
extrmgr = 0;
ukhas = 0;
/* This prevents deadlocks with our use of Fl::lock in the uploader
* thread (which is a necessary evil since we're accessing loads of
* global fldigi stuff) */
if (uthr)
uthr->shutdown();
while (uthr)
Fl::wait();
delete cgl;
cgl = 0;
}
void rig_set_freq(long long freq)
{
EZ::MutexLock lock(rig_mutex);
rig_freq_updated = time(NULL);
rig_freq = freq;
}
void rig_set_mode(const string &mode)
{
EZ::MutexLock lock(rig_mutex);
rig_mode_updated = time(NULL);
rig_mode = mode;
}
static void uthr_thread_death(void *what)
{
if (what != uthr)
{
LOG_ERROR("unknown thread");
return;
}
LOG_INFO("cleaning up");
uthr->join();
delete uthr;
uthr = 0;
}
/* Modify run() to help us shutdown the thread */
void *DUploaderThread::run()
{
void *ret = UploaderThread::run();
Fl::awake(uthr_thread_death, this);
return ret;
}
/* Some functions below are called via a DUploaderThread pointer so
* the fact that they are non virtual is OK. Having a different set of
* arguments even prevents the wrong function from being selected.
* payload_telemetry() is actually virtual, so that the ExtractorManager
* can call it */
void DUploaderThread::settings()
{
Fl_AutoLock lock;
UploaderThread::reset();
if (!online())
{
warning("upload disabled: offline");
return;
}
if (!progdefaults.myCall.size() || !progdefaults.habitat_uri.size() ||
!progdefaults.habitat_db.size())
{
warning("upload disabled: settings missing");
return;
}
UploaderThread::settings(progdefaults.myCall, progdefaults.habitat_uri,
progdefaults.habitat_db);
}
void DUploaderThread::payload_telemetry(const string &data,
const Json::Value &metadata, int time_created)
{
Fl_AutoLock lock;
/* If the frequency/mode from the rig is recent, upload it.
* null metadata is automatically converted to an object by jsoncpp */
Json::Value rig_info(Json::objectValue);
if (rig_freq_updated >= time(NULL) - 30)
rig_info["frequency"] = rig_freq;
if (rig_mode_updated >= time(NULL) - 30)
rig_info["mode"] = rig_mode;
rig_info["audio_frequency"] = active_modem->get_freq();
rig_info["reversed"] = active_modem->get_reverse();
Json::Value new_metadata = metadata;
new_metadata["rig_info"] = rig_info;
UploaderThread::payload_telemetry(data, new_metadata, time_created);
}
/* This function is used for stationary listener telemetry only */
void DUploaderThread::listener_telemetry()
{
Fl_AutoLock lock;
if (location::current_location_mode != location::LOC_STATIONARY)
{
warning("attempted to upload stationary listener "
"telemetry while in GPS telemetry mode");
return;
}
location::update_stationary();
if (!location::listener_valid)
return;
Json::Value data(Json::objectValue);
data["latitude"] = location::listener_latitude;
data["longitude"] = location::listener_longitude;
if (location::listener_altitude != 0)
data["altitude"] = location::listener_altitude;
UploaderThread::listener_telemetry(data);
}
void DUploaderThread::listener_telemetry(const Json::Value &data)
{
Fl_AutoLock lock;
if (location::current_location_mode != location::LOC_GPS)
throw runtime_error("Attempted to upload GPS data while not "
"in GPS mode");
UploaderThread::listener_telemetry(data);
}
static void info_add(Json::Value &data, const string &key, const string &value)
{
if (value.size())
data[key] = value;
}
void DUploaderThread::listener_information()
{
Fl_AutoLock lock;
Json::Value data(Json::objectValue);
info_add(data, "name", progdefaults.myName);
info_add(data, "location", progdefaults.myQth);
info_add(data, "radio", progdefaults.myRadio);
info_add(data, "antenna", progdefaults.myAntenna);
data["dl_fldigi"] = git_short_commit;
UploaderThread::listener_information(data);
}
/* These functions absolutely must be thread safe. */
void DUploaderThread::log(const string &message)
{
Fl_AutoLock lock;
LOG_DEBUG("hbtUT %s", message.c_str());
}
void DUploaderThread::warning(const string &message)
{
Fl_AutoLock lock;
LOG_WARN("hbtUT %s", message.c_str());
status_important(message);
}
void DUploaderThread::caught_exception(const habitat::NotInitialisedError &e)
{
Fl_AutoLock lock;
LOG_WARN("NotInitialisedError");
status_important("Can't upload! Either in offline mode, or "
"your callsign is not set.");
}
void DUploaderThread::saved_id(const string &type, const string &id)
{
/* Log as normal, but also set status */
UploaderThread::saved_id(type, id);
status("Uploaded " + type + " successfully");
}
void DUploaderThread::got_flights(const vector<Json::Value> &new_flights)
{
ostringstream ltmp;
ltmp << "Downloaded " << new_flights.size() << " flight docs";
log(ltmp.str());
flights::new_flight_docs(new_flights);
}
void DUploaderThread::got_payloads(const vector<Json::Value> &new_payloads)
{
ostringstream ltmp;
ltmp << "Downloaded " << new_payloads.size() << " payload docs";
log(ltmp.str());
flights::new_payload_docs(new_payloads);
}
/* Be careful not to call this function instead of dl_fldigi::status() */
void DExtractorManager::status(const string &msg)
{
Fl_AutoLock lock;
LOG_DEBUG("hbtE %s", msg.c_str());
}
static void set_jvalue(Fl_Output *widget, const Json::Value &value)
{
if (value.isString())
{
widget->value(value.asCString());
}
else if (value.isBool() || value.isNumeric())
{
ostringstream tmp;
if (value.isDouble())
{
tmp.setf(ios::fixed);
tmp.precision(6);
tmp << value.asDouble();
}
else if (value.isIntegral())
{
tmp << value.asInt();
}
else if (value.isBool())
{
tmp << value.asBool();
}
widget->value(tmp.str().c_str());
}
else
{
/* We can't print objects or arrays, and null is just "" */
widget->value("");
}
}
void DExtractorManager::data(const Json::Value &d)
{
Fl_AutoLock lock;
if (!hab_ui_exists)
return;
if (d["_sentence"].isString())
{
string clean = d["_sentence"].asString();
/* the \n shows up badly. remove it */
int last = clean.size() - 1;
if (last >= 0 && clean[last] == '\n')
clean[last] = '\0';
habString->value(clean.c_str());
if (d["_parsed"].isBool() && d["_parsed"].asBool())
habString->color(FL_GREEN);
else
habString->color(FL_RED);
}
else
{
habString->value("");
habString->color(FL_WHITE);
}
if (d["_sentence"].isNull())
{
habString->color(FL_WHITE);
habChecksum->value("");
}
else if (d["_parsed"].isBool() && d["_parsed"].asBool())
{
habString->color(FL_GREEN);
habChecksum->value("GOOD :-)");
}
else
{
habString->color(FL_RED);
habChecksum->value("BAD :-(");
}
habString->damage(FL_DAMAGE_ALL);
/* UKHAS crude parser doesn't split up the time, like the real one does */
set_jvalue(habRXPayload, d["payload"]);
set_jvalue(habTime, d["time"]);
set_jvalue(habLat, d["latitude"]);
set_jvalue(habLon, d["longitude"]);
set_jvalue(habAlt, d["altitude"]);
habTimeSinceLastRx->value("just now");
last_rx = time(NULL);
if (d["latitude"].isString() && d["longitude"].isString())
{
istringstream lat_strm(d["latitude"].asString());
istringstream lon_strm(d["longitude"].asString());
istringstream alt_strm(d["altitude"].asString());
lat_strm >> location::balloon_latitude;
lon_strm >> location::balloon_longitude;
alt_strm >> location::balloon_altitude;
location::balloon_valid = !lat_strm.fail() && !lon_strm.fail() &&
!alt_strm.fail();
}
else
{
location::balloon_valid = false;
}
location::update_distance_bearing();
}
} /* namespace hbtint */
} /* namespace dl_fldigi */