Skip to content

A Python library for interacting with JS8Call's TCP API, featuring GPS integration and automated grid square management. This library provides a clean interface to control JS8Call, manage frequencies, and automatically update grid squares based on GPS data. Perfect for amateur radio automation projects.

License

Notifications You must be signed in to change notification settings

tirandagan/backstop-python-js8call-api

Repository files navigation

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
PTT Status get_ptt_status() bool Get current PTT status
Directed Message get_directed_message() Optional[Dict[str, Any]] Get last directed message received
Spot get_spot() Optional[Dict[str, Any]] Get last spot received
TX Frame get_tx_frame() Optional[Dict[str, Any]] Get last TX frame sent
Connection Status is_closed() bool Check if JS8Call connection is closed

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

If you want to contribute or modify the code:

  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

Provided Examples

The package includes several example scripts in the examples directory demonstrating different use cases:

1. Basic Connection (01_basic_connection.py)

Demonstrates the fundamental connection to JS8Call and retrieving basic information like frequency. This example shows how to:

  • Establish a connection to JS8Call
  • Retrieve current frequency information
  • Safely close the connection

Example output:

Connected to JS8Call
Current frequency: 14,078,000 Hz
Dial frequency: 14,077,000 Hz
Offset: 1,000 Hz
Connection closed

2. Message Sending (02_send_message.py)

Shows how to send messages to other stations using the API. This example demonstrates:

  • Connecting to JS8Call
  • Sending a message to a specific callsign
  • Verifying message transmission

Example output:

Connected to JS8Call
Sending message to W1AW: "Hello from JS8Call API!"
Message sent successfully
Connection closed

3. Frequency Control (03_change_frequencies.py)

Illustrates how to change and monitor JS8Call frequencies. This example shows:

  • Reading current frequency
  • Changing to a new frequency (20m band)
  • Verifying the frequency change
  • Returning to original frequency

Example output:

Connected to JS8Call
Current frequency: 7,040,000 Hz
Changing to 20m band (14,078,000 Hz)
New frequency: 14,078,000 Hz
Returning to original frequency
Connection closed

4. Message Monitoring (04_monitor_messages.py)

Demonstrates real-time monitoring of incoming messages. This example shows:

  • Continuous monitoring of the receive window
  • Displaying message details (sender, recipient, text)
  • Graceful handling of connection closure

Example output:

Connected to JS8Call
Monitoring messages (Press Ctrl+C to stop)...

[14:30:15] From: W1AW To: ALL Text: CQ CQ CQ
[14:30:45] From: K1ABC To: W1AW Text: Hello W1AW
[14:31:00] From: N1XYZ To: ALL Text: QSY 20m
Connection closed

5. Station Information (05_get_station_info.py)

Shows how to retrieve and display station information. This example demonstrates:

  • Getting station details
  • Displaying callsign and grid square
  • Handling missing information

Example output:

Connected to JS8Call
Station Information:
Callsign: W1AW
Grid Square: FN31
Status: Active
Connection closed

6. Mode Control (06_get_mode_info.py)

Demonstrates how to check and control JS8Call operating modes. This example shows:

  • Reading current mode
  • Changing mode settings
  • Verifying mode changes

Example output:

Connected to JS8Call
Current mode: JS8
Changing to JS8Fast mode
Mode changed successfully
Connection closed

7. Signal Quality (07_get_snr.py)

Shows how to monitor signal-to-noise ratio. This example demonstrates:

  • Reading current SNR
  • Monitoring SNR changes
  • Displaying signal quality information

Example output:

Connected to JS8Call
Current SNR: 12 dB
Signal Quality: Good
Monitoring SNR for 60 seconds...
SNR fluctuated between 8-15 dB
Connection closed

8. Speed Control (08_get_speed.py)

Illustrates how to control JS8Call's speed settings. This example shows:

  • Reading current speed
  • Changing speed modes
  • Verifying speed changes

Example output:

Connected to JS8Call
Current speed: Normal
Changing to Turbo mode
Speed changed successfully
Connection closed

9. Callsign Operations (09_get_callsign.py)

Demonstrates how to work with station callsigns. This example shows:

  • Reading current callsign
  • Validating callsign format
  • Displaying station identification

Example output:

Connected to JS8Call
Current callsign: W1AW
Callsign format: Valid
Station identification complete
Connection closed

10. Grid Square Operations (10_get_grid.py)

Shows how to handle Maidenhead grid square operations. This example demonstrates:

  • Reading current grid square
  • Validating grid square format
  • Updating grid square from GPS (if available)

Example output:

Connected to JS8Call
Current grid square: FN31
Grid square format: Valid
GPS grid square: FN31pr
Grid square updated successfully
Connection closed

11. Band Predictor (11_band_predictor.py)

A comprehensive example that demonstrates how to use the JS8Call API along with GPS, weather, and solar data to recommend the optimal band for operation. This example includes:

  • GPS integration for location and time
  • Weather data fetching from OpenWeatherMap API
  • Solar conditions from HamQSL API
  • Smart band prediction based on:
    • Time of day
    • Location (latitude)
    • Temperature and weather conditions
    • Solar flux and K-index
    • Maximum Usable Frequency (MUF)
  • Automatic JS8Call frequency switching
  • Detailed explanation of recommendation rationale

To use this example, you'll need:

  1. A GPS device connected to your system
  2. An OpenWeatherMap API key (set as OPENWEATHERMAP_API_KEY in .env)
  3. Internet connectivity for weather and solar data
  4. JS8Call running and configured for API access

The script will:

  1. Get your location from GPS
  2. Fetch current weather and solar conditions
  3. Analyze conditions to recommend the best band
  4. Explain the rationale behind the recommendation
  5. Offer to automatically switch JS8Call to the recommended frequency

Example output:

Location: 40.9000°N, -74.3000°E
Time: 14:30 America/New_York

Solar Conditions:
Solar Flux: 125
K-index: 2
Maximum Usable Frequency: 15.2 MHz

What this means:
• Moderate solar activity - Fair conditions for long-distance communication
• Quiet geomagnetic field - Good conditions for HF propagation
• Maximum usable frequency of 15.2 MHz suggests limited high-band propagation

Recommended: 20m (14.078 MHz)

Recommendation rationale:
• Current temperature: 78°F
• Weather conditions: partly cloudy
• Mid-day conditions typically support higher frequency bands
• Early morning/late afternoon conditions favor mid-range frequencies

Would you like to switch JS8Call to this band? (y/n):

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

The main class for interacting with JS8Call's TCP API. This class handles all message formatting and response parsing.

Constants

Constant Value Description
JS8_NORMAL 0 Normal speed mode (JS8)
JS8_FAST 1 Fast speed mode (JS8Fast)
JS8_TURBO 2 Turbo speed mode (JS8Turbo)
JS8_SLOW 3 Slow speed mode (JS8Slow)
JS8_ULTRA 4 Ultra slow mode (JS8Ultra)

Constructor

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

Parameters:

  • host (str): JS8Call server hostname/IP (default: '127.0.0.1')
  • port (int): TCP port for JS8Call API (default: 2442)

Connection Methods

connect()

Establishes connection to JS8Call server.

Returns: None

Raises:

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

connect_gps()

Connects to GPS daemon (gpsd).

Returns: None

Raises:

  • Exception: If connection to gpsd fails

close()

Closes socket connection to JS8Call.

Returns: None

ping()

Checks if JS8Call is responsive.

Returns:

  • bool: True if JS8Call responds, False otherwise

Frequency Methods

get_frequency()

Gets current frequency information.

Returns:

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

set_frequency(dial_freq=None, offset=None)

Sets current frequency.

Parameters:

  • dial_freq (int, optional): Dial frequency in Hz
  • offset (int, optional): Frequency offset in Hz

Returns:

  • bool: True if command sent successfully

Station Information Methods

get_callsign()

Gets current station callsign.

Returns:

  • str: Current station callsign

get_grid()

Gets current grid locator.

Returns:

  • str: Current Maidenhead grid locator

set_grid(grid)

Sets current grid locator.

Parameters:

  • grid (str): Maidenhead grid locator to set

Returns:

  • bool: True if successful

get_station_info()

Gets station information text.

Returns:

  • str: Station information text

set_station_info(info)

Sets station information text.

Parameters:

  • info (str): Station information text to set

Returns:

  • bool: True if successful

get_status()

Gets current station status text.

Returns:

  • str: Current status message

set_status(status)

Sets current station status text.

Parameters:

  • status (str): Status text to set

Returns:

  • bool: True if successful

Messaging Methods

get_rx_text()

Gets text from receive window.

Returns:

  • str: Received text (up to 1024 characters)

get_tx_text()

Gets text from transmit buffer.

Returns:

  • str: Text in transmit buffer

set_tx_text(text)

Sets text in transmit buffer.

Parameters:

  • text (str): Text to set in transmit buffer

Returns:

  • bool: True if successful

send_message_text(text)

Sends a message immediately.

Parameters:

  • text (str): Message text to send

Returns:

  • bool: True if command 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...
    }
}

get_selected_call()

Gets currently selected callsign in UI.

Returns:

  • str: 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...
    }
}

Mode Control Methods

get_speed()

Gets current JS8Call speed setting.

Returns:

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

set_speed(speed)

Sets JS8Call speed mode.

Parameters:

  • speed (int): 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
        }
    }
]

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 JS8Call window to foreground.

Returns:

  • bool: True if command sent successfully

GPS Methods

get_gps_grid_square()

Gets current grid square from GPS coordinates.

Returns:

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

Additional Methods

get_directed_message()

Gets last directed message received.

Returns:

  • Optional[Dict[str, Any]]: Last directed message received

get_spot()

Gets last spot received.

Returns:

  • Optional[Dict[str, Any]]: Last spot received

get_tx_frame()

Gets last TX frame sent.

Returns:

  • Optional[Dict[str, Any]]: Last TX frame sent

is_closed()

Checks if JS8Call connection has been closed.

Returns:

  • bool: True if JS8Call has been closed, False otherwise

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

About

A Python library for interacting with JS8Call's TCP API, featuring GPS integration and automated grid square management. This library provides a clean interface to control JS8Call, manage frequencies, and automatically update grid squares based on GPS data. Perfect for amateur radio automation projects.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages