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_SOURCE_DIR}/src)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/src) INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/src)
INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${PORTAUDIO_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${GETOPT_INCLUDE_DIRS})
# Set some variables for the configuration file # Set some variables for the configuration file
IF(FFTW_FOUND) IF(FFTW_FOUND)
@ -41,7 +42,8 @@ ELSE(TRLSDR_FOUND)
ENDIF(RTLSDR_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 compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC")

@ -8,15 +8,15 @@ IF(SDR_WITH_PORTAUDIO)
add_executable(sdr_rec sdr_rec.cc) add_executable(sdr_rec sdr_rec.cc)
target_link_libraries(sdr_rec ${LIBS} libsdr) 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) add_executable(sdr_rtty sdr_rtty.cc)
target_link_libraries(sdr_rtty ${LIBS} libsdr) target_link_libraries(sdr_rtty ${LIBS} libsdr)
add_executable(sdr_pocsag sdr_pocsag.cc) add_executable(sdr_pocsag sdr_pocsag.cc)
target_link_libraries(sdr_pocsag ${LIBS} libsdr) 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) 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 "autocast.hh"
#include "portaudio.hh" #include "portaudio.hh"
#include "wavfile.hh" #include "wavfile.hh"
#include "fsk.hh" #include "fsk.hh"
#include "utils.hh" #include "utils.hh"
#include "pocsag.hh" #include "pocsag.hh"
#include "options.hh"
#include "rtlsource.hh"
#include "demod.hh"
#include "baseband.hh"
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
@ -11,54 +35,147 @@
using namespace sdr; using namespace sdr;
static void __sigint_handler(int signo) {
// On SIGINT -> stop queue properly // On SIGINT -> stop queue properly
static void __sigint_handler(int signo) {
Queue::get().stop(); Queue::get().stop();
} }
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
if (2 != argc) { // Install log handler
std::cerr << "Usage: sdr_posag FILENAME" << std::endl;
return -1;
}
sdr::Logger::get().addHandler( sdr::Logger::get().addHandler(
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
// Register handler: // Register signal handler:
signal(SIGINT, __sigint_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(); PortAudio::init();
// Get the global queue
Queue &queue = Queue::get(); Queue &queue = Queue::get();
WavSource src(argv[1]); // pointer to the selected source
PortSink sink; Source *src = 0;
AutoCast<int16_t> cast;
ASKDetector<int16_t> detector(false);
BitStream bits(1200, BitStream::NORMAL);
POCSAGDump pocsag(std::cout);
// Cast to int16 // Nodes for WAV file input
src.connect(&cast); WavSource *wav_src=0;
// ASK detector AutoCast<int16_t> *wav_cast=0;
cast.connect(&detector);
// bit decoder // 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); detector.connect(&bits);
// POCSAG decoder and print // and bit decoder to POCSAG decoder and print
bits.connect(&pocsag); bits.connect(&pocsag);
// on idle -> read next buffer from input file // If monitor is enabled -> connect to sink
queue.addIdle(&src, &WavSource::next); if (opts.has("monitor")) {
// on end-of-file -> stop queue src->connect(&sink);
src.addEOS(&queue, &Queue::stop); }
// Start queue // Start queue
queue.start(); queue.start();
// wait for queue to exit // wait for queue to exit
queue.wait(); 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 // terminate port audio system properly
PortAudio::terminate(); PortAudio::terminate();

@ -1,14 +1,13 @@
# Sources of libsdr # Sources of libsdr
set(LIBSDR_SOURCES set(LIBSDR_SOURCES
buffer.cc node.cc queue.cc traits.cc buffer.cc node.cc queue.cc traits.cc portaudio.cc utils.cc wavfile.cc exception.cc logger.cc
portaudio.cc utils.cc wavfile.cc psk31.cc options.cc fsk.cc ax25.cc aprs.cc baudot.cc pocsag.cc bch31_21.cc)
exception.cc logger.cc psk31.cc options.cc fsk.cc ax25.cc baudot.cc pocsag.cc bch31_21.cc)
set(LIBSDR_HEADERS sdr.hh math.hh set(LIBSDR_HEADERS sdr.hh math.hh
buffer.hh node.hh queue.hh buffernode.hh filternode.hh traits.hh autocast.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 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 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 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) if(SDR_WITH_PORTAUDIO)
set(LIBSDR_SOURCES ${LIBSDR_SOURCES} portaudio.cc) 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 "ax25.hh"
#include "logger.hh" #include "logger.hh"
#include "traits.hh" #include "traits.hh"
#include <ctime>
using namespace sdr; using namespace sdr;
@ -49,10 +51,23 @@ static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
return (crc & 0xffff) == 0xf0b8; 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... // pass...
} }
@ -77,15 +92,9 @@ AX25::config(const Config &src_cfg) {
_state = 0; _state = 0;
_ptr = _rxbuffer; _ptr = _rxbuffer;
// Allocate output buffer
_buffer = Buffer<uint8_t>(512);
LogMessage msg(LOG_DEBUG); LogMessage msg(LOG_DEBUG);
msg << "Config AX.25 node."; msg << "Config AX.25 node.";
Logger::get().log(msg); Logger::get().log(msg);
// propergate config
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
} }
void void
@ -104,16 +113,9 @@ AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
msg << "AX.25: Received invalid buffer: " << _rxbuffer; msg << "AX.25: Received invalid buffer: " << _rxbuffer;
Logger::get().log(msg); Logger::get().log(msg);
} else { } else {
bool addrExt; // Assemble message
std::string src, dst; Message msg(_rxbuffer, _ptr-_rxbuffer-2);
int srcSSID, dstSSID; this->handleAX25Message(msg);
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));
} }
} }
// Receive data // 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 void
AX25::unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt) { AX25Dump::handleAX25Message(const Message &message) {
size_t length = 0; call.resize(6); _stream << "AX25: " << message << std::endl;
for (size_t i=0; i<6; i++) { }
call.at(i) = char(buffer[i]>>1);
if (' ' != call.at(i)) { length++; }
} /* ******************************************************************************************** *
call.resize(length); * Implementation of AX25 Address
ssid = int( (buffer[6] & 0x1f) >> 1); * ******************************************************************************************** */
addrExt = !bool(buffer[6] & 0x01); 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" #include "node.hh"
namespace sdr { namespace sdr {
/** Decodes AX25 (PacketRadio) messages from a bit stream. /** 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 * forwards the AX.25 datagram to all connected sinks on success. The receiving node is responsible
* for unpacking and handling the received datagram. * for unpacking and handling the received datagram.
* @ingroup datanodes */ * @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: public:
/** Constructor. */ /** Constructor. */
AX25(); AX25();
/** Destructor. */ /** Destructor. */
virtual ~AX25(); virtual ~AX25();
/** Configures the node. */ /** Configures the node. */
virtual void config(const Config &src_cfg); virtual void config(const Config &src_cfg);
/** Processes the bit stream. */ /** Processes the bit stream. */
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite); virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
/** Unpacks a AX.25 encoded call (address). */ virtual void handleAX25Message(const Message &message);
static void unpackCall(const uint8_t *buffer, std::string &call, int &ssid, bool &addrExt);
protected: protected:
/** The last bits. */ /** The last bits. */
@ -44,12 +84,32 @@ protected:
uint8_t _rxbuffer[512]; uint8_t _rxbuffer[512];
/** Insert-pointer to the buffer. */ /** Insert-pointer to the buffer. */
uint8_t *_ptr; 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__ #endif // __SDR_AX25_HH__

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