kopia lustrzana https://github.com/jamescoxon/dl-fldigi
1290 wiersze
32 KiB
C++
1290 wiersze
32 KiB
C++
/*
|
|
* Copyright (C) 2011 Daniel Richman
|
|
* License: GNU GPL 3
|
|
*
|
|
* flights.cxx: flight and payload document management, GUI and autoconfig.
|
|
*/
|
|
|
|
#include "dl_fldigi/flights.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <set>
|
|
#include <unistd.h>
|
|
|
|
#include "main.h"
|
|
#include "debug.h"
|
|
#include "fl_digi.h"
|
|
#include "configuration.h"
|
|
#include "confdialog.h"
|
|
#include "timeops.h"
|
|
|
|
#include "jsoncpp.h"
|
|
#include "habitat/RFC3339.h"
|
|
#include "dl_fldigi/dl_fldigi.h"
|
|
#include "dl_fldigi/hbtint.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace dl_fldigi {
|
|
namespace flights {
|
|
|
|
bool downloaded_flights_once, downloaded_payloads_once;
|
|
|
|
static string flight_cache_file, payload_cache_file;
|
|
static vector<Json::Value> flight_docs, payload_docs;
|
|
|
|
/* These pointers just point at some part of the heap allocated by something
|
|
* in either the flight_docs vector (if cur_heap == TRACKING_FLIGHT) or
|
|
* the payload_docs vector (if cur_heap == TRACKING_PAYLOAD).
|
|
* They're invalidated when the relevant vector is modified. When new data is
|
|
* downloaded, the relvant populate_{flights,payloads} function will update
|
|
* these if necessary.
|
|
* hbtint::extrmgr->payload should be called when cur_payload is updated.
|
|
* The data pointed to must not be modified at all while extrmgr has a
|
|
* pointer to it. populate_*'s cleanup actions remove it before modifying. */
|
|
static const Json::Value *cur_flight, *cur_payload, *cur_transmission;
|
|
static int cur_transmission_index, cur_transmission_count;
|
|
static int payload_search_first = 1;
|
|
/* managed by select_flight and select_payload. Checked by
|
|
* select_flight_payload, populate_flights and populate_payloads */
|
|
static enum tracking_type_enum cur_heap = TRACKING_NOTHING;
|
|
|
|
static void load_cache_file(const string &name, vector<Json::Value> &target);
|
|
static void write_cache_file(const string &name,
|
|
const vector<Json::Value> &docs);
|
|
|
|
/* Note: these functions, in the menus they populate, store the index of the
|
|
* Json::Value in the array it's contained in as the userdata of the item,
|
|
* cast (int) -> (void *) */
|
|
static void populate_flights();
|
|
static void populate_payloads();
|
|
|
|
static void select_flight_payload(int index);
|
|
static void do_select_payload(const Json::Value &payload);
|
|
static void select_transmission(int index);
|
|
|
|
static void autoconfigure_rtty(const Json::Value &settings);
|
|
static void autoconfigure_rtty_shift(const Json::Value &value);
|
|
static void autoconfigure_rtty_baud(const Json::Value &value);
|
|
static void autoconfigure_rtty_encoding(const Json::Value &value);
|
|
static void autoconfigure_rtty_parity(const Json::Value &value);
|
|
static void autoconfigure_rtty_stop(const Json::Value &value);
|
|
static void autoconfigure_dominoex(const Json::Value &settings);
|
|
static void autoconfigure_hellschreiber(const Json::Value &settings);
|
|
|
|
static string escape_menu_string(const string &s_);
|
|
static string escape_browser_string(const string &s_);
|
|
static string squash_string(const char *str);
|
|
static string join_set(const set<string> &items, const string &sep=", ");
|
|
|
|
static string flight_choice_item(const string &name,
|
|
const string &callsign_list,
|
|
int attempt);
|
|
static string flight_browser_item(const string &name, const string &date,
|
|
const string &callsign_list);
|
|
static string payload_browser_item(const string &name,
|
|
const string &callsign_list,
|
|
const string &description);
|
|
static string flight_payload_menu_item(const string &name,
|
|
const string &callsign_list,
|
|
int attempt);
|
|
static string mode_menu_name(int index, const Json::Value &settings);
|
|
|
|
static string flight_callsign_list(const Json::Value &flight);
|
|
static string flight_launch_date(const Json::Value &flight);
|
|
static string payload_callsign_list(const Json::Value &payload);
|
|
|
|
static void flight_choice_callback(Fl_Widget *w, void *a);
|
|
static void flight_payload_choice_callback(Fl_Widget *w, void *a);
|
|
static void mode_choice_callback(Fl_Widget *w, void *a);
|
|
|
|
void init()
|
|
{
|
|
/* called with Fl lock acquired */
|
|
|
|
flight_cache_file = HomeDir + "flight_docs.json";
|
|
payload_cache_file = HomeDir + "payload_configuration_docs.json";
|
|
}
|
|
|
|
void cleanup()
|
|
{
|
|
/* called with Fl lock acquired */
|
|
|
|
flight_docs.clear();
|
|
payload_docs.clear();
|
|
cur_flight = NULL;
|
|
cur_payload = NULL;
|
|
cur_transmission = NULL;
|
|
cur_transmission_index = 0;
|
|
cur_transmission_count = 0;
|
|
cur_heap = TRACKING_NOTHING;
|
|
}
|
|
|
|
void load_cache()
|
|
{
|
|
/* resets everything */
|
|
select_flight(-1);
|
|
|
|
/* called with Fl lock acquired */
|
|
|
|
load_cache_file(flight_cache_file, flight_docs);
|
|
load_cache_file(payload_cache_file, payload_docs);
|
|
|
|
populate_flights();
|
|
populate_payloads();
|
|
}
|
|
|
|
void new_flight_docs(const vector<Json::Value> &new_flights)
|
|
{
|
|
Fl_AutoLock lock;
|
|
flight_docs = new_flights;
|
|
downloaded_flights_once = true;
|
|
write_cache_file(flight_cache_file, flight_docs);
|
|
populate_flights();
|
|
}
|
|
|
|
void new_payload_docs(const vector<Json::Value> &new_payloads)
|
|
{
|
|
Fl_AutoLock lock;
|
|
payload_docs = new_payloads;
|
|
downloaded_payloads_once = true;
|
|
write_cache_file(payload_cache_file, payload_docs);
|
|
populate_payloads();
|
|
}
|
|
|
|
void payload_search(bool next)
|
|
{
|
|
/* Searching the payload_browser rather than looking through our JSON docs?
|
|
* Well: it's easier in several ways, and quicker. We've already extracted
|
|
* the important strings in populate_flights and filtered for testing
|
|
* flights; that would have to be duplicated. */
|
|
|
|
Fl_AutoLock lock;
|
|
|
|
const string search(squash_string(payload_search_text->value()));
|
|
if (!search.size())
|
|
return;
|
|
|
|
int n = payload_browser->size();
|
|
|
|
if (!n)
|
|
return;
|
|
|
|
if (!next || payload_search_first > n)
|
|
payload_search_first = 1;
|
|
|
|
int i = payload_search_first;
|
|
|
|
do
|
|
{
|
|
const string line(squash_string(payload_browser->text(i)));
|
|
|
|
if (line.find(search) != string::npos)
|
|
{
|
|
payload_browser->value(i);
|
|
select_payload(i - 1);
|
|
|
|
payload_search_first = i + 1;
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
if (i > n)
|
|
i = 1;
|
|
}
|
|
while (i != payload_search_first);
|
|
}
|
|
|
|
void select_flight(int index)
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("Selecting flight, index %i", index);
|
|
|
|
/* Reset */
|
|
cur_flight = NULL;
|
|
cur_heap = TRACKING_NOTHING;
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
habCHPayload->value(-1);
|
|
habCHPayload->clear();
|
|
habCHPayload->deactivate();
|
|
}
|
|
|
|
flight_payload_list->value(-1);
|
|
flight_payload_list->clear();
|
|
flight_payload_list->deactivate();
|
|
|
|
do_select_payload(Json::Value::null);
|
|
|
|
/* Tests */
|
|
if (index < 0 || index >= int(flight_docs.size()))
|
|
return;
|
|
|
|
const Json::Value &flight = flight_docs[index];
|
|
|
|
if (!flight.isObject() || !flight.size() || !flight["_id"].isString())
|
|
return;
|
|
|
|
const string id = flight["_id"].asString();
|
|
const Json::Value &payloads = flight["_payload_docs"];
|
|
|
|
if (!id.size() || !payloads.isArray() || !payloads.size())
|
|
return;
|
|
|
|
LOG_DEBUG("flight OK. contains %i payloads", payloads.size());
|
|
|
|
/* Doc looks ok, so set up */
|
|
cur_flight = &flight;
|
|
cur_heap = TRACKING_FLIGHT;
|
|
|
|
if (progdefaults.tracking_type != TRACKING_FLIGHT ||
|
|
progdefaults.tracking_doc != id)
|
|
{
|
|
progdefaults.tracking_type = TRACKING_FLIGHT;
|
|
progdefaults.tracking_doc = id;
|
|
progdefaults.tracking_flight_payload = -1;
|
|
progdefaults.tracking_transmission = -1;
|
|
progdefaults.changed = true;
|
|
}
|
|
|
|
set<string> choice_items;
|
|
|
|
for (Json::Value::const_iterator it = payloads.begin();
|
|
it != payloads.end(); ++it)
|
|
{
|
|
const Json::Value &root = *it;
|
|
string name, callsigns;
|
|
|
|
if (root.isObject() && root["name"].isString())
|
|
{
|
|
name = root["name"].asString();
|
|
callsigns = payload_callsign_list(root);
|
|
}
|
|
else
|
|
{
|
|
name = "Unknown";
|
|
}
|
|
|
|
int attempt = 1;
|
|
string item;
|
|
|
|
do
|
|
{
|
|
item = flight_payload_menu_item(name, callsigns, attempt);
|
|
attempt++;
|
|
}
|
|
while(choice_items.count(item));
|
|
choice_items.insert(item);
|
|
|
|
if (hab_ui_exists)
|
|
habCHPayload->add(item.c_str(), (int) 0,
|
|
flight_payload_choice_callback, NULL);
|
|
flight_payload_list->add(item.c_str(), (int) 0,
|
|
flight_payload_choice_callback, NULL);
|
|
}
|
|
|
|
int auto_select = progdefaults.tracking_flight_payload;
|
|
if (auto_select < 0 || auto_select >= int(payloads.size()))
|
|
auto_select = 0;
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
habCHPayload->activate();
|
|
habCHPayload->value(auto_select);
|
|
}
|
|
|
|
flight_payload_list->activate();
|
|
flight_payload_list->value(auto_select);
|
|
|
|
select_flight_payload(auto_select);
|
|
}
|
|
|
|
void select_payload(int index)
|
|
{
|
|
select_flight(-1);
|
|
do_select_payload(Json::Value::null);
|
|
|
|
cur_heap = TRACKING_NOTHING;
|
|
|
|
LOG_DEBUG("Selecting payload, index %i", index);
|
|
|
|
if (index < 0 || index >= int(payload_docs.size()))
|
|
return;
|
|
|
|
const Json::Value &payload = payload_docs[index];
|
|
|
|
if (!payload.isObject() || !payload.size() || !payload["_id"].isString())
|
|
return;
|
|
|
|
const string id = payload["_id"].asString();
|
|
|
|
if (progdefaults.tracking_type != TRACKING_PAYLOAD ||
|
|
progdefaults.tracking_doc != id)
|
|
{
|
|
progdefaults.tracking_type = TRACKING_PAYLOAD;
|
|
progdefaults.tracking_doc = id;
|
|
progdefaults.tracking_flight_payload = -1;
|
|
progdefaults.tracking_transmission = -1;
|
|
progdefaults.changed = true;
|
|
}
|
|
|
|
cur_heap = TRACKING_PAYLOAD;
|
|
do_select_payload(payload);
|
|
}
|
|
|
|
void auto_configure()
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("autoconfiguring");
|
|
|
|
if (!cur_transmission)
|
|
return;
|
|
|
|
const Json::Value &settings = *cur_transmission;
|
|
|
|
if (!settings.isObject() || !settings.size())
|
|
return;
|
|
|
|
if (!settings["modulation"].isString())
|
|
return;
|
|
|
|
const string modulation = settings["modulation"].asString();
|
|
|
|
if (modulation == "RTTY")
|
|
autoconfigure_rtty(settings);
|
|
else if (modulation == "DominoEX")
|
|
autoconfigure_dominoex(settings);
|
|
else if (modulation == "Hellschreiber")
|
|
autoconfigure_hellschreiber(settings);
|
|
}
|
|
|
|
void auto_switchmode()
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("autoswitchmoding");
|
|
|
|
int next = cur_transmission_index + 1;
|
|
if (next >= cur_transmission_count)
|
|
next = 0;
|
|
|
|
if (hab_ui_exists)
|
|
habCHTransmission->value(next);
|
|
|
|
if (cur_heap == TRACKING_PAYLOAD)
|
|
payload_transmission_list->value(next);
|
|
|
|
if (cur_heap == TRACKING_FLIGHT)
|
|
flight_payload_transmission_list->value(next);
|
|
|
|
select_transmission(next);
|
|
auto_configure();
|
|
}
|
|
|
|
static void load_cache_file(const string &name, vector<Json::Value> &target)
|
|
{
|
|
ifstream cf(name.c_str());
|
|
|
|
if (cf.fail())
|
|
{
|
|
LOG_DEBUG("Failed to open cache file %s", name.c_str());
|
|
return;
|
|
}
|
|
|
|
target.clear();
|
|
|
|
while (cf.good())
|
|
{
|
|
string line;
|
|
getline(cf, line, '\n');
|
|
|
|
char discard;
|
|
while (cf.good() && cf.peek() == '\n')
|
|
cf.get(discard);
|
|
|
|
Json::Reader reader;
|
|
Json::Value root;
|
|
if (!reader.parse(line, root, false))
|
|
break;
|
|
|
|
target.push_back(root);
|
|
}
|
|
|
|
bool failed = cf.fail() || !cf.eof();
|
|
|
|
cf.close();
|
|
|
|
if (failed)
|
|
{
|
|
target.clear();
|
|
LOG_WARN("Failed to load %s", name.c_str());
|
|
}
|
|
else
|
|
{
|
|
long int n = target.size();
|
|
LOG_DEBUG("Loaded %li docs from file %s", n, name.c_str());
|
|
}
|
|
}
|
|
|
|
static void write_cache_file(const string &name,
|
|
const vector<Json::Value> &docs)
|
|
{
|
|
ofstream cf(name.c_str(), ios_base::out | ios_base::trunc);
|
|
|
|
for (vector<Json::Value>::const_iterator it = docs.begin();
|
|
it != docs.end() && cf.good();
|
|
it++)
|
|
{
|
|
Json::FastWriter writer;
|
|
cf << writer.write(*it);
|
|
}
|
|
|
|
bool success = cf.good();
|
|
|
|
cf.close();
|
|
|
|
if (!success)
|
|
{
|
|
LOG_WARN("unable to save docs to %s", name.c_str());
|
|
unlink(name.c_str());
|
|
}
|
|
}
|
|
|
|
static void populate_flights()
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("populating flights (%zi)", flight_docs.size());
|
|
|
|
set<string> choice_items;
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
habFlight->value(-1);
|
|
habFlight->clear();
|
|
}
|
|
|
|
flight_browser->clear();
|
|
|
|
if (cur_heap == TRACKING_FLIGHT)
|
|
select_flight(-1);
|
|
|
|
for (int i = 0; i < int(flight_docs.size()); i++)
|
|
{
|
|
const Json::Value &root = flight_docs[i];
|
|
|
|
string id, name, callsign_list, date;
|
|
|
|
if (root.isObject() && root.size() &&
|
|
root["_id"].isString() && root["name"].isString())
|
|
{
|
|
id = root["_id"].asString();
|
|
name = root["name"].asString();
|
|
callsign_list = flight_callsign_list(root);
|
|
date = flight_launch_date(root);
|
|
}
|
|
|
|
bool root_ok = (id.size() && name.size());
|
|
|
|
if (!root_ok)
|
|
{
|
|
name = "Invalid flight doc";
|
|
LOG_WARN("invalid flight doc");
|
|
}
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
string item;
|
|
int attempt = 1;
|
|
|
|
/* Avoid duplicate menu items: fltk removes them */
|
|
do
|
|
{
|
|
item = flight_choice_item(name, callsign_list, attempt);
|
|
attempt++;
|
|
}
|
|
while (choice_items.count(item));
|
|
choice_items.insert(item);
|
|
|
|
habFlight->add(item.c_str(), (int) 0, flight_choice_callback,
|
|
NULL);
|
|
}
|
|
|
|
string browser_item = flight_browser_item(name, date, callsign_list);
|
|
flight_browser->add(browser_item.c_str(), NULL);
|
|
|
|
if (root_ok &&
|
|
progdefaults.tracking_type == TRACKING_FLIGHT &&
|
|
progdefaults.tracking_doc == id)
|
|
{
|
|
if (hab_ui_exists)
|
|
habFlight->value(i);
|
|
flight_browser->value(i + 1);
|
|
select_flight(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void populate_payloads()
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("populating payloads (%zi)", payload_docs.size());
|
|
|
|
payload_browser->clear();
|
|
|
|
if (cur_heap == TRACKING_PAYLOAD)
|
|
select_payload(-1);
|
|
|
|
for (int i = 0; i < int(payload_docs.size()); i++)
|
|
{
|
|
const Json::Value &root = payload_docs[i];
|
|
|
|
string id, name, callsign_list, description;
|
|
|
|
if (root.isObject() && root.size() &&
|
|
root["_id"].isString() && root["name"].isString())
|
|
{
|
|
id = root["_id"].asString();
|
|
name = root["name"].asString();
|
|
callsign_list = payload_callsign_list(root);
|
|
|
|
if (root["metadata"]["description"].isString())
|
|
description = root["metadata"]["description"].asString();
|
|
}
|
|
|
|
bool root_ok = (id.size() && name.size());
|
|
|
|
if (!root_ok)
|
|
LOG_WARN("invalid payload doc");
|
|
|
|
string browser_item = payload_browser_item(name, callsign_list,
|
|
description);
|
|
payload_browser->add(browser_item.c_str(), NULL);
|
|
|
|
if (root_ok &&
|
|
progdefaults.tracking_type == TRACKING_PAYLOAD &&
|
|
progdefaults.tracking_doc == id)
|
|
{
|
|
payload_browser->value(i + 1);
|
|
select_payload(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void select_flight_payload(int index)
|
|
{
|
|
Fl_AutoLock lock;
|
|
LOG_DEBUG("Selecting flight payload %i", index);
|
|
|
|
if (!cur_flight || cur_heap != TRACKING_FLIGHT)
|
|
return;
|
|
|
|
const Json::Value &payload_list = (*cur_flight)["_payload_docs"];
|
|
|
|
if (!payload_list.isArray() || !payload_list.size())
|
|
return;
|
|
|
|
if (index < 0 || index >= int(payload_list.size()))
|
|
return;
|
|
|
|
if (progdefaults.tracking_flight_payload != index)
|
|
{
|
|
progdefaults.tracking_flight_payload = index;
|
|
progdefaults.tracking_transmission = -1;
|
|
progdefaults.changed = true;
|
|
}
|
|
|
|
do_select_payload(payload_list[index]);
|
|
}
|
|
|
|
static void do_select_payload(const Json::Value &payload)
|
|
{
|
|
if (payload.isNull())
|
|
LOG_DEBUG("do_select_payload(null)");
|
|
else
|
|
LOG_DEBUG("do_select_payload(object)");
|
|
|
|
/* Disable stuff, incase tests fail */
|
|
cur_payload = NULL;
|
|
hbtint::extrmgr->payload(NULL);
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
habCHTransmission->value(-1);
|
|
habCHTransmission->clear();
|
|
habCHTransmission->deactivate();
|
|
habSwitchModes->deactivate();
|
|
}
|
|
|
|
payload_transmission_list->value(-1);
|
|
payload_transmission_list->clear();
|
|
payload_transmission_list->deactivate();
|
|
|
|
flight_payload_transmission_list->value(-1);
|
|
flight_payload_transmission_list->clear();
|
|
flight_payload_transmission_list->deactivate();
|
|
|
|
select_transmission(-1);
|
|
|
|
/* Sanity checks */
|
|
if (!payload.isObject() || !payload.size())
|
|
return;
|
|
|
|
/* OK. Setup */
|
|
cur_payload = &payload;
|
|
hbtint::extrmgr->payload(&payload);
|
|
|
|
LOG_DEBUG("payload OK, checking transmissions");
|
|
|
|
const Json::Value &transmissions = payload["transmissions"];
|
|
|
|
if (!transmissions.isArray() || !transmissions.size())
|
|
return;
|
|
|
|
cur_transmission_count = transmissions.size();
|
|
if (cur_transmission_count < 0)
|
|
cur_transmission_count = 0;
|
|
|
|
LOG_DEBUG("populating transmissions (%i)", cur_transmission_count);
|
|
|
|
for (int i = 0; i < cur_transmission_count; i++)
|
|
{
|
|
const Json::Value &transmission = transmissions[i];
|
|
|
|
const string name = mode_menu_name(i + 1, transmission);
|
|
|
|
if (hab_ui_exists)
|
|
habCHTransmission->add(name.c_str(), (int) 0,
|
|
mode_choice_callback, NULL);
|
|
if (cur_heap == TRACKING_FLIGHT)
|
|
flight_payload_transmission_list->add(name.c_str(), (int) 0,
|
|
mode_choice_callback, NULL);
|
|
if (cur_heap == TRACKING_PAYLOAD)
|
|
payload_transmission_list->add(name.c_str(), (int) 0,
|
|
mode_choice_callback, NULL);
|
|
}
|
|
|
|
int auto_select = progdefaults.tracking_transmission;
|
|
if (auto_select < 0 || auto_select >= cur_transmission_count)
|
|
auto_select = 0;
|
|
|
|
if (hab_ui_exists)
|
|
{
|
|
if (cur_transmission_count > 1)
|
|
habSwitchModes->activate();
|
|
|
|
habCHTransmission->activate();
|
|
habCHTransmission->value(auto_select);
|
|
}
|
|
|
|
if (cur_heap == TRACKING_FLIGHT)
|
|
{
|
|
flight_payload_transmission_list->activate();
|
|
flight_payload_transmission_list->value(auto_select);
|
|
}
|
|
|
|
if (cur_heap == TRACKING_PAYLOAD)
|
|
{
|
|
payload_transmission_list->activate();
|
|
payload_transmission_list->value(auto_select);
|
|
}
|
|
|
|
select_transmission(auto_select);
|
|
}
|
|
|
|
static void select_transmission(int index)
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
LOG_DEBUG("selecting transmission %i", index);
|
|
|
|
/* Reset */
|
|
cur_transmission = NULL;
|
|
cur_transmission_index = 0;
|
|
|
|
if (hab_ui_exists)
|
|
habConfigureButton->deactivate();
|
|
|
|
payload_autoconfigure_a->deactivate();
|
|
payload_autoconfigure_b->deactivate();
|
|
|
|
/* Checks */
|
|
if (!cur_payload)
|
|
return;
|
|
|
|
const Json::Value &transmissions = (*cur_payload)["transmissions"];
|
|
|
|
if (!transmissions.isArray() ||
|
|
int(transmissions.size()) != cur_transmission_count)
|
|
return;
|
|
|
|
if (index < 0 || index >= cur_transmission_count)
|
|
return;
|
|
|
|
LOG_DEBUG("transmission OK");
|
|
|
|
/* OK. Set stuff */
|
|
cur_transmission = &(transmissions[index]);
|
|
cur_transmission_index = index;
|
|
|
|
if (progdefaults.tracking_transmission != index)
|
|
{
|
|
progdefaults.tracking_transmission = index;
|
|
progdefaults.changed = true;
|
|
}
|
|
|
|
if (hab_ui_exists)
|
|
habConfigureButton->activate();
|
|
|
|
payload_autoconfigure_a->activate();
|
|
payload_autoconfigure_b->activate();
|
|
}
|
|
|
|
static void autoconfigure_rtty(const Json::Value &settings)
|
|
{
|
|
LOG_DEBUG("autoconfigure rtty");
|
|
|
|
autoconfigure_rtty_shift(settings["shift"]);
|
|
autoconfigure_rtty_baud(settings["baud"]);
|
|
autoconfigure_rtty_encoding(settings["encoding"]);
|
|
autoconfigure_rtty_parity(settings["parity"]);
|
|
autoconfigure_rtty_stop(settings["stop"]);
|
|
|
|
init_modem_sync(MODE_RTTY);
|
|
resetRTTY();
|
|
progdefaults.changed = true;
|
|
}
|
|
|
|
static void autoconfigure_rtty_shift(const Json::Value &value)
|
|
{
|
|
if (!value.isNumeric())
|
|
return;
|
|
|
|
double shift = value.asDouble();
|
|
|
|
/* Look in the standard shifts first */
|
|
int search;
|
|
for (search = 0; rtty::SHIFT[search] != 0; search++)
|
|
{
|
|
double diff = rtty::SHIFT[search] - shift;
|
|
/* I love floats :-( */
|
|
if (diff < 0.1 && diff > -0.1)
|
|
{
|
|
selShift->index(search);
|
|
selCustomShift->deactivate();
|
|
progdefaults.rtty_shift = search;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If not found (i.e., we found the terminating 0, and haven't returned)
|
|
* then search == the index of the "Custom" menu item */
|
|
selShift->index(search);
|
|
selCustomShift->activate();
|
|
progdefaults.rtty_shift = search;
|
|
selCustomShift->value(shift);
|
|
progdefaults.rtty_custom_shift = shift;
|
|
}
|
|
|
|
static void autoconfigure_rtty_baud(const Json::Value &value)
|
|
{
|
|
if (!value.isNumeric())
|
|
return;
|
|
|
|
double baud = value.asDouble();
|
|
int search;
|
|
for (search = 0; rtty::BAUD[search] != 0; search++)
|
|
{
|
|
double diff = rtty::BAUD[search] - baud;
|
|
if (diff < 0.01 && diff > -0.01)
|
|
{
|
|
selBaud->index(search);
|
|
progdefaults.rtty_baud = search;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void autoconfigure_rtty_encoding(const Json::Value &value)
|
|
{
|
|
if (!value.isString())
|
|
return;
|
|
|
|
const string encoding = value.asString();
|
|
int select = -1;
|
|
|
|
/* rtty::BITS[] = {5, 7, 8}; */
|
|
if (encoding == "BAUDOT")
|
|
select = 0;
|
|
else if (encoding == "ASCII-7")
|
|
select = 1;
|
|
else if (encoding == "ASCII-8")
|
|
select = 2;
|
|
else
|
|
return;
|
|
|
|
selBits->index(select);
|
|
progdefaults.rtty_bits = select;
|
|
|
|
/* From selBits' callback */
|
|
if (select == 0)
|
|
{
|
|
progdefaults.rtty_parity = RTTY_PARITY_NONE;
|
|
selParity->index(RTTY_PARITY_NONE);
|
|
}
|
|
}
|
|
|
|
static void autoconfigure_rtty_parity(const Json::Value &value)
|
|
{
|
|
if (!value.isString())
|
|
return;
|
|
|
|
/* Parity is disabled when using baudot (rtty_bits == 0) */
|
|
if (progdefaults.rtty_bits == 0)
|
|
return;
|
|
|
|
const string parity = value.asString();
|
|
int select = -1;
|
|
|
|
if (parity == "none")
|
|
select = RTTY_PARITY_NONE;
|
|
else if (parity == "even")
|
|
select = RTTY_PARITY_EVEN;
|
|
else if (parity == "odd")
|
|
select = RTTY_PARITY_ODD;
|
|
else if (parity == "zero")
|
|
select = RTTY_PARITY_ZERO;
|
|
else if (parity == "one")
|
|
select = RTTY_PARITY_ONE;
|
|
else
|
|
return;
|
|
|
|
selParity->index(select);
|
|
progdefaults.rtty_parity = select;
|
|
}
|
|
|
|
static void autoconfigure_rtty_stop(const Json::Value &value)
|
|
{
|
|
if (!value.isNumeric())
|
|
return;
|
|
|
|
int select = -1;
|
|
|
|
if (value.isInt())
|
|
{
|
|
#ifdef JSON_HAS_INT64 /* test if jsoncpp 0.6 */
|
|
int stop = value.asLargestInt();
|
|
#else
|
|
int stop = value.asInt();
|
|
#endif
|
|
if (stop == 1)
|
|
select = 0;
|
|
else if (stop == 2)
|
|
select = 2;
|
|
}
|
|
else
|
|
{
|
|
double stop = value.asDouble();
|
|
if (stop > 1.49 && stop < 1.51)
|
|
select = 1;
|
|
}
|
|
|
|
if (select == -1)
|
|
return;
|
|
|
|
progdefaults.rtty_stop = select;
|
|
selStopBits->index(select);
|
|
}
|
|
|
|
static void autoconfigure_dominoex(const Json::Value &settings)
|
|
{
|
|
LOG_DEBUG("autoconfigure dominoex");
|
|
|
|
if (!settings["speed"].isInt())
|
|
return;
|
|
|
|
#ifdef JSON_HAS_INT64 /* jsoncpp 0.6 */
|
|
int type = settings["speed"].asLargestInt();
|
|
#else
|
|
int type = settings["speed"].asInt();
|
|
#endif
|
|
int modem = -1;
|
|
|
|
switch (type)
|
|
{
|
|
case 4:
|
|
modem = MODE_DOMINOEX4;
|
|
break;
|
|
|
|
case 5:
|
|
modem = MODE_DOMINOEX5;
|
|
break;
|
|
|
|
case 8:
|
|
modem = MODE_DOMINOEX8;
|
|
break;
|
|
|
|
case 11:
|
|
modem = MODE_DOMINOEX11;
|
|
break;
|
|
|
|
case 16:
|
|
modem = MODE_DOMINOEX16;
|
|
break;
|
|
|
|
case 22:
|
|
modem = MODE_DOMINOEX22;
|
|
break;
|
|
}
|
|
|
|
if (modem != -1)
|
|
{
|
|
init_modem_sync(modem);
|
|
resetDOMEX();
|
|
}
|
|
}
|
|
|
|
static void autoconfigure_hellschreiber(const Json::Value &settings)
|
|
{
|
|
LOG_DEBUG("autoconfigure hellschreiber");
|
|
|
|
if (!settings["variant"].isString())
|
|
return;
|
|
|
|
const string variant = settings["variant"].asString();
|
|
int modem = -1;
|
|
|
|
if (variant == "slowhell")
|
|
modem = MODE_SLOWHELL;
|
|
else if (variant == "feldhell")
|
|
modem = MODE_FELDHELL;
|
|
|
|
if (modem != -1)
|
|
init_modem_sync(modem);
|
|
}
|
|
|
|
static string escape_menu_string(const string &orig_str)
|
|
{
|
|
string new_str;
|
|
size_t pos = 0;
|
|
|
|
do
|
|
{
|
|
size_t start = pos;
|
|
|
|
pos = orig_str.find_first_of("&/\\_", start);
|
|
new_str.append(orig_str.substr(start, pos - start));
|
|
|
|
if (pos != string::npos)
|
|
{
|
|
new_str.push_back('\\');
|
|
new_str.push_back(orig_str[pos]);
|
|
pos++;
|
|
}
|
|
}
|
|
while (pos != string::npos);
|
|
|
|
return new_str;
|
|
}
|
|
|
|
static string escape_browser_string(const string &s_)
|
|
{
|
|
string s(s_);
|
|
size_t pos = 0;
|
|
|
|
do
|
|
{
|
|
pos = s.find('\t', pos);
|
|
if (pos != string::npos)
|
|
s[pos] = ' ';
|
|
}
|
|
while (pos != string::npos);
|
|
|
|
return s;
|
|
}
|
|
|
|
static string squash_string(const char *str)
|
|
{
|
|
string result;
|
|
result.reserve(strlen(str));
|
|
|
|
while (*str)
|
|
{
|
|
char c = *str;
|
|
if (isalnum(c))
|
|
result.push_back(tolower(c));
|
|
str++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static string join_set(const set<string> &items, const string &sep)
|
|
{
|
|
string result;
|
|
|
|
for (set<string>::const_iterator it = items.begin();
|
|
it != items.end(); ++it)
|
|
{
|
|
if (it != items.begin())
|
|
result.append(sep);
|
|
|
|
result.append(*it);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static string flight_choice_item(const string &name,
|
|
const string &callsign_list,
|
|
int attempt)
|
|
{
|
|
/* "<name>: <payload>,<payload>,<payload>" */
|
|
|
|
string attempt_suffix;
|
|
|
|
if (attempt != 1)
|
|
{
|
|
ostringstream sfx;
|
|
sfx << " (" << attempt << ")";
|
|
attempt_suffix = sfx.str();
|
|
}
|
|
|
|
return escape_menu_string(name + ": " + callsign_list + attempt_suffix);
|
|
}
|
|
|
|
static string flight_browser_item(const string &name, const string &date,
|
|
const string &callsign_list)
|
|
{
|
|
/* "<name>\t<optional date>\t<callsign>,<callsign>,..." */
|
|
return "@." + escape_browser_string(name) + "\t" +
|
|
"@." + date + "\t" +
|
|
"@." + escape_browser_string(callsign_list);
|
|
}
|
|
|
|
static string payload_browser_item(const string &name,
|
|
const string &callsign_list,
|
|
const string &description)
|
|
{
|
|
return "@." + escape_browser_string(name) + "\t"
|
|
+ "@." + "(" + escape_browser_string(callsign_list) + ") "
|
|
+ escape_browser_string(description);
|
|
}
|
|
|
|
static string flight_payload_menu_item(const string &name,
|
|
const string &callsign_list,
|
|
int attempt)
|
|
{
|
|
/* "<name>" or "<name> (callsign)" + (attempt) */
|
|
|
|
string attempt_suffix;
|
|
|
|
if (attempt != 1)
|
|
{
|
|
ostringstream sfx;
|
|
sfx << " (" << attempt << ")";
|
|
attempt_suffix = sfx.str();
|
|
}
|
|
|
|
return escape_menu_string(name + " (" + callsign_list + ") "
|
|
+ attempt_suffix);
|
|
}
|
|
|
|
static string mode_menu_name(int index, const Json::Value &settings)
|
|
{
|
|
/* index: modulation type
|
|
* e.g.: 2: RTTY 300 */
|
|
|
|
ostringstream name;
|
|
|
|
name << index << ": ";
|
|
|
|
string modulation;
|
|
|
|
if (settings.isObject() && settings.size() &&
|
|
settings["modulation"].isString())
|
|
{
|
|
modulation = settings["modulation"].asString();
|
|
}
|
|
|
|
if (modulation == "RTTY")
|
|
{
|
|
name << "RTTY";
|
|
|
|
if (settings["baud"].isInt())
|
|
#ifdef JSON_HAS_INT64 /* jsoncpp 0.6 */
|
|
name << ' ' << settings["baud"].asLargestInt();
|
|
#else
|
|
name << ' ' << settings["baud"].asInt();
|
|
#endif
|
|
else if (settings["baud"].isDouble())
|
|
name << ' ' << settings["baud"].asDouble();
|
|
}
|
|
else if (modulation == "DominoEX" && settings["speed"].isInt())
|
|
{
|
|
name << "DomEX " << settings["speed"].asInt();
|
|
}
|
|
else if (modulation == "Hellschreiber" && settings["variant"].isString())
|
|
{
|
|
string variant = settings["variant"].asString();
|
|
if (variant == "feldhell")
|
|
name << "FeldHell";
|
|
else if (variant == "slowhell")
|
|
name << "SlowHell";
|
|
else
|
|
name << "Hellschreiber";
|
|
}
|
|
else
|
|
{
|
|
name << "Unknown";
|
|
}
|
|
|
|
return escape_menu_string(name.str());
|
|
}
|
|
|
|
static string flight_callsign_list(const Json::Value &flight)
|
|
{
|
|
const Json::Value &payloads = flight["_payload_docs"];
|
|
|
|
if (!payloads.isArray() || !payloads.size())
|
|
return "";
|
|
|
|
set<string> callsigns;
|
|
|
|
for (Json::Value::const_iterator it = payloads.begin();
|
|
it != payloads.end(); ++it)
|
|
{
|
|
const Json::Value &payload = *it;
|
|
|
|
if (!payload.isObject())
|
|
continue;
|
|
|
|
const Json::Value &sentences = payload["sentences"];
|
|
|
|
if (!sentences.isArray())
|
|
continue;
|
|
|
|
for (Json::Value::const_iterator it2 = sentences.begin();
|
|
it2 != sentences.end(); ++it2)
|
|
{
|
|
const Json::Value &sentence = *it2;
|
|
|
|
if (!sentence.isObject())
|
|
continue;
|
|
|
|
const Json::Value &callsign = sentence["callsign"];
|
|
|
|
if (!callsign.isString())
|
|
continue;
|
|
|
|
callsigns.insert(callsign.asString());
|
|
}
|
|
}
|
|
|
|
/* sets are sorted, so this comes out in the right order: */
|
|
return join_set(callsigns);
|
|
}
|
|
|
|
static string flight_launch_date(const Json::Value &flight)
|
|
{
|
|
const Json::Value &launch = flight["launch"];
|
|
|
|
if (!launch.isObject() || !launch.size())
|
|
return "";
|
|
|
|
if (!launch.isMember("time") || !launch["time"].isString())
|
|
return "";
|
|
|
|
time_t date = RFC3339::rfc3339_to_timestamp(launch["time"].asString());
|
|
char buf[20];
|
|
struct tm tm;
|
|
|
|
if (localtime_r(&date, &tm) != &tm)
|
|
return "";
|
|
|
|
if (strftime(buf, sizeof(buf), "%a %d %b %y", &tm) <= 0)
|
|
return "";
|
|
|
|
return buf;
|
|
}
|
|
|
|
static string payload_callsign_list(const Json::Value &payload)
|
|
{
|
|
const Json::Value &sentences = payload["sentences"];
|
|
if (!sentences.isArray())
|
|
return "";
|
|
|
|
set<string> callsign_list;
|
|
|
|
for (Json::Value::const_iterator it = sentences.begin();
|
|
it != sentences.end(); ++it)
|
|
{
|
|
const Json::Value &sentence = *it;
|
|
|
|
if (!sentence.isObject())
|
|
continue;
|
|
|
|
const Json::Value &callsign = sentence["callsign"];
|
|
|
|
if (!callsign.isString())
|
|
continue;
|
|
|
|
callsign_list.insert(callsign.asString());
|
|
}
|
|
|
|
return join_set(callsign_list);
|
|
}
|
|
|
|
static void flight_payload_choice_callback(Fl_Widget *w, void *a)
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
Fl_Choice *choice = static_cast<Fl_Choice *>(w);
|
|
|
|
if (choice != flight_payload_list)
|
|
flight_payload_list->value(choice->value());
|
|
if (hab_ui_exists && choice != habCHPayload)
|
|
habCHPayload->value(choice->value());
|
|
|
|
select_flight_payload(choice->value());
|
|
}
|
|
|
|
static void mode_choice_callback(Fl_Widget *w, void *a)
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
Fl_Choice *choice = static_cast<Fl_Choice *>(w);
|
|
|
|
if (hab_ui_exists && choice != habCHTransmission)
|
|
habCHTransmission->value(choice->value());
|
|
if (cur_heap == TRACKING_FLIGHT &&
|
|
choice != flight_payload_transmission_list)
|
|
flight_payload_transmission_list->value(choice->value());
|
|
if (cur_heap == TRACKING_PAYLOAD && choice != payload_transmission_list)
|
|
payload_transmission_list->value(choice->value());
|
|
|
|
select_transmission(choice->value());
|
|
}
|
|
|
|
static void flight_choice_callback(Fl_Widget *w, void *a)
|
|
{
|
|
Fl_AutoLock lock;
|
|
|
|
Fl_Choice *choice = static_cast<Fl_Choice *>(w);
|
|
flight_browser->value(choice->value() + 1);
|
|
|
|
payload_browser->deselect();
|
|
select_flight(choice->value());
|
|
}
|
|
|
|
|
|
} /* namespace flights */
|
|
} /* namespace dl_fldigi */
|