Skip to content

Fix 'GATT operation not permitted' by picking the writable characteristic - #28

Merged
IvanR3D merged 1 commit into
OttoDIY:mainfrom
K-STEAM:main
Aug 24, 2026
Merged

Fix 'GATT operation not permitted' by picking the writable characteristic#28
IvanR3D merged 1 commit into
OttoDIY:mainfrom
K-STEAM:main

Conversation

@jangminhyeok

Copy link
Copy Markdown
Contributor

Problem

Sending any command to the robot fails with the following error in the browser console, even though the device pairs and connects successfully:

Uncaught (in promise) NotSupportedError: GATT operation not permitted.

Chrome throws NotSupportedError: GATT operation not permitted. when writeValue() is called on a characteristic whose properties include neither write nor writeWithoutResponse.

gotCharacteristics() in js/bluetooth.js picked the target characteristic by position:

blueToothCharacteristic = characteristics[0];

getCharacteristics() does not guarantee any order, and several BLE 4.0 serial modules (HM-10 clones such as CC41-A, AT-09 or MLT-BT05) expose more than one characteristic inside the FFE0 service, sometimes splitting the serial port into a notify-only characteristic and a separate writable one. When the first characteristic in the list happens to be the read/notify-only one, the connection and the sensor notifications work, but every command sent by sendData() is rejected.

Changes

All changes are in js/bluetooth.js, which is shared by index.html, ninja.html and calibration.html, so all three pages are covered.

  • Pick the characteristics by their properties instead of by position. The one advertising write / writeWithoutResponse is used to send commands, the one advertising notify / indicate is used to receive sensor values. blueToothCharacteristic is kept and now points at the writable characteristic, so nothing else in the codebase had to change.
  • Report the problem instead of failing silently. Every characteristic of the service is logged on connect with its UUID and its properties, which makes unknown modules easy to diagnose. If no characteristic accepts writes, the user gets a clear message rather than an unhandled promise rejection on the first button press.
  • Fall back through the available write methods: writeValueWithoutResponse(), then writeValueWithResponse(), then the deprecated writeValue() for older browsers.
  • Serialize the writes. The joystick can emit commands faster than a GATT operation completes, which made the browser reject the overlapping ones. Writes now go through a promise queue.
  • Split the payload into 20-byte chunks. BLE 4.0 modules only forward 20 bytes per write (23-byte MTU minus the 3-byte ATT header), so longer commands such as the calibration and settings strings were being truncated.
  • Clear the cached characteristics on disconnect.

Notes

  • No change to the wire protocol, the command strings or the Arduino side.
  • No change to the public functions: connectToBle(), sendData(), gotValue() and onDisconnected() keep the same names and signatures.
  • Modules whose first characteristic was already the writable one (genuine HM-10) keep working exactly as before.

Testing

Tested on Windows 10 / Chrome with an Otto and a bluetooth module: pairing, sending movement and gesture commands, and receiving sensor values.

@IvanR3D

IvanR3D commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

This is a great contribution! Thank you. I will be testing it asap to include it in the repo. :)

@IvanR3D
IvanR3D merged commit a8e7404 into OttoDIY:main Aug 24, 2026
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.

2 participants