cleanup + beginning of hermes source

This commit is contained in:
AlexandreRouma 2022-10-18 11:09:22 +02:00
parent 8eed0fcc9c
commit a9e8db2a24
10 changed files with 1055 additions and 113 deletions

View File

@ -30,6 +30,7 @@ option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies:
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
@ -92,6 +93,10 @@ if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("source_modules/hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)

View File

@ -1,2 +0,0 @@
#include "new_networking.h"

View File

@ -1,96 +0,0 @@
#pragma once
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
/*
Ryzerth's Epic Networking Functions
*/
namespace net {
enum SocketType {
SOCK_TYPE_TCP,
SOCK_TYPE_UDP
};
struct ReadHandler {
int count;
void* buf;
void (*handle)(int count, void* buf, void* ctx);
void* ctx;
};
class SocketClass {
public:
SocketClass(struct addrinfo* localAddr, struct addrinfo* remoteAddr, SocketType sockType);
~SocketClass();
int read(int count, void* buf, int timeout);
int write(int count, void* buf);
void readAsync(int count, void* buf, void (*handle)(int count, void* buf, void* ctx), void* ctx);
bool isOpen();
void close();
private:
void readWorker();
bool open = false;
struct addrinfo* laddr;
struct addrinfo* raddr;
SocketType type;
std::queue<ReadHandler> readQueue;
std::thread readThread;
std::mutex readMtx;
std::condition_variable readCnd;
};
typedef std::shared_ptr<SocketClass> Socket;
namespace tcp {
struct AcceptHandler {
void (*handle)(Socket client, void* ctx);
void* ctx;
};
class ListenerClass {
public:
ListenerClass(struct addrinfo* addr);
~ListenerClass();
Socket accept(int count, void* buf, int timeout);
void acceptAsync(void (*handle)(int count, void* buf, void* ctx), void* ctx);
bool isOpen();
void close();
private:
void acceptWorker();
bool open = false;
struct addrinfo* addr;
std::queue<AcceptHandler> acceptQueue;
std::thread acceptThread;
std::mutex acceptMtx;
std::condition_variable acceptCnd;
};
typedef std::shared_ptr<ListenerClass> Listener;
Socket connect(std::string host, int port);
Listener listen(std::string host, int port);
}
namespace udp {
Socket open(std::string remoteHost, int remotePort, std::string localHost = "", int localPort = -1);
}
}

View File

@ -274,34 +274,41 @@ Modules in beta are still included in releases for the most part but not enabled
## Sources
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|------------------|------------|-------------------|----------------------------|:---------------:|:-----------------------:|:---------------------------:|
| airspy_source | Working | libairspy | OPT_BUILD_AIRSPY_SOURCE | ✅ | ✅ | ✅ |
| airspyhf_source | Working | libairspyhf | OPT_BUILD_AIRSPYHF_SOURCE | ✅ | ✅ | ✅ |
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ⚠️ (not Debian Buster) | ✅ |
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
| rtl_tcp_source | Working | - | OPT_BUILD_RTL_TCP_SOURCE | ✅ | ✅ | ✅ |
| sdrplay_source | Working | SDRplay API | OPT_BUILD_SDRPLAY_SOURCE | ⛔ | ✅ | ✅ |
| soapy_source | Working | soapysdr | OPT_BUILD_SOAPY_SOURCE | ✅ | ✅ | ✅ |
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|---------------------|------------|-------------------|----------------------------|:---------------:|:-----------------------:|:---------------------------:|
| airspy_source | Working | libairspy | OPT_BUILD_AIRSPY_SOURCE | ✅ | ✅ | ✅ |
| airspyhf_source | Working | libairspyhf | OPT_BUILD_AIRSPYHF_SOURCE | ✅ | ✅ | ✅ |
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ⚠️ (not Debian Buster) | ✅ |
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
| hermes_source | Unfinished | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
| rtl_tcp_source | Working | - | OPT_BUILD_RTL_TCP_SOURCE | ✅ | ✅ | ✅ |
| sdrplay_source | Working | SDRplay API | OPT_BUILD_SDRPLAY_SOURCE | ⛔ | ✅ | ✅ |
| sdrpp_server_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
| soapy_source | Working | soapysdr | OPT_BUILD_SOAPY_SOURCE | ✅ | ✅ | ✅ |
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
## Sinks
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|--------------------|------------|--------------|------------------------------|:---------------:|:----------------:|:---------------------------:|
| android_audio_sink | Working | - | OPT_BUILD_ANDROID_AUDIO_SINK | ⛔ | ✅ | ⛔ |
| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ |
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
| portaudio_sink | Beta | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
## Decoders
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|---------------------|------------|--------------|-------------------------------|:---------------:|:----------------:|:---------------------------:|
| dmr_decoder | Unfinished | - | OPT_BUILD_DMR_DECODER | ⛔ | ⛔ | ⛔ |
| falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ |
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
@ -314,8 +321,10 @@ Modules in beta are still included in releases for the most part but not enabled
| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE | ✅ | ✅ | ⛔ |
| frequency_manager | Working | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ |
| recorder | Working | - | OPT_BUILD_RECORDER | ✅ | ✅ | ✅ |
| rigctl_server | Working | - | OPT_BUILD_RIGCTL_SERVER | ✅ | ✅ | ⛔ |
| rigctl_client | Unfinished | - | OPT_BUILD_RIGCTL_CLIENT | ⛔ | ⛔ | ⛔ |
| rigctl_server | Working | - | OPT_BUILD_RIGCTL_SERVER | ✅ | ✅ | ✅ |
| scanner | Beta | - | OPT_BUILD_SCANNER | ✅ | ✅ | ✅ |
| scheduler | Unfinished | - | OPT_BUILD_SCHEDULER | ⛔ | ⛔ | ⛔ |
# Troubleshooting

View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.13)
project(hermes_source)
file(GLOB SRC "src/*.cpp")
add_library(hermes_source SHARED ${SRC})
target_link_libraries(hermes_source PRIVATE sdrpp_core)
set_target_properties(hermes_source PROPERTIES PREFIX "")
target_include_directories(hermes_source PRIVATE "src/")
if (MSVC)
target_compile_options(hermes_source PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(hermes_source PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
target_compile_options(hermes_source PRIVATE -O3 -std=c++17)
endif ()
if(WIN32)
target_link_libraries(hermes_source PRIVATE wsock32 ws2_32)
endif()
# Install directives
install(TARGETS hermes_source DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,203 @@
#include "hermes.h"
#include <spdlog/spdlog.h>
namespace hermes {
Client::Client(std::shared_ptr<net::Socket> sock) {
this->sock = sock;
// Start worker
workerThread = std::thread(&Client::worker, this);
}
void Client::close() {
if (!open) { return; }
sock->close();
// Wait for worker to exit
if (workerThread.joinable()) { workerThread.join(); }
open = false;
}
void Client::start() {
sendMetisControl((MetisControl)(METIS_CTRL_IQ | METIS_CTRL_NO_WD));
}
void Client::stop() {
sendMetisControl(METIS_CTRL_NONE);
}
void Client::setFrequency(double freq) {
writeReg(HL_REG_TX1_NCO_FREQ, freq);
}
void Client::setGain(int gain) {
writeReg(HL_REG_RX_LNA, gain | (1 << 6));
}
void Client::sendMetisUSB(uint8_t endpoint, void* frame0, void* frame1) {
// Build packet
uint32_t seq = usbSeq++;
MetisUSBPacket pkt;
pkt.hdr.signature = htons(HERMES_METIS_SIGNATURE);
pkt.hdr.type = METIS_PKT_USB;
pkt.endpoint = endpoint;
pkt.seq = htonl(seq);
if (frame0) { memcpy(pkt.frame[0], frame0, 512); }
else { memset(pkt.frame[0], 0, 512); }
if (frame1) { memcpy(pkt.frame[1], frame1, 512); }
else { memset(pkt.frame[1], 0, 512); }
// Send packet
sock->send((uint8_t*)&pkt, sizeof(pkt));
}
void Client::sendMetisControl(MetisControl ctrl) {
// Build packet
MetisControlPacket pkt;
pkt.hdr.signature = htons(HERMES_METIS_SIGNATURE);
pkt.hdr.type = METIS_PKT_CONTROL;
pkt.ctrl = ctrl;
// Send packet
sock->send((uint8_t*)&pkt, sizeof(pkt));
}
uint32_t Client::readReg(uint8_t addr) {
uint8_t frame[512];
memset(frame, 0, sizeof(frame));
HPSDRUSBHeader* hdr = (HPSDRUSBHeader*)frame;
hdr->sync[0] = HERMES_HPSDR_USB_SYNC;
hdr->sync[1] = HERMES_HPSDR_USB_SYNC;
hdr->sync[2] = HERMES_HPSDR_USB_SYNC;
hdr->c0 = (addr << 1) | (1 << 7);
sendMetisUSB(2, frame);
return 0;
}
void Client::writeReg(uint8_t addr, uint32_t val) {
uint8_t frame[512];
memset(frame, 0, sizeof(frame));
HPSDRUSBHeader* hdr = (HPSDRUSBHeader*)frame;
hdr->sync[0] = HERMES_HPSDR_USB_SYNC;
hdr->sync[1] = HERMES_HPSDR_USB_SYNC;
hdr->sync[2] = HERMES_HPSDR_USB_SYNC;
hdr->c0 = addr << 1;
*(uint32_t*)hdr->c = htonl(val);
sendMetisUSB(2, frame);
}
void Client::worker() {
uint8_t rbuf[2048];
MetisUSBPacket* pkt = (MetisUSBPacket*)rbuf;
while (true) {
// Wait for a packet or exit if connection closed
int len = sock->recv(rbuf, 2048);
if (len <= 0) { break; }
// Ignore anything that's not a USB packet
if (htons(pkt->hdr.signature) != HERMES_METIS_SIGNATURE || pkt->hdr.type != METIS_PKT_USB) {
continue;
}
// Parse frames
for (int frn = 0; frn < 2; frn++) {
uint8_t* frame = pkt->frame[frn];
HPSDRUSBHeader* hdr = (HPSDRUSBHeader*)frame;
// Make sure this is a valid frame by checking the sync
if (hdr->sync[0] != 0x7F || hdr->sync[1] != 0x7F || hdr->sync[2] != 0x7F) {
continue;
}
// Check if this is a response
if (hdr->c0 & (1 << 7)) {
uint8_t reg = (hdr->c0 >> 1) & 0x3F;
spdlog::warn("Got response! Reg={0}, Seq={1}", reg, htonl(pkt->seq));
}
// Decode and send IQ to stream
// TODO: More efficient way?
uint8_t* iq = &frame[8];
for (int i = 0; i < 63; i++) {
// Convert to 32bit
int32_t si = ((uint32_t)iq[(i*8) + 0] << 16) | ((uint32_t)iq[(i*8) + 1] << 8) | (uint32_t)iq[(i*8) + 2];
int32_t sq = ((uint32_t)iq[(i*8) + 3] << 16) | ((uint32_t)iq[(i*8) + 4] << 8) | (uint32_t)iq[(i*8) + 5];
// Sign extend
si = (si << 8) >> 8;
sq = (sq << 8) >> 8;
// Convert to float (IQ swapper for some reason... I means in-phase :facepalm:)
out.writeBuf[i].im = (float)si / (float)0x1000000;
out.writeBuf[i].re = (float)sq / (float)0x1000000;
}
out.swap(63);
}
}
}
std::vector<Info> discover() {
auto sock = net::openudp("192.168.0.255", 1024);
// Build discovery packet
uint8_t discoveryPkt[64];
memset(discoveryPkt, 0, sizeof(discoveryPkt));
*(uint16_t*)&discoveryPkt[0] = htons(HERMES_METIS_SIGNATURE);
discoveryPkt[2] = METIS_PKT_DISCOVER;
// Send the packet 5 times to make sure it's received
for (int i = 0; i < HERMES_DISCOVER_REPEAT; i++) {
sock->send(discoveryPkt, sizeof(discoveryPkt));
}
std::vector<Info> devices;
while (true) {
// Wait for a response
uint8_t resp[1024];
int len = sock->recv(resp, sizeof(resp), false, HERMES_DISCOVER_TIMEOUT);
// Give up if timeout or error
if (len <= 0) { break; }
// Verify that it is a valid response
if (len < 60) { continue; }
if (resp[0] != 0xEF || resp[1] != 0xFE) { continue; }
// Analyze
Info info;
memcpy(info.mac, &resp[3], 6);
info.gatewareVerMaj = resp[0x09];
info.gatewareVerMin = resp[0x15];
// Check if the device is already in the list
bool found = false;
for (const auto& d : devices) {
if (!memcmp(info.mac, d.mac, 6)) {
found = true;
break;
}
}
if (found) { continue; }
devices.push_back(info);
}
return devices;
}
std::shared_ptr<Client> open(std::string host, int port) {
// Open UDP socket
auto sock = net::openudp(host, port);
// TODO: Check if open successful
return std::make_shared<Client>(sock);
}
}

View File

@ -0,0 +1,148 @@
#pragma once
#include "net.h"
#include <dsp/stream.h>
#include <dsp/types.h>
#include <memory>
#include <vector>
#include <string>
#define HERMES_DISCOVER_REPEAT 5
#define HERMES_DISCOVER_TIMEOUT 1000
#define HERMES_METIS_SIGNATURE 0xEFFE
#define HERMES_HPSDR_USB_SYNC 0x7F
namespace hermes {
enum MetisPacketType {
METIS_PKT_USB = 0x01,
METIS_PKT_DISCOVER = 0x02,
METIS_PKT_CONTROL = 0x04
};
enum MetisControl {
METIS_CTRL_NONE = 0,
METIS_CTRL_IQ = (1 << 0),
METIS_CTRL_WIDEBAND = (1 << 1),
METIS_CTRL_NO_WD = (1 << 7)
};
enum BoardID {
BOARD_ID_HERMES_EMUL = 1,
BOARD_ID_HL2 = 6
};
struct Info {
uint8_t mac[6];
uint8_t gatewareVerMaj;
uint8_t gatewareVerMin;
BoardID boardId;
};
enum HermesLiteReg {
HL_REG_TX1_NCO_FREQ = 0x01,
HL_REG_RX1_NCO_FREQ = 0x02,
HL_REG_RX2_NCO_FREQ = 0x03,
HL_REG_RX3_NCO_FREQ = 0x04,
HL_REG_RX4_NCO_FREQ = 0x05,
HL_REG_RX5_NCO_FREQ = 0x06,
HL_REG_RX6_NCO_FREQ = 0x07,
HL_REG_RX7_NCO_FREQ = 0x08,
HL_REG_RX_LNA = 0x0A,
HL_REG_TX_LNA = 0x0E,
HL_REG_CWX = 0x0F,
HL_REG_CW_HANG_TIME = 0x10,
HL_REG_RX8_NCO_FREQ = 0x12,
HL_REG_RX9_NCO_FREQ = 0x13,
HL_REG_RX10_NCO_FREQ = 0x14,
HL_REG_RX11_NCO_FREQ = 0x15,
HL_REG_RX12_NCO_FREQ = 0x16,
HL_REG_PREDISTORTION = 0x2B,
HL_REG_RESET_ON_DCNT = 0x3A,
HL_REG_AD9866_SPI = 0x3B,
HL_REG_I2C_1 = 0x3C,
HL_REG_I2C_2 = 0x3D,
HL_REG_ERROR = 0x3F,
};
enum HermesLiteSamplerate {
HL_SAMP_RATE_48KHZ = 0,
HL_SAMP_RATE_96KHZ = 1,
HL_SAMP_RATE_192KHZ = 2,
HL_SAMP_RATE_384KHZ = 3
};
#pragma pack(push, 1)
struct HPSDRUSBHeader {
uint8_t sync[3];
uint8_t c0;
uint8_t c[4];
};
struct MetisPacketHeader {
uint16_t signature;
uint8_t type;
};
struct MetisUSBPacket {
MetisPacketHeader hdr;
uint8_t endpoint;
uint32_t seq;
uint8_t frame[2][512];
};
struct MetisControlPacket {
MetisPacketHeader hdr;
uint8_t ctrl;
uint8_t rsvd[60];
};
// struct MetisDiscoverPacket {
// MetisPacketHeader hdr;
// union {
// {
// uint16_t mac[6];
// uint8_t gatewareMajVer;
// uint8_t boardId;
// };
// uint8_t rsvd[60];
// };
// };
#pragma pack(pop)
class Client {
public:
Client(std::shared_ptr<net::Socket> sock);
void close();
void start();
void stop();
void setFrequency(double freq);
void setGain(int gain);
dsp::stream<dsp::complex_t> out;
//private:
void sendMetisUSB(uint8_t endpoint, void* frame0, void* frame1 = NULL);
void sendMetisControl(MetisControl ctrl);
uint32_t readReg(uint8_t addr);
void writeReg(uint8_t addr, uint32_t val);
void worker();
bool open = true;
std::thread workerThread;
std::shared_ptr<net::Socket> sock;
uint32_t usbSeq = 0;
};
std::vector<Info> discover();
std::shared_ptr<Client> open(std::string host, int port);
}

View File

@ -0,0 +1,201 @@
#include "hermes.h"
#include <spdlog/spdlog.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <config.h>
#include <gui/smgui.h>
#include <gui/widgets/stepped_slider.h>
#include <dsp/routing/stream_link.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "hermes_source",
/* Description: */ "Hermes Lite 2 source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
class HermesSourceModule : public ModuleManager::Instance {
public:
HermesSourceModule(std::string name) {
this->name = name;
lnk.init(NULL, &stream);
sampleRate = 384000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
refresh();
// TODO: Select device
sigpath::sourceManager.registerSource("Hermes", &handler);
}
~HermesSourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("Hermes");
}
void postInit() {}
enum AGCMode {
AGC_MODE_OFF,
AGC_MODE_LOW,
AGC_MODE_HIGG
};
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
void refresh() {
devList.clear();
devListTxt = "";
// TOOD: Update dev list
}
// TODO: Implement select functions
private:
static void menuSelected(void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
spdlog::info("HermesSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
spdlog::info("HermesSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
if (_this->running) { return; }
// TODO: Implement start
_this->dev = hermes::open("192.168.0.144", 1024);
// TODO: STOP USING A LINK, FIND A BETTER WAY
_this->lnk.setInput(&_this->dev->out);
_this->lnk.start();
_this->dev->start();
// TODO: Check if the USB commands are accepted before start
_this->dev->setFrequency(_this->freq);
_this->dev->setGain(_this->gain);
_this->dev->writeReg(0, 3 << 24);
_this->running = true;
spdlog::info("HermesSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
// TODO: Implement stop
_this->dev->stop();
_this->dev->close();
_this->lnk.stop();
spdlog::info("HermesSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
if (_this->running) {
// TODO: Check if dev exists
_this->dev->setFrequency(_this->freq);
}
_this->freq = freq;
spdlog::info("HermesSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
HermesSourceModule* _this = (HermesSourceModule*)ctx;
if (_this->running) { SmGui::BeginDisabled(); }
// TODO: Device selection
if (SmGui::Button("Discover")) {
auto disc = hermes::discover();
spdlog::warn("Found {0} devices!", disc.size());
}
if (_this->running) { SmGui::EndDisabled(); }
// TODO: Device parameters
if (SmGui::SliderInt("Gain##hermes_source", &_this->gain, 0, 60)) {
_this->dev->setGain(_this->gain);
}
if (SmGui::Button("Hermes Test")) {
_this->dev->readReg(hermes::HL_REG_RX1_NCO_FREQ);
}
}
std::string name;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
dsp::routing::StreamLink<dsp::complex_t> lnk;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
int gain = 0;
std::shared_ptr<hermes::Client> dev;
std::vector<uint64_t> devList;
std::string devListTxt;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(core::args["root"].s() + "/hermes_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new HermesSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (HermesSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,294 @@
#include "net.h"
#include <string.h>
#ifdef _WIN32
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
#else
#define WOULD_BLOCK (errno == EWOULDBLOCK)
#endif
namespace net {
bool _init = false;
// === Private functions ===
void init() {
if (_init) { return; }
#ifdef _WIN32
// Initialize WinSock2
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)) {
throw std::runtime_error("Could not initialize WinSock2");
return;
}
#else
// Disable SIGPIPE to avoid closing when the remote host disconnects
signal(SIGPIPE, SIG_IGN);
#endif
_init = true;
}
bool queryHost(uint32_t* addr, std::string host) {
hostent* ent = gethostbyname(host.c_str());
if (!ent || !ent->h_addr_list[0]) { return false; }
*addr = *(uint32_t*)ent->h_addr_list[0];
return true;
}
void closeSocket(SockHandle_t sock) {
#ifdef _WIN32
closesocket(sock);
#else
shutdown(sock, SHUT_RDWR);
close(sock);
#endif
}
// === Socket functions ===
Socket::Socket(SockHandle_t sock, struct sockaddr_in* raddr) {
this->sock = sock;
if (raddr) {
this->raddr = (struct sockaddr_in*)malloc(sizeof(sockaddr_in));
memcpy(this->raddr, raddr, sizeof(sockaddr_in));
}
}
Socket::~Socket() {
close();
if (raddr) { free(raddr); }
}
void Socket::close() {
if (!open) { return; }
open = false;
closeSocket(sock);
}
bool Socket::isOpen() {
return open;
}
SocketType Socket::type() {
return raddr ? SOCKET_TYPE_UDP : SOCKET_TYPE_TCP;
}
int Socket::send(const uint8_t* data, size_t len) {
return sendto(sock, (const char*)data, len, 0, (sockaddr*)raddr, sizeof(sockaddr_in));
}
int Socket::sendstr(const std::string& str) {
return send((const uint8_t*)str.c_str(), str.length());
}
int Socket::recv(uint8_t* data, size_t maxLen, bool forceLen, int timeout) {
// Create FD set
fd_set set;
FD_ZERO(&set);
FD_SET(sock, &set);
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
int read = 0;
bool blocking = (timeout != NONBLOCKING);
do {
// Wait for data or error if
if (blocking) {
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
if (err <= 0) { return err; }
}
// Receive
int err = ::recv(sock, (char*)&data[read], maxLen - read, 0);
if (err <= 0 && !WOULD_BLOCK) {
close();
return err;
}
read += err;
}
while (blocking && forceLen && read < maxLen);
return read;
}
int Socket::recvline(std::string& str, int maxLen, int timeout) {
// Disallow nonblocking mode
if (timeout < 0) { return -1; }
str.clear();
int read = 0;
while (true) {
char c;
int err = recv((uint8_t*)&c, 1, timeout);
if (err <= 0) { return err; }
if (c == '\n') { break; }
str += c;
read++;
if (maxLen && read >= maxLen) { break; }
}
return read;
}
// === Listener functions ===
Listener::Listener(SockHandle_t sock) {
this->sock = sock;
}
Listener::~Listener() {
stop();
}
void Listener::stop() {
closeSocket(sock);
open = false;
}
bool Listener::listening() {
return open;
}
std::shared_ptr<Socket> Listener::accept(int timeout) {
// Create FD set
fd_set set;
FD_ZERO(&set);
FD_SET(sock, &set);
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
// Wait for data or error
// TODO: Support non-blockign mode
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
if (err <= 0) { return NULL; }
// Accept
SockHandle_t s = ::accept(sock, NULL, 0);
if (!s) {
stop();
return NULL;
}
// Enable nonblocking mode
#ifdef _WIN32
u_long enabled = 1;
ioctlsocket(s, FIONBIO, &enabled);
#else
fcntl(s, F_SETFL, O_NONBLOCK);
#endif
return std::make_shared<Socket>(s);
}
// === Creation functions ===
std::shared_ptr<Listener> listen(std::string host, int port) {
// Init library if needed
init();
// Get host address
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (!queryHost((uint32_t*)&addr.sin_addr.s_addr, host)) { return NULL; }
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// TODO: Support non-blockign mode
#ifndef _WIN32
// Allow port reusing if the app was killed or crashed
// and the socket is stuck in TIME_WAIT state.
// This option has a different meaning on Windows,
// so we use it only for non-Windows systems
int enable = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
closeSocket(s);
throw std::runtime_error("Could not configure socket");
return NULL;
}
#endif
// Bind socket to the port
if (bind(s, (sockaddr*)&addr, sizeof(addr))) {
closeSocket(s);
throw std::runtime_error("Could not bind socket");
return NULL;
}
// Enable listening
if (::listen(s, SOMAXCONN) != 0) {
throw std::runtime_error("Could start listening for connections");
return NULL;
}
// Return listener class
return std::make_shared<Listener>(s);
}
std::shared_ptr<Socket> connect(std::string host, int port) {
// Init library if needed
init();
// Get host address
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (!queryHost((uint32_t*)&addr.sin_addr.s_addr, host)) { return NULL; }
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Connect to server
if (::connect(s, (sockaddr*)&addr, sizeof(addr))) {
closeSocket(s);
throw std::runtime_error("Could not connect");
return NULL;
}
// Enable nonblocking mode
#ifdef _WIN32
u_long enabled = 1;
ioctlsocket(s, FIONBIO, &enabled);
#else
fcntl(s, F_SETFL, O_NONBLOCK);
#endif
// Return socket class
return std::make_shared<Socket>(s);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
// Init library if needed
init();
// Get local address
struct sockaddr_in laddr;
laddr.sin_family = AF_INET;
laddr.sin_port = htons(lport);
if (!queryHost((uint32_t*)&laddr.sin_addr.s_addr, lhost)) { return NULL; }
// Get remote address
struct sockaddr_in raddr;
raddr.sin_family = AF_INET;
raddr.sin_port = htons(rport);
if (!queryHost((uint32_t*)&raddr.sin_addr.s_addr, rhost)) { return NULL; }
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// Bind socket to local port
if (bind(s, (sockaddr*)&laddr, sizeof(laddr))) {
closeSocket(s);
throw std::runtime_error("Could not bind socket");
return NULL;
}
// Return socket class
return std::make_shared<Socket>(s, &raddr);
}
}

View File

@ -0,0 +1,155 @@
#pragma once
#include <stdint.h>
#include <mutex>
#include <memory>
#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#else
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <poll.h>
#include <fcntl.h>
#endif
namespace net {
#ifdef _WIN32
typedef SOCKET SockHandle_t;
#else
typedef int SockHandle_t;
#endif
enum {
NO_TIMEOUT = -1,
NONBLOCKING = 0
};
enum SocketType {
SOCKET_TYPE_TCP,
SOCKET_TYPE_UDP
};
class Socket {
public:
Socket(SockHandle_t sock, struct sockaddr_in* raddr = NULL);
~Socket();
/**
* Close socket. The socket can no longer be used after this.
*/
void close();
/**
* Check if the socket is open.
* @return True if open, false if closed.
*/
bool isOpen();
/**
* Get socket type. Either TCP or UDP.
* @return Socket type.
*/
SocketType type();
/**
* Send data on socket.
* @param data Data to be sent.
* @param len Number of bytes to be sent.
* @return Number of bytes sent.
*/
int send(const uint8_t* data, size_t len);
/**
* Send string on socket. Terminating null byte is not sent, include one in the string if you need it.
* @param str String to be sent.
* @return Number of bytes sent.
*/
int sendstr(const std::string& str);
/**
* Receive data from socket.
* @param data Buffer to read the data into.
* @param maxLen Maximum number of bytes to read.
* @param forceLen Read the maximum number of bytes even if it requires multiple receive operations.
* @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed.
* @return Number of bytes read. 0 means timed out or closed. -1 means would block or error.
*/
int recv(uint8_t* data, size_t maxLen, bool forceLen = false, int timeout = NO_TIMEOUT);
/**
* Receive line from socket.
* @param str String to read the data into.
* @param maxLen Maximum line length allowed, 0 for no limit.
* @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed.
* @return Length of the returned string. 0 means timed out or closed. -1 means would block or error.
*/
int recvline(std::string& str, int maxLen = 0, int timeout = NO_TIMEOUT);
private:
struct sockaddr_in* raddr = NULL;
SockHandle_t sock;
bool open = true;
};
class Listener {
public:
Listener(SockHandle_t sock);
~Listener();
/**
* Stop listening. The listener can no longer be used after this.
*/
void stop();
/**
* CHeck if the listener is still listening.
* @return True if listening, false if not.
*/
bool listening();
/**
* Accept connection.
* @param timeout Timeout in milliseconds. 0 means no timeout.
* @return Socket of the connection. NULL means timed out or closed.
*/
std::shared_ptr<Socket> accept(int timeout = NO_TIMEOUT);
private:
SockHandle_t sock;
bool open = true;
};
/**
* Create TCP listener.
* @param host Hostname or IP to listen on ("0.0.0.0" for Any).
* @param port Port to listen on.
* @return Listener instance on success, null otherwise.
*/
std::shared_ptr<Listener> listen(std::string host, int port);
/**
* Create TCP connection.
* @param host Remote hostname or IP address.
* @param port Remote port.
* @return Socket instance on success, null otherwise.
*/
std::shared_ptr<Socket> connect(std::string host, int port);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket (optional, 0 to allocate automatically).
* @return Socket instance on success, null otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
}