Metadata-Version: 2.4
Name: js8call-improved
Version: 0.1.0
Summary: Python library and utilities for interacting with the JS8Call-improved API via TCP
Author-email: Jeff Francis <gjfrancis@protonmail.com>
License: MIT
Project-URL: Homepage, https://github.com/jfrancis42/js8net-improved
Requires-Python: >=3.6
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: gps
Requires-Dist: gpsd-py3; extra == "gps"
Dynamic: license-file

# js8call-improved

#### _Jeff Francis <gjfrancis@protonmail.com>_, N0GQ

---

> ## ⚠️ ALPHA SOFTWARE — READ BEFORE USING ⚠️
>
> **This library should be considered Alpha quality software.** It has not been
> extensively tested against a running JS8Call-improved instance. APIs may
> change, functions may behave unexpectedly, and some features may not yet work
> correctly. Use with caution, especially in any automated or unattended
> scenario involving actual transmissions.
>
> **The library is also subject to change** as the JS8Call-improved API itself
> evolves and improves. Function signatures, return values, and the set of
> supported commands may all shift in future releases without notice until the
> API stabilizes. Watch the changelog and pin to a specific version in
> production code.

---

> **Note:** This library targets the **JS8Call-improved** fork. It is **not**
> compatible with the original (legacy) JS8Call software. For the original
> JS8Call, see [js8call-legacy](https://github.com/jfrancis42/js8call-legacy).

## Installation

```
pip install js8call-improved
```

Or from source:

```
git clone https://github.com/jfrancis42/js8net-improved.git
cd js8net-improved
pip install .
```

In your Python code, import the library as:

```python3
from js8call_improved import *
```

## The Library

js8call-improved is a python3 package for interacting with the JS8Call-improved
API. It works exclusively in TCP mode and targets the API extensions introduced
in JS8Call-improved (API v2.6+), while also supporting all commands available
in the original API (v2.5).

The JS8Call-improved API is a significant improvement over the legacy API,
adding remote configuration control, filter management, PTT status, queue
depth queries, OS/version introspection, and much more. The underlying protocol
is the same JSON-over-TCP transport used by the legacy software.

This library is closely modeled on
[js8call-legacy](https://github.com/jfrancis42/js8call-legacy) to make
migration as smooth as possible. See the **Differences from js8call-legacy**
section below for a complete list of changes.

## Getting Started

To get started, import the library, then tell it to connect to your
JS8Call-improved instance:

```python3
from js8call_improved import *
start_net("10.1.1.141", 2242)
```

Note that the **default port is 2242**, not 2442. This is the default API port
in JS8Call-improved (configurable under Settings → Reporting → API).

At this point, three daemon threads are running: one receives messages from
JS8Call-improved and updates internal state; one sends queued commands; one
sends keepalive heartbeats every five minutes.

## API Reference

### Connection

```python3
start_net(host, port)
```

Connect to a JS8Call-improved instance. Default: `localhost`, port `2242`.

### Frequency Control

```python3
get_freq()
```

Returns `{'dial': hz, 'freq': hz, 'offset': hz}`.

```python3
set_freq(dial, offset)
```

Set dial frequency and audio offset, both in Hz.

```python3
get_ptt()
```

**New in js8call-improved.** Get current PTT state. Returns `True` if
transmitting, `False` if not.

```python3
set_tune(enabled)
```

**New in js8call-improved.** Enable (`True`) or disable (`False`) continuous
carrier tune mode for antenna tuning.

```python3
tx_halt()
```

**New in js8call-improved.** Emergency stop: immediately halt any ongoing
transmission.

### Station Configuration

```python3
get_callsign()
get_grid()
set_grid(grid)
get_info()
set_info(info)
```

These work identically to their js8call-legacy counterparts.

```python3
get_status()
set_status(status_text)
```

**New in js8call-improved.** Get or set the station status message.

```python3
get_version()
```

**New in js8call-improved.** Returns the JS8Call-improved version string.

```python3
get_os()
```

**New in js8call-improved.** Returns OS information from the running
JS8Call-improved instance.

```python3
get_spot()
set_spot(enabled)
```

**New in js8call-improved.** Get or set the spot-enabled state.

```python3
get_config()
```

**New in js8call-improved.** Fetch the full station configuration as a dict.
Keys include: `AUTO_REPLY`, `JS8HB`, `HBACK`, `MULTI_DECODER`, `HB_INTERVAL`,
`HB_TIMER_ACTIVE`, `MONITOR`, `TX_ENABLED`, `SPEED`, `CAN_HB`,
`AUTOREPLY_CONFIRMATION`, `MY_GROUPS`, `AVOID_ALLCALL`.

```python3
set_auto_reply(enabled)
set_js8hb(enabled)
set_hback(enabled)
set_multi_decoder(enabled)
```

**New in js8call-improved.** Toggle autoreply, JS8 heartbeat networking,
heartbeat acknowledgements, and simultaneous multi-decoder on or off.

```python3
set_hb_interval(minutes)
set_hb_timer(active)
send_hb()
```

**New in js8call-improved.** Control the automated heartbeat: set the interval
in minutes, start/stop the timer, or trigger an immediate API-level heartbeat.
Note: `send_hb()` triggers JS8Call-improved's built-in heartbeat mechanism and
is distinct from `send_heartbeat()`, which constructs and queues a heartbeat
message directly.

```python3
set_groups(groups)
```

**New in js8call-improved.** Replace the list of subscribed groups. Pass a
list of strings, e.g. `["@GROUPONE", "@GROUPTWO"]`.

```python3
set_avoid_allcall(enabled)
```

**New in js8call-improved.** Opt out of (`True`) or back in to (`False`)
`@ALLCALL` messages.

### Activity Queries

```python3
get_call_activity()
```

Get the list of recently heard callsigns (right panel). Returns a list of
`Callstation` objects.

```python3
get_call_selected()
```

Return the callsign currently selected in the GUI.

```python3
get_band_activity()
```

Get the band activity list (left panel). Returns a list of `Bandstation`
objects.

```python3
get_free_offsets()
```

**New in js8call-improved.** Get available (unoccupied) frequency segments in
the passband. Returns a dict with:
- `FREE`: list of `{'LOW': hz, 'HIGH': hz, 'WIDTH': hz}` segments
- `BANDWIDTH`: signal bandwidth in Hz for the current speed
- `SPEED`: current speed mode
- `LOW`, `HIGH`: usable passband limits in Hz

```python3
get_filter()
set_filter(center, width)
set_filter_enabled(enabled)
```

**New in js8call-improved.** Get, set, or toggle the bandpass filter. `center`
and `width` are in Hz.

### Text Buffers

```python3
get_rx_text()
get_tx_text()
set_tx_text(text)
```

These work identically to their js8call-legacy counterparts.

### Transmission Queue

```python3
get_queue_depth()
```

**New in js8call-improved.** Returns the number of messages currently waiting
in the transmit queue.

### Speed Control

```python3
get_speed()
set_speed(speed)
speed_name(speed)
```

Speed values: `0`=Normal, `1`=Fast, `2`=Turbo, `4`=Slow, `8`=Ultra.

**Change from js8call-legacy:** Speed `8` (Ultra/JS8-60) is newly supported.
`speed_name()` now returns `'Ultra'` for speed `8` instead of `'Invalid'`.

### Window

```python3
raise_window()
```

Bring the JS8Call-improved window to the foreground.

### Messaging

```python3
send_message(message)
send_directed_message(dest_call, message)
send_inbox_message(dest_call, message)
```

These work identically to their js8call-legacy counterparts.

```python3
send_heartbeat(grid=False)
```

Construct and queue a heartbeat message over the air. See also `send_hb()`.

```python3
send_aprs(dest, message)
send_aprs_grid(grid)
send_sms(phone, message)
send_email(address, message)
send_sota(summit, freq, mode, comment=False)
send_pota(park, freq, mode, comment=False)
```

These work identically to their js8call-legacy counterparts.

```python3
query_snr(dest_call)
query_grid(dest_call)
query_status(dest_call)
query_info(dest_call)
query_hearing(dest_call)
```

These work identically to their js8call-legacy counterparts.

```python3
get_messages()
store_message(callsign, text)
```

These work identically to their js8call-legacy counterparts.

### Connection Health

```python3
alive()
```

Returns `True` if a valid response was received in the last ~5.5 minutes.

## Receiving Messages

Incoming messages that don't map to a query response are placed into `rx_queue`
(protected by `rx_lock`). The message types you'll typically see are:

- `RX.SPOT` — spot message
- `RX.ACTIVITY` — partial frame (a fragment of a larger message)
- `RX.DIRECTED` — complete reassembled directed message
- `RX.TEXT` — received text buffer update
- `STATION.CLOSING` — **new in js8call-improved**: JS8Call-improved is
  shutting down; the `closing` global is also set to `True`

Example receive loop:

```python3
while True:
    if not rx_queue.empty():
        with rx_lock:
            message = rx_queue.get()
            if message['type'] == 'RX.DIRECTED':
                print(message['params']['FROM'], '->', message['params']['TO'])
                print(message['value'])
            elif message['type'] == 'STATION.CLOSING':
                print("JS8Call-improved is shutting down.")
    time.sleep(0.1)
```

## Differences from js8call-legacy

Users migrating from js8call-legacy should note the following changes:

| Item | js8call-legacy | js8call-improved |
|------|---------------|-----------------|
| **Import** | `from js8call_legacy import *` | `from js8call_improved import *` |
| **Default port** | `2442` | `2242` |
| **Speed 8 (Ultra)** | Not supported; `speed_name(8)` returns `'Invalid'` | Supported; `speed_name(8)` returns `'Ultra'` |
| **`send_heartbeat()`** | Sends heartbeat over the air | Same behavior; see also new `send_hb()` |
| **`STATION.CLOSING`** | Not handled | Sets `closing=True` global; queued for user processing |
| **`get_status()` / `set_status()`** | Not available | New functions |
| **`get_version()` / `get_os()`** | Not available | New functions |
| **`get_ptt()` / `set_tune()` / `tx_halt()`** | Not available | New functions |
| **`get_spot()` / `set_spot()`** | Not available | New functions |
| **`get_config()`** | Not available | New function; returns full config dict |
| **`set_auto_reply()` / `set_js8hb()` / `set_hback()` / `set_multi_decoder()`** | Not available | New functions |
| **`set_hb_interval()` / `set_hb_timer()` / `send_hb()`** | Not available | New functions |
| **`set_groups()` / `set_avoid_allcall()`** | Not available | New functions |
| **`get_free_offsets()`** | Not available | New function |
| **`get_filter()` / `set_filter()` / `set_filter_enabled()`** | Not available | New functions |
| **`get_queue_depth()`** | Not available | New function |

All other functions (`get_callsign()`, `get_grid()`, `set_grid()`, `get_info()`,
`set_info()`, `get_freq()`, `set_freq()`, `get_call_activity()`,
`get_band_activity()`, `get_call_selected()`, `get_rx_text()`, `get_tx_text()`,
`set_tx_text()`, `get_speed()`, `set_speed()`, `raise_window()`,
`send_message()`, `send_directed_message()`, `send_inbox_message()`,
`send_heartbeat()`, `send_aprs()`, `send_aprs_grid()`, `send_sms()`,
`send_email()`, `send_sota()`, `send_pota()`, `query_snr()`, `query_grid()`,
`query_status()`, `query_info()`, `query_hearing()`, `get_messages()`,
`store_message()`, `alive()`) are **drop-in compatible** with js8call-legacy.

## Bundled Scripts

| Script | Description |
|---|---|
| `send_aprs.py` | Send an APRS message to a callsign |
| `send_email.py` | Send an email via a JS8 gateway |
| `send_sms.py` | Send an SMS via a JS8 gateway |
| `send_heartbeat.py` | Send a heartbeat message |
| `send_message.py` | Send a directed message |
| `send_pota.py` | Submit a POTA spot |
| `send_sota.py` | Submit a SOTA spot |
| `send_grid.py` | Send your grid square to APRS; optionally track via GPSD |
| `fill_grids.py` | Query stations with missing grid squares |
| `make_calldb.py` | Build a local callsign database (USA, Canada, Australia) |
| `stations.py` | Report current station activity |
| `example.py` | Example code showing basic library usage |

All scripts accept `--js8-host` and `--js8-port` (default `localhost:2242`), or
the environment variables `JS8HOST` and `JS8PORT` combined with `--env`. Speed
arguments now accept `8` for Ultra mode in addition to the legacy values.

For GPS tracking with `send_grid.py`, install the optional dependency:

```
pip install gpsd-py3
```

## Credits

* The SVG icons are from Creative Commons and Font Awesome Free.
* Thanks to COAS Book in Las Cruces, NM for being awesome. https://www.coasbooks.com/
