mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-24 18:08:27 +01:00
cleanup + beginning of hermes source
This commit is contained in:
parent
8eed0fcc9c
commit
a9e8db2a24
@ -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_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
|
||||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" 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_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_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)
|
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")
|
add_subdirectory("source_modules/hackrf_source")
|
||||||
endif (OPT_BUILD_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)
|
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||||
add_subdirectory("source_modules/limesdr_source")
|
add_subdirectory("source_modules/limesdr_source")
|
||||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
#include "new_networking.h"
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
39
readme.md
39
readme.md
@ -274,34 +274,41 @@ Modules in beta are still included in releases for the most part but not enabled
|
|||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
||||||
|------------------|------------|-------------------|----------------------------|:---------------:|:-----------------------:|:---------------------------:|
|
|---------------------|------------|-------------------|----------------------------|:---------------:|:-----------------------:|:---------------------------:|
|
||||||
| airspy_source | Working | libairspy | OPT_BUILD_AIRSPY_SOURCE | ✅ | ✅ | ✅ |
|
| airspy_source | Working | libairspy | OPT_BUILD_AIRSPY_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| airspyhf_source | Working | libairspyhf | OPT_BUILD_AIRSPYHF_SOURCE | ✅ | ✅ | ✅ |
|
| airspyhf_source | Working | libairspyhf | OPT_BUILD_AIRSPYHF_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ⚠️ (not Debian Buster) | ✅ |
|
| bladerf_source | Working | libbladeRF | OPT_BUILD_BLADERF_SOURCE | ⛔ | ⚠️ (not Debian Buster) | ✅ |
|
||||||
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
|
| file_source | Working | - | OPT_BUILD_FILE_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
| hackrf_source | Working | libhackrf | OPT_BUILD_HACKRF_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
|
| hermes_source | Unfinished | - | OPT_BUILD_HERMES_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
|
| limesdr_source | Working | liblimesuite | OPT_BUILD_LIMESDR_SOURCE | ⛔ | ✅ | ✅ |
|
||||||
| rtl_tcp_source | Working | - | OPT_BUILD_RTL_TCP_SOURCE | ✅ | ✅ | ✅ |
|
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| sdrplay_source | Working | SDRplay API | OPT_BUILD_SDRPLAY_SOURCE | ⛔ | ✅ | ✅ |
|
| rfspace_source | Working | - | OPT_BUILD_RFSPACE_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| soapy_source | Working | soapysdr | OPT_BUILD_SOAPY_SOURCE | ✅ | ✅ | ✅ |
|
| rtl_sdr_source | Working | librtlsdr | OPT_BUILD_RTL_SDR_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| spyserver_source | Working | - | OPT_BUILD_SPYSERVER_SOURCE | ✅ | ✅ | ✅ |
|
| rtl_tcp_source | Working | - | OPT_BUILD_RTL_TCP_SOURCE | ✅ | ✅ | ✅ |
|
||||||
| plutosdr_source | Working | libiio, libad9361 | OPT_BUILD_PLUTOSDR_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
|
## Sinks
|
||||||
|
|
||||||
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
| 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 | ✅ | ✅ | ✅ |
|
| audio_sink | Working | rtaudio | OPT_BUILD_AUDIO_SINK | ✅ | ✅ | ✅ |
|
||||||
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
|
| network_sink | Working | - | OPT_BUILD_NETWORK_SINK | ✅ | ✅ | ✅ |
|
||||||
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
| new_portaudio_sink | Beta | portaudio | OPT_BUILD_NEW_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||||
|
| portaudio_sink | Beta | portaudio | OPT_BUILD_PORTAUDIO_SINK | ⛔ | ✅ | ⛔ |
|
||||||
|
|
||||||
## Decoders
|
## Decoders
|
||||||
|
|
||||||
| Name | Stage | Dependencies | Option | Built by default| Built in Release | Enabled in SDR++ by default |
|
| 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 | ⛔ | ⛔ | ⛔ |
|
| falcon9_decoder | Unfinished | ffplay | OPT_BUILD_FALCON9_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
|
| kgsstv_decoder | Unfinished | - | OPT_BUILD_KGSSTV_DECODER | ⛔ | ⛔ | ⛔ |
|
||||||
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
| m17_decoder | Beta | - | OPT_BUILD_M17_DECODER | ⛔ | ✅ | ⛔ |
|
||||||
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
| meteor_demodulator | Working | - | OPT_BUILD_METEOR_DEMODULATOR | ✅ | ✅ | ⛔ |
|
||||||
| radio | Working | - | OPT_BUILD_RADIO | ✅ | ✅ | ✅ |
|
| 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 | ✅ | ✅ | ⛔ |
|
| discord_integration | Working | - | OPT_BUILD_DISCORD_PRESENCE | ✅ | ✅ | ⛔ |
|
||||||
| frequency_manager | Working | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ |
|
| frequency_manager | Working | - | OPT_BUILD_FREQUENCY_MANAGER | ✅ | ✅ | ✅ |
|
||||||
| recorder | Working | - | OPT_BUILD_RECORDER | ✅ | ✅ | ✅ |
|
| 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 | ✅ | ✅ | ✅ |
|
| scanner | Beta | - | OPT_BUILD_SCANNER | ✅ | ✅ | ✅ |
|
||||||
|
| scheduler | Unfinished | - | OPT_BUILD_SCHEDULER | ⛔ | ⛔ | ⛔ |
|
||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
|
25
source_modules/hermes_source/CMakeLists.txt
Normal file
25
source_modules/hermes_source/CMakeLists.txt
Normal 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)
|
203
source_modules/hermes_source/src/hermes.cpp
Normal file
203
source_modules/hermes_source/src/hermes.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
148
source_modules/hermes_source/src/hermes.h
Normal file
148
source_modules/hermes_source/src/hermes.h
Normal 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);
|
||||||
|
}
|
201
source_modules/hermes_source/src/main.cpp
Normal file
201
source_modules/hermes_source/src/main.cpp
Normal 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();
|
||||||
|
}
|
294
source_modules/hermes_source/src/net.cpp
Normal file
294
source_modules/hermes_source/src/net.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
155
source_modules/hermes_source/src/net.h
Normal file
155
source_modules/hermes_source/src/net.h
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user