← Ordo Artificum

jf8net

JF8Call Python API library

A Python library for the JF8Call WebSocket API. Full asyncio coverage with typed dataclasses, a synchronous wrapper for scripts, and a complete event system. Drive a JF8Call instance — send messages, tune frequencies, read configuration, stream decoded traffic — from any Python application.

Open Source — GPL-3.0 GitHub

Installation

# install dependencies
pip install websockets
# editable install from source
pip install -e /path/to/jf8net

Requires Python 3.10+ and websockets 11+. JF8Call must be running with the WebSocket API enabled (default: ws://localhost:2102).

Quick start

Async (recommended)

import asyncio
from jf8net import JF8Client, DecodedMessage
async def main():
  async with JF8Client() as client:
    print(await client.get_status())
    @client.on_message
    async def handle(msg: DecodedMessage):
      print(f"{msg.from_call}{msg.to}: {msg.body}")
    await client.run_forever()
asyncio.run(main())

Sync (for scripts)

from jf8net.sync import JF8ClientSync
with JF8ClientSync() as client:
  print(client.get_status())
  for msg in client.messages():
    print(msg)

API reference

Status & Configuration

get_status() Returns a typed Status dataclass
get_config() Returns a typed Config dataclass (superset of Status)
set_config(**kwargs) Update any subset of config fields by name; returns updated Config

Radio (Hamlib)

get_radio() Returns RadioStatus
connect_radio(rig_model, port, baud, ptt_type, …) Configure and connect a Hamlib rig
disconnect_radio() Disconnect the rig
set_frequency(khz) Tune to frequency in kHz; triggers ATU if auto_atu is set
tune() Issue ATU tune command (CAT only, no RF)

Transmit

send(text, submode=None) Queue a message; returns TX queue depth
send_and_wait(text, timeout=120) Send and block until TX completes
send_heartbeat() Transmit a heartbeat
send_snr_query(callsign) Query SNR from another station
get_tx_queue() / clear_tx_queue() Inspect or clear the pending TX queue
wait_for_tx(timeout=120) Block until a tx.finished event is received

Messages & Spectrum

get_messages(offset, limit) Returns list[DecodedMessage]
clear_messages() Clear the message log
get_spectrum() Returns Spectrum with .peak_hz() and .slice(lo, hi)

Events

Register handlers with decorators or direct calls. Handlers may be plain functions or coroutines. The wildcard event "*" receives everything.

Event Convenience method Argument type
message.decodedon_messageDecodedMessage
message.frameon_frameFrameUpdate
statuson_statusStatus
spectrumon_spectrumSpectrum
tx.started / tx.finishedon_tx_started / on_tx_finishedNone
radio.connectedon_radio_connecteddict
config.changedon_config_changeddict

message.frame fires for each incoming partial GFSK8 frame, letting you display in-progress multi-frame messages before they complete. Single-frame messages go directly to message.decoded.

Included examples

File Description
01_basic_status.pyStatus, config, audio devices, recent messages
02_receive_messages.pyStream decoded messages with filters
03_send_message.pySend directed messages and wait for TX
04_frame_assembly.pyReal-time multi-frame GFSK8 assembly display
05_radio_control.pyFrequency changes, ATU tuning, rig connect
06_config_management.pyRead/write all config fields
07_spectrum_monitor.pyASCII waterfall from live spectrum events
08_sync_usage.pyAll of the above using the sync wrapper
09_chat.pyInteractive two-way terminal chat

Run any example with --help for options, and --host to point at a remote JF8Call instance.

jf8net vs. js8net

js8net is the companion library for the original JS8Call application, using its TCP-based API. jf8net targets JF8Call and its purpose-built WebSocket API — a cleaner, fully documented protocol designed for automation from the start.

If you're running JS8Call, use js8net. If you're running JF8Call, use jf8net. The two APIs are not interchangeable, but the concepts map closely.