Timur Davydov has uploaded this change for review.

View Change

device/websdr: add WebSDR transceiver backend and build target

- add libosmo-trx-websdr library and pkg-config file
- integrate WebSDR device into the autotools build system
- update .gitignore for wasm and pkg-config artifacts

Change-Id: Ia0d340c323c2eea28fbe82601ba0af7cfbd68f6d
---
M .gitignore
M Transceiver52M/Makefile.am
M Transceiver52M/device/Makefile.am
A Transceiver52M/device/websdr/Makefile.am
A Transceiver52M/device/websdr/TransceiverWebSdr.cpp
A Transceiver52M/device/websdr/TransceiverWebSdr.h
A Transceiver52M/device/websdr/libosmo-trx-websdr.pc.in
A Transceiver52M/device/websdr/osmo-trx-websdr.cpp
A Transceiver52M/device/websdr/osmo-trx-websdr.h
A Transceiver52M/device/websdr/radioInterfaceWebSdr.cpp
A Transceiver52M/device/websdr/radioInterfaceWebSdr.h
M Transceiver52M/radioInterface.h
M configure.ac
13 files changed, 852 insertions(+), 1 deletion(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-trx refs/changes/11/42411/1
diff --git a/.gitignore b/.gitignore
index fc567db..1995586 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
*.o
*.lo
*.la
+*.pc
+*.wasm
+*.out.js
Transceiver52M/osmo-trx-uhd
Transceiver52M/osmo-trx-usrp1
Transceiver52M/osmo-trx-lms
@@ -35,6 +38,7 @@

# automake/autoconf
*.in
+!*.pc.in
.deps
.libs
.dirstamp
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am
index 74152b3..ac410f1 100644
--- a/Transceiver52M/Makefile.am
+++ b/Transceiver52M/Makefile.am
@@ -201,3 +201,15 @@
$(COMMON_LDADD)
osmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS)
endif
+
+if DEVICE_WEBSDR
+include_HEADERS = device/websdr/osmo-trx-websdr.h
+lib_LTLIBRARIES = libosmo-trx-websdr.la
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = device/websdr/libosmo-trx-websdr.pc
+libosmo_trx_websdr_la_SOURCES =
+libosmo_trx_websdr_la_LIBADD = \
+ $(builddir)/device/websdr/libdevice.la \
+ $(COMMON_LDADD)
+ $(NULL)
+endif
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..422b16f
--- /dev/null
+++ b/Transceiver52M/device/websdr/Makefile.am
@@ -0,0 +1,14 @@
+include $(top_srcdir)/Makefile.common
+
+AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common -I${srcdir}/../.. -I${srcdir}/../../arch/common
+AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS)
+
+noinst_HEADERS = osmo-trx-websdr.h
+
+noinst_LTLIBRARIES = libdevice.la
+
+libdevice_la_SOURCES = \
+ radioInterfaceWebSdr.cpp \
+ TransceiverWebSdr.cpp \
+ osmo-trx-websdr.cpp \
+ $(NULL)
\ No newline at end of file
diff --git a/Transceiver52M/device/websdr/TransceiverWebSdr.cpp b/Transceiver52M/device/websdr/TransceiverWebSdr.cpp
new file mode 100644
index 0000000..0518de4
--- /dev/null
+++ b/Transceiver52M/device/websdr/TransceiverWebSdr.cpp
@@ -0,0 +1,322 @@
+/*
+ * WebSDR radio transceiver implementation
+ *
+ * Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
+ * Copyright 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ *
+ * This software is distributed under the terms of the GNU 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TransceiverWebSdr.h"
+
+#include <fstream>
+#include <grgsm_vitac/grgsm_vitac.h>
+
+extern "C" {
+#include "proto_trxd.h"
+#include "osmocom/core/bits.h"
+}
+
+using namespace GSM;
+
+enum {
+ RET_OK = 0,
+ RET_OK_UPDSTAT = 1,
+ RET_FAILED = -1
+};
+
+TransceiverWebSdr::TransceiverWebSdr(const struct trx_cfg *cfg,
+ GSM::Time wTransmitLatency,
+ RadioInterfaceWebSdr *wRadioInterface)
+ : Transceiver(cfg, wTransmitLatency, wRadioInterface)
+{
+}
+
+/*
+ * Initialize transceiver
+ *
+ * Start or restart the control loop. Any further control is handled through the
+ * socket API. Randomize the central radio clock set the downlink burst
+ * counters. Note that the clock will not update until the radio starts, but we
+ * are still expected to report clock indications through control channel
+ * activity.
+ */
+bool TransceiverWebSdr::init()
+{
+ if (!mChans) {
+ LOG(FATAL) << "No channels assigned";
+ return false;
+ }
+
+ if (!sigProcLibSetup()) {
+ LOG(FATAL) << "Failed to initialize signal processing library";
+ return false;
+ }
+
+ initvita();
+
+ /* Filler table retransmissions - support only on channel 0 */
+ if (cfg->filler == FILLER_DUMMY)
+ mStates[0].mRetrans = true;
+
+ /* Randomize the central clock */
+ GSM::Time startTime(random() % gHyperframe, 0);
+ mRadioInterfaceWebSdr->getClock()->set(startTime);
+ mTransmitDeadlineClock = startTime;
+ mLastClockUpdateTime = startTime;
+ mLatencyUpdateTime = startTime;
+
+ return true;
+}
+
+bool TransceiverWebSdr::start()
+{
+ if (mOn) {
+ LOG(ERR) << "Transceiver already running";
+ return true;
+ }
+
+ LOG(NOTICE) << "Starting the transceiver";
+
+ GSM::Time time = mRadioInterfaceWebSdr->getClock()->get();
+ mTransmitDeadlineClock = time;
+ mLastClockUpdateTime = time;
+ mLatencyUpdateTime = time;
+
+ if (!mRadioInterfaceWebSdr->start()) {
+ LOG(FATAL) << "Device failed to start";
+ return false;
+ }
+
+ mForceClockInterface = true;
+ mOn = true;
+ return true;
+}
+
+void TransceiverWebSdr::stop()
+{
+ if (!mOn)
+ return;
+
+ LOG(NOTICE) << "Stopping the transceiver";
+ LOG(INFO) << "Stopping the device";
+ mRadioInterfaceWebSdr->stop();
+
+ mOn = false;
+ LOG(NOTICE) << "Transceiver stopped";
+}
+
+int TransceiverWebSdr::driveReceiveRadioShortVector(short **buffers, int samples, int underrun,
+ TIMESTAMP ts, unsigned samples_skipped)
+{
+ int rc;
+
+ rc = mRadioInterfaceWebSdr->fillPullBuffer(buffers, samples, underrun, ts);
+ if (rc)
+ return rc;
+
+ rc = mRadioInterfaceWebSdr->driveReceiveRadioWithBuffer(samples_skipped);
+ if (rc)
+ return rc;
+
+ if (mForceClockInterface || mTransmitDeadlineClock > mLastClockUpdateTime + GSM::Time(216, 0)) {
+ if (mForceClockInterface)
+ LOGC(DTRXCLK, NOTICE) << "Sending CLOCK indications";
+ mForceClockInterface = false;
+ if (on_write_clock((unsigned long long)(mTransmitDeadlineClock.FN() + 2))) {
+ mLastClockUpdateTime = mTransmitDeadlineClock;
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+int TransceiverWebSdr::driveReceiveFIFOBurst(size_t chan, char *buf, int maxLen)
+{
+ struct trx_ul_burst_ind bi;
+ int rc;
+
+ if ((rc = pullRadioVector(chan, &bi)) < 0) {
+ if (rc == -EAGAIN)
+ return 0;
+ if (rc == -ENOENT) { /* timeslot off, continue processing */
+ LOGCHAN(chan, DTRXDUL, DEBUG) << unsigned(bi.tn) << ":" << bi.fn << " timeslot is off";
+ return rc;
+ }
+ return rc; /* other errors: we want to stop the process */
+ }
+
+ if (!bi.idle)
+ logRxBurst(chan, &bi);
+
+ switch (mVersionTRXD[chan]) {
+ case 0:
+ return trxd_prepare_burst_ind_v0(&bi, buf, maxLen);
+ case 1:
+ return trxd_prepare_burst_ind_v1(&bi, buf, maxLen);
+ default:
+ //OSMO_ASSERT(false);
+ return -EFAULT;
+ }
+}
+
+void TransceiverWebSdr::driveTxFIFO()
+{
+ RadioClock *radioClock = (mRadioInterfaceWebSdr->getClock());
+
+ if (mOn) {
+ LOGC(DTRXCLK, DEBUG) << "radio clock " << radioClock->get();
+ while (radioClock->get() + mTransmitLatency > mTransmitDeadlineClock) {
+ // time to push burst to transmit FIFO
+ pushRadioVector(mTransmitDeadlineClock);
+ mTransmitDeadlineClock.incTN();
+ }
+ }
+}
+
+bool TransceiverWebSdr::driveTxPriorityQueue(size_t chan, char *buffer, int msgLen)
+{
+ int res = post_tx_burst(chan, buffer, msgLen);
+ if (res == RET_OK_UPDSTAT) {
+ return true;
+ } else if (res == RET_OK) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int TransceiverWebSdr::processCommand(int chan, const char *buffer, char *response, size_t response_size)
+{
+ return ctrl_cmd_handle(chan, buffer, response, response_size);
+}
+
+bool TransceiverWebSdr::on_write_clock(unsigned long long clock)
+{
+ LOG(ALERT) << "IND CLOCK " << clock;
+ mIndClockValid = true;
+ mIndClock = clock;
+
+ return true;
+}
+
+
+bool TransceiverWebSdr::peekClock(unsigned *out)
+{
+ bool res = mIndClockValid;
+ *out = mIndClock;
+ mIndClockValid = false;
+ return res;
+}
+
+int TransceiverWebSdr::post_tx_burst(size_t chan, char *buffer, int msgLen)
+{
+ int burstLen;
+ struct trxd_hdr_v01_dl *dl;
+ uint32_t fn;
+ uint8_t tn;
+ int res = RET_OK;
+
+ switch (msgLen) {
+ case sizeof(*dl) + gSlotLen: /* GSM burst */
+ burstLen = gSlotLen;
+ break;
+ case sizeof(*dl) + EDGE_BURST_NBITS: /* EDGE burst */
+ if (cfg->tx_sps != 4) {
+ LOGCHAN(chan, DTRXDDL, ERROR) << "EDGE burst received but SPS is set to " << cfg->tx_sps;
+ return RET_FAILED;
+ }
+ burstLen = EDGE_BURST_NBITS;
+ break;
+ default:
+ LOGCHAN(chan, DTRXDDL, ERROR) << "badly formatted packet on GSM->TRX interface (len=" << msgLen << ")";
+ return RET_FAILED;
+ }
+
+ dl = (struct trxd_hdr_v01_dl *)buffer;
+
+ /* Convert TDMA FN to the host endianness */
+ fn = osmo_load32be(&dl->common.fn);
+ tn = dl->common.tn;
+
+ /* Make sure we support the received header format */
+ switch (dl->common.version) {
+ case 0:
+ /* Version 1 has the same format */
+ case 1:
+ break;
+ default:
+ LOGCHAN(chan, DTRXDDL, ERROR)
+ << "Rx TRXD message with unknown header version " << unsigned(dl->common.version);
+ return RET_FAILED;
+ }
+
+ LOGCHAN(chan, DTRXDDL, DEBUG) << "Rx TRXD message (hdr_ver=" << unsigned(dl->common.version) << "): fn=" << fn
+ << ", tn=" << unsigned(tn) << ", burst_len=" << burstLen;
+
+ TransceiverState *state = &mStates[chan];
+ GSM::Time currTime = GSM::Time(fn, tn);
+
+ /* Verify proper FN order in DL stream */
+ if (state->first_dl_fn_rcv[tn]) {
+ int32_t delta = GSM::FNDelta(currTime.FN(), state->last_dl_time_rcv[tn].FN());
+ if (delta == 1) {
+ /* usual expected scenario, continue code flow */
+ } else if (delta == 0) {
+ LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with repeated FN " << currTime;
+ state->ctrs.tx_trxd_fn_repeated++;
+ return RET_OK_UPDSTAT;
+ } else if (delta < 0) {
+ LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with previous FN " << currTime << " vs last "
+ << state->last_dl_time_rcv[tn];
+ state->ctrs.tx_trxd_fn_outoforder++;
+ res = RET_OK_UPDSTAT;
+ /* Allow adding radio vector below, since it gets sorted in the queue */
+ } else if (chan == 0 && state->mFiller == FILLER_ZERO) {
+ /* delta > 1. Some FN was lost in the middle. We can only easily rely
+ * on consecutive FNs in TRX0 since it must transmit continuously in all
+ * setups. Also, osmo-trx supports optionally filling empty bursts on
+ * its own. In that case bts-trx is not obliged to submit all bursts.
+ */
+ LOGCHAN(chan, DTRXDDL, INFO) << "Rx TRXD msg with future FN " << currTime << " vs last "
+ << state->last_dl_time_rcv[tn] << ", " << delta - 1 << " FN lost";
+ state->ctrs.tx_trxd_fn_skipped += delta - 1;
+ res = RET_OK_UPDSTAT;
+ }
+ if (delta > 0)
+ state->last_dl_time_rcv[tn] = currTime;
+ } else { /* Initial check, simply store state */
+ state->first_dl_fn_rcv[tn] = true;
+ state->last_dl_time_rcv[tn] = currTime;
+ }
+
+ BitVector newBurst(burstLen);
+ BitVector::iterator itr = newBurst.begin();
+ uint8_t *bufferItr = dl->soft_bits;
+ while (itr < newBurst.end())
+ *itr++ = *bufferItr++;
+
+ addRadioVector(chan, newBurst, dl->tx_att, currTime);
+
+ return res;
+}
diff --git a/Transceiver52M/device/websdr/TransceiverWebSdr.h b/Transceiver52M/device/websdr/TransceiverWebSdr.h
new file mode 100644
index 0000000..cf17282
--- /dev/null
+++ b/Transceiver52M/device/websdr/TransceiverWebSdr.h
@@ -0,0 +1,70 @@
+/*
+ * WebSDR radio transceiver interface
+ *
+ * Copyright 2008 Free Software Foundation, Inc.
+ * Copyright 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ *
+ * This software is distributed under the terms of the GNU 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRANSCEIVER_WEBSDR_H
+#define TRANSCEIVER_WEBSDR_H
+
+#include "Transceiver.h"
+#include "radioInterfaceWebSdr.h"
+
+class TransceiverWebSdr : public Transceiver {
+public:
+ TransceiverWebSdr(const struct trx_cfg *cfg,
+ GSM::Time wTransmitLatency,
+ RadioInterfaceWebSdr *wRadioInterface);
+
+ bool init(void);
+
+ /* Command handling */
+ int processCommand(int chan, const char *buffer, char *response, size_t response_size);
+
+ /* RX path methods */
+ int driveReceiveRadioShortVector(short **buffers, int samples, int underrun,
+ TIMESTAMP ts, unsigned samples_skipped);
+
+ /* Obtain receive bursts from QUEUE */
+ int driveReceiveFIFOBurst(size_t chan, char *buf, int maxLen);
+
+ /* TX path methods */
+ void driveTxFIFO();
+ bool driveTxPriorityQueue(size_t chan, char *buffer, int msgLen);
+ bool on_write_clock(unsigned long long clock);
+ bool peekClock(unsigned *out);
+ int post_tx_burst(size_t chan, char* buffer, int msgLen);
+
+protected:
+ RadioInterfaceWebSdr *mRadioInterfaceWebSdr; /* associated radioInterfaceWebSdr object */
+
+ bool mIndClockValid;
+ unsigned mIndClock;
+
+ bool start();
+ void stop();
+};
+
+#endif /* TRANSCEIVER_WEBSDR_H */
\ No newline at end of file
diff --git a/Transceiver52M/device/websdr/libosmo-trx-websdr.pc.in b/Transceiver52M/device/websdr/libosmo-trx-websdr.pc.in
new file mode 100644
index 0000000..5ac04b4
--- /dev/null
+++ b/Transceiver52M/device/websdr/libosmo-trx-websdr.pc.in
@@ -0,0 +1,13 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom TRX WebSdr Transceiver Common Library
+Description: WebSdr Transceiver common code for osmo-bts
+Version: @VERSION@
+Requires:
+Requires.private: libosmocore libosmoctrl libosmovty
+Libs: -L${libdir} -losmo-trx-websdr
+Libs.private: @LIBOSMOCORE_LIBS@ @LIBOSMOCTRL_LIBS@ @LIBOSMOVTY_LIBS@
+Cflags: -I${includedir}/ @LIBOSMOCORE_CFLAGS@ @LIBOSMOCTRL_CFLAGS@ @LIBOSMOVTY_CFLAGS@
diff --git a/Transceiver52M/device/websdr/osmo-trx-websdr.cpp b/Transceiver52M/device/websdr/osmo-trx-websdr.cpp
new file mode 100644
index 0000000..39e1c6b
--- /dev/null
+++ b/Transceiver52M/device/websdr/osmo-trx-websdr.cpp
@@ -0,0 +1,216 @@
+/*
+ * Osmo TRX WebSDR API implementation
+ *
+ * Copyright (C) 2013 Thomas Tsou <tom@tsou.cc>
+ * Copyright (C) 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "TransceiverWebSdr.h"
+
+#include "radioDevice.h"
+
+extern "C" {
+#include "convolve.h"
+#include "convert.h"
+#include "trx_vty.h"
+}
+
+#include "osmo-trx-websdr.h"
+
+#define charp2str(a) ((a) ? std::string(a) : std::string(""))
+
+int g_no_tx_call;
+uint8_t g_tx_buffer[10000];
+uint32_t g_tx_ts;
+uint32_t g_tx_len;
+
+static struct trx_ctx trx_ctx;
+static struct trx_ctx* g_trx_ctx = &trx_ctx;
+
+static RadioDevice *radioDevice;
+static RadioInterfaceWebSdr *radio;
+static TransceiverWebSdr *transceiverWebSdr;
+
+RadioInterfaceWebSdr *makeRadioInterface(struct trx_ctx *trx, RadioDevice *radioDevice, int type)
+{
+ RadioInterfaceWebSdr *radio = NULL;
+
+ switch (type) {
+ case RadioDevice::NORMAL:
+ radio = new RadioInterfaceWebSdr(radioDevice, trx->cfg.tx_sps, trx->cfg.rx_sps);
+ break;
+ default:
+ LOG(ALERT) << "Unsupported radio interface configuration";
+ return NULL;
+ }
+
+ if (!radio->init(type)) {
+ LOG(ALERT) << "Failed to initialize radio interface";
+ return NULL;
+ }
+
+ return radio;
+}
+
+int makeTransceiver(struct trx_ctx *trx, RadioInterfaceWebSdr *radio)
+{
+ VectorFIFO *fifo;
+
+ transceiverWebSdr = new TransceiverWebSdr(&trx->cfg, GSM::Time(3,0), radio);
+ if (!transceiverWebSdr->init()) {
+ LOG(ALERT) << "Failed to initialize transceiver";
+ return -1;
+ }
+
+ for (size_t i = 0; i < trx->cfg.num_chans; i++) {
+ fifo = radio->receiveFIFO(i);
+ if (fifo && transceiverWebSdr->receiveFIFO(fifo, i))
+ continue;
+
+ LOG(ALERT) << "Could not attach FIFO to channel " << i;
+ return -1;
+ }
+ return 0;
+}
+
+static void trx_stop()
+{
+ LOG(NOTICE) << "Shutting down transceiver..." << std::endl;
+
+ delete transceiverWebSdr;
+ delete radio;
+ delete radioDevice;
+}
+
+static int trx_start(struct trx_ctx *trx)
+{
+ int type, chans;
+ unsigned int i;
+ std::vector<std::string> rx_paths, tx_paths;
+ RadioDevice::InterfaceType iface = RadioDevice::NORMAL;
+
+ /* Generate vector of rx/tx_path: */
+ for (i = 0; i < trx->cfg.num_chans; i++) {
+ rx_paths.push_back(charp2str(trx->cfg.chans[i].rx_path));
+ tx_paths.push_back(charp2str(trx->cfg.chans[i].tx_path));
+ }
+
+ radioDevice = RadioDevice::make(iface, &trx->cfg);
+ type = radioDevice->open();
+ if (type < 0) {
+ LOG(ALERT) << "Failed to create radio device" << std::endl;
+ goto shutdown;
+ }
+
+ /* Setup the appropriate device interface */
+ radio = makeRadioInterface(trx, radioDevice, type);
+ if (!radio)
+ goto shutdown;
+
+ /* Create the transceiver core */
+ if (makeTransceiver(trx, radio) < 0)
+ goto shutdown;
+
+ chans = transceiverWebSdr->numChans();
+ LOG(NOTICE) << "-- Transceiver active with "
+ << chans << " channel(s)" << std::endl;
+
+ return 0;
+
+shutdown:
+ trx_stop();
+ return -1;
+}
+
+static int trx_start_and_configure()
+{
+ convolve_init();
+ convert_init();
+
+ g_trx_ctx->cfg.tx_sps = 4;
+ g_trx_ctx->cfg.rx_sps = 4;
+ g_trx_ctx->cfg.base_port = 5700;
+ g_trx_ctx->cfg.bind_addr = (char*)"127.0.0.1";
+ g_trx_ctx->cfg.remote_addr = (char*)"127.0.1.1";
+ g_trx_ctx->cfg.filler = FILLER_DUMMY;
+
+ g_trx_ctx->cfg.num_chans = 1;
+ g_trx_ctx->cfg.chans[0].idx = 0;
+ g_trx_ctx->cfg.chans[0].trx = g_trx_ctx;
+ g_trx_ctx->cfg.chans[0].rx_path = (char*)"auto";
+ g_trx_ctx->cfg.chans[0].tx_path = (char*)"auto";
+
+ g_trx_ctx->cfg.multi_arfcn = 0;
+
+ srandom(time(NULL));
+
+ g_no_tx_call = true;
+
+ if(trx_start(g_trx_ctx) < 0)
+ return EXIT_FAILURE;
+
+ return 0;
+}
+
+/* Osmo TRX WebSDR API */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int osmotrxlib_init()
+{
+ return trx_start_and_configure();
+}
+
+void osmotrxlib_deinit()
+{
+ trx_stop();
+}
+
+int osmotrxlib_process_command(const char* buffer, char* response, size_t response_size)
+{
+ return transceiverWebSdr->processCommand(0, buffer, response, response_size);
+}
+
+int osmotrxlib_push_rx_short_vector(short* buffers, int samples, int underrun,
+ unsigned ts, unsigned samples_skipped)
+{
+ return transceiverWebSdr->driveReceiveRadioShortVector(&buffers, samples, underrun, ts, samples_skipped);
+}
+
+int osmotrxlib_get_rx_burst(char* buf, int maxLen)
+{
+ return transceiverWebSdr->driveReceiveFIFOBurst(0, buf, maxLen);
+}
+
+int osmotrxlib_get_tx_short_vector()
+{
+ transceiverWebSdr->driveTxFIFO();
+ return 0;
+}
+
+int osmotrxlib_put_tx_burst(char* buffer, int msgLen)
+{
+ return transceiverWebSdr->driveTxPriorityQueue(0, buffer, msgLen);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Transceiver52M/device/websdr/osmo-trx-websdr.h b/Transceiver52M/device/websdr/osmo-trx-websdr.h
new file mode 100644
index 0000000..6248d11
--- /dev/null
+++ b/Transceiver52M/device/websdr/osmo-trx-websdr.h
@@ -0,0 +1,46 @@
+/*
+ * Osmo TRX WebSDR API
+ *
+ * Copyright (C) 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#ifndef OSMO_TRX_WEBSDR_API_H
+#define OSMO_TRX_WEBSDR_API_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int osmotrxlib_init();
+void osmotrxlib_deinit();
+
+int osmotrxlib_process_command(const char* buffer, char *response, size_t response_size);
+
+// RX path
+int osmotrxlib_push_rx_short_vector(short* buffers, int samples, int underrun,
+ unsigned ts, unsigned samples_skipped);
+int osmotrxlib_get_rx_burst(char* buf, int maxLen);
+
+// TX path
+int osmotrxlib_get_tx_short_vector();
+int osmotrxlib_put_tx_burst(char* buffer, int msgLen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/Transceiver52M/device/websdr/radioInterfaceWebSdr.cpp b/Transceiver52M/device/websdr/radioInterfaceWebSdr.cpp
new file mode 100644
index 0000000..7c440ab
--- /dev/null
+++ b/Transceiver52M/device/websdr/radioInterfaceWebSdr.cpp
@@ -0,0 +1,110 @@
+/*
+ * WebSDR radio device implementation
+ *
+ * Copyright (C) 2008-2014 Free Software Foundation, Inc.
+ * Copyright (C) 2015 Ettus Research LLC
+ * Copyright (C) 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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/>.
+ * See the COPYING file in the main directory for details.
+ */
+
+#include "radioInterfaceWebSdr.h"
+
+extern "C" {
+#include "convert.h"
+}
+
+#define CHUNK 625
+#define NUMCHUNKS 4
+
+int RadioInterfaceWebSdr::driveReceiveRadioWithBuffer(unsigned samples_skipped)
+{
+ radioVector *burst = NULL;
+
+ GSM::Time rcvClock = mClock.get();
+ rcvClock.decTN(receiveOffset);
+ unsigned tN = rcvClock.TN();
+ int recvSz = recvBuffer[0]->getAvailSamples();
+ const int symbolsPerSlot = gSlotLen + 8;
+ int burstSize;
+ unsigned lost_tN = 0;
+
+ if (mSPSRx == NUMCHUNKS) {
+ burstSize = CHUNK;
+ } else {
+ burstSize = symbolsPerSlot + (tN % NUMCHUNKS == 0);
+ }
+
+ lost_tN = samples_skipped / burstSize;
+
+ while (lost_tN-- != 0) {
+ mClock.incTN();
+ rcvClock.incTN();
+ }
+
+ /*
+ * Pre-allocate head room for the largest correlation size
+ * so we can later avoid a re-allocation and copy
+ */
+ size_t head = GSM::gRACHSynchSequenceTS0.size();
+
+ /*
+ * Form receive bursts and pass up to transceiver. Use repeating
+ * pattern of 157-156-156-156 symbols per timeslot
+ */
+ while (recvSz > burstSize) {
+ for (size_t i = 0; i < mChans; i++) {
+ burst = new radioVector(rcvClock, burstSize, head);
+ unRadioifyVector(burst->getVector(), i);
+
+ if (mReceiveFIFO[i].size() < 32)
+ mReceiveFIFO[i].write(burst);
+ else
+ delete burst;
+ }
+
+ mClock.incTN();
+ rcvClock.incTN();
+ recvSz -= burstSize;
+
+ tN = rcvClock.TN();
+
+ if (mSPSRx != NUMCHUNKS)
+ burstSize = (symbolsPerSlot + (tN % NUMCHUNKS == 0)) * mSPSRx;
+ }
+
+ return 0;
+}
+
+int RadioInterfaceWebSdr::fillPullBuffer(short **buffers, int samples, int recvunderrun, TIMESTAMP ts)
+{
+ if (recvBuffer[0]->getFreeSegments() <= 0)
+ return -EBUSY;
+
+ size_t segmentLen = recvBuffer[0]->getSegmentLen();
+ if (segmentLen != samples)
+ return -EINVAL;
+
+ for (size_t i = 0; i < mChans; i++) {
+ convert_short_float(recvBuffer[i]->getWriteSegment(), buffers[i], segmentLen * 2);
+ }
+
+ osmo_trx_sync_or_and_fetch(&underrun, recvunderrun);
+ readTimestamp += segmentLen;
+
+ return 0;
+}
diff --git a/Transceiver52M/device/websdr/radioInterfaceWebSdr.h b/Transceiver52M/device/websdr/radioInterfaceWebSdr.h
new file mode 100644
index 0000000..b9aa5ba
--- /dev/null
+++ b/Transceiver52M/device/websdr/radioInterfaceWebSdr.h
@@ -0,0 +1,32 @@
+/*
+ * WebSDR radio device interface
+ *
+ * Copyright 2008 Free Software Foundation, Inc.
+ * Copyright 2026 Wavelet Lab <info@wavelet-lab.com>
+ *
+ * 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 RADIO_INTERFACE_WEBSDR_H
+#define RADIO_INTERFACE_WEBSDR_H
+
+#include "radioInterface.h"
+
+class RadioInterfaceWebSdr : public RadioInterface {
+public:
+ RadioInterfaceWebSdr(RadioDevice* wDevice, size_t tx_sps, size_t rx_sps);
+ virtual ~RadioInterfaceWebSdr();
+
+ int fillPullBuffer(short** buffers, int samples,
+ int underrun, TIMESTAMP ts);
+ int driveReceiveRadioWithBuffer(unsigned samples_skipped);
+};
+
+#endif /* RADIO_INTERFACE_WEBSDR_H */
diff --git a/Transceiver52M/radioInterface.h b/Transceiver52M/radioInterface.h
index cdcfd07..5299a1e 100644
--- a/Transceiver52M/radioInterface.h
+++ b/Transceiver52M/radioInterface.h
@@ -57,7 +57,7 @@

bool mOn; ///< indicates radio is on

-private:
+protected:

/** format samples to USRP */
int radioifyVector(signalVector &wVector, size_t chan, bool zero);
diff --git a/configure.ac b/configure.ac
index 01b2f34..7c88440 100644
--- a/configure.ac
+++ b/configure.ac
@@ -144,6 +144,11 @@
[enable bladeRF])
])

+AC_ARG_WITH(websdr, [
+ AS_HELP_STRING([--with-websdr],
+ [enable WebUSB based transceiver])
+])
+
AC_ARG_WITH(mstrx, [
AS_HELP_STRING([--with-mstrx],
[enable MS TRX])
@@ -282,6 +287,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"])
@@ -381,6 +387,8 @@
Transceiver52M/device/lms/Makefile \
Transceiver52M/device/ipc/Makefile \
Transceiver52M/device/bladerf/Makefile \
+ Transceiver52M/device/websdr/Makefile \
+ Transceiver52M/device/websdr/libosmo-trx-websdr.pc \
tests/Makefile \
tests/CommonLibs/Makefile \
tests/Transceiver52M/Makefile \

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

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