Skip to main content

A Python library for interacting with JS8Call's TCP API, featuring GPS integration and automated grid square management.

Project description

JS8Call API Python Client

JS8Call Logo

Python 3.6+ License: MIT JS8Call

A comprehensive Python client library for interacting with JS8Call's TCP API

Developed by Tiran Dagan, BackstopRadio.com


Table of Contents


Overview

This Python client library provides a comprehensive, high-level interface to JS8Call's TCP API. It allows you to programmatically control JS8Call and integrate it with other applications or systems.

The package includes:

  • A robust API client library (JS8CallAPI.py)
  • An interactive demo script (demo.py) showcasing all features
  • Supporting utilities for the demo interface (demo_functions.py)

Key Features

  • 🔌 Complete TCP API support
  • 📡 Control transceiver frequency
  • 📋 Manage station information
  • 💬 Send and receive messages
  • 📊 Monitor band activity
  • 📬 Access inbox messages
  • 🌐 GPS position integration
  • 🔄 Grid square conversion
  • ⚙️ Control JS8Call modes
  • 🛡️ Robust error handling

API Features Summary

The following table provides a complete overview of all available API methods:

Feature Get Set Return Type Description
Frequency get_frequency() set_frequency() Dict[str, int] Get/set current dial frequency and offset
Callsign get_callsign() str Get current station callsign
Grid Square get_grid() set_grid() str / bool Get/set station grid locator
Station Info get_station_info() set_station_info() str / bool Get/set station information text
Status get_status() set_status() str / bool Get/set current station status message
Call Activity get_call_activity() Dict[str, Dict[str, Any]] Get structured list of recently heard stations
Selected Call get_selected_call() str Get currently selected callsign in UI
Band Activity get_band_activity() Dict[str, Dict[str, Any]] Get structured activity across the band
RX Text get_rx_text() str Get text from receive window
TX Text get_tx_text() set_tx_text() str / bool Get/set text in transmit buffer
Send Message send_message_text() bool Send a message immediately
Speed get_speed() set_speed() int / bool Get/set JS8Call speed mode
Inbox get_inbox_messages() store_message() List[Dict[str, Any]] / Dict[str, Any] Get/store inbox messages
Window raise_window() bool Raise JS8Call window to foreground
GPS Grid get_gps_grid_square() Optional[str] Get grid square from current GPS position
Ping ping() bool Check if JS8Call is responsive

Installation

Prerequisites

  • Python 3.6 or higher
  • JS8Call running with TCP API enabled
  • Network connectivity to the JS8Call server
  • gpsd (for GPS functionality)

Quick Install

Install directly from PyPI:

pip install backstop-python-js8call-api

Development Install

  1. Clone this repository:
git clone https://github.com/tirandagan/backstop-python-js8call-api.git
cd backstop-python-js8call-api
  1. Install in development mode:
pip install -e .

File Structure

The repository contains the following main files:

  • JS8CallAPI.py: The main API client library
  • demo.py: An interactive demo script showcasing all API features
  • demo_functions.py: Supporting functions for the demo script's UI and utilities

Basic Usage

The simplest way to use the library is through the JS8CallAPI class, which ensures proper message formatting for the JS8Call API.

from JS8CallAPI import JS8CallAPI

# Create API client
api = JS8CallAPI()

try:
    # Connect to JS8Call
    api.connect()
    
    # Get current frequency
    freq = api.get_frequency()
    print(f"Current frequency: {freq['freq']:,} Hz")
    print(f"Dial frequency: {freq['dial']:,} Hz")
    print(f"Offset: {freq['offset']} Hz")
    
    # Get station information
    callsign = api.get_callsign()
    grid = api.get_grid()
    print(f"Station: {callsign} in {grid}")
    
finally:
    # Always close the connection
    api.close()

Running the Interactive Demo

The package includes an interactive demo script (demo.py) that showcases all API features:

python demo.py

This will launch a menu-driven interface allowing you to:

  • Test basic API connectivity
  • View and update station information
  • Monitor station and band activity
  • Send and receive messages
  • Control JS8Call modes and speeds
  • Integrate with GPS for grid square updates

Advanced Examples

Using the Direct Implementation

from JS8CallAPI import JS8CallAPI

api = JS8CallAPI()

try:
    api.connect()
    
    # Get current frequency
    freq = api.get_frequency()
    print(f"Current frequency: {freq['freq']:,} Hz")
    
    # Get current grid and update it
    current_grid = api.get_grid()
    print(f"Current grid: {current_grid}")
    
    # Update grid square (correctly sends in 'value' field)
    success = api.set_grid("FN42")
    if success:
        print("Grid updated successfully")
    
finally:
    api.close()

Messaging Operations

from JS8CallAPI import JS8CallAPI

api = JS8CallAPI()

try:
    api.connect()
    
    # Get the content of the transmit buffer
    tx_text = api.get_tx_text()
    print(f"Current TX text: {tx_text}")
    
    # Set new text to transmit
    api.set_tx_text("CQ CQ CQ DE K1ABC")
    
    # Send a message immediately 
    api.send_message_text("CQ CQ CQ DE K1ABC")
    
    # Get received text
    rx_text = api.get_rx_text()
    print(f"Received text: {rx_text}")
    
    # Store a message in the inbox
    response = api.store_message("W1AW", "Hello from my API script!")
    print(f"Message ID: {response['params'].get('ID')}")
    
    # Get messages from the inbox
    messages = api.get_inbox_messages()
    for msg in messages:
        print(f"From: {msg['params'].get('FROM')}")
        print(f"Text: {msg['params'].get('TEXT')}")
        print(f"Time: {msg['params'].get('UTC')}")
        print("---")
    
finally:
    api.close()

Station Monitoring

from JS8CallAPI import JS8CallAPI
import time
from datetime import datetime

api = JS8CallAPI()

try:
    api.connect()
    
    # Monitor call activity for 60 seconds
    end_time = time.time() + 60
    while time.time() < end_time:
        # Get all recently heard stations
        stations = api.get_call_activity()
        
        print(f"\n--- Station Activity at {datetime.now().strftime('%H:%M:%S')} ---")
        # Display info for each station
        for call, info in stations.items():
            print(f"Call: {call}")
            print(f"  SNR: {info.get('SNR')} dB")
            print(f"  Grid: {info.get('GRID')}")
            print(f"  Time: {datetime.fromtimestamp(info.get('UTC', 0)/1000).strftime('%Y-%m-%d %H:%M:%S')}")
        
        # Get band activity
        band = api.get_band_activity()
        print(f"\n--- Band Activity: {len(band)} frequencies ---")
        
        time.sleep(10)  # Check every 10 seconds
    
finally:
    api.close()

Mode Control

from JS8CallAPI import JS8CallAPI

api = JS8CallAPI()

try:
    api.connect()
    
    # Get current speed mode
    speed = api.get_speed()
    speed_names = {
        api.JS8_NORMAL: "Normal",
        api.JS8_FAST: "Fast",
        api.JS8_TURBO: "Turbo",
        api.JS8_SLOW: "Slow",
        api.JS8_ULTRA: "Ultra"
    }
    print(f"Current speed: {speed_names.get(speed, 'Unknown')}")
    
    # Set to turbo mode
    api.set_speed(api.JS8_TURBO)
    print("Switched to Turbo mode")
    
    # Set to normal mode
    api.set_speed(api.JS8_NORMAL)
    print("Switched back to Normal mode")
    
finally:
    api.close()

GPS Integration

from JS8CallAPI import JS8CallAPI

api = JS8CallAPI()

try:
    api.connect()
    
    # Get current grid from JS8Call
    js8call_grid = api.get_grid()
    print(f"JS8Call grid: {js8call_grid}")
    
    # Connect to GPS and get current position as grid square
    api.connect_gps()
    gps_grid = api.get_gps_grid_square()
    
    if gps_grid:
        print(f"GPS grid: {gps_grid}")
        
        # Compare and update if different
        if gps_grid.upper() != js8call_grid.upper():
            print("Grid square mismatch detected!")
            success = api.set_grid(gps_grid)
            if success:
                print(f"Grid square updated to: {gps_grid}")
            else:
                print("Error updating grid square")
    
finally:
    api.close()

Frequency Control

from JS8CallAPI import JS8CallAPI
import time

api = JS8CallAPI()

try:
    api.connect()
    
    # Store current frequency
    current_freq = api.get_frequency()
    print(f"Current dial frequency: {current_freq['dial']:,} Hz")
    print(f"Current offset: {current_freq['offset']} Hz")
    print(f"Current operating frequency: {current_freq['freq']:,} Hz")
    
    # Change to 14.078 MHz
    api.set_frequency(dial_freq=14078000)
    print("Changed to 14.078 MHz")
    time.sleep(5)
    
    # Change back to original frequency
    api.set_frequency(dial_freq=current_freq['dial'])
    print(f"Changed back to {current_freq['dial']/1000000:.3f} MHz")
    
finally:
    api.close()

API Reference

JS8CallAPI Class

A direct implementation that ensures correct message formatting for the JS8Call API and proper parsing of responses.

Class Constants

from JS8CallAPI import JS8_NORMAL, JS8_FAST, JS8_TURBO, JS8_SLOW, JS8_ULTRA

JS8_NORMAL  # Normal speed mode (JS8)
JS8_FAST    # Fast speed mode (JS8Fast)
JS8_TURBO   # Turbo speed mode (JS8Turbo)
JS8_SLOW    # Slow speed mode (JS8Slow)
JS8_ULTRA   # Ultra slow mode (JS8Ultra)

Constructor

JS8CallAPI(host='127.0.0.1', port=2442)

Parameters:

  • host (str): The hostname or IP address of the JS8Call server (default: '127.0.0.1')
  • port (int): The TCP port number for the JS8Call API (default: 2442)

Connection Methods

connect()

Establishes a connection to the JS8Call server.

Returns: None

Raises:

  • ConnectionRefusedError: If JS8Call is not running or API not enabled
  • Exception: For other connection errors

connect_gps()

Connects to the GPS daemon (gpsd).

Returns: None

Raises:

  • Exception: If connection to gpsd fails

close()

Closes the socket connection to JS8Call.

Returns: None

ping()

Sends a ping message to check if JS8Call is responsive.

Returns:

  • bool: True if JS8Call responds, False otherwise

Frequency Methods

get_frequency()

Gets the current frequency information from JS8Call.

Returns:

{
    'freq': int,    # Actual operating frequency in Hz
    'dial': int,    # Dial frequency in Hz
    'offset': int   # Frequency offset in Hz
}

set_frequency(dial_freq=None, offset=None)

Sets the current frequency.

Parameters:

  • dial_freq (int, optional): The dial frequency in Hz
  • offset (int, optional): The frequency offset in Hz

Returns:

  • bool: True if the command was sent successfully

Station Information Methods

get_callsign()

Gets the current station callsign.

Returns:

  • str: The current station callsign

get_grid()

Gets the current grid locator.

Returns:

  • str: The current Maidenhead grid locator

set_grid(grid)

Sets the current grid locator.

Parameters:

  • grid (str): The Maidenhead grid locator to set

Returns:

  • bool: True if successful

get_station_info()

Gets the station information text.

Returns:

  • str: The station information text

set_station_info(info)

Sets the station information text.

Parameters:

  • info (str): The station information text to set

Returns:

  • bool: True if successful

get_status()

Gets the current station status text.

Returns:

  • str: The current status message

set_status(status)

Sets the current station status text.

Parameters:

  • status (str): The status text to set

Returns:

  • bool: True if successful

Messaging Methods

get_rx_text()

Gets the text from the receive window.

Returns:

  • str: The received text (up to 1024 characters)

get_tx_text()

Gets the text from the transmit buffer.

Returns:

  • str: The text in the transmit buffer

set_tx_text(text)

Sets the text in the transmit buffer.

Parameters:

  • text (str): The text to set in the transmit buffer

Returns:

  • bool: True if successful

send_message_text(text)

Sends a message immediately.

Parameters:

  • text (str): The message text to send

Returns:

  • bool: True if the command was sent successfully

Activity Monitoring Methods

get_call_activity()

Gets information about recently heard stations.

Returns:

{
    'CALLSIGN1': {
        'SNR': int,        # Signal-to-noise ratio in dB
        'GRID': str,       # Grid square
        'UTC': int         # UTC timestamp in milliseconds
    },
    'CALLSIGN2': {
        # Same structure...
    },
    # More callsigns...
}

get_selected_call()

Gets the currently selected callsign in the UI.

Returns:

  • str: The selected callsign or empty string if none selected

get_band_activity()

Gets activity across the band.

Returns:

{
    'OFFSET1': {
        'FREQ': int,       # Operating frequency in Hz
        'DIAL': int,       # Dial frequency in Hz
        'OFFSET': int,     # Frequency offset in Hz
        'TEXT': str,       # Decoded text
        'SNR': int,        # Signal-to-noise ratio in dB
        'UTC': int         # UTC timestamp in milliseconds
    },
    'OFFSET2': {
        # Same structure...
    },
    # More offsets...
}

Mode Control Methods

get_speed()

Gets the current JS8Call speed setting.

Returns:

  • int: The current speed mode (use class constants to interpret)

set_speed(speed)

Sets the JS8Call speed mode.

Parameters:

  • speed (int): The speed mode (use class constants, e.g., JS8_NORMAL)

Returns:

  • bool: True if successful

Inbox Methods

get_inbox_messages(callsign=None)

Gets messages from the inbox.

Parameters:

  • callsign (str, optional): Filter messages by callsign

Returns:

[
    {
        'type': str,       # Message type
        'value': str,      # Message value
        'params': {
            'FROM': str,   # Sender callsign
            'TO': str,     # Recipient callsign
            'TEXT': str,   # Message text
            'UTC': int     # UTC timestamp in milliseconds
        }
    },
    # More messages...
]

store_message(callsign, text)

Stores a message in the inbox.

Parameters:

  • callsign (str): Destination callsign
  • text (str): Message text

Returns:

{
    'type': 'INBOX.MESSAGE',
    'params': {
        '_ID': int,        # Message request ID
        'ID': int          # Stored message ID
    }
}

UI Control Methods

raise_window()

Raises the JS8Call window to the foreground.

Returns:

  • bool: True if the command was sent successfully

GPS Methods

get_gps_grid_square()

Gets the current grid square from GPS coordinates.

Returns:

  • str: The Maidenhead grid square calculated from current GPS position
  • None: If GPS error or no fix

Response Data Structures

Most API methods return structured data extracted from the JS8Call API response. The library automatically parses JSON responses and provides appropriate Python data types.

Frequency Data

{
    'freq': 14074000,      # Current operating frequency in Hz
    'dial': 14073000,      # Current dial frequency in Hz  
    'offset': 1000         # Current offset in Hz
}

Call Activity Data

{
    'K1ABC': {
        'SNR': -5,
        'GRID': 'FN42eq',
        'UTC': 1617981234567
    },
    'W1XYZ': {
        'SNR': 12,
        'GRID': 'EM73',
        'UTC': 1617981234123
    }
}

Inbox Message Data

[
    {
        'type': 'MESSAGE',
        'value': '',
        'params': {
            'FROM': 'K1ABC',
            'TO': 'W1XYZ',
            'TEXT': 'Hello there!',
            'UTC': 1617981234567
        }
    }
]

Error Handling

The library includes comprehensive error handling with informative messages for common issues:

from JS8CallAPI import JS8CallAPI

api = JS8CallAPI()

try:
    api.connect()
    
    # Structured error handling for different operations
    try:
        api.connect_gps()
        gps_grid = api.get_gps_grid_square()
        if gps_grid:
            print(f"GPS grid: {gps_grid}")
    except ConnectionError as e:
        print(f"GPS connection error: {e}")
    except TimeoutError as e:
        print(f"GPS timeout: {e}")
    except Exception as e:
        print(f"GPS error: {e}")
    
    # Frequency operations
    try:
        freq = api.get_frequency()
        print(f"Current frequency: {freq['freq']:,} Hz")
    except ConnectionError:
        print("Connection to JS8Call lost")
    except TimeoutError:
        print("JS8Call did not respond in time")
    except Exception as e:
        print(f"Error getting frequency: {e}")
    
finally:
    api.close()

Requirements

  • Python 3.6+: For type annotations and modern language features
  • JS8Call: Running with TCP API enabled (Settings -> Reporting -> "Enable TCP Server API")
  • gpsd (optional): For GPS functionality, install with sudo apt install gpsd on Linux
  • Network connectivity: To the JS8Call server

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin feature-name
  5. Submit a pull request

For bug reports, please open an issue with a detailed description.


License and Legal

Copyright Notice

© 2023 Tiran Dagan, BackstopRadio.com. All rights reserved.

MIT License

This project is licensed under the MIT License - see the LICENSE file for details.

Disclaimer

This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

JS8Call is a separate project created by Jordan Sherer (KN4CRD). This API client is not officially affiliated with or endorsed by JS8Call.

Amateur Radio Notice

This software is designed for use by licensed amateur radio operators. Users are responsible for ensuring all transmissions comply with local regulations and licensing requirements.


Acknowledgments

  • JS8Call by Jordan Sherer (KN4CRD)
  • gpsd-py3 for GPS integration
  • All contributors to this project

Developed with ❤️ for the amateur radio community
BackstopRadio.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

backstop_python_js8call_api-0.1.1.tar.gz (13.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

backstop_python_js8call_api-0.1.1-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

File details

Details for the file backstop_python_js8call_api-0.1.1.tar.gz.

File metadata

File hashes

Hashes for backstop_python_js8call_api-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c30e82c6d32023d7fc375516b39c86ff83d614ec0bd18dc0aa508f37000e6dc6
MD5 5d09f9a769ec10e8c453bfc0cb797ec8
BLAKE2b-256 b2233ba3057547e4df4fe1a16c0a44a64323e4a90f9f371455e8d52f5fd40466

See more details on using hashes here.

File details

Details for the file backstop_python_js8call_api-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for backstop_python_js8call_api-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a9a3f36fd4c29ac3612cdef26b2613a1812b1c3e7959583b3d6c22e86af60348
MD5 b7967d4246a889fe973d67ae29dc96b9
BLAKE2b-256 2a10f1ad97e1133ef622a3de29bb0aa6e02c8ad82da62ad1c5cd82a40351bc0a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page