Files
rockblock9704-zephyr/README.md
T
scottp 97affae86c Who made it, and why: author, credits, licence and support
The README says who wrote it and the two projects it was built for, WII6
and OGLAS, with their sites; credits Ground Control for the modem and
the library that does the protocol work; and gives the licence with
contact details, where to report issues and what helps in a report, and
that it is not a Ground Control product. The licence, every source file
and the Python package's metadata carry the copyright and email; the
package lists its homepage, repository, issues and changelog.
2026-10-09 13:16:30 +11:00

213 lines
8.8 KiB
Markdown

# rockblock9704-zephyr
The RockBLOCK 9704, Ground Control's Iridium Certus modem, on Zephyr: a
module that builds Ground Control's own C library for any board with a
UART, gives it the one thread it needs, and offers a small API, a shell
command and a devicetree binding. With it come host tools in Python and a
fake modem, so the same tests run against Ground Control's Python binding,
against Zephyr on `native_sim`, and against Zephyr on a board.
The 9704 sends and receives IMT messages of up to 100 kB through the
Iridium network, and speaks JSPR, JSON lines over a 230400 8N1 UART. There
is no AT mode; it is not a 9602 or 9603.
rockblock9704-zephyr is written and maintained by Scott Penrose
([Digital Dimensions](https://www.dd.com.au/)). It was built for two
projects that send data home by satellite:
[WII6](https://wii6.au/), a wave-measuring buoy, and
[OGLAS](https://oglas.au/), a fleet of LoRa sensors, repeaters and hubs.
Both run it, and neither is needed to use it.
## Ground Control's library
This module does not reimplement or fork the protocol. Ground Control's
repository, [rock7/RockBLOCK-9704](https://github.com/rock7/RockBLOCK-9704)
(MIT), is pinned untouched in `west.yml` at `modules/lib/rockblock9704`,
and `CMakeLists.txt` compiles its portable core. What each part does:
| | Ground Control's library | This module |
| --- | --- | --- |
| JSPR, segmenting, CRC, provisioning check | yes | uses it unchanged |
| Platforms | Linux, Windows, Arduino serial presets | a Zephyr serial port, on the UART in devicetree |
| Threads | none: no locking, shared static buffers | one worker thread owns every call into it |
| API | blocking calls, or async calls with callbacks and `rbPoll()` | requests with a timeout each, callbacks from the worker, a status snapshot |
| Polling | the caller calls `rbPoll()`, one line per call | the worker wakes on a byte from the modem, so an idle modem costs nothing |
| Power pins | `rbBeginGpio` with libgpiod, Linux only | `power-gpios`, `iridium-enable-gpios`, `booted-gpios` in devicetree |
| Python | `rockblock9704` on PyPI, a binding of the C | the `rb9704` command line uses it; the fake modem tests it |
The library's source shows several things a caller has to work around;
`docs/JSPR.md`, section 8, lists them all. The ones this module handles:
- **Provisioning is asked once and kept for good**, a miss included. A
modem provisioned after the first send would refuse every topic until
a reboot. Before a send whose topic is not in what the library kept,
and after the modem refuses one, the module clears the library's cache
so it asks again: one free JSPR round trip.
- **The blocking calls stop working once callbacks are registered.** The
module uses the async path only.
- **A query during a send swallows the modem's segment requests**, since
the reader discards lines it is not waiting for. Info and signal
requests are refused with `-EBUSY` while a message is moving.
- **No locking.** Every library call is on the worker thread.
Others are for upstream to fix, and are listed in `docs/JSPR.md` for that:
`setState` waits for a misspelt target, a line with no status code reads
past its buffer, MT segment data is cut at 1446 characters, and the
Python binding never releases the GIL.
## Using it
In the application's `west.yml`; the import brings Ground Control's
library, pinned, at the path the module looks in, and nothing else:
```yaml
- name: rockblock9704-zephyr
url: https://github.com/SH3D/rockblock9704-zephyr
revision: v0.1.0
path: modules/lib/rockblock9704-zephyr
import:
name-allowlist:
- rockblock9704
```
In the board overlay, a node on the modem's UART. The pins are optional;
without them the modem is taken to be powered:
```dts
&usart3 {
current-speed = <230400>;
status = "okay";
rockblock {
compatible = "rock7,rockblock-9704";
power-gpios = <&gpiob 0 GPIO_ACTIVE_HIGH>; /* P_EN */
iridium-enable-gpios = <&gpiob 1 GPIO_ACTIVE_HIGH>; /* I_EN */
booted-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>; /* I_BTD */
};
};
```
`CONFIG_RB9704` follows the node. Then `<rb9704/rb9704.h>`:
```c
static void mo_done(uint16_t topic, uint16_t id, int result, void *user)
{
/* result 0: the network has it */
}
static void mt(const struct rb9704_mt *m, void *user)
{
/* m->data, m->len: copy what you need, it is acknowledged after */
}
static const struct rb9704_callbacks cb = { .mo_done = mo_done, .mt = mt };
rb9704_set_callbacks(&cb);
rb9704_begin(K_SECONDS(30));
rb9704_send(RB9704_TOPIC_RAW, buf, len, K_SECONDS(10)); /* costs airtime */
rb9704_send(RB9704_TOPIC_RAW, buf, len, K_NO_WAIT); /* queues, never blocks */
```
Every request takes a timeout and waits no longer; with `K_NO_WAIT` a send
queues and returns `-EINPROGRESS`, and its outcome, a refusal included,
comes through `mo_done`. Callbacks run on the worker thread. One modem per
system: the library keeps global state.
| Kconfig | Default | |
| --- | --- | --- |
| `CONFIG_RB9704_MESSAGE_MAX` | 2048 | largest message each way; three static buffers of this size |
| `CONFIG_RB9704_WORKER_STACK_SIZE` | 8192 | measured: the library parks 1.5 kB on the stack in `rbPoll()` |
| `CONFIG_RB9704_WORKER_PRIORITY` | 8 | |
| `CONFIG_RB9704_RX_RING_SIZE` | 8192 | the library's longest JSPR line |
| `CONFIG_RB9704_POWER` | with `power-gpios` | drive the pins |
| `CONFIG_RB9704_SHELL` | with `CONFIG_SHELL` | `rb9704` on the shell |
| `CONFIG_RB9704_UPSTREAM_DIR` | empty | where Ground Control's library is, if not where `west.yml` puts it |
## The shell
```
rb9704 begin | end | power on|off
rb9704 status what is known, without asking the modem
rb9704 info IMEI, serial, versions, ICCID, SIM, temperature
rb9704 signal bars now
rb9704 send <topic> <text> | sendhex <topic> <hex> costs airtime
rb9704 spy on|off every JSPR line, both ways
```
`samples/shell` is the shell plus callbacks that print what arrives.
## Host tools
`python/` is `rb9704` (the modem and Cloudloop from a computer), `rb9704-fake`
(a fake modem on a pty or a serial port), and the tests. `python/README.md`
has every verb; `docs/CLOUDLOOP.md` is the account, the provisioning and
the order to test a new modem in.
## Testing
```sh
python/install.sh
python/.venv/bin/pytest -q python/tests # Python
python/.venv/bin/pytest -q python/tests --zephyr-exe <native_sim zephyr.exe>
python/.venv/bin/pytest -q python/tests/test_zephyr.py \
--board-console /dev/ttyACM0 --board-modem /dev/ttyUSB0 # a board
python/.venv/bin/pytest -q python/tests/test_real.py --real-modem /dev/ttyUSB0
```
`docs/TESTING.md` has the levels, the two-board rig that needs no
USB-serial adapter, and what has passed: the Python level, `native_sim`,
an ESP32-S3 and an STM32U5 against the fake, and free checks against a
real 9704 from Python and from `native_sim`.
## Boards
Any board with a free UART. The sample has overlays for `native_sim`,
NUCLEO-U575ZI-Q, Seeed XIAO ESP32S3, ESP32-S3-DevKitC, ESP32 DevKitC and
LilyGO T3-S3 (an out-of-tree board in OGLAST2: build with its
`-DBOARD_ROOT`). It has run on the first three; all six build.
## Layout
```
CMakeLists.txt, Kconfig, zephyr/module.yml the module
dts/bindings/rock7,rockblock-9704.yaml the devicetree binding
include/rb9704/rb9704.h the API
src/ serial port, shims, worker, power, shell
samples/shell/ the shell sample, with board overlays
tests/rig/ the two-board test rig
python/ rb9704, rb9704-fake, the tests
docs/JSPR.md the protocol as the library speaks it
docs/CLOUDLOOP.md account to first message
docs/TESTING.md the tests and how to run them
west.yml the pins
```
## Credits
- [Ground Control](https://www.groundcontrol.com/), for the RockBLOCK 9704
and its [C library](https://github.com/rock7/RockBLOCK-9704) (MIT),
which does all of the protocol work. This module builds it unchanged.
- The serial port and the worker began in OGLAS's satellite bench and were
carried through WII6 before moving here.
## Licence
MIT, as Ground Control's library is. See `LICENSE`.
Copyright (c) 2026 Scott Penrose <scottp@dd.com.au>
* https://www.sh3d.com.au/ - Sh3d
* https://www.dd.com.au/ - Digital Dimensions
## Support
Report issues on the [GitHub mirror](https://github.com/SH3D/rockblock9704-zephyr/issues)
(the Gitea instance does not allow public sign-ups). Most helpful in a
report: the output of `rb9704 spy on` around the problem, and the modem's
firmware version from `rb9704 info`.
## Disclaimer
This is not an official Ground Control product. Use at your own risk, and
remember that every `send` costs airtime.