{"id":34036366,"url":"https://github.com/tirandagan/backstop-python-js8call-api","last_synced_at":"2026-05-28T08:32:02.059Z","repository":{"id":285673577,"uuid":"958952347","full_name":"tirandagan/backstop-python-js8call-api","owner":"tirandagan","description":"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.","archived":false,"fork":false,"pushed_at":"2025-05-15T12:27:18.000Z","size":181,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-02T19:47:35.364Z","etag":null,"topics":["ham","js8call","python","radio"],"latest_commit_sha":null,"homepage":"https://www.tirandagan.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tirandagan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-02T03:06:01.000Z","updated_at":"2025-09-29T10:47:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"4a636b45-9a6e-42e1-a3b6-d8c22af3ab86","html_url":"https://github.com/tirandagan/backstop-python-js8call-api","commit_stats":null,"previous_names":["tirandagan/backstop-python-js8call-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tirandagan/backstop-python-js8call-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tirandagan%2Fbackstop-python-js8call-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tirandagan%2Fbackstop-python-js8call-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tirandagan%2Fbackstop-python-js8call-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tirandagan%2Fbackstop-python-js8call-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tirandagan","download_url":"https://codeload.github.com/tirandagan/backstop-python-js8call-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tirandagan%2Fbackstop-python-js8call-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33601380,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ham","js8call","python","radio"],"created_at":"2025-12-13T20:20:50.981Z","updated_at":"2026-05-28T08:32:02.048Z","avatar_url":"https://github.com/tirandagan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JS8Call API Python Client\n\n\u003cdiv align=\"center\"\u003e\n\n![JS8Call Logo](js8call-logo.png)\n\n[![Python 3.6+](https://img.shields.io/badge/Python-3.6+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![JS8Call](https://img.shields.io/badge/JS8Call-API-green.svg)](https://js8call.com/)\n\n**A comprehensive Python client library for interacting with JS8Call's TCP API**\n\n*Developed by [Tiran Dagan](https://github.com/tirandagan), [BackstopRadio.com](https://backstopradio.com)*\n\n\u003c/div\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [API Features Summary](#api-features-summary)\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n- [Provided Examples](#provided-examples)\n- [Advanced Examples](#advanced-examples)\n  - [Using the Direct Implementation](#using-the-direct-implementation)\n  - [Messaging Operations](#messaging-operations)\n  - [Station Monitoring](#station-monitoring)\n  - [Mode Control](#mode-control)\n  - [GPS Integration](#gps-integration)\n  - [Frequency Control](#frequency-control)\n- [API Reference](#api-reference)\n  - [JS8CallAPI Class](#js8callapi-class)\n  - [Class Constants](#class-constants)\n  - [Connection Methods](#connection-methods)\n  - [Frequency Methods](#frequency-methods)\n  - [Station Information Methods](#station-information-methods)\n  - [Messaging Methods](#messaging-methods)\n  - [Activity Monitoring Methods](#activity-monitoring-methods)\n  - [Mode Control Methods](#mode-control-methods)\n  - [Inbox Methods](#inbox-methods)\n  - [UI Control Methods](#ui-control-methods)\n  - [GPS Methods](#gps-methods)\n- [Response Data Structures](#response-data-structures)\n- [Error Handling](#error-handling)\n- [Requirements](#requirements)\n- [Contributing](#contributing)\n- [License and Legal](#license-and-legal)\n- [Acknowledgments](#acknowledgments)\n\n---\n\n## Overview\n\nThis 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.\n\nThe package includes:\n- A robust API client library (`JS8CallAPI.py`)\n- An interactive demo script (`demo.py`) showcasing all features\n- Supporting utilities for the demo interface (`demo_functions.py`)\n\n### Key Features\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"50%\"\u003e\n      \u003cul\u003e\n        \u003cli\u003e🔌 Complete TCP API support\u003c/li\u003e\n        \u003cli\u003e📡 Control transceiver frequency\u003c/li\u003e\n        \u003cli\u003e📋 Manage station information\u003c/li\u003e\n        \u003cli\u003e💬 Send and receive messages\u003c/li\u003e\n        \u003cli\u003e📊 Monitor band activity\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/td\u003e\n    \u003ctd width=\"50%\"\u003e\n      \u003cul\u003e\n        \u003cli\u003e📬 Access inbox messages\u003c/li\u003e\n        \u003cli\u003e🌐 GPS position integration\u003c/li\u003e\n        \u003cli\u003e🔄 Grid square conversion\u003c/li\u003e\n        \u003cli\u003e⚙️ Control JS8Call modes\u003c/li\u003e\n        \u003cli\u003e🛡️ Robust error handling\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n---\n\n## API Features Summary\n\nThe following table provides a complete overview of all available API methods:\n\n\u003ctable\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth width=\"20%\"\u003eFeature\u003c/th\u003e\n      \u003cth width=\"15%\"\u003eGet\u003c/th\u003e\n      \u003cth width=\"15%\"\u003eSet\u003c/th\u003e\n      \u003cth width=\"20%\"\u003eReturn Type\u003c/th\u003e\n      \u003cth width=\"30%\"\u003eDescription\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eFrequency\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_frequency()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_frequency()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eDict[str, int]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set current dial frequency and offset\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eCallsign\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_callsign()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet current station callsign\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eGrid Square\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_grid()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_grid()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e / \u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set station grid locator\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eStation Info\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_station_info()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_station_info()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e / \u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set station information text\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eStatus\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_status()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_status()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e / \u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set current station status message\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eCall Activity\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_call_activity()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eDict[str, Dict[str, Any]]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet structured list of recently heard stations\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eSelected Call\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_selected_call()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet currently selected callsign in UI\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eBand Activity\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_band_activity()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eDict[str, Dict[str, Any]]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet structured activity across the band\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eRX Text\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_rx_text()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet text from receive window\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eTX Text\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_tx_text()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_tx_text()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003estr\u003c/code\u003e / \u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set text in transmit buffer\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eSend Message\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003esend_message_text()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eSend a message immediately\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eSpeed\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_speed()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eset_speed()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eint\u003c/code\u003e / \u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/set JS8Call speed mode\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eInbox\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_inbox_messages()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003estore_message()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eList[Dict[str, Any]]\u003c/code\u003e / \u003ccode\u003eDict[str, Any]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet/store inbox messages\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eWindow\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eraise_window()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eRaise JS8Call window to foreground\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eGPS Grid\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_gps_grid_square()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eOptional[str]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet grid square from current GPS position\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003ePing\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eping()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eCheck if JS8Call is responsive\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003ePTT Status\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_ptt_status()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet current PTT status\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eDirected Message\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_directed_message()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eOptional[Dict[str, Any]]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet last directed message received\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eSpot\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_spot()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eOptional[Dict[str, Any]]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet last spot received\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eTX Frame\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eget_tx_frame()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003eOptional[Dict[str, Any]]\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eGet last TX frame sent\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003ctd\u003eConnection Status\u003c/td\u003e\n      \u003ctd\u003e✅ \u003ccode\u003eis_closed()\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003e❌\u003c/td\u003e\n      \u003ctd\u003e\u003ccode\u003ebool\u003c/code\u003e\u003c/td\u003e\n      \u003ctd\u003eCheck if JS8Call connection is closed\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\n\n## Installation\n\n### Prerequisites\n\n- Python 3.6 or higher\n- JS8Call running with TCP API enabled\n- Network connectivity to the JS8Call server\n- gpsd (for GPS functionality)\n\n### Quick Install\n\nInstall directly from PyPI:\n\n```bash\npip install backstop-python-js8call-api\n```\n\n### Development Install\n\nIf you want to contribute or modify the code:\n\n1. Clone this repository:\n```bash\ngit clone https://github.com/tirandagan/backstop-python-js8call-api.git\ncd backstop-python-js8call-api\n```\n\n2. Install in development mode:\n```bash\npip install -e .\n```\n\n### File Structure\n\nThe repository contains the following main files:\n- `JS8CallAPI.py`: The main API client library\n- `demo.py`: An interactive demo script showcasing all API features\n- `demo_functions.py`: Supporting functions for the demo script's UI and utilities\n\n---\n\n## Basic Usage\n\nThe simplest way to use the library is through the `JS8CallAPI` class, which ensures proper message formatting for the JS8Call API.\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\n# Create API client\napi = JS8CallAPI()\n\ntry:\n    # Connect to JS8Call\n    api.connect()\n    \n    # Get current frequency\n    freq = api.get_frequency()\n    print(f\"Current frequency: {freq['freq']:,} Hz\")\n    print(f\"Dial frequency: {freq['dial']:,} Hz\")\n    print(f\"Offset: {freq['offset']} Hz\")\n    \n    # Get station information\n    callsign = api.get_callsign()\n    grid = api.get_grid()\n    print(f\"Station: {callsign} in {grid}\")\n    \nfinally:\n    # Always close the connection\n    api.close()\n```\n\n### Running the Interactive Demo\n\nThe package includes an interactive demo script (`demo.py`) that showcases all API features:\n\n```bash\npython demo.py\n```\n\nThis will launch a menu-driven interface allowing you to:\n- Test basic API connectivity\n- View and update station information\n- Monitor station and band activity\n- Send and receive messages\n- Control JS8Call modes and speeds\n- Integrate with GPS for grid square updates\n\n## Provided Examples\n\nThe package includes several example scripts in the `examples` directory demonstrating different use cases:\n\n### 1. Basic Connection (`01_basic_connection.py`)\nDemonstrates the fundamental connection to JS8Call and retrieving basic information like frequency. This example shows how to:\n- Establish a connection to JS8Call\n- Retrieve current frequency information\n- Safely close the connection\n\nExample output:\n```\nConnected to JS8Call\nCurrent frequency: 14,078,000 Hz\nDial frequency: 14,077,000 Hz\nOffset: 1,000 Hz\nConnection closed\n```\n\n### 2. Message Sending (`02_send_message.py`)\nShows how to send messages to other stations using the API. This example demonstrates:\n- Connecting to JS8Call\n- Sending a message to a specific callsign\n- Verifying message transmission\n\nExample output:\n```\nConnected to JS8Call\nSending message to W1AW: \"Hello from JS8Call API!\"\nMessage sent successfully\nConnection closed\n```\n\n### 3. Frequency Control (`03_change_frequencies.py`)\nIllustrates how to change and monitor JS8Call frequencies. This example shows:\n- Reading current frequency\n- Changing to a new frequency (20m band)\n- Verifying the frequency change\n- Returning to original frequency\n\nExample output:\n```\nConnected to JS8Call\nCurrent frequency: 7,040,000 Hz\nChanging to 20m band (14,078,000 Hz)\nNew frequency: 14,078,000 Hz\nReturning to original frequency\nConnection closed\n```\n\n### 4. Message Monitoring (`04_monitor_messages.py`)\nDemonstrates real-time monitoring of incoming messages. This example shows:\n- Continuous monitoring of the receive window\n- Displaying message details (sender, recipient, text)\n- Graceful handling of connection closure\n\nExample output:\n```\nConnected to JS8Call\nMonitoring messages (Press Ctrl+C to stop)...\n\n[14:30:15] From: W1AW To: ALL Text: CQ CQ CQ\n[14:30:45] From: K1ABC To: W1AW Text: Hello W1AW\n[14:31:00] From: N1XYZ To: ALL Text: QSY 20m\nConnection closed\n```\n\n### 5. Station Information (`05_get_station_info.py`)\nShows how to retrieve and display station information. This example demonstrates:\n- Getting station details\n- Displaying callsign and grid square\n- Handling missing information\n\nExample output:\n```\nConnected to JS8Call\nStation Information:\nCallsign: W1AW\nGrid Square: FN31\nStatus: Active\nConnection closed\n```\n\n### 6. Mode Control (`06_get_mode_info.py`)\nDemonstrates how to check and control JS8Call operating modes. This example shows:\n- Reading current mode\n- Changing mode settings\n- Verifying mode changes\n\nExample output:\n```\nConnected to JS8Call\nCurrent mode: JS8\nChanging to JS8Fast mode\nMode changed successfully\nConnection closed\n```\n\n### 7. Signal Quality (`07_get_snr.py`)\nShows how to monitor signal-to-noise ratio. This example demonstrates:\n- Reading current SNR\n- Monitoring SNR changes\n- Displaying signal quality information\n\nExample output:\n```\nConnected to JS8Call\nCurrent SNR: 12 dB\nSignal Quality: Good\nMonitoring SNR for 60 seconds...\nSNR fluctuated between 8-15 dB\nConnection closed\n```\n\n### 8. Speed Control (`08_get_speed.py`)\nIllustrates how to control JS8Call's speed settings. This example shows:\n- Reading current speed\n- Changing speed modes\n- Verifying speed changes\n\nExample output:\n```\nConnected to JS8Call\nCurrent speed: Normal\nChanging to Turbo mode\nSpeed changed successfully\nConnection closed\n```\n\n### 9. Callsign Operations (`09_get_callsign.py`)\nDemonstrates how to work with station callsigns. This example shows:\n- Reading current callsign\n- Validating callsign format\n- Displaying station identification\n\nExample output:\n```\nConnected to JS8Call\nCurrent callsign: W1AW\nCallsign format: Valid\nStation identification complete\nConnection closed\n```\n\n### 10. Grid Square Operations (`10_get_grid.py`)\nShows how to handle Maidenhead grid square operations. This example demonstrates:\n- Reading current grid square\n- Validating grid square format\n- Updating grid square from GPS (if available)\n\nExample output:\n```\nConnected to JS8Call\nCurrent grid square: FN31\nGrid square format: Valid\nGPS grid square: FN31pr\nGrid square updated successfully\nConnection closed\n```\n\n### 11. Band Predictor (`11_band_predictor.py`)\nA 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:\n\n- GPS integration for location and time\n- Weather data fetching from OpenWeatherMap API\n- Solar conditions from HamQSL API\n- Smart band prediction based on:\n  - Time of day\n  - Location (latitude)\n  - Temperature and weather conditions\n  - Solar flux and K-index\n  - Maximum Usable Frequency (MUF)\n- Automatic JS8Call frequency switching\n- Detailed explanation of recommendation rationale\n\nTo use this example, you'll need:\n1. A GPS device connected to your system\n2. An OpenWeatherMap API key (set as OPENWEATHERMAP_API_KEY in .env)\n3. Internet connectivity for weather and solar data\n4. JS8Call running and configured for API access\n\nThe script will:\n1. Get your location from GPS\n2. Fetch current weather and solar conditions\n3. Analyze conditions to recommend the best band\n4. Explain the rationale behind the recommendation\n5. Offer to automatically switch JS8Call to the recommended frequency\n\nExample output:\n```\nLocation: 40.9000°N, -74.3000°E\nTime: 14:30 America/New_York\n\nSolar Conditions:\nSolar Flux: 125\nK-index: 2\nMaximum Usable Frequency: 15.2 MHz\n\nWhat this means:\n• Moderate solar activity - Fair conditions for long-distance communication\n• Quiet geomagnetic field - Good conditions for HF propagation\n• Maximum usable frequency of 15.2 MHz suggests limited high-band propagation\n\nRecommended: 20m (14.078 MHz)\n\nRecommendation rationale:\n• Current temperature: 78°F\n• Weather conditions: partly cloudy\n• Mid-day conditions typically support higher frequency bands\n• Early morning/late afternoon conditions favor mid-range frequencies\n\nWould you like to switch JS8Call to this band? (y/n):\n```\n\n## Advanced Examples\n\n### Using the Direct Implementation\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Get current frequency\n    freq = api.get_frequency()\n    print(f\"Current frequency: {freq['freq']:,} Hz\")\n    \n    # Get current grid and update it\n    current_grid = api.get_grid()\n    print(f\"Current grid: {current_grid}\")\n    \n    # Update grid square (correctly sends in 'value' field)\n    success = api.set_grid(\"FN42\")\n    if success:\n        print(\"Grid updated successfully\")\n    \nfinally:\n    api.close()\n```\n\n### Messaging Operations\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Get the content of the transmit buffer\n    tx_text = api.get_tx_text()\n    print(f\"Current TX text: {tx_text}\")\n    \n    # Set new text to transmit\n    api.set_tx_text(\"CQ CQ CQ DE K1ABC\")\n    \n    # Send a message immediately \n    api.send_message_text(\"CQ CQ CQ DE K1ABC\")\n    \n    # Get received text\n    rx_text = api.get_rx_text()\n    print(f\"Received text: {rx_text}\")\n    \n    # Store a message in the inbox\n    response = api.store_message(\"W1AW\", \"Hello from my API script!\")\n    print(f\"Message ID: {response['params'].get('ID')}\")\n    \n    # Get messages from the inbox\n    messages = api.get_inbox_messages()\n    for msg in messages:\n        print(f\"From: {msg['params'].get('FROM')}\")\n        print(f\"Text: {msg['params'].get('TEXT')}\")\n        print(f\"Time: {msg['params'].get('UTC')}\")\n        print(\"---\")\n    \nfinally:\n    api.close()\n```\n\n### Station Monitoring\n\n```python\nfrom JS8CallAPI import JS8CallAPI\nimport time\nfrom datetime import datetime\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Monitor call activity for 60 seconds\n    end_time = time.time() + 60\n    while time.time() \u003c end_time:\n        # Get all recently heard stations\n        stations = api.get_call_activity()\n        \n        print(f\"\\n--- Station Activity at {datetime.now().strftime('%H:%M:%S')} ---\")\n        # Display info for each station\n        for call, info in stations.items():\n            print(f\"Call: {call}\")\n            print(f\"  SNR: {info.get('SNR')} dB\")\n            print(f\"  Grid: {info.get('GRID')}\")\n            print(f\"  Time: {datetime.fromtimestamp(info.get('UTC', 0)/1000).strftime('%Y-%m-%d %H:%M:%S')}\")\n        \n        # Get band activity\n        band = api.get_band_activity()\n        print(f\"\\n--- Band Activity: {len(band)} frequencies ---\")\n        \n        time.sleep(10)  # Check every 10 seconds\n    \nfinally:\n    api.close()\n```\n\n### Mode Control\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Get current speed mode\n    speed = api.get_speed()\n    speed_names = {\n        api.JS8_NORMAL: \"Normal\",\n        api.JS8_FAST: \"Fast\",\n        api.JS8_TURBO: \"Turbo\",\n        api.JS8_SLOW: \"Slow\",\n        api.JS8_ULTRA: \"Ultra\"\n    }\n    print(f\"Current speed: {speed_names.get(speed, 'Unknown')}\")\n    \n    # Set to turbo mode\n    api.set_speed(api.JS8_TURBO)\n    print(\"Switched to Turbo mode\")\n    \n    # Set to normal mode\n    api.set_speed(api.JS8_NORMAL)\n    print(\"Switched back to Normal mode\")\n    \nfinally:\n    api.close()\n```\n\n### GPS Integration\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Get current grid from JS8Call\n    js8call_grid = api.get_grid()\n    print(f\"JS8Call grid: {js8call_grid}\")\n    \n    # Connect to GPS and get current position as grid square\n    api.connect_gps()\n    gps_grid = api.get_gps_grid_square()\n    \n    if gps_grid:\n        print(f\"GPS grid: {gps_grid}\")\n        \n        # Compare and update if different\n        if gps_grid.upper() != js8call_grid.upper():\n            print(\"Grid square mismatch detected!\")\n            success = api.set_grid(gps_grid)\n            if success:\n                print(f\"Grid square updated to: {gps_grid}\")\n            else:\n                print(\"Error updating grid square\")\n    \nfinally:\n    api.close()\n```\n\n### Frequency Control\n\n```python\nfrom JS8CallAPI import JS8CallAPI\nimport time\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Store current frequency\n    current_freq = api.get_frequency()\n    print(f\"Current dial frequency: {current_freq['dial']:,} Hz\")\n    print(f\"Current offset: {current_freq['offset']} Hz\")\n    print(f\"Current operating frequency: {current_freq['freq']:,} Hz\")\n    \n    # Change to 14.078 MHz\n    api.set_frequency(dial_freq=14078000)\n    print(\"Changed to 14.078 MHz\")\n    time.sleep(5)\n    \n    # Change back to original frequency\n    api.set_frequency(dial_freq=current_freq['dial'])\n    print(f\"Changed back to {current_freq['dial']/1000000:.3f} MHz\")\n    \nfinally:\n    api.close()\n```\n\n---\n\n## API Reference\n\n### JS8CallAPI Class\n\nThe main class for interacting with JS8Call's TCP API. This class handles all message formatting and response parsing.\n\n#### Constants\n\n| Constant | Value | Description |\n|----------|-------|-------------|\n| `JS8_NORMAL` | 0 | Normal speed mode (JS8) |\n| `JS8_FAST` | 1 | Fast speed mode (JS8Fast) |\n| `JS8_TURBO` | 2 | Turbo speed mode (JS8Turbo) |\n| `JS8_SLOW` | 3 | Slow speed mode (JS8Slow) |\n| `JS8_ULTRA` | 4 | Ultra slow mode (JS8Ultra) |\n\n#### Constructor\n\n```python\nJS8CallAPI(host='127.0.0.1', port=2442)\n```\n\n**Parameters:**\n- `host` (str): JS8Call server hostname/IP (default: '127.0.0.1')\n- `port` (int): TCP port for JS8Call API (default: 2442)\n\n### Connection Methods\n\n#### `connect()`\nEstablishes connection to JS8Call server.\n\n**Returns:** None\n\n**Raises:**\n- `ConnectionRefusedError`: If JS8Call is not running or API not enabled\n- `Exception`: For other connection errors\n\n#### `connect_gps()`\nConnects to GPS daemon (gpsd).\n\n**Returns:** None\n\n**Raises:**\n- `Exception`: If connection to gpsd fails\n\n#### `close()`\nCloses socket connection to JS8Call.\n\n**Returns:** None\n\n#### `ping()`\nChecks if JS8Call is responsive.\n\n**Returns:**\n- `bool`: True if JS8Call responds, False otherwise\n\n### Frequency Methods\n\n#### `get_frequency()`\nGets current frequency information.\n\n**Returns:**\n```python\n{\n    'freq': int,    # Operating frequency in Hz\n    'dial': int,    # Dial frequency in Hz\n    'offset': int   # Frequency offset in Hz\n}\n```\n\n#### `set_frequency(dial_freq=None, offset=None)`\nSets current frequency.\n\n**Parameters:**\n- `dial_freq` (int, optional): Dial frequency in Hz\n- `offset` (int, optional): Frequency offset in Hz\n\n**Returns:**\n- `bool`: True if command sent successfully\n\n### Station Information Methods\n\n#### `get_callsign()`\nGets current station callsign.\n\n**Returns:**\n- `str`: Current station callsign\n\n#### `get_grid()`\nGets current grid locator.\n\n**Returns:**\n- `str`: Current Maidenhead grid locator\n\n#### `set_grid(grid)`\nSets current grid locator.\n\n**Parameters:**\n- `grid` (str): Maidenhead grid locator to set\n\n**Returns:**\n- `bool`: True if successful\n\n#### `get_station_info()`\nGets station information text.\n\n**Returns:**\n- `str`: Station information text\n\n#### `set_station_info(info)`\nSets station information text.\n\n**Parameters:**\n- `info` (str): Station information text to set\n\n**Returns:**\n- `bool`: True if successful\n\n#### `get_status()`\nGets current station status text.\n\n**Returns:**\n- `str`: Current status message\n\n#### `set_status(status)`\nSets current station status text.\n\n**Parameters:**\n- `status` (str): Status text to set\n\n**Returns:**\n- `bool`: True if successful\n\n### Messaging Methods\n\n#### `get_rx_text()`\nGets text from receive window.\n\n**Returns:**\n- `str`: Received text (up to 1024 characters)\n\n#### `get_tx_text()`\nGets text from transmit buffer.\n\n**Returns:**\n- `str`: Text in transmit buffer\n\n#### `set_tx_text(text)`\nSets text in transmit buffer.\n\n**Parameters:**\n- `text` (str): Text to set in transmit buffer\n\n**Returns:**\n- `bool`: True if successful\n\n#### `send_message_text(text)`\nSends a message immediately.\n\n**Parameters:**\n- `text` (str): Message text to send\n\n**Returns:**\n- `bool`: True if command sent successfully\n\n### Activity Monitoring Methods\n\n#### `get_call_activity()`\nGets information about recently heard stations.\n\n**Returns:**\n```python\n{\n    'CALLSIGN1': {\n        'SNR': int,        # Signal-to-noise ratio in dB\n        'GRID': str,       # Grid square\n        'UTC': int         # UTC timestamp in milliseconds\n    },\n    'CALLSIGN2': {\n        # Same structure...\n    }\n}\n```\n\n#### `get_selected_call()`\nGets currently selected callsign in UI.\n\n**Returns:**\n- `str`: Selected callsign or empty string if none selected\n\n#### `get_band_activity()`\nGets activity across the band.\n\n**Returns:**\n```python\n{\n    'OFFSET1': {\n        'FREQ': int,       # Operating frequency in Hz\n        'DIAL': int,       # Dial frequency in Hz\n        'OFFSET': int,     # Frequency offset in Hz\n        'TEXT': str,       # Decoded text\n        'SNR': int,        # Signal-to-noise ratio in dB\n        'UTC': int         # UTC timestamp in milliseconds\n    },\n    'OFFSET2': {\n        # Same structure...\n    }\n}\n```\n\n### Mode Control Methods\n\n#### `get_speed()`\nGets current JS8Call speed setting.\n\n**Returns:**\n- `int`: Current speed mode (use class constants to interpret)\n\n#### `set_speed(speed)`\nSets JS8Call speed mode.\n\n**Parameters:**\n- `speed` (int): Speed mode (use class constants, e.g., JS8_NORMAL)\n\n**Returns:**\n- `bool`: True if successful\n\n### Inbox Methods\n\n#### `get_inbox_messages(callsign=None)`\nGets messages from the inbox.\n\n**Parameters:**\n- `callsign` (str, optional): Filter messages by callsign\n\n**Returns:**\n```python\n[\n    {\n        'type': str,       # Message type\n        'value': str,      # Message value\n        'params': {\n            'FROM': str,   # Sender callsign\n            'TO': str,     # Recipient callsign\n            'TEXT': str,   # Message text\n            'UTC': int     # UTC timestamp in milliseconds\n        }\n    }\n]\n```\n\n#### `store_message(callsign, text)`\nStores a message in the inbox.\n\n**Parameters:**\n- `callsign` (str): Destination callsign\n- `text` (str): Message text\n\n**Returns:**\n```python\n{\n    'type': 'INBOX.MESSAGE',\n    'params': {\n        '_ID': int,        # Message request ID\n        'ID': int          # Stored message ID\n    }\n}\n```\n\n### UI Control Methods\n\n#### `raise_window()`\nRaises JS8Call window to foreground.\n\n**Returns:**\n- `bool`: True if command sent successfully\n\n### GPS Methods\n\n#### `get_gps_grid_square()`\nGets current grid square from GPS coordinates.\n\n**Returns:**\n- `str`: Maidenhead grid square calculated from current GPS position\n- `None`: If GPS error or no fix\n\n### Additional Methods\n\n#### `get_directed_message()`\nGets last directed message received.\n\n**Returns:**\n- `Optional[Dict[str, Any]]`: Last directed message received\n\n#### `get_spot()`\nGets last spot received.\n\n**Returns:**\n- `Optional[Dict[str, Any]]`: Last spot received\n\n#### `get_tx_frame()`\nGets last TX frame sent.\n\n**Returns:**\n- `Optional[Dict[str, Any]]`: Last TX frame sent\n\n#### `is_closed()`\nChecks if JS8Call connection has been closed.\n\n**Returns:**\n- `bool`: True if JS8Call has been closed, False otherwise\n\n---\n\n## Response Data Structures\n\nMost API methods return structured data extracted from the JS8Call API response. The library automatically parses JSON responses and provides appropriate Python data types.\n\n### Frequency Data\n```python\n{\n    'freq': 14074000,      # Current operating frequency in Hz\n    'dial': 14073000,      # Current dial frequency in Hz  \n    'offset': 1000         # Current offset in Hz\n}\n```\n\n### Call Activity Data\n```python\n{\n    'K1ABC': {\n        'SNR': -5,\n        'GRID': 'FN42eq',\n        'UTC': 1617981234567\n    },\n    'W1XYZ': {\n        'SNR': 12,\n        'GRID': 'EM73',\n        'UTC': 1617981234123\n    }\n}\n```\n\n### Inbox Message Data\n```python\n[\n    {\n        'type': 'MESSAGE',\n        'value': '',\n        'params': {\n            'FROM': 'K1ABC',\n            'TO': 'W1XYZ',\n            'TEXT': 'Hello there!',\n            'UTC': 1617981234567\n        }\n    }\n]\n```\n\n---\n\n## Error Handling\n\nThe library includes comprehensive error handling with informative messages for common issues:\n\n```python\nfrom JS8CallAPI import JS8CallAPI\n\napi = JS8CallAPI()\n\ntry:\n    api.connect()\n    \n    # Structured error handling for different operations\n    try:\n        api.connect_gps()\n        gps_grid = api.get_gps_grid_square()\n        if gps_grid:\n            print(f\"GPS grid: {gps_grid}\")\n    except ConnectionError as e:\n        print(f\"GPS connection error: {e}\")\n    except TimeoutError as e:\n        print(f\"GPS timeout: {e}\")\n    except Exception as e:\n        print(f\"GPS error: {e}\")\n    \n    # Frequency operations\n    try:\n        freq = api.get_frequency()\n        print(f\"Current frequency: {freq['freq']:,} Hz\")\n    except ConnectionError:\n        print(\"Connection to JS8Call lost\")\n    except TimeoutError:\n        print(\"JS8Call did not respond in time\")\n    except Exception as e:\n        print(f\"Error getting frequency: {e}\")\n    \nfinally:\n    api.close()\n```\n\n---\n\n## Requirements\n\n- **Python 3.6+**: For type annotations and modern language features\n- **JS8Call**: Running with TCP API enabled (Settings -\u003e Reporting -\u003e \"Enable TCP Server API\")\n- **gpsd** (optional): For GPS functionality, install with `sudo apt install gpsd` on Linux\n- **Network connectivity**: To the JS8Call server\n\n---\n\n## Contributing\n\nContributions are welcome! Please follow these steps:\n\n1. Fork the repository\n2. Create a feature branch: `git checkout -b feature-name`\n3. Commit your changes: `git commit -am 'Add some feature'`\n4. Push to the branch: `git push origin feature-name`\n5. Submit a pull request\n\nFor bug reports, please open an issue with a detailed description.\n\n---\n\n## License and Legal\n\n### Copyright Notice\n© 2023 Tiran Dagan, [BackstopRadio.com](https://backstopradio.com). All rights reserved.\n\n### MIT License\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n### Disclaimer\nThis 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.\n\nJS8Call is a separate project created by Jordan Sherer (KN4CRD). This API client is not officially affiliated with or endorsed by JS8Call.\n\n### Amateur Radio Notice\nThis software is designed for use by licensed amateur radio operators. Users are responsible for ensuring all transmissions comply with local regulations and licensing requirements.\n\n---\n\n## Acknowledgments\n\n- [JS8Call](https://js8call.com/) by Jordan Sherer (KN4CRD)\n- [gpsd-py3](https://github.com/MartijnBraam/gpsd-py3) for GPS integration\n- All contributors to this project\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n*Developed with ❤️ for the amateur radio community*  \n[BackstopRadio.com](https://backstopradio.com)\n\n\u003c/div\u003e ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftirandagan%2Fbackstop-python-js8call-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftirandagan%2Fbackstop-python-js8call-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftirandagan%2Fbackstop-python-js8call-api/lists"}