-
Notifications
You must be signed in to change notification settings - Fork 396
Added m8-controller-box dir with examples #826
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
base: main
Are you sure you want to change the base?
Changes from all commits
dfdd1b1
1aeab25
de4830a
8f4ce81
767ae68
a33004f
66faf3c
f5c94f1
2509017
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this PR creates a new top-level directory we need to also add it to the first page README here |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| # M8 Controller Box (PR1) | ||
|
|
||
| The **M8 Controller Box (PR1)** is a USB-connected expansion module designed for integrating CAN devices, GPIO, relays, and peripheral interfaces into OAK-based systems. | ||
|
|
||
| This document serves as the **primary usage reference** for the M8 Controller Box within `oak-examples`. | ||
|
|
||
| > **Note:** The examples in this directory are intended to run on **OAK4 in standalone mode**. The M8 Controller Box connects directly to the OAK4 device, so these examples are not supported in host-driven peripheral mode. | ||
|
|
||
|
|
||
| ## Key Features | ||
|
|
||
| ### CAN Interface | ||
|
|
||
| * Supports **CAN 2.0A and 2.0B** | ||
| * Baud rates up to **1 Mbps** | ||
| * Native **Linux SocketCAN** interface | ||
|
|
||
| > For more information look at [can-example](./can-example/) | ||
|
|
||
| ### USB Audio | ||
|
|
||
| * Integrated **buzzer / 3.5mm audio output** | ||
| * Available via internal USB connection | ||
|
|
||
| > For more information look at the [simple-example](./simple-example/) | ||
|
|
||
| ### USB Expansion | ||
|
|
||
| * **2× USB-A ports** | ||
| * USB 2.0 speeds | ||
| * Up to **500mA current limit (shared)** | ||
|
|
||
|
|
||
| ### Serial Interface | ||
|
|
||
| * **1× RS232 interface** | ||
|
|
||
| > See [simple-example](./simple-example/) and modify `main.py` with [library example](https://github.com/luxonis/rp2040_u2if/blob/main/examples/ControllerBox/example_controller_box_serial.py). | ||
|
|
||
| ### Isolated Strobe Driver | ||
|
|
||
| * Supports **5–24V strobe lights** | ||
| * Electrically isolated output | ||
|
|
||
| > For more information look at the [strobe-relay example](./strobe-relay-example/) | ||
|
|
||
| ### GPIO | ||
|
|
||
| * **16× GPIO pins** | ||
| * 3.3V logic level | ||
| * Reverse voltage protection and ESD protection | ||
| * Configurable as input or output | ||
|
|
||
| **Electrical limits:** | ||
|
|
||
| * Total combined current must not exceed **50mA** | ||
|
|
||
| > For more information look at the [simple-example](./simple-example/) | ||
|
|
||
| ### Power Relays | ||
|
|
||
| * **4× SPDT latching relays** | ||
| * Up to **16A current** | ||
| * Maximum **400VAC switching voltage** | ||
|
|
||
| > For relay control, see the [strobe-relay example](./strobe-relay-example/). | ||
|
|
||
| ### User Interface | ||
|
|
||
| * **3× physical buttons** | ||
| * **3× status LEDs** | ||
|
|
||
| > For more example look at the [simple-example](./simple-example/) | ||
|
|
||
| ## Pinout | ||
|
|
||
| Device-level pinout is shown below: | ||
|
|
||
|  | ||
|
|
||
|
|
||
|
|
||
| ## Example Applications | ||
|
|
||
| The repository includes reference applications demonstrating typical usage. | ||
|
|
||
| ### [simple-example](./simple-example/) | ||
|
|
||
| * Blinks LED 1 | ||
| * Button 2 toggles LED 2 | ||
|
|
||
|
|
||
| ### [depthai-example](./depthai-example/) | ||
|
|
||
| * Based on Luxonis hand pose detection | ||
| * Turns on LED 1 when a hand is detected | ||
|
|
||
|
|
||
| ### [can-example](./can-example/) | ||
|
|
||
| * Monitors button 1 | ||
| * Sends CAN frame on press | ||
| * Uses Linux SocketCAN (`python-can`) | ||
|
|
||
| ### [strobe-relay-example](./strobe-relay-example/) | ||
|
|
||
| * Detects barcodes using `pyzbar` | ||
| * On barcode detection it switches relay | ||
|
|
||
| ## Running The Examples | ||
|
|
||
| All examples in this directory should be run as OAK apps on the OAK4 device. | ||
|
|
||
| 1. Install `oakctl` by following the instructions [here](https://docs.luxonis.com/software-v3/oak-apps/oakctl). | ||
| 2. Change into the example directory you want to run. | ||
| 3. Connect to the device and start the app: | ||
|
|
||
| ```bash | ||
| oakctl connect <DEVICE_IP> | ||
| oakctl app run . | ||
| ``` | ||
|
|
||
| If you have a locally connected device, `oakctl app run .` may be enough. Use `oakctl connect <DEVICE_IP>` when targeting a device over the network or when multiple devices are available. | ||
|
|
||
| ## Notes | ||
|
|
||
| * GPIO and peripheral control is exposed via the **u2if (USB-to-interfaces) protocol** | ||
| * Example applications demonstrate recommended interaction patterns | ||
| * Library repository: [rp2040_u2if](https://github.com/luxonis/rp2040_u2if) - Note, library must be used within OAK App, to run on the device - running these on Host will result in following error: | ||
| ``` | ||
| File "rp2040_u2if\examples\example_simple.py", line 5, in <module> | ||
|
|
||
| rp2040.open() | ||
|
|
||
| ~~~~~~~~~~~^^ | ||
|
|
||
| File ".venv\Lib\site-packages\luxonis_u2if\rp2040_u2if.py", line 154, in open | ||
|
|
||
| self._hid.open(self._vid, self._pid, self._serial) | ||
|
|
||
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
||
| File "hid.pyx", line 143, in hid.device.open | ||
|
|
||
| OSError: open failed | ||
|
|
||
| Process finished with exit code 1 | ||
| ``` | ||
|
|
||
|
|
||
| ## Support | ||
|
|
||
| For integration support or early access features, contact support@luxonis.com |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Python virtual environments | ||
| venv/ | ||
| .venv/ | ||
|
|
||
| # Node.js | ||
| # ignore node_modules, it will be reinstalled in the container | ||
| node_modules/ | ||
|
|
||
| # Multimedia files | ||
| media/ | ||
|
|
||
| # Documentation | ||
| README.md | ||
|
|
||
| # VCS | ||
| .git/ | ||
| .github/ | ||
| .gitlab/ | ||
|
|
||
| # The following files are ignored by default | ||
| # uncomment a line if you explicitly need it | ||
|
|
||
| # !*.oakapp | ||
|
|
||
| # Python | ||
| # !**/.mypy_cache/ | ||
| # !**/.ruff_cache/ | ||
|
|
||
| # IDE files | ||
| # !**/.idea | ||
| # !**/.vscode | ||
| # !**/.zed | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # M8 CAN Transmission Example (Button Triggered) | ||
|
|
||
| This example demonstrates how to send data over the **CAN bus** from the M8 Controller Box by pressing a physical button. | ||
|
|
||
| The application runs inside a container directly on the device and uses `python-can` with the Linux SocketCAN interface. | ||
|
|
||
| > **Note:** This example works only on OAK4 in standalone mode. It is not supported in host-driven peripheral mode because the M8 Controller Box is connected directly to the OAK4 device. | ||
|
|
||
| ## Functionality | ||
|
|
||
| This example performs the following actions: | ||
|
|
||
| - Monitors the **button 1**. | ||
| - When the button is pressed, a CAN frame is transmitted over the M8 CAN interface (`can0`). | ||
|
|
||
| This provides a minimal, practical reference for sending CAN messages from a containerized application running on the OAK4 device. | ||
|
|
||
| ## Usage | ||
|
|
||
| Running this example requires an OAK4 device with the M8 Controller Box attached. | ||
|
|
||
| ## Standalone Mode (RVC4 only) | ||
|
|
||
| Install `oakctl` by following the instructions [here](https://docs.luxonis.com/software-v3/oak-apps/oakctl). | ||
|
|
||
| Then run the example from this directory: | ||
|
|
||
| ```bash | ||
| oakctl connect <DEVICE_IP> | ||
| oakctl app run . | ||
| ``` | ||
|
|
||
| This builds the app from the local `oakapp.toml`, deploys it to the OAK4 device, and starts it in standalone mode. | ||
|
|
||
| ## Peripheral Mode | ||
|
|
||
| This mode is not supported for this example. The M8 Controller Box must be attached directly to the OAK4 device running the app. | ||
|
|
||
| ## CAN Interface Setup (Required) | ||
|
|
||
| Before running the application, the CAN interface must be configured on the target device where CAN messages should be transmitted. | ||
|
|
||
| Run the following commands on the device: | ||
|
|
||
| ```bash | ||
| ip link set can0 type can bitrate 500000 | ||
| ip link set can0 up | ||
| ``` | ||
|
|
||
| This: | ||
|
|
||
| - Configures the CAN bitrate to **500 kbps** | ||
| - Brings the `can0` interface online | ||
|
|
||
| The bitrate must match the rest of the CAN network. | ||
|
|
||
| ## Monitoring CAN Traffic | ||
|
|
||
| To verify transmission, you can listen to CAN traffic on a Linux system using: | ||
|
|
||
| ```bash | ||
| candump can0 | ||
| ``` | ||
|
|
||
| When the **button 1** is pressed, a CAN frame will appear on the bus. | ||
|
|
||
| ## Use Case | ||
|
|
||
| This example serves as a minimal reference implementation for: | ||
|
|
||
| - Sending CAN messages from the M8 Controller Box | ||
| - Integrating containerized applications with CAN-based systems | ||
| - Trigger-based CAN communication using GPIO inputs | ||
|
|
||
| It can be extended to transmit structured data, control messages, or sensor outputs over CAN. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #!/bin/sh | ||
| echo "Starting Backend" | ||
| exec python3.12 -u /app/main.py |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| """ | ||
| Controller Box CAN Button IRQ Example | ||
| ------------------------------------ | ||
|
|
||
| Demonstrates how to send a CAN frame using | ||
| ControllerBox button events without polling. | ||
| Uses GPIO interrupts instead of continuous polling. | ||
| """ | ||
|
|
||
| import time | ||
| import can | ||
| from luxonis_u2if import ControllerBox | ||
|
|
||
|
|
||
| # ------------------------------------------------------------ | ||
| # Connect to ControllerBox device | ||
| # ------------------------------------------------------------ | ||
|
|
||
| box = ControllerBox() | ||
|
|
||
|
|
||
| # ------------------------------------------------------------ | ||
| # CAN configuration | ||
| # ------------------------------------------------------------ | ||
|
|
||
| CAN_IFACE = "can0" | ||
| CAN_ID = 0x123 | ||
| CAN_DATA = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] | ||
|
|
||
| bus = can.interface.Bus(channel=CAN_IFACE, interface="socketcan") | ||
|
|
||
|
|
||
| def send_can_frame(): | ||
| """ | ||
| Send a predefined CAN frame. | ||
| """ | ||
| msg = can.Message( | ||
| arbitration_id=CAN_ID, | ||
| data=CAN_DATA, | ||
| is_extended_id=False, | ||
| ) | ||
| bus.send(msg) | ||
| print(f"Sent CAN: {CAN_IFACE} id=0x{CAN_ID:X} data={CAN_DATA}") | ||
|
|
||
|
|
||
| # ------------------------------------------------------------ | ||
| # Button callback | ||
| # ------------------------------------------------------------ | ||
|
|
||
| BUTTON_INDEX = 1 # Button 1 | ||
| button_pin = ControllerBox.BUTTON_PINS[BUTTON_INDEX - 1] | ||
|
|
||
| def button_cb(btn, state): | ||
| """ | ||
| Triggered on button press/release. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| btn : int | ||
| Button index (1..3) | ||
| state : bool | ||
| True = pressed, False = released | ||
| """ | ||
| if btn == BUTTON_INDEX and state: # pressed | ||
| try: | ||
| send_can_frame() | ||
| except Exception as e: | ||
| print(f"[ERROR] Failed to send CAN frame: {e}") | ||
|
|
||
|
|
||
| # ------------------------------------------------------------ | ||
| # Register callback | ||
| # ------------------------------------------------------------ | ||
|
|
||
| box.set_btn_callback(button_cb) | ||
|
|
||
|
|
||
| print("ControllerBox ready") | ||
| print("Press Button 1 to send CAN frame") | ||
|
|
||
|
|
||
| # ------------------------------------------------------------ | ||
| # Main loop | ||
| # ------------------------------------------------------------ | ||
|
|
||
| # Nothing required here — button events handled via IRQ | ||
| while True: | ||
| time.sleep(1) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| identifier = "com.example.m8-controller-box.can-example" | ||
| entrypoint = ["bash", "-c", "/usr/bin/runsvdir -P /etc/service"] | ||
| app_version = "1.0.2" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems strange that we start of with |
||
| assign_frontend_port = true | ||
|
|
||
| prepare_container = [ | ||
| { type = "COPY", source = "./requirements.txt", target = "./requirements.txt" }, | ||
| { type = "RUN", command = "python3.12 -m pip install -r /app/requirements.txt --break-system-packages" }, | ||
| { type = "RUN", command = "bash -lc 'set -e; apt-get update && apt-get install -y libhidapi-hidraw0 libhidapi-libusb0'" }, | ||
| ] | ||
|
|
||
| build_steps = [ | ||
| "mkdir -p /etc/service/backend", | ||
| "cp /app/backend-run.sh /etc/service/backend/run", | ||
| "chmod +x /etc/service/backend/run", | ||
| ] | ||
|
|
||
| allowed_devices = [{ allow = true, access = "rwm" }] | ||
|
|
||
| additional_mounts = [ | ||
| { source = "/dev", target = "/dev", type = "devtmpfs", options = [ | ||
| "mode=777", | ||
| ] }, | ||
| ] | ||
|
|
||
| [base_image] | ||
| api_url = "https://registry-1.docker.io" | ||
| service = "registry.docker.io" | ||
| oauth_url = "https://auth.docker.io/token" | ||
| auth_type = "repository" | ||
| auth_name = "luxonis/oakapp-base" | ||
| image_name = "luxonis/oakapp-base" | ||
| image_tag = "1.2.6" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| hidapi | ||
| python-can | ||
| git+https://github.com/luxonis/rp2040_u2if.git |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest having a README that is more similar to other READMEs we have in other top level folders (e.g here or here etc.). Though since this does have some additional information about the actual interfaces we could do a merge of the two styles but I would still prefer to have this table towards the top of the README so that user can quickly jump to the example that suits them.