Skip to content

Commit 147a1c4

Browse files
committed
alt_core_api: Introduce a IDL/code-generation for compatible API headers
Summary ------- This adds a `protoc` plugin that reads Arduino API definitions expressed as protobuf services and generates C++ interface headers from them. Protobuf is used here primarily as an IDL/DSL: the first-class source of truth is the `.proto` definition, while the current generated target is a minimal C++ compatibility header. Motivation ---------- The goal is not to dead-copy the existing Arduino API implementation. Instead, this establishes an alternative API definition layer that can describe Arduino-compatible interfaces in a structured form and generate the required C++ surface from that definition. Although this PR currently focuses only on generating replacement/alternative C++ headers, the same IDL model can later be used as a base for additional generated artifacts, including RPC stubs or adapters for other protocols. What This Adds -------------- - A `protoc-gen-arduinoif` plugin for generating C++ interface headers. - Protobuf custom options for C++ naming, return types, argument types, visibility, includes, and service inheritance. - IDL support for Arduino-style inheritance such as `Print -> Stream -> HardwareSerial`. - Generated C++ interfaces using virtual inheritance where needed. - A compact generation pipeline focused on the current PoC target: header generation only. Design Notes ------------ - Protobuf services represent Arduino API classes/interfaces. - RPC methods represent callable API methods. - Request message fields represent C++ method parameters. - Method options map protobuf method names to Arduino-compatible C++ names, including overloads. - The protobuf definitions remain useful beyond C++ header generation because they preserve API semantics in an inspectable IDL form. Current Scope ------------- This PR intentionally keeps the implementation small. This is just only able to build hello_arduino example at this time. It does not introduce runtime RPC transport, client/server stubs, or full Arduino API coverage yet. The immediate purpose is to prove that Arduino-compatible interface headers can be generated from the IDL cleanly. Validation ---------- - Generated headers for the current Arduino API subset. - Verified `Print`, `Stream`, and `HardwareSerial` interface generation. - Confirmed duplicate C++ declarations are detected during generation. Signed-off-by: TOKITA Hiroshi <tokita.hiroshi@gmail.com>
1 parent 60932c7 commit 147a1c4

30 files changed

Lines changed: 1190 additions & 259 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
rust/Cargo.lock
1+
alt_core_api/rust/Cargo.lock

CMakeLists.txt

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,75 +18,8 @@ if (CONFIG_ARDUINO_API)
1818
zephyr_include_directories(${variant_dir})
1919

2020
if (CONFIG_USE_ARDUINO_API_RUST_IMPLEMENTATION)
21-
# quote from https://github.com/zephyrproject-rtos/zephyr-lang-rust/blob/main/CMakeLists.txt
22-
function (rust_target_arch RUST_TARGET)
23-
# Map Zephyr targets to LLVM targets.
24-
if(CONFIG_CPU_CORTEX_M)
25-
if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1)
26-
set(${RUST_TARGET} "thumbv6m-none-eabi" PARENT_SCOPE)
27-
elseif(CONFIG_CPU_CORTEX_M3)
28-
set(${RUST_TARGET} "thumbv7m-none-eabi" PARENT_SCOPE)
29-
elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7)
30-
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
31-
set(${RUST_TARGET} "thumbv7em-none-eabihf" PARENT_SCOPE)
32-
else()
33-
set(${RUST_TARGET} "thumbv7em-none-eabi" PARENT_SCOPE)
34-
endif()
35-
elseif(CONFIG_CPU_CORTEX_M23)
36-
set(${RUST_TARGET} "thumbv8m.base-none-eabi" PARENT_SCOPE)
37-
elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55)
38-
# Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m.
39-
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
40-
set(${RUST_TARGET} "thumbv8m.main-none-eabihf" PARENT_SCOPE)
41-
else()
42-
set(${RUST_TARGET} "thumbv8m.main-none-eabi" PARENT_SCOPE)
43-
endif()
44-
45-
# Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust
46-
# gain support for this target.
47-
else()
48-
message(FATAL_ERROR "Unknown Cortex-M target.")
49-
endif()
50-
elseif(CONFIG_RISCV)
51-
if(CONFIG_RISCV_ISA_RV64I)
52-
# TODO: Should fail if the extensions don't match.
53-
set(${RUST_TARGET} "riscv64imac-unknown-none-elf" PARENT_SCOPE)
54-
elseif(CONFIG_RISCV_ISA_RV32I)
55-
# TODO: We have multiple choices, try to pick the best.
56-
set(${RUST_TARGET} "riscv32i-unknown-none-elf" PARENT_SCOPE)
57-
else()
58-
message(FATAL_ERROR "Rust: Unsupported riscv ISA")
59-
endif()
60-
elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64"))
61-
set(${RUST_TARGET} "x86_64-unknown-none" PARENT_SCOPE)
62-
elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64"))
63-
set(${RUST_TARGET} "aarch64-unknown-none" PARENT_SCOPE)
64-
else()
65-
message(FATAL_ERROR "Rust: Add support for other target")
66-
endif()
67-
endfunction()
68-
69-
rust_target_arch(RUST_TARGET_TRIPLE)
70-
71-
set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rust)
72-
set(RUST_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust)
73-
set(RUST_LIB ${RUST_OUT_DIR}/target/${RUST_TARGET_TRIPLE}/release/libarduinocore_api_rust.a)
74-
75-
add_custom_command(
76-
OUTPUT ${RUST_LIB}
77-
COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_OUT_DIR}/target
78-
cargo build --manifest-path ${RUST_CRATE_DIR}/Cargo.toml
79-
--target ${RUST_TARGET_TRIPLE} --release
80-
WORKING_DIRECTORY ${RUST_CRATE_DIR}
81-
COMMENT "Building Rust staticlib for ${RUST_TARGET}"
82-
VERBATIM
83-
)
84-
85-
add_custom_target(arduinocore_api_rust_build ALL DEPENDS ${RUST_LIB})
86-
add_library(arduinocore_api_rust STATIC IMPORTED GLOBAL)
87-
set_target_properties(arduinocore_api_rust PROPERTIES IMPORTED_LOCATION ${RUST_LIB})
88-
zephyr_link_libraries(arduinocore_api_rust)
89-
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/)
21+
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/alt_core_api)
22+
add_subdirectory(alt_core_api)
9023
else()
9124
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/)
9225
zephyr_sources(${CMAKE_CURRENT_SOURCE_DIR}/zephyr/blobs/ArduinoCore-API/api/CanMsg.cpp)

Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ if ARDUINO_API
2020
config USE_ARDUINO_API_RUST_IMPLEMENTATION
2121
bool "Use Rust implementation API core"
2222
select RUST
23+
select NANOPB
2324

2425
config QEMU_ICOUNT
2526
bool "QEMU icount mode"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The **Arduino Core API** module for zephyr leverages the power of Zephyr under a
66

77
* [Using external Arduino Libraries](/documentation/arduino_libs.md)
88
* [Adding custom boards/ variants](/documentation/variants.md)
9+
* [Arduino IDL code generation](/documentation/idl_codegen.md)
910

1011
## Adding Arduino Core API to Zephyr
1112

alt_core_api/CMakeLists.txt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
function(rust_target_arch RUST_TARGET)
4+
# Map Zephyr targets to LLVM targets.
5+
if(CONFIG_CPU_CORTEX_M)
6+
if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1)
7+
set(${RUST_TARGET} "thumbv6m-none-eabi" PARENT_SCOPE)
8+
elseif(CONFIG_CPU_CORTEX_M3)
9+
set(${RUST_TARGET} "thumbv7m-none-eabi" PARENT_SCOPE)
10+
elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7)
11+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
12+
set(${RUST_TARGET} "thumbv7em-none-eabihf" PARENT_SCOPE)
13+
else()
14+
set(${RUST_TARGET} "thumbv7em-none-eabi" PARENT_SCOPE)
15+
endif()
16+
elseif(CONFIG_CPU_CORTEX_M23)
17+
set(${RUST_TARGET} "thumbv8m.base-none-eabi" PARENT_SCOPE)
18+
elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55)
19+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
20+
set(${RUST_TARGET} "thumbv8m.main-none-eabihf" PARENT_SCOPE)
21+
else()
22+
set(${RUST_TARGET} "thumbv8m.main-none-eabi" PARENT_SCOPE)
23+
endif()
24+
else()
25+
message(FATAL_ERROR "Unknown Cortex-M target.")
26+
endif()
27+
elseif(CONFIG_RISCV)
28+
if(CONFIG_RISCV_ISA_RV64I)
29+
set(${RUST_TARGET} "riscv64imac-unknown-none-elf" PARENT_SCOPE)
30+
elseif(CONFIG_RISCV_ISA_RV32I)
31+
set(${RUST_TARGET} "riscv32i-unknown-none-elf" PARENT_SCOPE)
32+
else()
33+
message(FATAL_ERROR "Rust: Unsupported riscv ISA")
34+
endif()
35+
elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "x86_64"))
36+
set(${RUST_TARGET} "x86_64-unknown-none" PARENT_SCOPE)
37+
elseif(CONFIG_ARCH_POSIX AND CONFIG_64BIT AND (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64"))
38+
set(${RUST_TARGET} "aarch64-unknown-none" PARENT_SCOPE)
39+
else()
40+
message(FATAL_ERROR "Rust: Add support for other target")
41+
endif()
42+
endfunction()
43+
44+
rust_target_arch(RUST_TARGET_TRIPLE)
45+
46+
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/rust/Cargo.toml)
47+
set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rust)
48+
else()
49+
set(RUST_CRATE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../rust)
50+
endif()
51+
set(RUST_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust)
52+
set(RUST_LIB ${RUST_OUT_DIR}/target/${RUST_TARGET_TRIPLE}/release/libarduinocore_api_rust.a)
53+
set(RUST_SRCS
54+
${RUST_CRATE_DIR}/src/lib.rs
55+
${RUST_CRATE_DIR}/src/common.rs
56+
)
57+
58+
add_custom_command(
59+
OUTPUT ${RUST_LIB}
60+
COMMAND ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${RUST_OUT_DIR}/target
61+
cargo build --manifest-path ${RUST_CRATE_DIR}/Cargo.toml
62+
--target ${RUST_TARGET_TRIPLE} --release
63+
WORKING_DIRECTORY ${RUST_CRATE_DIR}
64+
DEPENDS ${RUST_CRATE_DIR}/Cargo.toml ${RUST_SRCS}
65+
COMMENT "Building Rust staticlib for ${RUST_TARGET_TRIPLE}"
66+
VERBATIM
67+
)
68+
69+
add_custom_target(arduinocore_api_rust_build ALL DEPENDS ${RUST_LIB})
70+
add_library(arduinocore_api_rust STATIC IMPORTED GLOBAL)
71+
set_target_properties(arduinocore_api_rust PROPERTIES IMPORTED_LOCATION ${RUST_LIB})
72+
zephyr_link_libraries(arduinocore_api_rust)
73+
74+
add_subdirectory(idl)
75+
add_subdirectory(api)
76+
77+
if(TARGET arduinocore_idl_generate_interfaces)
78+
add_dependencies(app arduinocore_idl_generate_interfaces)
79+
endif()

alt_core_api/api/ArduinoAPI.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2026 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#include <stdint.h>
10+
#include <stddef.h>
11+
#include <stdlib.h>
12+
13+
#include <zephyr/drivers/gpio.h>
14+
#include <zephyr/sys/util.h>
15+
16+
#include "common_types.h"
17+
18+
typedef gpio_port_pins_t pin_size_t;
19+
typedef uint8_t byte;
20+
21+
typedef void (*voidFuncPtr)(void);
22+
typedef void (*voidFuncPtrParam)(void *);
23+
24+
void setup(void);
25+
void loop(void);
26+
void delay(unsigned long);
27+
28+
namespace arduino {}
29+
30+
using namespace arduino;
31+
32+
template <class T> constexpr const T &min(const T &a, const T &b) {
33+
return (a < b) ? a : b;
34+
}

alt_core_api/api/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
zephyr_sources(apiCommon.cpp)
4+
zephyr_sources(apiPrint.cpp)

alt_core_api/api/Common.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright (c) 2026 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/* stub file for compat */

alt_core_api/api/HardwareSerial.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2026 TOKITA Hiroshi
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#include "Stream.h"
10+
#include <hardware_serial_interface.hpp>
11+
12+
namespace arduino {
13+
14+
enum SerialConfig {
15+
SERIAL_5E1 = (SERIAL_DATA_5 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1),
16+
SERIAL_5E2 = (SERIAL_DATA_5 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2),
17+
SERIAL_5O1 = (SERIAL_DATA_5 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1),
18+
SERIAL_5O2 = (SERIAL_DATA_5 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2),
19+
SERIAL_5N1 = (SERIAL_DATA_5 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1),
20+
SERIAL_5N2 = (SERIAL_DATA_5 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2),
21+
SERIAL_5M1 = (SERIAL_DATA_5 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1),
22+
SERIAL_5M2 = (SERIAL_DATA_5 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2),
23+
SERIAL_5S1 = (SERIAL_DATA_5 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1),
24+
SERIAL_5S2 = (SERIAL_DATA_5 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2),
25+
SERIAL_6E1 = (SERIAL_DATA_6 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1),
26+
SERIAL_6E2 = (SERIAL_DATA_6 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2),
27+
SERIAL_6O1 = (SERIAL_DATA_6 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1),
28+
SERIAL_6O2 = (SERIAL_DATA_6 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2),
29+
SERIAL_6N1 = (SERIAL_DATA_6 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1),
30+
SERIAL_6N2 = (SERIAL_DATA_6 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2),
31+
SERIAL_6M1 = (SERIAL_DATA_6 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1),
32+
SERIAL_6M2 = (SERIAL_DATA_6 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2),
33+
SERIAL_6S1 = (SERIAL_DATA_6 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1),
34+
SERIAL_6S2 = (SERIAL_DATA_6 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2),
35+
SERIAL_7E1 = (SERIAL_DATA_7 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1),
36+
SERIAL_7E2 = (SERIAL_DATA_7 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2),
37+
SERIAL_7O1 = (SERIAL_DATA_7 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1),
38+
SERIAL_7O2 = (SERIAL_DATA_7 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2),
39+
SERIAL_7N1 = (SERIAL_DATA_7 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1),
40+
SERIAL_7N2 = (SERIAL_DATA_7 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2),
41+
SERIAL_7M1 = (SERIAL_DATA_7 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1),
42+
SERIAL_7M2 = (SERIAL_DATA_7 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2),
43+
SERIAL_7S1 = (SERIAL_DATA_7 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1),
44+
SERIAL_7S2 = (SERIAL_DATA_7 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2),
45+
SERIAL_8E1 = (SERIAL_DATA_8 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_1),
46+
SERIAL_8E2 = (SERIAL_DATA_8 | SERIAL_PARITY_EVEN | SERIAL_STOP_BIT_2),
47+
SERIAL_8O1 = (SERIAL_DATA_8 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_1),
48+
SERIAL_8O2 = (SERIAL_DATA_8 | SERIAL_PARITY_ODD | SERIAL_STOP_BIT_2),
49+
SERIAL_8N1 = (SERIAL_DATA_8 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_1),
50+
SERIAL_8N2 = (SERIAL_DATA_8 | SERIAL_PARITY_NONE | SERIAL_STOP_BIT_2),
51+
SERIAL_8M1 = (SERIAL_DATA_8 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_1),
52+
SERIAL_8M2 = (SERIAL_DATA_8 | SERIAL_PARITY_MARK | SERIAL_STOP_BIT_2),
53+
SERIAL_8S1 = (SERIAL_DATA_8 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_1),
54+
SERIAL_8S2 = (SERIAL_DATA_8 | SERIAL_PARITY_SPACE | SERIAL_STOP_BIT_2),
55+
};
56+
57+
class HardwareSerial : virtual public Stream, virtual public HardwareSerialInterface {
58+
public:
59+
using Print::write;
60+
};
61+
62+
extern void __weak serialEventRun(void);
63+
64+
} // namespace arduino

0 commit comments

Comments
 (0)