Skip to content

Advanced features #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
24a0691
add more functionaltity
Katze719 Jul 4, 2025
f0b6881
modernize
Katze719 Jul 4, 2025
88149e6
refactor project name to lowercase and update references in CMake, RE…
Katze719 Jul 4, 2025
ecca33e
refactor serial API to improve callback handling and update versionin…
Katze719 Jul 5, 2025
93d603b
enhance serial API to provide detailed port information via callback,…
Katze719 Jul 5, 2025
f2c3d92
remove unnecessary output for available serial ports in unit test
Katze719 Jul 5, 2025
fc3e54a
implement serialGetPortsInfo in a new file, refactoring the logic for…
Katze719 Jul 5, 2025
a779b3b
update version to 1.0.0 in CMakeLists.txt and remove unused includes …
Katze719 Jul 5, 2025
dd728f4
Add comprehensive documentation for C API, building instructions, Den…
Katze719 Jul 5, 2025
1cc1838
Enhance CMake configuration for improved portability and CI workflow;…
Katze719 Jul 6, 2025
e843a65
Refactor CI workflow to build in manylinux2014 container, consolidati…
Katze719 Jul 6, 2025
7794e36
Update CI workflow to include GCC and C++ compilers in manylinux2014 …
Katze719 Jul 6, 2025
bb2e1ce
Update CI workflow to use GCC 11 and C++ compilers in manylinux2014 c…
Katze719 Jul 6, 2025
b9f6dda
Refactor serial API to integrate cpp_core library, updating includes …
Katze719 Jul 6, 2025
0f3ade2
Update C API reference documentation to include detailed function des…
Katze719 Jul 6, 2025
2420b65
Refactor serial API by removing legacy serial.cpp and implementing mo…
Katze719 Jul 10, 2025
9d5476e
Refactor serialGetPortsInfo function to rename callback parameter fro…
Katze719 Jul 10, 2025
c523f11
Add mutex protection to serial functions for thread safety
Katze719 Jul 11, 2025
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
22 changes: 6 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build & Release CPP-Unix-Bindings
name: Build & Release cpp_unix_bindings

on:
push:
Expand All @@ -12,34 +12,24 @@ jobs:
build:
name: Build shared library
runs-on: ubuntu-latest
env:
CC: clang
CXX: clang++

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install build dependencies
- name: Build in manylinux2014 container
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake clang make

- name: Configure CMake
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

- name: Compile
run: cmake --build build -j $(nproc)
docker run --rm -v "${{ github.workspace }}:/project" -w /project quay.io/pypa/manylinux2014_x86_64 /bin/bash -c "yum install -y cmake3 devtoolset-11-gcc devtoolset-11-gcc-c++ && ln -sf /usr/bin/cmake3 /usr/bin/cmake && source /opt/rh/devtoolset-11/enable && cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j\$(nproc)"

- name: Upload library artifact
uses: actions/upload-artifact@v4
with:
name: libCPP-Unix-Bindings
path: build/libCPP-Unix-Bindings.so
name: libcpp_unix_bindings
path: build/libcpp_unix_bindings.so
Comment on lines +27 to +28
Copy link
Member

@Mqxx Mqxx Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: libcpp_unix_bindings
path: build/libcpp_unix_bindings.so
name: bindings_linux
path: build/bindings_linux.so

retention-days: 14

- name: Attach library to release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v1
with:
files: build/libCPP-Unix-Bindings.so
files: build/libcpp_unix_bindings.so
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
files: build/libcpp_unix_bindings.so
files: build/bindings_linux.so

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.vscode/
build/
src/version_config.cpp
src/version_config.h
.cache/
compile_commands.json
57 changes: 34 additions & 23 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
cmake_minimum_required(VERSION 3.30)

set(VERSION_MAJOR 0)
set(VERSION_MINOR 2)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
set(VERSION_PATCH 0)

set(PROJECT_N CPP-Unix-Bindings)
set(PROJECT_N cpp_unix_bindings)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set(PROJECT_N cpp_unix_bindings)
set(PROJECT_N cpp_bindings_linux)

project(${PROJECT_N} VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})

# Ensure position-independent code for all targets (required for shared libraries)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Always use pthread
add_link_options(-pthread)
add_compile_options(-pthread)

# Generate compile_commands.json for clang-based tooling (clangd / clang-tidy)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

Expand All @@ -22,6 +29,16 @@ CPMAddPackage(
OPTIONS "GTEST_BUILD_DOCS OFF"
)

set(CPP_CORE_VERSION_MAJOR ${VERSION_MAJOR} CACHE STRING "")
set(CPP_CORE_VERSION_MINOR ${VERSION_MINOR} CACHE STRING "")
set(CPP_CORE_VERSION_PATCH ${VERSION_PATCH} CACHE STRING "")

CPMAddPackage(
NAME cpp_core
GITHUB_REPOSITORY Serial-IO/cpp-core
GIT_TAG main
)

# Ensure clang tooling can pick up compile_commands.json from project root
if(CMAKE_EXPORT_COMPILE_COMMANDS)
add_custom_target(copy-compile-commands ALL
Expand All @@ -35,12 +52,13 @@ endif()
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Collect all .cpp files except the legacy monolithic implementation
file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/**.cpp)
# Remove serial.cpp from the list (it has been superseded by modular implementations)
list(REMOVE_ITEM SRCS ${PROJECT_SOURCE_DIR}/src/serial.cpp)

set(LIB true)

configure_file(versioning/version_config.cpp.in ${PROJECT_SOURCE_DIR}/src/version_config.cpp)

# a macro that gets all of the header containing directories.
MACRO(header_directories return_list includes_base_folder extention )
FILE(GLOB_RECURSE new_list ${includes_base_folder}/*.${extention})
Expand Down Expand Up @@ -72,34 +90,27 @@ else()
add_executable(${PROJECT_N} ${SRCS})
endif(LIB)

target_link_libraries(${PROJECT_N} PRIVATE cpp_core)

set_target_properties(${PROJECT_N} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR})

target_include_directories(${PROJECT_N} PUBLIC ${PROJECT_SOURCE_DIR}/src)

add_executable(serial_integration_tests tests/serial_test.cpp)
target_link_libraries(serial_integration_tests PRIVATE ${PROJECT_N} gtest_main)

target_include_directories(serial_integration_tests PRIVATE ${PROJECT_SOURCE_DIR}/src)
add_executable(tests
tests/serial_test.cpp
tests/serial_unit_tests.cpp)

add_custom_command(TARGET serial_integration_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${PROJECT_N}>
$<TARGET_FILE_DIR:serial_integration_tests>
COMMENT "Copy shared library next to test binary")
target_link_libraries(tests PRIVATE ${PROJECT_N} gtest cpp_core)

# Unit test target covering additional API aspects
add_executable(serial_unit_tests tests/serial_unit_tests.cpp)
target_link_libraries(serial_unit_tests PRIVATE ${PROJECT_N} gtest_main)
target_include_directories(serial_unit_tests PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_include_directories(tests PRIVATE ${PROJECT_SOURCE_DIR}/src)

add_custom_command(TARGET serial_unit_tests POST_BUILD
add_custom_command(TARGET tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${PROJECT_N}>
$<TARGET_FILE_DIR:serial_unit_tests>
COMMENT "Copy shared library next to unit test binary")
$<TARGET_FILE_DIR:tests>
COMMENT "Copy shared library next to aggregated test binary")

enable_testing()
add_test(NAME SerialEchoTest COMMAND serial_integration_tests /dev/ttyUSB0)
add_test(NAME SerialUnitTests COMMAND serial_unit_tests)
add_test(NAME AllTests COMMAND tests /dev/ttyUSB0)
86 changes: 71 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CPP-Unix-Bindings
# cpp_unix_bindings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# cpp_unix_bindings
# C++ Bindings Linux


A compact C++23 library for talking to serial devices on Linux (e.g. Arduino).
The project builds a **shared library `libCPP-Unix-Bindings.so`** that can be used via
The project builds a **shared library `libcpp_unix_bindings.so`** that can be used via
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The project builds a **shared library `libcpp_unix_bindings.so`** that can be used via
The project builds a **shared library `bindings_linux.so`** that can be used via

Deno's native FFI.

---
Expand All @@ -21,7 +21,7 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

# The resulting library will be located at
# build/libCPP-Unix-Bindings.so
# build/libcpp_unix_bindings.so
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# build/libcpp_unix_bindings.so
# build/bindings_linux.so

```

> Because `CMAKE_EXPORT_COMPILE_COMMANDS` is enabled, the build also generates a
Expand All @@ -36,7 +36,7 @@ Deno ships with a first-class FFI API.

```ts
// serial_deno.ts
const lib = Deno.dlopen('./build/libCPP-Unix-Bindings.so', {
const lib = Deno.dlopen('./build/libcpp_unix_bindings.so', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const lib = Deno.dlopen('./build/libcpp_unix_bindings.so', {
const lib = Deno.dlopen('./build/bindings_linux.so', {

serialOpen: { parameters: [ 'buffer', 'i32', 'i32', 'i32', 'i32' ], result: 'pointer' },
serialClose: { parameters: [ 'pointer' ], result: 'void' },
serialRead: { parameters: [ 'pointer', 'buffer', 'i32', 'i32', 'i32' ], result: 'i32' },
Expand Down Expand Up @@ -64,16 +64,72 @@ lib.close();

## 3 C API reference

| Function | Description |
|----------|-------------|
| `intptr_t serialOpen(const char* dev, int baud, int bits, int parity, int stop)` | Open a device and return a handle. |
| `void serialClose(intptr_t handle)` | Close the port. |
| `int serialRead(...)` | Read bytes with timeout. |
| `int serialWrite(...)` | Write bytes with timeout. |
| `int serialGetPortsInfo(char* buffer, int len, const char* sep)` | List ports under `/dev/serial/by-id`. |
| `void serialOnError(void (*)(int))` | Register an error callback. |
| *(others in `serial.h`)* |

Return values ≤ 0 indicate error codes defined in `status_codes.h`.
Below is a condensed overview of the most relevant functions. See `serial.h` for full
signatures and additional helpers.

### Connection
* `serialOpen(...)` – open a serial device and return a handle
* `serialClose(handle)` – close the device

### I/O
* `serialRead(...)` / `serialWrite(...)` – basic read/write with timeout
* `serialReadUntil(...)` – read until a specific char is encountered (inclusive)
* `serialReadLine(...)` – read until `\n`
* `serialWriteLine(...)` – write buffer and append `\n`
* `serialReadUntilToken(...)` – read until a string token is encountered
* `serialReadFrame(...)` – read a frame delimited by start & end bytes

### Helpers
* `serialPeek(...)` – look at the next byte without consuming it
* `serialDrain(...)` – wait until all TX bytes are sent
* `serialClearBufferIn/Out(...)` – drop buffered bytes
* `serialAbortRead/Write(...)` – abort pending I/O operations

### Statistics
* `serialGetRxBytes(handle)` / `serialGetTxBytes(handle)` – cumulative RX / TX byte counters

### Enumeration & autodetect
* `serialGetPortsInfo(...)` – list available ports under `/dev/serial/by-id`

### Callbacks
* `serialOnError(func)` – error callback
* `serialOnRead(func)` – read callback (bytes read)
* `serialOnWrite(func)` – write callback (bytes written)

Return values ≤ 0 correspond to error codes defined in `status_codes.h`.

---

## 4 Ready-made Deno examples

Two runnable scripts live in `examples/` and require only Deno plus the compiled
shared library.

- **serial_echo.ts** – Minimal echo test that lists available ports, opens the
first one and verifies that the micro-controller echoes the sent string.
Run it with:
```bash
deno run --allow-ffi --allow-read examples/serial_echo.ts \
--lib ./build/libcpp_unix_bindings.so \
--port /dev/ttyUSB0
```

- **serial_advanced.ts** – Shows the high-level helpers (`serialWriteLine`,
`serialReadLine`, `serialPeek`, `statistics`, `serialDrain`). It sends three
lines and then prints the TX/RX counters.
```bash
deno run --allow-ffi --allow-read examples/serial_advanced.ts \
--lib ./build/libcpp_unix_bindings.so \
--port /dev/ttyUSB0
```

Notes:
1. `--lib` defaults to `./build/libcpp_unix_bindings.so`; pass a custom path if
you installed the library elsewhere.
2. `--port` defaults to `/dev/ttyUSB0`; adjust if your board shows up under a
different device (e.g. `/dev/ttyACM0`).
3. On most Arduino boards opening the port toggles DTR and triggers a reset.
Both scripts therefore wait ~2 s after opening the device before sending the
first command.

---
129 changes: 129 additions & 0 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# C API Reference

**Scope** – Public interface of `libcpp_unix_bindings` (C-linkable).
*Linux fully supported · Windows fully supported*

Every function is declared in `src/serial.h` and follows the same
convention: **positive / non-zero** ⇒ success, **≤ 0** ⇒ failure (maps to
`StatusCodes`).

---

## 1 · Connection

```c
intptr_t serialOpen(void* port, int baud, int dataBits,
int parity /*0-none·1-even·2-odd*/,
int stopBits /*0-1bit·2-2bit*/);

void serialClose(int64_t handle);
```

*Linux*: `port` is a NULL-terminated path like `/dev/ttyUSB0` or
`/dev/tty.SLAB_USBtoUART`.
*Windows*: use `"COM3"`, `"\\.\\COM12"`, etc. (to be implemented).

---

## 2 · Basic I/O

```c
int serialRead (int64_t handle, void* buf, int len, int timeoutMs, int mult);
int serialWrite(int64_t handle, const void* buf, int len, int timeoutMs, int mult);

int serialReadUntil(int64_t handle, void* buf, int len,
int timeoutMs, int mult, void* untilCharPtr);
```

*mult* is a polling multiplier (usually **1**). `serialReadUntil` stops **after**
the delimiter byte has been copied into *buf*.

---

## 3 · Convenience helpers

```c
int serialReadLine (int64_t h, void* buf, int len, int timeoutMs); // until '\n'
int serialWriteLine(int64_t h, const void* buf, int len, int timeoutMs); // appends '\n'

int serialReadUntilSequence(int64_t h, void* buf, int len,
int timeoutMs, void* sequencePtr);

int serialReadFrame(int64_t h, void* buf, int len, int timeoutMs,
char startByte, char endByte);
```

---

## 4 · Statistics & Buffer control

```c
int64_t serialInBytesTotal (int64_t handle); // cumulative RX
int64_t serialOutBytesTotal(int64_t handle); // cumulative TX

int serialInBytesWaiting (int64_t handle); // queued in driver
int serialOutBytesWaiting(int64_t handle);

int serialDrain(int64_t handle); // wait for TX empty

void serialClearBufferIn (int64_t handle);
void serialClearBufferOut(int64_t handle);

void serialAbortRead (int64_t handle);
void serialAbortWrite(int64_t handle);
```

---

## 5 · Port enumeration & Callbacks

```c
// Enumerate available ports. For each port the callback receives:
// portPath, aliasPath, manufacturer, serialNumber, pnpId,
// locationId, productId, vendorId
int serialGetPortsInfo(void (*cb)(const char*, const char*, const char*,
const char*, const char*, const char*,
const char*, const char*));

void serialOnError(void (*cb)(int code, const char* msg));
void serialOnRead (void (*cb)(int bytes));
void serialOnWrite(void (*cb)(int bytes));
```

*Linux*: implementation scans `/dev/serial/by-id` if present, otherwise falls
back to common tty names.
*Windows*: will iterate over available COM ports.

---

## 6 · StatusCodes

```c++
enum class StatusCodes : int {
SUCCESS = 0,
CLOSE_HANDLE_ERROR = -1,
INVALID_HANDLE_ERROR = -2,
READ_ERROR = -3,
WRITE_ERROR = -4,
GET_STATE_ERROR = -5,
SET_STATE_ERROR = -6,
SET_TIMEOUT_ERROR = -7,
BUFFER_ERROR = -8,
NOT_FOUND_ERROR = -9,
CLEAR_BUFFER_IN_ERROR = -10,
CLEAR_BUFFER_OUT_ERROR = -11,
ABORT_READ_ERROR = -12,
ABORT_WRITE_ERROR = -13
};
```

---

### Error-handling idiom

```c
int rv = serialWrite(h, data, len, 500, 1);
if (rv <= 0) {
// handle error – detailed info via registered onError callback (if any)
}
```
Loading