fix: thread safety, GATT cleanup, onResume guard, relay2 docs
This commit is contained in:
parent
404f46acf8
commit
a6e5df0052
3 changed files with 69 additions and 16 deletions
42
AGENTS.md
Normal file
42
AGENTS.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Project_SylDa
|
||||||
|
|
||||||
|
ESP32-S3-PICO-1-N8R8 (Atom S3R Voice), 2 relais, 2 touch sensors (JR223B), BLE, Android app USB OTG.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- **Board:** Atom S3R Voice (ESP32-S3-PICO-1-N8R8), 16MB OPI flash
|
||||||
|
- **Pinnen:** G1=RELAY1, G2=RELAY2, G8+pullup=TOUCH1, G6+pullup=TOUCH2, G41=BUTTON (active low), G46=LED
|
||||||
|
- **Touch:** DC3-24V Capacitieve Touch Schakelaar JR223B (GEEN TTP223). Minder C op punt A = gevoeliger = meer false triggers.
|
||||||
|
- **BLE:** "AtomS3R-Relay", service `4fafc201-1fb5-459e-8fcc-c5c9c331914b`
|
||||||
|
- **BLE cmds:** 0x00=relay1 OFF, 0x01=ON, 0x02=relay2 ON, 0x03=OFF, HMAC_PKT_SIZE=37 (1 cmd + 4 seq + 32 hmac)
|
||||||
|
- **NVS:** namespace `hmac`, key `key` — HMAC key 32 bytes, NVS op 0x9000-0xDFFF (partitie tabel)
|
||||||
|
|
||||||
|
## Workspace
|
||||||
|
|
||||||
|
`/home/eddy/src/Project_SylDa/` — S3R firmware + Android app
|
||||||
|
|
||||||
|
## Firmware — `src/main.cpp`
|
||||||
|
|
||||||
|
- PlatformIO, `platform = espressif32@6.7.0`, board `esp32-s3-devkitc-1`
|
||||||
|
- CDC USB serial (`ARDUINO_USB_CDC_ON_BOOT=1`), QIO OPI flash
|
||||||
|
- Upload laptop: `pio run -t upload`, port `/dev/ttyACM0`, 115200 baud
|
||||||
|
- NVS: namespace "hmac", key "key" (32 bytes HMAC)
|
||||||
|
- BLE advertising "AtomS3R-Relay", HMAC-verified relay commands
|
||||||
|
|
||||||
|
## Android app — `android/`
|
||||||
|
|
||||||
|
- CDC key exchange (geen FTDI), 2 relay knoppen (touch-to-hold), byte commands
|
||||||
|
- Key exchange: SettingsActivity via USB CDC (`UsbSerialTransport`)
|
||||||
|
- Flash: EspFlasher via USB CDC, `eraseFlash()` VERWIJDERD (NVS blijft behouden)
|
||||||
|
- Partitie layout flash: bootloader 0x0, partitions 0x8000, boot_app0 0xE000, firmware 0x10000
|
||||||
|
- BLE scan: 30s timeout, auto-retry 3x, reconnect na disconnect
|
||||||
|
- `deviceId` = UUID van ESP, `hmacKey` = HMAC key uit key exchange
|
||||||
|
- Bestanden: `MainActivity.kt`, `SettingsActivity.kt`, `EspFlasher.kt`, `UsbSerialTransport.kt`, `PrefsHelper.kt`, `UsbUtils.kt`
|
||||||
|
- Assets: `firmware/bootloader.bin`, `partitions.bin`, `boot_app0.bin`, `firmware.bin`
|
||||||
|
|
||||||
|
## ADB — telefoon
|
||||||
|
|
||||||
|
```
|
||||||
|
adb connect 192.168.1.43:5555
|
||||||
|
adb -s 192.168.1.43:5555 install -r app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
```
|
||||||
|
|
@ -73,23 +73,23 @@ class BleRelayManager(
|
||||||
fun startScan() {
|
fun startScan() {
|
||||||
if (isScanning) return
|
if (isScanning) return
|
||||||
if (!hasPermissions()) {
|
if (!hasPermissions()) {
|
||||||
callbacks.onStatusChanged(BleStatus.PermissionsDenied)
|
handler.post { callbacks.onStatusChanged(BleStatus.PermissionsDenied) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||||
if (adapter == null || !adapter.isEnabled) {
|
if (adapter == null || !adapter.isEnabled) {
|
||||||
callbacks.onStatusChanged(BleStatus.BluetoothDisabled)
|
handler.post { callbacks.onStatusChanged(BleStatus.BluetoothDisabled) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadState()
|
loadState()
|
||||||
if (hmacKey == null || deviceUuid == null) {
|
if (hmacKey == null || deviceUuid == null) {
|
||||||
callbacks.onStatusChanged(BleStatus.NoKey)
|
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isScanning = true
|
isScanning = true
|
||||||
scanTimedOut = false
|
scanTimedOut = false
|
||||||
scanTimeoutRetries = 0
|
scanTimeoutRetries = 0
|
||||||
callbacks.onStatusChanged(BleStatus.Scanning)
|
handler.post { callbacks.onStatusChanged(BleStatus.Scanning) }
|
||||||
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
|
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
|
||||||
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
|
||||||
adapter.bluetoothLeScanner.startScan(listOf(filter), settings, scanCallback)
|
adapter.bluetoothLeScanner.startScan(listOf(filter), settings, scanCallback)
|
||||||
|
|
@ -105,6 +105,7 @@ class BleRelayManager(
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun stopScan() {
|
fun stopScan() {
|
||||||
|
handler.removeCallbacks(scanRunnable)
|
||||||
if (!isScanning) return
|
if (!isScanning) return
|
||||||
isScanning = false
|
isScanning = false
|
||||||
scanTimeoutRunnable?.let { handler.removeCallbacks(it) }
|
scanTimeoutRunnable?.let { handler.removeCallbacks(it) }
|
||||||
|
|
@ -116,7 +117,7 @@ class BleRelayManager(
|
||||||
handler.postDelayed(scanRunnable, 2000L)
|
handler.postDelayed(scanRunnable, 2000L)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callbacks.onStatusChanged(BleStatus.DeviceNotFound)
|
handler.post { callbacks.onStatusChanged(BleStatus.DeviceNotFound) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +141,9 @@ class BleRelayManager(
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
handler.removeCallbacksAndMessages(null)
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
stopScan()
|
||||||
|
bluetoothGatt?.disconnect()
|
||||||
|
bluetoothGatt?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
|
|
@ -173,9 +177,14 @@ class BleRelayManager(
|
||||||
triedAddresses.clear()
|
triedAddresses.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isConnected(): Boolean {
|
||||||
|
return bluetoothGatt != null
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun connectToDevice(device: android.bluetooth.BluetoothDevice) {
|
private fun connectToDevice(device: android.bluetooth.BluetoothDevice) {
|
||||||
callbacks.onStatusChanged(BleStatus.Connecting)
|
handler.post { callbacks.onStatusChanged(BleStatus.Connecting) }
|
||||||
|
Log.d(TAG, "Connecting to ${device.address}")
|
||||||
bluetoothGatt = device.connectGatt(context, false, gattCallback)
|
bluetoothGatt = device.connectGatt(context, false, gattCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +194,7 @@ class BleRelayManager(
|
||||||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
if (hmacKey == null) {
|
if (hmacKey == null) {
|
||||||
Log.w(TAG, "GATT connected but no key -- disconnecting")
|
Log.w(TAG, "GATT connected but no key -- disconnecting")
|
||||||
callbacks.onStatusChanged(BleStatus.NoKey)
|
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
|
||||||
gatt.disconnect()
|
gatt.disconnect()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +203,7 @@ class BleRelayManager(
|
||||||
gatt.discoverServices()
|
gatt.discoverServices()
|
||||||
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||||
Log.d(TAG, "GATT disconnected")
|
Log.d(TAG, "GATT disconnected")
|
||||||
callbacks.onStatusChanged(BleStatus.Disconnected)
|
handler.post { callbacks.onStatusChanged(BleStatus.Disconnected) }
|
||||||
gatt.close()
|
gatt.close()
|
||||||
bluetoothGatt = null
|
bluetoothGatt = null
|
||||||
relayCharacteristic = null
|
relayCharacteristic = null
|
||||||
|
|
@ -207,7 +216,7 @@ class BleRelayManager(
|
||||||
override fun onServicesDiscovered(gatt: android.bluetooth.BluetoothGatt, status: Int) {
|
override fun onServicesDiscovered(gatt: android.bluetooth.BluetoothGatt, status: Int) {
|
||||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||||
Log.w(TAG, "Service discovery failed (status=$status)")
|
Log.w(TAG, "Service discovery failed (status=$status)")
|
||||||
callbacks.onStatusChanged(BleStatus.ServiceError)
|
handler.post { callbacks.onStatusChanged(BleStatus.ServiceError) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Services discovered, requesting MTU...")
|
Log.d(TAG, "Services discovered, requesting MTU...")
|
||||||
|
|
@ -226,11 +235,11 @@ class BleRelayManager(
|
||||||
gatt.readCharacteristic(uuidChar)
|
gatt.readCharacteristic(uuidChar)
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Service or characteristics not found")
|
Log.e(TAG, "Service or characteristics not found")
|
||||||
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
|
handler.post { callbacks.onStatusChanged(BleStatus.ServiceNotFound) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Service not found")
|
Log.e(TAG, "Service not found")
|
||||||
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
|
handler.post { callbacks.onStatusChanged(BleStatus.ServiceNotFound) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,11 +255,11 @@ class BleRelayManager(
|
||||||
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||||
gatt.writeDescriptor(descriptor)
|
gatt.writeDescriptor(descriptor)
|
||||||
}
|
}
|
||||||
callbacks.onStatusChanged(BleStatus.Connected)
|
handler.post { callbacks.onStatusChanged(BleStatus.Connected) }
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "UUID mismatch -- disconnecting")
|
Log.w(TAG, "UUID mismatch -- disconnecting")
|
||||||
triedAddresses.add(gatt.device.address)
|
triedAddresses.add(gatt.device.address)
|
||||||
callbacks.onStatusChanged(BleStatus.WrongDevice)
|
handler.post { callbacks.onStatusChanged(BleStatus.WrongDevice) }
|
||||||
gatt.disconnect()
|
gatt.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +269,7 @@ class BleRelayManager(
|
||||||
override fun onCharacteristicWrite(gatt: android.bluetooth.BluetoothGatt, characteristic: android.bluetooth.BluetoothGattCharacteristic, status: Int) {
|
override fun onCharacteristicWrite(gatt: android.bluetooth.BluetoothGatt, characteristic: android.bluetooth.BluetoothGattCharacteristic, status: Int) {
|
||||||
if (status != BluetoothGatt.GATT_SUCCESS) {
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
||||||
Log.w(TAG, "Write failed (status=$status)")
|
Log.w(TAG, "Write failed (status=$status)")
|
||||||
callbacks.onStatusChanged(BleStatus.CommError)
|
handler.post { callbacks.onStatusChanged(BleStatus.CommError) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,10 @@ class MainActivity : AppCompatActivity(), BleCallbacks {
|
||||||
requestPermissions()
|
requestPermissions()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
manager.resetTriedAddresses()
|
if (!manager.isConnected()) {
|
||||||
handler.postDelayed({ manager.startScan() }, ON_RESUME_SCAN_DELAY_MS)
|
manager.resetTriedAddresses()
|
||||||
|
handler.postDelayed({ manager.startScan() }, ON_RESUME_SCAN_DELAY_MS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue