mirror of https://github.com/hmatuschek/libsdr
Compare commits
21 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
7cfba39a23 | 11 years ago |
|
|
589f2e5d7c | 11 years ago |
|
|
5ded7135d0 | 11 years ago |
|
|
23d2e122bd | 11 years ago |
|
|
4d79c7e1e4 | 11 years ago |
|
|
a3a6165ede | 11 years ago |
|
|
7c5cfb2019 | 11 years ago |
|
|
28b907dd9a | 11 years ago |
|
|
907527a7f9 | 11 years ago |
|
|
36ac8d3cc1 | 11 years ago |
|
|
acee3dc318 | 11 years ago |
|
|
40e384772a | 11 years ago |
|
|
00c21e6b6e | 11 years ago |
|
|
0ad4ec8634 | 11 years ago |
|
|
0b7bc758d5 | 11 years ago |
|
|
71e3d29d69 | 11 years ago |
|
|
6dde3c11c7 | 11 years ago |
|
|
fca04605f3 | 11 years ago |
|
|
97e42901c4 | 11 years ago |
|
|
0cf7570624 | 11 years ago |
|
|
2afdd5060c | 11 years ago |
@ -0,0 +1,7 @@
|
||||
macro(link_resources HEADER_NAME)
|
||||
#create_resources("${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" ${ARGN})
|
||||
add_custom_target(${HEADER_NAME} ALL
|
||||
COMMAND ${CMAKE_COMMAND} -DOUTPUT="${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME}.hh" -DRESOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}" -DFILES="${ARGN}" -P "${PROJECT_SOURCE_DIR}/cmake/create_resources.cmake"
|
||||
DEPENDS ${ARGN} SOURCES ${ARGN})
|
||||
endmacro(link_resources)
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
#
|
||||
# Implements the packing of resource files into a header file
|
||||
#
|
||||
get_filename_component(OUTPUT_FILE ${OUTPUT} NAME)
|
||||
message(STATUS "Generate resource file '${OUTPUT_FILE}' from: ${FILES}")
|
||||
# Create empty file
|
||||
file(WRITE ${OUTPUT} "")
|
||||
# For each resource file
|
||||
foreach(file ${FILES})
|
||||
# Normalize filename
|
||||
string(REGEX MATCH "([^/]+)$" filename ${file})
|
||||
string(REGEX REPLACE "\\.| " "_" filename ${filename})
|
||||
# Read and convert file content
|
||||
file(READ "${RESOURCE_PATH}/${file}" filedata HEX)
|
||||
string(REGEX REPLACE "([0-9a-fA-F][0-9a-fA-F])" "0x\\1," filedata ${filedata})
|
||||
# Update output file
|
||||
file(APPEND ${OUTPUT}
|
||||
"extern \"C\" {\n"
|
||||
" static char ${filename}[] = {${filedata}};\n"
|
||||
" static unsigned ${filename}_size = sizeof(${filename});\n"
|
||||
"}\n")
|
||||
endforeach()
|
||||
@ -0,0 +1,8 @@
|
||||
|
||||
# Creates a header file including the shared resources
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
|
||||
link_resources(sdr_cmd_resources shared/index.html)
|
||||
|
||||
add_executable(sdr_cmd main.cc aprsapplication.cc)
|
||||
add_dependencies(sdr_cmd sdr_cmd_resources)
|
||||
target_link_libraries(sdr_cmd ${LIBS} libsdr )
|
||||
@ -0,0 +1,87 @@
|
||||
#include "aprsapplication.hh"
|
||||
|
||||
// This file will be generated at build time
|
||||
// and contains the static content served.
|
||||
#include "sdr_cmd_resources.hh"
|
||||
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
APRSApplication::APRSApplication(http::Server &server)
|
||||
: APRS(), _server(server), _messages()
|
||||
{
|
||||
// Register callbacks
|
||||
server.addStatic("/", std::string(index_html, index_html_size), "text/html");
|
||||
server.addJSON("/spots", this, &APRSApplication::spots);
|
||||
server.addHandler("/update", this, &APRSApplication::update);
|
||||
}
|
||||
|
||||
APRSApplication::~APRSApplication() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
APRSApplication::spots(const http::JSON &request, http::JSON &response) {
|
||||
std::list<http::JSON> msg_list;
|
||||
for (std::list<Message>::iterator msg = _messages.begin(); msg != _messages.end(); msg++) {
|
||||
std::map<std::string, http::JSON> message;
|
||||
message["call"] = http::JSON(msg->from().call());
|
||||
if (msg->hasLocation()) {
|
||||
message["lat"] = http::JSON(msg->latitude());
|
||||
message["lon"] = http::JSON(msg->longitude());
|
||||
}
|
||||
time_t time = msg->time();
|
||||
message["time"] = http::JSON(ctime(&time));
|
||||
msg_list.push_back(message);
|
||||
}
|
||||
response = http::JSON(msg_list);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
APRSApplication::handleAPRSMessage(const Message &message) {
|
||||
_messages.push_back(message);
|
||||
// Serialize JSON message
|
||||
std::string json_text;
|
||||
if (_clients.size()) {
|
||||
std::map<std::string, http::JSON> msg;
|
||||
msg["call"] = http::JSON(message.from().call());
|
||||
if (message.hasLocation()) {
|
||||
msg["lat"] = http::JSON(message.latitude());
|
||||
msg["lon"] = http::JSON(message.longitude());
|
||||
}
|
||||
time_t time = message.time();
|
||||
msg["time"] = http::JSON(ctime(&time));
|
||||
http::JSON(msg).serialize(json_text);
|
||||
}
|
||||
// a list collecting the closed
|
||||
// signal all clients connected
|
||||
std::list<http::Connection>::iterator client = _clients.begin();
|
||||
while (client != _clients.end()) {
|
||||
if (client->isClosed()) {
|
||||
// remove client from list
|
||||
client = _clients.erase(client);
|
||||
} else {
|
||||
// send event
|
||||
client->send("data: ");
|
||||
client->send(json_text);
|
||||
client->send("\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
APRSApplication::update(const http::Request &request, http::Response &response) {
|
||||
// This call back implements a server side event stream, means the response will
|
||||
// be blocked until the connection is closed
|
||||
response.setHeader("Content-Type", "text/event-stream");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setStatus(http::Response::STATUS_OK);
|
||||
response.sendHeaders();
|
||||
// Signal connection thread to exit without closing the connection
|
||||
response.connection().setProtocolUpgrade();
|
||||
// Store connection
|
||||
_clients.push_back(response.connection());
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
#ifndef __SDR_APRS_APRSAPPLICATION_HH__
|
||||
#define __SDR_APRS_APRSAPPLICATION_HH__
|
||||
|
||||
#include "http.hh"
|
||||
#include "aprs.hh"
|
||||
|
||||
|
||||
namespace sdr {
|
||||
|
||||
class APRSApplication: public APRS
|
||||
{
|
||||
public:
|
||||
APRSApplication(http::Server &server);
|
||||
~APRSApplication();
|
||||
|
||||
bool spots(const http::JSON &request, http::JSON &response);
|
||||
void update(const http::Request &request, http::Response &response);
|
||||
|
||||
void handleAPRSMessage(const Message &message);
|
||||
|
||||
protected:
|
||||
http::Server &_server;
|
||||
std::list<Message> _messages;
|
||||
std::list<http::Connection> _clients;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // APRSAPPLICATION_HH
|
||||
@ -0,0 +1,36 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "queue.hh"
|
||||
#include "logger.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <csignal>
|
||||
|
||||
#include "aprsapplication.hh"
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
|
||||
static http::Server *server = 0;
|
||||
|
||||
static void __sigint_handler(int signo) {
|
||||
if (server) { server->stop(true); }
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
server = new http::Server(8080);
|
||||
APRSApplication app(*server);
|
||||
|
||||
// Install log handler
|
||||
sdr::Logger::get().addHandler(
|
||||
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
|
||||
|
||||
// Register signal handler:
|
||||
signal(SIGINT, __sigint_handler);
|
||||
|
||||
// start server
|
||||
server->start(true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>sdr-aprs — An APRS receiver using libsdr.</title>
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
html, body, #map-canvas {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px
|
||||
}
|
||||
</style>
|
||||
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
|
||||
|
||||
<script>
|
||||
var map = null;
|
||||
var spots = {};
|
||||
var source = null;
|
||||
|
||||
function onAddSpot (callsign, lon, lat) {
|
||||
// Check if connection exists
|
||||
if (callsign in spots) {
|
||||
// If connection exists, update SNR
|
||||
connections[spots].marker.setPosition(new google.maps.LatLng(lat, lon));
|
||||
} else {
|
||||
// otherwise, add marker at location with label callsign
|
||||
var marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(lat, lon), title: callsign, map: map});
|
||||
spots[callsign] = { marker: marker };
|
||||
}
|
||||
}
|
||||
|
||||
function updateHandler(e) {
|
||||
var msg = JSON.parse(e.data);
|
||||
console.log("RX: " + msg)
|
||||
onAddSpot(msg.call, msg.lon, msg.lat);
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
// Create Map object
|
||||
map = new google.maps.Map(
|
||||
document.getElementById('map-canvas'),
|
||||
{zoom:2, center: new google.maps.LatLng(0,0), streetViewControl:false});
|
||||
|
||||
// Request all spots
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
var url = "spots";
|
||||
xmlhttp.onreadystatechange = function() {
|
||||
if (xmlhttp.readyState != 4 && xmlhttp.status != 200) { return; }
|
||||
var spots = JSON.parse(xmlhttp.responseText);
|
||||
for (spot in spots) { onAddSpot(spot); }
|
||||
}
|
||||
xmlhttp.open("GET", url, true);
|
||||
xmlhttp.send();
|
||||
|
||||
// Now, connect to event source for updates
|
||||
if (!!window.EventSource) {
|
||||
source = new window.EventSource("update");
|
||||
source.addEventListener('message', updateHandler);
|
||||
} else {
|
||||
alert("Your browser does not support the EventSource -> no live update.")
|
||||
}
|
||||
}
|
||||
|
||||
google.maps.event.addDomListener(window, 'load', initialize);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="map-canvas">
|
||||
<p>Can not load Google Maps view. Check your internet conenction!</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,21 +1,25 @@
|
||||
Source: libsdr
|
||||
Priority: extra
|
||||
Maintainer: Hannes Matuschek <hmatuschek@gmail.com>
|
||||
Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 8.0.0), cmake, qtbase5-dev (>= 5.0), portaudio19-dev, librtlsdr-dev, fftw3-dev
|
||||
Standards-Version: 3.9.2
|
||||
Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 9.0.0), cmake, portaudio19-dev, librtlsdr-dev, fftw3-dev
|
||||
Standards-Version: 3.9.5
|
||||
Section: hamradio
|
||||
Homepage: http://github.com/hmatuschek/libsdr
|
||||
|
||||
Package: libsdr
|
||||
Package: libsdr1
|
||||
Section: hamradio
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Multi-Arch: same
|
||||
Pre-Depends: multiarch-support, ${misc:Pre-Depends}
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libportaudio2
|
||||
Description: libsdr
|
||||
A C++ library for software defined radio.
|
||||
|
||||
Package: libsdr-dev
|
||||
Section: hamradio
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libsdr
|
||||
Multi-Arch: same
|
||||
Pre-Depends: multiarch-support, ${misc:Pre-Depends}
|
||||
Depends: libsdr1 (= ${binary:Version}), ${misc:Depends}, portaudio19-dev, librtlsdr-dev, fftw3-dev, libsdr1
|
||||
Description: libsdr
|
||||
A C++ library for software defined radio. Development files.
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
#! /usr/bin/dh-exec
|
||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr.so
|
||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr-gui.so
|
||||
/usr/include/*
|
||||
usr/lib/*/libsdr.so
|
||||
usr/include/*
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#! /usr/bin/dh-exec
|
||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr.so.*
|
||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr-gui.so.*
|
||||
@ -0,0 +1,2 @@
|
||||
#! /usr/bin/dh-exec
|
||||
usr/lib/*/libsdr.so.*
|
||||
@ -1,358 +0,0 @@
|
||||
#include "wavfile.hh"
|
||||
#include "autocast.hh"
|
||||
#include "interpolate.hh"
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
static const uint16_t crc_ccitt_table[] = {
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||
};
|
||||
|
||||
static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
|
||||
{
|
||||
uint32_t crc = 0xffff;
|
||||
for (; cnt > 0; cnt--, buf++) {
|
||||
crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff];
|
||||
}
|
||||
return (crc & 0xffff) == 0xf0b8;
|
||||
}
|
||||
|
||||
|
||||
class AFSK: public Sink<int16_t>, public Source {
|
||||
public:
|
||||
AFSK(double baud=1200.0, double Fmark=1200.0, double Fspace=2200.0)
|
||||
: Sink<int16_t>(), Source(), _baud(baud), _Fmark(Fmark), _Fspace(Fspace)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
virtual ~AFSK() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
virtual void config(const Config &src_cfg) {
|
||||
// Check if config is complete
|
||||
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
|
||||
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<int16_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure AFSK1200: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<int16_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// The input sample rate
|
||||
_sampleRate = src_cfg.sampleRate();
|
||||
|
||||
// Samples per bit
|
||||
_corrLen = int(_sampleRate/_baud);
|
||||
// Compute symbol rate:
|
||||
_symbolRate = std::min(10*_baud, _baud*_corrLen);
|
||||
|
||||
// Samples per symbol (fractional):
|
||||
_mu = 0.0; _muIncr = _sampleRate/_symbolRate;
|
||||
// Delayline for interpolating sub-sampler
|
||||
_dl = Buffer<float>(2*8);
|
||||
for (size_t i=0; i<(2*8); i++) { _dl[i] = 0; }
|
||||
_dl_idx = 0;
|
||||
|
||||
// Assemble phase LUT:
|
||||
_markLUT = Buffer< std::complex<float> >(_corrLen);
|
||||
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
|
||||
|
||||
// Allocate ring-buffers for mark and space symbols
|
||||
_markHist = Buffer< std::complex<float> >(_corrLen);
|
||||
_spaceHist = Buffer< std::complex<float> >(_corrLen);
|
||||
|
||||
// Initialize LUTs and ring-buffers
|
||||
double phiMark=0, phiSpace=0;
|
||||
for (size_t i=0; i<_corrLen; i++) {
|
||||
_markLUT[i] = std::exp(std::complex<float>(0.0, phiMark));
|
||||
_spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace));
|
||||
phiMark += (2.*M_PI*_Fmark)/_sampleRate;
|
||||
phiSpace += (2.*M_PI*_Fspace)/_sampleRate;
|
||||
_markHist[i] = 0; _spaceHist[i] = 0;
|
||||
}
|
||||
_lutIdx = 0;
|
||||
|
||||
// Get phase increment per symbol
|
||||
_phase = 0; _omega = _baud/_symbolRate;
|
||||
_omegaMin = _omega - 0.01*_omega;
|
||||
_omegaMax = _omega + 0.01*_omega;
|
||||
_gainOmega = 0.01;
|
||||
|
||||
// Allocate output buffer:
|
||||
_buffer = Buffer<uint8_t>(src_cfg.bufferSize()/_corrLen + 1);
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config AFSK1200 node: " << std::endl
|
||||
<< " input sample rate: " << _sampleRate << "Hz" << std::endl
|
||||
<< " samples per symbol: " << _muIncr << std::endl
|
||||
<< " symbols per bit: " << _corrLen << std::endl
|
||||
<< " symbol rate: " << _symbolRate << "Hz" << std::endl
|
||||
<< " Phase incr/symbol: " << float(_omega);
|
||||
|
||||
Logger::get().log(msg);
|
||||
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1));
|
||||
}
|
||||
|
||||
|
||||
virtual void process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
|
||||
size_t i=0, o=0;
|
||||
while (i<buffer.size()) {
|
||||
// Update sub-sampler
|
||||
while ((_mu>1) && (i<buffer.size())) {
|
||||
_markHist[_lutIdx] = float(buffer[i])*_markLUT[_lutIdx];
|
||||
_spaceHist[_lutIdx] = float(buffer[i])*_spaceLUT[_lutIdx];
|
||||
// Modulo LUT length
|
||||
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
|
||||
float symbol = _getSymbol();
|
||||
// Put symbol into delay line
|
||||
_dl[_dl_idx] = symbol; _dl[_dl_idx+8] = symbol;
|
||||
_dl_idx = (_dl_idx+1)%8; _mu -= 1; i++;
|
||||
}
|
||||
|
||||
|
||||
if (i<buffer.size()) {
|
||||
// Get interpolated symbol
|
||||
float sample = interpolate(_dl.sub(_dl_idx, 8), _mu); _mu += _muIncr;
|
||||
// Get symbol
|
||||
_symbols <<= 1; _symbols |= (sample>0);
|
||||
// Advance phase
|
||||
_phase += _omega;
|
||||
|
||||
// Sample bit
|
||||
if (_phase >= 1) {
|
||||
// Modulo "2 pi"
|
||||
_phase = fmodf(_phase, 1.0);
|
||||
// Store bit
|
||||
_lastBits <<= 1; _lastBits |= (_symbols & 1);
|
||||
// Put decoded bit in output buffer
|
||||
// transition -> 0; no transition -> 1
|
||||
_buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 1) & 1);
|
||||
}
|
||||
|
||||
// If transition
|
||||
if ((_symbols ^ (_symbols >> 1)) & 1) {
|
||||
// Phase correction
|
||||
/*std::cerr << "Transition at phi=" << _phase << std::endl
|
||||
<< " update omega from " << _omega << " to "; */
|
||||
if (_phase < 0.5) { _omega -= _gainOmega*(_phase); }
|
||||
else { _omega += _gainOmega*(1-_phase); }
|
||||
// Limit omega
|
||||
_omega = std::min(_omegaMax, std::max(_omegaMin, _omega));
|
||||
//std::cerr << _omega << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->send(_buffer.head(o));
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
inline double _getSymbol() {
|
||||
std::complex<double> markSum(0), spaceSum(0);
|
||||
for (size_t i=0; i<_corrLen; i++) {
|
||||
markSum += _markHist[i];
|
||||
spaceSum += _spaceHist[i];
|
||||
}
|
||||
double f = markSum.real()*markSum.real() +
|
||||
markSum.imag()*markSum.imag() -
|
||||
spaceSum.real()*spaceSum.real() -
|
||||
spaceSum.imag()*spaceSum.imag();
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
float _sampleRate, _symbolRate;
|
||||
float _baud;
|
||||
float _Fmark, _Fspace;
|
||||
|
||||
uint32_t _corrLen;
|
||||
uint32_t _lutIdx;
|
||||
Buffer< std::complex<float> > _markLUT;
|
||||
Buffer< std::complex<float> > _spaceLUT;
|
||||
|
||||
float _mu, _muIncr;
|
||||
Buffer< float > _dl;
|
||||
size_t _dl_idx;
|
||||
|
||||
Buffer< std::complex<float> > _markHist;
|
||||
Buffer< std::complex<float> > _spaceHist;
|
||||
|
||||
uint32_t _symbols;
|
||||
uint32_t _lastBits;
|
||||
float _phase;
|
||||
float _omega, _omegaMin, _omegaMax;
|
||||
float _gainOmega;
|
||||
|
||||
static const uint32_t _phasePeriod = 0x10000u;
|
||||
static const uint32_t _phaseMask = 0x0ffffu;
|
||||
|
||||
/** Output buffer. */
|
||||
Buffer<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
class AX25: public Sink<uint8_t>, public Source
|
||||
{
|
||||
public:
|
||||
AX25()
|
||||
: Sink<uint8_t>(), Source()
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
virtual ~AX25() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
virtual void config(const Config &src_cfg) {
|
||||
if (! src_cfg.hasType()) { return; }
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure AX25: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<uint8_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
_bitstream = 0;
|
||||
_bitbuffer = 0;
|
||||
_state = 0;
|
||||
_ptr = _rxbuffer;
|
||||
|
||||
// Allocate output buffer
|
||||
_buffer = Buffer<uint8_t>(512);
|
||||
|
||||
// propergate config
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, 512, 1));
|
||||
}
|
||||
|
||||
virtual void process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
// Store bit in stream
|
||||
_bitstream <<= 1; _bitstream |= !!buffer[i];
|
||||
|
||||
// Check for sync byte
|
||||
if ((_bitstream & 0xff) == 0x7e) {
|
||||
if (_state && ((_ptr - _rxbuffer) > 2)) {
|
||||
*_ptr = 0;
|
||||
if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) {
|
||||
std::cerr << "Got invalid buffer: " << _rxbuffer << std::endl;
|
||||
} else {
|
||||
std::cerr << "GOT: " << _rxbuffer << std::endl;
|
||||
memcpy(_buffer.ptr(), _rxbuffer, _ptr-_rxbuffer);
|
||||
this->send(_buffer.head(_ptr-_rxbuffer));
|
||||
}
|
||||
|
||||
}
|
||||
_state = 1;
|
||||
_ptr = _rxbuffer;
|
||||
_bitbuffer = 0x80;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If 7 ones are received in a row -> error, wait or sync byte
|
||||
if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; }
|
||||
|
||||
// If state == wait for sync byte -> receive next bit
|
||||
if (!_state) { continue; }
|
||||
|
||||
/* stuffed bit */
|
||||
if ((_bitstream & 0x3f) == 0x3e) { continue; }
|
||||
|
||||
// prepend bit to bitbuffer
|
||||
_bitbuffer |= ((_bitstream & 1) << 8);
|
||||
|
||||
// If 8 bits have been received (stored in b8-b1 of _bitbuffer)
|
||||
if (_bitbuffer & 1) {
|
||||
// Check for buffer overrun
|
||||
if ((_ptr-_rxbuffer) >= 512) {
|
||||
Logger::get().log(LogMessage(LOG_ERROR, "AX.25 packet too long."));
|
||||
// Wait for next sync byte
|
||||
_state = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store received byte and ...
|
||||
*_ptr++ = (_bitbuffer >> 1);
|
||||
// reset bit buffer
|
||||
_bitbuffer = 0x80;
|
||||
continue;
|
||||
}
|
||||
// Shift bitbuffer one to the left
|
||||
_bitbuffer >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t _bitstream;
|
||||
uint32_t _bitbuffer;
|
||||
uint32_t _state;
|
||||
|
||||
uint8_t _rxbuffer[512];
|
||||
uint8_t *_ptr;
|
||||
|
||||
Buffer<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
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));
|
||||
|
||||
WavSource src(argv[1], 1024);
|
||||
if (! src.isOpen()) { std::cout << "Can not open file " << argv[1] << std::endl; return -1; }
|
||||
|
||||
AutoCast< int16_t > cast;
|
||||
AFSK demod;
|
||||
AX25 decode;
|
||||
|
||||
src.connect(&cast);
|
||||
cast.connect(&demod);
|
||||
demod.connect(&decode);
|
||||
|
||||
Queue::get().addIdle(&src, &WavSource::next);
|
||||
|
||||
Queue::get().start();
|
||||
Queue::get().wait();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* sdr_ax25 -- A AX.25 and APRS 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 "options.hh"
|
||||
#include "autocast.hh"
|
||||
#include "rtlsource.hh"
|
||||
#include "baseband.hh"
|
||||
#include "demod.hh"
|
||||
#include "portaudio.hh"
|
||||
#include "wavfile.hh"
|
||||
#include "fsk.hh"
|
||||
#include "utils.hh"
|
||||
#include "aprs.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
// On SIGINT -> stop queue properly
|
||||
static void __sigint_handler(int signo) {
|
||||
Queue::get().stop();
|
||||
}
|
||||
|
||||
// Command line options
|
||||
static Options::Definition options[] = {
|
||||
{"frequency", 'F', Options::FLOAT,
|
||||
"Selects a RTL2832 as the source and specifies the frequency in Hz."},
|
||||
{"correction", 0, Options::FLOAT,
|
||||
"Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."},
|
||||
{"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."},
|
||||
{"help", 0, Options::FLAG, "Prints this help message."},
|
||||
{0,0,Options::FLAG,0}
|
||||
};
|
||||
|
||||
|
||||
void print_help() {
|
||||
std::cerr << "USAGE: sdr_ax25 SOURCE [OPTIONS]" << std::endl << std::endl;
|
||||
Options::print_help(std::cerr, options);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Install log handler
|
||||
sdr::Logger::get().addHandler(
|
||||
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
|
||||
|
||||
// Register signal handler:
|
||||
signal(SIGINT, __sigint_handler);
|
||||
|
||||
// Parse command line options.
|
||||
Options opts;
|
||||
if (! Options::parse(options, argc, argv, opts)) {
|
||||
print_help(); return -1;
|
||||
}
|
||||
|
||||
// If help flag is present -> print and done.
|
||||
if (opts.has("help")) { print_help(); return 0; }
|
||||
|
||||
// If no source has been selected
|
||||
if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) {
|
||||
print_help(); return -1;
|
||||
}
|
||||
|
||||
// Init audio system
|
||||
PortAudio::init();
|
||||
|
||||
// Get the global queue
|
||||
Queue &queue = Queue::get();
|
||||
|
||||
// pointer to the selected source
|
||||
Source *src = 0;
|
||||
|
||||
// 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());
|
||||
if (opts.has("correction")) {
|
||||
// Apply specified frequency correction.
|
||||
rtl_source->setFreqCorrection(opts.get("correction").toFloat());
|
||||
}
|
||||
rtl_cast = new AutoCast< std::complex<int16_t> >();
|
||||
rtl_baseband = new IQBaseBand<int16_t>(0, 15.0e3, 21, 0, 22050.0);
|
||||
rtl_demod = new FMDemod<int16_t>();
|
||||
rtl_deemph = new FMDeemph<int16_t>();
|
||||
// Connect nodes
|
||||
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. */
|
||||
|
||||
// (A)FSK detector
|
||||
FSKDetector detector(1200, 1200, 2200);
|
||||
// Bit decoder
|
||||
BitStream bits(1200, BitStream::TRANSITION);
|
||||
// APRS decoder
|
||||
APRS aprs;
|
||||
// Audio sink for monitor
|
||||
PortSink sink;
|
||||
|
||||
// connect source ASK detector
|
||||
src->connect(&detector);
|
||||
// detector to bit decoder
|
||||
detector.connect(&bits);
|
||||
// and bit decoder to APRS decoder and print
|
||||
bits.connect(&aprs);
|
||||
|
||||
// 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();
|
||||
|
||||
// quit.
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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>
|
||||
#include <csignal>
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
// On SIGINT -> stop queue properly
|
||||
static void __sigint_handler(int signo) {
|
||||
Queue::get().stop();
|
||||
}
|
||||
|
||||
// Command line options
|
||||
static Options::Definition options[] = {
|
||||
{"frequency", 'F', Options::FLOAT,
|
||||
"Selects a RTL2832 as the source and specifies the frequency in Hz."},
|
||||
{"correction", 0, Options::FLOAT,
|
||||
"Specifies the frequency correction for the RTL2832 device in parts-per-million (ppm)."},
|
||||
{"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}
|
||||
};
|
||||
|
||||
void print_help() {
|
||||
std::cerr << "USAGE: sdr_pocsag SOURCE [OPTIONS]" << std::endl << std::endl;
|
||||
Options::print_help(std::cerr, options);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Install log handler
|
||||
sdr::Logger::get().addHandler(
|
||||
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
|
||||
|
||||
// Register signal handler:
|
||||
signal(SIGINT, __sigint_handler);
|
||||
|
||||
// Parse command line options.
|
||||
Options opts;
|
||||
if (! Options::parse(options, argc, argv, opts)) {
|
||||
print_help(); return -1;
|
||||
}
|
||||
|
||||
if (opts.has("help")) {
|
||||
print_help(); return 0;
|
||||
}
|
||||
|
||||
// If no source has been selected
|
||||
if (! (opts.has("frequency")|opts.has("audio")|opts.has("file"))) {
|
||||
print_help(); return -1;
|
||||
}
|
||||
|
||||
// Init audio system
|
||||
PortAudio::init();
|
||||
|
||||
// Get the global queue
|
||||
Queue &queue = Queue::get();
|
||||
|
||||
// pointer to the selected source
|
||||
Source *src = 0;
|
||||
|
||||
// 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());
|
||||
if (opts.has("correction")) {
|
||||
rtl_source->setFreqCorrection(opts.get("correction").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);
|
||||
// and bit decoder to POCSAG decoder and print
|
||||
bits.connect(&pocsag);
|
||||
|
||||
// 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();
|
||||
|
||||
// quit.
|
||||
return 0;
|
||||
}
|
||||
@ -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 <iostream>
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
|
||||
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<int16_t> > cast;
|
||||
IQBaseBand<int16_t> baseband(0, 2144., 200.0, 63, 1);
|
||||
baseband.setCenterFrequency(2144.0);
|
||||
BPSK31<int16_t> demod;
|
||||
PortSink sink;
|
||||
Varicode decode;
|
||||
DebugDump<uint8_t> 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<double>::next);
|
||||
|
||||
queue.start();
|
||||
app.exec();
|
||||
queue.stop();
|
||||
queue.wait();
|
||||
|
||||
PortAudio::terminate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
#include "sdr.hh"
|
||||
#include "rtlsource.hh"
|
||||
#include "baseband.hh"
|
||||
#include "autocast.hh"
|
||||
#include "gui/gui.hh"
|
||||
#include "logger.hh"
|
||||
#include <signal.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
|
||||
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<int16_t> > cast;
|
||||
IQBaseBand<int16_t> baseband(0, 200e3, 16, 5);
|
||||
FMDemod<int16_t, int16_t> demod;
|
||||
BaseBand<int16_t> mono(0, 15e3, 16, 6);
|
||||
BaseBand<int16_t> pilot(19e3, 5e2, 16, 84);
|
||||
BaseBand<int16_t> 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;
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
#include "autocast.hh"
|
||||
#include "portaudio.hh"
|
||||
#include "wavfile.hh"
|
||||
#include "fsk.hh"
|
||||
#include "baudot.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
|
||||
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_rtty FILENAME" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sdr::Logger::get().addHandler(
|
||||
new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
|
||||
|
||||
// Register handler:
|
||||
signal(SIGINT, __sigint_handler);
|
||||
|
||||
PortAudio::init();
|
||||
|
||||
Queue &queue = Queue::get();
|
||||
|
||||
WavSource src(argv[1]);
|
||||
PortSink sink;
|
||||
AutoCast<int16_t> cast;
|
||||
FSKDetector fsk(90.90, 930., 1100.);
|
||||
BitStream bits(90.90, BitStream::NORMAL);
|
||||
Baudot decoder;
|
||||
TextDump dump;
|
||||
|
||||
// Playback
|
||||
src.connect(&sink);
|
||||
// Cast to int16
|
||||
src.connect(&cast);
|
||||
// FSK demod
|
||||
cast.connect(&fsk);
|
||||
fsk.connect(&bits);
|
||||
// Baudot decoder
|
||||
bits.connect(&decoder);
|
||||
// dump to std::cerr
|
||||
decoder.connect(&dump);
|
||||
|
||||
// on idle -> read next buffer from input file
|
||||
queue.addIdle(&src, &WavSource::next);
|
||||
// on end-of-file -> stop queue
|
||||
src.addEOS(&queue, &Queue::stop);
|
||||
|
||||
// Start queue
|
||||
queue.start();
|
||||
// wait for queue to exit
|
||||
queue.wait();
|
||||
|
||||
// terminate port audio system properly
|
||||
PortAudio::terminate();
|
||||
|
||||
// quit.
|
||||
return 0;
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
#include "sdr.hh"
|
||||
#include "rtlsource.hh"
|
||||
#include "baseband.hh"
|
||||
#include "utils.hh"
|
||||
#include "gui/gui.hh"
|
||||
#include <signal.h>
|
||||
#include "portaudio.hh"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
|
||||
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<int16_t> agc;
|
||||
//IQBaseBand<int16_t> baseband(0, 500e3, 8, 5);
|
||||
//AMDemod<int16_t> 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;
|
||||
}
|
||||
@ -0,0 +1,344 @@
|
||||
#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 && (! _readTime(offset))) { _hasTime = false; _hasLocation = false; return; }
|
||||
if (_hasLocation && (! _readLocation(offset))) { _hasLocation = false; return; }
|
||||
// Remaining text is comment
|
||||
if (offset < _payload.size()) {
|
||||
_comment = _payload.substr(offset);
|
||||
offset += _comment.size();
|
||||
}
|
||||
}
|
||||
|
||||
APRS::Message::Message(const Message &msg)
|
||||
: AX25::Message(msg), _hasLocation(msg._hasLocation), _latitude(msg._latitude),
|
||||
_longitude(msg._longitude), _symbol(msg._symbol), _hasTime(msg._hasTime),
|
||||
_time(msg._hasTime)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
|
||||
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__
|
||||
@ -0,0 +1,277 @@
|
||||
#include "ax25.hh"
|
||||
#include "logger.hh"
|
||||
#include "traits.hh"
|
||||
#include <ctime>
|
||||
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
|
||||
static const uint16_t crc_ccitt_table[] = {
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||
};
|
||||
|
||||
static inline bool check_crc_ccitt(const uint8_t *buf, int cnt)
|
||||
{
|
||||
uint32_t crc = 0xffff;
|
||||
for (; cnt > 0; cnt--, buf++) {
|
||||
crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ (*buf)) & 0xff];
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************************************************************** *
|
||||
* Implementation of AX25 decoder
|
||||
* ******************************************************************************************** */
|
||||
AX25::AX25() : Sink<uint8_t>()
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
AX25::~AX25() {
|
||||
// pass...
|
||||
}
|
||||
|
||||
void
|
||||
AX25::config(const Config &src_cfg) {
|
||||
if (! src_cfg.hasType()) { return; }
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure AX25: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<uint8_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
_bitstream = 0;
|
||||
_bitbuffer = 0;
|
||||
_state = 0;
|
||||
_ptr = _rxbuffer;
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config AX.25 node.";
|
||||
Logger::get().log(msg);
|
||||
}
|
||||
|
||||
void
|
||||
AX25::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
|
||||
{
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
// Store bit in stream
|
||||
_bitstream = ((_bitstream << 1) | (buffer[i] & 0x01));
|
||||
|
||||
// Check for sync byte
|
||||
if ((_bitstream & 0xff) == 0x7e) {
|
||||
if ((1==_state) && ((_ptr - _rxbuffer) > 2)) {
|
||||
*_ptr = 0;
|
||||
if (! check_crc_ccitt(_rxbuffer, _ptr-_rxbuffer)) {
|
||||
/*LogMessage msg(LOG_DEBUG);
|
||||
msg << "AX.25: Received invalid buffer: " << _rxbuffer;
|
||||
Logger::get().log(msg); */
|
||||
} else {
|
||||
// Assemble message
|
||||
Message msg(_rxbuffer, _ptr-_rxbuffer-2);
|
||||
this->handleAX25Message(msg);
|
||||
}
|
||||
}
|
||||
// Receive data
|
||||
_state = 1;
|
||||
_ptr = _rxbuffer;
|
||||
_bitbuffer = 0x80;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If 7 ones are received in a row -> error, wait or sync byte
|
||||
if ((_bitstream & 0x7f) == 0x7f) { _state = 0; continue; }
|
||||
|
||||
// If state == wait for sync byte -> receive next bit
|
||||
if (!_state) { continue; }
|
||||
|
||||
/* stuffed bit */
|
||||
if ((_bitstream & 0x3f) == 0x3e) { continue; }
|
||||
|
||||
// prepend bit to bitbuffer
|
||||
/// @todo Double check this!!!
|
||||
_bitbuffer |= ((_bitstream & 0x01) << 8);
|
||||
|
||||
// If 8 bits have been received (stored in b8-b1 of _bitbuffer)
|
||||
if (_bitbuffer & 0x01) {
|
||||
// Check for buffer overrun
|
||||
if ((_ptr-_rxbuffer) >= 512) {
|
||||
Logger::get().log(LogMessage(LOG_DEBUG, "AX.25 packet too long."));
|
||||
// Wait for next sync byte
|
||||
_state = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store received byte and ...
|
||||
*_ptr++ = (_bitbuffer >> 1);
|
||||
// reset bit buffer
|
||||
_bitbuffer = 0x80;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shift bitbuffer one to the left
|
||||
_bitbuffer >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
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<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;
|
||||
}
|
||||
|
||||
@ -0,0 +1,115 @@
|
||||
#ifndef __SDR_AX25_HH__
|
||||
#define __SDR_AX25_HH__
|
||||
|
||||
#include "node.hh"
|
||||
|
||||
namespace sdr {
|
||||
|
||||
/** Decodes AX25 (PacketRadio) messages from a bit stream.
|
||||
*
|
||||
* In conjecture with the (A)FSK demodulator, the AX25 can be used to receive packet radio or APRS
|
||||
* messages. AX25 is usually transmitted as FSK in transition mode, means the bits aren't
|
||||
* encoded by mark & space tones but rather as a transition from mark to space or in reverse. Hence
|
||||
* the FSK node needs to be configured in transition mode.
|
||||
*
|
||||
* The node does not process the actual AX.25 packages, it only checks the frame check sequence and
|
||||
* 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:
|
||||
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);
|
||||
|
||||
virtual void handleAX25Message(const Message &message);
|
||||
|
||||
protected:
|
||||
/** The last bits. */
|
||||
uint32_t _bitstream;
|
||||
/** A buffer of received bits. */
|
||||
uint32_t _bitbuffer;
|
||||
/** The current state. */
|
||||
uint32_t _state;
|
||||
|
||||
/** Message buffer. */
|
||||
uint8_t _rxbuffer[512];
|
||||
/** Insert-pointer to the buffer. */
|
||||
uint8_t *_ptr;
|
||||
};
|
||||
|
||||
|
||||
/** 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__
|
||||
@ -0,0 +1,111 @@
|
||||
#include "baudot.hh"
|
||||
#include "traits.hh"
|
||||
#include "logger.hh"
|
||||
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
// Baudot code tables
|
||||
char Baudot::_letter[32] = { 0, 'E','\n', 'A', ' ', 'S', 'I', 'U','\n', 'D', 'R', 'J', 'N', 'F',
|
||||
'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', 0,
|
||||
'M', 'X', 'V', 0};
|
||||
char Baudot::_figure[32] = { 0, '3','\n', '-', ' ','\a', '8', '7','\n', '?', '4','\'', ',', '!',
|
||||
':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', 0,
|
||||
'.', '/', ';', 0};
|
||||
|
||||
// Some special codes
|
||||
#define CHAR_NUL 0
|
||||
#define CHAR_STF 27
|
||||
#define CHAR_STL 31
|
||||
#define CHAR_SPA 4
|
||||
|
||||
|
||||
Baudot::Baudot(StopBits stopBits)
|
||||
: Sink<uint8_t>(), Source(), _mode(LETTERS)
|
||||
{
|
||||
switch (stopBits) {
|
||||
case STOP1:
|
||||
// Pattern xx11 xxxx xxxx xx00
|
||||
// Mask 0011 0000 0000 0011
|
||||
_stopHBits = 2;
|
||||
_bitsPerSymbol = 14;
|
||||
_pattern = 0x3000;
|
||||
_mask = 0x3003;
|
||||
break;
|
||||
case STOP15:
|
||||
// Pattern x11x xxxx xxxx x000
|
||||
// Mask 0110 0000 0000 0111
|
||||
_stopHBits = 3;
|
||||
_bitsPerSymbol = 15;
|
||||
_pattern = 0x6000;
|
||||
_mask = 0x6007;
|
||||
break;
|
||||
case STOP2:
|
||||
// Pattern 11xx xxxx xxxx 0000
|
||||
// Mask 1100 0000 0000 1111
|
||||
_stopHBits = 4;
|
||||
_bitsPerSymbol = 16;
|
||||
_pattern = 0xC000;
|
||||
_mask = 0xC00F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Baudot::config(const Config &src_cfg) {
|
||||
if (! src_cfg.hasType()) { return; }
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure Baudot: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<uint8_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Init (half) bit stream and counter
|
||||
_bitstream = 0;
|
||||
_bitcount = 0;
|
||||
|
||||
// Compute buffer size.
|
||||
size_t buffer_size = (src_cfg.bufferSize()/(2*_bitsPerSymbol))+1;
|
||||
_buffer = Buffer<uint8_t>(buffer_size);
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config Baudot node: " << std::endl
|
||||
<< " input sample rate: " << src_cfg.sampleRate() << " half-bits/s" << std::endl
|
||||
<< " start bits: " << 1 << std::endl
|
||||
<< " stop bits: " << float(_stopHBits)/2 << std::endl;
|
||||
Logger::get().log(msg);
|
||||
|
||||
// propergate config
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, 0, buffer_size, 1));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Baudot::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
|
||||
{
|
||||
size_t o=0;
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
_bitstream = (_bitstream << 1) | (buffer[i] & 0x1); _bitcount++;
|
||||
// Check if symbol as received:
|
||||
if ((_bitsPerSymbol <= _bitcount) && (_pattern == (_bitstream & _mask))) {
|
||||
_bitcount = 0;
|
||||
// Unpack 5bit baudot code
|
||||
uint8_t code = 0;
|
||||
for (int j=0; j<5; j++) {
|
||||
int shift = _stopHBits + 2*j;
|
||||
code |= (((_bitstream>>shift)&0x01)<<j);
|
||||
}
|
||||
// Decode to ASCII
|
||||
if (CHAR_STL == code) { _mode = LETTERS; }
|
||||
else if (CHAR_STF == code) { _mode = FIGURES; }
|
||||
else {
|
||||
if (CHAR_SPA == code) { _mode = LETTERS; }
|
||||
if (LETTERS == _mode) { _buffer[o++] = _letter[code]; }
|
||||
else { _buffer[o++] = _figure[code]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (0 < o) { this->send(_buffer.head(o)); }
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
#ifndef __SDR_BAUDOT_HH__
|
||||
#define __SDR_BAUDOT_HH__
|
||||
|
||||
#include "node.hh"
|
||||
#include <map>
|
||||
|
||||
namespace sdr {
|
||||
|
||||
/** Implements a Baudot decoder. Inconjecture with the (A)FSK demodulator, it enables the
|
||||
* reception of radio teletype (RTTY) messages.
|
||||
*
|
||||
* Please note that a baudot encoded char is usually transmitted in a frame with one start bit and
|
||||
* 1, 1.5 or 2 stop bits. Hence this node expects to receive two bits for one decoded bit in order
|
||||
* to detect the 1.5 stop bits reliably.
|
||||
*
|
||||
* I.e. to receive a 45.45 baud RTTY signal, the (A)FSK demodulator need to be configured for
|
||||
* 90.90 baud (= 2*45.45 baud).
|
||||
* @ingroup datanodes */
|
||||
class Baudot: public Sink<uint8_t>, public Source
|
||||
{
|
||||
public:
|
||||
/** Specifies the current code-tables. */
|
||||
typedef enum {
|
||||
LETTERS, ///< Letters.
|
||||
FIGURES ///< Numbers, symbols etc.
|
||||
} Mode;
|
||||
|
||||
/** Specifies the number of stop bits. */
|
||||
typedef enum {
|
||||
STOP1, ///< 1 stop bit.
|
||||
STOP15, ///< 1.5 stop bits.
|
||||
STOP2 ///< 2 stop bits.
|
||||
} StopBits;
|
||||
|
||||
public:
|
||||
/** Constructor. */
|
||||
Baudot(StopBits stopBits = STOP15);
|
||||
|
||||
/** 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);
|
||||
|
||||
protected:
|
||||
/** Code table for letters. */
|
||||
static char _letter[32];
|
||||
/** Code table for symbols or figure (i.e. numbers). */
|
||||
static char _figure[32];
|
||||
/** The last bits received. */
|
||||
uint16_t _bitstream;
|
||||
/** The number of bits received. */
|
||||
size_t _bitcount;
|
||||
|
||||
/** The currently selected table. */
|
||||
Mode _mode;
|
||||
|
||||
/** Specifies the number of half bits per symbol. */
|
||||
size_t _bitsPerSymbol;
|
||||
/** Specifies the frame pattern. */
|
||||
uint16_t _pattern;
|
||||
/** Specifies the frame mask. */
|
||||
uint16_t _mask;
|
||||
/** Number of half bits forming the stop bit. */
|
||||
uint16_t _stopHBits;
|
||||
|
||||
/** The output buffer. */
|
||||
Buffer<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BAUDOT_HH
|
||||
@ -0,0 +1,213 @@
|
||||
#include "bch31_21.hh"
|
||||
#include "stdlib.h"
|
||||
#include "string.h"
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
/*
|
||||
* the code used by POCSAG is a (n=31,k=21) BCH Code with dmin=5,
|
||||
* thus it could correct two bit errors in a 31-Bit codeword.
|
||||
* It is a systematic code.
|
||||
* The generator polynomial is:
|
||||
* g(x) = x^10+x^9+x^8+x^6+x^5+x^3+1
|
||||
* The parity check polynomial is:
|
||||
* h(x) = x^21+x^20+x^18+x^16+x^14+x^13+x^12+x^11+x^8+x^5+x^3+1
|
||||
* g(x) * h(x) = x^n+1
|
||||
*/
|
||||
#define BCH_POLY 03551 /* octal */
|
||||
#define BCH_N 31
|
||||
#define BCH_K 21
|
||||
|
||||
static inline unsigned char even_parity(uint32_t data)
|
||||
{
|
||||
unsigned int temp = data ^ (data >> 16);
|
||||
|
||||
temp = temp ^ (temp >> 8);
|
||||
temp = temp ^ (temp >> 4);
|
||||
temp = temp ^ (temp >> 2);
|
||||
temp = temp ^ (temp >> 1);
|
||||
return temp & 1;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
pocsag_syndrome(uint32_t data)
|
||||
{
|
||||
uint32_t shreg = data >> 1; /* throw away parity bit */
|
||||
uint32_t mask = 1L << (BCH_N-1), coeff = BCH_POLY << (BCH_K-1);
|
||||
int n = BCH_K;
|
||||
|
||||
for(; n > 0; mask >>= 1, coeff >>= 1, n--) {
|
||||
if (shreg & mask) { shreg ^= coeff; }
|
||||
}
|
||||
if (even_parity(data)) {
|
||||
shreg |= (1 << (BCH_N - BCH_K));
|
||||
}
|
||||
return shreg;
|
||||
}
|
||||
|
||||
static void
|
||||
bitslice_syndrome(uint32_t *slices)
|
||||
{
|
||||
const int firstBit = BCH_N - 1;
|
||||
int i, n;
|
||||
uint32_t paritymask = slices[0];
|
||||
|
||||
// do the parity and shift together
|
||||
for (i = 1; i < 32; ++i) {
|
||||
paritymask ^= slices[i];
|
||||
slices[i-1] = slices[i];
|
||||
}
|
||||
slices[31] = 0;
|
||||
|
||||
// BCH_POLY << (BCH_K - 1) is
|
||||
// 20 21 22 23
|
||||
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ONE, 0, 0, ONE,
|
||||
// 24 25 26 27 28 29 30 31
|
||||
// 0, ONE, ONE, 0, ONE, ONE, ONE, 0
|
||||
|
||||
for (n = 0; n < BCH_K; ++n) {
|
||||
// one line here for every '1' bit in coeff (above)
|
||||
const int bit = firstBit - n;
|
||||
slices[20 - n] ^= slices[bit];
|
||||
slices[23 - n] ^= slices[bit];
|
||||
slices[25 - n] ^= slices[bit];
|
||||
slices[26 - n] ^= slices[bit];
|
||||
slices[28 - n] ^= slices[bit];
|
||||
slices[29 - n] ^= slices[bit];
|
||||
slices[30 - n] ^= slices[bit];
|
||||
slices[31 - n] ^= slices[bit];
|
||||
}
|
||||
|
||||
// apply the parity mask we built up
|
||||
slices[BCH_N - BCH_K] |= paritymask;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
transpose_n(int n, uint32_t *matrix)
|
||||
{
|
||||
uint32_t out = 0;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 32; ++j) {
|
||||
if (matrix[j] & (1<<n)) {
|
||||
out |= (1<<j);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static uint32_t *
|
||||
transpose_clone(uint32_t src, uint32_t *out)
|
||||
{
|
||||
int i;
|
||||
if (!out) { out = (uint32_t *)malloc(sizeof(uint32_t)*32); }
|
||||
for (i = 0; i < 32; ++i) {
|
||||
if (src & (1<<i)) {
|
||||
out[i] = 0xffffffff;
|
||||
} else {
|
||||
out[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// This might not be elegant, yet effective!
|
||||
// Error correction via bruteforce ;)
|
||||
//
|
||||
// It's a pragmatic solution since this was much faster to implement
|
||||
// than understanding the math to solve it while being as effective.
|
||||
// Besides that the overhead is neglectable.
|
||||
int
|
||||
sdr::pocsag_repair(uint32_t &data)
|
||||
{
|
||||
// Check if data is correct
|
||||
if (0 == pocsag_syndrome(data)) { return 0; }
|
||||
|
||||
int i, n, b1, b2;
|
||||
uint32_t res;
|
||||
uint32_t *xpose = 0, *in = 0;
|
||||
|
||||
// check for single bit errors
|
||||
xpose = (uint32_t *) malloc(sizeof(uint32_t)*32);
|
||||
in = (uint32_t *) malloc(sizeof(uint32_t)*32);
|
||||
|
||||
transpose_clone(data, xpose);
|
||||
for (i = 0; i < 32; ++i) { xpose[i] ^= (1<<i); }
|
||||
bitslice_syndrome(xpose);
|
||||
|
||||
res = 0;
|
||||
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
|
||||
res = ~res;
|
||||
|
||||
if (res) {
|
||||
int n = 0;
|
||||
while (res) { ++n; res >>= 1; }
|
||||
--n;
|
||||
data ^= (1<<n);
|
||||
goto returnfree;
|
||||
}
|
||||
|
||||
//check for two bit errors
|
||||
n = 0;
|
||||
transpose_clone(data, xpose);
|
||||
|
||||
for (b1 = 0; b1 < 32; ++b1) {
|
||||
for (b2 = b1; b2 < 32; ++b2) {
|
||||
xpose[b1] ^= (1<<n);
|
||||
xpose[b2] ^= (1<<n);
|
||||
|
||||
if (++n == 32) {
|
||||
memcpy(in, xpose, sizeof(uint32_t)*32);
|
||||
bitslice_syndrome(xpose);
|
||||
res = 0;
|
||||
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
|
||||
res = ~res;
|
||||
if (res) {
|
||||
int n = 0;
|
||||
while (res) { ++n; res >>= 1; }
|
||||
--n;
|
||||
|
||||
data = transpose_n(n, in);
|
||||
goto returnfree;
|
||||
}
|
||||
|
||||
transpose_clone(data, xpose);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
memcpy(in, xpose, sizeof(uint32_t)*32);
|
||||
|
||||
bitslice_syndrome(xpose);
|
||||
|
||||
res = 0;
|
||||
for (i = 0; i < 32; ++i) { res |= xpose[i]; }
|
||||
res = ~res;
|
||||
|
||||
if (res) {
|
||||
int n = 0;
|
||||
while (res) { ++n; res >>= 1; }
|
||||
--n;
|
||||
|
||||
data = transpose_n(n, in);
|
||||
goto returnfree;
|
||||
}
|
||||
}
|
||||
|
||||
if (xpose) { free(xpose); }
|
||||
if (in) { free(in); }
|
||||
return 1;
|
||||
|
||||
returnfree:
|
||||
if (xpose)
|
||||
free(xpose);
|
||||
if (in)
|
||||
free(in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
#ifndef __SDR_BCH31_21_HH__
|
||||
#define __SDR_BCH31_21_HH__
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace sdr {
|
||||
|
||||
/** Checks and repairs a POCSAG message with its
|
||||
* BCH(31,21) ECC. */
|
||||
int pocsag_repair(uint32_t &data);
|
||||
|
||||
}
|
||||
|
||||
#endif // __SDR_BCH31_21_HH__
|
||||
@ -0,0 +1,236 @@
|
||||
#include "fsk.hh"
|
||||
#include "logger.hh"
|
||||
#include "traits.hh"
|
||||
#include "interpolate.hh"
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
|
||||
/* ******************************************************************************************** *
|
||||
* Implementation of FSKDetector
|
||||
* ******************************************************************************************** */
|
||||
FSKDetector::FSKDetector(float baud, float Fmark, float Fspace)
|
||||
: Sink<int16_t>(), Source(), _baud(baud), _corrLen(0), _Fmark(Fmark), _Fspace(Fspace)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
void
|
||||
FSKDetector::config(const Config &src_cfg)
|
||||
{
|
||||
// Check if config is complete
|
||||
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
|
||||
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<int16_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure FSKBase: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<int16_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
_corrLen = int(src_cfg.sampleRate()/_baud);
|
||||
_markLUT = Buffer< std::complex<float> >(_corrLen);
|
||||
_spaceLUT = Buffer< std::complex<float> >(_corrLen);
|
||||
_markHist = Buffer< std::complex<float> >(_corrLen);
|
||||
_spaceHist = Buffer< std::complex<float> >(_corrLen);
|
||||
|
||||
// Initialize LUTs and ring-buffers
|
||||
double phiMark=0, phiSpace=0;
|
||||
for (size_t i=0; i<_corrLen; i++) {
|
||||
_markLUT[i] = std::exp(std::complex<float>(0.0, phiMark));
|
||||
_spaceLUT[i] = std::exp(std::complex<float>(0.0, phiSpace));
|
||||
phiMark += (2.*M_PI*_Fmark)/src_cfg.sampleRate();
|
||||
phiSpace += (2.*M_PI*_Fspace)/src_cfg.sampleRate();
|
||||
// Apply Window functions
|
||||
//_markLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
|
||||
//_spaceLUT[i] *= (0.42 - 0.5*cos((2*M_PI*i)/_corrLen) + 0.08*cos((4*M_PI*i)/_corrLen));
|
||||
_markHist[i] = 0; _spaceHist[i] = 0;
|
||||
}
|
||||
// Ring buffer index
|
||||
_lutIdx = 0;
|
||||
|
||||
// Allocate output buffer
|
||||
_buffer = Buffer<int8_t>(src_cfg.bufferSize());
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config FSKDetector node: " << std::endl
|
||||
<< " sample/symbol rate: " << src_cfg.sampleRate() << " Hz" << std::endl
|
||||
<< " target baud rate: " << _baud << std::endl
|
||||
<< " approx. samples per bit: " << _corrLen;
|
||||
Logger::get().log(msg);
|
||||
|
||||
// Forward config.
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), src_cfg.bufferSize(), 1));
|
||||
}
|
||||
|
||||
|
||||
uint8_t
|
||||
FSKDetector::_process(int16_t sample) {
|
||||
_markHist[_lutIdx] = float(sample)*_markLUT[_lutIdx];
|
||||
_spaceHist[_lutIdx] = float(sample)*_spaceLUT[_lutIdx];
|
||||
// inc _lutIdx, modulo LUT length
|
||||
_lutIdx++; if (_lutIdx==_corrLen) { _lutIdx=0; }
|
||||
|
||||
std::complex<float> markSum(0), spaceSum(0);
|
||||
for (size_t i=0; i<_corrLen; i++) {
|
||||
markSum += _markHist[i];
|
||||
spaceSum += _spaceHist[i];
|
||||
}
|
||||
|
||||
float f = markSum.real()*markSum.real() +
|
||||
markSum.imag()*markSum.imag() -
|
||||
spaceSum.real()*spaceSum.real() -
|
||||
spaceSum.imag()*spaceSum.imag();
|
||||
|
||||
return (f>0);
|
||||
}
|
||||
|
||||
void
|
||||
FSKDetector::process(const Buffer<int16_t> &buffer, bool allow_overwrite) {
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
_buffer[i] = _process(buffer[i]);
|
||||
}
|
||||
this->send(_buffer.head(buffer.size()), false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ******************************************************************************************** *
|
||||
* Implementation of BitStream
|
||||
* ******************************************************************************************** */
|
||||
BitStream::BitStream(float baud, Mode mode)
|
||||
: Sink<uint8_t>(), Source(), _baud(baud), _mode(mode), _corrLen(0)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
void
|
||||
BitStream::config(const Config &src_cfg) {
|
||||
// Check if config is complete
|
||||
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
|
||||
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure BitStream: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<int16_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// # of symbols for each bit
|
||||
_corrLen = int(src_cfg.sampleRate()/_baud);
|
||||
|
||||
// Config PLL for bit detection
|
||||
_phase = 0;
|
||||
// exact bits per sample (<< 1)
|
||||
_omega = _baud/src_cfg.sampleRate();
|
||||
// PLL limits +/- 10% around _omega
|
||||
_omegaMin = _omega - 0.005*_omega;
|
||||
_omegaMax = _omega + 0.005*_omega;
|
||||
// PLL gain
|
||||
_pllGain = 0.0005;
|
||||
|
||||
// symbol moving average
|
||||
_symbols = Buffer<int8_t>(_corrLen);
|
||||
for (size_t i=0; i<_corrLen; i++) { _symbols[i] = 0; }
|
||||
_symIdx = 0; _symSum = 0; _lastSymSum = 0;
|
||||
|
||||
// Reset bit hist
|
||||
_lastBits = 0;
|
||||
|
||||
// Allocate output buffer
|
||||
_buffer = Buffer<uint8_t>(1+src_cfg.bufferSize()/_corrLen);
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
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.
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, _baud, _buffer.size(), 1));
|
||||
}
|
||||
|
||||
void
|
||||
BitStream::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
|
||||
{
|
||||
size_t o=0;
|
||||
for (size_t i=0; i<buffer.size(); i++)
|
||||
{
|
||||
// store symbol & update _symSum and _lastSymSum
|
||||
_lastSymSum = _symSum;
|
||||
_symSum -= _symbols[_symIdx];
|
||||
_symbols[_symIdx] = ( buffer[i] ? 1 : -1 );
|
||||
_symSum += _symbols[_symIdx];
|
||||
_symIdx = ((_symIdx+1) % _corrLen);
|
||||
|
||||
// Advance phase
|
||||
_phase += _omega;
|
||||
|
||||
// Sample bit ...
|
||||
if (_phase >= 1) {
|
||||
// Modulo "2 pi", phase is defined on the interval [0,1)
|
||||
while (_phase>=1) { _phase -= 1; }
|
||||
// Estimate bit by majority vote on all symbols (_symSum)
|
||||
_lastBits = ((_lastBits<<1) | (_symSum>0));
|
||||
// Put decoded bit in output buffer
|
||||
if (TRANSITION == _mode) {
|
||||
// transition -> 0; no transition -> 1
|
||||
_buffer[o++] = ((_lastBits ^ (_lastBits >> 1) ^ 0x1) & 0x1);
|
||||
} else {
|
||||
// mark -> 1, space -> 0
|
||||
_buffer[o++] = _lastBits & 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
// If there was a symbol transition
|
||||
if (((_lastSymSum < 0) && (_symSum>=0)) || ((_lastSymSum >= 0) && (_symSum<0))) {
|
||||
// Phase correction
|
||||
// transition at [-pi,0] -> increase omega
|
||||
if (_phase < 0.5) { _omega += _pllGain*(0.5-_phase); }
|
||||
// transition at [0,pi] -> decrease omega
|
||||
else { _omega -= _pllGain*(_phase-0.5); }
|
||||
// Limit omega
|
||||
_omega = std::min(_omegaMax, std::max(_omegaMin, _omega));
|
||||
}
|
||||
}
|
||||
|
||||
if (o>0) { this->send(_buffer.head(o)); }
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************************************************************** *
|
||||
* Implementation of BitDump
|
||||
* ******************************************************************************************** */
|
||||
BitDump::BitDump(std::ostream &stream)
|
||||
: Sink<uint8_t>(), _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<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure BitDump: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<int16_t>();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BitDump::process(const Buffer<uint8_t> &buffer, bool allow_overwrite) {
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
_stream << int(buffer[i]) << " ";
|
||||
}
|
||||
_stream << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
#ifndef __SDR_FSK_HH__
|
||||
#define __SDR_FSK_HH__
|
||||
|
||||
#include "node.hh"
|
||||
#include "traits.hh"
|
||||
#include "logger.hh"
|
||||
|
||||
|
||||
namespace sdr {
|
||||
|
||||
|
||||
/** Implements the basic FSK/AFSK symbol detection.
|
||||
* This node contains two FIR filters for the detection of the mark and space frequencies. The node
|
||||
* returns a sequence of symbols (i.e. sub-bits) which need to be processed to obtain a sequenc of
|
||||
* transmitted bits (i.e. by the @c BitStream node).
|
||||
*
|
||||
* @ingroup demods */
|
||||
class FSKDetector: public Sink<int16_t>, 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);
|
||||
void process(const Buffer<int16_t> &buffer, bool allow_overwrite);
|
||||
|
||||
protected:
|
||||
/** Updates the mark/space FIR filter and returns the sampled symbol. */
|
||||
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<float> > _markLUT;
|
||||
/** Space frequency FIR filter LUT. */
|
||||
Buffer< std::complex<float> > _spaceLUT;
|
||||
/** FIR filter buffer. */
|
||||
Buffer< std::complex<float> > _markHist;
|
||||
/** FIR filter buffer. */
|
||||
Buffer< std::complex<float> > _spaceHist;
|
||||
/** Output buffer. */
|
||||
Buffer<int8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
/** 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 Scalar>
|
||||
class ASKDetector: public Sink<Scalar>, public Source
|
||||
{
|
||||
public:
|
||||
/** Constructor. */
|
||||
ASKDetector(bool invert=false)
|
||||
: Sink<Scalar>(), Source(), _invert(invert)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
void config(const Config &src_cfg) {
|
||||
// Check if config is complete
|
||||
if (!src_cfg.hasType() || !src_cfg.hasSampleRate()) { return; }
|
||||
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<int16_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure ASKDetector: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<int16_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Allocate output buffer
|
||||
_buffer = Buffer<uint8_t>(src_cfg.bufferSize());
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config ASKDetector node: " << std::endl
|
||||
<< " threshold: " << 0 << std::endl
|
||||
<< " invert: " << ( _invert ? "yes" : "no" ) << std::endl
|
||||
<< " symbol rate: " << src_cfg.sampleRate() << " Hz";
|
||||
Logger::get().log(msg);
|
||||
|
||||
// Forward config.
|
||||
this->setConfig(Config(Traits<uint8_t>::scalarId, src_cfg.sampleRate(), _buffer.size(), 1));
|
||||
}
|
||||
|
||||
void process(const Buffer<Scalar> &buffer, bool allow_overwrite) {
|
||||
for (size_t i=0; i<buffer.size(); i++) {
|
||||
_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<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
/** 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<uint8_t>, public Source
|
||||
{
|
||||
public:
|
||||
/** Possible bit decoding modes. */
|
||||
typedef enum {
|
||||
NORMAL, ///< Normal mode (i.e. mark -> 1, space -> 0).
|
||||
TRANSITION ///< Transition mode (i.e. transition -> 0, no transition -> 1).
|
||||
} 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<uint8_t> &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<int8_t> _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<uint8_t> _buffer;
|
||||
};
|
||||
|
||||
|
||||
/** Trivial node to dump a bit-stream to a std::ostream.
|
||||
* @ingroup sinks */
|
||||
class BitDump : public Sink<uint8_t>
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param stream Specifies the output stream. */
|
||||
BitDump(std::ostream &stream);
|
||||
|
||||
void config(const Config &src_cfg);
|
||||
void process(const Buffer<uint8_t> &buffer, bool allow_overwrite);
|
||||
|
||||
protected:
|
||||
/** The output stream. */
|
||||
std::ostream &_stream;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // __SDR_FSK_HH__
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,627 @@
|
||||
/** @defgroup http A rather trivia HTTP daemon implementation.
|
||||
*
|
||||
* This module collects some classes allowing to implement a simple HTTP server to serve static
|
||||
* and dynamic content. The central class is the @c Server class which dispatches incomming
|
||||
* requests to the registered @c Handler instances.
|
||||
*
|
||||
* There are several specializations of the @c Handler class avaliable suited to perform specific
|
||||
* tasks. I.e. the @c StaticHandler serves some static content (i.e. strings) while the
|
||||
* @c DelegateJSONHandler allows to implement a REST api easily.
|
||||
*
|
||||
* An example of a server, serving a static index page and provides a trivial JSON echo method.
|
||||
* \code
|
||||
* #include "http.hh"
|
||||
* #include "logger.hh"
|
||||
*
|
||||
* using namespace sdr;
|
||||
*
|
||||
* // Implements an application, a collection of methods being called by the http::Server.
|
||||
* class Application {
|
||||
* public:
|
||||
* // contstructor.
|
||||
* Application() {}
|
||||
*
|
||||
* // The callback to handle the JSON echo api.
|
||||
* bool echo(const http::JSON &request, http::JSON &result) {
|
||||
* // just echo
|
||||
* result = request;
|
||||
* // signal success
|
||||
* return true;
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Static content
|
||||
* const char *index_html = "<html> ... </html>";
|
||||
*
|
||||
*
|
||||
* int main(int argc, char *argv[]) {
|
||||
* // install log handler
|
||||
* sdr::Logger::get().addHandler(
|
||||
* new sdr::StreamLogHandler(std::cerr, sdr::LOG_DEBUG));
|
||||
*
|
||||
* // serve on port 8080
|
||||
* http::Server server(8080);
|
||||
*
|
||||
* // Instantiate application
|
||||
* Application app;
|
||||
*
|
||||
* // Register static content handlers
|
||||
* server.addStatic("/", index_html, "text/html");
|
||||
* // Register JSON echo method
|
||||
* server.addJSON("/echo", &app, &Application::echo);
|
||||
*
|
||||
* // Start server
|
||||
* server.start(true);
|
||||
*
|
||||
* return 0;
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
|
||||
#ifndef __SDR_HTTPD_HH__
|
||||
#define __SDR_HTTPD_HH__
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
|
||||
namespace sdr {
|
||||
namespace http {
|
||||
|
||||
// Forward declarations
|
||||
class Server;
|
||||
|
||||
|
||||
/** Represents a JSON object.
|
||||
* JSON is a popular means to implement remote procedure calls (RPCs) using java script. JSON is
|
||||
* valid java script code and allows to transfer numbers, string, lists and objects.
|
||||
* @ingroup http */
|
||||
class JSON
|
||||
{
|
||||
public:
|
||||
/** Specifies the possible json types. */
|
||||
typedef enum {
|
||||
EMPTY, ///< null.
|
||||
BOOLEAN, ///< A boolean value.
|
||||
NUMBER, ///< A number.
|
||||
STRING, ///< A string.
|
||||
ARRAY, ///< An array or list.
|
||||
TABLE ///< A table or object.
|
||||
} Type;
|
||||
|
||||
public:
|
||||
/** Empty constructor (null). */
|
||||
JSON();
|
||||
/** Constructs a boolean value. */
|
||||
JSON(bool value);
|
||||
/** Constructs a number. */
|
||||
JSON(double value);
|
||||
/** Constructs a string. */
|
||||
JSON(const std::string &value);
|
||||
/** Constructs a list. */
|
||||
JSON(const std::list<JSON> &list);
|
||||
/** Constructs a table. */
|
||||
JSON(const std::map<std::string, JSON> &table);
|
||||
/** Copy constructor (deep copy). */
|
||||
JSON(const JSON &other);
|
||||
/** Destructor. */
|
||||
~JSON();
|
||||
/** Assignment operator (deep copy). */
|
||||
JSON &operator=(const JSON &other);
|
||||
|
||||
/** Retruns @c true for the null. */
|
||||
inline bool isNull() const { return EMPTY == _type; }
|
||||
/** Returns @c true if this is a boolean value. */
|
||||
inline bool isBoolean() const { return BOOLEAN == _type; }
|
||||
/** Returns the boolean value. */
|
||||
inline const bool &asBoolean() const { return *_value.boolean; }
|
||||
/** Returns @c true if this is a number. */
|
||||
inline bool isNumber() const { return NUMBER == _type; }
|
||||
/** Returns the number. */
|
||||
inline const double &asNumber() const { return *_value.number; }
|
||||
/** Returns @c true if this is a string. */
|
||||
inline bool isString() const { return STRING == _type; }
|
||||
/** Returns the string. */
|
||||
inline const std::string &asString() const { return *_value.string; }
|
||||
/** Returns @c true if this is an array. */
|
||||
inline bool isArray() const { return ARRAY == _type; }
|
||||
/** Retruns the array as a list. */
|
||||
inline const std::list<JSON> &asArray() const { return *_value.list; }
|
||||
/** Returns @c true if this is a table. */
|
||||
inline bool isTable() const { return TABLE == _type; }
|
||||
/** Returns the table. */
|
||||
inline const std::map<std::string, JSON> &asTable() const { return *_value.table; }
|
||||
/** Resets the JSON object to null. */
|
||||
void clear();
|
||||
|
||||
/** Parses the given string. Retruns @c true on success. */
|
||||
static bool parse(const std::string &text, JSON &obj);
|
||||
/** Serializes the JSON object. */
|
||||
void serialize(std::ostream &stream) const;
|
||||
/** Serializes the JSON object. */
|
||||
inline void serialize(std::string &text) const {
|
||||
std::stringstream buffer; this->serialize(buffer);
|
||||
text = buffer.str();
|
||||
}
|
||||
|
||||
protected:
|
||||
/** The type of the object. */
|
||||
Type _type;
|
||||
/** Union of value pointers. */
|
||||
union {
|
||||
/** The boolean value. */
|
||||
bool *boolean;
|
||||
/** The number. */
|
||||
double *number;
|
||||
/** The string. */
|
||||
std::string *string;
|
||||
/** The array. */
|
||||
std::list<JSON> *list;
|
||||
/** The table. */
|
||||
std::map<std::string, JSON> *table;
|
||||
} _value;
|
||||
};
|
||||
|
||||
|
||||
/** Lists the possible HTTP methods.
|
||||
* @ingroup http */
|
||||
typedef enum {
|
||||
HTTP_UNKNOWN, ///< Unknown method. Results into an invalid request.
|
||||
HTTP_GET, ///< The get method.
|
||||
HTTP_HEAD, ///< The head method.
|
||||
HTTP_POST ///< The post method.
|
||||
} Method;
|
||||
|
||||
/** Lists the possible HTTP versions.
|
||||
* @ingroup http */
|
||||
typedef enum {
|
||||
UNKNOWN_VERSION, ///< Unknown http version. Results into an invalid request.
|
||||
HTTP_1_0, ///< HTTP/1.0
|
||||
HTTP_1_1 ///< HTTP/1.1
|
||||
} Version;
|
||||
|
||||
|
||||
/** Represents a URL.
|
||||
* @ingroup http */
|
||||
class URL
|
||||
{
|
||||
public:
|
||||
/** Empty constructor. */
|
||||
URL();
|
||||
/** Constructor from protocol, host and path. */
|
||||
URL(const std::string &proto, const std::string &host, const std::string &path);
|
||||
/** Copy constructor. */
|
||||
URL(const URL &other);
|
||||
|
||||
/** Assignment operator. */
|
||||
URL &operator=(const URL &other);
|
||||
|
||||
/** Parses a URL from the given string. */
|
||||
static URL fromString(const std::string &url);
|
||||
/** Serializes the URL into a string. */
|
||||
std::string toString() const;
|
||||
|
||||
/** Encode a string. */
|
||||
static std::string encode(const std::string &str);
|
||||
/** Decodes a string. */
|
||||
static std::string decode(const std::string &str);
|
||||
|
||||
/** Returns @c true if the URL specifies a protocol. */
|
||||
inline bool hasProtocol() const { return (0 != _protocol.size()); }
|
||||
/** Returns the protocol. */
|
||||
inline const std::string protocol() const { return _protocol; }
|
||||
/** Sets the protocol. */
|
||||
inline void setProtocol(const std::string &proto) { _protocol = proto; }
|
||||
/** Returns @c true if the URL specified a host name. */
|
||||
inline bool hasHost() const { return (0 != _host.size()); }
|
||||
/** Returns the host name. */
|
||||
inline const std::string &host() const { return _host; }
|
||||
/** Set the host name. */
|
||||
inline void setHost(const std::string &host) { _host = host; }
|
||||
/** Retruns the path of the URL. */
|
||||
inline const std::string &path() const { return _path; }
|
||||
/** Sets the path of the URL. */
|
||||
inline void setPath(const std::string &path) { _path = path; }
|
||||
/** Adds a query pair (key, value) to the URL. */
|
||||
inline void addQuery(const std::string &name, const std::string &value) {
|
||||
_query.push_back(std::pair<std::string, std::string>(name, value));
|
||||
}
|
||||
/** Returns the list of query (key, value) pairs. */
|
||||
inline const std::list< std::pair<std::string, std::string> > &query() const {
|
||||
return _query;
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Holds the protocol. */
|
||||
std::string _protocol;
|
||||
/** Holds the host name. */
|
||||
std::string _host;
|
||||
/** Holds the path. */
|
||||
std::string _path;
|
||||
/** Holds the query pairs. */
|
||||
std::list< std::pair<std::string, std::string> > _query;
|
||||
};
|
||||
|
||||
|
||||
struct ConnectionObj
|
||||
{
|
||||
public:
|
||||
ConnectionObj(Server *server, int cli_socket);
|
||||
~ConnectionObj();
|
||||
|
||||
ConnectionObj *ref();
|
||||
void unref();
|
||||
|
||||
public:
|
||||
/** A weak reference to the server instance. */
|
||||
Server *server;
|
||||
/** The connection socket. */
|
||||
int socket;
|
||||
/** If @c true (i.e. set by a handler), the http parser thread will exit without closing
|
||||
* the connection. This allows to "take-over" the tcp connection to the client by the request
|
||||
* handler. */
|
||||
bool protocol_upgrade;
|
||||
/** Reference counter. */
|
||||
size_t refcount;
|
||||
};
|
||||
|
||||
|
||||
/** Implements a HTTP connection to a client.
|
||||
* @c ingroup http */
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
/** Empty constructor. */
|
||||
Connection();
|
||||
/** Constructor. */
|
||||
Connection(Server *server, int socket);
|
||||
/** Copy constructor. Implements reference counting. */
|
||||
Connection(const Connection &other);
|
||||
/** Destructor. */
|
||||
~Connection();
|
||||
|
||||
Connection &operator=(const Connection &other);
|
||||
|
||||
/** Closes the connection.
|
||||
* If @c wait is @c true, the method will wait until the thread listening for incomming
|
||||
* request joined. */
|
||||
void close(bool wait=false);
|
||||
/** Returns @c true if the connection is closed. */
|
||||
bool isClosed() const;
|
||||
|
||||
/** Sets the protocol-update flag. */
|
||||
inline void setProtocolUpgrade() const { _object->protocol_upgrade = true; }
|
||||
inline bool protocolUpgrade() const { return _object->protocol_upgrade; }
|
||||
|
||||
inline ssize_t write(const void *data, size_t n) const {
|
||||
if (0 == _object) { return -1; }
|
||||
return ::write(_object->socket, data, n);
|
||||
}
|
||||
|
||||
inline ssize_t read(void *data, size_t n) const {
|
||||
if (0 == _object) { return -1; }
|
||||
return ::read(_object->socket, data, n);
|
||||
}
|
||||
|
||||
bool send(const std::string &data) const;
|
||||
|
||||
/** Main loop for incomming requests. */
|
||||
void main();
|
||||
|
||||
protected:
|
||||
ConnectionObj *_object;
|
||||
};
|
||||
|
||||
|
||||
/** Represents a HTTP request.
|
||||
* @ingroup http */
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
/** Constructor. */
|
||||
Request(const Connection &connection);
|
||||
|
||||
/** Parses the HTTP request header, returns @c true on success. */
|
||||
bool parse();
|
||||
|
||||
/** Return the connection to the client. */
|
||||
inline const Connection &connection() const { return _connection; }
|
||||
|
||||
/** Returns @c true if the connection to the client is kept alive after the response
|
||||
* has been send. */
|
||||
bool isKeepAlive() const;
|
||||
|
||||
/** Returns @c true if the given header is present. */
|
||||
bool hasHeader(const std::string &name) const;
|
||||
/** Returns the value of the given header. */
|
||||
std::string header(const std::string &name) const;
|
||||
|
||||
/** Returns @c true if the Content-Length header is present. */
|
||||
inline bool hasContentLength() const { return hasHeader("Content-Length"); }
|
||||
/** Returns the value of the Content-Length header. */
|
||||
size_t contentLength() const { return atol(header("Content-Length").c_str()); }
|
||||
|
||||
/** Retruns the request method. */
|
||||
inline Method method() const { return _method; }
|
||||
/** Returns the request URL. */
|
||||
inline const URL &url() const { return _url; }
|
||||
/** Reads the complete body (if Content-Length header is present).
|
||||
* Retruns @c true on success.*/
|
||||
bool readBody(std::string &body) const;
|
||||
|
||||
protected:
|
||||
/** The connection socket. */
|
||||
Connection _connection;
|
||||
/** The request method. */
|
||||
Method _method;
|
||||
/** The HTTP version. */
|
||||
Version _version;
|
||||
/** The request URL. */
|
||||
URL _url;
|
||||
/** The request headers. */
|
||||
std::map<std::string, std::string> _headers;
|
||||
};
|
||||
|
||||
|
||||
/** Represents a HTTP response. */
|
||||
class Response
|
||||
{
|
||||
public:
|
||||
/** Defines all possible responses. */
|
||||
typedef enum {
|
||||
STATUS_OK = 200, ///< OK.
|
||||
STATUS_BAD_REQUEST = 400, ///< Your fault.
|
||||
STATUS_NOT_FOUND = 404, ///< Resource not found.
|
||||
STATUS_SERVER_ERROR = 500 ///< My fault.
|
||||
} Status;
|
||||
|
||||
public:
|
||||
/** Constructor. */
|
||||
Response(const Connection &connnection);
|
||||
|
||||
/** Return the connection to the client. */
|
||||
inline const Connection &connection() const { return _connection; }
|
||||
|
||||
/** Specifies the response code. */
|
||||
void setStatus(Status status);
|
||||
/** Returns @c true if the response has the given header. */
|
||||
bool hasHeader(const std::string &name) const;
|
||||
/** Returns the value of the header. */
|
||||
std::string header(const std::string &name) const;
|
||||
/** Sets the header. */
|
||||
void setHeader(const std::string &name, const std::string &value);
|
||||
/** Helper function to set the content length. */
|
||||
void setContentLength(size_t length);
|
||||
|
||||
/** Sends the response code and all defined headers. */
|
||||
bool sendHeaders() const;
|
||||
|
||||
protected:
|
||||
/** The socket over which the response will be send. */
|
||||
Connection _connection;
|
||||
/** The response code. */
|
||||
Status _status;
|
||||
/** The response headers. */
|
||||
std::map<std::string, std::string> _headers;
|
||||
/** If @c true, the connection will be closed after the response has been send. */
|
||||
bool _close_connection;
|
||||
};
|
||||
|
||||
|
||||
/** Base class of all HTTP request handlers.
|
||||
* @ingroup http */
|
||||
class Handler
|
||||
{
|
||||
protected:
|
||||
/** Hidden constructor. */
|
||||
Handler();
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Handler();
|
||||
/** Needs to be implemented to accept the given request.
|
||||
* If this method returns @c true, the server will call the @c handle instance next
|
||||
* in expectation that this instance will process the request. */
|
||||
virtual bool match(const Request &request) = 0;
|
||||
/** Needs to be implemented to process requests, which have been accepted by the @c match()
|
||||
* method. */
|
||||
virtual void handle(const Request &request, Response &response) = 0;
|
||||
};
|
||||
|
||||
|
||||
/** Serves some static content.
|
||||
* @ingroup http */
|
||||
class StaticHandler: public Handler
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param url Specifies the URL (path) of the static content.
|
||||
* @param text Speciefies the content.
|
||||
* @param mimeType Speficies the mime-type of the content. */
|
||||
StaticHandler(const std::string &url, const std::string &text,
|
||||
const std::string mimeType="text/text");
|
||||
/** Destructor. */
|
||||
virtual ~StaticHandler();
|
||||
|
||||
bool match(const Request &request);
|
||||
void handle(const Request &request, Response &response);
|
||||
|
||||
protected:
|
||||
/** Holds the URL (path) of the content. */
|
||||
std::string _url;
|
||||
/** Holds the mime-type of the content. */
|
||||
std::string _mimeType;
|
||||
/** Holds the content itself. */
|
||||
std::string _text;
|
||||
};
|
||||
|
||||
|
||||
/** Utility class to provide a handler as a delegate.
|
||||
* @ingroup http */
|
||||
template <class T>
|
||||
class DelegateHandler: public Handler
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param url Specifies the path of the handler.
|
||||
* @param instance Specifies the instance of the delegate.
|
||||
* @param func Specifies the method of the delegate instance being called. */
|
||||
DelegateHandler(const std::string &url, T *instance, void (T::*func)(const Request &, Response &))
|
||||
: Handler(), _url(url), _instance(instance), _callback(func)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
bool match(const Request &request) {
|
||||
return this->_url == request.url().path();
|
||||
}
|
||||
|
||||
void handle(const Request &request, Response &response) {
|
||||
(this->_instance->*(this->_callback))(request, response);
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Holds the path of the handler. */
|
||||
std::string _url;
|
||||
/** Holds the instance of the delegate. */
|
||||
T *_instance;
|
||||
/** Holds the method of the instance being called. */
|
||||
void (T::*_callback)(const Request &, Response &);
|
||||
};
|
||||
|
||||
|
||||
/** Implements a specialized handler to ease the processing of JSON REST-APIs.
|
||||
* @ingroup http */
|
||||
class JSONHandler: public http::Handler
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param url Speficies the path of the method. */
|
||||
JSONHandler(const std::string &url);
|
||||
|
||||
bool match(const http::Request &request);
|
||||
void handle(const http::Request &request, http::Response &response);
|
||||
|
||||
/** Needs to be implemented to process the request and assemble the result.
|
||||
* An error (400 BAD REQUEST) will be send if the function does not return @c true. */
|
||||
virtual bool process(const JSON &request, JSON &result) = 0;
|
||||
|
||||
protected:
|
||||
/** The URL of the method. */
|
||||
std::string _url;
|
||||
};
|
||||
|
||||
|
||||
/** A delegate JSON handler.
|
||||
* @ingroup http */
|
||||
template <class T>
|
||||
class DelegateJSONHandler: public JSONHandler
|
||||
{
|
||||
public:
|
||||
/** Constructor. */
|
||||
DelegateJSONHandler(const std::string &url, T *instance,
|
||||
bool (T::*func)(const JSON &request, JSON &response))
|
||||
: JSONHandler(url), _instance(instance), _callback(func)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
virtual bool process(const JSON &request, JSON &result) {
|
||||
return (this->_instance->*(this->_callback))(request, result);
|
||||
}
|
||||
|
||||
protected:
|
||||
/** The delegate instance. */
|
||||
T *_instance;
|
||||
/** The method to be called from the delegate instance. */
|
||||
bool (T::*_callback)(const JSON &request, JSON &response);
|
||||
};
|
||||
|
||||
|
||||
/** Implements a trivial HTTP/1.1 server.
|
||||
* @ingroup http */
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param port Specifies the port number to listen on. */
|
||||
Server(uint port);
|
||||
/** Destructor. */
|
||||
~Server();
|
||||
|
||||
/** Starts the server.
|
||||
* If @c wait is @c true, the call to this method will bock until the server thread stops. */
|
||||
void start(bool wait=false);
|
||||
|
||||
/** Stops the server.
|
||||
* If @c wait is @c true, the call will block until the server thread stopped. */
|
||||
void stop(bool wait=false);
|
||||
|
||||
/** Wait for the server thread to join. */
|
||||
void wait();
|
||||
|
||||
/** Adds a generic handler to the dispatcher. */
|
||||
void addHandler(Handler *handler);
|
||||
/** Adds a delegate to the dispatcher. */
|
||||
template <class T>
|
||||
void addHandler(const std::string &url,
|
||||
T *instance, void (T::*func)(const Request &, Response &)) {
|
||||
addHandler(new DelegateHandler<T>(url, instance, func));
|
||||
}
|
||||
/** Adds a JSON delegate to the dispatcher. */
|
||||
template <class T>
|
||||
void addJSON(const std::string &url,
|
||||
T *instance, bool (T::*func)(const JSON &request, JSON &result)) {
|
||||
addHandler(new DelegateJSONHandler<T>(url, instance, func));
|
||||
}
|
||||
/** Adds some static content to the dispatcher. */
|
||||
inline void addStatic(const std::string &url, const std::string &text) {
|
||||
addHandler(new StaticHandler(url, text));
|
||||
}
|
||||
/** Adds a generic handler to the dispatcher. */
|
||||
inline void addStatic(const std::string &url, const std::string &text, const std::string &mimeType) {
|
||||
addHandler(new StaticHandler(url, text, mimeType));
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Dispatches a request. */
|
||||
void dispatch(const Request &request, Response &response);
|
||||
/** The thread waiting for incomming connections. */
|
||||
static void *_listen_main(void *ctx);
|
||||
/** The thread handling connections. */
|
||||
static void *_connection_main(void *ctx);
|
||||
|
||||
protected:
|
||||
/** Port to bind to. */
|
||||
uint _port;
|
||||
/** The socket to listen on. */
|
||||
int _socket;
|
||||
/** While true, the server is listening on the port for incomming connections. */
|
||||
bool _is_running;
|
||||
/** The listen thread. */
|
||||
pthread_t _thread;
|
||||
/** All registered handler. */
|
||||
std::list<Handler *> _handler;
|
||||
/** The connection queue. */
|
||||
std::list<Connection> _queue;
|
||||
/** The queue lock. */
|
||||
pthread_mutex_t _queue_lock;
|
||||
/** The set of handler threads. */
|
||||
std::set<pthread_t> _threads;
|
||||
|
||||
/* Allow Connection to access dispatch(). */
|
||||
friend class Connection;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif // __SDR_HTTPD_HH__
|
||||
@ -0,0 +1,373 @@
|
||||
#include "pocsag.hh"
|
||||
#include "bch31_21.hh"
|
||||
#include "logger.hh"
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
inline bool is_address(uint32_t word) {
|
||||
return (0 == (0x80000000 & word));
|
||||
}
|
||||
|
||||
|
||||
/* ********************************************************************************************* *
|
||||
* Implementation of POCSAG
|
||||
* ********************************************************************************************* */
|
||||
POCSAG::POCSAG()
|
||||
: Sink<uint8_t>()
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
void
|
||||
POCSAG::config(const Config &src_cfg) {
|
||||
if (! src_cfg.hasType()) { return; }
|
||||
// Check if buffer type matches
|
||||
if (Config::typeId<uint8_t>() != src_cfg.type()) {
|
||||
ConfigError err;
|
||||
err << "Can not configure POCSAG: Invalid type " << src_cfg.type()
|
||||
<< ", expected " << Config::typeId<uint8_t>();
|
||||
throw err;
|
||||
}
|
||||
|
||||
LogMessage msg(LOG_DEBUG);
|
||||
msg << "Config POCSAG node.";
|
||||
Logger::get().log(msg);
|
||||
|
||||
_state = WAIT;
|
||||
_bits = 0;
|
||||
}
|
||||
|
||||
void
|
||||
POCSAG::process(const Buffer<uint8_t> &buffer, bool allow_overwrite)
|
||||
{
|
||||
for (size_t i=0; i<buffer.size(); i++)
|
||||
{
|
||||
// put bit into shift register
|
||||
_bits = ( (_bits<<1) | (buffer[i] & 0x01) );
|
||||
|
||||
// Dispatch by state
|
||||
if (WAIT == _state) {
|
||||
// Wait for the sync word to appear
|
||||
uint32_t word = (_bits & 0xffffffff);
|
||||
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
|
||||
// init messages
|
||||
_reset_message();
|
||||
_state = RECEIVE; _bitcount = 0; _slot = 0;
|
||||
}
|
||||
} else if (RECEIVE == _state) {
|
||||
// Receive 64 bit (2 words)
|
||||
_bitcount++;
|
||||
if (64 == _bitcount) {
|
||||
_bitcount=0;
|
||||
|
||||
// get and check 1st word bits
|
||||
uint32_t word = ( (_bits>>32) & 0xffffffff );
|
||||
if (0 == pocsag_repair(word)) { _process_word(word); }
|
||||
|
||||
// get and check 2nd word bits
|
||||
word = ( _bits & 0xffffffff );
|
||||
if (0 == pocsag_repair(word)) { _process_word(word); }
|
||||
|
||||
// Advance slot counter
|
||||
_slot++;
|
||||
if (8 == _slot) {
|
||||
// If all slots (8) has been processed -> wait for continuation
|
||||
_state = CHECK_CONTINUE;
|
||||
}
|
||||
}
|
||||
} else if (CHECK_CONTINUE == _state) {
|
||||
// Wait for an immediate sync word
|
||||
_bitcount++;
|
||||
if (32 == _bitcount) {
|
||||
uint32_t word = (_bits&0xffffffff);
|
||||
if ( (0 == pocsag_repair(word)) && (0x7cd215d8 == word)) {
|
||||
// If a sync word has been received -> continue with reception of slot 0
|
||||
_state = RECEIVE; _slot = 0; _bitcount = 0;
|
||||
} else {
|
||||
// Otherwise -> end of transmission, wait for next sync
|
||||
_finish_message(); _state = WAIT;
|
||||
// Process received messages
|
||||
this->handleMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
POCSAG::_process_word(uint32_t word)
|
||||
{
|
||||
/*std::cerr << "POCSAG: RX " << std::hex << word
|
||||
<< std::dec << " @ " << int(_slot) << std::endl; */
|
||||
|
||||
|
||||
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 );
|
||||
uint8_t func = ((word>>11) & 0x03);
|
||||
// init new message
|
||||
_message = Message(addr, func);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
POCSAG::_reset_message() {
|
||||
_message = Message();
|
||||
}
|
||||
|
||||
void
|
||||
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();
|
||||
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)" << std::endl << " " << msg.asNumeric() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ********************************************************************************************* *
|
||||
* Implementation of POCSAG::Message
|
||||
* ********************************************************************************************* */
|
||||
std::string
|
||||
ascii2text(uint8_t byte) {
|
||||
switch ( byte ) {
|
||||
case 0: return "<NUL>";
|
||||
case 1: return "<SOH>";
|
||||
case 2: return "<STX>";
|
||||
case 3: return "<ETX>";
|
||||
case 4: return "<EOT>";
|
||||
case 5: return "<ENQ>";
|
||||
case 6: return "<ACK>";
|
||||
case 7: return "<BEL>";
|
||||
case 8: return "<BS>";
|
||||
case 9: return "<HT>";
|
||||
case 10: return "<LF>";
|
||||
case 11: return "<VT>";
|
||||
case 12: return "<FF>";
|
||||
case 13: return "<CR>";
|
||||
case 14: return "<SO>";
|
||||
case 15: return "<SI>";
|
||||
case 16: return "<DLE>";
|
||||
case 17: return "<DC1>";
|
||||
case 18: return "<DC2>";
|
||||
case 19: return "<DC3>";
|
||||
case 20: return "<DC4>";
|
||||
case 21: return "<NAK>";
|
||||
case 22: return "<SYN>";
|
||||
case 23: return "<ETB>";
|
||||
case 24: return "<CAN>";
|
||||
case 25: return "<EM>";
|
||||
case 26: return "<SUB>";
|
||||
case 27: return "<ESC>";
|
||||
case 28: return "<FS>";
|
||||
case 29: return "<GS>";
|
||||
case 30: return "<RS>";
|
||||
case 31: return "<US>";
|
||||
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)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
POCSAG::Message::Message(uint32_t addr, uint8_t func)
|
||||
: _address(addr), _function(func), _empty(false), _bits(0)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
POCSAG::Message::Message(const Message &other)
|
||||
: _address(other._address), _function(other._function), _empty(other._empty),
|
||||
_bits(other._bits), _payload(other._payload)
|
||||
{
|
||||
// pass...
|
||||
}
|
||||
|
||||
POCSAG::Message &
|
||||
POCSAG::Message::operator =(const Message &other) {
|
||||
_address = other._address;
|
||||
_function = other._function;
|
||||
_empty = other._empty;
|
||||
_bits = other._bits;
|
||||
_payload = other._payload;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
POCSAG::Message::addPayload(uint32_t word) {
|
||||
// Add data bits from data to payload vector
|
||||
uint32_t mask = 0x40000000;
|
||||
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)>>(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;
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
#ifndef __SDR_POSAG_HH__
|
||||
#define __SDR_POSAG_HH__
|
||||
|
||||
#include "node.hh"
|
||||
|
||||
namespace sdr {
|
||||
|
||||
/** Implements a POCSAG decoder.
|
||||
* In conjecture with the @c FSKDetector or @c AFSDetector and the @c BitStream nodes, this node
|
||||
* can be used to receive and process POCSAG (pages) messages.
|
||||
*
|
||||
* The POCSAG protocol is defined as followig:
|
||||
*
|
||||
* 1. at least 576 bits of alternating value (1 0 1 0 ...)
|
||||
* 2. a 32-bit sync word (0x7CD215D8)
|
||||
* 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).
|
||||
*
|
||||
* In order to process the received message you need to override the @c handleMessages() method
|
||||
* which gets called once a batch of messages has been received.
|
||||
*
|
||||
* @ingroup datanodes */
|
||||
class POCSAG: public Sink<uint8_t>
|
||||
{
|
||||
public:
|
||||
/** A pocsag message.
|
||||
* A pocsag message can be either a numeric message (i.e. phone numbers) or a text message.
|
||||
* The transmitter knows which type a certain receiver expects, hence there is no information
|
||||
* embedded into the actual message that determines the type. Hence a heuristic needs to be
|
||||
* used in order to select the message type by the contents of the message. This is done using
|
||||
* the @c estimateText and @c estimateNumeric functions. They return a weight in favor of one of
|
||||
* the types. I.e. if estimateText() > estimateNumeric(), the message is likely to be a text
|
||||
* message. Like any heuristic, this approach may fail.
|
||||
* The message text is returned by @c asText() and the numeric data is returned by
|
||||
* @c asNumeric(). Both methods return a @c std::string as the numeric message may also contain
|
||||
* a very limited set of non-number symbols. */
|
||||
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);
|
||||
|
||||
/** Retruns the "likelihood" that the message is a text message (actually a log-likelihood). */
|
||||
int estimateText() const;
|
||||
/** Retruns the "likelihood" that the message is a numeric message
|
||||
* (actually a log-likelihood). */
|
||||
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<uint8_t> _payload;
|
||||
};
|
||||
|
||||
protected:
|
||||
/** The possible states of the POGSAC receiver. */
|
||||
typedef enum {
|
||||
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<uint8_t> &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);
|
||||
/** 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;
|
||||
/** The current message. */
|
||||
Message _message;
|
||||
/** The completed messages. */
|
||||
std::list<Message> _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 // __SDR_POSAG_HH__
|
||||
@ -0,0 +1,189 @@
|
||||
/* This code is public-domain - it is based on libcrypt
|
||||
* placed in the public domain by Wei Dai and other contributors.
|
||||
*/
|
||||
// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
|
||||
|
||||
#include "sha1.hh"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace sdr;
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
# define SHA_BIG_ENDIAN
|
||||
#elif defined __LITTLE_ENDIAN__
|
||||
/* override */
|
||||
#elif defined __BYTE_ORDER
|
||||
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define SHA_BIG_ENDIAN
|
||||
# endif
|
||||
#else // ! defined __LITTLE_ENDIAN__
|
||||
# include <endian.h> // machine/endian.h
|
||||
# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define SHA_BIG_ENDIAN
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
/* code */
|
||||
#define SHA1_K0 0x5a827999
|
||||
#define SHA1_K20 0x6ed9eba1
|
||||
#define SHA1_K40 0x8f1bbcdc
|
||||
#define SHA1_K60 0xca62c1d6
|
||||
|
||||
void
|
||||
sdr::sha1_init(sha1 *s) {
|
||||
s->state[0] = 0x67452301;
|
||||
s->state[1] = 0xefcdab89;
|
||||
s->state[2] = 0x98badcfe;
|
||||
s->state[3] = 0x10325476;
|
||||
s->state[4] = 0xc3d2e1f0;
|
||||
s->byteCount = 0;
|
||||
s->bufferOffset = 0;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
sha1_rol32(uint32_t number, uint8_t bits) {
|
||||
return ((number << bits) | (number >> (32-bits)));
|
||||
}
|
||||
|
||||
void
|
||||
sha1_hashBlock(sha1 *s) {
|
||||
uint8_t i;
|
||||
uint32_t a,b,c,d,e,t;
|
||||
|
||||
a=s->state[0];
|
||||
b=s->state[1];
|
||||
c=s->state[2];
|
||||
d=s->state[3];
|
||||
e=s->state[4];
|
||||
for (i=0; i<80; i++) {
|
||||
if (i>=16) {
|
||||
t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15];
|
||||
s->buffer[i&15] = sha1_rol32(t,1);
|
||||
}
|
||||
if (i<20) {
|
||||
t = (d ^ (b & (c ^ d))) + SHA1_K0;
|
||||
} else if (i<40) {
|
||||
t = (b ^ c ^ d) + SHA1_K20;
|
||||
} else if (i<60) {
|
||||
t = ((b & c) | (d & (b | c))) + SHA1_K40;
|
||||
} else {
|
||||
t = (b ^ c ^ d) + SHA1_K60;
|
||||
}
|
||||
t+=sha1_rol32(a,5) + e + s->buffer[i&15];
|
||||
e=d;
|
||||
d=c;
|
||||
c=sha1_rol32(b,30);
|
||||
b=a;
|
||||
a=t;
|
||||
}
|
||||
s->state[0] += a;
|
||||
s->state[1] += b;
|
||||
s->state[2] += c;
|
||||
s->state[3] += d;
|
||||
s->state[4] += e;
|
||||
}
|
||||
|
||||
void
|
||||
sha1_addUncounted(sha1 *s, uint8_t data) {
|
||||
uint8_t * const b = (uint8_t*) s->buffer;
|
||||
#ifdef SHA_BIG_ENDIAN
|
||||
b[s->bufferOffset] = data;
|
||||
#else
|
||||
b[s->bufferOffset ^ 3] = data;
|
||||
#endif
|
||||
s->bufferOffset++;
|
||||
if (s->bufferOffset == SDR_SHA1_BLOCK_LENGTH) {
|
||||
sha1_hashBlock(s);
|
||||
s->bufferOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sdr::sha1_writebyte(sha1 *s, uint8_t data) {
|
||||
++s->byteCount;
|
||||
sha1_addUncounted(s, data);
|
||||
}
|
||||
|
||||
void
|
||||
sdr::sha1_write(sha1 *s, const char *data, size_t len) {
|
||||
for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
|
||||
}
|
||||
|
||||
void
|
||||
sha1_pad(sha1 *s) {
|
||||
// Implement SHA-1 padding (fips180-2 §5.1.1)
|
||||
|
||||
// Pad with 0x80 followed by 0x00 until the end of the block
|
||||
sha1_addUncounted(s, 0x80);
|
||||
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
|
||||
|
||||
// Append length in the last 8 bytes
|
||||
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
|
||||
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
|
||||
sha1_addUncounted(s, 0); // So zero pad the top bits
|
||||
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
|
||||
sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
|
||||
sha1_addUncounted(s, s->byteCount >> 13); // byte.
|
||||
sha1_addUncounted(s, s->byteCount >> 5);
|
||||
sha1_addUncounted(s, s->byteCount << 3);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
sdr::sha1_result(sha1 *s) {
|
||||
// Pad to complete the last block
|
||||
sha1_pad(s);
|
||||
|
||||
#ifndef SHA_BIG_ENDIAN
|
||||
// Swap byte order back
|
||||
int i;
|
||||
for (i=0; i<5; i++) {
|
||||
s->state[i]=
|
||||
(((s->state[i])<<24)& 0xff000000)
|
||||
| (((s->state[i])<<8) & 0x00ff0000)
|
||||
| (((s->state[i])>>8) & 0x0000ff00)
|
||||
| (((s->state[i])>>24)& 0x000000ff);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Return pointer to hash (20 characters)
|
||||
return (uint8_t*) s->state;
|
||||
}
|
||||
|
||||
#define HMAC_IPAD 0x36
|
||||
#define HMAC_OPAD 0x5c
|
||||
|
||||
void
|
||||
sdr::sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength) {
|
||||
uint8_t i;
|
||||
memset(s->keyBuffer, 0,SDR_SHA1_BLOCK_LENGTH);
|
||||
if (keyLength >SDR_SHA1_BLOCK_LENGTH) {
|
||||
// Hash long keys
|
||||
sha1_init(s);
|
||||
for (;keyLength--;) sha1_writebyte(s, *key++);
|
||||
memcpy(s->keyBuffer, sha1_result(s),SDR_SHA1_HASH_LENGTH);
|
||||
} else {
|
||||
// Block length keys are used as is
|
||||
memcpy(s->keyBuffer, key, keyLength);
|
||||
}
|
||||
// Start inner hash
|
||||
sha1_init(s);
|
||||
for (i=0; i<SDR_SHA1_BLOCK_LENGTH; i++) {
|
||||
sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
sdr::sha1_resultHmac(sha1 *s) {
|
||||
uint8_t i;
|
||||
// Complete inner hash
|
||||
memcpy(s->innerHash,sha1_result(s), SDR_SHA1_HASH_LENGTH);
|
||||
// Calculate outer hash
|
||||
sha1_init(s);
|
||||
for (i=0; i<SDR_SHA1_BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
|
||||
for (i=0; i<SDR_SHA1_HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
|
||||
return sha1_result(s);
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
#ifndef __SDR_SHA1_HH__
|
||||
#define __SDR_SHA1_HH__
|
||||
|
||||
#include <memory.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace sdr {
|
||||
|
||||
/* header */
|
||||
|
||||
#define SDR_SHA1_HASH_LENGTH 20
|
||||
#define SDR_SHA1_BLOCK_LENGTH 64
|
||||
|
||||
typedef struct {
|
||||
uint32_t buffer[SDR_SHA1_BLOCK_LENGTH/4];
|
||||
uint32_t state[SDR_SHA1_HASH_LENGTH/4];
|
||||
uint32_t byteCount;
|
||||
uint8_t bufferOffset;
|
||||
uint8_t keyBuffer[SDR_SHA1_BLOCK_LENGTH];
|
||||
uint8_t innerHash[SDR_SHA1_HASH_LENGTH];
|
||||
} sha1;
|
||||
|
||||
/**
|
||||
*/
|
||||
void sha1_init(sha1 *s);
|
||||
/**
|
||||
*/
|
||||
void sha1_writebyte(sha1 *s, uint8_t data);
|
||||
/**
|
||||
*/
|
||||
void sha1_write(sha1 *s, const char *data, size_t len);
|
||||
/**
|
||||
*/
|
||||
uint8_t* sha1_result(sha1 *s);
|
||||
/**
|
||||
*/
|
||||
void sha1_initHmac(sha1 *s, const uint8_t* key, int keyLength);
|
||||
/**
|
||||
*/
|
||||
uint8_t* sha1_resultHmac(sha1 *s);
|
||||
|
||||
}
|
||||
|
||||
#endif // __SDR_SHA1_HH__
|
||||
Loading…
Reference in New Issue