diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bf1aa3..a35154d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 13a6128..155bb75 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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) diff --git a/examples/sdr_afsk1200.cc b/examples/sdr_afsk1200.cc deleted file mode 100644 index 0bbda85..0000000 --- a/examples/sdr_afsk1200.cc +++ /dev/null @@ -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; -} diff --git a/examples/sdr_pocsag.cc b/examples/sdr_pocsag.cc index 43e73ca..757a12f 100644 --- a/examples/sdr_pocsag.cc +++ b/examples/sdr_pocsag.cc @@ -1,9 +1,33 @@ +/* + * sdr_pocsag -- A POCSAG receiver using libsdr. + * + * (c) 2015 Hannes Matuschek + * + * 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 #include @@ -11,54 +35,147 @@ using namespace sdr; +// On SIGINT -> stop queue properly static void __sigint_handler(int signo) { - // On SIGINT -> stop queue properly 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 cast; - ASKDetector 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 *wav_cast=0; + + // Nodes for PortAudio input + PortSource *audio_src=0; + + // Nodes for RTL2832 input + RTLSource *rtl_source=0; + AutoCast< std::complex > *rtl_cast=0; + IQBaseBand *rtl_baseband=0; + FMDemod *rtl_demod=0; + FMDeemph *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 >(); + rtl_baseband = new IQBaseBand(0, 12.5e3, 21, 0, 22050.0); + rtl_demod = new FMDemod(); + rtl_deemph = new FMDeemph(); + 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(22010., 1024); + src = audio_src; + // On queue idle, read next chunk from audio source + Queue::get().addIdle(audio_src, &PortSource::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(); + 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 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(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c953063..33cb475 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/aprs.cc b/src/aprs.cc new file mode 100644 index 0000000..f182caa --- /dev/null +++ b/src/aprs.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 + + +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__ diff --git a/src/ax25.cc b/src/ax25.cc index ea42d4a..67cb983 100644 --- a/src/ax25.cc +++ b/src/ax25.cc @@ -1,6 +1,8 @@ #include "ax25.hh" #include "logger.hh" #include "traits.hh" +#include + 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(), Source() +/* ******************************************************************************************** * + * Implementation of AX25 decoder + * ******************************************************************************************** */ +AX25::AX25() : Sink() { // pass... } @@ -77,15 +92,9 @@ AX25::config(const Config &src_cfg) { _state = 0; _ptr = _rxbuffer; - // Allocate output buffer - _buffer = Buffer(512); - LogMessage msg(LOG_DEBUG); msg << "Config AX.25 node."; Logger::get().log(msg); - - // propergate config - this->setConfig(Config(Traits::scalarId, 0, 512, 1)); } void @@ -104,16 +113,9 @@ AX25::process(const Buffer &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 &buffer, bool allow_overwrite) } } - 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); +AX25::handleAX25Message(const Message &message) { + // pass... +} + + +/* ******************************************************************************************** * + * Implementation of AX25Dump + * ******************************************************************************************** */ +AX25Dump::AX25Dump(std::ostream &stream) + : AX25(), _stream(stream) +{ + // pass... +} + +void +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 " << msg.to(); + if (0 < msg.via().size()) { + stream << " via " << msg.via()[0]; + for (size_t i=1; i, public Source +class AX25: public Sink { +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
&via() const { return _via; } + + inline const std::string &payload() const { return _payload; } + + protected: + Address _from; + Address _to; + std::vector
_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 &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 _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__ diff --git a/src/pocsag.cc b/src/pocsag.cc index d341c83..6fef855 100644 --- a/src/pocsag.cc +++ b/src/pocsag.cc @@ -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; } }