diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f63c53f..13a6128 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,12 +1,12 @@ IF(SDR_WITH_PORTAUDIO) -# add_executable(sdr_wavplay sdr_wavplay.cc) -# target_link_libraries(sdr_wavplay ${LIBS} libsdr) + add_executable(sdr_wavplay sdr_wavplay.cc) + target_link_libraries(sdr_wavplay ${LIBS} libsdr) -# add_executable(sdr_fm sdr_fm.cc) -# target_link_libraries(sdr_fm ${LIBS} libsdr) + add_executable(sdr_fm sdr_fm.cc) + target_link_libraries(sdr_fm ${LIBS} libsdr) -# add_executable(sdr_rec sdr_rec.cc) -# target_link_libraries(sdr_rec ${LIBS} libsdr) + 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) diff --git a/examples/sdr_afsk1200.cc b/examples/sdr_afsk1200.cc index 4563c88..0bbda85 100644 --- a/examples/sdr_afsk1200.cc +++ b/examples/sdr_afsk1200.cc @@ -1,7 +1,7 @@ #include "wavfile.hh" #include "autocast.hh" #include "interpolate.hh" -#include "afsk.hh" +#include "fsk.hh" #include "ax25.hh" #include "utils.hh" diff --git a/examples/sdr_pocsag.cc b/examples/sdr_pocsag.cc index ddac8e2..43e73ca 100644 --- a/examples/sdr_pocsag.cc +++ b/examples/sdr_pocsag.cc @@ -1,7 +1,7 @@ #include "autocast.hh" #include "portaudio.hh" #include "wavfile.hh" -#include "afsk.hh" +#include "fsk.hh" #include "utils.hh" #include "pocsag.hh" @@ -36,21 +36,17 @@ int main(int argc, char *argv[]) WavSource src(argv[1]); PortSink sink; AutoCast cast; - ASKDetector detector; + ASKDetector detector(false); BitStream bits(1200, BitStream::NORMAL); - POCSAG pocsag; + POCSAGDump pocsag(std::cout); - //BitDump dump; - - // Playback - //src.connect(&sink); // Cast to int16 src.connect(&cast); // ASK detector cast.connect(&detector); + // bit decoder detector.connect(&bits); - // Baudot decoder - // dump to std::cerr + // POCSAG decoder and print bits.connect(&pocsag); // on idle -> read next buffer from input file diff --git a/examples/sdr_psk31.cc b/examples/sdr_psk31.cc deleted file mode 100644 index 7f8ddc6..0000000 --- a/examples/sdr_psk31.cc +++ /dev/null @@ -1,81 +0,0 @@ -#include "baseband.hh" -#include "autocast.hh" -#include "demod.hh" -#include "portaudio.hh" -#include "wavfile.hh" -#include "gui/gui.hh" -#include "psk31.hh" - -#include -#include -#include - -#include -#include -#include - -using namespace sdr; - -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_psk31 FILENAME" << std::endl; - return -1; - } - - sdr::Logger::get().addHandler( - new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); - - // Register handler: - signal(SIGINT, __sigint_handler); - - QApplication app(argc, argv); - - QMainWindow *win = new QMainWindow(); - gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5); - gui::SpectrumView *spec_view = new gui::SpectrumView(spec); - spec_view->setMindB(-60); - win->setCentralWidget(spec_view); - win->setMinimumSize(640, 240); - win->show(); - - PortAudio::init(); - Queue &queue = Queue::get(); - - WavSource src(argv[1]); - AutoCast< std::complex > cast; - IQBaseBand baseband(0, 2144., 200.0, 63, 1); - baseband.setCenterFrequency(2144.0); - BPSK31 demod; - PortSink sink; - Varicode decode; - DebugDump dump; - - src.connect(&cast, true); - cast.connect(&baseband, true); - baseband.connect(spec); - baseband.connect(&sink); - baseband.connect(&demod); - demod.connect(&decode, true); - decode.connect(&dump, true); - - queue.addIdle(&src, &WavSource::next); - //queue.addIdle(&src, &IQSigGen::next); - - queue.start(); - app.exec(); - queue.stop(); - queue.wait(); - - PortAudio::terminate(); - - return 0; -} diff --git a/examples/sdr_rds.cc b/examples/sdr_rds.cc deleted file mode 100644 index 87590cf..0000000 --- a/examples/sdr_rds.cc +++ /dev/null @@ -1,82 +0,0 @@ -#include "sdr.hh" -#include "rtlsource.hh" -#include "baseband.hh" -#include "autocast.hh" -#include "gui/gui.hh" -#include "logger.hh" -#include - -#include -#include -#include - -using namespace sdr; - -static void __sigint_handler(int signo) { - // On SIGINT -> stop queue properly - Queue::get().stop(); -} - - -int main(int argc, char *argv[]) -{ - if (argc < 2) { - std::cerr << "USAGE: sdr_rds FREQUENCY" << std::endl; - return -1; - } - double freq = atof(argv[1]); - - PortAudio::init(); - Queue &queue = Queue::get(); - - // Register handler: - signal(SIGINT, __sigint_handler); - - QApplication app(argc, argv); - - QMainWindow *win = new QMainWindow(); - gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5); - gui::SpectrumView *spec_view = new gui::SpectrumView(spec); - spec_view->setMindB(-200); - win->setCentralWidget(spec_view); - win->setMinimumSize(640, 240); - - win->show(); - - sdr::Logger::get().addHandler(new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG)); - - // Assemble processing chain - //RTLSource src(freq, 1e6); - WavSource src(argv[1]); - AutoCast< std::complex > cast; - IQBaseBand baseband(0, 200e3, 16, 5); - FMDemod demod; - BaseBand mono(0, 15e3, 16, 6); - BaseBand pilot(19e3, 5e2, 16, 84); - BaseBand rds(57e3, 3e3, 16, 84); - PortSink sink; - - src.connect(&cast, true); - cast.connect(&baseband, true); - //src.connect(&baseband, true); - baseband.connect(&demod, true); - demod.connect(&mono); - demod.connect(&pilot); - demod.connect(&rds); - - mono.connect(&sink); - mono.connect(spec); - - //queue.addStart(&src, &RTLSource::start); - //queue.addStop(&src, &RTLSource::stop); - queue.addIdle(&src, &WavSource::next); - - queue.start(); - app.exec(); - queue.stop(); - queue.wait(); - - PortAudio::terminate(); - - return 0; -} diff --git a/examples/sdr_rtty.cc b/examples/sdr_rtty.cc index 9c4a62c..823cb36 100644 --- a/examples/sdr_rtty.cc +++ b/examples/sdr_rtty.cc @@ -1,7 +1,7 @@ #include "autocast.hh" #include "portaudio.hh" #include "wavfile.hh" -#include "afsk.hh" +#include "fsk.hh" #include "baudot.hh" #include "utils.hh" diff --git a/examples/sdr_spec.cc b/examples/sdr_spec.cc deleted file mode 100644 index 0830c98..0000000 --- a/examples/sdr_spec.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "sdr.hh" -#include "rtlsource.hh" -#include "baseband.hh" -#include "utils.hh" -#include "gui/gui.hh" -#include -#include "portaudio.hh" - -#include -#include -#include - -using namespace sdr; - -static void __sigint_handler(int signo) { - // On SIGINT -> stop queue properly - Queue::get().stop(); -} - - -int main(int argc, char *argv[]) -{ - Queue &queue = Queue::get(); - - // Register handler: - signal(SIGINT, __sigint_handler); - - PortAudio::init(); - - QApplication app(argc, argv); - - QMainWindow *win = new QMainWindow(); - gui::Spectrum *spec = new gui::Spectrum(2, 1024, 5); - gui::WaterFallView *spec_view = new gui::WaterFallView(spec); - win->setCentralWidget(spec_view); - win->setMinimumSize(640, 240); - - win->show(); - - // Assemble processing chain - PortSource< int16_t > src(44100.0, 2048); - AGC agc; - //IQBaseBand baseband(0, 500e3, 8, 5); - //AMDemod demod; - PortSink sink; - - src.connect(&agc, true); - agc.connect(&sink, true); - //baseband.connect(&demod); - //demod.connect(&sink, true); - src.connect(spec); - - queue.addIdle(&src, &PortSource< int16_t >::next); - - queue.start(); - app.exec(); - - queue.stop(); - queue.wait(); - - PortAudio::terminate(); - - return 0; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09841c0..c953063 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,12 +2,12 @@ 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 afsk.cc ax25.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 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 afsk.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) if(SDR_WITH_PORTAUDIO) diff --git a/src/afsk.cc b/src/fsk.cc similarity index 76% rename from src/afsk.cc rename to src/fsk.cc index f89d518..3a80c3b 100644 --- a/src/afsk.cc +++ b/src/fsk.cc @@ -1,4 +1,4 @@ -#include "afsk.hh" +#include "fsk.hh" #include "logger.hh" #include "traits.hh" #include "interpolate.hh" @@ -6,6 +6,9 @@ using namespace sdr; +/* ******************************************************************************************** * + * Implementation of FSKDetector + * ******************************************************************************************** */ FSKDetector::FSKDetector(float baud, float Fmark, float Fspace) : Sink(), Source(), _baud(baud), _corrLen(0), _Fmark(Fmark), _Fspace(Fspace) { @@ -92,6 +95,10 @@ FSKDetector::process(const Buffer &buffer, bool allow_overwrite) { } + +/* ******************************************************************************************** * + * Implementation of BitStream + * ******************************************************************************************** */ BitStream::BitStream(float baud, Mode mode) : Sink(), Source(), _baud(baud), _mode(mode), _corrLen(0) { @@ -106,7 +113,7 @@ BitStream::config(const Config &src_cfg) { // Check if buffer type matches if (Config::typeId() != src_cfg.type()) { ConfigError err; - err << "Can not configure FSKBitStreamBase: Invalid type " << src_cfg.type() + err << "Can not configure BitStream: Invalid type " << src_cfg.type() << ", expected " << Config::typeId(); throw err; } @@ -136,11 +143,11 @@ BitStream::config(const Config &src_cfg) { _buffer = Buffer(1+src_cfg.bufferSize()/_corrLen); LogMessage msg(LOG_DEBUG); - msg << "Config FSKBitStreamBase node: " << std::endl - << " input sample rate: " << src_cfg.sampleRate() << " Hz" << std::endl - << " baud rate: " << _baud << std::endl - << " samples per bit: " << 1./_omega << std::endl - << " phase incr/symbol: " << _omega; + msg << "Config BitStream node: " << std::endl + << " symbol rate: " << src_cfg.sampleRate() << " Hz" << std::endl + << " baud rate: " << _baud << std::endl + << " symbols/bit: " << 1./_omega << std::endl + << " bit mode: " << ( (NORMAL == _mode) ? "normal" : "transition" ); Logger::get().log(msg); // Forward config. @@ -193,3 +200,37 @@ BitStream::process(const Buffer &buffer, bool allow_overwrite) if (o>0) { this->send(_buffer.head(o)); } } + + +/* ******************************************************************************************** * + * Implementation of BitDump + * ******************************************************************************************** */ +BitDump::BitDump(std::ostream &stream) + : Sink(), _stream(stream) +{ + // pass... +} + +void +BitDump::config(const Config &src_cfg) { + // Check if config is complete + if (!src_cfg.hasType()) { return; } + + // Check if buffer type matches + if (Config::typeId() != src_cfg.type()) { + ConfigError err; + err << "Can not configure BitDump: Invalid type " << src_cfg.type() + << ", expected " << Config::typeId(); + throw err; + } +} + +void +BitDump::process(const Buffer &buffer, bool allow_overwrite) { + for (size_t i=0; i, public Source { public: + /** Constructor. + * @param baud Specifies the baud-rate of the signal. + * @param Fmark Specifies the mark frequency in Hz. + * @param Fspace Specifies the space frequency in Hz. */ FSKDetector(float baud, float Fmark, float Fspace); void config(const Config &src_cfg); @@ -23,11 +32,16 @@ protected: uint8_t _process(int16_t sample); protected: + /** Baudrate of the transmission. Needed to compute the filter length of the FIR mark/space + * filters. */ float _baud; + /** The filter lenght. */ size_t _corrLen; /** The current FIR filter LUT index. */ size_t _lutIdx; + /** Mark "tone" frequency. */ float _Fmark; + /** Space "tone" frequency. */ float _Fspace; /** Mark frequency FIR filter LUT. */ Buffer< std::complex > _markLUT; @@ -42,12 +56,23 @@ protected: }; +/** Rather trivial node to detect mark/space symbols by the amplitude. + * For low baud rates (i.e. <= 1200 baud) a FSK signal can be "demodulated" using a + * simple FM demodulator. The result will be a series of decaying exponentials. Hence the + * mark/space symbols can be determined by the means of the input amplitude (positive/negative). + * + * This node implements such a simple symbol detection by the means of the amplitude. The node + * returns a sequence of symbols (sub-bits) that need to be processed to obtain the sequence of + * received bits (i.e. @c BitStream). + * + * @ingroup demods */ template class ASKDetector: public Sink, public Source { public: - ASKDetector() - : Sink(), Source() + /** Constructor. */ + ASKDetector(bool invert=false) + : Sink(), Source(), _invert(invert) { // pass... } @@ -69,8 +94,9 @@ public: LogMessage msg(LOG_DEBUG); msg << "Config ASKDetector node: " << std::endl - << " detection threshold: " << 0 << std::endl - << " sample/symbol rate: " << src_cfg.sampleRate() << " Hz"; + << " threshold: " << 0 << std::endl + << " invert: " << ( _invert ? "yes" : "no" ) << std::endl + << " symbol rate: " << src_cfg.sampleRate() << " Hz"; Logger::get().log(msg); // Forward config. @@ -79,17 +105,22 @@ public: void process(const Buffer &buffer, bool allow_overwrite) { for (size_t i=0; i0); + _buffer[i] = ((buffer[i]>0)^_invert); } this->send(_buffer.head(buffer.size()), false); } protected: + /** If true the symbol logic is inverted. */ + bool _invert; + /** The output buffer. */ Buffer _buffer; }; -/** Decodes a bitstream with the desired baud rate. */ +/** Decodes a bitstream with the desired baud rate. + * This node implements a simple PLL to syncronize the bit sampling with the transitions + * of the input symbol sequence. */ class BitStream: public Sink, public Source { public: @@ -100,45 +131,62 @@ public: } Mode; public: + /** Constructor. + * @param baud Specifies the baud-rate of the input signal. + * @param mode Specifies the bit detection mode. */ BitStream(float baud, Mode mode = TRANSITION); void config(const Config &src_cfg); void process(const Buffer &buffer, bool allow_overwrite); protected: + /** The baud rate. */ float _baud; + /** The bit detection mode. */ Mode _mode; + /** The approximative bit length in samples. */ size_t _corrLen; - + /** Last received symbols. */ Buffer _symbols; + /** Insertion index for the next symbol. */ size_t _symIdx; - + /** Sum over all received symbol (encoded as -1 & 1). */ int32_t _symSum; + /** Last sum over all received symbol (encoded as -1 & 1). */ int32_t _lastSymSum; - + /** Current bit "phase". */ float _phase; + /** Phase velocity. */ float _omega; + /** Minimum phase velocity. */ float _omegaMin; + /** Maximum phase velocity. */ float _omegaMax; + /** PLL gain. */ float _pllGain; - + /** The last decoded bits (needed for transition mode). */ uint8_t _lastBits; - + /** Output buffer. */ Buffer _buffer; }; +/** Trivial node to dump a bit-stream to a std::ostream. + * @ingroup sinks */ class BitDump : public Sink { public: - void config(const Config &src_cfg) {} - void process(const Buffer &buffer, bool allow_overwrite) { - for (size_t i=0; i &buffer, bool allow_overwrite); + +protected: + /** The output stream. */ + std::ostream &_stream; }; } -#endif // __SDR_AFSK_HH__ +#endif // __SDR_FSK_HH__ diff --git a/src/pocsag.cc b/src/pocsag.cc index b7f3e22..d341c83 100644 --- a/src/pocsag.cc +++ b/src/pocsag.cc @@ -1,5 +1,6 @@ #include "pocsag.hh" #include "bch31_21.hh" +#include "logger.hh" using namespace sdr; @@ -8,6 +9,9 @@ inline bool is_address(uint32_t word) { } +/* ********************************************************************************************* * + * Implementation of POCSAG + * ********************************************************************************************* */ POCSAG::POCSAG() : Sink() { @@ -25,6 +29,10 @@ POCSAG::config(const Config &src_cfg) { throw err; } + LogMessage msg(LOG_DEBUG); + msg << "Config POCSAG node."; + Logger::get().log(msg); + _state = WAIT; _bits = 0; } @@ -43,7 +51,7 @@ POCSAG::process(const Buffer &buffer, bool allow_overwrite) uint32_t word = (_bits & 0xffffffff); if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) { // init messages - _reset_all_messages(); + _reset_message(); _state = RECEIVE; _bitcount = 0; _slot = 0; } } else if (RECEIVE == _state) { @@ -77,7 +85,8 @@ POCSAG::process(const Buffer &buffer, bool allow_overwrite) _state = RECEIVE; _slot = 0; _bitcount = 0; } else { // Otherwise -> end of transmission, wait for next sync - _finish_all_messages(); _state = WAIT; + _finish_message(); _state = WAIT; + // Process received messages this->handleMessages(); } } @@ -89,66 +98,157 @@ POCSAG::process(const Buffer &buffer, bool allow_overwrite) void POCSAG::_process_word(uint32_t word) { - // Check if slot is skipped - if (0x7A89C197 == word) { // Skip slot - _finish_message(_slot); return; - } + /*std::cerr << "POCSAG: RX " << std::hex << word + << std::dec << " @ " << int(_slot) << std::endl; */ - // Check if word is an address word - if (is_address(word)) { - _finish_message(_slot); + + if (0x7A89C197 == word) { + // Skip + _finish_message(); + } else if (is_address(word)) { + // If address word + _finish_message(); // Assemble address - uint32_t addr = (((word>>13) & 0x03ffff)<<3) | (_slot & 0x03); + uint32_t addr = ((((word>>13) & 0x03ffff)<<3) + _slot ); uint8_t func = ((word>>11) & 0x03); - _messages[_slot] = Message(addr, func); + // init new message + _message = Message(addr, func); } else { - if (_messages[_slot].isEmpty()) { - std::cerr << "Oops: Payload w/o address in slot " << int(_slot) - << " word: " << std::hex << word << std::dec << std::endl; + // on data word + if (_message.isEmpty()) { + LogMessage msg(LOG_DEBUG); + msg << "POCSAG: Payload w/o address in slot " << int(_slot) + << " word: " << std::hex << word; + Logger::get().log(msg); + } else { + _message.addPayload(word); } - _messages[_slot].addPayload(word); } } void -POCSAG::_reset_message(uint8_t slot) { - _messages[slot] = Message(); +POCSAG::_reset_message() { + _message = Message(); } void -POCSAG::_reset_all_messages() { - for (uint8_t i=0; i<8; i++) { - _reset_message(i); - } -} - -void -POCSAG::_finish_message(uint8_t slot) { - if (_messages[slot].isEmpty()) { return; } - _queue.push_back(_messages[slot]); - _reset_message(slot); -} - -void -POCSAG::_finish_all_messages() { - for (uint8_t i=0; i<8; i++) { - _finish_message(i); - } +POCSAG::_finish_message() { + if (_message.isEmpty()) { return; } + _queue.push_back(_message); + _reset_message(); } void POCSAG::handleMessages() { + // pass... +} + + +/* ********************************************************************************************* * + * Implementation of POCSAGDump + * ********************************************************************************************* */ +POCSAGDump::POCSAGDump(std::ostream &stream) + : POCSAG(), _stream(stream) +{ + // pass... +} + +void +POCSAGDump::handleMessages() { // You may re-implement this virutal method to process the queued messages in _queue. while (_queue.size()) { 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; + } else { + std::cerr << " num: " << msg.asNumeric() << std::endl; + } + //std::cerr << " hex: " << msg.asHex() << std::endl; } } +/* ********************************************************************************************* * + * Implementation of POCSAG::Message + * ********************************************************************************************* */ +std::string +ascii2text(uint8_t byte) { + switch ( byte ) { + case 0: return ""; + case 1: return ""; + case 2: return ""; + case 3: return ""; + case 4: return ""; + case 5: return ""; + case 6: return ""; + case 7: return ""; + case 8: return ""; + case 9: return ""; + case 10: return ""; + case 11: return ""; + case 12: return ""; + case 13: return ""; + case 14: return ""; + case 15: return ""; + case 16: return ""; + case 17: return ""; + case 18: return ""; + case 19: return ""; + case 20: return ""; + case 21: return ""; + case 22: return ""; + case 23: return ""; + case 24: return ""; + case 25: return ""; + case 26: return ""; + case 27: return ""; + case 28: return ""; + case 29: return ""; + case 30: return ""; + case 31: return ""; + default: break; + } + std::string txt; txt.resize(1, char(byte)); + return txt; +} + +char +bcd2text(uint8_t bcd) { + static const char *conv_table = "084 2.6]195-3U7["; + return conv_table[bcd&0xf]; +} + +int +pocsag_text_weight(char c) { + if (c < 32 || c == 127) { + return -5; // Non printable characters are uncommon + } + if ( (c > 32 && c < 48) + || (c > 57 && c < 65) + || (c > 90 && c < 97) + || (c > 122 && c < 127) ) { + return -2; // Penalize special characters + } + return 1; +} + +int +pocsag_numeric_weight(char cp, size_t pos) { + if(cp == 'U') + return -10; + if(cp == '[' || cp == ']') + return -5; + if(cp == ' ' || cp == '.' || cp == '-') + return -2; + if(pos < 10) // Penalize long messages + return 5; + return 0; +} + POCSAG::Message::Message() : _address(0), _function(0), _empty(true), _bits(0) @@ -181,15 +281,92 @@ POCSAG::Message::operator =(const Message &other) { void POCSAG::Message::addPayload(uint32_t word) { - // Add 20 LSB bits from data to payload vector + // Add data bits from data to payload vector uint32_t mask = 0x40000000; - for (size_t i=0; i<20; i++) { - // on new byte + for (int i=19; i>=0; i--) { + // on new byte -> add empty byte to payload if (0 == (_bits % 8)) { _payload.push_back(0x00); } // add bit to last byte of payload - _payload.back() = ((_payload.back()<<1)|(word&mask)); + _payload.back() = ((_payload.back()<<1) | ((word & mask)>>(i+11))); // Increment bit counter and update mask _bits++; mask = (mask>>1); } } +std::string +POCSAG::Message::asText() const +{ + uint8_t byte = 0; + // Decode message + std::stringstream buffer; + for (size_t i=0; i<_bits; i++) { + size_t byteIdx = i/8; + size_t bitIdx = (7-(i%8)); + // add bit to byte (reverse order) + byte = ((byte>>1) | (((_payload[byteIdx]>>bitIdx) & 0x01) << 6)); + if (6 == (i%7)) { + buffer << ascii2text(byte&0x7f); + } + //std::cerr << "byte " << byteIdx << " bit " << bitIdx << " ascii bit " << (i%7) << std::endl; + } + return buffer.str(); +} + + +std::string +POCSAG::Message::asNumeric() const +{ + // Get number of complete half-bytes in payload + size_t N = _bits/4; + // Decode message + std::stringstream buffer; + for (size_t i=0; i<(N/2); i++) { + buffer << bcd2text((_payload[i]>>4) & 0xf); + buffer << bcd2text((_payload[i]>>0) & 0xf); + } + if (N%2) { + buffer << bcd2text((_payload[N/2]>>0) & 0xf); + } + return buffer.str(); +} + +std::string +POCSAG::Message::asHex() const { + std::stringstream buffer; + buffer << std::hex; + for (size_t i=0; i<_payload.size(); i++) { + buffer << int(_payload[i]); + } + return buffer.str(); +} + +int +POCSAG::Message::estimateText() const { + int weight = 0; + uint8_t byte = 0; + for (size_t i=0; i<_bits; i++) { + size_t byteIdx = i/8; + size_t bitIdx = (7-i%8); + // add bit to byte + byte = ((byte>>1) | (((_payload[byteIdx]>>bitIdx) & 0x01) << 6)); + if (6 == i%7) { + weight += pocsag_text_weight(byte&0x7f); + } + } + return weight; +} + +int +POCSAG::Message::estimateNumeric() const { + int weight = 0; + // Get number of complete half-bytes in payload + size_t N = _bits/4; + for (size_t i=0; i<(N/2); i++) { + weight += pocsag_numeric_weight(bcd2text((_payload[i]>>4) & 0xf), i); + weight += pocsag_numeric_weight(bcd2text((_payload[i]>>0) & 0xf), i); + } + if (N%2) { + weight += pocsag_numeric_weight(bcd2text((_payload[N/2]>>0) & 0xf), N/2); + } + return weight; +} diff --git a/src/pocsag.hh b/src/pocsag.hh index 8def917..158ff25 100644 --- a/src/pocsag.hh +++ b/src/pocsag.hh @@ -13,93 +13,126 @@ namespace sdr { * * 1) at least 576 bits of alternating value (1 0 1 0 ...) * 2) a 32-bit sync word (0x7CD215D8) - * 3) 16 data words (each 32 bit) + * 3) 2x8 data words (each 32 bit) + * 4) If data left to send -> continue with step 2 * * Unused data words are send as 0x7A89C197. Each dataword is either a address word (bit 31 = 0) * or message word (bit 31 = 1). * - * Address word: - * - * +---+---+---+---+---+---+---+---+ - * | 0 | Address (18bits) | - * +---+---+---+---+---+---+---+---+ - * | ... | - * +---+---+---+---+---+---+---+---+ - * | ... | F1 F0 | ECC ... | - * +---+---+---+---+---+---+---+---+ - * | ... (10 bits) | P | - * +---+---+---+---+---+---+---+---+ - * - * Message word - * - * +---+---+---+---+---+---+---+---+ - * | 0 | Data (20bits) | - * +---+---+---+---+---+---+---+---+ - * | ... | - * +---+---+---+---+---+---+---+---+ - * | ... | ECC ... | - * +---+---+---+---+---+---+---+---+ - * | ... (10 bits) | P | - * +---+---+---+---+---+---+---+---+ - * * @ingroup datanodes */ class POCSAG: public Sink { public: + /** A posac message. */ class Message { public: + /** Empty constructor. */ Message(); + /** Constructor from address and function. */ Message(uint32_t addr, uint8_t func); + /** Copy constructor. */ Message(const Message &other); + /** Assignment operator. */ Message &operator=(const Message &other); + /** Retruns @c true if the message is empty (has no address). */ inline bool isEmpty() const { return _empty; } + /** Returns the address of the message. */ inline uint32_t address() const { return _address; } + /** Returns the function of the message. */ inline uint8_t function() const { return _function; } + /** Returns the number of data bits. */ inline uint32_t bits() const { return _bits; } + + /** Adds some payload from the given POGSAC word. */ void addPayload(uint32_t word); + int estimateText() const; + int estimateNumeric() const; + + /** Decodes the message as a text message. */ + std::string asText() const; + /** Decodes the message as a numeric message. */ + std::string asNumeric() const; + /** Dumps the payload. */ + std::string asHex() const; + protected: + /** The address of the message. */ uint32_t _address; + /** The function of the message. */ uint8_t _function; + /** If @c true the message is empty. */ bool _empty; + /** The number of payload bits in the message. */ uint32_t _bits; + /** The actual payload. */ std::vector _payload; }; protected: + /** The possible states of the POGSAC receiver. */ typedef enum { - WAIT, - RECEIVE, - CHECK_CONTINUE + WAIT, ///< Wait for a sync word. + RECEIVE, ///< Receive data. + CHECK_CONTINUE ///< Wait for the sync word for continuation. } State; public: + /** Constructor. */ POCSAG(); void config(const Config &src_cfg); void process(const Buffer &buffer, bool allow_overwrite); + /** Can be overwritten by any other implementation to process the received messages + * stored in @c _queue. */ virtual void handleMessages(); protected: + /** Process a POGSAC word. */ void _process_word(uint32_t word); - void _reset_all_messages(); - void _reset_message(uint8_t slot); - void _finish_all_messages(); - void _finish_message(uint8_t slot); + /** Clear the message. */ + void _reset_message(); + /** Add the (non-empty) message to the queue. */ + void _finish_message(); protected: + /** The current state. */ State _state; + /** The last received bits. */ uint64_t _bits; + /** The number of received bits. */ uint8_t _bitcount; + /** The current slot. */ uint8_t _slot; - - Message _messages[8]; + /** The current message. */ + Message _message; + /** The completed messages. */ std::list _queue; }; + +/** A simple extention of the @c POCSAG node that prints the received messages to a + * @c std::ostream. + * @ingroup datanodes */ +class POCSAGDump: public POCSAG +{ +public: + /** Constructor. + * @param stream Specifies the stream, the received messages are serialized into. */ + POCSAGDump(std::ostream &stream); + + /** Dumps the received messages. */ + void handleMessages(); + +protected: + /** The output stream. */ + std::ostream &_stream; +}; + + } -#endif // POSAG_HH +#endif // __SDR_POSAG_HH__ diff --git a/src/sdr.hh b/src/sdr.hh index 3eaa227..a7d4d6a 100644 --- a/src/sdr.hh +++ b/src/sdr.hh @@ -290,9 +290,10 @@ #include "demod.hh" #include "psk31.hh" -#include "afsk.hh" +#include "fsk.hh" #include "baudot.hh" #include "ax25.hh" +#include "pocsag.hh" #include "fftplan.hh"