Android BLE写入回调onCharacteristicWrite返回GATT_WRITE_NOT_PERMITTED求助
Hey there! Let’s troubleshoot why your custom Android BLE app is giving you grief with the HM-10 module—especially since you confirmed other store apps work fine with it. I’ve spent a fair bit of time tinkering with HM-10s, so let’s walk through the most common pitfalls and check your code against them.
HM-10 modules almost universally use the full 128-bit UUIDs:
- Service UUID:
0000ffe0-0000-1000-8000-00805f9b34fb - Write/Read Characteristic UUID:
0000ffe1-0000-1000-8000-00805f9b34fb
A super common mistake is using the short FFE0/FFE1 UUIDs instead of the full version—Android requires the full 128-bit format for non-standard BLE profiles, so skipping this will break service/characteristic discovery.
If you’re targeting Android 10 (API 29) or higher, missing runtime permissions is one of the top culprits:
- For scanning:
BLUETOOTH_SCAN(addandroid:usesPermissionFlags="neverForLocation"if you don’t need location data; pre-Android 12, you’ll still needACCESS_FINE_LOCATIONfor BLE scanning) - For connecting/writing:
BLUETOOTH_CONNECT
Make sure these are added to your AndroidManifest.xml and that you request them at runtime before attempting any BLE operations.
BLE is inherently asynchronous—you can’t call connectGatt() and immediately try to write a characteristic. You need to wait for each step to complete via callbacks:
- Call
bluetoothDevice.connectGatt(context, false, gattCallback) - Wait for
onConnectionStateChange()to confirmSTATE_CONNECTED, then callgatt.discoverServices() - Wait for
onServicesDiscovered()to fire (withGATT_SUCCESSstatus), then retrieve the HM-10 service and characteristic - Only then attempt to write data
If you’re skipping any of these steps (like writing before services are discovered), your app will fail silently or throw errors.
HM-10’s FFE1 characteristic supports both WRITE and WRITE_NO_RESPONSE properties, but you need to confirm this before writing. Add a check in onServicesDiscovered():
if ((writeChar.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0 || (writeChar.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { // Safe to proceed with writes }
Also, set the correct write type before sending data—HM-10 often works better with WRITE_TYPE_NO_RESPONSE for faster transfers:
writeChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
Using a hardcoded MAC address works, but keep these in mind:
- Ensure the MAC is formatted correctly (uppercase, colon-separated:
AA:BB:CC:DD:EE:FF) - On Android 12+, you need to have scanned the device recently or have it paired to connect via MAC (due to privacy restrictions)
- HM-10s typically use static MACs, but double-check yours with a BLE scanner app if you’re unsure
Add logs to every callback in your BluetoothGattCallback to pinpoint where things are breaking:
- Does
onConnectionStateChange()ever hitSTATE_CONNECTED, or does it disconnect immediately? - Does
onServicesDiscovered()returnGATT_SUCCESS, or an error likeGATT_FAILURE? - Does
onCharacteristicWrite()fire, and what’s the status code? (0 = success; any other code means failure—look upBluetoothGatterror constants for details)
For example, if onServicesDiscovered() returns GATT_FAILURE, it might mean the HM-10 didn’t respond to the service discovery request (try adding a short delay before calling discoverServices()).
All BLE operations (connect, discover services, write characteristics) must run on the main thread—Android’s BLE APIs are not thread-safe. Also, always confirm the Bluetooth adapter is enabled before attempting to connect:
if (!bluetoothAdapter.isEnabled()) { // Prompt user to enable Bluetooth }
Here’s a condensed version of a reliable HM-10 write flow:
private BluetoothGatt gatt; private BluetoothGattCharacteristic writeCharacteristic; private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d("BLE", "Connected—discovering services..."); gatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d("BLE", "Disconnected from HM-10"); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (status == BluetoothGatt.GATT_SUCCESS) { BluetoothGattService hm10Service = gatt.getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb")); if (hm10Service != null) { writeCharacteristic = hm10Service.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); if (writeCharacteristic != null) { // Now ready to send data sendBytesToHM10(new byte[]{0x01, 0x02, 0x03}); } } } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.d("BLE", "Data written successfully!"); } else { Log.e("BLE", "Write failed with status: " + status); } } }; private void sendBytesToHM10(byte[] data) { if (writeCharacteristic == null || gatt == null) return; writeCharacteristic.setValue(data); writeCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); gatt.writeCharacteristic(writeCharacteristic); } // To initiate connection: BluetoothDevice hm10Device = bluetoothAdapter.getRemoteDevice("AA:BB:CC:DD:EE:FF"); gatt = hm10Device.connectGatt(this, false, gattCallback);
- Use a BLE debugging app (like nRF Connect) to inspect the HM-10’s services/characteristics and test writes—this confirms the hardware is working as expected.
- HM-10 has a 20-byte MTU limit per write, so split large data into chunks if needed.
- Always call
gatt.close()when you’re done with the connection to avoid resource leaks.
内容的提问来源于stack exchange,提问作者Olliek

