Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors = ["Your Name <you@example.com>"]
publish = false

[dependencies]
trunk-recorder-plugin = "0.5.0"
trunk-recorder-plugin = "0.7.0"
# The settings form is drawn from your config structs' JSON Schema. Keep the
# major version the SDK uses (1).
schemars = "1"
Expand Down
40 changes: 35 additions & 5 deletions docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ you don't use costs everyone something.
| `unit` | `unit` | A radio registers, affiliates, … | Busy systems: many per second |
| `audio` | `audio` | Live audio of recording calls | About 50 per second per call |
| `status` | `status` | Every system's state | Every 5 seconds |
| `tone_pattern` | `tone_pattern` | A call's tones made a pattern: a page, an alert tone, a DTMF or KNOX string, a Morse ID | As heard |

The examples below are the JSON on the wire. The Rust SDK hands you the same
data as structs; the field names match. Times are Unix seconds. Frequencies
Expand Down Expand Up @@ -65,10 +66,13 @@ Only recorded calls conclude. Calls the recorder skipped (encrypted, no free
recorder, an unknown talkgroup when those aren't recorded) have no files.

A system can have several sites, each a control channel the recorder follows.
A call heard on more than one is one call, saved once from the site that heard
it best: `short_name` is the system, and **`site`** is the site the call was
kept from (a system of one site: its short name; none for a conventional
channel's call).
A call heard on more than one is recorded on each and the best copy saved:
`short_name` is the system, and **`site`** is the site the call was kept from
(a system of one site: its short name; none for a conventional channel's call).
A copy the saved one doesn't hold all of is saved too (better a duplicate than
a transmission lost), and copies of one transmission share
**`transmission_id`**. A system with `conversationMode` off makes each
transmission a call of its own.

## `call.start` and `call.end`

Expand Down Expand Up @@ -100,6 +104,8 @@ it was recorded, come later in `call.concluded`.
talkgroups aren't recorded), `no_recorder` (all in use) or `no_source` (no
radio covers its frequency). More reasons may be added.
- **`units`**: radios heard on the call so far. At `call.end`, all of them.
**`sources`** has the same radios with each one's name (`tag`, as the call
JSON's `srcList` has it) and what its transmission said of itself.
- **`patched_talkgroups`**: the talkgroups patched with this one so far, its
own included, ascending; empty when it isn't patched.

Expand All @@ -108,9 +114,12 @@ it was recorded, come later in `call.concluded`.
A radio did something on the control channel.

```json
{ "type": "unit", "system": 0, "short_name": "county", "site": "county", "kind": "affiliation", "unit": 1116707, "talkgroup": 101, "time": 1790771551.4 }
{ "type": "unit", "system": 0, "short_name": "county", "site": "county", "kind": "affiliation", "unit": 1116707, "tag": "Engine 12", "talkgroup": 101, "alpha_tag": "Fire Dispatch", "time": 1790771551.4 }
```

`tag` is the radio's name and `alpha_tag` the talkgroup's, looked up as the
event is sent (left out when there's none).

`kind` is one of:

| Kind | Meaning | `talkgroup` |
Expand Down Expand Up @@ -164,6 +173,27 @@ several sites has several, with the same `index` and `short_name` and its own
- **`decode_rate`** is control channel messages decoded per second. A healthy
P25 control channel gives about 40.

## `tone_pattern`

A call's in-band tones made a pattern, as the call JSON's `tone_patterns` will
list it.

```json
{
"type": "tone_pattern", "short_name": "county", "site": "county", "call_num": 412,
"talkgroup": 101, "alpha_tag": "Fire Dispatch", "src_tag": "Station 1", "time": 1790771552.0,
"pattern": { "pattern": "two_tone", "tones": [849.63, 1292.85], "lengths": [1.0, 3.04],
"format": "Quick Call II", "pos": 0.4, "len": 4.04, "src": 1116707, "origin": "frame",
"text": "two-tone 849.63 → 1292.85 Hz, 1 + 3.04 s (Quick Call II)", "signals": ["FD_PAGE"] }
}
```

`pattern` is `two_tone` (a page), `long`, `hi_low` (a warble), `pulsed`,
`digits` (DTMF; `knox: true` for KNOX), `morse`, `sequence` or `beep`.
`signals` names the system's signals it matches. `decrypted` is set when it was
heard in voice decrypted with the system's keys (only plugins allowed decrypted
calls get those).

## Order and loss

Events of one topic arrive in the order they happened. `call.concluded` comes
Expand Down
5 changes: 5 additions & 0 deletions docs/how-plugins-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ cheap to clone and works from any thread.
| `host.info(…)`, `warn`, `error`, `debug` | In the recorder's log, prefixed with the plugin's id. `debug` lines are dropped. |
| `host.status(State::Warning, "OpenMHz is down; 12 calls queued")` | Next to the plugin in the plugins list, until the next status. |
| `host.call_result(path, Outcome::Ok, "", url)` | What became of a call: `Ok`, `Skipped` (deliberately not handled) or `Failed`. |
| `reply.ok("Sent")`, `reply.fail(why)` | The answer to one of its buttons: `Plugin::action(a, reply)` gets a `Reply` to answer with, now or later from another thread. Dropped unanswered, it says the plugin didn't answer. |

A plugin with `live_settings` in its manifest gets new settings through
`Plugin::settings(setup)` while it runs; returning `Err` keeps the ones it had
and shows why.

> **Never print to stdout.** A plugin's stdout carries the protocol, so
> `println!` sends garbage to the recorder. The recorder logs a line it can't
Expand Down
39 changes: 35 additions & 4 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ JSON object on stdout and exits 0:
| `id` | yes | `[a-z0-9][a-z0-9-]*` |
| `name`, `version`, `description` | yes | |
| `api` | yes | The plugin API version it speaks. The recorder refuses a higher one than it knows. |
| `subscribe` | yes | Topics: `call.start`, `call.end`, `call.concluded`, `unit`, `audio`, `status`. |
| `subscribe` | yes | Topics: `call.start`, `call.end`, `call.concluded`, `unit`, `audio`, `status`, `tone_pattern`. |
| `audio_formats` | no | `["m4a"]` to get M4A with `call.concluded`. |
| `config`, `system_config` | no | [Settings schemas](#settings-schemas). Leave them out for none. |
| `homepage`, `repository`, `authors`, `license` | no | |
| `page` | no | `{ "title": "…" }`: a page of its own in the recorder's navigation, for settings too many for its card |
| `contribute` | no | Buttons elsewhere in the interface: `[{ "on": "signal", "label", "hint", "append", "item" }]` adds `item` to the settings list `append` from each of a system's signals |
| `live_settings` | no | `true`: it takes new settings as it runs (`settings`, below) instead of being restarted |

It must finish within 10 seconds.

Expand All @@ -63,7 +66,7 @@ Every message has a `type`. The first is always `hello`.
{
"type": "hello",
"api": 1,
"host": { "name": "trunk-pro", "version": "0.4.0" },
"host": { "name": "trunk-pro", "version": "0.2.10" },
"config": { "server": "https://api.openmhz.com" },
"systems": [
{ "index": 0, "short_name": "county", "kind": "p25", "config": { "apiKey": "…" } },
Expand Down Expand Up @@ -93,8 +96,27 @@ settings, it sends a `status` of `error` saying why, and exits with status

### Events

`call.concluded`, `call.start`, `call.end`, `unit`, `audio` and `status`. Each
message is the event's fields plus `type`. See [Events](events.md).
`call.concluded`, `call.start`, `call.end`, `unit`, `audio`, `status` and
`tone_pattern`. Each message is the event's fields plus `type`. See
[Events](events.md).

### `action`

```json
{ "type": "action", "request": 7, "action": "test", "value": { "name": "Discord", "url": "…" } }
```

Someone pressed one of the plugin's buttons (`x-actions` in its settings
schema). `value` is the settings object the button is on, as edited (saved or
not). Answer with `action.result` and the same `request` within 15 seconds;
the answer is shown by the button, in the browser that pressed it.

### `settings`

The plugin's settings changed while it runs: the `hello` it would start with
now, with `"type": "settings"`. Only a plugin with `live_settings` is sent
one; others are restarted. A plugin that can't take them reports an `error`
status and keeps the ones it had.

### `shutdown`

Expand Down Expand Up @@ -142,6 +164,14 @@ next status.
(not handled, on purpose) or `failed` (given up on). `message` and `url` are
optional.

### `action.result`

```json
{ "type": "action.result", "request": 7, "ok": true, "message": "Sent a test to Discord" }
```

The answer to an `action`, with its `request`.

A line that isn't JSON is logged as it is.

## Exit status
Expand Down Expand Up @@ -176,6 +206,7 @@ objects. The recorder's settings form understands this subset:
| `{"type": "array", "items": {"type": "string"}}` (or number) | List |
| `{"type": "array", "items": {"type": "object", …}}` | A list of groups, added and removed one by one |
| `{"type": "string", "x-system": true}` | Menu of the recorder's systems (short names); follows renames |
| `x-systems`, `x-signals`, `x-ref`, `x-tones`, `x-title`, `x-summary`, `x-when`, `x-actions` | Systems or signals to tick, items of another list, tones typed in, list items titled and summed up, fields shown only when they apply, buttons: see [the recorder's guide](https://github.com/TrunkRecorder/trunk-recorder-pro/blob/main/docs/plugins/writing-plugins.md#settings-and-the-form) |

On any field: `title` (the label; the key if there's none), `description`
(help text), `default`.
Expand Down
6 changes: 6 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ for programmers, and isn't shown.
| `Vec` of a struct | A list of groups, with Add and Remove buttons. The struct's doc comment names each one ("Stream 1", "Add stream"). |
| `String` with `#[schemars(extend("x-system" = true))]` | Menu of the recorder's systems, by short name. When a system is renamed, the recorder changes the setting to match. |

More hints for bigger forms (systems or signals to tick, a list naming another
list's items, typed-in tones, list items titled and summed up, fields shown
only when they apply, buttons): `x-systems`, `x-signals`, `x-ref`, `x-tones`,
`x-title`, `x-summary`, `x-when`, `x-actions`, in [the recorder's
guide](https://github.com/TrunkRecorder/trunk-recorder-pro/blob/main/docs/plugins/writing-plugins.md#settings-and-the-form).

Fields appear in the order you declare them.

### Fields that have to be filled in
Expand Down
Loading