Skip to content

yo6ssw/cwsd

Repository files navigation

CWSD

Part of the hamtools suite — see the hub for how cwsd, xlog2 and usb-paddles combine into an integrated (remote) station.

CW sender daemon.

cwsd runs on Linux next to your transceiver (developed against the Icom IC-7300), links against hamlib, and exposes several independent network services so logging and contest software can control the rig, send Morse, stream receiver audio, and even key the rig with a real paddle over the internet. Each service is individually toggled in the config and listens on its own port.

Services

Service Config section Default port Protocol Enabled by default Purpose
rigctld rigctld TCP 4532 hamlib rigctld yes Query/set frequency, mode, PTT, VFO — for WSJT-X, fldigi, etc.
cwdaemon cwdaemon UDP 6789 cwdaemon yes Receive text and key it as Morse (DTR=key, RTS=PTT).
audio stream audio UDP 7355 Opus over UDP no Capture rig audio (ALSA), Opus-encode, fan out to subscribers.
remote key remote_key UDP 6790 timestamped edges no Replay real paddle keying over the internet (DTR=key, RTS=PTT).

cwsd service architecture

Diagram source: docs/architecture.dot — regenerate with dot -Tsvg docs/architecture.dot -o docs/architecture.svg.

Note: cwdaemon and remote_key both drive the same DTR/RTS control lines. You can enable both on the same serial device, but don't key through them at once — they are alternative keying front-ends, not meant to run simultaneously. The audio stream has no configured target; clients subscribe (and NAT-punch) by sending any datagram to its port, then must send a periodic keepalive.

Running WSJT-X (or other soundcard apps) from another machine? cwsd handles CAT/PTT via rigctld, but its audio stream is RX-only. For full-duplex remote audio (RX and TX) over the LAN, see docs/wsjtx-remote-audio.md, a step-by-step runbook for bridging the rig's USB soundcard with PipeWire-Pulse.

Install (Ubuntu PPA)

Prebuilt packages for current Ubuntu releases are on the ppa:benishor/hamtools PPA — no compiling:

sudo add-apt-repository ppa:benishor/hamtools
sudo apt update
sudo apt install cwsd

This installs the binary, a systemd service (see below) and /etc/cwsd/cwsdrc.sample. On other distributions — or Raspberry Pi OS — grab a self-contained binary from the releases (static x86_64/arm64 tarballs), or build from source below.

Build from source

mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j $(nproc)
sudo make install

Build requirements: CMake ≥ 3.25, a C++20 compiler, hamlib dev headers/libs, and — for the audio stream — ALSA and Opus dev libs. On Debian/Ubuntu:

sudo apt install libhamlib-dev libasound2-dev libopus-dev

Configuration

By default cwsd will read configuration from ~/.config/cwsdrc when run by hand. Installed from a package and started via systemd, it instead reads the system config /etc/cwsd/cwsdrc (see Installing as a system service below) — the unit runs as a DynamicUser with no home directory. Despite the rc name it is YAML. The rig.model field is a hamlib rig model number (e.g. 3073 = IC-7300). Each service has its own enabled flag and port. See cwsdrc.sample for the fully-commented template. A typical configuration:

rig:
  port: /dev/icom7300      # stable symlink from the udev rule below
  model: 3073              # hamlib rig model: Icom IC-7300
cwdaemon:
  enabled: true
  port: 6789
  initial_wpm: 40
rigctld:
  enabled: true
  port: 4532
audio:                     # Opus receiver-audio stream (off by default)
  enabled: false
  device: pipewire         # RX via PipeWire (shared w/ WSJT-X); or plughw:0,0 for the raw card
  port: 7355               # UDP port to bind; clients subscribe by sending here
  sample_rate: 8000        # opus rates only: 8000/12000/16000/24000/48000
  channels: 1
  bitrate: 96000           # opus target bitrate in bits/s
  frame_ms: 20             # opus frame size: 2.5/5/10/20/40/60
  client_timeout_ms: 10000 # drop subscribers silent longer than this
  fec_loss_perc: 10        # expected packet loss %; drives Opus in-band FEC (0 off)
remote_key:                # real paddle keying over the internet (off by default)
  enabled: false           # shares DTR/RTS with cwdaemon; you can run both, just don't key through both at once
  port: 6790               # UDP port to bind for the timestamped edge stream
  # device: /dev/icom7300  # serial device with DTR=key/RTS=PTT; defaults to rig.port
  playout_ms: 150          # jitter-buffer depth; the rig lags the operator by this much
  silence_ms: 250          # force key-up if no packet arrives for this long
  max_key_down_ms: 5000    # hard watchdog: never hold the key down longer than this
  ptt_lead_ms: 10          # assert PTT this long before the first key-down
  ptt_tail_ms: 100         # hold PTT this long after the last key-up
logging:
  level: info
  filename: /tmp/cwsd.log
  max_size: 1048576

ALSA capture device (audio stream)

The device string is passed straight to ALSA. Two sensible choices:

  • pipewire — read RX through PipeWire, sharing the rig's USB codec with other apps (e.g. WSJT-X) instead of grabbing the raw card exclusively. Needs the PipeWire ALSA plugin, and cwsd must have access to the session where PipeWire runs (so this suits a desktop / user-session cwsd more than the hardened DynamicUser service).
  • plughw:0,0 — capture the raw card directly. Prefer this numeric form over the by-name plughw:CARD=CODEC,DEV=0; name resolution can fail when cwsd runs under systemd (Cannot get card index for CODEC).

Either way, the user running cwsd must be in the audio group to open /dev/snd/*. List capture devices with arecord -l.

Usage:

cwsd will simply run in foreground. Config is loaded from ~/.config/cwsdrc by default.

cwsd -c /path/to/cwsdrc (or --config) reads config from an explicit path — used by the system service, which reads /etc/cwsd/cwsdrc.

cwsd -d will make it daemonize.

cwsd --version will print the current running version.

Making a fixed symlink for the USB device the rig connects to

  • sudo cp shared/80-ic7300.rules /etc/udev/rules.d/
  • sudo udevadm control --reload-rules && sudo udevadm trigger

After plugging in the Icom 7300 USB cable a /dev/icom7300 symlink will be created pointing to the actual /dev/ttyUSBx device that the rig was allocated. The rule matches a specific rig serial — edit ID_SERIAL_SHORT for your unit (find it with udevadm info -q property -n /dev/ttyUSB0 | grep ID_SERIAL_SHORT). The user running cwsd must also be in the dialout group to open the serial port.

Installing as a system service

make install (or the packages/tarballs) installs a systemd unit to /usr/lib/systemd/system/cwsd.service and a sample config to /etc/cwsd/cwsdrc.sample. The unit runs cwsd as a transient, unprivileged system user (DynamicUser=yes) — no account to create and no username baked into the unit — added to the dialout and audio groups so it can reach the rig's serial line and the sound card. It reads /etc/cwsd/cwsdrc (a system path, not a per-user ~/.config).

sudo install -m644 /etc/cwsd/cwsdrc.sample /etc/cwsd/cwsdrc   # then edit rig.port / rig.model
sudo systemctl daemon-reload
sudo systemctl enable --now cwsd
journalctl -u cwsd -f                                          # logs

The unit grants CAP_SYS_NICE/CAP_IPC_LOCK (with LimitRTPRIO/LimitMEMLOCK) so the keyer thread can run at real-time priority for jitter-free element timing. Logs go to the journal; set logging.filename to /var/log/cwsd/cwsd.log for an on-disk copy (the unit provisions that directory via LogsDirectory).

For an interactive/desktop setup you can instead just run cwsd yourself (it reads ~/.config/cwsdrc) — the system service is for always-on / headless stations.

Authors

Related projects

Part of a small suite of Linux ham-radio tools:

  • xlog2 — logging application that drives cwsd's rigctld (rig control) and cwdaemon (network CW), plays its Opus rig-audio stream, and does real paddle keying through its remote_key service.
  • usb-paddles — USB Morse-paddle firmware; pairs with cwsd's remote_key for paddle keying over the internet.

License

cwsd is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See LICENSE for the full text.

Bundled third-party libraries under src/libs/ keep their own licenses:

About

CW sender daemon for ham-radio transceivers: rigctld, cwdaemon, Opus rig-audio streaming and remote paddle keying over the network (Linux, hamlib)

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages