Timur Davydov has uploaded this change for review.

View Change

device: add WebSDR radio backend

Introduce a new optional WebSDR device backend, enabled via
--with-websdr.

Add WebSDRDevice implementation and build integration, providing
a RadioDevice interface backed by callback hooks for control and
sample I/O.

Intended for Web-based deployments where osmo-trx interacts with
SDR hardware via a WebSDR/WebUSB frontend.

Change-Id: Ie459cbd70388dd8ff5b89221d30770bab0bd9014
---
M Transceiver52M/device/Makefile.am
A Transceiver52M/device/websdr/Makefile.am
A Transceiver52M/device/websdr/WebSDRDevice.cpp
A Transceiver52M/device/websdr/WebSDRDevice.h
M configure.ac
5 files changed, 352 insertions(+), 0 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/56/42656/1
diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am
index b5a5cec..62a013e 100644
--- a/Transceiver52M/device/Makefile.am
+++ b/Transceiver52M/device/Makefile.am
@@ -21,3 +21,7 @@
if DEVICE_BLADE
SUBDIRS += bladerf
endif
+
+if DEVICE_WEBSDR
+SUBDIRS += websdr
+endif
diff --git a/Transceiver52M/device/websdr/Makefile.am b/Transceiver52M/device/websdr/Makefile.am
new file mode 100644
index 0000000..86e4a4d
--- /dev/null
+++ b/Transceiver52M/device/websdr/Makefile.am
@@ -0,0 +1,11 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
+AM_CXXFLAGS = $(LIBOSMOCORE_CFLAGS)
+
+noinst_HEADERS = WebSDRDevice.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = WebSDRDevice.cpp
+libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
diff --git a/Transceiver52M/device/websdr/WebSDRDevice.cpp b/Transceiver52M/device/websdr/WebSDRDevice.cpp
new file mode 100644
index 0000000..ebb97b4
--- /dev/null
+++ b/Transceiver52M/device/websdr/WebSDRDevice.cpp
@@ -0,0 +1,237 @@
+/*
+* Copyright 2026 Wavelet Lab <info@wavelet-lab.com>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* This software is distributed under the terms of the GNU Affero Public License.
+* See the COPYING file in the main directory for details.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include "Logger.h"
+#include "Threads.h"
+#include "WebSDRDevice.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+using namespace std;
+
+extern uint8_t g_tx_buffer[10000];
+extern uint32_t g_tx_ts;
+extern uint32_t g_tx_len;
+
+#ifdef __EMSCRIPTEN__
+
+#include <emscripten.h>
+
+EM_JS(bool, on_start_wrapper, (), {
+ return on_start();
+});
+
+EM_JS(bool, on_stop_wrapper, (), {
+ return on_stop();
+});
+
+EM_JS(bool, on_set_rx_frequency_wrapper, (unsigned frequency), {
+ return on_set_rx_frequency(frequency);
+});
+
+EM_JS(bool, on_set_tx_frequency_wrapper, (unsigned frequency), {
+ return on_set_tx_frequency(frequency);
+});
+
+EM_JS(void, write_log_wrapper, (const char *msg), {
+ write_log(msg);
+});
+
+EM_JS(int, on_write_samples_wrapper, (short* msg, int samples, uint64_t timestamp), {
+ return on_write_samples(msg, samples, timestamp);
+});
+
+#else /* __EMSCRIPTEN__ */
+
+extern "C" {
+bool on_start();
+bool on_stop();
+bool on_set_rx_frequency(unsigned frequency);
+bool on_set_tx_frequency(unsigned frequency);
+int on_write_samples(short* msg, int samples, uint64_t timestamp);
+
+static bool on_start_wrapper() { return on_start(); }
+static bool on_stop_wrapper() { return on_stop(); }
+static bool on_set_rx_frequency_wrapper(unsigned frequency) { return on_set_rx_frequency(frequency); }
+static bool on_set_tx_frequency_wrapper(unsigned frequency) { return on_set_tx_frequency(frequency); }
+static int on_write_samples_wrapper(short* msg, int samples, uint64_t timestamp) { return on_write_samples(msg, samples, timestamp); }
+}
+
+#endif /* __EMSCRIPTEN__ */
+
+WebSDRDevice::WebSDRDevice(InterfaceType iface, const struct trx_cfg *cfg) :
+ RadioDevice(iface, cfg)
+{
+ rx_gains.resize(chans);
+ tx_gains.resize(chans);
+
+ rx_freqs.resize(chans);
+ tx_freqs.resize(chans);
+}
+
+int WebSDRDevice::open()
+{
+ return NORMAL;
+}
+
+bool WebSDRDevice::start()
+{
+ return on_start_wrapper();
+}
+
+bool WebSDRDevice::stop()
+{
+ return on_stop_wrapper();
+}
+
+int WebSDRDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
+ TIMESTAMP timestamp, bool *underrun)
+{
+ return 0;
+}
+
+int WebSDRDevice::writeSamples(std::vector<short *> &bufs, int len,
+ bool *underrun, unsigned long long timestamp)
+{
+ memcpy(g_tx_buffer, bufs[0], 2 * len * sizeof(int16_t));
+ g_tx_ts = timestamp;
+ g_tx_len = len;
+ return len;
+}
+
+bool WebSDRDevice::setTxFreq(double wFreq, size_t chan) {
+ const bool res = on_set_tx_frequency_wrapper(static_cast<unsigned>(wFreq));
+ if (res)
+ tx_freqs[chan] = wFreq;
+
+ return res;
+}
+
+bool WebSDRDevice::setRxFreq(double wFreq, size_t chan) {
+ const bool res = on_set_rx_frequency_wrapper(static_cast<unsigned>(wFreq));
+ if (res)
+ rx_freqs[chan] = wFreq;
+
+ return res;
+}
+
+double WebSDRDevice::setRxGain(double dB, size_t chan)
+{
+ rx_gains[chan] = dB;
+ return dB;
+}
+
+double WebSDRDevice::getRxGain(size_t chan)
+{
+ return rx_gains[chan];
+}
+
+double WebSDRDevice::maxRxGain()
+{
+ return 0;
+}
+
+double WebSDRDevice::minRxGain()
+{
+ return 0;
+}
+
+int WebSDRDevice::getNominalTxPower(size_t chan)
+{
+ return 0;
+}
+
+bool WebSDRDevice::setRxAntenna(const std::string &ant, size_t chan)
+{
+ rx_paths[chan] = ant;
+ return true;
+}
+
+std::string WebSDRDevice::getRxAntenna(size_t chan)
+{
+ return rx_paths[chan];
+}
+
+bool WebSDRDevice::setTxAntenna(const std::string &ant, size_t chan)
+{
+ tx_paths[chan] = ant;
+ return true;
+}
+
+std::string WebSDRDevice::getTxAntenna(size_t chan)
+{
+ return tx_paths[chan];
+}
+
+GSM::Time WebSDRDevice::minLatency() {
+ return GSM::Time(1,1);
+}
+
+double WebSDRDevice::getTxFreq(size_t chan) {
+ return tx_freqs[chan];
+}
+
+double WebSDRDevice::getRxFreq(size_t chan) {
+ return rx_freqs[chan];
+}
+
+double WebSDRDevice::setPowerAttenuation(int atten, size_t chan) {
+ tx_gains[chan] = atten;
+ return atten;
+}
+
+double WebSDRDevice::getPowerAttenuation(size_t chan) {
+ return tx_gains[chan];
+}
+
+double WebSDRDevice::maxTxGain()
+{
+ return 0;
+}
+
+double WebSDRDevice::minTxGain()
+{
+ return 0;
+}
+
+RadioDevice *RadioDevice::make(InterfaceType iface, const struct trx_cfg *cfg)
+{
+ if (cfg->tx_sps != cfg->rx_sps) {
+ return NULL;
+ }
+ if (cfg->num_chans != 1) {
+ return NULL;
+ }
+ if (cfg->offset != 0.0) {
+ return NULL;
+ }
+ return new WebSDRDevice(iface, cfg);
+}
diff --git a/Transceiver52M/device/websdr/WebSDRDevice.h b/Transceiver52M/device/websdr/WebSDRDevice.h
new file mode 100644
index 0000000..b4e0c04
--- /dev/null
+++ b/Transceiver52M/device/websdr/WebSDRDevice.h
@@ -0,0 +1,98 @@
+/*
+* Copyright 2026 Wavelet Lab <info@wavelet-lab.com>
+*
+* SPDX-License-Identifier: AGPL-3.0+
+*
+* This software is distributed under multiple licenses; see the COPYING file in
+* the main directory for licensing information for this specific distribution.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+*
+* 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.
+*
+*/
+
+#ifndef _WEBSDR_DEVICE_H_
+#define _WEBSDR_DEVICE_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "radioDevice.h"
+
+#include <sys/time.h>
+#include <math.h>
+#include <string>
+#include <iostream>
+
+
+/** A class to handle a WebSDR device */
+class WebSDRDevice: public RadioDevice {
+public:
+
+ WebSDRDevice(InterfaceType iface, const struct trx_cfg *cfg);
+
+ int open();
+ bool start();
+ bool stop();
+
+ enum TxWindowType getWindowType() { return TX_WINDOW_FIXED; }
+
+ int readSamples(std::vector<short *> &buf, int len, bool *overrun,
+ TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL);
+ int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
+ TIMESTAMP timestamp = 0xffffffff);
+
+ bool updateAlignment(TIMESTAMP timestamp) { return true; }
+
+ bool setTxFreq(double wFreq, size_t chan = 0);
+ bool setRxFreq(double wFreq, size_t chan = 0);
+
+ TIMESTAMP initialWriteTimestamp(void) { return 0; }
+ TIMESTAMP initialReadTimestamp(void) { return 0; }
+
+ double fullScaleInputValue() { return 32767.0 * 0.7071; }
+ double fullScaleOutputValue() { return 32767.0; }
+
+ double setRxGain(double dB, size_t chan = 0);
+ double getRxGain(size_t chan = 0);
+ double maxRxGain(void);
+ double minRxGain(void);
+ double rssiOffset(size_t chan) { return 0.0f; }
+
+ int getNominalTxPower(size_t chan = 0);
+
+ bool setRxAntenna(const std::string &ant, size_t chan = 0);
+ std::string getRxAntenna(size_t chan = 0);
+
+ bool setTxAntenna(const std::string &ant, size_t chan = 0);
+ std::string getTxAntenna(size_t chan = 0);
+
+ bool requiresRadioAlign() { return false; }
+
+ GSM::Time minLatency();
+
+ double getTxFreq(size_t chan = 0);
+ double getRxFreq(size_t chan = 0);
+
+ double getSampleRate() { return 0; }
+
+ double setPowerAttenuation(int atten, size_t chan);
+ double getPowerAttenuation(size_t chan=0);
+ double maxTxGain(void);
+ double minTxGain(void);
+
+private:
+ /* Cached TX/RX gains */
+ std::vector<double> tx_gains, rx_gains;
+
+ /* Cached TX/RX frequencies */
+ std::vector<double> tx_freqs, rx_freqs;
+
+};
+
+#endif // _WEBSDR_DEVICE_H_
diff --git a/configure.ac b/configure.ac
index bf3bc5e..b0d79a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -310,6 +310,7 @@
AM_CONDITIONAL(DEVICE_LMS, [test "x$with_lms" = "xyes"])
AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"])
AM_CONDITIONAL(DEVICE_BLADE, [test "x$with_bladerf" = "xyes"])
+AM_CONDITIONAL(DEVICE_WEBSDR, [test "x$with_websdr" = "xyes"])
AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"])
AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
AM_CONDITIONAL(ENABLE_MS_TRX, [test "x$with_mstrx" = "xyes"])
@@ -411,6 +412,7 @@
Transceiver52M/device/lms/Makefile \
Transceiver52M/device/ipc/Makefile \
Transceiver52M/device/bladerf/Makefile \
+ Transceiver52M/device/websdr/Makefile \
tests/Makefile \
tests/CommonLibs/Makefile \
tests/Transceiver52M/Makefile \

To view, visit change 42656. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-trx
Gerrit-Branch: master
Gerrit-Change-Id: Ie459cbd70388dd8ff5b89221d30770bab0bd9014
Gerrit-Change-Number: 42656
Gerrit-PatchSet: 1
Gerrit-Owner: Timur Davydov <dtv.comp@gmail.com>