Implemented trivial APRS decoder.

master
Hannes Matuschek 11 years ago
parent acee3dc318
commit 36ac8d3cc1

@ -19,6 +19,7 @@ find_package(RTLSDR)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/src)
INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${GETOPT_INCLUDE_DIRS})
# Set some variables for the configuration file
IF(FFTW_FOUND)
@ -41,7 +42,8 @@ ELSE(TRLSDR_FOUND)
ENDIF(RTLSDR_FOUND)
set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES} "pthread")
set(LIBS ${FFTW_LIBRARIES} ${FFTWSingle_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${RTLSDR_LIBRARIES}
"pthread")
# Set compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC")

@ -8,15 +8,15 @@ IF(SDR_WITH_PORTAUDIO)
add_executable(sdr_rec sdr_rec.cc)
target_link_libraries(sdr_rec ${LIBS} libsdr)
add_executable(sdr_afsk1200 sdr_afsk1200.cc)
target_link_libraries(sdr_afsk1200 ${LIBS} libsdr)
add_executable(sdr_rtty sdr_rtty.cc)
target_link_libraries(sdr_rtty ${LIBS} libsdr)
add_executable(sdr_pocsag sdr_pocsag.cc)
target_link_libraries(sdr_pocsag ${LIBS} libsdr)
add_executable(sdr_ax25 sdr_ax25.cc)
target_link_libraries(sdr_ax25 ${LIBS} libsdr)
ENDIF(SDR_WITH_PORTAUDIO)

@ -1,41 +0,0 @@
#include "wavfile.hh"
#include "autocast.hh"
#include "interpolate.hh"
#include "fsk.hh"
#include "ax25.hh"
#include "utils.hh"
using namespace sdr;
int main(int argc, char *argv[]) {
if (2 > argc) { std::cout << "USAGE: sdr_afsk1200 FILENAME" << std::endl; return -1; }
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
Queue &queue = Queue::get();
WavSource src(argv[1], 1024);
if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; }
AutoCast< int16_t > cast;
FSKDetector demod(1200, 1200, 2200);
BitStream bits(1200, BitStream::TRANSITION);
AX25 decode;
src.connect(&cast);
cast.connect(&demod);
demod.connect(&bits);
bits.connect(&decode);
Queue::get().addIdle(&src, &WavSource::next);
src.addEOS(&queue, &Queue::stop);
Queue::get().start();
Queue::get().wait();
return 0;
}

@ -1,9 +1,33 @@
/*
* sdr_pocsag -- A POCSAG receiver using libsdr.
*
* (c) 2015 Hannes Matuschek <hmatuschek at gmail dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "autocast.hh"
#include "portaudio.hh"
#include "wavfile.hh"
#include "fsk.hh"
#include "utils.hh"
#include "pocsag.hh"
#include "options.hh"
#include "rtlsource.hh"
#include "demod.hh"
#include "baseband.hh"
#include <iostream>
#include <cmath>
@ -11,54 +35,147 @@
using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly
static void __sigint_handler(int signo) {
Queue::get().stop();
}
int main(int argc, char *argv[])
{
if (2 != argc) {
std::cerr << "Usage: sdr_posag FILENAME" << std::endl;
return -1;
}
// Install log handler
sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register handler:
// Register signal handler:
signal(SIGINT, __sigint_handler);
// Command line options
Options::Definition options[] = {
{"frequency", 'F', Options::FLOAT,
"Selects a RTL2832 as the source and specifies the frequency in Hz."},
{"audio", 'a', Options::FLAG, "Selects the system audio as the source."},
{"file", 'f', Options::ANY, "Selects a WAV file as the source."},
{"monitor", 'M', Options::FLAG, "Enable sound monitor."},
{"invert", 0, Options::FLAG, "Inverts mark/space logic."},
{"help", 0, Options::FLAG, "Prints this help message."},
{0,0,Options::FLAG,0}
};
// Parse command line options.
Options opts;
if (! Options::parse(options, argc, argv, opts)) {
Options::print_help(std::cerr, options);
return -1;
}
if (opts.has("help")) {
Options::print_help(std::cout, options);
return 0;
}
// If no source has been selected
if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) {
Options::print_help(std::cerr, options);
return -1;
}
// Init audio system
PortAudio::init();
// Get the global queue
Queue &queue = Queue::get();
WavSource src(argv[1]);
PortSink sink;
AutoCast<int16_t> cast;
ASKDetector<int16_t> detector(false);
BitStream bits(1200, BitStream::NORMAL);
POCSAGDump pocsag(std::cout);
// pointer to the selected source
Source *src = 0;
// Cast to int16
src.connect(&cast);
// ASK detector
cast.connect(&detector);
// bit decoder
// Nodes for WAV file input
WavSource *wav_src=0;
AutoCast<int16_t> *wav_cast=0;
// Nodes for PortAudio input
PortSource<int16_t> *audio_src=0;
// Nodes for RTL2832 input
RTLSource *rtl_source=0;
AutoCast< std::complex<int16_t> > *rtl_cast=0;
IQBaseBand<int16_t> *rtl_baseband=0;
FMDemod<int16_t> *rtl_demod=0;
FMDeemph<int16_t> *rtl_deemph=0;
if (opts.has("frequency")) {
// Assemble processing chain for the RTL2832 intput
rtl_source = new RTLSource(opts.get("frequency").toFloat());
rtl_cast = new AutoCast< std::complex<int16_t> >();
rtl_baseband = new IQBaseBand<int16_t>(0, 12.5e3, 21, 0, 22050.0);
rtl_demod = new FMDemod<int16_t>();
rtl_deemph = new FMDeemph<int16_t>();
rtl_source->connect(rtl_cast);
rtl_cast->connect(rtl_baseband, true);
rtl_baseband->connect(rtl_demod);
rtl_demod->connect(rtl_deemph);
// FM deemph. is source for decoder
src = rtl_deemph;
// On queue start, start RTL source
Queue::get().addStart(rtl_source, &RTLSource::start);
// On queue stop, stop RTL source
Queue::get().addStop(rtl_source, &RTLSource::stop);
} else if (opts.has("audio")) {
// Configure audio source
audio_src = new PortSource<int16_t>(22010., 1024);
src = audio_src;
// On queue idle, read next chunk from audio source
Queue::get().addIdle(audio_src, &PortSource<int16_t>::next);
} else if (opts.has("file")) {
// Assemble processing chain for WAV file input
wav_src = new WavSource(opts.get("file").toString());
wav_cast = new AutoCast<int16_t>();
wav_src->connect(wav_cast);
src = wav_cast;
// On queue idle, read next chunk from file
Queue::get().addIdle(wav_src, &WavSource::next);
// On end of file, stop queue
wav_src->addEOS(&(Queue::get()), &Queue::stop);
}
/* Common demodulation nodes. */
// amplitude detector
ASKDetector<int16_t> detector(opts.has("invert"));
// Bit decoder
BitStream bits(1200, BitStream::NORMAL);
// POCSAG decoder
POCSAGDump pocsag(std::cout);
// Audio sink for monitor
PortSink sink;
// connect source ASK detector
src->connect(&detector);
// detector to bit decoder
detector.connect(&bits);
// POCSAG decoder and print
// and bit decoder to POCSAG decoder and print
bits.connect(&pocsag);
// on idle -> read next buffer from input file
queue.addIdle(&src, &WavSource::next);
// on end-of-file -> stop queue
src.addEOS(&queue, &Queue::stop);
// If monitor is enabled -> connect to sink
if (opts.has("monitor")) {
src->connect(&sink);
}
// Start queue
queue.start();
// wait for queue to exit
queue.wait();
// Free allocated nodes
if (rtl_source) { delete rtl_source; }
if (rtl_cast) { delete rtl_cast; }
if (rtl_baseband) { delete rtl_baseband; }
if (rtl_demod) { delete rtl_demod; }
if (rtl_deemph) { delete rtl_deemph; }
if (audio_src) { delete audio_src; }
if (wav_src) { delete wav_src; }
if (wav_cast) { delete wav_cast; }
// terminate port audio system properly
PortAudio::terminate();

@ -1,14 +1,13 @@
# Sources of libsdr
set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc
portaudio.cc utils.cc wavfile.cc
exception.cc logger.cc psk31.cc options.cc fsk.cc ax25.cc baudot.cc pocsag.cc bch31_21.cc)
buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc exception.cc logger.cc
psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc)
set(LIBSDR_HEADERS sdr.hh math.hh
buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.hh
siggen.hh portaudio.hh utils.hh wavfile.hh demod.hh firfilter.hh
fftplan.hh fftplan_native.hh exception.hh baseband.hh freqshift.hh subsample.hh
combine.hh logger.hh psk31.hh interpolate.hh operators.hh options.hh fsk.hh ax25.hh
baudot.hh pocsag.hh bch31_21.hh)
aprs.hh baudot.hh pocsag.hh bch31_21.hh)
if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc)

@ -0,0 +1,339 @@
#include "aprs.hh"
#include "logger.hh"
using namespace sdr;
/* ******************************************************************************************** *
* Implementation of APRS
* ******************************************************************************************** */
APRS::APRS()
: AX25()
{
// pass...
}
void
APRS::handleAX25Message(const AX25::Message &message) {
// Skip non-UI frames
if (0x03 != uint8_t(message.payload()[0])) {
LogMessage msg(LOG_DEBUG);
msg << "APRS: Skip non-UI frame (type="
<< std::hex << int(uint8_t(message.payload()[0]))
<< std::dec << "): " << message;
Logger::get().log(msg);
return;
}
// Skip frames woth level-3 protocol
if (0xf0 != uint8_t(message.payload()[1])) {
LogMessage msg(LOG_DEBUG);
msg << "APRS: Skip invalid UI (pid="
<< std::hex << int(uint8_t(message.payload()[1]))
<< std::dec << "): " << message;
Logger::get().log(msg);
return;
}
// Construct APRS message
Message msg(message);
// Handle message
this->handleAPRSMessage(msg);
}
void
APRS::handleAPRSMessage(const Message &message) {
std::cerr << message << std::endl;
}
/* ******************************************************************************************** *
* Implementation of APRS::Message
* ******************************************************************************************** */
bool __is_number(char c) {
return (('0'<=c) && ('9'>=c));
}
APRS::Message::Symbol
__toSymbol(char table, char sym) {
if ('/' == table) {
switch (sym) {
case 'P':
case '!': return APRS::Message::POLICE;
case '%':
case '&':
case '(':
case 'B':
case 'n':
case '#': return APRS::Message::DIGI;
case '[':
case 'e':
case '$': return APRS::Message::JOGGER;
case 'X':
case '^':
case 'g':
case '\'': return APRS::Message::AIRCRAFT;
case '-': return APRS::Message::HOUSE;
case 'b':
case '<': return APRS::Message::MOTORCYCLE;
case '=':
case '*':
case 'U':
case 'j':
case 'k':
case 'u':
case 'v':
case '>': return APRS::Message::CAR;
case 'Y':
case 's':
case 'C': return APRS::Message::BOAT;
case 'O': return APRS::Message::BALLOON;
case '_': return APRS::Message::WX;
default: break;
}
} else if ('\\' == table) {
switch (sym) {
default: break;
}
}
return APRS::Message::NONE;
}
APRS::Message::Message()
: AX25::Message(), _hasLocation(false), _latitude(0), _longitude(0), _symbol(NONE),
_hasTime(false), _time(::time(0))
{
// pass...
}
APRS::Message::Message(const AX25::Message &msg)
: AX25::Message(msg),
_hasLocation(false), _latitude(0), _longitude(0), _symbol(NONE),
_hasTime(false), _time(::time(0))
{
size_t offset = 2;
// Dispatch by message type
switch (_payload[offset]) {
case '=':
case '!':
_hasLocation = true;
offset++;
break;
case '/':
case '@':
_hasTime = true;
_hasLocation = true;
offset++;
break;
case ';':
_hasTime = true;
_hasLocation = true;
// Skip ';', 9 char of object identifier and delimiter ('*' or '_').
offset += 11;
break;
default:
// On unknown message type -> store complete message as comment
if (offset < _payload.size()) {
_comment = _payload.substr(offset);
offset += _comment.size();
}
return;
}
if (_hasTime) {
if (! _readTime(offset)) { return; }
}
if (_hasLocation) {
if (! _readLocation(offset)) { return; }
}
// Remaining text is comment
if (offset < _payload.size()) {
_comment = _payload.substr(offset);
offset += _comment.size();
}
}
bool
APRS::Message::_readLocation(size_t &offset) {
// Read latitude
if(! _readLatitude(offset)) { return false; }
// Read symbol table
char symbolTable = _payload[offset]; offset++;
// Read longitude
if (! _readLongitude(offset)) { return false; }
// Read symbol
_symbol = __toSymbol(symbolTable, _payload[offset]); offset++;
return true;
}
bool
APRS::Message::_readLatitude(size_t &offset)
{
// read degree
if (! __is_number(_payload[offset])) { return false; }
_latitude = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_latitude = _latitude*10 + int(_payload[offset]-0x30); offset++;
// read mintues
if (! __is_number(_payload[offset])) { return false; }
double min = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
min = min*10 + int(_payload[offset]-0x30); offset++;
// check for '.'
if ('.' != _payload[offset]) { return false; } offset++;
// read minutes decimal
if (! __is_number(_payload[offset])) { return false; }
double mindec = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
mindec = mindec*10 + int(_payload[offset]-0x30); offset++;
min += mindec/100;
// Update degree
_latitude += min/60;
// Process north/south indicator
switch (_payload[offset]) {
case 'N': offset++; break;
case 'S': _latitude = -_latitude; offset++; break;
default: return false; // <- on invalid indicator.
}
// done.
return true;
}
bool
APRS::Message::_readLongitude(size_t &offset) {
// read degree
if (! __is_number(_payload[offset])) { return false; }
_longitude = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_longitude = _longitude*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
_longitude = _longitude*10 + int(_payload[offset]-0x30); offset++;
// read minutes
if (! __is_number(_payload[offset])) { return false; }
double min = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
min = min*10 + int(_payload[offset]-0x30); offset++;
// skip '.'
if ('.' != _payload[offset]) { return false; } offset++;
// Read minutes decimals
if (! __is_number(_payload[offset])) { return false; }
double mindec = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
mindec = mindec*10 + int(_payload[offset]-0x30); offset++;
min += mindec/100;
// Update longitude
_longitude += min/60;
// read east/west indicator
switch (_payload[offset]) {
case 'E': offset++; break;
case 'W': offset++; _longitude = -_longitude; break;
default: return false;
}
return true;
}
bool
APRS::Message::_readTime(size_t &offset) {
if (! __is_number(_payload[offset])) { return false; }
int a = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
a = a*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
int b = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
b = b*10 + int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
int c = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
c = c*10 + int(_payload[offset]-0x30); offset++;
// Determine type of date-time
if ('z' == _payload[offset]) {
// Alter current time in terms of UTC
struct tm timeStruct = *gmtime(&_time);
timeStruct.tm_mday = a;
timeStruct.tm_hour = b;
timeStruct.tm_min = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if ('/' == _payload[offset]) {
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_mday = a;
timeStruct.tm_hour = b;
timeStruct.tm_min = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if ('h' == _payload[offset]) {
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_hour = a;
timeStruct.tm_min = b;
timeStruct.tm_sec = c;
// Update time-stamp
_time = mktime(&timeStruct);
offset++;
} else if (('0' <= _payload[offset]) && (('9' >= _payload[offset]))) {
if (! __is_number(_payload[offset])) { return false; }
int d = int(_payload[offset]-0x30); offset++;
if (! __is_number(_payload[offset])) { return false; }
d = d*10 + int(_payload[offset]-0x30); offset++;
// Alter current time in terms of local time
struct tm timeStruct = *localtime(&_time);
timeStruct.tm_mon = a;
timeStruct.tm_mday = b;
timeStruct.tm_hour = c;
timeStruct.tm_min = d;
// Update time-stamp
_time = mktime(&timeStruct);
} else {
return false;
}
// done...
return true;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const APRS::Message &message) {
std::cerr << "APRS: " << message.from() << " > " << message.to();
if (message.via().size()) {
std::cerr << " via " << message.via()[0];
for (size_t i=1; i<message.via().size(); i++) {
std::cerr << ", " << message.via()[i];
}
}
std::cerr << std::endl << " time: " << asctime(localtime(&message.time()));
if (message.hasLocation()) {
std::cerr << " location: " << message.latitude() << ", " << message.longitude()
<< std::endl << " symbol: " << message.symbol() << std::endl;
}
if (message.hasComment()) {
std::cerr << " comment: " << message.comment() << std::endl;
}
return stream;
}

@ -0,0 +1,66 @@
#ifndef __SDR_APRS_HH__
#define __SDR_APRS_HH__
#include "ax25.hh"
#include <ctime>
namespace sdr {
class APRS: public AX25
{
public:
class Message: public AX25::Message
{
public:
/** A small selection of possible symbols to display the station. */
typedef enum {
NONE, POLICE, DIGI, PHONE, AIRCRAFT, HOUSE, MOTORCYCLE, CAR, BBS, BALLOON, BUS,
BOAT, JOGGER, WX
} Symbol;
public:
Message();
Message(const AX25::Message &msg);
Message(const Message &msg);
Message &operator=(const Message &other);
inline bool hasLocation() const { return _hasLocation; }
inline double latitude() const { return _latitude; }
inline double longitude() const { return _longitude; }
inline Symbol symbol() const { return _symbol; }
inline const time_t &time() const { return _time; }
inline bool hasComment() const { return (0 != _comment.size()); }
inline const std::string &comment() const { return _comment; }
protected:
bool _readLocation(size_t &offset);
bool _readLatitude(size_t &offset);
bool _readLongitude(size_t &offset);
bool _readTime(size_t &offset);
protected:
bool _hasLocation;
double _latitude;
double _longitude;
Symbol _symbol;
bool _hasTime;
time_t _time;
std::string _comment;
};
public:
APRS();
void handleAX25Message(const AX25::Message &message);
virtual void handleAPRSMessage(const Message &message);
};
std::ostream& operator<<(std::ostream &stream, const APRS::Message &msg);
}
#endif // __SDR_APRS_HH__

@ -1,6 +1,8 @@
#include "ax25.hh"
#include "logger.hh"
#include "traits.hh"
#include <ctime>
using namespace sdr;
@ -49,10 +51,23 @@ static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
return (crc & 0xffff) == 0xf0b8;
}
void
unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt) {
size_t length = 0; call.resize(6);
for (size_t i=0; i<6; i++) {
call.at(i) = char(buffer[i]>>1);
if (' ' != call.at(i)) { length++; }
}
call.resize(length);
ssid = int( (buffer[6] & 0x1f) >> 1);
addrExt = !bool(buffer[6] & 0x01);
}
AX25::AX25()
: Sink<uint8_t>(), Source()
/* ******************************************************************************************** *
* Implementation of AX25 decoder
* ******************************************************************************************** */
AX25::AX25() : Sink<uint8_t>()
{
// pass...
}
@ -77,15 +92,9 @@ AX25::config(const Config &src_cfg) {
_state = 0;
_ptr = _rxbuffer;
// Allocate output buffer
_buffer = Buffer<uint8_t>(512);
LogMessage msg(LOG_DEBUG);
msg << "Config AX.25 node.";
Logger::get().log(msg);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
}
void
@ -104,16 +113,9 @@ AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
msg << "AX.25: Received invalid buffer: " << _rxbuffer;
Logger::get().log(msg);
} else {
bool addrExt;
std::string src, dst;
int srcSSID, dstSSID;
unpackCall(_rxbuffer, dst, dstSSID, addrExt);
unpackCall(_rxbuffer+7, src, srcSSID, addrExt);
std::cerr << "RX: " << src << "-" << srcSSID
<< " > " << dst << "-" << dstSSID << std::endl
<< " " << _rxbuffer+14 << std::endl;
memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer);
this->send(_buffer.head(_ptr-_rxbuffer));
// Assemble message
Message msg(_rxbuffer, _ptr-_rxbuffer-2);
this->handleAX25Message(msg);
}
}
// Receive data
@ -158,16 +160,118 @@ AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
}
}
void
AX25::handleAX25Message(const Message &message) {
// pass...
}
/* ******************************************************************************************** *
* Implementation of AX25Dump
* ******************************************************************************************** */
AX25Dump::AX25Dump(std::ostream &stream)
: AX25(), _stream(stream)
{
// pass...
}
void
AX25::unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt) {
size_t length = 0; call.resize(6);
for (size_t i=0; i<6; i++) {
call.at(i) = char(buffer[i]>>1);
if (' ' != call.at(i)) { length++; }
}
call.resize(length);
ssid = int( (buffer[6] & 0x1f) >> 1);
addrExt = !bool(buffer[6] & 0x01);
AX25Dump::handleAX25Message(const Message &message) {
_stream << "AX25: " << message << std::endl;
}
/* ******************************************************************************************** *
* Implementation of AX25 Address
* ******************************************************************************************** */
AX25::Address::Address()
: _call(), _ssid(0)
{
// pass...
}
AX25::Address::Address(const std::string &call, size_t ssid)
: _call(call), _ssid(ssid)
{
// pass...
}
AX25::Address::Address(const Address &other)
: _call(other._call), _ssid(other._ssid)
{
// pass...
}
AX25::Address &
AX25::Address::operator =(const Address &other) {
_call = other._call;
_ssid = other._ssid;
return *this;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const AX25::Address &addr) {
stream << addr.call() << "-" << addr.ssid();
return stream;
}
/* ******************************************************************************************** *
* Implementation of AX25 Message
* ******************************************************************************************** */
AX25::Message::Message()
: _from(), _to(), _via(), _payload()
{
// pass...
}
AX25::Message::Message(uint8_t *buffer, size_t length)
: _via(), _payload()
{
std::string call; int ssid; bool addrExt;
// Get destination address
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_to = Address(call, ssid);
// Get source address
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_from = Address(call, ssid);
while (addrExt) {
unpackCall(buffer, call, ssid, addrExt); buffer+=7; length -= 7;
_via.push_back(Address(call, ssid));
}
// Store payload
_payload.resize(length);
for (size_t i=0; i<length; i++) { _payload[i] = char(buffer[i]); }
}
AX25::Message::Message(const Message &other)
: _from(other._from), _to(other._to), _via(other._via),
_payload(other._payload)
{
// pass...
}
AX25::Message &
AX25::Message::operator =(const Message &other) {
_from = other._from;
_to = other._to;
_via = other._via;
_payload = other._payload;
return *this;
}
std::ostream &
sdr::operator <<(std::ostream &stream, const AX25::Message &msg) {
stream << msg.from() << " > " << msg.to();
if (0 < msg.via().size()) {
stream << " via " << msg.via()[0];
for (size_t i=1; i<msg.via().size(); i++) {
stream << ", " << msg.via()[i];
}
}
stream << " N=" << msg.payload().size() << std::endl;
stream << msg.payload();
return stream;
}

@ -3,7 +3,6 @@
#include "node.hh"
namespace sdr {
/** Decodes AX25 (PacketRadio) messages from a bit stream.
@ -17,20 +16,61 @@ namespace sdr {
* forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible
* for unpacking and handling the received datagram.
* @ingroup datanodes */
class AX25: public Sink<uint8_t>, public Source
class AX25: public Sink<uint8_t>
{
public:
class Address
{
public:
Address();
Address(const std::string &call, size_t ssid);
Address(const Address &other);
Address &operator=(const Address &other);
inline bool isEmpty() const { return 0 == _call.size(); }
inline const std::string &call() const { return _call; }
inline size_t ssid() const { return _ssid; }
protected:
std::string _call;
size_t _ssid;
};
class Message
{
public:
Message();
Message(uint8_t *buffer, size_t length);
Message(const Message &other);
Message &operator=(const Message &other);
inline const Address &from() const { return _from; }
inline const Address &to() const { return _to; }
inline const std::vector<Address> &via() const { return _via; }
inline const std::string &payload() const { return _payload; }
protected:
Address _from;
Address _to;
std::vector<Address> _via;
std::string _payload;
};
public:
/** Constructor. */
AX25();
/** Destructor. */
virtual ~AX25();
/** Configures the node. */
virtual void config(const Config &src_cfg);
/** Processes the bit stream. */
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
/** Unpacks a AX.25 encoded call (address). */
static void unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt);
virtual void handleAX25Message(const Message &message);
protected:
/** The last bits. */
@ -44,12 +84,32 @@ protected:
uint8_t _rxbuffer[512];
/** Insert-pointer to the buffer. */
uint8_t *_ptr;
/** Output buffer. */
Buffer<uint8_t> _buffer;
};
}
/** Prints received AX25 messages to the specified stream. */
class AX25Dump: public AX25
{
public:
/** Constructor.
* @param stream The output stream. */
AX25Dump(std::ostream &stream);
/** Implements AX25 interface. */
void handleAX25Message(const Message &message);
protected:
/** The output stream. */
std::ostream &_stream;
};
/** Serialization of AX25 address. */
std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Address &addr);
/** Serialization of AX25 message. */
std::ostream& operator<<(std::ostream &stream, const sdr::AX25::Message &msg);
} // namespace sdr
#endif // __SDR_AX25_HH__

@ -161,13 +161,14 @@ POCSAGDump::handleMessages() {
Message msg = _queue.back(); _queue.pop_back();
std::cerr << "POCSAG: @" << msg.address()
<< ", F=" << int(msg.function())
<< ", bits=" << msg.bits() << std::endl;
if (msg.estimateText() >= msg.estimateNumeric()) {
std::cerr << " txt: " << msg.asText() << "" << std::endl;
<< ", bits=" << msg.bits();
if (0 == msg.bits()) {
std::cerr << " (alert)" << std::endl;
} else if (msg.estimateText() >= msg.estimateNumeric()) {
std::cerr << " (txt)" << std::endl << " " << msg.asText() << std::endl;
} else {
std::cerr << " num: " << msg.asNumeric() << std::endl;
std::cerr << " (num)" << std::endl << " " << msg.asNumeric() << std::endl;
}
//std::cerr << " hex: " << msg.asHex() << std::endl;
}
}

Loading…
Cancel
Save