Skip to content

Instantly share code, notes, and snippets.

@zjwhitehead
Created May 19, 2025 00:29
Show Gist options
  • Save zjwhitehead/8db6f40ea066d17e68e8b0b9469fc0fe to your computer and use it in GitHub Desktop.
Save zjwhitehead/8db6f40ea066d17e68e8b0b9469fc0fe to your computer and use it in GitHub Desktop.

Organizing BLE Services for App Development

Okay, let's break down how we can organize your BLE services and characteristics for the ePPG controller. The goal is to create a logical, robust, and extensible structure that incorporates your desired data points while building upon what you already have in src/sp140/extra-data.ino.

Here's a proposed structure:

I. Standard Services

It's good practice to use standard BLE services where they fit.

1. Device Information Service (UUID: 0x180A)

  • This service provides basic information about the device. It's largely for identification.
  • Characteristics (all Read-Only):
    • Manufacturer Name String (UUID: 0x2A29): Value: "OpenPPG" (Currently in your DEVICE_INFO_SERVICE_UUID)
    • Model Number String (UUID: 0x2A24): Value: e.g., "SP140 Controller" (New - Standard)
    • Serial Number String (UUID: 0x2A25): Value: Unique device serial number (New - Standard)
    • Hardware Revision String (UUID: 0x2A27): Device hardware version. (Currently HW_REVISION_UUID in your CONFIG_SERVICE_UUID - recommend moving here and using standard UUID)
    • Firmware Revision String (UUID: 0x2A26): Device firmware version. (Currently FW_VERSION_UUID in your CONFIG_SERVICE_UUID - recommend moving here and using standard UUID)
    • Software Revision String (UUID: 0x2A28): (Optional) Can be used if you have a separate software version distinct from firmware. (New - Standard)

II. Custom Services

These services will house the application-specific data and controls.

2. Controller Configuration & Status Service

  • This service handles settings configurable by the user, operational status of the hand controller, and sensor data originating directly from it. This largely expands on your existing CONFIG_SERVICE_UUID.
  • Proposed Service UUID: CONFIG_SERVICE_UUID (Existing: 1779A55B-DEB8-4482-A5D1-A12E62146138) - You can keep this UUID.
  • Characteristics:
    • Device State (Existing: DEVICE_STATE_UUID - 8F80BCF5-B58F-4908-B079-E8AD6F5EE257): (uint8, Read/Notify) Current operational state (e.g., Disarmed, Armed, Cruising, Error).
    • Armed Duration (Existing: ARMED_TIME_UUID - 58B29259-43EF-4593-B700-250EC839A2B2): (uint32, Read/Notify) Total time the device has been armed (e.g., in seconds).
    • Throttle Position (Existing: THROTTLE_VALUE_UUID - 50AB3859-9FBF-4D30-BF97-2516EE632FAD): (uint16, Read/Write/Notify/Indicate) Current throttle input value.
    • Screen Rotation (Existing: SCREEN_ROTATION_UUID - 9CBAB736-3705-4ECF-8086-FB7C5FB86282): (uint8, Read/Write) Values: 1 or 3.
    • Sea Level Pressure (Existing: SEA_PRESSURE_UUID - DB47E20E-D8C1-405A-971A-DA0A2DF7E0F6): (float, Read/Write) For barometric altitude calibration (hPa).
    • Metric Temperature Unit (Existing: METRIC_TEMP_UUID - D4962473-A3FB-4754-AD6A-90B079C3FB38): (Boolean, Read/Write) True for Celsius, False for Fahrenheit.
    • Metric Altitude Unit (Existing: METRIC_ALT_UUID - DF63F19E-7295-4A44-A0DC-184D1AFEDDF7): (Boolean, Read/Write) True for meters, False for feet.
    • Performance Mode (Existing: PERFORMANCE_MODE_UUID - D76C2E92-3547-4F5F-AFB4-515C5C08B06B): (uint8, Read/Write) e.g., 0=Chill, 1=Sport.
    • UI Theme (Existing: THEME_UUID - AD0E4309-1EB2-461A-B36C-697B2E1604D2): (uint8, Read/Write) e.g., 0=Light, 1=Dark.
    • Unix Timestamp (Existing: UNIX_TIME_UUID - E09FF0B7-5D02-4FD5-889E-C4251A58D9E7): (uint32, Read/Write) UTC seconds since epoch for time setting.
    • Timezone Offset (Existing: TIMEZONE_UUID - CAE49D1A-7C21-4B0C-8520-416F3EF69DB1): (int32, Read/Write) Offset from UTC in seconds.
    • New Characteristics:
      • CONTROLLER_BARO_TEMP_UUID (New UUID): (float, Read/Notify) Barometric sensor temperature on the controller (°C).
      • CONTROLLER_BARO_PRESSURE_UUID (New UUID): (float, Read/Notify) Raw barometric pressure reading from controller (hPa).
      • CONTROLLER_CPU_TEMP_UUID (New UUID): (float, Read/Notify) Controller's CPU temperature (°C).

3. BMS Telemetry Service

  • This service provides detailed information from the Battery Management System.
  • Service UUID: BMS_TELEMETRY_SERVICE_UUID (Existing: 9E0F2FA3-3F2B-49C0-A6A3-3D8923062133)
  • Characteristics (mostly Read/Notify):
    • BMS State of Charge (SoC) (Existing: BMS_SOC_UUID - ACDEB138-3BD0-4BB3-B159-19F6F70871ED): (float, R/N) Battery percentage (0-100).
    • BMS Bus Voltage (Existing: BMS_VOLTAGE_UUID - AC0768DF-2F49-43D4-B23D-1DC82C90A9E9): (float, R/N) Total battery pack voltage (V).
    • BMS Bus Current (Existing: BMS_CURRENT_UUID - 6FEEC926-BA3C-4E65-BC71-5DB481811186): (float, R/N) Battery current (A), positive for discharge, negative for charge.
    • BMS Power (Existing: BMS_POWER_UUID - 9DEA1343-434F-4555-A0A1-BB43FCBC68A6): (float, R/N) Battery power (W).
    • BMS Highest Cell Voltage (Existing: BMS_HIGH_CELL_UUID - 49267B41-560F-4CFF-ADC8-90EF85D2BE20): (float, R/N) (V).
    • BMS Lowest Cell Voltage (Existing: BMS_LOW_CELL_UUID - B9D01E5C-3751-4092-8B06-6D1FFF479E77): (float, R/N) (V).
    • BMS Highest Temperature (Existing: BMS_HIGH_TEMP_UUID - 0EA08B6D-C905-4D9D-93F8-51E35DA096FC): (float, R/N) (°C).
    • BMS Lowest Temperature (Existing: BMS_LOW_TEMP_UUID - 26CD6E8A-175D-4C8E-B487-DEFF0B034F2A): (float, R/N) (°C).
    • BMS Failure Level (Existing: BMS_FAILURE_LEVEL_UUID - 396C768B-F348-44CC-9D46-92388F25A557): (uint8, R/N) Fault codes or status.
    • BMS Cell Voltage Differential (Existing: BMS_VOLTAGE_DIFF_UUID - 1C45825B-7C81-430B-8D5F-B644FFFC71BB): (float, R/N) Max difference (V).
    • BMS All Cell Voltages (Existing: BMS_CELL_VOLTAGES_UUID - 4337e58b-8462-49b2-b061-c3bf379ef4af): (array of uint16, R/N) Voltages in mV for each cell.
    • New Characteristics:
      • BMS_CHARGE_MOSFET_STATE_UUID (New UUID): (Boolean, R/N) True if charge MOSFET is enabled.
      • BMS_DISCHARGE_MOSFET_STATE_UUID (New UUID): (Boolean, R/N) True if discharge MOSFET is enabled.
      • BMS_CELL_TEMPERATURES_UUID (New UUID): (array of float/int16, R/N) Temperatures for individual cells or sensor groups (T1-T4) (°C).
      • BMS_MOSFET_TEMPERATURE_UUID (New UUID): (float, R/N) BMS internal MOSFET temperature (°C).
      • BMS_BALANCE_STATE_UUID (New UUID): (uintX, R/N) Bitmap or enum indicating which cells (if any) are currently balancing.
      • BMS_CYCLE_COUNT_UUID (New UUID): (uint16, R/N) Number of charge/discharge cycles.
      • BMS_ENERGY_THROUGHPUT_UUID (New UUID): (uint32, R/N) Total energy cycled (e.g., Watt-hours).
      • BMS_BATTERY_IDENTIFIER_UUID (New UUID): (String/Bytes, R) A unique ID for the battery pack, if available.
      • BMS_CHARGING_STATUS_UUID (New UUID): (Boolean, R/N) True if the battery is currently charging.

4. ESC Telemetry Service

  • This service provides detailed information from the Electronic Speed Controller(s).
  • Service UUID: ESC_TELEMETRY_SERVICE_UUID (Existing: C154DAE9-1984-40EA-B20F-5B23F9CBA0A9)
  • Characteristics (mostly Read/Notify):
    • ESC Bus Voltage (Existing: ESC_VOLTAGE_UUID - 0528ecd8-9337-4249-95e4-9aba69f6c1f4): (float, R/N) Voltage at the ESC input (V).
    • ESC Bus Current (Existing: ESC_CURRENT_UUID - 3889e01e-7d2d-4478-b5cc-a06b803e2788): (float, R/N) Current drawn by the ESC (A).
    • ESC Motor RPM (Existing: ESC_RPM_UUID - 24dc4a84-0be3-4eba-a8c3-ed9748daa599): (int32, R/N) Electrical RPM of the motor.
    • ESC Temperatures (Existing: ESC_TEMPS_UUID - d087f190-5450-4fea-b9ff-17133a0b6f64): (struct/byte array, R/N) Contains Mosfet, Capacitor, MCU temperatures. Consider if individual characteristics are preferred by the client app, but a struct is efficient.
      • Alternative/Breakdown of ESC_TEMPS_UUID if preferred:
        • ESC_MOSFET_TEMP_UUID (float, R/N) (°C)
        • ESC_CAPACITOR_TEMP_UUID (float, R/N) (°C)
        • ESC_MCU_TEMP_UUID (float, R/N) (°C)
    • New Characteristics:
      • ESC_OPERATIONAL_STATE_UUID (New UUID): (uint8, R/N) e.g., Idle, Running, Fault, Initialization.
      • ESC_UPTIME_UUID (New UUID): (uint32, R/N) Uptime of the ESC in milliseconds or seconds.
      • ESC_RECEIVED_PWM_UUID (New UUID): (uint16, R/N) PWM signal received by ESC (µs).
      • ESC_COMMUTATION_PWM_UUID (New UUID): (uint16 or float, R/N) Actual PWM sent to motor phases (µs or duty cycle %).
      • ESC_PHASE_CURRENT_UUID (New UUID): (float, R/N) Motor phase current (A) - could be average, max, or per-phase if available.
      • ESC_VOLTAGE_MODULATION_UUID (New UUID): (float, R/N) Voltage modulation percentage (0.0-1.0).
      • ESC_MOTOR_TEMPERATURE_UUID (New UUID): (float, R/N) Temperature from an external motor sensor, if present (°C).

Rationale for this Structure:

  • Logical Grouping: Data is grouped by its source or functional area (Controller, BMS, ESC). This makes it intuitive for client applications to find relevant information.
  • Standardization: Using standard UUIDs for common information (Device Info) improves interoperability.
  • Clarity: Separating configuration/status (Controller service) from pure telemetry (BMS, ESC services) provides a clear distinction.
  • Efficiency: Using Read/Notify for dynamic data ensures the client app gets updates efficiently. Read/Write is used for settings.
  • Scalability: If you add more components in the future (e.g., a second ESC or another sensor module), you can add new services or characteristics within the existing logical framework without making other services too cluttered.
  • Maintainability: This organization should be easier to understand and maintain as your firmware and app evolve.

Next Steps:

  1. Define New UUIDs: Generate unique 128-bit UUIDs for all the "New UUID" characteristics listed above.
  2. Implement in Firmware: Update extra-data.ino (and any other relevant files) to declare these services and characteristics, populate them with data, and handle read/write requests and notifications.
  3. Client App Development: Your BLE app will then connect to these services and subscribe to/read these characteristics based on this defined structure.

This organization should provide a solid foundation for your BLE communication! Let me know if you have any questions or want to refine any part of this.

Review

You're right to want to scrutinize the design – that's key to building a robust system! Let's collaborate and weigh the pros and cons of the proposed BLE structure.

Overall, the proposed structure is strong because it emphasizes:

  • Clarity and Modularity: Grouping characteristics by their source (Controller, BMS, ESC) and function (Configuration, Telemetry) makes the system intuitive. When you need BMS data, you know which service to look for.
  • Standardization: Leveraging the standard Device Information Service is good for interoperability with generic BLE scanner apps and future-proofing.
  • Scalability: If you add a new sensor or component (e.g., a secondary ESC, an ambient light sensor), you can encapsulate its data within a new service or add characteristics to an existing relevant service without a major overhaul.
  • Efficiency of Updates: Using Notify for frequently changing telemetry data is efficient, pushing updates to the client when they occur. Read/Write for configuration is appropriate.
  • Separation of Concerns: Distinguishing between user-configurable settings (like Screen Rotation) and read-only telemetry (like BMS Voltage) is a sound design principle.
  • Comprehensiveness: It aims to include all the data points you've listed.

However, here are some potential trade-offs, considerations, and points where we could be "on the fence" or might adjust based on specific needs:

  1. Granularity vs. Payload Size/Number of Characteristics:

    • Example: ESC Temperatures (Mosfet, Capacitor, MCU)
      • Current in extra-data.ino & My Proposal: One characteristic (ESC_TEMPS_UUID) that sends a struct or byte array containing all three temperatures.
        • Pro: Fewer characteristics in the GATT table. Potentially one slightly larger packet for all three if they update together, which can be efficient.
        • Con: The client always receives all three, even if it only cares about one. The client needs to parse the struct/array. If only one temperature changes frequently and others are stable, you're still sending the stable data.
      • Alternative: Three separate characteristics (e.g., ESC_MOSFET_TEMP_UUID, ESC_CAPACITOR_TEMP_UUID, ESC_MCU_TEMP_UUID).
        • Pro: Client can subscribe to or read only the specific temperature(s) it needs. Notifications are only sent for the temperature that changed. Simpler for the client as it gets a direct float.
        • Con: More characteristics to define and manage. If all three change simultaneously, it's three small notifications instead of one larger one.
    • Decision Point: How will the client app typically use this data? If it always displays all ESC temps together, the struct is fine. If it has separate displays, alerts, or logic for individual temps, separate characteristics might be better.
    • This applies similarly to: BMS_CELL_VOLTAGES_UUID and the new BMS_CELL_TEMPERATURES_UUID. For cell voltages/temperatures, an array is generally a good compromise because you usually want all of them, and having 16+ individual characteristics would be too many.
  2. Total Number of Services and Characteristics:

    • Pro: Provides detailed and well-organized access to a lot of data.
    • Con: A larger GATT table means slightly longer service discovery times for the client on initial connection (though usually negligible for modern devices). It also means more UUIDs to manage and more client-side code to handle them.
    • Consideration: Is every single data point absolutely necessary for the initial BLE app? Could some be deferred if they are "nice-to-haves" rather than "must-haves" to simplify the first iteration? (Though your list seems pretty core).
  3. Data Types and Precision:

    • Consideration: We've used float, uint16_t, uint32_t, etc. These are generally good. Ensure the chosen type provides enough range and precision without wasting bytes. For example, if a temperature never exceeds 255 and only needs integer precision, a uint8_t could save space over a float, but float is often more convenient for temperatures. Your current choices in extra-data.ino seem sensible.
  4. Notification Frequency and Data Volatility:

    • Consideration: For very rapidly changing data (e.g., Throttle Position if updated on every ADC read, or BMS Bus Current), sending a BLE notification for every single minute change can flood the BLE link and drain battery on both the controller and the connected device.
    • Mitigation (Application Level): The BLE structure itself doesn't dictate this, but the firmware implementation should consider:
      • Sending notifications only when the value changes by a certain threshold (hysteresis).
      • Sending notifications at a maximum fixed rate (e.g., 5-10 Hz) even if the underlying data changes faster.
      • The current delay(5) in updateThrottleBLE is a simple form of rate limiting.
  5. Command vs. Configuration Characteristics:

    • Consideration: Your WebSerial parse_serial_commands handles "commands" like "reboot" and "sync". For BLE, most interactions are direct characteristic reads/writes. If you needed explicit "commands" via BLE (e.g., "Start Motor Test Sequence," "Factory Reset"), you might define a specific "Command Characteristic" (Write-Only) and a corresponding "Response Characteristic" (Notify). For your current list, direct setting/getting of values seems appropriate.
  6. Moving Existing Characteristics:

    • I suggested moving FW_VERSION_UUID and HW_REVISION_UUID from your custom CONFIG_SERVICE_UUID to the standard Device Information Service (using standard UUIDs 0x2A26 and 0x2A27).
      • Pro: Adherence to BLE standards, better interoperability.
      • Con: A minor refactoring in your code. Requires client apps to look for this info in a different place if they were already using your custom UUIDs. (This is a good change for long-term health).

My "On the Fence" Point / Key Collaboration Area:

The main area I'd be keen to discuss further with you is the granularity of data within a service, like the ESC Temperatures example. The current combined struct in extra-data.ino is a valid approach. Breaking it out offers more flexibility to the client at the cost of more characteristics.

To help decide on granularity, consider:

  • Client Application UI/UX: Will the app show these pieces of data independently? Will it trigger different actions based on individual values within a group?
  • Data Volatility: Do items within a group change at very different rates?
  • Bandwidth/Battery Concerns: Is minimizing the number of notifications paramount, even if it means sending slightly more data per notification? Or is sending only the changed data more critical?

For BMS_CELL_VOLTAGES, sending an array (as you do) is almost certainly the best approach. For ESC_TEMPS, it's more of a judgment call based on anticipated client needs. The current struct method is perfectly fine and common.

What are your thoughts on these points, especially the granularity of grouped data like the ESC temperatures? Knowing how you envision the client app interacting with this data would be helpful.

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