Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f263132
project.yml: Exclude STM32 vendor tree from unit tests
ante-ublox Sep 5, 2026
9146298
u_cx_at_client: Fix binary URCs without queue
ante-ublox Sep 5, 2026
039b4f1
test: Expand AT client coverage
ante-ublox Sep 5, 2026
13c87a6
build: Update unit tests to Ceedling 1.0.1
ante-ublox Sep 5, 2026
91f861f
u_cx_at_client: Defer URC processing during binary RX
ante-ublox Sep 5, 2026
641af68
test: Cover fragmented binary receive stages
ante-ublox Sep 5, 2026
0474ce7
u_cx_at_client: Preserve binary header per client
ante-ublox Sep 5, 2026
4f4e3ef
test: Cover interleaved fragmented binary headers
ante-ublox Sep 5, 2026
924c6bd
u_cx_at_client: Preserve binary URC line terminator
ante-ublox Sep 5, 2026
8a2a24b
test: Expand binary receive boundary coverage
ante-ublox Sep 5, 2026
d5a299c
test: Expand Zephyr port coverage
ante-ublox Sep 5, 2026
31f5564
u_cx_at_client: Synchronize close with background RX
ante-ublox Sep 5, 2026
d611428
test: Add Zephyr client integration coverage
ante-ublox Sep 5, 2026
2ae44dd
u_cx_at_urc_queue: Release lock between fragments
ante-ublox Sep 6, 2026
4d2eec5
u_cx_at_client: Reset receive state on reopen
ante-ublox Sep 6, 2026
f58f119
test: Add UART fault and lifecycle stress coverage
ante-ublox Sep 6, 2026
dd9a8ea
u_port_uart_linux: Add high-speed baud rates
ante-ublox Sep 6, 2026
09799d6
u_port_uart_linux: Honor read timeouts
ante-ublox Sep 6, 2026
69b10ce
u_port_posix: Keep RX task alive across reopen
ante-ublox Sep 6, 2026
5a231a0
test: Add native POSIX port coverage
ante-ublox Sep 6, 2026
880d167
u_port_uart_stm32f4: Disable clock after init failure
ante-ublox Sep 6, 2026
5996e76
u_port_uart_stm32f4: Check receive startup
ante-ublox Sep 6, 2026
b5713ad
u_port_uart_stm32f4: Chunk large writes
ante-ublox Sep 6, 2026
33c4c86
u_port_uart_stm32f4: Support blocking reads
ante-ublox Sep 6, 2026
9f9b790
u_port_freertos: Harden RX task lifecycle
ante-ublox Sep 6, 2026
f314210
test: Add STM32 port coverage
ante-ublox Sep 6, 2026
37288fd
u_port_no_os: Make timing wrap-safe
ante-ublox Sep 6, 2026
a6daf3b
test: Add no-OS port coverage
ante-ublox Sep 6, 2026
4d05d9e
ports: Wake RX workers from UART events
ante-ublox Sep 6, 2026
b46a7e7
test: Cover event-driven RX workers
ante-ublox Sep 6, 2026
d7f0133
u_cx_at_urc_queue: Use circular storage
ante-ublox Sep 6, 2026
7521ebd
test: Cover circular URC queue reuse
ante-ublox Sep 6, 2026
e1933e4
ci: Run port tests in pull requests
ante-ublox Sep 6, 2026
e01e535
u_cx_at_urc_queue: Bound wrapped payload capacity
ante-ublox Sep 6, 2026
53408c5
u_cx_at_client: Synchronize binary RX state check
ante-ublox Sep 6, 2026
009c9f5
test: Cover full wrapped payload capacity
ante-ublox Sep 6, 2026
a85d7f4
test: Add Windows port coverage
ante-ublox Sep 6, 2026
af2eeba
ci: Run Windows port tests
ante-ublox Sep 6, 2026
ab5e7a8
ci: Fix Windows tests and STM32 cache setup
ante-ublox Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ceedling.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: '3.2'
- name: Install Ceedling
run: gem install ceedling -v 0.31.1
run: gem install ceedling --version 1.0.1 --no-document
- name: Install PyInvoke
run: pip install invoke
- name: Run Unit Tests
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/port-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: 'Native Port Unit Tests'

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
run-tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: POSIX
task: test.posix.run
build-directory: posix
- name: no-OS
task: test.no-os.run
build-directory: no-os
- name: STM32 host
task: test.stm32.unit
build-directory: stm32-unit

name: ${{ matrix.name }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Install test tools
run: pip install gcovr==8.6 invoke

- name: Run tests
run: invoke ${{ matrix.task }}

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ matrix.build-directory }}-coverage
path: build/${{ matrix.build-directory }}/coverage.html
if-no-files-found: ignore

windows:
name: Windows
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@v1

- name: Install Invoke
run: pip install invoke

- name: Run tests
run: invoke test.windows.run
13 changes: 12 additions & 1 deletion .github/workflows/stm32-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
env:
REGISTRY: ghcr.io
IMAGE_NAME: u-blox/u-connectclient/stm32f4-builder
CCACHE_DIR: /tmp/ccache

jobs:
build-and-test-stm32:
Expand Down Expand Up @@ -87,7 +88,6 @@ jobs:
working-directory: ./examples
env:
SKIP_DOCKER_BUILD: '1'
CCACHE_DIR: /tmp/ccache
run: |
mkdir -p /tmp/ccache
inv stm32.http.build --docker
Expand All @@ -108,6 +108,15 @@ jobs:
inv stm32.http.emulate --timeout 120 2>&1 | tee build_stm32/renode_output.txt
timeout-minutes: 5

- name: Run socket Renode emulation test
working-directory: ./examples
env:
SKIP_DOCKER_BUILD: '1'
run: |
set -o pipefail
inv stm32.socket.emulate --build --timeout 120 2>&1 | tee build_stm32/renode_socket_output.txt
timeout-minutes: 5

- name: Upload build artifacts
uses: actions/upload-artifact@v4
if: always()
Expand All @@ -119,6 +128,7 @@ jobs:
examples/bin/http_example_stm32.elf
examples/bin/http_example_stm32.map
examples/build_stm32/renode_output.txt
examples/build_stm32/renode_socket_output.txt
retention-days: 30

- name: Upload test results
Expand All @@ -128,4 +138,5 @@ jobs:
name: test-results
path: |
examples/build_stm32/renode_output.txt
examples/build_stm32/renode_socket_output.txt
retention-days: 7
2 changes: 1 addition & 1 deletion .github/workflows/zephyr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
working-directory: u-connectClient/
shell: bash
run: |
west twister -T zephyr/ --integration
west twister -T zephyr/ --integration --exclude-tag stress

- name: Process Test results
uses: dorny/test-reporter@v1
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,30 @@ See [examples/README.md](examples/README.md) for complete build instructions and
# Run Ceedling unit tests
invoke test.ceedling.run

# Run native Linux/POSIX port tests with coverage
invoke test.posix.run

# Run bare-metal/no-OS port tests with coverage
invoke test.no-os.run

# Run host-based STM32 UART and FreeRTOS port tests with coverage
invoke test.stm32.unit

# Windows port tests (native Windows or portable fake Win32 API)
invoke test.windows.run

# Run STM32 HTTP and socket integration tests in Renode
invoke test.stm32.renode

# Run Zephyr Twister tests (automatically sets up west workspace)
invoke test.zephyr.run

# Clean test artifacts
invoke test.ceedling.clean
invoke test.posix.clean
invoke test.no-os.clean
invoke test.stm32.clean
invoke test.windows.clean
invoke test.zephyr.clean
invoke test.zephyr.clean-west # Remove west workspace
```
Expand Down
6 changes: 4 additions & 2 deletions docker/stm32f4/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
wget \
xz-utils \
make \
build-essential \
cmake \
ccache \
git \
mono-complete \
ruby ruby-dev \
python3 python3-pip python-is-python3 python3-dev python3-wheel \
netcat-openbsd \
sudo \
gdb-multiarch \
&& pip3 install invoke \
&& gem install ceedling --version 1.0.1 --no-document \
&& pip3 install gcovr==8.6 invoke \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

Expand Down
1 change: 1 addition & 0 deletions inc/u_cx_at_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ typedef struct {

uCxAtBinaryState_t state;
uint8_t rxHeaderCount;
uint8_t lengthBuf[2];
uint16_t remainingDataBytes;
uint8_t *pBuffer;
uint16_t bufferSize;
Expand Down
8 changes: 7 additions & 1 deletion inc/u_cx_at_urc_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ typedef struct {
typedef struct uCxAtUrcQueue {
uint8_t *pBuffer;
size_t bufferLen;
size_t bufferPos;
size_t startPos;
size_t readPos;
size_t writePos;
size_t usedBytes;
size_t wrapPos;
size_t enqueueCapacity;
bool isWrapped;
U_CX_MUTEX_HANDLE queueMutex;
U_CX_MUTEX_HANDLE dequeueMutex;
uUrcEntry_t *pEnqueueEntry;
Expand Down
11 changes: 6 additions & 5 deletions ports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ See also: [STM32F4 port](extra/stm32f4/README.md) for embedded ARM Cortex-M4 wit

The port layer optionally implements `uPortBgRxTaskCreate()` and `uPortBgRxTaskDestroy()`:

* **POSIX port**: Creates a pthread that polls `uCxAtClientHandleRx()` every 10ms
* **Windows port**: Creates a Windows thread that polls `uCxAtClientHandleRx()` every 10ms
* **Zephyr port**: Uses work queue that is triggered by UART ISR
* **FreeRTOS port**: Creates a FreeRTOS task that polls `uCxAtClientHandleRx()` every 10ms
* **POSIX port**: Blocks a pthread on UART readiness and wakes it for shutdown
* **Windows port**: Blocks a thread on COM port receive events
* **Zephyr port**: Uses a work queue that is triggered by the UART ISR
* **FreeRTOS port**: Blocks a task on notifications from the UART ISR
* **No-OS port**: Stub implementation - user must call `uCxAtClientHandleRx()` manually in their main loop

These functions are called automatically by `uCxAtClientInit()` and `uCxAtClientDeinit()`.
These functions are called automatically by `uCxAtClientOpen()` and
`uCxAtClientClose()`.

## Using an Example Port

Expand Down
41 changes: 35 additions & 6 deletions ports/os/u_port_freertos.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include "u_cx_at_client.h"
#include "u_cx_log.h"

extern int32_t uCxAtClientHandleRxAvailable(uCxAtClient_t *pClient);

/* ----------------------------------------------------------------
* COMPILE-TIME MACROS
* -------------------------------------------------------------- */
Expand All @@ -50,7 +52,7 @@

typedef struct {
uCxAtClient_t *pClient;
TaskHandle_t rxTaskHandle;
volatile TaskHandle_t rxTaskHandle;
volatile bool terminateRxTask;
} uPortRxContext_t;

Expand All @@ -70,11 +72,14 @@ static void rxTask(void *pArg)
uPortRxContext_t *pCtx = (uPortRxContext_t *)pArg;

while (!pCtx->terminateRxTask) {
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms delay
uCxAtClientHandleRx(pCtx->pClient);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (!pCtx->terminateRxTask) {
uCxAtClientHandleRxAvailable(pCtx->pClient);
}
}

U_CX_LOG_LINE_I(U_CX_LOG_CH_DBG, pCtx->pClient->instance, "RX task terminated");
pCtx->rxTaskHandle = NULL;
vTaskDelete(NULL);
}

Expand Down Expand Up @@ -117,6 +122,12 @@ int32_t uPortMutexTryLock(SemaphoreHandle_t mutex, uint32_t timeoutMs)

void uPortBgRxTaskCreate(uCxAtClient_t *pClient)
{
TaskHandle_t taskHandle = NULL;

if (gRxContext.rxTaskHandle != NULL) {
return;
}

memset(&gRxContext, 0, sizeof(gRxContext));
gRxContext.pClient = pClient;
gRxContext.terminateRxTask = false;
Expand All @@ -127,17 +138,35 @@ void uPortBgRxTaskCreate(uCxAtClient_t *pClient)
U_PORT_FREERTOS_RX_TASK_STACK_SIZE,
&gRxContext,
U_PORT_FREERTOS_RX_TASK_PRIORITY,
&gRxContext.rxTaskHandle
&taskHandle
);
gRxContext.rxTaskHandle = taskHandle;
if (taskHandle != NULL) {
xTaskNotifyGive(taskHandle);
}
}

void uPortBgRxTaskDestroy(uCxAtClient_t *pClient)
{
(void)pClient;
gRxContext.terminateRxTask = true;
if (gRxContext.rxTaskHandle != NULL) {
xTaskNotifyGive(gRxContext.rxTaskHandle);
}

// Wait for task to terminate (it will delete itself)
while (eTaskGetState(gRxContext.rxTaskHandle) != eDeleted) {
// Wait for the task to release its handle before deleting itself.
while (gRxContext.rxTaskHandle != NULL) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}

void uPortUartRxSignalFromIsr(void)
{
BaseType_t higherPriorityTaskWoken = pdFALSE;

if (gRxContext.rxTaskHandle != NULL) {
vTaskNotifyGiveFromISR(gRxContext.rxTaskHandle,
&higherPriorityTaskWoken);
portYIELD_FROM_ISR(higherPriorityTaskWoken);
}
}
30 changes: 21 additions & 9 deletions ports/os/u_port_no_os.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,23 @@
* STATIC VARIABLES
* -------------------------------------------------------------- */

static int32_t gBootTime = 0;
static uint32_t gBootTime = 0;
static bool gInitialized = false;

/* ----------------------------------------------------------------
* STATIC FUNCTIONS
* -------------------------------------------------------------- */

static int32_t getTickTimeMs(void)
static uint32_t getTickTimeMs(void)
{
#ifdef _WIN32
return (int32_t)GetTickCount();
return (uint32_t)GetTickCount();
#else
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
int64_t timeMs = (time.tv_sec * 1000) + (time.tv_nsec / (1000 * 1000));
return (int32_t)(timeMs % (1000 * 60 * 60 * 24));
uint64_t timeMs = ((uint64_t)time.tv_sec * 1000U) +
((uint64_t)time.tv_nsec / 1000000U);
return (uint32_t)timeMs;
#endif
}

Expand All @@ -64,8 +66,9 @@ static int32_t getTickTimeMs(void)

void uPortInit(void)
{
if (gBootTime == 0) {
if (!gInitialized) {
gBootTime = getTickTimeMs();
gInitialized = true;
}
}

Expand All @@ -76,13 +79,17 @@ void uPortDeinit(void)

int32_t uPortGetTickTimeMs(void)
{
return getTickTimeMs() - gBootTime;
return (int32_t)(getTickTimeMs() - gBootTime);
}

int32_t uPortSleepMs(int32_t ms)
{
int32_t startTime = getTickTimeMs();
while (getTickTimeMs() - startTime < ms) {
if (ms <= 0) {
return 0;
}

uint32_t startTime = getTickTimeMs();
while ((getTickTimeMs() - startTime) < (uint32_t)ms) {
// Busy wait
}
return 0;
Expand All @@ -107,3 +114,8 @@ void uPortBgRxTaskDestroy(uCxAtClient_t *pClient)
(void)pClient;
// Nothing to do
}

void uPortUartRxSignalFromIsr(void)
{
// No scheduler to wake. The application calls uCxAtClientHandleRx().
}
Loading
Loading