Debugging BLE macros on Nature Remo was such an ordeal that I built a Swift development environment

TL;DR I have prepared an environment for developing a subset of Android nRF Connect BLE macros on macOS, iOS, and other Apple platforms. Kuniwak / swift-ble-macro BLE (Bluetooth Low Energy) Macros for Swift swift-ble-macro This is a simple Swift library that allows you to easily create nRF Connect BLE macros 1 for your iOS/iPadOS/macOS/watchOS/visionOS/tvOS app It is a wrapper around Core Bluetooth that allows you to create macros for BLE devices. It is designed to easily develop Nature Remo compatible BLE macros2. Installation Add the following to your Package.swift file: .package(url: "https://github.com/Kuniwak/swift-ble-macro.git", from: "") Products Product Description BLEMacroEasy Defines the easy interface to execute BLE macros. BLEMacro Defines the BLE macro XML parser. BLEMacroCompiler Defines the BLE macro compiler that compile BLE macro XML to BLE macro IR. BLECommand Defines the BLE macro IR. BLEInterpreter Defines the BLE macro interpreter that interprets BLE macro IR. BLEInternal Defines the utilities for BLE macros. BLEModel Defines the convenient state machines for BLE. Supported Macros Currently, only the following BLE macro XML elements… View on GitHub import Foundation import BLEMacroEasy // You can find your iPhone’s UUID by running the commands below in Terminal: // $ git clone https://github.com/Kuniwak/swift-ble-macro // $ cd swift-ble-macro // $ swift run ble discover let myIPhoneUUID = UUID(uuidString: "********-****-****-****-************")! let myMacro = try String(contentsOf: URL(string: "https://ble-macro.kuniwak.com/iphone/battery-level.xml")!) try await run(macroXMLString: myMacro, on: myIPhoneUUID) { data in // This handler is called every time a value is read from the peripheral. let batteryLevel = Int(data[0]) print("\(batteryLevel)%") } Besides the ability to run BLE macros on macOS/iOS/…, the project also provides a CLI that helps you develop those macros. For the macro-execution API, see the README. The CLI supports device scanning, macro validation, macro execution, and an interactive REPL: $ # Scan BLE devices $ ble discover 00000000-0000-0000-0000-000000000000 Example Device 1 -78 11111111-1111-1111-1111-111111111111 Example Device 2 -47 22222222-2222-2222-2222-222222222222 Example Device 3 -54 ... $ # Abort scanning with Ctrl+C $ # Run a BLE macro $ ble run path/to/your/ble-macro.xml --uuid 00000000-0000-0000-0000-000000000000 $ # Run a BLE macro interactively $ ble repl --uuid 00000000-0000-0000-0000-000000000000 connecting... connected (ble) ? write-command, w, wc Write to a characteristic without a response write-descriptor, wd Write to a descriptor write-request, req Write to a characteristic with a response read, r Read from a characteristic discovery-service, ds Discover services discovery-characteristics, dc Discover characteristics discovery-descriptor, dd Discover descriptors q, quit Quit the REPL (ble) dc 180A 2A29 read 180A 2A24 read D0611E78-BBB4-4591-A5F8-487910AE4366 8667556C-9A37-4C91-84ED-54EE27D90049 write/write/notify/extendedProperties 9FA480E0-4967-4542-9390-D343DC5D04AE AF0BADB1-5B99-43CD-917A-A77BC549E3CC write/write/notify/extendedProperties 180F 2A19 read/notify 1805 2A2B read/notify 1805 2A0F readk (ble) r 180F 2A19 58 The BLE macros I have written are published at Kuniwak/ble-macro. Background Nature Remo recently gained a Bluetooth Low Energy (BLE) macro feature (official announcement). If you own a compatible model (Nature Remo 3 or Nature Remo mini 2 family), you can control BLE devices directly from your Remo. I therefore started writing BLE macros so that our three full-colour Philips Hue bulbs could be driven from Remo (I have always had a faint admiration for scenes like this video, hence the extravagant colours). The macros supported by Remo are a subset of those used by Android nRF Connect. Macros are written in XML. For example, to turn a Hue bulb on in daylight-white you can write: With BLE macros you instruct read/write operations against services and characteristics. For well-known devices you can often find the details thanks to specifications reverse-engineered by third parties, but bringing a hand-written macro to a “working on Remo” state still requires a fair bit of trial and error. And that trial and error was utterly painful. According to the documentation, Android users can rely on the nRF Connect for Mobile app, which supports everything from recording to running BLE macros, making development easy. Unfortunately I had no Android device at hand. There is an iOS version by the same developer, but it does not include the macro feature. In the beginning I was stuc

Apr 28, 2025 - 04:10
 0
Debugging BLE macros on Nature Remo was such an ordeal that I built a Swift development environment

TL;DR

I have prepared an environment for developing a subset of Android nRF Connect BLE macros on macOS, iOS, and other Apple platforms.

GitHub logo Kuniwak / swift-ble-macro

BLE (Bluetooth Low Energy) Macros for Swift

swift-ble-macro

This is a simple Swift library that allows you to easily create nRF Connect BLE macros 1 for your iOS/iPadOS/macOS/watchOS/visionOS/tvOS app It is a wrapper around Core Bluetooth that allows you to create macros for BLE devices. It is designed to easily develop Nature Remo compatible BLE macros2.

Installation

Add the following to your Package.swift file:

.package(url: "https://github.com/Kuniwak/swift-ble-macro.git", from: "")

Products

Product Description
BLEMacroEasy Defines the easy interface to execute BLE macros.
BLEMacro Defines the BLE macro XML parser.
BLEMacroCompiler Defines the BLE macro compiler that compile BLE macro XML to BLE macro IR.
BLECommand Defines the BLE macro IR.
BLEInterpreter Defines the BLE macro interpreter that interprets BLE macro IR.
BLEInternal Defines the utilities for BLE macros.
BLEModel Defines the convenient state machines for BLE.

Supported Macros

Currently, only the following BLE macro XML elements…


import Foundation
import BLEMacroEasy

// You can find your iPhone’s UUID by running the commands below in Terminal:
// $ git clone https://github.com/Kuniwak/swift-ble-macro
// $ cd swift-ble-macro
// $ swift run ble discover
let myIPhoneUUID = UUID(uuidString: "********-****-****-****-************")!
let myMacro = try String(contentsOf: URL(string: "https://ble-macro.kuniwak.com/iphone/battery-level.xml")!)

try await run(macroXMLString: myMacro, on: myIPhoneUUID) { data in
    // This handler is called every time a value is read from the peripheral.
    let batteryLevel = Int(data[0])
    print("\(batteryLevel)%")
}

Besides the ability to run BLE macros on macOS/iOS/…, the project also provides a CLI that helps you develop those macros.
For the macro-execution API, see the README.
The CLI supports device scanning, macro validation, macro execution, and an interactive REPL:

$ # Scan BLE devices
$ ble discover
00000000-0000-0000-0000-000000000000    Example Device 1    -78
11111111-1111-1111-1111-111111111111    Example Device 2    -47
22222222-2222-2222-2222-222222222222    Example Device 3    -54
...

$ # Abort scanning with Ctrl+C

$ # Run a BLE macro
$ ble run path/to/your/ble-macro.xml --uuid 00000000-0000-0000-0000-000000000000

$ # Run a BLE macro interactively
$ ble repl --uuid 00000000-0000-0000-0000-000000000000
connecting...
connected

(ble) ?
write-command, w, wc            Write to a characteristic without a response
write-descriptor, wd            Write to a descriptor
write-request, req              Write to a characteristic with a response
read, r                         Read from a characteristic
discovery-service, ds           Discover services
discovery-characteristics, dc   Discover characteristics
discovery-descriptor, dd        Discover descriptors
q, quit                         Quit the REPL

(ble) dc
180A 2A29 read
180A 2A24 read
D0611E78-BBB4-4591-A5F8-487910AE4366 8667556C-9A37-4C91-84ED-54EE27D90049 write/write/notify/extendedProperties
9FA480E0-4967-4542-9390-D343DC5D04AE AF0BADB1-5B99-43CD-917A-A77BC549E3CC write/write/notify/extendedProperties
180F 2A19 read/notify
1805 2A2B read/notify
1805 2A0F readk

(ble) r 180F 2A19
58

The BLE macros I have written are published at Kuniwak/ble-macro.

Background

Nature Remo recently gained a Bluetooth Low Energy (BLE) macro feature (official announcement).
If you own a compatible model (Nature Remo 3 or Nature Remo mini 2 family), you can control BLE devices directly from your Remo.
I therefore started writing BLE macros so that our three full-colour Philips Hue bulbs could be driven from Remo (I have always had a faint admiration for scenes like this video, hence the extravagant colours).

The macros supported by Remo are a subset of those used by Android nRF Connect.
Macros are written in XML. For example, to turn a Hue bulb on in daylight-white you can write:

 name="hue-daylight-white" icon="BRIGHTNESS_HIGH">
     description="Hue" uuid="932c32bd-0000-47a2-835a-a8d455b859dd">
         description="Combined" uuid="932c32bd-0007-47a2-835a-a8d455b859dd">
             name="WRITE" requirement="MANDATORY"/>
        
    
     description="Set to Daylight White"
           characteristic-uuid="932c32bd-0007-47a2-835a-a8d455b859dd"
           service-uuid="932c32bd-0000-47a2-835a-a8d455b859dd"
           value="0101010201fe0302fa0005020100"
           type="WRITE_REQUEST" />

With BLE macros you instruct read/write operations against services and characteristics.
For well-known devices you can often find the details thanks to specifications reverse-engineered by third parties, but bringing a hand-written macro to a “working on Remo” state still requires a fair bit of trial and error. And that trial and error was utterly painful.

According to the documentation, Android users can rely on the nRF Connect for Mobile app, which supports everything from recording to running BLE macros, making development easy.
Unfortunately I had no Android device at hand.
There is an iOS version by the same developer, but it does not include the macro feature.
In the beginning I was stuck in a loop of feeding a macro to Remo, hitting a barely-described error, tweaking the XML by guesswork, and trying again…

Because this cycle was so fruitless, I decided to build an environment with functionality similar to Android’s nRF Connect for Mobile.
That is how Kuniwak/swift-ble-macro was born.
Although it offers only a subset of features compared with nRF Connect, it is more than enough to develop BLE macros that run on Remo. Feel free to give it a try!

When developing macros it is also important to capture what traffic an existing app is exchanging with the BLE device.

BLE-macro Growing Pains

During my adventures with Hue and Switchbot I ran into all sorts of issues; let me document them here for posterity.

Hue returns an insufficient encryption error

Hue bulbs cannot be operated unless they are paired.
Attempting to read/write from a non-paired central results in an insufficient encryption error.
Only the first device that performs pairing gets registered (there is a workaround, explained below).
For example, if you connect using the official Hue app first, any subsequent device (e.g. Remo or Google Nest mini) will be rejected.

You can work around this by opening Hue app › Settings › Voice assistant › Google Home › Make discoverable.
This puts the bulb into a discoverable state for a very short time (about a minute).
Programmatically, the same effect can be achieved by writing 0x01 to characteristic 97fe6561-2004-4f62-86e9-b71ee2da3d22 of service 0000fe0f-0000-1000-8000-00805f9b34fb (see the make-discoverable BLE macro).

No clear factory-reset procedure after a failed Hue pairing

If your Hue bulb gets paired to, say, a Google Nest mini before you manage to pair it with the official app or a Mac, the insufficient encryption problem prevents any further operation, including factory reset, because that too is done over BLE.

Some guides propose toggling the power repeatedly to reset the bulb — for example this discussion.
However, this did not work for my bulb (model LCA009, firmware v1.116.3).
What did work was cutting the power at the wall switch, then turning it back on; for a brief moment and at very close range the official Hue app could see the bulb again, from which I was able to perform a factory reset.

Switchbot does not respond

Switchbot devices can also be controlled via BLE, but they are finicky.
For firmware v6.6 you can operate them with the macro shared in the developer community on Discord, but v6.3 ignores the same commands.
After some trial and error I discovered that, on v6.3, subscribing to notifications on characteristic cba20003-224d-11e6-9fb8-0002a5d5c51b before writing 0x01 to cba20002-224d-11e6-9fb8-0002a5d5c51b makes the arm move.
Here is a macro that works on v6.3:

 name="switchbot-push" icon="PLAY">
     description="Switchbot" uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b">
         description="Push" uuid="cba20002-224d-11e6-9fb8-0002a5d5c51b">
             name="WRITE" requirement="MANDATORY"/>
        
         description="Configuration" uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b">
             name="NOTIFY" requirement="MANDATORY"/>
             />
        
    

     description="Enable notifications"
                      characteristic-uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b"
                      service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b"
                      uuid="00002902-0000-1000-8000-00805f9b34fb"
                      value="0100" />

     description="Write 0x570100"
           characteristic-uuid="cba20002-224d-11e6-9fb8-0002a5d5c51b"
           service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b"
           value="570100"
           type="WRITE_REQUEST" />

     description="Wait for notification"
                           characteristic-uuid="cba20003-224d-11e6-9fb8-0002a5d5c51b"
                           service-uuid="cba20d00-224d-11e6-9fb8-0002a5d5c51b"
                           timeout="5000" />

Products and References I Found Helpful

GitHub logo jnross / Bluetility

A Bluetooth Low Energy browser, an open-source alternative to LightBlue for OS X

Bluetility

Bluetility is a general-purpose Bluetooth Low-Energy utility for Mac OS X. It scans for advertising peripherals, provides a interface to browse a connected peripheral's services and characteristics, and allows characteristic values to be read, written, and subscribed Bluetility Screenshot

Installation

Manual

  1. Download the latest release: https://github.com/jnross/Bluetility/releases/latest/download/Bluetility.app.zip
  2. Extract the downloaded archive.
  3. Move Bluetility.app into your /Applications folder. Or don't!
  4. Open Bluetility.app.

Using Homebrew

brew install --cask bluetility

Features

  • Scan for nearby advertising peripherals
  • Sort peripherals by received signal strength
  • View advertising data via tooltip on Devices list
  • Browse services and characteristics of connected peripheral
  • Subscribe to characteristic notifications
  • Read/Write characteristic values
  • View log of characteristic read/writes, logs may be saved as CSV

Motivation

Bluetility is inspired by LightBlue, a free bluetooth utility published by Punch Through Design. Bluetility was created to resolve issues in this tool, and add missing features:

  • Support copy/paste via Cmd+C and Cmd+V
  • Sort peripherals by received…

GitHub logo OpenWonderLabs / SwitchBotAPI-BLE

SwitchBot BLE open API

SwitchBotAPI-BLE

Device Types

Product Device Type
Bot H (0x48)
Meter T (0x54)
Humidifier e (0x65)
Curtain c (0x63)
Curtain 3 {(0x7B)
Motion Sensor s (0x73)
Contact Sensor d (0x64)
Color Bulb u (0x75)
LED Strip Light r (0x72)
Smart Lock o (0x6F)
Plug Mini g (0x67)
Meter Plus i (0x69)

The device type is in the service data of SCAN_RSP.

Service data
Byte: 0 Enc type Bit[7] NC
Byte: 0 Dev Type Bit [6:0] – Device Type

UUID Update Notes

From Bot V6.4, Curtain V4.6, Meter V2.7,

  • Company ID ( ADV_IND - Manufacture Data ) modified from 0x0059