The two boards OGLAST2's satellite bench was built for: the T3-S3 with the modem on uart0 (GPIO43 and 44), the original ESP32 DevKitC on uart2 (GPIO17 and 16). Both build; neither has run here.
7.7 KiB
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.
Ground Control's library
This module does not reimplement or fork the protocol. Ground Control's
repository, 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
-EBUSYwhile 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:
- 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:
&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>:
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
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
Licence
MIT, as Ground Control's library is. The serial port and worker began in the OGLAS satellite bench and were carried through WII6 before moving here.