Metadata-Version: 2.4
Name: roboreactor-firmata
Version: 0.1.3
Summary: A unified, dynamic Firmata control library supporting GPIO, I2C, SPI, UART, and CAN-bus on STM32 microcontrollers
Author-email: Chanapai Chuadchum <teslacoil358@gmail.com>
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyfirmata2>=2.4.4
Requires-Dist: pyserial>=3.5
Dynamic: license-file

# roboreactor-firmata

A unified, dynamic Firmata client library for configuring and controlling **GPIO**, **I2C**, **SPI**, **UART**, and **CAN-bus** peripherals on **STM32** microcontrollers.

Instead of being limited to hardcoded hardware pins, `roboreactor-firmata` allows you to dynamically assign peripheral pins at runtime from Python using a custom extended SysEx protocol.

---

## Installation

```bash
pip install roboreactor-firmata
```

---

## Firmware Flashing

Before running the Python client, flash the matching custom `.ino` sketch located inside the `firmware/` directory to your microcontroller:

- **STM32**: Flash `StandardFirmataCustom_STM32.ino` (Requires `STM32duino` core and optional `STM32_CAN` library).

---

## Features & Quick Start Examples

### 1. Analog Input Read (Background Reporting)

Read analog inputs using physical named pins. Register callbacks to receive updates asynchronously.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure A0 analog channel on PA0
analog_in = board.get_gpio('PA0', mode='a')

# Callback receives value between 0.0 and 1.0
def analog_callback(value):
    print(f"Analog Read PA0: {value:.3f} | Voltage: {value * 3.3:.2f}V")

analog_in.register_callback(analog_callback)
analog_in.enable_reporting()

# Start messaging loop
board.samplingOn(50)

time.sleep(3)
board.exit()
```
See [examples/analog_read_example.py](examples/analog_read_example.py) for the complete runnable script.

### 2. Servo Motor Control

Configure a physical pin as a servo output to control angular position (0-180 degrees).

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure PA9 as Servo
servo_out = board.get_gpio('PA9', mode='s')

# Set angles
print("Setting servo to 0 deg")
servo_out.write(0)
time.sleep(1)

print("Setting servo to 90 deg")
servo_out.write(90)
time.sleep(1)

print("Setting servo to 180 deg")
servo_out.write(180)
time.sleep(1)

board.exit()
```
See [examples/servo_control_example.py](examples/servo_control_example.py) for the complete runnable script.

### 3. PWM Output Control

Configure pins for PWM output to write duty cycles between 0.0 (0%) and 1.0 (100%).

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure PA1 as PWM
pwm_out = board.get_gpio('PA1', mode='p')

# Set PWM duty cycle to 50%
pwm_out.write(0.5)
time.sleep(2)

# Turn off PWM
pwm_out.write(0.0)
board.exit()
```
See [examples/pwm_output_example.py](examples/pwm_output_example.py) for the complete runnable script.

### 4. Dynamic I2C Read

Route dynamic SDA/SCL pins and request read bytes from an I2C sensor via a response callback.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')
board.samplingOn(50)

# Configure dynamic SDA/SCL pins
board.configure_custom_i2c(sda_pin="PB9", scl_pin="PB8")
board.i2c_config()

# Define read response callback
def on_i2c_reply(data):
    if len(data) >= 6:
        accel_x = (data[0] << 8) | data[1]
        print(f"MPU6050 Accel X: {accel_x}")

# Read 6 bytes starting from register 0x3B on MPU6050 (0x68)
board.i2c_read(0x68, register=0x3B, num_bytes=6, callback=on_i2c_reply)

time.sleep(2)
board.exit()
```
See [examples/i2c_read_example.py](examples/i2c_read_example.py) for the complete runnable script.

### 5. Dynamic I2C Write

Configure dynamic I2C pins and write command configuration bytes to configure an external device or driver register.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure dynamic SDA/SCL pins
board.configure_custom_i2c(sda_pin="PB9", scl_pin="PB8")
board.i2c_config()

# Wake up MPU6050 (0x68) by writing 0x00 to Power Management register (0x6B)
board.i2c_write(0x68, register=0x6B, data_bytes=[0x00])

time.sleep(0.5)
board.exit()
```
See [examples/i2c_write_example.py](examples/i2c_write_example.py) for the complete runnable script.

### 6. Dynamic SPI Read

Configure dynamic SPI clock, data, and CS pins to perform a read transaction using a response callback.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')
board.samplingOn(50)

# Configure dynamic SPI lines
board.configure_custom_spi(mosi="PA7", miso="PA6", sclk="PA5", cs="PA4")

# Define SPI reply callback
def spi_callback(data):
    print(f"SPI Read Bytes: {list(data)}")

# Transfer JEDEC ID command to read response
board.spi_transfer("PA4", [0x9F, 0x00, 0x00, 0x00], callback=spi_callback)

time.sleep(2)
board.exit()
```
See [examples/spi_read_example.py](examples/spi_read_example.py) for the complete runnable script.

### 7. Dynamic SPI Write

Configure SPI lines and write command or configuration payloads to a device without registering a callback.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure dynamic SPI lines
board.configure_custom_spi(mosi="PA7", miso="PA6", sclk="PA5", cs="PA4")

# Write Write Enable (WREN) command (0x06) to SPI flash chip
board.spi_transfer("PA4", [0x06])

time.sleep(0.5)
board.exit()
```
See [examples/spi_write_example.py](examples/spi_write_example.py) for the complete runnable script.

### 8. Dynamic UART Read

Configure dynamic RX/TX pins at a set baudrate, and receive serial streams asynchronously.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')
board.samplingOn(50)

def uart_callback(data):
    print(f"UART Rx: {data}")

# Route RX=PA3, TX=PA2 at 9600 baud
board.configure_custom_uart(rx_pin="PA3", tx_pin="PA2", baudrate=9600, callback=uart_callback)

time.sleep(3)
board.exit()
```
See [examples/uart_read_example.py](examples/uart_read_example.py) for the complete runnable script.

### 9. Dynamic UART Write

Configure dynamic UART pins and write raw serial command packages/data to external devices.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Route RX=PA3, TX=PA2 at 9600 baud
board.configure_custom_uart(rx_pin="PA3", tx_pin="PA2", baudrate=9600)

# Write command string
board.uart_write(b"CMD_SET_SPEED:100\r\n")

board.exit()
```
See [examples/uart_write_example.py](examples/uart_write_example.py) for the complete runnable script.

### 10. Dynamic CAN-Bus Read

Configure dynamic CAN RX/TX pins and handle incoming standard or extended frames via a callback.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')
board.samplingOn(50)

def can_callback(msg_id, payload, is_extended):
    print(f"CAN frame - ID: {hex(msg_id)} | Payload: {payload.hex()}")

# Configure CAN at 500kbps on PB12/PB13
board.configure_custom_can(rx_pin="PB12", tx_pin="PB13", baudrate=500000, callback=can_callback)

time.sleep(3)
board.exit()
```
See [examples/can_read_example.py](examples/can_read_example.py) for the complete runnable script.

### 11. Dynamic CAN-Bus Write

Configure dynamic CAN pins and transmit standard (11-bit ID) or extended (29-bit ID) frames.

```python
import time
from roboreactor_firmata import STM32Board

board = STM32Board('/dev/ttyUSB0', mcu='STM32F401RCT6')

# Configure CAN at 500kbps on PB12/PB13
board.configure_custom_can(rx_pin="PB12", tx_pin="PB13", baudrate=500000)

# Send standard CAN frame
board.can_write(msg_id=0x123, data_bytes=[0x01, 0x02, 0x03], is_extended=0)

# Send extended CAN frame
board.can_write(msg_id=0x1F0000, data_bytes=[0xAA, 0xBB, 0xCC], is_extended=1)

board.exit()
```
See [examples/can_write_example.py](examples/can_write_example.py) for the complete runnable script.

### 12. Custom MCU Pin Mapping

For custom boards not natively supported in pin mappings, you can register and inject a custom virtual pin mapping at initialization.

See [examples/custom_mcu_example.py](examples/custom_mcu_example.py) for a complete example showing custom pin maps and simple LED/Servo/Analog callback integration.
```

---

## License

This project is licensed under the MIT License - see the `LICENSE` file for details.
