Skip to content

Grove driver#532

Open
KenVanHoeylandt wants to merge 1 commit into
mainfrom
grove-driver
Open

Grove driver#532
KenVanHoeylandt wants to merge 1 commit into
mainfrom
grove-driver

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added support for ESP32 Grove devices with configurable pin mappings and communication modes
    • Enabled dynamic switching between UART and I2C protocols for Grove ports
    • Added device configuration through device tree bindings with customizable clock frequency and port settings

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new Grove driver for ESP32 that enables dynamic switching between UART and I2C communication modes. The implementation follows a layered architecture: a public GroveMode enumeration and GroveApi interface define the driver contract in TactilityKernel; an ESP32-specific binding declares the device tree compatible string and required properties (pins, ports, clock frequency); the ESP32 driver implementation manages a child device whose configuration changes when the mode switches; finally, the driver is wired into the platform module's startup and shutdown sequences. The driver uses nothrow allocation and proper cleanup paths for error handling.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Grove driver' accurately describes the main change: adding a new Grove driver with supporting bindings, configuration structs, and driver implementation across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch grove-driver

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8de92cb0-4616-4082-aa46-e22743183e0b

📥 Commits

Reviewing files that changed from the base of the PR and between 8dd9bee and 5f52372.

📒 Files selected for processing (7)
  • Platforms/platform-esp32/bindings/espressif,esp32-grove.yaml
  • Platforms/platform-esp32/include/tactility/bindings/esp32_grove.h
  • Platforms/platform-esp32/include/tactility/drivers/esp32_grove.h
  • Platforms/platform-esp32/source/drivers/esp32_grove.cpp
  • Platforms/platform-esp32/source/module.cpp
  • TactilityKernel/include/tactility/drivers/grove.h
  • TactilityKernel/source/drivers/grove.cpp

defaultMode:
type: int
required: true
description: "One of enum Esp32GroveMode"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent enum name in documentation.

The description references Esp32GroveMode, but the actual enum is GroveMode (defined in TactilityKernel/include/tactility/drivers/grove.h).

📝 Proposed fix
-    description: "One of enum Esp32GroveMode"
+    description: "One of enum GroveMode"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "One of enum Esp32GroveMode"
description: "One of enum GroveMode"

Comment on lines +29 to +67
static error_t stop_child(struct Device* device) {
auto* data = GET_DATA(device);
if (!data) return ERROR_NONE;

if (data->child_device) {
if (data->child_device->internal) {
if (device_is_added(data->child_device)) {
if (device_is_ready(data->child_device)) {
if (device_stop(data->child_device) != ERROR_NONE) {
LOG_E(TAG, "%s: failed to stop child device", device->name);
return ERROR_RESOURCE;
}
}
if (device_remove(data->child_device) != ERROR_NONE) {
LOG_E(TAG, "%s: failed to remove child device", device->name);
return ERROR_RESOURCE;
}
}
check(device_destruct(data->child_device) == ERROR_NONE);
}
delete data->child_device;
data->child_device = nullptr;
}

if (data->child_config) {
if (data->current_mode == GROVE_MODE_UART) {
delete (struct Esp32UartConfig*)data->child_config;
} else if (data->current_mode == GROVE_MODE_I2C) {
delete (struct Esp32I2cConfig*)data->child_config;
}
data->child_config = nullptr;
}

delete[] data->child_name;
data->child_name = nullptr;

data->current_mode = GROVE_MODE_DISABLED;
return ERROR_NONE;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Resource leak: stop_child early return leaves child resources allocated.

When device_stop (line 37) or device_remove (line 42) fails, stop_child returns ERROR_RESOURCE without cleaning up child_device, child_config, or child_name. Since stop_device (line 168) ignores the error return and unconditionally deletes data, the pointers to these allocated resources are lost, causing a leak.

Impact: If the child device enters a failure state where it cannot be stopped or removed, the Grove driver will leak memory on every subsequent mode change or device shutdown.

♻️ Recommended fix: Best-effort cleanup in `stop_child`

Continue cleanup even if device_stop or device_remove fails. Log errors but don't abort the cleanup sequence:

                 if (device_is_ready(data->child_device)) {
-                    if (device_stop(data->child_device) != ERROR_NONE) {
+                    error_t stop_err = device_stop(data->child_device);
+                    if (stop_err != ERROR_NONE) {
                         LOG_E(TAG, "%s: failed to stop child device", device->name);
-                        return ERROR_RESOURCE;
+                        // Continue with cleanup
                     }
                 }
-                if (device_remove(data->child_device) != ERROR_NONE) {
+                error_t remove_err = device_remove(data->child_device);
+                if (remove_err != ERROR_NONE) {
                     LOG_E(TAG, "%s: failed to remove child device", device->name);
-                    return ERROR_RESOURCE;
+                    // Continue with cleanup
                 }
             }

Also propagate the error from stop_child in stop_device:

 static error_t stop_device(struct Device* device) {
     auto* data = GET_DATA(device);
     if (!data) return ERROR_NONE;

-    stop_child(device);
+    error_t err = stop_child(device);
     delete data;
     device_set_driver_data(device, nullptr);

-    return ERROR_NONE;
+    return err;
 }

Comment on lines +53 to +60
if (data->child_config) {
if (data->current_mode == GROVE_MODE_UART) {
delete (struct Esp32UartConfig*)data->child_config;
} else if (data->current_mode == GROVE_MODE_I2C) {
delete (struct Esp32I2cConfig*)data->child_config;
}
data->child_config = nullptr;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical memory leak: child_config not deleted when current_mode is GROVE_MODE_DISABLED.

When start_child fails after allocating child_config (line 113 for UART or line 130 for I2C) but before setting current_mode (line 151), the internally-called stop_child (line 147) will leak child_config:

Failure scenario:

  1. start_child(device, GROVE_MODE_UART) is called
  2. child_device, child_name, and uart_cfg are successfully allocated (lines 79–113)
  3. device_construct_add_start fails (line 144)
  4. stop_child is called (line 147)
  5. At this point, current_mode is still GROVE_MODE_DISABLED (line 151 never executed)
  6. In stop_child, line 53 condition is true (child_config is not nullptr)
  7. Lines 54 and 56 both evaluate to false (current_mode is DISABLED, not UART or I2C)
  8. Line 59 sets child_config = nullptr without deleting the allocated config → memory leak
🔧 Proposed fix: Set `current_mode` before calling `device_construct_add_start`

Move the current_mode assignment to immediately after allocating and assigning child_config, so stop_child can correctly identify the type even if device_construct_add_start fails:

For UART mode:

     uart_cfg->pin_cts = GPIO_PIN_SPEC_NONE;
     uart_cfg->pin_rts = GPIO_PIN_SPEC_NONE;
     data->child_config = uart_cfg;
+    data->current_mode = GROVE_MODE_UART;
     compatible = "espressif,esp32-uart";
     LOG_I(TAG, "%s: starting UART mode on port %d", device->name, (int)uart_cfg->port);

For I2C mode:

     i2c_cfg->pinSda = config->pinSdaRx;
     i2c_cfg->pinScl = config->pinSclTx;
     data->child_config = i2c_cfg;
+    data->current_mode = GROVE_MODE_I2C;
     compatible = "espressif,esp32-i2c";
     LOG_I(TAG, "%s: starting I2C mode on port %d", device->name, (int)i2c_cfg->port);

Then remove the assignment at the end:

     if (err != ERROR_NONE) {
         LOG_E(TAG, "%s: failed to start child device: %d", device->name, err);
         stop_child(device);
         return err;
     }

-    data->current_mode = mode;
     return ERROR_NONE;
 }

Comment on lines +155 to +162
static error_t start_device(struct Device* device) {
const auto* config = GET_CONFIG(device);
auto* data = new(std::nothrow) Esp32GroveInternal();
if (!data) return ERROR_OUT_OF_MEMORY;
device_set_driver_data(device, data);

return start_child(device, config->defaultMode);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Memory leak: data not freed when start_child fails.

If start_child fails (line 161), start_device returns the error but leaves the allocated Esp32GroveInternal instance in driver_data. Assuming the device framework does not call stop_device when start_device fails (typical driver lifecycle), this leaks data.

🛠️ Proposed fix: Clean up `data` on `start_child` failure
 static error_t start_device(struct Device* device) {
     const auto* config = GET_CONFIG(device);
     auto* data = new(std::nothrow) Esp32GroveInternal();
     if (!data) return ERROR_OUT_OF_MEMORY;
     device_set_driver_data(device, data);

-    return start_child(device, config->defaultMode);
+    error_t err = start_child(device, config->defaultMode);
+    if (err != ERROR_NONE) {
+        delete data;
+        device_set_driver_data(device, nullptr);
+    }
+    return err;
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant