Merged master 8748
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
#ifndef MESSAGE_SERVER_HPP__
|
||||
#define MESSAGE_SERVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "udp_export.h"
|
||||
#include "Radio.hpp"
|
||||
|
||||
#include "pimpl_h.hpp"
|
||||
|
||||
class QString;
|
||||
|
||||
//
|
||||
// MessageServer - a reference implementation of a message server
|
||||
// matching the MessageClient class at the other end
|
||||
// of the wire
|
||||
//
|
||||
// This class is fully functioning and suitable for use in C++
|
||||
// applications that use the Qt framework. Other applications should
|
||||
// use this classes' implementation as a reference implementation.
|
||||
//
|
||||
class UDP_EXPORT MessageServer
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
using port_type = quint16;
|
||||
using Frequency = Radio::Frequency;
|
||||
|
||||
MessageServer (QObject * parent = nullptr,
|
||||
QString const& version = QString {}, QString const& revision = QString {});
|
||||
|
||||
// start or restart the server, if the multicast_group_address
|
||||
// argument is given it is assumed to be a multicast group address
|
||||
// which the server will join
|
||||
Q_SLOT void start (port_type port,
|
||||
QHostAddress const& multicast_group_address = QHostAddress {});
|
||||
|
||||
// ask the client with identification 'id' to make the same action
|
||||
// as a double click on the decode would
|
||||
//
|
||||
// note that the client is not obliged to take any action and only
|
||||
// takes any action if the decode is present and is a CQ or QRZ message
|
||||
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
|
||||
|
||||
// ask the client with identification 'id' to replay all decodes
|
||||
Q_SLOT void replay (QString const& id);
|
||||
|
||||
// ask the client with identification 'id' to halt transmitting
|
||||
// auto_only just disables auto Tx, otherwise halt is immediate
|
||||
Q_SLOT void halt_tx (QString const& id, bool auto_only);
|
||||
|
||||
// ask the client with identification 'id' to set the free text
|
||||
// message and optionally send it ASAP
|
||||
Q_SLOT void free_text (QString const& id, QString const& text, bool send);
|
||||
|
||||
// the following signals are emitted when a client broadcasts the
|
||||
// matching message
|
||||
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
|
||||
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode);
|
||||
Q_SIGNAL void client_closed (QString const& id);
|
||||
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air);
|
||||
Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency
|
||||
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
|
||||
, bool off_air);
|
||||
Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
, Frequency dial_frequency, QString const& mode, QString const& report_sent
|
||||
, QString const& report_received, QString const& tx_power, QString const& comments
|
||||
, QString const& name, QDateTime time_on);
|
||||
Q_SIGNAL void clear_decodes (QString const& id);
|
||||
|
||||
// this signal is emitted when a network error occurs
|
||||
Q_SIGNAL void error (QString const&) const;
|
||||
|
||||
private:
|
||||
class UDP_NO_EXPORT impl;
|
||||
pimpl<impl> m_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,100 +0,0 @@
|
||||
<HTML><HEAD>
|
||||
|
||||
<TITLE> Support Programs </TITLE>
|
||||
|
||||
</HEAD><BODY>
|
||||
|
||||
|
||||
<H1> Support Programs </H1>
|
||||
|
||||
The following programs provide support for testing and performance
|
||||
assessment.
|
||||
|
||||
|
||||
<P><A NAME="rand-src"><HR><B>rand-src</B>: Generate random message bits.
|
||||
|
||||
<BLOCKQUOTE><PRE>
|
||||
rand-src <I>source-file seed n-bits</I>
|
||||
</PRE></BLOCKQUOTE>
|
||||
|
||||
<P>Creates a file of random messages bits called
|
||||
<TT><I>source-file</I></TT>, which is suitable for testing the
|
||||
correctness and performance of other programs. The bits in the file
|
||||
are independent, and are equally likely to be 0 or 1. They are
|
||||
generated pseudo-randomly based on <TT><I>seed</I></TT>. The actual
|
||||
random number seed used will be <TT><I>seed</I></TT> times 10 plus 2,
|
||||
so that the stream of pseudo-random numbers will not be the same as
|
||||
any that might have been used by another program.
|
||||
|
||||
<P>The <TT><I>n-bits</I></TT> argument specifies the number of bits to
|
||||
produce. It can be a single number, or it can consist of a block size
|
||||
and a number of blocks, written with <TT>x</TT> separating these
|
||||
numbers, with no spaces. Each block is written as a single line, with
|
||||
the bits in the block represented by the characters '0' and '1', with
|
||||
no intervening spaces. If the bit count is given by a single number,
|
||||
the block size is assumed to be one.
|
||||
|
||||
<P><B>Example:</B> The following command produces a file containing
|
||||
3 blocks, each consisting of 15 random bits, produced using the pseudo-random
|
||||
number stream identified by the <TT><I>seed</I></TT> of 17:
|
||||
<UL><PRE>
|
||||
<LI>rand-src rsrc 17 15x3
|
||||
</PRE></UL>
|
||||
The contents of the file <TT>rsrc</TT> after this command might be something
|
||||
like the following:
|
||||
<BLOCKQUOTE><PRE>
|
||||
111011000110000
|
||||
010010110010111
|
||||
100000000000111
|
||||
</BLOCKQUOTE></PRE>
|
||||
|
||||
|
||||
<P><A NAME="verify"><HR><B>verify</B>: Verify that decoded blocks are
|
||||
codewords, and that they match the source.
|
||||
|
||||
<BLOCKQUOTE><PRE>
|
||||
verify [ -t ] <I>pchk-file decoded-file</I> [ <I>gen-file</I> [ <I>source-file</I> ] ]
|
||||
</PRE></BLOCKQUOTE>
|
||||
|
||||
<P>Checks whether or not the blocks in <TT><I>decoded-file</I></TT>
|
||||
are codewords, according to the parity check matrix in
|
||||
<TT><I>pchk-file</I></TT>. If <TT><I>gen-file</I></TT> is specified,
|
||||
the message bits of the blocks are also checked against the
|
||||
corresponding blocks of <TT><I>source-file</I></TT>, or against zero
|
||||
if <TT><I>source-file</I></TT> is not given. (Normally, one would
|
||||
leave out <TT><I>source-file</I></TT> only if the <A
|
||||
HREF="channel.html#transmit"><TT>transmit</TT></A> command was used
|
||||
with an argument specifying that zeros are to be transmitted, rather
|
||||
than a file of encoded data.)
|
||||
|
||||
<P>A summary of the results is displayed on standard error, giving the
|
||||
total numbers of blocks, the number with parity check errors, and, if
|
||||
<TT><I>gen-file</I></TT> was specified, the number of blocks with
|
||||
source errors and the number with errors of both kinds. If
|
||||
<TT><I>gen-file</I></TT> was specified, a second
|
||||
summary line displays the bit error rate from
|
||||
comparing the decoded message bits with the true message bits (zeros
|
||||
if no <TT><I>source file</I></TT> was given).
|
||||
|
||||
<P>If the <B>-t</B> option is given, block-by-block results are
|
||||
printed on standard output in two or three columns, giving the block
|
||||
number (from zero), the number of parity check errors for that block,
|
||||
and the number of errors in source bits. The last column is omitted
|
||||
if <TT><I>gen-file</I></TT> is not specified. The columns are
|
||||
preceded by a line of headers, so the file is suitable for reading
|
||||
into the S-Plus or R statistics packages, with a command such as
|
||||
<BLOCKQUOTE><PRE>
|
||||
data <- read.table(<I>file</I>,header=T)
|
||||
</PRE></BLOCKQUOTE>
|
||||
|
||||
<P>Warning messages are displayed on standard error if the number of
|
||||
bits in <TT><I>decoded-file</I></TT> is not a multiple of the block
|
||||
length, or if <TT><I>source-file</I></TT> is too short. Newlines
|
||||
in these files are ignored, even though they would normally occur
|
||||
at the ends of blocks.
|
||||
|
||||
<HR>
|
||||
|
||||
<A HREF="index.html">Back to index for LDPC software</A>
|
||||
|
||||
</BODY></HTML>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MessageAveraging</class>
|
||||
<widget class="QWidget" name="MessageAveraging">
|
||||
<property name="windowTitle">
|
||||
<string>Message Averaging</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="header_label">
|
||||
<property name="text">
|
||||
<string> UTC Sync DT Freq </string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QPlainTextEdit" name="msgAvgPlainTextEdit">
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,495 @@
|
||||
#include "MessageServer.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "NetworkMessage.hpp"
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageServer.cpp"
|
||||
|
||||
class MessageServer::impl
|
||||
: public QUdpSocket
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
impl (MessageServer * self, QString const& version, QString const& revision)
|
||||
: self_ {self}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, port_ {0u}
|
||||
, clock_ {new QTimer {this}}
|
||||
{
|
||||
// register the required types with Qt
|
||||
Radio::register_types ();
|
||||
|
||||
connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
|
||||
connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
|
||||
, [this] (SocketError /* e */)
|
||||
{
|
||||
Q_EMIT self_->error (errorString ());
|
||||
});
|
||||
connect (clock_, &QTimer::timeout, this, &impl::tick);
|
||||
clock_->start (NetworkMessage::pulse * 1000);
|
||||
}
|
||||
|
||||
enum StreamStatus {Fail, Short, OK};
|
||||
|
||||
void leave_multicast_group ();
|
||||
void join_multicast_group ();
|
||||
void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg);
|
||||
void tick ();
|
||||
void pending_datagrams ();
|
||||
StreamStatus check_status (QDataStream const&) const;
|
||||
void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
writeDatagram (message, address, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
MessageServer * self_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
port_type port_;
|
||||
QHostAddress multicast_group_address_;
|
||||
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
|
||||
struct Client
|
||||
{
|
||||
Client () = default;
|
||||
Client (QHostAddress const& sender_address, port_type const& sender_port)
|
||||
: sender_address_ {sender_address}
|
||||
, sender_port_ {sender_port}
|
||||
, negotiated_schema_number_ {2} // not 1 because it's broken
|
||||
, last_activity_ {QDateTime::currentDateTime ()}
|
||||
{
|
||||
}
|
||||
Client (Client const&) = default;
|
||||
Client& operator= (Client const&) = default;
|
||||
|
||||
QHostAddress sender_address_;
|
||||
port_type sender_port_;
|
||||
quint32 negotiated_schema_number_;
|
||||
QDateTime last_activity_;
|
||||
};
|
||||
QHash<QString, Client> clients_; // maps id to Client
|
||||
QTimer * clock_;
|
||||
};
|
||||
|
||||
MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
|
||||
|
||||
#include "MessageServer.moc"
|
||||
|
||||
void MessageServer::impl::leave_multicast_group ()
|
||||
{
|
||||
if (!multicast_group_address_.isNull () && BoundState == state ())
|
||||
{
|
||||
leaveMulticastGroup (multicast_group_address_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::join_multicast_group ()
|
||||
{
|
||||
if (BoundState == state ()
|
||||
&& !multicast_group_address_.isNull ())
|
||||
{
|
||||
if (IPv4Protocol == multicast_group_address_.protocol ()
|
||||
&& IPv4Protocol != localAddress ().protocol ())
|
||||
{
|
||||
close ();
|
||||
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
|
||||
}
|
||||
if (!joinMulticastGroup (multicast_group_address_))
|
||||
{
|
||||
multicast_group_address_.clear ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::pending_datagrams ()
|
||||
{
|
||||
while (hasPendingDatagrams ())
|
||||
{
|
||||
QByteArray datagram;
|
||||
datagram.resize (pendingDatagramSize ());
|
||||
QHostAddress sender_address;
|
||||
port_type sender_port;
|
||||
if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
|
||||
{
|
||||
parse_message (sender_address, sender_port, datagram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
NetworkMessage::Reader in {msg};
|
||||
|
||||
auto id = in.id ();
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
if (!clients_.contains (id))
|
||||
{
|
||||
auto& client = (clients_[id] = {sender, sender_port});
|
||||
QByteArray client_version;
|
||||
QByteArray client_revision;
|
||||
|
||||
if (NetworkMessage::Heartbeat == in.type ())
|
||||
{
|
||||
// negotiate a working schema number
|
||||
in >> client.negotiated_schema_number_;
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
auto sn = NetworkMessage::Builder::schema_number;
|
||||
client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_);
|
||||
|
||||
// reply to the new client informing it of the
|
||||
// negotiated schema number
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_};
|
||||
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (impl::OK == check_status (hb))
|
||||
{
|
||||
writeDatagram (message, client.sender_address_, client.sender_port_);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
// we don't care if this fails to read
|
||||
in >> client_version >> client_revision;
|
||||
}
|
||||
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version),
|
||||
QString::fromUtf8 (client_revision));
|
||||
}
|
||||
clients_[id].last_activity_ = QDateTime::currentDateTime ();
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
switch (in.type ())
|
||||
{
|
||||
case NetworkMessage::Heartbeat:
|
||||
//nothing to do here as time out handling deals with lifetime
|
||||
break;
|
||||
|
||||
case NetworkMessage::Clear:
|
||||
Q_EMIT self_->clear_decodes (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::Status:
|
||||
{
|
||||
// unpack message
|
||||
Frequency f;
|
||||
QByteArray mode;
|
||||
QByteArray dx_call;
|
||||
QByteArray report;
|
||||
QByteArray tx_mode;
|
||||
bool tx_enabled {false};
|
||||
bool transmitting {false};
|
||||
bool decoding {false};
|
||||
qint32 rx_df {-1};
|
||||
qint32 tx_df {-1};
|
||||
QByteArray de_call;
|
||||
QByteArray de_grid;
|
||||
QByteArray dx_grid;
|
||||
bool watchdog_timeout {false};
|
||||
QByteArray sub_mode;
|
||||
bool fast_mode {false};
|
||||
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
|
||||
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
|
||||
>> fast_mode;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
|
||||
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
|
||||
, tx_enabled, transmitting, decoding, rx_df, tx_df
|
||||
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
|
||||
, QString::fromUtf8 (dx_grid), watchdog_timeout
|
||||
, QString::fromUtf8 (sub_mode), fast_mode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Decode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
quint32 delta_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray message;
|
||||
bool low_confidence {false};
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode
|
||||
>> message >> low_confidence >> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency
|
||||
, QString::fromUtf8 (mode), QString::fromUtf8 (message)
|
||||
, low_confidence, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::WSPRDecode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
Frequency frequency;
|
||||
qint32 drift;
|
||||
QByteArray callsign;
|
||||
QByteArray grid;
|
||||
qint32 power;
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power
|
||||
>> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift
|
||||
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
|
||||
, power, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::QSOLogged:
|
||||
{
|
||||
QDateTime time_off;
|
||||
QByteArray dx_call;
|
||||
QByteArray dx_grid;
|
||||
Frequency dial_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray report_sent;
|
||||
QByteArray report_received;
|
||||
QByteArray tx_power;
|
||||
QByteArray comments;
|
||||
QByteArray name;
|
||||
QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window
|
||||
QByteArray operator_call;
|
||||
QByteArray my_call;
|
||||
QByteArray my_grid;
|
||||
in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received
|
||||
>> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
|
||||
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
|
||||
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
|
||||
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
|
||||
, QString::fromUtf8 (my_grid));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
Q_EMIT self_->client_closed (id);
|
||||
clients_.remove (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::LoggedADIF:
|
||||
{
|
||||
QByteArray ADIF;
|
||||
in >> ADIF;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->logged_ADIF (id, ADIF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("MessageServer warning: invalid UDP message received");
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Q_EMIT self_->error ("Unexpected exception in MessageServer");
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::tick ()
|
||||
{
|
||||
auto now = QDateTime::currentDateTime ();
|
||||
auto iter = std::begin (clients_);
|
||||
while (iter != std::end (clients_))
|
||||
{
|
||||
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
|
||||
{
|
||||
Q_EMIT self_->clear_decodes (iter.key ());
|
||||
Q_EMIT self_->client_closed (iter.key ());
|
||||
iter = clients_.erase (iter); // safe while iterating as doesn't rehash
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto MessageServer::impl::check_status (QDataStream const& stream) const -> StreamStatus
|
||||
{
|
||||
auto stat = stream.status ();
|
||||
StreamStatus result {Fail};
|
||||
switch (stat)
|
||||
{
|
||||
case QDataStream::ReadPastEnd:
|
||||
result = Short;
|
||||
break;
|
||||
|
||||
case QDataStream::ReadCorruptData:
|
||||
Q_EMIT self_->error ("Message serialization error: read corrupt data");
|
||||
break;
|
||||
|
||||
case QDataStream::WriteFailed:
|
||||
Q_EMIT self_->error ("Message serialization error: write error");
|
||||
break;
|
||||
|
||||
default:
|
||||
result = OK;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision)
|
||||
: QObject {parent}
|
||||
, m_ {this, version, revision}
|
||||
{
|
||||
}
|
||||
|
||||
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
|
||||
{
|
||||
if (port != m_->port_
|
||||
|| multicast_group_address != m_->multicast_group_address_)
|
||||
{
|
||||
m_->leave_multicast_group ();
|
||||
if (impl::BoundState == m_->state ())
|
||||
{
|
||||
m_->close ();
|
||||
}
|
||||
m_->multicast_group_address_ = multicast_group_address;
|
||||
auto address = m_->multicast_group_address_.isNull ()
|
||||
|| impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4;
|
||||
if (port && m_->bind (address, port, m_->bind_mode_))
|
||||
{
|
||||
m_->port_ = port;
|
||||
m_->join_multicast_group ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_->port_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message_text, bool low_confidence, quint8 modifiers)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_};
|
||||
out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
|
||||
<< message_text.toUtf8 () << low_confidence << modifiers;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::replay (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::halt_tx (QString const& id, bool auto_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_};
|
||||
out << auto_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::free_text (QString const& id, QString const& text, bool send)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_};
|
||||
out << text.toUtf8 () << send;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::location (QString const& id, QString const& loc)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_};
|
||||
out << loc.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::highlight_callsign (QString const& id, QString const& callsign
|
||||
, QColor const& bg, QColor const& fg, bool last_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_};
|
||||
out << callsign.toUtf8 () << bg << fg << last_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
#include "displaytext.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QDateTime>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "moc_displaytext.cpp"
|
||||
|
||||
DisplayText::DisplayText(QWidget *parent) :
|
||||
QTextEdit(parent)
|
||||
{
|
||||
setReadOnly (true);
|
||||
viewport ()->setCursor (Qt::ArrowCursor);
|
||||
setWordWrapMode (QTextOption::NoWrap);
|
||||
document ()->setMaximumBlockCount (5000); // max lines to limit heap usage
|
||||
}
|
||||
|
||||
void DisplayText::setContentFont(QFont const& font)
|
||||
{
|
||||
char_font_ = font;
|
||||
selectAll ();
|
||||
auto cursor = textCursor ();
|
||||
cursor.beginEditBlock ();
|
||||
auto char_format = cursor.charFormat ();
|
||||
char_format.setFont (char_font_);
|
||||
cursor.mergeCharFormat (char_format);
|
||||
cursor.clearSelection ();
|
||||
cursor.movePosition (QTextCursor::End);
|
||||
|
||||
// position so viewport scrolled to left
|
||||
cursor.movePosition (QTextCursor::Up);
|
||||
cursor.movePosition (QTextCursor::StartOfLine);
|
||||
cursor.endEditBlock ();
|
||||
|
||||
setTextCursor (cursor);
|
||||
ensureCursorVisible ();
|
||||
}
|
||||
|
||||
void DisplayText::mouseDoubleClickEvent(QMouseEvent *e)
|
||||
{
|
||||
bool ctrl = (e->modifiers() & Qt::ControlModifier);
|
||||
bool alt = (e->modifiers() & Qt::AltModifier);
|
||||
emit(selectCallsign(alt,ctrl));
|
||||
QTextEdit::mouseDoubleClickEvent(e);
|
||||
}
|
||||
|
||||
void DisplayText::insertLineSpacer(QString const& line)
|
||||
{
|
||||
appendText (line, "#d3d3d3");
|
||||
}
|
||||
|
||||
void DisplayText::appendText(QString const& text, QColor bg)
|
||||
{
|
||||
auto cursor = textCursor ();
|
||||
cursor.movePosition (QTextCursor::End);
|
||||
auto block_format = cursor.blockFormat ();
|
||||
block_format.setBackground (bg);
|
||||
if (0 == cursor.position ())
|
||||
{
|
||||
cursor.setBlockFormat (block_format);
|
||||
auto char_format = cursor.charFormat ();
|
||||
char_format.setFont (char_font_);
|
||||
cursor.setCharFormat (char_format);
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor.insertBlock (block_format);
|
||||
}
|
||||
cursor.insertText (text);
|
||||
|
||||
// position so viewport scrolled to left
|
||||
cursor.movePosition (QTextCursor::StartOfLine);
|
||||
setTextCursor (cursor);
|
||||
ensureCursorVisible ();
|
||||
document ()->setMaximumBlockCount (document ()->maximumBlockCount ());
|
||||
}
|
||||
|
||||
|
||||
QString DisplayText::appendDXCCWorkedB4(QString message, QString const& callsign, QColor * bg,
|
||||
LogBook const& logBook, QColor color_CQ,
|
||||
QColor color_DXCC,
|
||||
QColor color_NewCall)
|
||||
{
|
||||
// allow for seconds
|
||||
unsigned padding {message.indexOf (" ") > 4 ? 2U : 0U};
|
||||
QString call = callsign;
|
||||
QString countryName;
|
||||
bool callWorkedBefore;
|
||||
bool countryWorkedBefore;
|
||||
|
||||
if(call.length()==2) {
|
||||
int i0=message.indexOf("CQ "+call);
|
||||
call=message.mid(i0+6,-1);
|
||||
i0=call.indexOf(" ");
|
||||
call=call.mid(0,i0);
|
||||
}
|
||||
if(call.length()<3) return message;
|
||||
if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return message;
|
||||
|
||||
logBook.match(/*in*/call,/*out*/countryName,callWorkedBefore,countryWorkedBefore);
|
||||
int charsAvail = 52 + padding;
|
||||
|
||||
// the decoder (seems) to always generate 41 chars. For a normal CQ
|
||||
// call, the last five are spaces
|
||||
//
|
||||
// A maximum length call is "QRZ VP2X/GM4WJS IO91" "CQ AA ..." or CQ
|
||||
// nnn ..." don't allow grid squares so are not longer. Here we align
|
||||
// the added info at least after the longest CQ/QRZ message plus one
|
||||
// space so that it can be stripped off algorithmically later.
|
||||
//
|
||||
int nmin = 46 + padding;
|
||||
int s3 = message.indexOf (" ", nmin);
|
||||
if (s3 < nmin) s3 = nmin; // always want at least the characters to position 45
|
||||
s3 += 1; // convert the index into a character count
|
||||
message = message.left(s3); // reduce trailing white space
|
||||
charsAvail -= s3;
|
||||
if (charsAvail > 4)
|
||||
{
|
||||
if (!countryWorkedBefore) // therefore not worked call either
|
||||
{
|
||||
message += "!";
|
||||
*bg = color_DXCC;
|
||||
}
|
||||
else
|
||||
if (!callWorkedBefore) // but have worked the country
|
||||
{
|
||||
message += "~";
|
||||
*bg = color_NewCall;
|
||||
}
|
||||
else
|
||||
{
|
||||
message += " "; // have worked this call before
|
||||
*bg = color_CQ;
|
||||
}
|
||||
charsAvail -= 1;
|
||||
|
||||
// do some obvious abbreviations
|
||||
countryName.replace ("Islands", "Is.");
|
||||
countryName.replace ("Island", "Is.");
|
||||
countryName.replace ("North ", "N. ");
|
||||
countryName.replace ("Northern ", "N. ");
|
||||
countryName.replace ("South ", "S. ");
|
||||
countryName.replace ("East ", "E. ");
|
||||
countryName.replace ("Eastern ", "E. ");
|
||||
countryName.replace ("West ", "W. ");
|
||||
countryName.replace ("Western ", "W. ");
|
||||
countryName.replace ("Central ", "C. ");
|
||||
countryName.replace (" and ", " & ");
|
||||
countryName.replace ("Republic", "Rep.");
|
||||
countryName.replace ("United States", "U.S.A.");
|
||||
countryName.replace ("Fed. Rep. of ", "");
|
||||
countryName.replace ("French ", "Fr.");
|
||||
countryName.replace ("Asiatic", "AS");
|
||||
countryName.replace ("European", "EU");
|
||||
countryName.replace ("African", "AF");
|
||||
|
||||
message += countryName;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
void DisplayText::displayDecodedText(DecodedText const& decodedText, QString const& myCall,
|
||||
bool displayDXCCEntity, LogBook const& logBook,
|
||||
QColor color_CQ, QColor color_MyCall,
|
||||
QColor color_DXCC, QColor color_NewCall)
|
||||
{
|
||||
QColor bg {Qt::white};
|
||||
bool CQcall = false;
|
||||
if (decodedText.string ().contains (" CQ ")
|
||||
|| decodedText.string ().contains (" CQDX ")
|
||||
|| decodedText.string ().contains (" QRZ "))
|
||||
{
|
||||
CQcall = true;
|
||||
bg = color_CQ;
|
||||
}
|
||||
if (myCall != "" and (
|
||||
decodedText.indexOf (" " + myCall + " ") >= 0
|
||||
or decodedText.indexOf (" " + myCall + "/") >= 0
|
||||
or decodedText.indexOf ("/" + myCall + " ") >= 0
|
||||
or decodedText.indexOf ("<" + myCall + " ") >= 0
|
||||
or decodedText.indexOf (" " + myCall + ">") >= 0)) {
|
||||
bg = color_MyCall;
|
||||
}
|
||||
// if enabled add the DXCC entity and B4 status to the end of the
|
||||
// preformated text line t1
|
||||
auto message = decodedText.string ();
|
||||
if (displayDXCCEntity && CQcall)
|
||||
message = appendDXCCWorkedB4 (message, decodedText.CQersCall (), &bg, logBook, color_CQ,
|
||||
color_DXCC, color_NewCall);
|
||||
appendText (message, bg);
|
||||
}
|
||||
|
||||
|
||||
void DisplayText::displayTransmittedText(QString text, QString modeTx, qint32 txFreq,
|
||||
QColor color_TxMsg, bool bFastMode)
|
||||
{
|
||||
QString t1=" @ ";
|
||||
if(modeTx=="FT8") t1=" ~ ";
|
||||
if(modeTx=="JT4") t1=" $ ";
|
||||
if(modeTx=="JT65") t1=" # ";
|
||||
if(modeTx=="MSK144") t1=" & ";
|
||||
QString t2;
|
||||
t2.sprintf("%4d",txFreq);
|
||||
QString t;
|
||||
if(bFastMode or modeTx=="FT8") {
|
||||
t = QDateTime::currentDateTimeUtc().toString("hhmmss") + \
|
||||
" Tx " + t2 + t1 + text;
|
||||
} else {
|
||||
t = QDateTime::currentDateTimeUtc().toString("hhmm") + \
|
||||
" Tx " + t2 + t1 + text;
|
||||
}
|
||||
appendText (t, color_TxMsg);
|
||||
}
|
||||
|
||||
void DisplayText::displayQSY(QString text)
|
||||
{
|
||||
QString t = QDateTime::currentDateTimeUtc().toString("hhmmss") + " " + text;
|
||||
appendText (t, "hotpink");
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
subroutine extract(s3,nadd,mode65,ntrials,naggressive,ndepth,nflip, &
|
||||
mycall_12,hiscall_12,hisgrid,nexp_decode,ncount,nhist,decoded, &
|
||||
ltext,nft,qual)
|
||||
|
||||
! Input:
|
||||
! s3 64-point spectra for each of 63 data symbols
|
||||
! nadd number of spectra summed into s3
|
||||
! nqd 0/1 to indicate decode attempt at QSO frequency
|
||||
|
||||
! Output:
|
||||
! ncount number of symbols requiring correction (-1 for no KV decode)
|
||||
! nhist maximum number of identical symbol values
|
||||
! decoded decoded message (if ncount >=0)
|
||||
! ltext true if decoded message is free text
|
||||
! nft 0=no decode; 1=FT decode; 2=hinted decode
|
||||
|
||||
use prog_args !shm_key, exe_dir, data_dir
|
||||
use packjt
|
||||
use jt65_mod
|
||||
use timer_module, only: timer
|
||||
|
||||
real s3(64,63)
|
||||
character decoded*22
|
||||
character*12 mycall_12,hiscall_12
|
||||
character*6 mycall,hiscall,hisgrid
|
||||
integer dat4(12)
|
||||
integer mrsym(63),mr2sym(63),mrprob(63),mr2prob(63)
|
||||
integer correct(63),tmp(63)
|
||||
logical ltext
|
||||
common/chansyms65/correct
|
||||
save
|
||||
|
||||
if(mode65.eq.-99) stop !Silence compiler warning
|
||||
mycall=mycall_12(1:6)
|
||||
hiscall=hiscall_12(1:6)
|
||||
qual=0.
|
||||
nbirdie=20
|
||||
npct=50
|
||||
afac1=1.1
|
||||
nft=0
|
||||
nfail=0
|
||||
decoded=' '
|
||||
call pctile(s3,4032,npct,base)
|
||||
s3=s3/base
|
||||
s3a=s3 !###
|
||||
|
||||
! Get most reliable and second-most-reliable symbol values, and their
|
||||
! probabilities
|
||||
1 call demod64a(s3,nadd,afac1,mrsym,mrprob,mr2sym,mr2prob,ntest,nlow)
|
||||
|
||||
call chkhist(mrsym,nhist,ipk) !Test for birdies and QRM
|
||||
if(nhist.ge.nbirdie) then
|
||||
nfail=nfail+1
|
||||
call pctile(s3,4032,npct,base)
|
||||
s3(ipk,1:63)=base
|
||||
if(nfail.gt.30) then
|
||||
decoded=' '
|
||||
ncount=-1
|
||||
go to 900
|
||||
endif
|
||||
go to 1
|
||||
endif
|
||||
|
||||
mrs=mrsym
|
||||
mrs2=mr2sym
|
||||
|
||||
call graycode65(mrsym,63,-1) !Remove gray code
|
||||
call interleave63(mrsym,-1) !Remove interleaving
|
||||
call interleave63(mrprob,-1)
|
||||
|
||||
call graycode65(mr2sym,63,-1) !Remove gray code and interleaving
|
||||
call interleave63(mr2sym,-1) !from second-most-reliable symbols
|
||||
call interleave63(mr2prob,-1)
|
||||
ntry=0
|
||||
|
||||
call timer('ftrsd ',0)
|
||||
param=0
|
||||
call ftrsd2(mrsym,mrprob,mr2sym,mr2prob,ntrials,correct,param,ntry)
|
||||
call timer('ftrsd ',1)
|
||||
ncandidates=param(0)
|
||||
nhard=param(1)
|
||||
nsoft=param(2)
|
||||
nerased=param(3)
|
||||
rtt=0.001*param(4)
|
||||
ntotal=param(5)
|
||||
qual=0.001*param(7)
|
||||
nd0=81
|
||||
r0=0.87
|
||||
if(naggressive.eq.10) then
|
||||
nd0=83
|
||||
r0=0.90
|
||||
endif
|
||||
if(ntotal.le.nd0 .and. rtt.le.r0) nft=1
|
||||
|
||||
if(nft.eq.0 .and. iand(ndepth,32).eq.32) then
|
||||
qmin=2.0 - 0.1*naggressive
|
||||
call timer('hint65 ',0)
|
||||
call hint65(s3,mrs,mrs2,nadd,nflip,mycall,hiscall,hisgrid,qual,decoded)
|
||||
if(qual.ge.qmin) then
|
||||
nft=2
|
||||
ncount=0
|
||||
else
|
||||
decoded=' '
|
||||
ntry=0
|
||||
endif
|
||||
call timer('hint65 ',1)
|
||||
go to 900
|
||||
endif
|
||||
|
||||
ncount=-1
|
||||
decoded=' '
|
||||
ltext=.false.
|
||||
if(nft.gt.0) then
|
||||
! Turn the corrected symbol array into channel symbols for subtraction;
|
||||
! pass it back to jt65a via common block "chansyms65".
|
||||
do i=1,12
|
||||
dat4(i)=correct(13-i)
|
||||
enddo
|
||||
do i=1,63
|
||||
tmp(i)=correct(64-i)
|
||||
enddo
|
||||
correct(1:63)=tmp(1:63)
|
||||
call interleave63(correct,63,1)
|
||||
call graycode65(correct,63,1)
|
||||
call unpackmsg(dat4,decoded,.false.,' ') !Unpack the user message
|
||||
ncount=0
|
||||
if(iand(dat4(10),8).ne.0) ltext=.true.
|
||||
endif
|
||||
900 continue
|
||||
if(nft.eq.1 .and. nhard.lt.0) decoded=' '
|
||||
|
||||
return
|
||||
end subroutine extract
|
||||
|
||||
subroutine getpp(workdat,p)
|
||||
|
||||
use jt65_mod
|
||||
integer workdat(63)
|
||||
integer a(63)
|
||||
|
||||
a(1:63)=workdat(63:1:-1)
|
||||
call interleave63(a,1)
|
||||
call graycode(a,63,1,a)
|
||||
|
||||
psum=0.
|
||||
do j=1,63
|
||||
i=a(j)+1
|
||||
x=s3a(i,j)
|
||||
s3a(i,j)=0.
|
||||
psum=psum + x
|
||||
s3a(i,j)=x
|
||||
enddo
|
||||
p=psum/63.0
|
||||
|
||||
return
|
||||
end subroutine getpp
|
||||
@@ -0,0 +1,154 @@
|
||||
integer, parameter:: N=204, K=68, M=N-K
|
||||
character*17 g(136)
|
||||
integer colorder(N)
|
||||
data g/ & !parity generator matrix for (204,68) code
|
||||
"2de7435fd27c0031d", &
|
||||
"f331b40671e20ea80", &
|
||||
"48bd3f8cb9a24392f", &
|
||||
"d4ed71c935162aa2a", &
|
||||
"c437a3284ec58bce7", &
|
||||
"35a806dd5be35627c", &
|
||||
"396e797c33a4739a6", &
|
||||
"768f331a59c15487b", &
|
||||
"c214eac24ae5e1732", &
|
||||
"0b5c53ff3a6da1192", &
|
||||
"99624981d2703fb97", &
|
||||
"e9f5447ef7f1ff6af", &
|
||||
"bd8c730f0cfdf0727", &
|
||||
"26f61e63e1e098f7f", &
|
||||
"ef826566137b6526f", &
|
||||
"af0e4fa251e9b4926", &
|
||||
"75974a8b2a24292c5", &
|
||||
"71caf0f2cd10f6d4f", &
|
||||
"b1103f1f26e6898b7", &
|
||||
"67ceb7d6f490da64f", &
|
||||
"ee0e8fbefec23008a", &
|
||||
"11cc2227e8bd676ca", &
|
||||
"6e71626ba1e278046", &
|
||||
"005d28da267e50e13", &
|
||||
"a9ae4a130aaba8219", &
|
||||
"d8ab72e0158d0da70", &
|
||||
"56009d42b37bd66ff", &
|
||||
"c39a75eca99b0e996", &
|
||||
"6886de0bf7c0bf4bb", &
|
||||
"1046cd8f64162f7b5", &
|
||||
"da0f15843ac21e3a5", &
|
||||
"e9bf9cd19f3db3913", &
|
||||
"2fb9cb42d650f47a7", &
|
||||
"a2b6c5a378fa75a65", &
|
||||
"41a88f3cd60b79d6c", &
|
||||
"fcf175794cc3ac96a", &
|
||||
"8677a3447d40a9f71", &
|
||||
"97a1f08c250b4bf12", &
|
||||
"0168f090a1df6e8ea", &
|
||||
"418a06bf372cc67d9", &
|
||||
"0f17b880c1ff51239", &
|
||||
"b2afd6d585deb961b", &
|
||||
"60298ac5b58dbeee0", &
|
||||
"8350c03c40119feff", &
|
||||
"b29c964a8accf6af4", &
|
||||
"9b46f036a5c178b5d", &
|
||||
"917398bff051c300a", &
|
||||
"5e52c03b2f8c5128c", &
|
||||
"beae6c33c87ba38ab", &
|
||||
"20843f7b056a02ebf", &
|
||||
"66690d65acd9de598", &
|
||||
"8f025841af5b54331", &
|
||||
"b43cd869d3be2c3db", &
|
||||
"c9c342fe63c18df50", &
|
||||
"d331b40671e28ea80", &
|
||||
"62406a0f4947e6ce9", &
|
||||
"d67b1495883b22e1b", &
|
||||
"734534c372408895b", &
|
||||
"d88750e33d9677dcd", &
|
||||
"6f96964da55138687", &
|
||||
"80bee98bb75d50ef2", &
|
||||
"c428ef3e3f06f4c56", &
|
||||
"b1a1499b125883a35", &
|
||||
"ac892d4b37fa9e395", &
|
||||
"458dbda0f95ab11a5", &
|
||||
"6f93c9e95b1094eed", &
|
||||
"2e370d713914f848e", &
|
||||
"758806dd5be35627c", &
|
||||
"8c52e01caec798b49", &
|
||||
"c286cc25bae3669cf", &
|
||||
"87c56fb895c100884", &
|
||||
"e89cb1376a18fd911", &
|
||||
"156ffe5f30dc354e0", &
|
||||
"f20d0b121d6a6b3ee", &
|
||||
"7db08891b491a95d2", &
|
||||
"191fac548d5077bdf", &
|
||||
"023a37d7ea5660bbc", &
|
||||
"6781668b363fee682", &
|
||||
"bbfaf262cab7370da", &
|
||||
"feea557965b7e474f", &
|
||||
"c094eb223e1d305b8", &
|
||||
"2be051abdd5beea35", &
|
||||
"0790449880fda9d00", &
|
||||
"f9029a39ec869e7b4", &
|
||||
"5a29f48926ec9a552", &
|
||||
"e0463306dc1470f87", &
|
||||
"9251058334d790f86", &
|
||||
"3019e1d4578e8a4dc", &
|
||||
"887e46631502fa111", &
|
||||
"c25fcd7a42465d326", &
|
||||
"cf64bcc1056b555c4", &
|
||||
"3e71c0fe5f0ad733b", &
|
||||
"11055ec43b076e5b2", &
|
||||
"3440f64dfa3c30a96", &
|
||||
"2b73885b4d3299f60", &
|
||||
"2e71627ba1e268046", &
|
||||
"ad23743d5e6e5b80c", &
|
||||
"c9757b05f29bfdc10", &
|
||||
"f7112bea739247b51", &
|
||||
"3664062387998b2b1", &
|
||||
"90897a3b8785aefba", &
|
||||
"29e126e3201fc1d46", &
|
||||
"96c9001c84d5257fc", &
|
||||
"067723447d40a9f71", &
|
||||
"1a019cc68f7511402", &
|
||||
"4bd48eb2330032763", &
|
||||
"d139a5da936b37647", &
|
||||
"765ab46a4dec5f04f", &
|
||||
"706f475ad19b91955", &
|
||||
"1755c988fa8a55e5c", &
|
||||
"2fd9ed5777eb01d6a", &
|
||||
"bec27d85b954d3fe8", &
|
||||
"7135a3b92c45b3f8d", &
|
||||
"353237872f002163a", &
|
||||
"e31e4a97aef10c729", &
|
||||
"da527d5e1cbc4edb6", &
|
||||
"6e33cdede17c3207e", &
|
||||
"ef2d2062e84dc401f", &
|
||||
"8217c84c50c1bf833", &
|
||||
"12ffbac7b2219c9e0", &
|
||||
"3729178706f66881f", &
|
||||
"2fdd748c382a608a1", &
|
||||
"dd0a00076f9dcec73", &
|
||||
"46b1d37bced447035", &
|
||||
"7316f33a9c05ef178", &
|
||||
"152c39a6de8954cc3", &
|
||||
"16efffb7b62e12ba3", &
|
||||
"9d9ec2bb467affd83", &
|
||||
"467723445d40a9f61", &
|
||||
"87994762b3bf50697", &
|
||||
"b1bfa5b51526dde9b", &
|
||||
"b0a6a19d709a96148", &
|
||||
"990d567c0aba31a14", &
|
||||
"171f190792461b1e0", &
|
||||
"166011c27d2b6b8a4", &
|
||||
"170c15831244ae73e"/
|
||||
|
||||
data colorder/ &
|
||||
0, 1, 2, 3, 4, 5, 47, 6, 7, 8, 9, 10, 11, 12, 58, 55, 13, &
|
||||
14, 15, 46, 17, 18, 60, 19, 20, 21, 22, 23, 24, 25, 57, 26, 27, 49, &
|
||||
28, 52, 65, 16, 50, 73, 59, 68, 63, 29, 30, 31, 32, 51, 62, 56, 66, &
|
||||
45, 33, 34, 53, 67, 35, 36, 37, 61, 69, 54, 38, 71, 82, 39, 77, 80, &
|
||||
83, 78, 84, 48, 41, 85, 40, 64, 75, 96, 74, 72, 76, 86, 87, 89, 90, &
|
||||
79, 70, 92, 99, 93,101, 95,100, 97, 94, 42, 98,103,105,102, 43,104, &
|
||||
88, 44,106, 81,107,110,108,111,112,109,113,114,117,118,116,121,115, &
|
||||
119,122,120,125,129,124,127,126,128, 91,123,133,131,130,134,135,137, &
|
||||
136,132,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152, &
|
||||
153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169, &
|
||||
170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186, &
|
||||
187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203/
|
||||
Reference in New Issue
Block a user