Timur Davydov has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-trx/+/42411?usp=email )


Change subject: device/websdr: add WebSDR transceiver backend and build target
......................................................................

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 <[email protected]>
+ *
+ *  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 <[email protected]>
+ *
+ *  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 <[email protected]>
+ * Copyright (C) 2026 Wavelet Lab <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 https://gerrit.osmocom.org/c/osmo-trx/+/42411?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

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 <[email protected]>

Reply via email to