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
|
Source: libsdr
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Hannes Matuschek <hmatuschek@gmail.com>
|
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
|
Build-Depends: cdbs (>= 0.4.51), dh-exec, debhelper (>= 9.0.0), cmake, portaudio19-dev, librtlsdr-dev, fftw3-dev
|
||||||
Standards-Version: 3.9.2
|
Standards-Version: 3.9.5
|
||||||
Section: hamradio
|
Section: hamradio
|
||||||
Homepage: http://github.com/hmatuschek/libsdr
|
Homepage: http://github.com/hmatuschek/libsdr
|
||||||
|
|
||||||
Package: libsdr
|
Package: libsdr1
|
||||||
Section: hamradio
|
Section: hamradio
|
||||||
Architecture: any
|
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
|
Description: libsdr
|
||||||
A C++ library for software defined radio.
|
A C++ library for software defined radio.
|
||||||
|
|
||||||
Package: libsdr-dev
|
Package: libsdr-dev
|
||||||
Section: hamradio
|
Section: hamradio
|
||||||
Architecture: any
|
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
|
Description: libsdr
|
||||||
A C++ library for software defined radio. Development files.
|
A C++ library for software defined radio. Development files.
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
#! /usr/bin/dh-exec
|
#! /usr/bin/dh-exec
|
||||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr.so
|
usr/lib/*/libsdr.so
|
||||||
/usr/lib/${DEB_HOST_MULTIARCH}/libsdr-gui.so
|
usr/include/*
|
||||||
/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