/* * Copyright (C) 2011 Daniel Richman * License: GNU GPL 3 * * hbtint.cxx: habitat integration (see habitat-cpp-connector) */ #include "dl_fldigi/hbtint.h" #include #include #include #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 &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 &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 */