nimble-service-skill

Create and edit BLE GATT services with NimBLE. Use when creating, editing, or refactoring BLE services, characteristics, descriptors, or callbacks.

16 stars

Best use case

nimble-service-skill is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create and edit BLE GATT services with NimBLE. Use when creating, editing, or refactoring BLE services, characteristics, descriptors, or callbacks.

Teams using nimble-service-skill should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/nimble-service-skill/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/nimble-service-skill/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/nimble-service-skill/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How nimble-service-skill Compares

Feature / Agentnimble-service-skillStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create and edit BLE GATT services with NimBLE. Use when creating, editing, or refactoring BLE services, characteristics, descriptors, or callbacks.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# NimBLE BLE Service Guide

## Authoritative References

Consult these official documents for UUIDs, format values, unit codes, and specifications:

- **[Bluetooth Assigned Numbers](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf)**
- **[GATT Specification Supplement](https://btprodspecificationrefs.blob.core.windows.net/gatt-specification-supplement/GATT_Specification_Supplement.pdf)**
- **[GATT XML Specification Repository](https://github.com/oesmith/gatt-xml)**

For NimBLE-specific methods, enums, and properties, check the NimBLE library headers (e.g., `NimBLECharacteristic.h`, `NimBLE2904.h`) in the project dependencies.

## UUID Conventions

1. Check Bluetooth Assigned Numbers PDF for an official UUID that fits the use case
2. If an official UUID exists and is appropriate, use the short form (e.g., `"180F"`)
3. If no official UUID fits, generate a custom 128-bit UUID:

```bash
python -c "import uuid; print(str(uuid.uuid4()))"
```

## Service Class Template

UUIDs are declared as `static const` members inside the class. This scopes them to the class and prevents naming collisions across libraries.

Variable names should match the UUID constant prefix. For services and characteristics from Bluetooth Assigned Numbers, use their canonical names (e.g., `SERVICE_UUID` → `sensor_service`, `DATA_CHARACTERISTIC_UUID` → `data_characteristic`).

### Header (.h)

```cpp
#ifndef BLE_SENSOR_SERVICE_H
#define BLE_SENSOR_SERVICE_H

#include <NimBLEDevice.h>

class BLESensorServiceClass {
  public:
    static const NimBLEUUID SERVICE_UUID;

    static const NimBLEUUID DATA_CHARACTERISTIC_UUID;
    static const NimBLEUUID CONTROL_CHARACTERISTIC_UUID;

    bool startService();

    NimBLEService* getService() { return sensor_service; }

    bool setData(uint16_t value, bool notify = false);
    bool isDataSubscribed() { return data_subscribed; }
    NimBLECharacteristic* getDataCharacteristic() { return data_characteristic; }

    NimBLECharacteristic* getControlCharacteristic() { return control_characteristic; }

  private:
    NimBLEService* sensor_service = nullptr;

    NimBLECharacteristic* data_characteristic = nullptr;
    NimBLECharacteristic* control_characteristic = nullptr;

    bool data_subscribed = false;

    void createDataCharacteristic();
    void createControlCharacteristic();

    class DataCallbacks;
    class ControlCallbacks;
};

extern BLESensorServiceClass BLESensorService;

#endif

```

### Implementation (.cpp)

```cpp
#include "ble_sensor_service.h"

const NimBLEUUID BLESensorServiceClass::SERVICE_UUID("...");

const NimBLEUUID BLESensorServiceClass::DATA_CHARACTERISTIC_UUID("...");
const NimBLEUUID BLESensorServiceClass::CONTROL_CHARACTERISTIC_UUID("...");

BLESensorServiceClass BLESensorService;

class BLESensorServiceClass::DataCallbacks : public NimBLECharacteristicCallbacks {
  public:
    DataCallbacks(BLESensorServiceClass* pService) : service(pService) {}

    void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override {
      service->data_subscribed = (subValue != 0);

      // Optional: Additional logic
    }

  private:
    BLESensorServiceClass* service;
};

class BLESensorServiceClass::ControlCallbacks : public NimBLECharacteristicCallbacks {
  public:
    void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override {
      NimBLEAttValue value = pCharacteristic->getValue();

      // Process command using value.data() and value.size()
    }
};

bool BLESensorServiceClass::startService() {
  NimBLEServer* pServer = NimBLEDevice::getServer();
  if (pServer == nullptr) return false;

  sensor_service = pServer->getServiceByUUID(SERVICE_UUID);
  if (sensor_service == nullptr) {
    sensor_service = pServer->createService(SERVICE_UUID);
  }

  createDataCharacteristic();
  createControlCharacteristic();

  return sensor_service->start();
}

void BLESensorServiceClass::createDataCharacteristic() {
  if (sensor_service == nullptr) return;

  if (data_characteristic == nullptr) {
    data_characteristic = sensor_service->getCharacteristic(DATA_CHARACTERISTIC_UUID);
    if (data_characteristic == nullptr) {
      data_characteristic = sensor_service->createCharacteristic(
        DATA_CHARACTERISTIC_UUID,
        NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
      );

      data_characteristic->setCallbacks(new DataCallbacks(this));

      NimBLEDescriptor* user_description = data_characteristic->createDescriptor(NimBLEUUID("2901"), NIMBLE_PROPERTY::READ);
      user_description->setValue("Sensor Data");

      NimBLE2904* presentation_format = (NimBLE2904*)data_characteristic->createDescriptor(NimBLEUUID("2904"), NIMBLE_PROPERTY::READ);
      presentation_format->setFormat(NimBLE2904::FORMAT_UINT16);
      presentation_format->setExponent(0x00);
      presentation_format->setUnit(0x2700);
      presentation_format->setNamespace(0x00);
      presentation_format->setDescription(0x0000);

      NimBLEDescriptor* valid_range = data_characteristic->createDescriptor(NimBLEUUID("2906"), NIMBLE_PROPERTY::READ);
      uint16_t range[2] = { 0, 1000 };
      valid_range->setValue((uint8_t*)range, sizeof(range));

      uint16_t initial = 0;
      data_characteristic->setValue((uint8_t*)&initial, sizeof(initial));
    }
  }
}

void BLESensorServiceClass::createControlCharacteristic() {
  if (sensor_service == nullptr) return;

  if (control_characteristic == nullptr) {
    control_characteristic = sensor_service->getCharacteristic(CONTROL_CHARACTERISTIC_UUID);
    if (control_characteristic == nullptr) {
      control_characteristic = sensor_service->createCharacteristic(
        CONTROL_CHARACTERISTIC_UUID,
        NIMBLE_PROPERTY::WRITE
      );

      control_characteristic->setCallbacks(new ControlCallbacks());

      NimBLEDescriptor* user_description = control_characteristic->createDescriptor(NimBLEUUID("2901"), NIMBLE_PROPERTY::READ);
      user_description->setValue("Control");

      NimBLE2904* presentation_format = (NimBLE2904*)control_characteristic->createDescriptor(NimBLEUUID("2904"), NIMBLE_PROPERTY::READ);
      presentation_format->setFormat(NimBLE2904::FORMAT_UINT8);
      presentation_format->setExponent(0x00);
      presentation_format->setUnit(0x2700);
      presentation_format->setNamespace(0x00);
      presentation_format->setDescription(0x0000);
    }
  }
}

bool BLESensorServiceClass::setData(uint16_t value, bool notify) {
  if (data_characteristic == nullptr) return false;

  // Optional: Additional logic (validation, transformation, side effects)

  data_characteristic->setValue((uint8_t*)&value, sizeof(value));
  if (notify) {
    data_characteristic->notify();
  }

  return true;
}

```

## Usage Example

```cpp
// In BLE initialization (after NimBLEDevice::createServer() has been called)
BLESensorService.startService();

// Advertise the service UUID if you want clients to discover this service (Optional)
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(BLESensorServiceClass::SERVICE_UUID);

// Example: Update sensor data from anywhere and optionally notify clients
BLESensorService.setData(123, true);
```

## NimBLE Server Singleton

NimBLE uses a singleton pattern for the BLE server - there is only one server per device. This means:

- **No constructor parameters needed** - services don't require a server pointer to be passed in
- **Global access** - the extern singleton pattern lets you call `BLESensorService.setData(...)` from anywhere
- **Simplified initialization** - just call `startService()` after `NimBLEDevice::createServer()` has been called

## Descriptor Conventions

### Namespace/Description Rule

These fields are linked in the 0x2904 descriptor:

- If `Description = 0x0000` → set `Namespace = 0x00`
- If `Description != 0x0000` (Bluetooth SIG enumeration) → set `Namespace = 0x01`

### Common Format/Unit Combinations

| Data Type | Format | Unit |
| ----------- | -------- | ------ |
| Percentage | `FORMAT_UINT8` | `0x27AD` |
| Acceleration (m/s²) | `FORMAT_FLOAT32` | `0x2713` |
| Angular velocity (rad/s) | `FORMAT_FLOAT32` | `0x2763` |
| Temperature (°C) | `FORMAT_SINT16` | `0x272F` |
| Boolean/Unitless | `FORMAT_BOOLEAN` or `FORMAT_UINT8` | `0x2700` |
| String | `FORMAT_UTF8` | `0x2700` |

## Service Ordering

When adding services to the BLE stack, maintain consistent ordering:

- Core/vital services first (Device Information, Error Report)
- Application-specific services in logical groups
- Utility services that rarely change (OTA) last

This ordering should be consistent across the codebase for predictability.

Related Skills

ocr-web-service-automation

16
from diegosouzapw/awesome-omni-skill

Automate OCR Web Service tasks via Rube MCP (Composio). Always search tools first for current schemas.

moqui-service-writer

16
from diegosouzapw/awesome-omni-skill

This skill should be used when users need to create, validate, or modify Moqui framework services, entities, and queries. It provides comprehensive guidance for writing correct Moqui XML definitions, following framework patterns and conventions.

microservices-orchestrator

16
from diegosouzapw/awesome-omni-skill

Expert skill for designing, decomposing, and managing microservices architectures. Activates when users need help with microservices design, service decomposition, bounded contexts, API contracts, or transitioning from monolithic to microservices architectures.

managed-db-services

16
from diegosouzapw/awesome-omni-skill

Configure DigitalOcean Managed MySQL, MongoDB, Valkey, Kafka, and OpenSearch for App Platform. Use when setting up non-PostgreSQL databases, configuring trusted sources, or troubleshooting database connectivity.

flox-services

16
from diegosouzapw/awesome-omni-skill

Running services and background processes in Flox environments. Use for service configuration, network services, logging, database setup, and service debugging.

effect-layers-services

16
from diegosouzapw/awesome-omni-skill

Define services, provide layers, compose dependencies, and switch live/test. Use for DI boundaries and app composition.

developing-backend-services

16
from diegosouzapw/awesome-omni-skill

Backend service development best practices. Use when designing, building, or reviewing backend services, REST APIs, gRPC services, microservices, webhooks, message queues, or server-side applications regardless of language or framework.

design-microservices

16
from diegosouzapw/awesome-omni-skill

マイクロサービス設計エージェント - ターゲットアーキテクチャ、変換計画、運用計画の策定。/design-microservices [対象パス] で呼び出し。

backend-service-patterns

16
from diegosouzapw/awesome-omni-skill

Architect scalable backend services using layered architecture, dependency injection, middleware patterns, service classes, and separation of concerns. Use when building API services, implementing business logic layers, creating service classes, setting up middleware chains, implementing dependency injection, designing controller-service-repository patterns, handling cross-cutting concerns, creating domain models, implementing CQRS patterns, or establishing backend architecture standards.

azure-servicebus-ts

16
from diegosouzapw/awesome-omni-skill

Build messaging applications using Azure Service Bus SDK for JavaScript (@azure/service-bus). Use when implementing queues, topics/subscriptions, message sessions, dead-letter handling, or enterpri...

azure-messaging-webpubsubservice-py

16
from diegosouzapw/awesome-omni-skill

Azure Web PubSub Service SDK for Python. Use for real-time messaging, WebSocket connections, and pub/sub patterns. Triggers: "azure-messaging-webpubsubservice", "WebPubSubServiceClient", "real-time",

arch-cross-service-integration

16
from diegosouzapw/awesome-omni-skill

Use when designing or implementing cross-service communication, data synchronization, or service boundary patterns.