ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_modem_at.c
Go to the documentation of this file.
1
22
23#include "ra8_modem_at.h"
24
25#include <stddef.h>
26#include <stdint.h>
27
28#include "ra8_attributes.h"
29#include "ra8_check.h"
30#include "ra8_err.h"
31#include "ra8_log.h"
33
38#define RA8_MODEM_AT_TAG "MODEM_AT"
39
47
65
76
96
104
105/* ------------------------------------------------------------------------- */
106/* Internal helpers */
107/* ------------------------------------------------------------------------- */
108
124uint16_t priv_modem_str_len(const char* s)
125{
126 uint16_t i = 0U;
127 while ((i < UINT16_MAX) && (s[i] != '\0')) {
128 ++i;
129 }
130 return i;
131}
132
149uint8_t priv_modem_starts_with(const char* hay, const char* needle)
150{
151 uint16_t i = 0U;
152 while (needle[i] != '\0') {
153 if (hay[i] != needle[i]) {
154 return 0U;
155 }
156 ++i;
157 }
158 return 1U;
159}
160
177uint8_t priv_modem_str_eq(const char* a, const char* b)
178{
179 uint16_t i = 0U;
180 while ((a[i] != '\0') && (b[i] != '\0')) {
181 if (a[i] != b[i]) {
182 return 0U;
183 }
184 ++i;
185 }
186 return (uint8_t)((a[i] == '\0') && (b[i] == '\0'));
187}
188
206static void internal_append_ch(char* out, size_t out_len, size_t* used, char ch)
207{
208 if ((*used + 1U) < out_len) {
209 out[*used] = ch;
210 ++(*used);
211 out[*used] = '\0';
212 }
213}
214
238uint8_t priv_modem_reset_line_should_clear(const void* line_buf, uint16_t line_buf_len)
239{
240 if ((line_buf != nullptr) && (line_buf_len > 0U)) {
241 return 1U;
242 }
243 return 0U;
244}
245
271uint8_t priv_modem_payload_prefix_matches(const char* line, const char* expected_response)
272{
273 if ((expected_response != nullptr) && (expected_response[0] != '\0') &&
274 (priv_modem_starts_with(line, expected_response) != 0U)) {
275 return 1U;
276 }
277 return 0U;
278}
279
304uint8_t priv_modem_capture_should_clear(const void* capture, size_t capture_len)
305{
306 if ((capture != nullptr) && (capture_len > 0U)) {
307 return 1U;
308 }
309 return 0U;
310}
311
328static void internal_reset_line(void)
329{
330 s_mod.line_len = 0U;
331 if (priv_modem_reset_line_should_clear(s_mod.cfg.line_buf, s_mod.cfg.line_buf_len) != 0U) {
332 s_mod.cfg.line_buf[0] = (uint8_t)'\0';
333 }
334}
335
353static uint8_t internal_classify_final(const char* line, uint8_t* is_error)
354{
355 *is_error = 0U;
356 if (priv_modem_str_eq(line, "OK") != 0U) {
357 return 1U;
358 }
359 if (priv_modem_str_eq(line, "ERROR") != 0U) {
360 *is_error = 1U;
361 return 1U;
362 }
363 if (priv_modem_starts_with(line, "+CME ERROR") != 0U) {
364 *is_error = 1U;
365 return 1U;
366 }
367 if (priv_modem_starts_with(line, "+CMS ERROR") != 0U) {
368 *is_error = 1U;
369 return 1U;
370 }
371 if (priv_modem_str_eq(line, "BUSY") != 0U) {
372 *is_error = 1U;
373 return 1U;
374 }
375 if (priv_modem_str_eq(line, "NO CARRIER") != 0U) {
376 *is_error = 1U;
377 return 1U;
378 }
379 return 0U;
380}
381
398static uint8_t internal_dispatch_urc(const char* line)
399{
400 for (uint8_t i = 0U; i < (uint8_t)k_ra8_modem_at_max_unsolicited; ++i) {
401 if (s_mod.urcs[i].used == 0U) {
402 continue;
403 }
404 if (priv_modem_starts_with(line, (const char*)s_mod.urcs[i].prefix) != 0U) {
405 s_mod.urcs[i].fn(line, s_mod.urcs[i].ctx);
406 return 1U;
407 }
408 }
409 return 0U;
410}
411
412/* ra8_modem_line_kind_t is defined in ra8_modem_at_internal.h so tests
413 * can read the return value of priv_modem_classify(). */
414
438priv_modem_classify(const char* line, const char* cmd_echo, const char* expected_response)
439{
440 if (line[0] == '\0') {
442 }
443 if ((cmd_echo != nullptr) && (priv_modem_str_eq(line, cmd_echo) != 0U)) {
445 }
446 uint8_t is_err = 0U;
447 if (internal_classify_final(line, &is_err) != 0U) {
449 }
450 /* If the caller didn't ask for a specific prefix, allow URC dispatch. */
451 if ((expected_response == nullptr) || (expected_response[0] == '\0') ||
452 (priv_modem_starts_with(line, expected_response) == 0U)) {
453 if (internal_dispatch_urc(line) != 0U) {
455 }
456 }
458}
459
476static void internal_accumulate(uint8_t byte, const char** line_out)
477{
478 *line_out = nullptr;
479 if ((byte == (uint8_t)'\r') || (byte == (uint8_t)'\n')) {
480 if (s_mod.line_len > 0U) {
481 s_mod.cfg.line_buf[s_mod.line_len] = (uint8_t)'\0';
482 *line_out = (const char*)s_mod.cfg.line_buf;
483 s_mod.line_len = 0U;
484 }
485 return;
486 }
487 if ((s_mod.line_len + 1U) >= s_mod.cfg.line_buf_len) {
488 /* Overflow: terminate, emit what we have, drop the new byte. */
489 s_mod.cfg.line_buf[s_mod.line_len] = (uint8_t)'\0';
490 *line_out = (const char*)s_mod.cfg.line_buf;
491 s_mod.line_len = 0U;
492 return;
493 }
494 s_mod.cfg.line_buf[s_mod.line_len] = byte;
495 ++s_mod.line_len;
496}
497
514static ra8_err_t internal_tx_command(const char* s)
515{
516 uint16_t i = 0U;
517 while (s[i] != '\0') {
518 const ra8_err_t err = s_mod.cfg.io.tx_byte(s_mod.cfg.io.ctx, (uint8_t)s[i]);
519 if (err != k_ra8_ok) {
520 return err;
521 }
522 ++i;
523 }
524 return s_mod.cfg.io.tx_byte(s_mod.cfg.io.ctx, (uint8_t)'\r');
525}
526
542static uint16_t internal_effective_timeout(uint16_t timeout_ms)
543{
544 if (timeout_ms != 0U) {
545 return timeout_ms;
546 }
547 if (s_mod.cfg.default_timeout_ms != 0U) {
548 return s_mod.cfg.default_timeout_ms;
549 }
550 return (uint16_t)k_ra8_modem_at_default_timeout_ms;
551}
552
562
578void priv_modem_capture_line(const char* line, char* capture, size_t capture_len, size_t* used)
579{
580 if ((capture == nullptr) || (capture_len == 0U)) {
581 return;
582 }
583 if (*used > 0U) {
584 internal_append_ch(capture, capture_len, used, '\n');
585 }
586 uint16_t k = 0U;
587 while (line[k] != '\0') {
588 internal_append_ch(capture, capture_len, used, line[k]);
589 ++k;
590 }
591}
592
617 const char* expected_response,
618 uint8_t* seen_exp,
619 char* capture,
620 size_t capture_len,
621 size_t* used)
622{
623 switch (kind) {
632 if (*seen_exp == 0U) {
633 ra8_log_error(RA8_MODEM_AT_TAG, "OK without expected prefix");
635 }
641 default:
642 if (priv_modem_payload_prefix_matches(line, expected_response) != 0U) {
643 *seen_exp = 1U;
644 }
645 priv_modem_capture_line(line, capture, capture_len, used);
648 }
650 }
651}
652
656typedef struct {
657 const char* cmd;
658 const char* expected_response;
659 char* capture;
660 size_t capture_len;
661 size_t* used;
662 uint8_t* seen_exp;
664
682{
683 uint8_t byte = 0U;
684 const ra8_err_t rx_err = s_mod.cfg.io.rx_byte(s_mod.cfg.io.ctx, &byte);
685 if (rx_err != k_ra8_ok) {
687 }
688 const char* line = nullptr;
689 internal_accumulate(byte, &line);
690 if (line == nullptr) {
692 }
694 return internal_handle_line(line,
695 kind,
697 wc->seen_exp,
698 wc->capture,
699 wc->capture_len,
700 wc->used);
701}
702
723static ra8_err_t internal_wait_response(const char* cmd,
724 const char* expected_response,
725 uint16_t timeout_ms,
726 char* capture,
727 size_t capture_len)
728{
729 const uint32_t start = s_mod.cfg.io.now_ms(s_mod.cfg.io.ctx);
730 size_t used = 0U;
731 uint8_t seen_exp = (uint8_t)((expected_response == nullptr) || (expected_response[0] == '\0'));
732
733 if (priv_modem_capture_should_clear(capture, capture_len) != 0U) {
734 capture[0] = '\0';
735 }
736
738
739 const ra8_modem_wait_ctx_t wc = {
740 .cmd = cmd,
741 .expected_response = expected_response,
742 .capture = capture,
743 .capture_len = capture_len,
744 .used = &used,
745 .seen_exp = &seen_exp,
746 };
747
748 while (1) {
749 const ra8_modem_line_action_t action = internal_pump_one(&wc);
750 if (action == k_ra8_modem_line_action_done_ok) {
752 return k_ra8_ok;
753 }
754 if (action == k_ra8_modem_line_action_done_err) {
756 return k_ra8_err_hw_error;
757 }
758 const uint32_t elapsed = s_mod.cfg.io.now_ms(s_mod.cfg.io.ctx) - start;
759 if (elapsed >= (uint32_t)timeout_ms) {
763 }
764 }
765}
766
767/* ------------------------------------------------------------------------- */
768/* Public API */
769/* ------------------------------------------------------------------------- */
770
784{
785 for (uint8_t i = 0U; i < (uint8_t)k_ra8_modem_at_max_unsolicited; ++i) {
786 s_mod.urcs[i].used = 0U;
787 s_mod.urcs[i].fn = nullptr;
788 s_mod.urcs[i].ctx = nullptr;
789 }
790}
791
808{
810 RA8_CHECK_NULL_PTR(cfg->line_buf, RA8_MODEM_AT_TAG, "cfg->line_buf");
811 RA8_CHECK_NULL_PTR((void*)cfg->io.tx_byte, RA8_MODEM_AT_TAG, "cfg->io.tx_byte");
812 RA8_CHECK_NULL_PTR((void*)cfg->io.rx_byte, RA8_MODEM_AT_TAG, "cfg->io.rx_byte");
813 RA8_CHECK_NULL_PTR((void*)cfg->io.now_ms, RA8_MODEM_AT_TAG, "cfg->io.now_ms");
814 if (cfg->line_buf_len < (uint16_t)k_ra8_modem_at_min_line_buf_bytes) {
815 ra8_log_error(RA8_MODEM_AT_TAG, "line_buf too small");
817 }
818 return k_ra8_ok;
819}
820
822{
823 const ra8_err_t verr = internal_validate_init_cfg(cfg);
824 if (verr != k_ra8_ok) {
825 return verr;
826 }
827 s_mod.cfg = *cfg;
828 s_mod.line_len = 0U;
830 s_mod.initialized = 1U;
833 return k_ra8_ok;
834}
835
836ra8_err_t ra8_modem_at_send_cmd(const char* cmd, const char* expected_response, uint16_t timeout_ms)
837{
838 if (s_mod.initialized == 0U) {
840 }
842
844 const ra8_err_t tx_err = internal_tx_command(cmd);
845 if (tx_err != k_ra8_ok) {
846 return tx_err;
847 }
848 return internal_wait_response(cmd,
849 expected_response,
850 internal_effective_timeout(timeout_ms),
851 nullptr,
852 0U);
853}
854
856ra8_modem_at_send_cmd_capture(const char* cmd, char* out_buf, size_t buf_len, uint16_t timeout_ms)
857{
858 if (s_mod.initialized == 0U) {
860 }
862 RA8_CHECK_NULL_PTR(out_buf, RA8_MODEM_AT_TAG, "out_buf");
863 if (buf_len == 0U) {
865 }
866
867 out_buf[0] = '\0';
869 const ra8_err_t tx_err = internal_tx_command(cmd);
870 if (tx_err != k_ra8_ok) {
871 return tx_err;
872 }
873 return internal_wait_response(cmd,
874 nullptr,
875 internal_effective_timeout(timeout_ms),
876 out_buf,
877 buf_len);
878}
879
897static uint8_t internal_urc_replace(const char* prefix, ra8_modem_at_urc_fn_t fn, void* ctx)
898{
899 for (uint8_t i = 0U; i < (uint8_t)k_ra8_modem_at_max_unsolicited; ++i) {
900 if (s_mod.urcs[i].used == 0U) {
901 continue;
902 }
903 if (priv_modem_str_eq((const char*)s_mod.urcs[i].prefix, prefix) != 0U) {
904 s_mod.urcs[i].fn = fn;
905 s_mod.urcs[i].ctx = ctx;
906 return 1U;
907 }
908 }
909 return 0U;
910}
911
930static uint8_t
931internal_urc_insert(const char* prefix, uint16_t plen, ra8_modem_at_urc_fn_t fn, void* ctx)
932{
933 for (uint8_t i = 0U; i < (uint8_t)k_ra8_modem_at_max_unsolicited; ++i) {
934 if (s_mod.urcs[i].used != 0U) {
935 continue;
936 }
937 uint16_t k = 0U;
938 while (k < plen) {
939 s_mod.urcs[i].prefix[k] = (uint8_t)prefix[k];
940 ++k;
941 }
942 s_mod.urcs[i].prefix[plen] = (uint8_t)'\0';
943 s_mod.urcs[i].fn = fn;
944 s_mod.urcs[i].ctx = ctx;
945 s_mod.urcs[i].used = 1U;
946 return 1U;
947 }
948 return 0U;
949}
950
953{
954 if (s_mod.initialized == 0U) {
956 }
957 RA8_CHECK_NULL_PTR(prefix, RA8_MODEM_AT_TAG, "prefix");
958 RA8_CHECK_NULL_PTR((void*)fn, RA8_MODEM_AT_TAG, "fn");
959
960 const uint16_t plen = priv_modem_str_len(prefix);
961 if ((plen == 0U) || (plen >= (uint16_t)k_ra8_modem_at_max_prefix_len)) {
963 }
964
965 if (internal_urc_replace(prefix, fn, ctx) != 0U) {
966 return k_ra8_ok;
967 }
968 if (internal_urc_insert(prefix, plen, fn, ctx) != 0U) {
969 return k_ra8_ok;
970 }
971 return k_ra8_err_no_mem;
972}
973
975{
976 if (s_mod.initialized == 0U) {
978 }
979 while (1) {
980 uint8_t byte = 0U;
981 const ra8_err_t err = s_mod.cfg.io.rx_byte(s_mod.cfg.io.ctx, &byte);
982 if (err != k_ra8_ok) {
983 break;
984 }
985 const char* line = nullptr;
986 internal_accumulate(byte, &line);
987 if (line != nullptr) {
988 const ra8_modem_line_kind_t kind = priv_modem_classify(line, nullptr, nullptr);
989 (void)kind;
990 }
991 }
992 return k_ra8_ok;
993}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_hw_timeout
Hardware timed out waiting for a flag or handshake.
Definition ra8_err.h:304
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_hw_error
Generic hardware fault detected (error flag set, fault interrupt).
Definition ra8_err.h:310
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
Lightweight Logging Interface for ra8-firmware.
#define ra8_log_error(tag, message)
RA8 log error.
Definition ra8_log.h:335
ra8_modem_line_kind_t priv_modem_classify(const char *line, const char *cmd_echo, const char *expected_response)
Classify a complete (NUL-terminated) line for the FSM.
uint8_t priv_modem_capture_should_clear(const void *capture, size_t capture_len)
Production carrier of the line-664 capture-init AND-decision.
uint8_t priv_modem_starts_with(const char *hay, const char *needle)
Return 1 if hay begins with needle.
static void internal_accumulate(uint8_t byte, const char **line_out)
Push a single received byte into the line accumulator.
ra8_modem_at_state_t
Internal line-accumulator FSM states.
@ k_ra8_modem_at_state_done
Final result code matched.
@ k_ra8_modem_at_state_idle
No command in flight.
@ k_ra8_modem_at_state_await_echo
Sent command, waiting for echo line.
@ k_ra8_modem_at_state_await_resp
Echo seen (or skipped), draining.
ra8_err_t ra8_modem_at_poll(void)
Pump the RX path without sending a command.
static ra8_modem_at_module_t s_mod
Singleton module state (file scope, NASA Rule 6).
static void internal_clear_urc_table(void)
Reset the URC dispatch table to all-empty.
static ra8_modem_line_action_t internal_pump_one(const ra8_modem_wait_ctx_t *wc)
Pull one byte and process any completed line.
static uint8_t internal_urc_insert(const char *prefix, uint16_t plen, ra8_modem_at_urc_fn_t fn, void *ctx)
Allocate the first free URC slot and copy the prefix into it.
static ra8_err_t internal_validate_init_cfg(const ra8_modem_at_cfg_t *cfg)
Validate every required pointer in cfg's IO block.
static ra8_err_t internal_wait_response(const char *cmd, const char *expected_response, uint16_t timeout_ms, char *capture, size_t capture_len)
Inner wait loop shared by send_cmd and send_cmd_capture.
static void internal_reset_line(void)
Reset the line accumulator for a fresh command cycle.
uint8_t priv_modem_str_eq(const char *a, const char *b)
Compare two NUL-terminated strings for exact equality.
ra8_err_t ra8_modem_at_send_cmd_capture(const char *cmd, char *out_buf, size_t buf_len, uint16_t timeout_ms)
Send an AT command and capture the response payload.
ra8_err_t ra8_modem_at_register_unsolicited_handler(const char *prefix, ra8_modem_at_urc_fn_t fn, void *ctx)
Register a URC (unsolicited result code) handler.
static uint8_t internal_urc_replace(const char *prefix, ra8_modem_at_urc_fn_t fn, void *ctx)
Find an existing URC slot for prefix and update its callback.
static ra8_err_t internal_tx_command(const char *s)
Transmit a NUL-terminated string then "\r".
static uint16_t internal_effective_timeout(uint16_t timeout_ms)
Resolve effective timeout in ms, applying default if 0.
ra8_err_t ra8_modem_at_send_cmd(const char *cmd, const char *expected_response, uint16_t timeout_ms)
Send an AT command and wait for OK/ERROR/expected.
uint8_t priv_modem_reset_line_should_clear(const void *line_buf, uint16_t line_buf_len)
Pure helper for the internal_reset_line line-227 AND.
uint16_t priv_modem_str_len(const char *s)
NUL-terminated string length.
static uint8_t internal_dispatch_urc(const char *line)
Dispatch a URC line to any matching registered handler.
static ra8_modem_line_action_t internal_handle_line(const char *line, ra8_modem_line_kind_t kind, const char *expected_response, uint8_t *seen_exp, char *capture, size_t capture_len, size_t *used)
Drive the response FSM by one classified line.
ra8_modem_line_action_t
Outcome of handling one classified line in internal_wait_response.
@ k_ra8_modem_line_action_continue
Keep waiting.
@ k_ra8_modem_line_action_done_err
Final error observed.
@ k_ra8_modem_line_action_done_ok
Final OK observed.
uint8_t priv_modem_payload_prefix_matches(const char *line, const char *expected_response)
Production carrier of the line-573 payload-prefix AND-decision.
void priv_modem_capture_line(const char *line, char *capture, size_t capture_len, size_t *used)
Append a NUL-terminated line into capture (with newline sep).
static uint8_t internal_classify_final(const char *line, uint8_t *is_error)
Test whether line is a final OK/ERROR result code.
static void internal_append_ch(char *out, size_t out_len, size_t *used, char ch)
Append a single character to out if there is room.
#define RA8_MODEM_AT_TAG
Component log tag.
ra8_err_t ra8_modem_at_init(const ra8_modem_at_cfg_t *cfg)
Bind transport, buffer and timeout policy.
ra8_modem_at_internal_const_t
Internal numeric constants used by the AT driver.
@ k_ra8_modem_at_min_line_buf_bytes
Minimum line buffer length.
Cellular modem AT command/response driver layered on UART.
void(* ra8_modem_at_urc_fn_t)(const char *line, void *ctx)
Unsolicited result-code (URC) handler signature.
@ k_ra8_modem_at_default_timeout_ms
RA8 modem at default timeout ms.
@ k_ra8_modem_at_max_prefix_len
Maximum bytes of a URC prefix (including + and :).
@ k_ra8_modem_at_max_unsolicited
Maximum number of registered URC handlers.
Test-access surface for ra8_modem_at internal helpers (MC/DC).
ra8_modem_line_kind_t
Result of processing a single completed line.
@ k_ra8_modem_line_kind_final_err
RA8 modem line kind final error.
@ k_ra8_modem_line_kind_final_ok
RA8 modem line kind final ok.
@ k_ra8_modem_line_kind_urc
RA8 modem line kind urc.
@ k_ra8_modem_line_kind_payload
RA8 modem line kind payload.
@ k_ra8_modem_line_kind_empty
RA8 modem line kind empty.
@ k_ra8_modem_line_kind_echo
RA8 modem line kind echo.
Configuration handed to ra8_modem_at_init.
uint16_t line_buf_len
Bytes in line_buf (>= 16).
uint8_t * line_buf
Caller-owned line accumulator buffer.
ra8_modem_at_io_t io
Byte transport (must be fully populated).
uint32_t(* now_ms)(void *ctx)
Return monotonic time in milliseconds.
ra8_err_t(* rx_byte)(void *ctx, uint8_t *out_byte)
Try to receive a single byte without blocking.
ra8_err_t(* tx_byte)(void *ctx, uint8_t byte)
Transmit a single byte (blocking).
Aggregated module state.
ra8_modem_at_state_t state
FSM state.
uint16_t line_len
Bytes pending in cfg.line_buf.
ra8_modem_at_urc_slot_t urcs[k_ra8_modem_at_max_unsolicited]
URC table.
ra8_modem_at_cfg_t cfg
Cached caller configuration.
uint8_t initialized
1 after ra8_modem_at_init.
One URC table entry.
void * ctx
Opaque ctx for fn.
uint8_t used
1 if slot occupied.
ra8_modem_at_urc_fn_t fn
Handler callback.
uint8_t prefix[k_ra8_modem_at_max_prefix_len]
NUL-terminated prefix.
Bundled args + state for one iteration of the wait loop.
char * capture
Optional capture buffer.
size_t capture_len
Capacity of capture.
size_t * used
Bytes already populated in capture.
const char * expected_response
Optional expected prefix.
const char * cmd
Command we sent (for echo).
uint8_t * seen_exp
Sticky: prefix observed yet?