The module: Ground Control's library on Zephyr, with one worker thread
Ground Control's RockBLOCK 9704 library is pinned untouched in west.yml and compiled here for any board with a UART: a serial port on the bus of the rock7,rockblock-9704 devicetree node, the library's timing calls on the kernel, and one worker thread that owns every call into it, since the library has no locking and shares static buffers. The worker sleeps until a request or a byte from the modem arrives. Requests (begin, end, power, send, info, signal) wait as long as the caller says; a send with K_NO_WAIT queues and reports through mo_done. Callbacks for a sent message, a received one, the signal and the provisioning; a status snapshot that never touches the modem. The library asks for provisioning once and keeps a miss for good; the worker clears its cache before a send whose topic is missing and after the modem refuses one, so a modem provisioned later is seen. The serial port and worker began in the OGLAS satellite bench and were carried through WII6's lib/wii6-sat.
This commit is contained in:
commit
c0e92a7f02
16 files changed
+1608
No files matched your search
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# Zephyr build trees
|
||||||
|
build/
|
||||||
|
build-*/
|
||||||
|
twister-out*/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Editors and OS
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# rockblock9704-zephyr: Ground Control's RockBLOCK 9704 library built for
|
||||||
|
# Zephyr, with the serial port, the timing calls and one worker thread to own
|
||||||
|
# it. The library itself is not copied or forked: it is Ground Control's
|
||||||
|
# repository, pristine, pinned in west.yml at modules/lib/rockblock9704, and
|
||||||
|
# this file compiles the part of it that runs on a microcontroller.
|
||||||
|
|
||||||
|
if(CONFIG_RB9704)
|
||||||
|
if(CONFIG_RB9704_UPSTREAM_DIR)
|
||||||
|
set(RB9704_UP ${CONFIG_RB9704_UPSTREAM_DIR})
|
||||||
|
elseif(DEFINED WEST_TOPDIR)
|
||||||
|
set(RB9704_UP ${WEST_TOPDIR}/modules/lib/rockblock9704)
|
||||||
|
else()
|
||||||
|
set(RB9704_UP ${ZEPHYR_BASE}/../modules/lib/rockblock9704)
|
||||||
|
endif()
|
||||||
|
get_filename_component(RB9704_UP ${RB9704_UP} ABSOLUTE)
|
||||||
|
if(NOT EXISTS ${RB9704_UP}/src/rockblock_9704.h)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"rockblock9704-zephyr: Ground Control's library is not at ${RB9704_UP}. "
|
||||||
|
"Add rock7/RockBLOCK-9704 to your west.yml at modules/lib/rockblock9704 "
|
||||||
|
"(this repository's west.yml has the pin) and run `west update`, or set "
|
||||||
|
"CONFIG_RB9704_UPSTREAM_DIR.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# The library's portable core. Left out: crossplatform.c (no Zephyr branch;
|
||||||
|
# src/rb9704_shims.c instead), serial_presets/ (src/rb9704_serial.c is the
|
||||||
|
# Zephyr preset), gpio.c (libgpiod), kermit (modem firmware update).
|
||||||
|
set(RB9704_UP_SRCS
|
||||||
|
${RB9704_UP}/src/rockblock_9704.c
|
||||||
|
${RB9704_UP}/src/jspr.c
|
||||||
|
${RB9704_UP}/src/jspr_command.c
|
||||||
|
${RB9704_UP}/src/imt_queue.c
|
||||||
|
${RB9704_UP}/src/serial.c
|
||||||
|
${RB9704_UP}/src/third_party/cJSON/cJSON.c
|
||||||
|
${RB9704_UP}/src/third_party/base64/base64.c)
|
||||||
|
|
||||||
|
zephyr_library_named(rb9704)
|
||||||
|
zephyr_library_sources(
|
||||||
|
src/rb9704_serial.c
|
||||||
|
src/rb9704_shims.c
|
||||||
|
src/rb9704_worker.c
|
||||||
|
${RB9704_UP_SRCS})
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_RB9704_SHELL src/rb9704_shell.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_RB9704_POWER src/rb9704_power.c)
|
||||||
|
zephyr_include_directories(include)
|
||||||
|
zephyr_library_include_directories(${RB9704_UP}/src src)
|
||||||
|
|
||||||
|
# Both the library and the worker see these: the message buffers' size,
|
||||||
|
# CRC included, and the library's hook for a platform's serial port.
|
||||||
|
math(EXPR RB9704_PAYLOAD "${CONFIG_RB9704_MESSAGE_MAX} + 2")
|
||||||
|
zephyr_library_compile_definitions(
|
||||||
|
IMT_PAYLOAD_SIZE=${RB9704_PAYLOAD}U
|
||||||
|
SERIAL_CONTEXT_SETUP_FUNC=rb9704_serial_set_context)
|
||||||
|
|
||||||
|
# The library's own files: the prototypes it calls without a declaration
|
||||||
|
# in scope outside its presets, the POSIX declarations picolibc guards
|
||||||
|
# behind feature macros, and -w because it is built pristine and its
|
||||||
|
# warnings are Ground Control's to fix.
|
||||||
|
set_source_files_properties(${RB9704_UP_SRCS} PROPERTIES COMPILE_OPTIONS
|
||||||
|
"-include${CMAKE_CURRENT_LIST_DIR}/src/rb9704_glue.h;-D_POSIX_C_SOURCE=200809L;-D_DEFAULT_SOURCE;-w")
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
menuconfig RB9704
|
||||||
|
bool "RockBLOCK 9704 Iridium modem"
|
||||||
|
default y
|
||||||
|
depends on DT_HAS_ROCK7_ROCKBLOCK_9704_ENABLED
|
||||||
|
depends on SERIAL
|
||||||
|
select RING_BUFFER
|
||||||
|
select UART_INTERRUPT_DRIVEN
|
||||||
|
help
|
||||||
|
Ground Control's RockBLOCK 9704 (Iridium Certus, IMT messages) on the
|
||||||
|
UART its devicetree node sits on. Builds Ground Control's library,
|
||||||
|
pinned pristine at modules/lib/rockblock9704, with a Zephyr serial
|
||||||
|
port and one worker thread that owns it. <rb9704/rb9704.h> is the API.
|
||||||
|
|
||||||
|
if RB9704
|
||||||
|
|
||||||
|
config RB9704_MESSAGE_MAX
|
||||||
|
int "Largest message in either direction (bytes)"
|
||||||
|
default 2048
|
||||||
|
range 16 100000
|
||||||
|
help
|
||||||
|
The modem takes up to 100 kB, and the library keeps one static buffer
|
||||||
|
of this size for messages out and one for messages in, plus one in
|
||||||
|
this module for the copy rb9704_send takes. 2 kB is three buffers of
|
||||||
|
2 kB; raise it as far as the RAM allows.
|
||||||
|
|
||||||
|
config RB9704_POWER
|
||||||
|
bool "Drive the power pins from devicetree"
|
||||||
|
default y if $(dt_compat_any_has_prop,$(DT_COMPAT_ROCK7_ROCKBLOCK_9704),power-gpios)
|
||||||
|
select GPIO
|
||||||
|
help
|
||||||
|
P_EN, I_EN and I_BTD as the devicetree node gives them. Without
|
||||||
|
them the modem is taken to be powered, as the board is with the pins
|
||||||
|
left open.
|
||||||
|
|
||||||
|
config RB9704_WORKER_STACK_SIZE
|
||||||
|
int "Worker thread stack size"
|
||||||
|
default 8192
|
||||||
|
help
|
||||||
|
Measured, not guessed: the library parks a 1.5 kB MT segment on the
|
||||||
|
stack in rbPoll(), with cJSON's recursion beneath it. A smaller stack
|
||||||
|
overflows only when a message arrives, which may be days after the
|
||||||
|
change.
|
||||||
|
|
||||||
|
config RB9704_WORKER_PRIORITY
|
||||||
|
int "Worker thread priority"
|
||||||
|
default 8
|
||||||
|
|
||||||
|
config RB9704_RX_RING_SIZE
|
||||||
|
int "Serial receive ring (bytes)"
|
||||||
|
default 8192
|
||||||
|
help
|
||||||
|
The library's longest JSPR line is 8 kB.
|
||||||
|
|
||||||
|
config RB9704_SHELL
|
||||||
|
bool "`rb9704` shell command"
|
||||||
|
default y
|
||||||
|
depends on SHELL
|
||||||
|
|
||||||
|
config RB9704_UPSTREAM_DIR
|
||||||
|
string "Where Ground Control's library is"
|
||||||
|
default ""
|
||||||
|
help
|
||||||
|
Empty: <west topdir>/modules/lib/rockblock9704, where this
|
||||||
|
repository's west.yml puts it.
|
||||||
|
|
||||||
|
module = RB9704
|
||||||
|
module-str = rb9704
|
||||||
|
source "subsys/logging/Kconfig.template.log_config"
|
||||||
|
|
||||||
|
endif # RB9704
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Scott Penrose
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE 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.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
description: |
|
||||||
|
RockBLOCK 9704, Ground Control's Iridium Certus (IMT) modem board, on a
|
||||||
|
UART. The modem speaks JSPR, JSON lines over a fixed 230400 8N1; there is
|
||||||
|
no AT mode. One per system: the library underneath keeps global state.
|
||||||
|
|
||||||
|
The control pins are optional. With none, the modem is assumed powered and
|
||||||
|
enabled, which is how the board behaves with the pins left open.
|
||||||
|
|
||||||
|
compatible: "rock7,rockblock-9704"
|
||||||
|
|
||||||
|
include:
|
||||||
|
- uart-device.yaml
|
||||||
|
|
||||||
|
properties:
|
||||||
|
power-gpios:
|
||||||
|
type: phandle-array
|
||||||
|
description: |
|
||||||
|
P_EN, the supply enable. Driven active to power the board.
|
||||||
|
|
||||||
|
iridium-enable-gpios:
|
||||||
|
type: phandle-array
|
||||||
|
description: |
|
||||||
|
I_EN, the modem enable. Driven active once the supply is up.
|
||||||
|
|
||||||
|
booted-gpios:
|
||||||
|
type: phandle-array
|
||||||
|
description: |
|
||||||
|
I_BTD, the modem's booted signal. When present, power-up waits for it
|
||||||
|
instead of a fixed delay.
|
||||||
|
|
||||||
|
boot-delay-ms:
|
||||||
|
type: int
|
||||||
|
default: 4000
|
||||||
|
description: |
|
||||||
|
How long to wait after enabling the modem before talking to it, when
|
||||||
|
there is no booted-gpios to wait on.
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- |
|
||||||
|
&usart3 {
|
||||||
|
current-speed = <230400>;
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
modem: rockblock {
|
||||||
|
compatible = "rock7,rockblock-9704";
|
||||||
|
power-gpios = <&gpiob 0 GPIO_ACTIVE_HIGH>;
|
||||||
|
iridium-enable-gpios = <&gpiob 1 GPIO_ACTIVE_HIGH>;
|
||||||
|
booted-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
rock7 Rock Seven Mobile Services Ltd (Ground Control)
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704.h - the RockBLOCK 9704 Iridium modem on Zephyr.
|
||||||
|
*
|
||||||
|
* Ground Control's C library (github.com/rock7/RockBLOCK-9704) does the
|
||||||
|
* protocol: JSPR, JSON lines over the modem's UART, and the segmenting of
|
||||||
|
* messages to and from the Iridium network. It has no locking, shares
|
||||||
|
* static buffers across its calls, and its blocking calls stop working once
|
||||||
|
* callbacks are registered. So this module gives the library one thread of
|
||||||
|
* its own, the worker, and every call into it happens there. What this
|
||||||
|
* header offers is that thread's front door:
|
||||||
|
*
|
||||||
|
* - requests (begin, end, send, info, signal) handed to the worker, each
|
||||||
|
* waiting as long as the caller says and no longer: K_NO_WAIT queues
|
||||||
|
* the request and returns, which is what a thread that must never
|
||||||
|
* block wants;
|
||||||
|
* - callbacks from the worker when a message has gone, a message has
|
||||||
|
* arrived, the signal changes or the provisioning is known;
|
||||||
|
* - a status snapshot that never touches the modem.
|
||||||
|
*
|
||||||
|
* One modem per system: the library keeps global state. The modem is the
|
||||||
|
* first devicetree node with compatible "rock7,rockblock-9704", a child of
|
||||||
|
* the UART it sits on (dts/bindings/rock7,rockblock-9704.yaml).
|
||||||
|
*
|
||||||
|
* Every call returns 0 or a negative errno, and never costs airtime except
|
||||||
|
* rb9704_send.
|
||||||
|
*/
|
||||||
|
#ifndef RB9704_RB9704_H_
|
||||||
|
#define RB9704_RB9704_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Topics below this are the network's own; the library refuses them. */
|
||||||
|
#define RB9704_TOPIC_MIN 64
|
||||||
|
/* Cloudloop's default topic for raw messages (cloudloopTopics_t). */
|
||||||
|
#define RB9704_TOPIC_RAW 244
|
||||||
|
|
||||||
|
#define RB9704_MAX_TOPICS 8
|
||||||
|
|
||||||
|
struct rb9704_topic {
|
||||||
|
uint16_t id;
|
||||||
|
char name[57];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* What the modem says about itself; rb9704_info() fills it. */
|
||||||
|
struct rb9704_info {
|
||||||
|
char imei[16];
|
||||||
|
char serial[16];
|
||||||
|
char hw_version[16];
|
||||||
|
char fw_version[16];
|
||||||
|
char iccid[23];
|
||||||
|
int8_t board_temp_c;
|
||||||
|
bool card_present;
|
||||||
|
bool sim_connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A snapshot for status displays; never talks to the modem. */
|
||||||
|
struct rb9704_status {
|
||||||
|
bool powered; /* the supply is on, or there is no power pin */
|
||||||
|
bool begun; /* rb9704_begin has run and the library is up */
|
||||||
|
/* signal, from the modem's unsolicited constellation state */
|
||||||
|
bool signal_known;
|
||||||
|
bool visibility_known; /* a constellation report has said */
|
||||||
|
bool constellation_visible;
|
||||||
|
uint8_t signal_bars; /* 0 to 5 */
|
||||||
|
int16_t signal_level;
|
||||||
|
int64_t signal_ms; /* uptime of the last report */
|
||||||
|
/* provisioning: the topics this modem may send on */
|
||||||
|
bool provisioning_known;
|
||||||
|
uint8_t topic_count;
|
||||||
|
struct rb9704_topic topics[RB9704_MAX_TOPICS];
|
||||||
|
/* messages */
|
||||||
|
bool mo_in_flight;
|
||||||
|
uint16_t mo_topic;
|
||||||
|
uint32_t mo_sent, mo_failed;
|
||||||
|
uint32_t mt_received, mt_failed;
|
||||||
|
/* the serial port */
|
||||||
|
uint32_t rx_dropped; /* bytes the receive ring had no room for */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A message from the network, valid for the length of the callback. */
|
||||||
|
struct rb9704_mt {
|
||||||
|
uint16_t topic;
|
||||||
|
uint16_t id;
|
||||||
|
const uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Called on the worker thread. Keep them short and never call back into a
|
||||||
|
* blocking rb9704_* request from one: the worker is the thread that would
|
||||||
|
* have to answer it. Any member may be NULL. */
|
||||||
|
struct rb9704_callbacks {
|
||||||
|
/* An MO has finished: result 0 when the network has it. */
|
||||||
|
void (*mo_done)(uint16_t topic, uint16_t id, int result, void *user);
|
||||||
|
/* An MT has arrived whole. It is acknowledged to the modem after this
|
||||||
|
* returns, so copy what is wanted. */
|
||||||
|
void (*mt)(const struct rb9704_mt *mt, void *user);
|
||||||
|
/* The constellation state changed. */
|
||||||
|
void (*signal)(uint8_t bars, bool visible, void *user);
|
||||||
|
/* The modem reported its provisioning. */
|
||||||
|
void (*provisioning)(const struct rb9704_topic *topics, uint8_t count, void *user);
|
||||||
|
void *user;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Replace the application's callbacks; NULL clears them. */
|
||||||
|
void rb9704_set_callbacks(const struct rb9704_callbacks *cb);
|
||||||
|
|
||||||
|
/* Switch the board's supply and modem enable, when the devicetree gives the
|
||||||
|
* pins. Waits for the booted pin or boot-delay-ms on the way up. -ENOTSUP
|
||||||
|
* with no power pin. rb9704_end first if it was begun. */
|
||||||
|
int rb9704_power(bool on);
|
||||||
|
|
||||||
|
/* Open the serial port and bring the library up: API version, SIM
|
||||||
|
* interface, operational state. Powers the modem first if there is a pin
|
||||||
|
* and it is off. -EALREADY when begun. */
|
||||||
|
int rb9704_begin(k_timeout_t timeout);
|
||||||
|
|
||||||
|
/* Close it down. -EALREADY when not begun. */
|
||||||
|
int rb9704_end(k_timeout_t timeout);
|
||||||
|
|
||||||
|
/* Hand a message to the modem for the network. The data is copied, so the
|
||||||
|
* caller's buffer is free on return. Returns 0 once the modem has accepted
|
||||||
|
* it; completion comes through mo_done. With K_NO_WAIT it queues and
|
||||||
|
* returns -EINPROGRESS, and a refusal also comes through mo_done.
|
||||||
|
*
|
||||||
|
* -EINVAL topic below RB9704_TOPIC_MIN, or len 0 or over
|
||||||
|
* CONFIG_RB9704_MESSAGE_MAX
|
||||||
|
* -ENOTCONN not begun
|
||||||
|
* -EBUSY a message is already in flight
|
||||||
|
* -EACCES the modem is not provisioned for this topic
|
||||||
|
* -EAGAIN the request slot was taken and the timeout ran out
|
||||||
|
*/
|
||||||
|
int rb9704_send(uint16_t topic, const void *data, size_t len, k_timeout_t timeout);
|
||||||
|
|
||||||
|
/* Ask the modem who it is. Free, milliseconds. */
|
||||||
|
int rb9704_info(struct rb9704_info *out, k_timeout_t timeout);
|
||||||
|
|
||||||
|
/* Ask the modem for its signal now. Returns 0 to 5 bars, or -errno. */
|
||||||
|
int rb9704_signal(k_timeout_t timeout);
|
||||||
|
|
||||||
|
void rb9704_status(struct rb9704_status *out);
|
||||||
|
|
||||||
|
/* Print every JSPR line in both directions with printk, for debugging. */
|
||||||
|
void rb9704_spy(bool on);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* RB9704_RB9704_H_ */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_glue.h - what Ground Control's library needs declared on Zephyr.
|
||||||
|
*
|
||||||
|
* Force-included into every library file (CMakeLists.txt): none of its
|
||||||
|
* platform branches (__linux__, _WIN32, ARDUINO) is taken on Zephyr, so its
|
||||||
|
* calls to the serial-setup hook and to millis() and delay() would otherwise
|
||||||
|
* be implicit declarations. rb9704_serial.c and rb9704_shims.c define them.
|
||||||
|
* usleep() is not declared here: picolibc's <unistd.h> does.
|
||||||
|
*/
|
||||||
|
#ifndef RB9704_GLUE_H_
|
||||||
|
#define RB9704_GLUE_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* SERIAL_CONTEXT_SETUP_FUNC: fills the library's serialContext with this
|
||||||
|
* port. `port` is ignored; the UART is the devicetree node's bus. */
|
||||||
|
bool rb9704_serial_set_context(const char *port, uint32_t baud);
|
||||||
|
|
||||||
|
unsigned long millis(void);
|
||||||
|
void delay(uint32_t ms);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* RB9704_GLUE_H_ */
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_internal.h - between this module's own files.
|
||||||
|
*/
|
||||||
|
#ifndef RB9704_INTERNAL_H_
|
||||||
|
#define RB9704_INTERNAL_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#define RB9704_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(rock7_rockblock_9704)
|
||||||
|
|
||||||
|
/* rb9704_serial.c */
|
||||||
|
int rb9704_serial_available(void);
|
||||||
|
uint32_t rb9704_serial_dropped(void);
|
||||||
|
void rb9704_serial_spy(bool on);
|
||||||
|
|
||||||
|
/* rb9704_worker.c */
|
||||||
|
k_tid_t rb9704_worker_tid_get(void);
|
||||||
|
void rb9704_worker_rx(void);
|
||||||
|
|
||||||
|
/* rb9704_power.c, or the stubs in rb9704_worker.c without the pins */
|
||||||
|
int rb9704_power_init(void);
|
||||||
|
int rb9704_power_set(bool on);
|
||||||
|
bool rb9704_power_on(void);
|
||||||
|
bool rb9704_power_present(void);
|
||||||
|
|
||||||
|
#endif /* RB9704_INTERNAL_H_ */
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_power.c - the board's control pins, as the devicetree gives them.
|
||||||
|
*
|
||||||
|
* power-gpios P_EN, the supply
|
||||||
|
* iridium-enable-gpios I_EN, the modem, once the supply is up
|
||||||
|
* booted-gpios I_BTD, the modem saying it has booted
|
||||||
|
*
|
||||||
|
* Up: supply, a moment, enable, then the booted pin or boot-delay-ms.
|
||||||
|
* Down: enable, then supply. Called on the worker thread only.
|
||||||
|
*/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "rb9704_internal.h"
|
||||||
|
|
||||||
|
LOG_MODULE_DECLARE(rb9704, CONFIG_RB9704_LOG_LEVEL);
|
||||||
|
|
||||||
|
#define BOOT_TIMEOUT_MS 30000
|
||||||
|
|
||||||
|
static const struct gpio_dt_spec pwr = GPIO_DT_SPEC_GET_OR(RB9704_NODE, power_gpios, {0});
|
||||||
|
static const struct gpio_dt_spec en = GPIO_DT_SPEC_GET_OR(RB9704_NODE, iridium_enable_gpios, {0});
|
||||||
|
static const struct gpio_dt_spec btd = GPIO_DT_SPEC_GET_OR(RB9704_NODE, booted_gpios, {0});
|
||||||
|
|
||||||
|
static bool on;
|
||||||
|
|
||||||
|
bool rb9704_power_present(void)
|
||||||
|
{
|
||||||
|
return pwr.port != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rb9704_power_on(void)
|
||||||
|
{
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_power_init(void)
|
||||||
|
{
|
||||||
|
if (pwr.port == NULL) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
if (!gpio_is_ready_dt(&pwr) || gpio_pin_configure_dt(&pwr, GPIO_OUTPUT_INACTIVE) != 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
if (en.port != NULL &&
|
||||||
|
(!gpio_is_ready_dt(&en) || gpio_pin_configure_dt(&en, GPIO_OUTPUT_INACTIVE) != 0)) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
if (btd.port != NULL && (!gpio_is_ready_dt(&btd) || gpio_pin_configure_dt(&btd, GPIO_INPUT) != 0)) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_power_set(bool want)
|
||||||
|
{
|
||||||
|
if (pwr.port == NULL) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
if (!want) {
|
||||||
|
if (en.port != NULL) {
|
||||||
|
(void)gpio_pin_set_dt(&en, 0);
|
||||||
|
}
|
||||||
|
(void)gpio_pin_set_dt(&pwr, 0);
|
||||||
|
on = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (on) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(void)gpio_pin_set_dt(&pwr, 1);
|
||||||
|
k_msleep(10);
|
||||||
|
if (en.port != NULL) {
|
||||||
|
(void)gpio_pin_set_dt(&en, 1);
|
||||||
|
}
|
||||||
|
if (btd.port != NULL) {
|
||||||
|
int64_t until = k_uptime_get() + BOOT_TIMEOUT_MS;
|
||||||
|
|
||||||
|
while (gpio_pin_get_dt(&btd) != 1) {
|
||||||
|
if (k_uptime_get() > until) {
|
||||||
|
LOG_ERR("modem did not signal booted in %d ms", BOOT_TIMEOUT_MS);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
k_msleep(50);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
k_msleep(DT_PROP(RB9704_NODE, boot_delay_ms));
|
||||||
|
}
|
||||||
|
on = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_serial.c - the library's serial port on a Zephyr UART. This is the
|
||||||
|
* whole port: five function pointers filled into the library's global
|
||||||
|
* serialContext by rb9704_serial_set_context(), which rbBegin() reaches
|
||||||
|
* through the SERIAL_CONTEXT_SETUP_FUNC macro. The UART is the bus of the
|
||||||
|
* "rock7,rockblock-9704" devicetree node.
|
||||||
|
*
|
||||||
|
* RX rides the UART ISR into a ring buffer sized for the library's largest
|
||||||
|
* JSPR line (8 kB, matching its jspr RX buffer); the library then pulls
|
||||||
|
* bytes one at a time from receiveJspr(). TX is polled: a worst-case 2 kB
|
||||||
|
* command is about 90 ms at 230400, and only the worker thread ever writes.
|
||||||
|
*
|
||||||
|
* The spy (rb9704_spy): JSPR is printable JSON lines, so the spy reassembles
|
||||||
|
* the lines in each direction and printk()s them. Do not use the library's
|
||||||
|
* own -DDEBUG for this: its TX debug path truncates the caller's buffer in
|
||||||
|
* place.
|
||||||
|
*/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/uart.h>
|
||||||
|
#include <zephyr/sys/ring_buffer.h>
|
||||||
|
#include <zephyr/sys/atomic.h>
|
||||||
|
#include <zephyr/sys/printk.h>
|
||||||
|
|
||||||
|
#include "rb9704_glue.h"
|
||||||
|
#include "rb9704_internal.h"
|
||||||
|
#include "serial.h" /* the library's serialContext */
|
||||||
|
|
||||||
|
/* Defined in the library's serial.c; each preset declares it itself. */
|
||||||
|
extern serialContext context;
|
||||||
|
|
||||||
|
static const struct device *const modem_uart = DEVICE_DT_GET(DT_BUS(RB9704_NODE));
|
||||||
|
|
||||||
|
RING_BUF_DECLARE(rb9704_rx_ring, CONFIG_RB9704_RX_RING_SIZE);
|
||||||
|
static K_SEM_DEFINE(rb9704_rx_sem, 0, 1);
|
||||||
|
static atomic_t rx_dropped;
|
||||||
|
static atomic_t spy_on;
|
||||||
|
|
||||||
|
/* Per-byte RX timeout. receiveJspr() treats a 0-return mid-line as
|
||||||
|
* end-of-data and discards the partial line, so this must comfortably
|
||||||
|
* exceed any inter-byte gap the modem produces (gaps at 230400 are
|
||||||
|
* microseconds; 500 ms is one bounded worker stall, worst case). */
|
||||||
|
#define RX_BYTE_TIMEOUT_MS 500
|
||||||
|
|
||||||
|
/* --- the spy ------------------------------------------------------ */
|
||||||
|
|
||||||
|
struct spy_line {
|
||||||
|
char buf[192];
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct spy_line spy_rx = { .len = 0 };
|
||||||
|
static struct spy_line spy_tx = { .len = 0 };
|
||||||
|
|
||||||
|
static void spy_flush(struct spy_line *l, const char *tag)
|
||||||
|
{
|
||||||
|
if (l->len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
l->buf[l->len] = '\0';
|
||||||
|
printk("rb9704 %s: %s\n", tag, l->buf);
|
||||||
|
l->len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spy_feed(struct spy_line *l, const char *tag,
|
||||||
|
const char *data, size_t len)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
char c = data[i];
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
spy_flush(l, tag);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
l->buf[l->len++] = (c >= 0x20 && c < 0x7f) ? c : '.';
|
||||||
|
if (l->len >= sizeof(l->buf) - 1) {
|
||||||
|
spy_flush(l, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rb9704_serial_spy(bool on)
|
||||||
|
{
|
||||||
|
atomic_set(&spy_on, on ? 1 : 0);
|
||||||
|
if (!on) {
|
||||||
|
spy_rx.len = 0;
|
||||||
|
spy_tx.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- ISR ---------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void modem_uart_isr(const struct device *dev, void *user_data)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(user_data);
|
||||||
|
|
||||||
|
while (uart_irq_update(dev) && uart_irq_rx_ready(dev)) {
|
||||||
|
uint8_t tmp[64];
|
||||||
|
int n = uart_fifo_read(dev, tmp, sizeof(tmp));
|
||||||
|
|
||||||
|
if (n <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t put = ring_buf_put(&rb9704_rx_ring, tmp, n);
|
||||||
|
|
||||||
|
if (put < (uint32_t)n) {
|
||||||
|
atomic_add(&rx_dropped, n - put);
|
||||||
|
}
|
||||||
|
k_sem_give(&rb9704_rx_sem);
|
||||||
|
rb9704_worker_rx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- the five serialContext functions ----------------------------------- */
|
||||||
|
|
||||||
|
static bool ser_init(void)
|
||||||
|
{
|
||||||
|
if (!device_is_ready(modem_uart)) {
|
||||||
|
printk("rb9704: UART %s not ready\n", modem_uart->name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ring_buf_reset(&rb9704_rx_ring);
|
||||||
|
k_sem_reset(&rb9704_rx_sem);
|
||||||
|
uart_irq_callback_user_data_set(modem_uart, modem_uart_isr, NULL);
|
||||||
|
uart_irq_rx_enable(modem_uart);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ser_deinit(void)
|
||||||
|
{
|
||||||
|
uart_irq_rx_disable(modem_uart);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ser_read(char *bytes, const uint16_t length)
|
||||||
|
{
|
||||||
|
int64_t deadline = k_uptime_get() + RX_BYTE_TIMEOUT_MS;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int n = ring_buf_get(&rb9704_rx_ring, (uint8_t *)bytes, length);
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
if (atomic_get(&spy_on)) {
|
||||||
|
spy_feed(&spy_rx, "RX", bytes, n);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t left = deadline - k_uptime_get();
|
||||||
|
|
||||||
|
if (left <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
k_sem_take(&rb9704_rx_sem, K_MSEC(left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ser_write(const char *data, const uint16_t length)
|
||||||
|
{
|
||||||
|
if (atomic_get(&spy_on)) {
|
||||||
|
spy_feed(&spy_tx, "TX", data, length);
|
||||||
|
}
|
||||||
|
for (uint16_t i = 0; i < length; i++) {
|
||||||
|
uart_poll_out(modem_uart, data[i]);
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ser_peek(void)
|
||||||
|
{
|
||||||
|
return (int)ring_buf_size_get(&rb9704_rx_ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- setup + worker-facing helpers -------------------------------------- */
|
||||||
|
|
||||||
|
bool rb9704_serial_set_context(const char *port, uint32_t baud)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(port); /* the UART is the devicetree node's bus */
|
||||||
|
|
||||||
|
context.serialInit = ser_init;
|
||||||
|
context.serialDeInit = ser_deinit;
|
||||||
|
context.serialRead = ser_read;
|
||||||
|
context.serialWrite = ser_write;
|
||||||
|
context.serialPeek = ser_peek;
|
||||||
|
context.serialBaud = baud; /* informational; rate is set by DT */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_serial_available(void)
|
||||||
|
{
|
||||||
|
return (int)ring_buf_size_get(&rb9704_rx_ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rb9704_serial_dropped(void)
|
||||||
|
{
|
||||||
|
return (uint32_t)atomic_get(&rx_dropped);
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_shell.c - `rb9704` on the Zephyr shell.
|
||||||
|
*
|
||||||
|
* Everything here is local serial traffic and free, except `send`, which
|
||||||
|
* costs airtime. Messages from the network arrive through the
|
||||||
|
* application's mt callback; the sample prints them.
|
||||||
|
*/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/shell/shell.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <rb9704/rb9704.h>
|
||||||
|
|
||||||
|
#define WAIT K_SECONDS(30)
|
||||||
|
|
||||||
|
/* The errno values differ between C libraries, so name the ones this
|
||||||
|
* module returns. */
|
||||||
|
static const char *err_name(int rc)
|
||||||
|
{
|
||||||
|
switch (-rc) {
|
||||||
|
case EALREADY: return "EALREADY";
|
||||||
|
case ENOTCONN: return "ENOTCONN";
|
||||||
|
case EBUSY: return "EBUSY";
|
||||||
|
case EACCES: return "EACCES";
|
||||||
|
case EAGAIN: return "EAGAIN";
|
||||||
|
case EINVAL: return "EINVAL";
|
||||||
|
case EIO: return "EIO";
|
||||||
|
case ETIMEDOUT: return "ETIMEDOUT";
|
||||||
|
case EINPROGRESS: return "EINPROGRESS";
|
||||||
|
case ENOTSUP: return "ENOTSUP";
|
||||||
|
case EDEADLK: return "EDEADLK";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int report(const struct shell *sh, const char *what, int rc)
|
||||||
|
{
|
||||||
|
if (rc < 0) {
|
||||||
|
shell_error(sh, "%s: %d %s", what, rc, err_name(rc));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
shell_print(sh, "%s: ok", what);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_begin(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(argc); ARG_UNUSED(argv);
|
||||||
|
return report(sh, "begin", rb9704_begin(WAIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_end(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(argc); ARG_UNUSED(argv);
|
||||||
|
return report(sh, "end", rb9704_end(WAIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_power(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(argc);
|
||||||
|
return report(sh, "power", rb9704_power(strcmp(argv[1], "on") == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_status(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
struct rb9704_status s;
|
||||||
|
|
||||||
|
ARG_UNUSED(argc); ARG_UNUSED(argv);
|
||||||
|
rb9704_status(&s);
|
||||||
|
shell_print(sh, "powered %s, begun %s", s.powered ? "yes" : "no", s.begun ? "yes" : "no");
|
||||||
|
if (s.signal_known) {
|
||||||
|
shell_print(sh, "signal %u bars%s, %lld s ago", s.signal_bars,
|
||||||
|
!s.visibility_known ? ""
|
||||||
|
: s.constellation_visible ? ", constellation visible"
|
||||||
|
: ", constellation not visible",
|
||||||
|
(k_uptime_get() - s.signal_ms) / 1000);
|
||||||
|
} else {
|
||||||
|
shell_print(sh, "signal unknown");
|
||||||
|
}
|
||||||
|
if (s.provisioning_known) {
|
||||||
|
shell_print(sh, "topics %u", s.topic_count);
|
||||||
|
for (int i = 0; i < s.topic_count; i++) {
|
||||||
|
shell_print(sh, " %u %s", s.topics[i].id, s.topics[i].name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shell_print(sh, "topics provisioning not seen yet");
|
||||||
|
}
|
||||||
|
shell_print(sh, "mo %u sent, %u failed%s", s.mo_sent, s.mo_failed,
|
||||||
|
s.mo_in_flight ? ", one in flight" : "");
|
||||||
|
shell_print(sh, "mt %u received, %u failed", s.mt_received, s.mt_failed);
|
||||||
|
shell_print(sh, "serial %u bytes dropped", s.rx_dropped);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_info(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
struct rb9704_info i;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
ARG_UNUSED(argc); ARG_UNUSED(argv);
|
||||||
|
rc = rb9704_info(&i, WAIT);
|
||||||
|
if (rc != 0) {
|
||||||
|
return report(sh, "info", rc);
|
||||||
|
}
|
||||||
|
shell_print(sh, "imei %s", i.imei);
|
||||||
|
shell_print(sh, "serial %s", i.serial);
|
||||||
|
shell_print(sh, "hardware %s", i.hw_version);
|
||||||
|
shell_print(sh, "firmware %s", i.fw_version);
|
||||||
|
shell_print(sh, "iccid %s", i.iccid);
|
||||||
|
shell_print(sh, "board %d C", i.board_temp_c);
|
||||||
|
shell_print(sh, "sim card %s, %s", i.card_present ? "present" : "absent",
|
||||||
|
i.sim_connected ? "connected" : "not connected");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_signal(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
ARG_UNUSED(argc); ARG_UNUSED(argv);
|
||||||
|
rc = rb9704_signal(WAIT);
|
||||||
|
if (rc < 0) {
|
||||||
|
return report(sh, "signal", rc);
|
||||||
|
}
|
||||||
|
shell_print(sh, "signal: %d bars", rc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unhex(const char *s, uint8_t *out, size_t cap)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
while (s[0] != '\0' && s[1] != '\0') {
|
||||||
|
char b[3] = { s[0], s[1], '\0' };
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
if (n == cap) {
|
||||||
|
return -ENOSPC;
|
||||||
|
}
|
||||||
|
out[n++] = (uint8_t)strtoul(b, &end, 16);
|
||||||
|
if (*end != '\0') {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
s += 2;
|
||||||
|
}
|
||||||
|
return s[0] == '\0' ? (int)n : -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_send(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
static uint8_t buf[CONFIG_RB9704_MESSAGE_MAX];
|
||||||
|
unsigned long topic = strtoul(argv[1], NULL, 10);
|
||||||
|
bool hex = strcmp(argv[0], "sendhex") == 0;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
ARG_UNUSED(argc);
|
||||||
|
if (hex) {
|
||||||
|
len = unhex(argv[2], buf, sizeof(buf));
|
||||||
|
} else {
|
||||||
|
len = MIN(strlen(argv[2]), sizeof(buf));
|
||||||
|
memcpy(buf, argv[2], len);
|
||||||
|
}
|
||||||
|
if (len <= 0) {
|
||||||
|
shell_error(sh, "nothing to send (%d)", len);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
shell_print(sh, "%d bytes on topic %lu: this costs airtime", len, topic);
|
||||||
|
return report(sh, "send", rb9704_send((uint16_t)topic, buf, len, WAIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_spy(const struct shell *sh, size_t argc, char **argv)
|
||||||
|
{
|
||||||
|
bool on = strcmp(argv[1], "on") == 0;
|
||||||
|
|
||||||
|
ARG_UNUSED(argc);
|
||||||
|
rb9704_spy(on);
|
||||||
|
shell_print(sh, "spy %s", on ? "on: every JSPR line printed" : "off");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHELL_STATIC_SUBCMD_SET_CREATE(rb9704_cmds,
|
||||||
|
SHELL_CMD(begin, NULL, "open the port and bring the library up", cmd_begin),
|
||||||
|
SHELL_CMD(end, NULL, "close it down", cmd_end),
|
||||||
|
SHELL_CMD_ARG(power, NULL, "power on|off: the board's supply, if wired", cmd_power, 2, 0),
|
||||||
|
SHELL_CMD(status, NULL, "what is known, without asking the modem", cmd_status),
|
||||||
|
SHELL_CMD(info, NULL, "IMEI, serial, versions, ICCID, SIM, temperature", cmd_info),
|
||||||
|
SHELL_CMD(signal, NULL, "signal bars now", cmd_signal),
|
||||||
|
SHELL_CMD_ARG(send, NULL, "send <topic> <text>: one message, costs airtime", cmd_send, 3, 0),
|
||||||
|
SHELL_CMD_ARG(sendhex, NULL, "sendhex <topic> <hex>: one message, costs airtime", cmd_send, 3, 0),
|
||||||
|
SHELL_CMD_ARG(spy, NULL, "spy on|off: print every JSPR line", cmd_spy, 2, 0),
|
||||||
|
SHELL_SUBCMD_SET_END);
|
||||||
|
|
||||||
|
SHELL_CMD_REGISTER(rb9704, &rb9704_cmds, "RockBLOCK 9704 Iridium modem", NULL);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_shims.c - the library's three timing calls on the Zephyr kernel,
|
||||||
|
* in place of its crossplatform.c, which has no Zephyr branch.
|
||||||
|
*/
|
||||||
|
/* useconds_t and usleep() sit behind a feature macro in picolibc. */
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "rb9704_glue.h"
|
||||||
|
|
||||||
|
unsigned long millis(void)
|
||||||
|
{
|
||||||
|
return k_uptime_get_32();
|
||||||
|
}
|
||||||
|
|
||||||
|
void delay(uint32_t ms)
|
||||||
|
{
|
||||||
|
k_msleep(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* picolibc declares usleep in <unistd.h>; the library calls it directly. */
|
||||||
|
int usleep(useconds_t us)
|
||||||
|
{
|
||||||
|
k_usleep(us);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,593 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* rb9704_worker.c - the one thread that calls into Ground Control's library.
|
||||||
|
*
|
||||||
|
* The library has no locking and shares static buffers between its calls,
|
||||||
|
* so every rb* call happens here. Its callbacks fire inside rbPoll(), on
|
||||||
|
* this thread, and only record; the loop does the work after.
|
||||||
|
*
|
||||||
|
* loop sleep until a request or a byte from the modem arrives; then
|
||||||
|
* rbPoll() once for every line waiting (it reads one per call),
|
||||||
|
* hand any whole MT to the application and acknowledge it, and
|
||||||
|
* serve the request. The library's async path does nothing on a
|
||||||
|
* timer, so there is no polling and an idle modem costs no
|
||||||
|
* wakes.
|
||||||
|
* requests one slot. A caller takes it, fills it, kicks the worker and
|
||||||
|
* waits as long as it said. If it stops waiting, the request is
|
||||||
|
* abandoned: the worker finishes it, frees the slot itself, and
|
||||||
|
* a send's outcome still comes through mo_done.
|
||||||
|
*
|
||||||
|
* Two rules the library forces:
|
||||||
|
* - its async API with callbacks only: the blocking send and receive
|
||||||
|
* paths do nothing once callbacks are registered;
|
||||||
|
* - no modem queries while an MO is moving: receiveJspr() discards lines
|
||||||
|
* that do not match what it is waiting for, so a query would swallow
|
||||||
|
* the modem's request for the next segment and stall the send.
|
||||||
|
*/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/spinlock.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* rockblock_9704.h declares the library's private helpers static, so every
|
||||||
|
* file that includes it gets "declared static but never defined" warnings.
|
||||||
|
* GCC raises them at the end of the file, so the ignore has to stay on for
|
||||||
|
* the whole of it. */
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||||
|
#include "rockblock_9704.h"
|
||||||
|
#include "imt_queue.h"
|
||||||
|
|
||||||
|
#include <rb9704/rb9704.h>
|
||||||
|
|
||||||
|
#include "rb9704_internal.h"
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(rb9704, CONFIG_RB9704_LOG_LEVEL);
|
||||||
|
|
||||||
|
enum op { OP_BEGIN, OP_END, OP_SEND, OP_INFO, OP_SIGNAL, OP_POWER };
|
||||||
|
enum req_state { REQ_PENDING, REQ_DONE, REQ_ABANDONED };
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
enum op op;
|
||||||
|
int result;
|
||||||
|
uint16_t topic;
|
||||||
|
size_t len;
|
||||||
|
bool on;
|
||||||
|
struct rb9704_info info;
|
||||||
|
enum req_state state;
|
||||||
|
} req;
|
||||||
|
|
||||||
|
static uint8_t mo_buf[CONFIG_RB9704_MESSAGE_MAX];
|
||||||
|
|
||||||
|
#define EV_REQ BIT(0)
|
||||||
|
#define EV_RX BIT(1)
|
||||||
|
|
||||||
|
static K_SEM_DEFINE(rb9704_slot, 1, 1);
|
||||||
|
static K_EVENT_DEFINE(rb9704_ev);
|
||||||
|
static K_SEM_DEFINE(rb9704_done, 0, 1);
|
||||||
|
static struct k_spinlock req_lock;
|
||||||
|
|
||||||
|
static struct rb9704_status st;
|
||||||
|
static K_MUTEX_DEFINE(rb9704_st_lock);
|
||||||
|
|
||||||
|
static const struct rb9704_callbacks *app;
|
||||||
|
static bool mt_waiting;
|
||||||
|
|
||||||
|
#if !IS_ENABLED(CONFIG_RB9704_POWER)
|
||||||
|
int rb9704_power_init(void)
|
||||||
|
{
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
int rb9704_power_set(bool on)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(on);
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
bool rb9704_power_on(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool rb9704_power_present(void)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* --- library callbacks: inside rbPoll(), on this thread ------------------ */
|
||||||
|
|
||||||
|
static void cb_mo_started(const uint16_t id)
|
||||||
|
{
|
||||||
|
LOG_DBG("MO accepted by the modem: id %u", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cb_mo_complete(const uint16_t id, const rbMsgStatus_t status)
|
||||||
|
{
|
||||||
|
uint16_t topic;
|
||||||
|
int result = status == RB_MSG_STATUS_OK ? 0 : -EIO;
|
||||||
|
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.mo_in_flight = false;
|
||||||
|
topic = st.mo_topic;
|
||||||
|
if (result == 0) {
|
||||||
|
st.mo_sent++;
|
||||||
|
} else {
|
||||||
|
st.mo_failed++;
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
LOG_INF("MO %s: topic %u id %u", result == 0 ? "sent" : "failed", topic, id);
|
||||||
|
if (app != NULL && app->mo_done != NULL) {
|
||||||
|
app->mo_done(topic, id, result, app->user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cb_mt_complete(const uint16_t id, const rbMsgStatus_t status)
|
||||||
|
{
|
||||||
|
if (status == RB_MSG_STATUS_OK) {
|
||||||
|
mt_waiting = true; /* delivered from the loop, outside rbPoll */
|
||||||
|
} else {
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.mt_failed++;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
LOG_WRN("MT failed: id %u", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cb_constellation(const jsprConstellationState_t *cs)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.signal_known = true;
|
||||||
|
st.visibility_known = true;
|
||||||
|
st.constellation_visible = cs->constellationVisible;
|
||||||
|
st.signal_bars = cs->signalBars;
|
||||||
|
st.signal_level = cs->signalLevel;
|
||||||
|
st.signal_ms = k_uptime_get();
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
LOG_DBG("signal: %u bars, visible %d", cs->signalBars, cs->constellationVisible);
|
||||||
|
if (app != NULL && app->signal != NULL) {
|
||||||
|
app->signal(cs->signalBars, cs->constellationVisible, app->user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cb_provisioning(const jsprMessageProvisioning_t *mp)
|
||||||
|
{
|
||||||
|
uint8_t n;
|
||||||
|
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.provisioning_known = mp->provisioningSet;
|
||||||
|
n = MIN(mp->topicCount, RB9704_MAX_TOPICS);
|
||||||
|
st.topic_count = n;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
st.topics[i].id = mp->provisioning[i].topicId;
|
||||||
|
strncpy(st.topics[i].name, mp->provisioning[i].topicName,
|
||||||
|
sizeof(st.topics[i].name) - 1);
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
LOG_INF("provisioning: %u topic(s)", mp->topicCount);
|
||||||
|
if (app != NULL && app->provisioning != NULL) {
|
||||||
|
app->provisioning(st.topics, n, app->user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const rbCallbacks_t callbacks = {
|
||||||
|
.messageProvisioning = cb_provisioning,
|
||||||
|
.moMessageComplete = cb_mo_complete,
|
||||||
|
.mtMessageComplete = cb_mt_complete,
|
||||||
|
.constellationState = cb_constellation,
|
||||||
|
.moMessageStarted = cb_mo_started,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* --- the requests, on this thread --------------------------------------- */
|
||||||
|
|
||||||
|
static int op_begin(void)
|
||||||
|
{
|
||||||
|
if (st.begun) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
if (rb9704_power_present() && !rb9704_power_on()) {
|
||||||
|
int rc = rb9704_power_set(true);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.powered = true;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
}
|
||||||
|
rbRegisterCallbacks(&callbacks);
|
||||||
|
if (!rbBegin("zephyr")) { /* the port name is unused: the UART is in devicetree */
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.begun = true;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int op_end(void)
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
if (!st.begun) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
ok = rbEnd();
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.begun = false;
|
||||||
|
st.mo_in_flight = false;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
return ok ? 0 : -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool topic_provisioned(uint16_t topic)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < st.topic_count; i++) {
|
||||||
|
if (st.topics[i].id == topic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The library asks the modem for its provisioning at the first send and
|
||||||
|
* keeps the answer for good, a miss included, so a modem provisioned after
|
||||||
|
* that would refuse every topic until a reboot. When the topic is not in
|
||||||
|
* what it kept, clear the cache and the library asks again: one free JSPR
|
||||||
|
* round trip. messageProvisioningInfo is the library's own global. */
|
||||||
|
extern jsprMessageProvisioning_t messageProvisioningInfo;
|
||||||
|
|
||||||
|
static int op_send(void)
|
||||||
|
{
|
||||||
|
bool known;
|
||||||
|
|
||||||
|
if (!st.begun) {
|
||||||
|
return -ENOTCONN;
|
||||||
|
}
|
||||||
|
if (st.mo_in_flight) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
known = false;
|
||||||
|
for (int i = 0; messageProvisioningInfo.provisioningSet &&
|
||||||
|
i < MIN(messageProvisioningInfo.topicCount, JSPR_MAX_TOPICS); i++) {
|
||||||
|
known |= messageProvisioningInfo.provisioning[i].topicId == req.topic;
|
||||||
|
}
|
||||||
|
if (!known) {
|
||||||
|
messageProvisioningInfo.provisioningSet = false;
|
||||||
|
}
|
||||||
|
if (!rbSendMessageAsync(req.topic, (const char *)mo_buf, req.len)) {
|
||||||
|
if (st.provisioning_known && !topic_provisioned(req.topic)) {
|
||||||
|
return -EACCES;
|
||||||
|
}
|
||||||
|
/* The modem refused a topic the cache said was fine: the cache
|
||||||
|
* may be stale, so the next send asks again. */
|
||||||
|
messageProvisioningInfo.provisioningSet = false;
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.mo_in_flight = true;
|
||||||
|
st.mo_topic = req.topic;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_str(char *dst, size_t cap, const char *src)
|
||||||
|
{
|
||||||
|
if (src != NULL) {
|
||||||
|
strncpy(dst, src, cap - 1);
|
||||||
|
dst[cap - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
dst[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int op_info(void)
|
||||||
|
{
|
||||||
|
struct rb9704_info *i = &req.info;
|
||||||
|
char *imei;
|
||||||
|
|
||||||
|
if (!st.begun) {
|
||||||
|
return -ENOTCONN;
|
||||||
|
}
|
||||||
|
if (st.mo_in_flight) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
/* Each is one JSPR round trip: free, milliseconds at 230400. */
|
||||||
|
imei = rbGetImei();
|
||||||
|
copy_str(i->imei, sizeof(i->imei), imei);
|
||||||
|
copy_str(i->serial, sizeof(i->serial), rbGetSerialNumber());
|
||||||
|
copy_str(i->hw_version, sizeof(i->hw_version), rbGetHwVersion());
|
||||||
|
copy_str(i->fw_version, sizeof(i->fw_version), rbGetFirmwareVersion());
|
||||||
|
copy_str(i->iccid, sizeof(i->iccid), rbGetIccid());
|
||||||
|
i->board_temp_c = rbGetBoardTemp();
|
||||||
|
i->card_present = rbGetCardPresent();
|
||||||
|
i->sim_connected = rbGetSimConnected();
|
||||||
|
return imei != NULL ? 0 : -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int op_signal(void)
|
||||||
|
{
|
||||||
|
int8_t bars;
|
||||||
|
|
||||||
|
if (!st.begun) {
|
||||||
|
return -ENOTCONN;
|
||||||
|
}
|
||||||
|
if (st.mo_in_flight) {
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
bars = rbGetSignal();
|
||||||
|
if (bars < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.signal_known = true;
|
||||||
|
st.signal_bars = (uint8_t)bars;
|
||||||
|
st.signal_ms = k_uptime_get();
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
return bars;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int op_power(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (!req.on && st.begun) {
|
||||||
|
(void)op_end();
|
||||||
|
}
|
||||||
|
rc = rb9704_power_set(req.on);
|
||||||
|
if (rc == 0) {
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.powered = req.on;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dispatch(void)
|
||||||
|
{
|
||||||
|
switch (req.op) {
|
||||||
|
case OP_BEGIN: return op_begin();
|
||||||
|
case OP_END: return op_end();
|
||||||
|
case OP_SEND: return op_send();
|
||||||
|
case OP_INFO: return op_info();
|
||||||
|
case OP_SIGNAL: return op_signal();
|
||||||
|
case OP_POWER: return op_power();
|
||||||
|
}
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After dispatch: hand the result to a caller still waiting, or free the
|
||||||
|
* slot for one that stopped. */
|
||||||
|
static void complete(void)
|
||||||
|
{
|
||||||
|
k_spinlock_key_t key = k_spin_lock(&req_lock);
|
||||||
|
bool abandoned = req.state == REQ_ABANDONED;
|
||||||
|
|
||||||
|
if (!abandoned) {
|
||||||
|
req.state = REQ_DONE;
|
||||||
|
k_sem_give(&rb9704_done);
|
||||||
|
}
|
||||||
|
k_spin_unlock(&req_lock, key);
|
||||||
|
if (abandoned) {
|
||||||
|
if (req.op == OP_SEND && req.result != 0 && app != NULL && app->mo_done != NULL) {
|
||||||
|
app->mo_done(req.topic, 0, req.result, app->user);
|
||||||
|
}
|
||||||
|
k_sem_give(&rb9704_slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caller side. The slot is held from here until the caller has read the
|
||||||
|
* result, or until the worker finishes a request the caller gave up on. */
|
||||||
|
static int submit(k_timeout_t timeout, bool async_ok)
|
||||||
|
{
|
||||||
|
k_spinlock_key_t key;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
req.state = REQ_PENDING;
|
||||||
|
k_sem_reset(&rb9704_done);
|
||||||
|
k_event_post(&rb9704_ev, EV_REQ);
|
||||||
|
rc = k_sem_take(&rb9704_done, timeout);
|
||||||
|
key = k_spin_lock(&req_lock);
|
||||||
|
if (rc != 0 && req.state != REQ_DONE) {
|
||||||
|
req.state = REQ_ABANDONED;
|
||||||
|
k_spin_unlock(&req_lock, key);
|
||||||
|
return async_ok ? -EINPROGRESS : -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
k_spin_unlock(&req_lock, key);
|
||||||
|
return req.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int take_slot(k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
if (k_current_get() == rb9704_worker_tid_get()) {
|
||||||
|
return -EDEADLK; /* from a callback: the worker would wait on itself */
|
||||||
|
}
|
||||||
|
return k_sem_take(&rb9704_slot, timeout) == 0 ? 0 : -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int simple(enum op op, k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
int rc = take_slot(timeout);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
req.op = op;
|
||||||
|
rc = submit(timeout, false);
|
||||||
|
if (rc != -ETIMEDOUT) {
|
||||||
|
k_sem_give(&rb9704_slot);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- the API -------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void rb9704_set_callbacks(const struct rb9704_callbacks *cb)
|
||||||
|
{
|
||||||
|
app = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_power(bool on)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (!rb9704_power_present()) {
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
rc = take_slot(K_FOREVER);
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
req.op = OP_POWER;
|
||||||
|
req.on = on;
|
||||||
|
rc = submit(K_FOREVER, false);
|
||||||
|
k_sem_give(&rb9704_slot);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_begin(k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
return simple(OP_BEGIN, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_end(k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
return simple(OP_END, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_signal(k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
return simple(OP_SIGNAL, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_info(struct rb9704_info *out, k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
int rc = take_slot(timeout);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
req.op = OP_INFO;
|
||||||
|
rc = submit(timeout, false);
|
||||||
|
if (rc == -ETIMEDOUT) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
if (rc == 0) {
|
||||||
|
*out = req.info;
|
||||||
|
}
|
||||||
|
k_sem_give(&rb9704_slot);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rb9704_send(uint16_t topic, const void *data, size_t len, k_timeout_t timeout)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (topic < RB9704_TOPIC_MIN || data == NULL || len == 0 || len > sizeof(mo_buf)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
rc = take_slot(timeout);
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
req.op = OP_SEND;
|
||||||
|
req.topic = topic;
|
||||||
|
req.len = len;
|
||||||
|
memcpy(mo_buf, data, len);
|
||||||
|
rc = submit(timeout, true);
|
||||||
|
if (rc != -EINPROGRESS) {
|
||||||
|
k_sem_give(&rb9704_slot);
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rb9704_status(struct rb9704_status *out)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
*out = st;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
out->rx_dropped = rb9704_serial_dropped();
|
||||||
|
}
|
||||||
|
|
||||||
|
void rb9704_spy(bool on)
|
||||||
|
{
|
||||||
|
rb9704_serial_spy(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- the loop -------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Every whole MT in the library's queue, to the application, then
|
||||||
|
* acknowledged so the modem can send the next. */
|
||||||
|
static void deliver_mts(void)
|
||||||
|
{
|
||||||
|
char *payload = NULL;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
while ((len = rbReceiveMessageAsync(&payload)) > 0 && payload != NULL) {
|
||||||
|
imt_t *head = imtQueueMtGetFirst();
|
||||||
|
struct rb9704_mt mt = {
|
||||||
|
.topic = head != NULL ? head->topic : 0,
|
||||||
|
.id = head != NULL ? head->id : 0,
|
||||||
|
.data = (const uint8_t *)payload,
|
||||||
|
.len = len,
|
||||||
|
};
|
||||||
|
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
st.mt_received++;
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
LOG_INF("MT received: topic %u id %u, %u bytes", mt.topic, mt.id,
|
||||||
|
(unsigned int)len);
|
||||||
|
if (app != NULL && app->mt != NULL) {
|
||||||
|
app->mt(&mt, app->user);
|
||||||
|
}
|
||||||
|
rbAcknowledgeReceiveHeadAsync();
|
||||||
|
payload = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void worker(void *a, void *b, void *c)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(a); ARG_UNUSED(b); ARG_UNUSED(c);
|
||||||
|
|
||||||
|
k_mutex_lock(&rb9704_st_lock, K_FOREVER);
|
||||||
|
/* No pins: the board is powered whenever this runs. */
|
||||||
|
st.powered = !rb9704_power_present();
|
||||||
|
if (rb9704_power_present() && rb9704_power_init() != 0) {
|
||||||
|
LOG_ERR("power pins not ready");
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&rb9704_st_lock);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
uint32_t ev = k_event_wait(&rb9704_ev, EV_REQ | EV_RX, false, K_FOREVER);
|
||||||
|
|
||||||
|
/* Cleared before the work, so a byte or request that arrives
|
||||||
|
* during it wakes the next wait. */
|
||||||
|
k_event_clear(&rb9704_ev, ev);
|
||||||
|
if (st.begun) {
|
||||||
|
while (rb9704_serial_available() > 0) {
|
||||||
|
rbPoll(); /* one JSPR line per call */
|
||||||
|
}
|
||||||
|
if (mt_waiting) {
|
||||||
|
mt_waiting = false;
|
||||||
|
deliver_mts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev & EV_REQ) {
|
||||||
|
req.result = dispatch();
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
K_THREAD_DEFINE(rb9704_worker_tid, CONFIG_RB9704_WORKER_STACK_SIZE, worker, NULL, NULL, NULL,
|
||||||
|
CONFIG_RB9704_WORKER_PRIORITY, 0, 0);
|
||||||
|
|
||||||
|
k_tid_t rb9704_worker_tid_get(void)
|
||||||
|
{
|
||||||
|
return rb9704_worker_tid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From the UART ISR: bytes have arrived for the library. */
|
||||||
|
void rb9704_worker_rx(void)
|
||||||
|
{
|
||||||
|
k_event_post(&rb9704_ev, EV_RX);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# rockblock9704-zephyr: a west manifest for working on this module on its
|
||||||
|
# own, and the pins an application imports.
|
||||||
|
#
|
||||||
|
# In an application's west.yml:
|
||||||
|
#
|
||||||
|
# - name: rockblock9704-zephyr
|
||||||
|
# url: https://github.com/SH3D/rockblock9704-zephyr (or gitea.sh3d.com.au/Sh3d)
|
||||||
|
# revision: <tag>
|
||||||
|
# path: modules/lib/rockblock9704-zephyr
|
||||||
|
# import:
|
||||||
|
# name-allowlist:
|
||||||
|
# - rockblock9704
|
||||||
|
#
|
||||||
|
# The import brings Ground Control's library with it, at the path this
|
||||||
|
# module's CMakeLists.txt looks in, and nothing else. The Zephyr entry below
|
||||||
|
# is only for working on the module alone (`west init -l` in a checkout of
|
||||||
|
# this repository).
|
||||||
|
manifest:
|
||||||
|
version: "0.13"
|
||||||
|
|
||||||
|
self:
|
||||||
|
path: rockblock9704-zephyr
|
||||||
|
|
||||||
|
remotes:
|
||||||
|
- name: zephyrproject
|
||||||
|
url-base: https://github.com/zephyrproject-rtos
|
||||||
|
|
||||||
|
projects:
|
||||||
|
# Ground Control's library, pristine, by commit (tag v1.0.1). MIT.
|
||||||
|
- name: rockblock9704
|
||||||
|
url: https://github.com/rock7/RockBLOCK-9704
|
||||||
|
revision: ab37e8ac7711e1ba8061835a0685c25b6069e767
|
||||||
|
path: modules/lib/rockblock9704
|
||||||
|
|
||||||
|
# Zephyr v4.4.0, for working on this module alone. Tested with it.
|
||||||
|
- name: zephyr
|
||||||
|
remote: zephyrproject
|
||||||
|
revision: 684c9e8f32e4373a21098559f748f06915f950c9
|
||||||
|
import:
|
||||||
|
name-allowlist:
|
||||||
|
- cmsis_6
|
||||||
|
- hal_stm32
|
||||||
|
- hal_espressif
|
||||||
|
- picolibc
|
||||||
|
- mbedtls
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
name: rockblock9704-zephyr
|
||||||
|
build:
|
||||||
|
cmake: .
|
||||||
|
kconfig: Kconfig
|
||||||
|
settings:
|
||||||
|
dts_root: .
|
||||||
Reference in new issue
Block a user