# Nimble Service

> Create, edit, refactor, or review BLE GATT services with NimBLE. Use when creating, editing, refactoring, or reviewing BLE services, characteristics, descriptors, or callbacks.

- Skill: `leeornahum/nimble-service` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add leeornahum/nimble-service`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leeornahum/nimble-service/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: LeeorNahum (https://skillmd.com/u/leeornahum)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/leeornahum/nimble-service

---


# 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

Copy `assets/service-template.h` and `assets/service-template.cpp` as the starting point for a new service, then rename `BLESensorService`, `BLESensorServiceClass`, and `ble_sensor_service.h` to match the new service, fill in real UUIDs, and add the characteristics the service actually needs.

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`).

## NimBLE Server Singleton

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

- **Parameterless construction**: obtain the server through `NimBLEDevice::getServer()`, so services do not need a server pointer 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`

### Presentation Format Selection for Multi-Field Struct Payloads

The 0x2904 `Format` field only describes the first logical value in the payload. It cannot describe a whole packed struct. Despite this, the format choice has a real practical effect in tools like nRF Connect:

- `FORMAT_FLOAT32` → nRF Connect renders the first 4 bytes as a human-readable float. For a struct whose first field is a float, this is immediately readable during testing even though the remaining fields are not displayed. **Prefer this for data structs where the first field is the most informative single value.**
- `FORMAT_OPAQUE` → nRF Connect renders all bytes as a raw hex dump. Technically more "correct" for a packed struct, but makes the payload completely unreadable during development. **Reserve this for payloads that are genuinely opaque to humans (e.g. bitmasks, binary command packets, image data).**

Rule of thumb: if a developer looking at nRF Connect during testing would benefit from seeing the first field as a formatted number, use the format of that first field (usually `FORMAT_FLOAT32` or `FORMAT_UINT8`). Use `FORMAT_OPAQUE` only when no human-readable display is useful.

### 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.

