feat: update documentation for GPIO, PCA9685, and AS5600 nodes; add README files and message definitions

master
a2nr 2026-03-16 20:08:19 +07:00
parent 89a12c85cc
commit f4f808e42c
6 changed files with 260 additions and 86 deletions

View File

@ -17,9 +17,9 @@ See [readme.md](readme.md) for project overview and status.
| `blockly_app` — creating custom blocks (full guide + reference) | [src/blockly_app/BLOCKS.md](src/blockly_app/BLOCKS.md) |
| `blockly_executor` — file reference, handlers & testing guide | [src/blockly_executor/README.md](src/blockly_executor/README.md) |
| `blockly_interfaces` — ROS2 action & message interfaces | [src/blockly_interfaces/README.md](src/blockly_interfaces/README.md) |
| `gpio_node` — Raspberry Pi GPIO node (C++, libgpiod) | [src/gpio_node/](src/gpio_node/) |
| `pca9685_node` — PCA9685 16-channel PWM controller (C++, I2C) | [src/pca9685_node/](src/pca9685_node/) |
| `as5600_node` — AS5600 12-bit magnetic encoder (C++, I2C) | [src/as5600_node/](src/as5600_node/) |
| `gpio_node` — Raspberry Pi GPIO node (C++, libgpiod) | [src/gpio_node/README.md](src/gpio_node/README.md) |
| `pca9685_node` — PCA9685 16-channel PWM controller (C++, I2C) | [src/pca9685_node/README.md](src/pca9685_node/README.md) |
| `as5600_node` — AS5600 12-bit magnetic encoder (C++, I2C) | [src/as5600_node/README.md](src/as5600_node/README.md) |
---

View File

@ -44,88 +44,6 @@ this list is short by priority
# Feature Task
## 1 Bug Fix: Blockly Debug Mode — Step Into for Function Blocks : [x]
Debug mode tidak bisa step into ke function blocks karena `highlightBlock()` bersifat synchronous — tidak bisa pause execution. Hanya `executeAction()` yang bisa pause, sehingga blocks tanpa `executeAction()` (function calls, variables, math) tidak bisa di-debug. Fix ini mengubah arsitektur debug engine:
1. **Async `highlightBlock()`** — menjadi universal pause point. Semua block generators menggunakan `await highlightBlock()` sehingga setiap block bisa di-breakpoint dan di-step.
2. **Call depth tracking**`enterFunction()/exitFunction()` di-inject ke generated code di procedure calls. Step Over menggunakan `callDepth` untuk skip function bodies.
3. **Step modes**`stepMode` state machine ('into'|'over'|'continue') menggantikan monkey-patching `highlightBlock` di setiap step function.
4. **Auto-pause at first block** — debug mode langsung pause di block pertama (tidak perlu breakpoint untuk mulai stepping).
5. **Run = Continue** — Run button saat paused berfungsi sebagai Continue (resume sampai breakpoint berikutnya).
### Definition Of Done
- `debug-engine.js` di-rewrite: async `highlightBlock()` override di `runDebug()`, `callDepth` tracking, `stepMode` state machine
- `enterFunction()`/`exitFunction()` global helpers tersedia untuk generated code
- `async-procedures.js`: `procedures_callreturn` wrapped dengan async IIFE + `highlightBlock()` + depth tracking
- `async-procedures.js`: `procedures_callnoreturn` menggunakan `await highlightBlock()` + `enterFunction()/exitFunction()` dengan try/finally
- Block generators (`digitalOut.js`, `delay.js`, `mainProgram.js`) menggunakan `await highlightBlock()`
- `ui-controls.js`: Run button enabled saat paused (Continue behavior), `onRunClick()` memanggil `continueExecution()`
- Step Into pada function call block → pause di block pertama dalam function body
- Step Over pada function call block → skip function body, pause di block berikutnya
- Debug mode pause di block pertama tanpa perlu breakpoint
- Non-debug mode (`runProgram()`) tidak terpengaruh — `await` pada synchronous `highlightBlock()` adalah no-op
- `pixi run build-app` berhasil tanpa error
## 2 Enhancement: Port gpio_node to C++ : [x]
gpio_node di-port dari Python (`ament_python`, `rclpy`, `gpiod` Python binding) ke C++ (`ament_cmake`, `rclcpp`, `libgpiod` **C API**). Node ini hardware-only — hanya berjalan di Raspberry Pi dengan akses ke `/dev/gpiochipX`.
### Implementasi
#### A. Package Structure (C++, ament_cmake)
```
src/gpio_node/
├── CMakeLists.txt # ament_cmake, pkg_check_modules(libgpiod), build executable
├── package.xml # depend: rclcpp, blockly_interfaces
├── include/gpio_node/
│ └── gpio_node.hpp # GpioNode class — rclcpp::Node + gpiod C API raw pointers
└── src/
├── gpio_node.cpp # GpioNode implementation (setup_gpio, write_callback, read_callback)
└── main.cpp # main() — rclcpp::spin(node)
```
Dihapus: semua file Python (`gpio_node.py`, `__init__.py`, `setup.py`, `setup.cfg`, `resource/`)
#### B. C++ Node — Same ROS2 API Surface
- **Subscribe** `/gpio/write` (`GpioWrite`) — `gpiod_line_request_set_value()` untuk set pin output
- **Publish** `/gpio/state` (`GpioRead`) — `gpiod_line_request_get_value()` via polling timer (10 Hz default)
- **Parameters**: `output_pins` (int array), `input_pins` (int array), `input_publish_rate` (double), `gpio_chip` (string, default `/dev/gpiochip0`)
- Pin tidak terdaftar di `output_pins` → log warning, ignore write
- Cleanup: `gpiod_line_request_release()` di destructor (manual memory management, bukan C++ RAII)
#### C. Catatan: libgpiod C API, bukan C++ Bindings
conda-forge `libgpiod` **hanya menyertakan C library** (`gpiod.h`), bukan C++ bindings (`gpiod.hpp` / `libgpiodcxx`). Oleh karena itu implementasi menggunakan gpiod v2 **C API** langsung. Semua resource (`gpiod_line_request`, `gpiod_line_config`, dll.) di-manage manual via `gpiod_*_free()`.
#### D. Platform-Specific Tasks & System Dependencies
Cross-compilation ROS2 C++ tidak praktis (butuh full aarch64 sysroot). Build dilakukan **native di Raspberry Pi**.
pixi.toml menggunakan **platform-specific task sections**:
- `[target.linux-64.tasks]` — desktop tasks (build-app, executor, app, test)
- `[target.linux-aarch64.tasks]` — Pi tasks (setup-dep, build-gpio, gpio-node)
`setup-dep` task menginstall system libraries via `apt` yang tidak tersedia / tidak lengkap di conda-forge:
```bash
sudo apt install -y liblttng-ust-dev lttng-tools libgpiod-dev gpiod
```
- `libgpiod-dev` — header + `.so` untuk linking (conda package tidak menyertakan `.so` symlink untuk linker)
- `liblttng-ust-dev` + `lttng-tools` — dibutuhkan oleh `rclcpp` (RoboStack) yang di-build dengan LTTng tracing support
```bash
# Di Pi: clone repo + install deps + build + run
git clone <repo> ~/amr-ros-k4 && cd ~/amr-ros-k4
pixi install && pixi run build-gpio # setup-dep runs automatically
pixi run gpio-node
```
### Definition Of Done
- [x] `src/gpio_node/` berisi `CMakeLists.txt`, `package.xml`, `include/`, `src/` — tidak ada file Python
- [x] `pixi.toml` menyertakan `ros-jazzy-rclcpp` di `linux-aarch64` dependencies
- [x] `pixi.toml` tidak lagi menyertakan `gpiod` di `linux-aarch64` pypi-dependencies
- [x] `setup-dep` task menginstall `libgpiod-dev`, `liblttng-ust-dev` via apt
- [x] `pixi run build-gpio` berhasil di Raspberry Pi (native build) tanpa error
- [x] Node berjalan: `pixi run gpio-node` — subscribe `/gpio/write`, publish `/gpio/state`
- [x] Parameter `output_pins`, `input_pins`, `input_publish_rate`, `gpio_chip` berfungsi via `--ros-args -p`
- [x] Executor (`blockly_executor`) tetap berfungsi tanpa perubahan — interface ROS2 identik
## 3 Enhancement: PCA9685 — 16-Channel PWM Controller (I2C) : [ ]
PCA9685 adalah 16-channel, 12-bit PWM controller via I2C. Motor DC kiwi wheel menggunakan 6 channel (3 motor × 2: PWM + direction), sehingga 10 channel tersedia untuk extensi (servo, LED, dll). Node ini general-purpose — mengontrol channel mana saja via Blockly block dengan parameter address, channel, dan PWM value.

80
src/as5600_node/README.md Normal file
View File

@ -0,0 +1,80 @@
# as5600_node — AS5600 12-bit Magnetic Rotary Encoder (C++, I2C)
ROS2 node for reading AS5600 magnetic rotary position sensors via Linux I2C (`ioctl`). Publishes angle data periodically. Supports multiple encoders on separate I2C buses.
**Platform:** linux-aarch64 only (Raspberry Pi)
## ROS2 Interface
| Direction | Topic | Message | Description |
|---|---|---|---|
| Publish | `/encoder/state` | `EncoderRead` | Periodic angle readings per encoder |
```
# EncoderRead.msg (as5600_node → executor)
uint8 encoder_id # Encoder index (0, 1, 2, ...)
float32 angle # Angle in degrees (0.0-360.0)
uint16 raw_angle # Raw 12-bit value (0-4095)
```
## Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `i2c_devices` | string[] | `["/dev/i2c-1"]` | List of I2C device paths, one per encoder |
| `publish_rate` | double | `10.0` | Publish frequency Hz |
## File Structure
```
src/as5600_node/
├── CMakeLists.txt # ament_cmake — NO external lib dependency
├── package.xml # depend: rclcpp, blockly_interfaces
├── include/as5600_node/
│ └── as5600_node.hpp # As5600Node class + I2C helpers + register constants
└── src/
├── as5600_node.cpp # I2C init, timer_callback, read_raw_angle
└── main.cpp # rclcpp::spin(node)
```
## Build & Run
```bash
pixi run build-as5600 # installs system deps (apt) + builds
pixi run as5600-node # start node with default parameters (1 encoder)
# 3 encoders on separate buses, 20 Hz
source install/setup.bash
ros2 run as5600_node as5600_node --ros-args \
-p i2c_devices:="['/dev/i2c-1', '/dev/i2c-3', '/dev/i2c-4']" \
-p publish_rate:=20.0
```
## AS5600 Register Map
| Register | Address | Description |
|---|---|---|
| RAW_ANGLE | `0x0C`-`0x0D` | 12-bit raw angle (0-4095) |
| STATUS | `0x0B` | Magnet detect status |
| AGC | `0x1A` | Automatic gain control |
## Implementation Notes
- AS5600 has a **fixed I2C address (0x36)** — cannot be changed. For multiple encoders, each must be on a **separate I2C bus**
- Uses Linux I2C via `ioctl()` + `linux/i2c-dev.h` — no external library dependency
- `I2C_SLAVE` address is set once per fd at startup (each fd is dedicated to one bus/encoder)
- Raw angle read: write register `0x0C`, read 2 bytes → `((buf[0] & 0x0F) << 8) | buf[1]` (12-bit)
- Angle conversion: `raw * 360.0 / 4096.0` degrees
- Timer callback iterates all fds and publishes one `EncoderRead` per encoder with `encoder_id` matching the index in `i2c_devices`
## Raspberry Pi I2C Setup
The Pi has one hardware I2C bus (`/dev/i2c-1`) by default. For multiple AS5600 encoders, enable extra I2C buses via GPIO bit-banging overlays in `/boot/config.txt`:
```
# Extra I2C buses using GPIO pins
dtoverlay=i2c-gpio,bus=3,i2c_gpio_sda=17,i2c_gpio_scl=27
dtoverlay=i2c-gpio,bus=4,i2c_gpio_sda=22,i2c_gpio_scl=23
```
After reboot, verify with `ls /dev/i2c-*` and `i2cdetect -y <bus>`.

View File

@ -38,6 +38,32 @@ bool state
Used for communication between the executor's GPIO handlers and the `gpio_node` running on Raspberry Pi.
## PwmWrite.msg
Defined in [`msg/PwmWrite.msg`](msg/PwmWrite.msg):
```
# PwmWrite — PWM duty cycle command (executor → pca9685_node)
uint8 address # I2C device address (e.g. 0x40)
uint8 channel # PWM channel (0-15)
uint16 value # Duty cycle (0-4095, 12-bit)
```
Used for communication between the executor's PWM handler and the `pca9685_node` running on Raspberry Pi.
## EncoderRead.msg
Defined in [`msg/EncoderRead.msg`](msg/EncoderRead.msg):
```
# EncoderRead — rotary encoder angle (as5600_node → executor)
uint8 encoder_id # Encoder index (0, 1, 2, ...)
float32 angle # Angle in degrees (0.0-360.0)
uint16 raw_angle # Raw 12-bit value (0-4095)
```
Used for communication between the `as5600_node` running on Raspberry Pi and the executor's encoder handler.
## Building
```bash
@ -48,7 +74,7 @@ This must be run before building any other package. The generated Python modules
```python
from blockly_interfaces.action import BlocklyAction
from blockly_interfaces.msg import GpioWrite, GpioRead
from blockly_interfaces.msg import GpioWrite, GpioRead, PwmWrite, EncoderRead
```
## Usage

71
src/gpio_node/README.md Normal file
View File

@ -0,0 +1,71 @@
# gpio_node — Raspberry Pi GPIO Node (C++, libgpiod)
ROS2 node for digital I/O on Raspberry Pi via libgpiod v2 C API. Subscribes to write commands and publishes input pin states.
**Platform:** linux-aarch64 only (Raspberry Pi)
## ROS2 Interface
| Direction | Topic | Message | Description |
|---|---|---|---|
| Subscribe | `/gpio/write` | `GpioWrite` | Set output pin HIGH/LOW |
| Publish | `/gpio/state` | `GpioRead` | Polled input pin states |
```
# GpioWrite.msg (executor → gpio_node)
uint8 pin
bool state
# GpioRead.msg (gpio_node → executor)
uint8 pin
bool state
```
## Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `output_pins` | int[] | `[17, 27, 22]` | GPIO pins configured as output |
| `input_pins` | int[] | `[5, 6, 13]` | GPIO pins configured as input |
| `input_publish_rate` | double | `10.0` | Input polling frequency (Hz) |
| `gpio_chip` | string | `/dev/gpiochip0` | GPIO chip device path |
## File Structure
```
src/gpio_node/
├── CMakeLists.txt # ament_cmake, pkg_check_modules(libgpiod)
├── package.xml # depend: rclcpp, blockly_interfaces
├── include/gpio_node/
│ └── gpio_node.hpp # GpioNode class + gpiod C API raw pointers
└── src/
├── gpio_node.cpp # setup_gpio, write_callback, read_callback
└── main.cpp # rclcpp::spin(node)
```
## Build & Run
```bash
pixi run build-gpio # installs system deps (apt) + builds
pixi run gpio-node # start node with default parameters
# Custom parameters
source install/setup.bash
ros2 run gpio_node gpio_node --ros-args \
-p output_pins:="[17, 27]" \
-p input_pins:="[5, 6]" \
-p gpio_chip:=/dev/gpiochip4
```
`build-gpio` automatically runs `setup-dep` which installs system libraries via apt:
```bash
sudo apt install -y libgpiod-dev gpiod liblttng-ust-dev lttng-tools
```
## Implementation Notes
- Uses **libgpiod v2 C API** (`gpiod.h`), not C++ bindings (`gpiod.hpp`/`libgpiodcxx`) — conda-forge only ships the C library
- All gpiod resources (`gpiod_line_request`, `gpiod_line_config`, etc.) are managed manually via `gpiod_*_free()` in the destructor
- Output and input pins are requested as **single batch** line requests for efficiency
- Writes to pins not listed in `output_pins` are ignored with a warning
- Input pins are polled via a `create_wall_timer` at the configured rate — each poll publishes one `GpioRead` message per input pin

View File

@ -0,0 +1,79 @@
# pca9685_node — PCA9685 16-Channel PWM Controller (C++, I2C)
ROS2 node for controlling PCA9685 PWM channels via Linux I2C (`ioctl`). Subscribes to write commands — no external library needed, uses kernel headers only.
**Platform:** linux-aarch64 only (Raspberry Pi)
## ROS2 Interface
| Direction | Topic | Message | Description |
|---|---|---|---|
| Subscribe | `/pwm/write` | `PwmWrite` | Set PWM duty cycle on a channel |
```
# PwmWrite.msg (executor → pca9685_node)
uint8 address # I2C device address (e.g. 0x40)
uint8 channel # PWM channel (0-15)
uint16 value # Duty cycle (0-4095, 12-bit)
```
PCA9685 is write-only — no read/feedback message needed.
## Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| `i2c_device` | string | `/dev/i2c-1` | Linux I2C device path |
| `frequency` | int | `50` | PWM frequency Hz (all channels) |
## File Structure
```
src/pca9685_node/
├── CMakeLists.txt # ament_cmake — NO external lib dependency
├── package.xml # depend: rclcpp, blockly_interfaces
├── include/pca9685_node/
│ └── pca9685_node.hpp # Pca9685Node class + I2C helpers + register constants
└── src/
├── pca9685_node.cpp # I2C init, configure_frequency, write_callback, set_pwm
└── main.cpp # rclcpp::spin(node)
```
## Build & Run
```bash
pixi run build-pca9685 # installs system deps (apt) + builds
pixi run pca9685-node # start node with default parameters
# Custom parameters
source install/setup.bash
ros2 run pca9685_node pca9685_node --ros-args \
-p i2c_device:=/dev/i2c-0 \
-p frequency:=1000
```
## PCA9685 Register Map
| Register | Address | Description |
|---|---|---|
| MODE1 | `0x00` | Sleep/restart, auto-increment |
| LED0_ON_L | `0x06` | Channel 0 ON timing (4 registers per channel) |
| PRE_SCALE | `0xFE` | PWM frequency: `prescale = round(25MHz / (4096 * freq)) - 1` |
## Implementation Notes
- Uses Linux I2C via `ioctl()` + `linux/i2c-dev.h` — no external library dependency
- **Multi-address support**: I2C slave address is sent per-message (`ioctl(I2C_SLAVE)` on every write), so one node can control multiple PCA9685 boards (0x40-0x7F, configurable via solder bridges)
- Frequency is configured once at startup on default address 0x40 (sleep → set prescale → wake + auto-increment → restart)
- Channel validation: values > 15 are rejected, values > 4095 are clamped
- PWM duty cycle: ON=0, OFF=value (simple duty cycle mode, 4 bytes per channel with auto-increment)
## I2C Debugging
```bash
# Check available I2C buses
ls /dev/i2c-*
# Scan devices on bus 1 (needs i2c-tools)
i2cdetect -y 1
```