Add USB auto-read key transfer + UI redesign
USB auto-read: - ESP responds to 'KEY?\n' over USB serial with 'HMAC_KEY:<hex>' - Android UsbKeyReceiver (manifest BroadcastReceiver) catches USB_DEVICE_ATTACHED - Filters on Espressif vendor 0x303a, requests USB permission - Opens CDC-ACM serial (115200), sends KEY?, parses response, saves to SharedPreferences - Works even when app not in foreground - usb-serial-for-android v3.7.3 via JitPack UI redesign: - activity_main.xml: FrameLayout with settings icon top-right, relay button center, status bottom-left (14sp) - Scan & Connect button moved to SettingsActivity - SettingsActivity: hint text 'Voer de HMAC sleutel in of sluit de relaismodule aan op de telefoon' - SettingsActivity: scan button saves key + returns RESULT_OK to trigger scanning - MainActivity: removed scanButton, auto-scan on onResume when key is set - Back button removed from settings (ActionBar up navigation only) README updated with USB protocol docs, new UI description, and updated setup guide
This commit is contained in:
parent
7f68e9ad70
commit
4518aec34e
10 changed files with 270 additions and 74 deletions
85
README.md
85
README.md
|
|
@ -53,7 +53,7 @@ De relay is beveiligd tegen ongeautoriseerde toegang via twee onafhankelijke lag
|
|||
|
||||
Omdat BLE Just Works geen MITM-bescherming biedt, is er een tweede laag toegevoegd:
|
||||
|
||||
- Both de ESP firmware en de Android app bevatten een **32-byte shared secret** (HMAC key)
|
||||
- Zowel de ESP firmware als de Android app bevatten een **32-byte shared secret** (HMAC key)
|
||||
- Elk commando wordt ondertekend met `HMAC-SHA256(key, command || sequence)`
|
||||
- De ESP verifieert de handtekening voordat het relais schakelt
|
||||
- Een **sequence counter** voorkomt replay-aanvallen: elk packet moet een strikt oplopend volgnummer hebben
|
||||
|
|
@ -74,10 +74,20 @@ De sequence counter reset naar 0 bij elke (re)connectie op de ESP. De app start
|
|||
- De HMAC key staat in `src/hmac_key.h` (niet in git, `.gitignore`)
|
||||
- Een CLI-tool genereert een willekeurige key: `python3 tools/generate_hmac_key.py`
|
||||
- De ESP print de key 5 seconden na opstarten naar serial
|
||||
- De gebruiker voert de key eenmalig in via het instellingenscherm in de Android app
|
||||
- **USB auto-read:** Sluit de ESP via USB-OTG aan op de telefoon → de app leest de key automatisch via USB serial (CDC-ACM). De ESP reageert op het commando `KEY?\n` met `HMAC_KEY:<hex>`. Geen encryptie nodig — fysieke connectie is de beveiliging.
|
||||
- Handmatige invoer: via het instellingenscherm in de Android app (64-teken hex string)
|
||||
- De app slaat de key op in SharedPreferences (overleeft app-herstarts)
|
||||
- Zo kan dezelfde app gebruikt worden voor meerdere ESP modules — each met een eigen key
|
||||
|
||||
#### USB Serial Key Transfer Protocol
|
||||
|
||||
```
|
||||
App → ESP: KEY?\n
|
||||
ESP → App: HMAC_KEY:C3F17B612B93E82C56B055FA9B04E84664ACE1EC4E889F2763F271B65A5B54AF\n
|
||||
```
|
||||
|
||||
De ESP luistert in `loop()` voor USB serial data. Bij `KEY?\n` antwoordt hij met de hex key. De Android `UsbKeyReceiver` (manifest-geregistreerde `BroadcastReceiver`) vangt `USB_DEVICE_ATTACHED` op, filtert op Espressif (vendor 0x303a), vraagt USB-permission, opent een CDC-ACM serial connectie, stuurt `KEY?\n`, en slaat de respons op in SharedPreferences.
|
||||
|
||||
### Beveiligingsanalyse
|
||||
|
||||
| Aanval | Hoe geweerd |
|
||||
|
|
@ -169,29 +179,38 @@ Na het uploaden treedt er een harde reset op. Als het board in download-mode bli
|
|||
- **BLE Pairing:** De app initieert bonding (`createBond()`) na connectie; Just Works pairing verloopt zonder gebruikersinteractie
|
||||
- **MTU negotiatie:** Vraagt MTU 256 aan na service discovery (voor 37-byte HMAC packets)
|
||||
- **HMAC authenticatie:** Elk commando wordt ondertekend met HMAC-SHA256 + sequence counter
|
||||
- **Instellingenscherm:** Losse Activity voor het invoeren van de HMAC key (met oog-icoon om de key te tonen/verbergen)
|
||||
- **USB auto-read:** Bij aansluiten van de ESP via USB-OTG wordt de HMAC key automatisch gelezen en opgeslagen (BroadcastReceiver)
|
||||
- **Instellingenscherm:** Losse Activity voor het invoeren van de HMAC key (met oog-icoon om de key te tonen/verbergen), "Scan & Verbind" knop, en hint-tekst
|
||||
- **Permissies:** Vraagt BLUETOOTH_SCAN, BLUETOOTH_CONNECT en ACCESS_FINE_LOCATION aan (Android 12+)
|
||||
|
||||
### Instellingenscherm
|
||||
|
||||
1. Open de app
|
||||
2. Tik op het **tandwiel-icoon** rechtsboven
|
||||
3. Voer de 64-teken hex key in (zelfde als de ESP op serial print)
|
||||
4. Tik op het **oog-icoon** om de key te tonen/verbergen
|
||||
5. Tik **Opslaan**
|
||||
3. De app toont een hint: _"Voer de HMAC sleutel in of sluit de relaismodule aan op de telefoon."_
|
||||
4. **Optie A — handmatig:** Voer de 64-teken hex key in (zelfde als de ESP op serial print). Tik op het **oog-icoon** om de key te tonen/verbergen. Tik **Opslaan**.
|
||||
5. **Optie B — automatisch:** Sluit de ESP via USB-OTG aan op de telefoon. Android vraagt USB-toestemming (1x). De key wordt automatisch gelezen en opgeslagen. Toast: "HMAC sleutel gelezen van ESP!"
|
||||
6. Tik **Scan & Verbind** om op te slaan en direct BLE scannen te starten.
|
||||
|
||||
De key wordt opgeslagen in SharedPreferences en overleeft app-herstarts. Bij geen key toont de app "Geen HMAC sleutel - open Instellingen" en wordt er niet gescant.
|
||||
De key wordt opgeslagen in SharedPreferences en overleeft app-herstarts. Bij geen key toont de hoofdapp "Geen HMAC sleutel - open Instellingen" en wordt er niet gescant.
|
||||
|
||||
### Hoofdscherm UI
|
||||
|
||||
- **Tandwiel-icoon** rechtsboven → opent instellingenscherm
|
||||
- **HOLD-knop** centrum → houd ingedrukt voor relais aan
|
||||
- **Status tekst** linksonder (14sp) → toont verbindingsstatus
|
||||
|
||||
### Bouwen
|
||||
|
||||
Vereisten:
|
||||
- Android SDK (API 34+)
|
||||
- Java 17+ (getest met Java 17 en 21; Java 26 werkt niet met Kotlin 1.9 daemon — valt terug op non-incremental)
|
||||
- Java 17+ (Java 26 werkt niet met Gradle 8.5 — gebruik Java 21)
|
||||
- Gradle 8.2.2 (via wrapper)
|
||||
- USB host support (usb-serial-for-android library via JitPack)
|
||||
|
||||
```bash
|
||||
cd android
|
||||
./gradlew assembleDebug
|
||||
JAVA_HOME=/pad/naar/jdk21 ./gradlew assembleDebug
|
||||
```
|
||||
|
||||
De APK staat na build in `android/app/build/outputs/apk/debug/app-debug.apk`.
|
||||
|
|
@ -222,29 +241,23 @@ adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
|||
pio run -t upload
|
||||
```
|
||||
|
||||
3. **Lees de HMAC key uit serial** (5 seconden na opstarten):
|
||||
```bash
|
||||
# Via python:
|
||||
python3 -c "import serial,time; s=serial.Serial('/dev/ttyACM0',115200); [print(l) for l in iter(lambda: s.readline().decode(errors='replace').strip() or None, None) if 'HMAC Key:' in l] if False else exec('''
|
||||
import serial, time
|
||||
s = serial.Serial(\"/dev/ttyACM0\", 115200, timeout=2)
|
||||
start = time.time()
|
||||
while time.time() - start < 15:
|
||||
d = s.read(1024)
|
||||
if d:
|
||||
for line in d.decode(errors=\"replace\").splitlines():
|
||||
if line.strip(): print(line.strip())
|
||||
if b\"HMAC Key:\" in d: break
|
||||
s.close()
|
||||
''')"
|
||||
```
|
||||
|
||||
4. **Installeer de app op de telefoon:**
|
||||
3. **Installeer de app op de telefoon:**
|
||||
```bash
|
||||
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
5. **Open de app → tandwiel-icoon → voer key in → Opslaan**
|
||||
4. **Voer de HMAC key in (kies één):**
|
||||
|
||||
**Optie A — USB auto-read (aanbevolen):**
|
||||
- Sluit de ESP via USB-OTG aan op de telefoon
|
||||
- Android vraagt USB-toestemming (1x) — tik "Toestaan"
|
||||
- De key wordt automatisch gelezen en opgeslagen (Toast bevestigt)
|
||||
- Ontkoppel de ESP en zet hem op stroom
|
||||
|
||||
**Optie B — handmatig:**
|
||||
- Open de app → tandwiel-icoon → voer de hex key in → Opslaan
|
||||
|
||||
5. **Scan & Verbind:** Tik in instellingen op "Scan & Verbind" (of de app scant automatisch bij terugkeren naar het hoofdscherm)
|
||||
|
||||
6. De app scant, paart (Just Works), en verbindt. Houd de HOLD-knop ingedrukt om het relais te schakelen.
|
||||
|
||||
|
|
@ -256,30 +269,31 @@ sylvia-touch-relay/
|
|||
├── tools/
|
||||
│ └── generate_hmac_key.py # CLI tool: genereert src/hmac_key.h
|
||||
├── src/
|
||||
│ ├── main.cpp # Firmware (BLE + HMAC + sensor + relais)
|
||||
│ ├── main.cpp # Firmware (BLE + HMAC + USB serial + sensor + relais)
|
||||
│ └── hmac_key.h # Gegenereerd, niet in git (.gitignore)
|
||||
├── android/
|
||||
│ ├── build.gradle # Root Gradle config
|
||||
│ ├── settings.gradle
|
||||
│ ├── settings.gradle # JitPack repos voor usb-serial-for-android
|
||||
│ ├── gradle.properties
|
||||
│ ├── gradlew # Gradle wrapper
|
||||
│ ├── gradle/wrapper/
|
||||
│ │ └── gradle-wrapper.properties
|
||||
│ └── app/
|
||||
│ ├── build.gradle # App module config
|
||||
│ ├── build.gradle # App module config (+ usb-serial-for-android dep)
|
||||
│ └── src/main/
|
||||
│ ├── AndroidManifest.xml
|
||||
│ ├── java/com/eddy/atomrelay/
|
||||
│ │ ├── MainActivity.kt # BLE client + HMAC signing
|
||||
│ │ └── SettingsActivity.kt # HMAC key invoer + oog toggle
|
||||
│ │ ├── SettingsActivity.kt # HMAC key invoer + scan knop
|
||||
│ │ └── UsbKeyReceiver.kt # USB auto-read BroadcastReceiver
|
||||
│ └── res/
|
||||
│ ├── drawable/
|
||||
│ │ ├── ic_eye_open.xml # Oog open icoon
|
||||
│ │ ├── ic_eye_closed.xml # Oog dicht icoon
|
||||
│ │ └── ic_settings.xml # Tandwiel icoon
|
||||
│ ├── layout/
|
||||
│ │ ├── activity_main.xml # Hoofd UI
|
||||
│ │ └── activity_settings.xml # Key invoer UI
|
||||
│ │ ├── activity_main.xml # Hoofd UI (FrameLayout)
|
||||
│ │ └── activity_settings.xml # Key invoer + scan UI
|
||||
│ └── values/strings.xml
|
||||
├── .gitignore
|
||||
└── README.md
|
||||
|
|
@ -295,3 +309,6 @@ sylvia-touch-relay/
|
|||
- De app gebruikt `WRITE_TYPE_NO_RESPONSE` voor minimale latency — de encryptie en HMAC verificatie gebeuren op de link layer resp. application layer en zijn onafhankelijk van het write type.
|
||||
- De sequence counter start bij 1 (niet 0) om te voorkomen dat het eerste packet na connectie als replay wordt geweerd (`0 <= 0` is true in de `seq <= lastSeq` check).
|
||||
- Für meerdere ESP modules met verschillende keys: genereer per module een eigen key, flash de firmware, en voer de juiste key in de app in. De app ondersteunt momenteel slechtséén key (opgeslagen in SharedPreferences).
|
||||
- De `UsbKeyReceiver` is een manifest-geregistreerde `BroadcastReceiver` voor `ACTION_USB_DEVICE_ATTACHED`. Hij filtert op Espressif (vendor 0x303a), vraagt USB-permission via `PendingIntent`, opent een CDC-ACM serial connectie (115200), stuurt `KEY?\n`, en slaat de respons op. Werkt ook als de app niet actief is.
|
||||
- De USB serial library (`usb-serial-for-android` v3.7.3 via JitPack) ondersteunt CDC-ACM (ESP32-S3), FTDI, CP210x, CH340, PL2303, en meer.
|
||||
- De `UsbKeyReceiver` doet serial I/O op een achtergrondthread (max 3s timeout) om ANR's te voorkomen.
|
||||
|
|
@ -34,4 +34,5 @@ android {
|
|||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:3.7.3'
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
|
@ -29,5 +30,13 @@
|
|||
android:name=".SettingsActivity"
|
||||
android:label="Instellingen"
|
||||
android:parentActivityName=".MainActivity"/>
|
||||
|
||||
<receiver
|
||||
android:name=".UsbKeyReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -2,6 +2,7 @@ package com.eddy.atomrelay
|
|||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
|
|
@ -40,8 +41,9 @@ class MainActivity : AppCompatActivity() {
|
|||
private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8")
|
||||
private val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
private const val DEVICE_NAME = "AtomS3R-Relay"
|
||||
private const val HMAC_PKT_SIZE = 37 // 1 cmd + 4 seq + 32 hmac
|
||||
private const val HMAC_PKT_SIZE = 37
|
||||
private const val HMAC_LEN = 32
|
||||
private const val REQUEST_SETTINGS = 1
|
||||
}
|
||||
|
||||
private var bluetoothGatt: BluetoothGatt? = null
|
||||
|
|
@ -55,7 +57,6 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var statusText: TextView
|
||||
private lateinit var relayButton: Button
|
||||
private lateinit var scanButton: Button
|
||||
|
||||
private val scanCallback = object : ScanCallback() {
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
|
|
@ -111,7 +112,7 @@ class MainActivity : AppCompatActivity() {
|
|||
gatt.close()
|
||||
bluetoothGatt = null
|
||||
relayCharacteristic = null
|
||||
seq = 0
|
||||
seq = 1
|
||||
handler.postDelayed({ startScan() }, 2000)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,18 +161,22 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
statusText = findViewById(R.id.statusText)
|
||||
relayButton = findViewById(R.id.relayButton)
|
||||
scanButton = findViewById(R.id.scanButton)
|
||||
val settingsButton = findViewById<ImageButton>(R.id.settingsButton)
|
||||
|
||||
scanButton.setOnClickListener { startScan() }
|
||||
settingsButton.setOnClickListener {
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
startActivityForResult(Intent(this, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||
}
|
||||
setupRelayButton()
|
||||
|
||||
registerReceiver(bondReceiver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
|
||||
}
|
||||
|
||||
handler.postDelayed({ startScan() }, 500)
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_SETTINGS && resultCode == Activity.RESULT_OK) {
|
||||
handler.postDelayed({ startScan() }, 300)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -179,6 +184,8 @@ class MainActivity : AppCompatActivity() {
|
|||
loadHmacKey()
|
||||
if (hmacKey == null) {
|
||||
statusText.text = "Geen HMAC sleutel - open Instellingen"
|
||||
} else if (!isScanning && bluetoothGatt == null) {
|
||||
handler.postDelayed({ startScan() }, 300)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +268,6 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
isScanning = true
|
||||
statusText.text = "Scanning..."
|
||||
scanButton.isEnabled = false
|
||||
Log.d(TAG, "Starting BLE scan for $DEVICE_NAME")
|
||||
|
||||
adapter.bluetoothLeScanner.startScan(scanCallback)
|
||||
|
|
@ -275,7 +281,6 @@ class MainActivity : AppCompatActivity() {
|
|||
private fun stopScan() {
|
||||
if (!isScanning) return
|
||||
isScanning = false
|
||||
scanButton.isEnabled = true
|
||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
||||
adapter?.bluetoothLeScanner?.stopScan(scanCallback)
|
||||
if (statusText.text == "Scanning...") {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
companion object {
|
||||
const val PREFS_NAME = "relay_prefs"
|
||||
const val PREF_KEY = "hmac_key"
|
||||
const val RESULT_SCAN = 100
|
||||
}
|
||||
|
||||
private var isVisible = false
|
||||
|
|
@ -31,7 +32,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
val keyInput = findViewById<EditText>(R.id.keyInput)
|
||||
val toggleBtn = findViewById<ImageButton>(R.id.toggleVisibility)
|
||||
val saveButton = findViewById<Button>(R.id.saveButton)
|
||||
val backButton = findViewById<Button>(R.id.backButton)
|
||||
val scanConnectButton = findViewById<Button>(R.id.scanConnectButton)
|
||||
val keyError = findViewById<TextView>(R.id.keyError)
|
||||
|
||||
keyInput.setText(prefs.getString(PREF_KEY, ""))
|
||||
|
|
@ -80,7 +81,14 @@ class SettingsActivity : AppCompatActivity() {
|
|||
finish()
|
||||
}
|
||||
|
||||
backButton.setOnClickListener { finish() }
|
||||
scanConnectButton.setOnClickListener {
|
||||
val key = keyInput.text.toString().uppercase().filter { it in "0123456789ABCDEF" }
|
||||
if (key.length == 64) {
|
||||
prefs.edit().putString(PREF_KEY, key).apply()
|
||||
}
|
||||
setResult(RESULT_SCAN)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
|
|
|
|||
139
android/app/src/main/java/com/eddy/atomrelay/UsbKeyReceiver.kt
Normal file
139
android/app/src/main/java/com/eddy/atomrelay/UsbKeyReceiver.kt
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package com.eddy.atomrelay
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class UsbKeyReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UsbKeyReceiver"
|
||||
private const val ACTION_USB_PERMISSION = "com.eddy.atomrelay.USB_PERMISSION"
|
||||
private const val ESP_VENDOR_ID = 0x303a
|
||||
private const val KEY_TIMEOUT_MS = 3000
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
|
||||
val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) ?: return
|
||||
Log.d(TAG, "USB device attached: vid=0x${device.vendorId.toString(16)} pid=0x${device.productId.toString(16)}")
|
||||
if (device.vendorId != ESP_VENDOR_ID) return
|
||||
|
||||
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
if (usbManager.hasPermission(device)) {
|
||||
Thread { readKey(context, usbManager, device) }.start()
|
||||
} else {
|
||||
val permIntent = PendingIntent.getBroadcast(
|
||||
context, 0,
|
||||
Intent(ACTION_USB_PERMISSION)
|
||||
.setClass(context, UsbKeyReceiver::class.java)
|
||||
.putExtra(UsbManager.EXTRA_DEVICE, device),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
usbManager.requestPermission(device, permIntent)
|
||||
Log.d(TAG, "Requesting USB permission for ${device.deviceName}")
|
||||
}
|
||||
}
|
||||
ACTION_USB_PERMISSION -> {
|
||||
val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE) ?: return
|
||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
||||
Log.d(TAG, "USB permission granted for ${device.deviceName}")
|
||||
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
Thread { readKey(context, usbManager, device) }.start()
|
||||
} else {
|
||||
Log.w(TAG, "USB permission denied")
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(context, "USB-toegang geweigerd", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readKey(context: Context, usbManager: UsbManager, device: UsbDevice) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
var port: UsbSerialPort? = null
|
||||
|
||||
try {
|
||||
val driver = CdcAcmSerialDriver(device)
|
||||
val connection = usbManager.openDevice(device)
|
||||
if (connection == null) {
|
||||
Log.e(TAG, "Failed to open USB device")
|
||||
handler.post { Toast.makeText(context, "Kan USB-apparaat niet openen", Toast.LENGTH_SHORT).show() }
|
||||
return
|
||||
}
|
||||
|
||||
val ports = driver.ports
|
||||
if (ports.isEmpty()) {
|
||||
Log.e(TAG, "No serial ports found")
|
||||
handler.post { Toast.makeText(context, "Geen serial-poort gevonden", Toast.LENGTH_SHORT).show() }
|
||||
return
|
||||
}
|
||||
|
||||
port = ports[0]
|
||||
port.open(connection)
|
||||
port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
|
||||
Log.d(TAG, "Serial port opened, sending KEY?")
|
||||
|
||||
port.purgeHwBuffers(true, true)
|
||||
|
||||
val cmd = "KEY?\n".toByteArray(StandardCharsets.US_ASCII)
|
||||
port.write(cmd, 500)
|
||||
|
||||
Thread.sleep(200)
|
||||
|
||||
val buf = ByteArray(256)
|
||||
val startTime = System.currentTimeMillis()
|
||||
val sb = StringBuilder()
|
||||
|
||||
while (System.currentTimeMillis() - startTime < KEY_TIMEOUT_MS) {
|
||||
val len = port.read(buf, 200)
|
||||
if (len > 0) {
|
||||
sb.append(String(buf, 0, len, StandardCharsets.US_ASCII))
|
||||
if (sb.contains("HMAC_KEY:") && sb.contains("\n")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val response = sb.toString()
|
||||
Log.d(TAG, "Serial response: $response")
|
||||
|
||||
val keyLine = response.lines().firstOrNull { it.startsWith("HMAC_KEY:") }
|
||||
if (keyLine != null) {
|
||||
val hexKey = keyLine.removePrefix("HMAC_KEY:").trim()
|
||||
if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) {
|
||||
val upperKey = hexKey.uppercase()
|
||||
val prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().putString(SettingsActivity.PREF_KEY, upperKey).apply()
|
||||
Log.d(TAG, "HMAC key saved: $upperKey")
|
||||
handler.post {
|
||||
Toast.makeText(context, "HMAC sleutel gelezen van ESP!", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Invalid key format: length=${hexKey.length}")
|
||||
handler.post { Toast.makeText(context, "Ongeldig sleutel formaat van ESP", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "No HMAC_KEY line found in response")
|
||||
handler.post { Toast.makeText(context, "Geen sleutel gevonden op de ESP", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error reading key: ${e.message}", e)
|
||||
handler.post { Toast.makeText(context, "Fout bij uitlezen: ${e.message}", Toast.LENGTH_SHORT).show() }
|
||||
} finally {
|
||||
try { port?.close() } catch (e: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="32dp">
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_gravity="end|top"
|
||||
android:src="@drawable/ic_settings"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Instellingen"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Disconnected"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginBottom="48dp"/>
|
||||
android:contentDescription="Instellingen" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/relayButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:text="HOLD"
|
||||
android:textSize="24sp"
|
||||
android:enabled="false"/>
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/scanButton"
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Scan & Connect"
|
||||
android:layout_marginTop="48dp"/>
|
||||
android:layout_gravity="bottom|start"
|
||||
android:text="Disconnected"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
@ -5,6 +5,15 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/keyHint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Voer de HMAC sleutel in of sluit de relaismodule aan op de telefoon."
|
||||
android:textSize="14sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -66,11 +75,10 @@
|
|||
android:layout_marginTop="24dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/backButton"
|
||||
android:id="@+id/scanConnectButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Terug"
|
||||
android:background="@android:color/transparent"
|
||||
android:text="Scan & Verbind"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -10,6 +10,7 @@ dependencyResolutionManagement {
|
|||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
rootProject.name = "AtomRelay"
|
||||
|
|
|
|||
17
src/main.cpp
17
src/main.cpp
|
|
@ -158,6 +158,23 @@ void setup() {
|
|||
}
|
||||
|
||||
void loop() {
|
||||
if (Serial.available()) {
|
||||
static String serialBuf;
|
||||
char c = Serial.read();
|
||||
serialBuf += c;
|
||||
if (c == '\n') {
|
||||
serialBuf.trim();
|
||||
if (serialBuf == "KEY?") {
|
||||
Serial.print("HMAC_KEY:");
|
||||
for (int i = 0; i < 32; i++) {
|
||||
Serial.printf("%02X", HMAC_KEY[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
serialBuf = "";
|
||||
}
|
||||
}
|
||||
|
||||
int sensorState = digitalRead(SENSOR_PIN);
|
||||
|
||||
bool touchActive = (sensorState == LOW);
|
||||
|
|
|
|||
Loading…
Reference in a new issue