Initial public release.
This commit is contained in:
parent
ed31acd60f
commit
58d87b11b7
249 changed files with 15831 additions and 4 deletions
264
subapp-konfigurator/README.md
Normal file
264
subapp-konfigurator/README.md
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# KTag Konfigurator Subapp
|
||||
|
||||
A Jetpack Compose Android application for configuring KTag laser tag devices via BLE and coordinating game sessions.
|
||||
|
||||
## Overview
|
||||
|
||||
The Konfigurator manages the full lifecycle of a KTag game session: configuring game parameters, discovering and assigning devices to teams, broadcasting game start/stop events, and running game timers. It communicates with KTag devices over BLE using the KTag 27-byte packet protocol.
|
||||
|
||||
## Architecture
|
||||
|
||||
The app follows the **MVVM (Model-View-ViewModel)** pattern with a state machine driving screen transitions.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ KonfiguratorActivity │
|
||||
│ (Compose Host + Permissions) │
|
||||
└─────────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────▼───────────────────────────────────┐
|
||||
│ StateMachineViewModel │
|
||||
│ • State machine (6 screens) │
|
||||
│ • BLE scanning & advertising │
|
||||
│ • Multi-device configuration │
|
||||
│ • Game config management │
|
||||
└──────┬──────────────┬───────────────────────────────────┘
|
||||
│ │
|
||||
┌──────▼──────┐ ┌─────▼──────────────────────────────────┐
|
||||
│ BleManager │ │ MultiDeviceConfigurator │
|
||||
│ (Singleton) │ │ (Sequential BLE config with mutex) │
|
||||
└─────────────┘ └────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/main/java/club/clubk/ktag/apps/konfigurator/
|
||||
├── KonfiguratorActivity.kt # Compose activity with BLE permissions
|
||||
├── StateMachineViewModel.kt # State machine, BLE orchestration
|
||||
├── BleManager.kt # BLE scan/advertise singleton
|
||||
├── MultiDeviceConfigurator.kt # Sequential multi-device configuration
|
||||
├── KTagPacket.kt # Packet types, parsing, generators
|
||||
├── Device.kt # Device data class
|
||||
├── DeviceState.kt # Sealed class: Configurable/Ready/Playing/WrapUp
|
||||
├── AppState.kt # Sealed class: 6 screen states
|
||||
├── GameConfig.kt # Game parameters data class
|
||||
├── ConfigurationProgress.kt # Configuration progress tracking
|
||||
├── Player.kt # Player data class
|
||||
├── SoundManager.kt # Audio feedback (SoundPool)
|
||||
├── KonfiguratorSubApp.kt # Subapp registration
|
||||
├── KonfiguratorInitializer.kt # androidx.startup initializer
|
||||
└── ui/theme/
|
||||
├── Theme.kt # Material3 dynamic theming
|
||||
├── Color.kt # Color palette
|
||||
└── Type.kt # Typography
|
||||
```
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ GameSettings │ Configure duration, health, rounds │
|
||||
└───────┬───────┘ │
|
||||
│ → start BLE scanning │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ TeamSettings │ Discover devices, assign teams, apply config │
|
||||
└───────┬───────┘ │
|
||||
│ [all devices ready] │
|
||||
│ → stop scanning │
|
||||
│ → advertise InstigatingGame packet │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ PregameTimer │ Countdown before game │
|
||||
└───────┬───────┘ │
|
||||
│ [timer expires] │
|
||||
│ → stop advertising │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ Countdown │ Countdown lights sequence (5+1 s) │
|
||||
└───────┬───────┘ │
|
||||
│ [lights out] │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ GameTimer │ Active game countdown │
|
||||
└───────┬───────┘ │
|
||||
│ [timer expires / End Game] │
|
||||
│ → advertise EVENT_GAME_OVER │
|
||||
▼ │
|
||||
┌───────────────┐ │
|
||||
│ WrapUp │ → advertise EVENT_WRAPUP_COMPLETE ──────────────────┘
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
| State | Description |
|
||||
|----------------|----------------------------------------------------------------------------|
|
||||
| `GameSettings` | Configure game duration, health, and rounds |
|
||||
| `TeamSettings` | Scan for BLE devices, assign teams (Purple/Red/Blue), apply config |
|
||||
| `PregameTimer` | Pregame countdown; InstigatingGame packet keeps devices in sync |
|
||||
| `Countdown` | Countdown lights-out sequence (6 s) |
|
||||
| `GameTimer` | Active game countdown with pie-chart timer; early-end available |
|
||||
| `WrapUp` | Game complete; broadcasts EVENT_WRAPUP_COMPLETE, returns to GameSettings |
|
||||
|
||||
## Key Components
|
||||
|
||||
### StateMachineViewModel
|
||||
|
||||
Manages game state and BLE operations:
|
||||
|
||||
- **State Machine**: Transitions between 6 screen states, plays audio on each transition
|
||||
- **Device Discovery**: BLE scanning with KTag manufacturer data filter (0xFFFF)
|
||||
- **Team Assignment**: Cycle devices through teams (0=Purple, 1=Red, 2=Blue)
|
||||
- **Configuration**: Sends Parameters packets to assign team and health to each device
|
||||
- **Game Control**: Broadcasts Instigate Game, Event (ready/game over/wrapup) packets
|
||||
|
||||
### BleManager
|
||||
|
||||
Singleton managing BLE operations:
|
||||
|
||||
- **Advertising**: Broadcasts KTag packets as manufacturer-specific data
|
||||
- **Scanning**: Filters for KTag magic bytes with scan holdoff (5s between scans)
|
||||
- **Device Configuration**: Advertise config packet, scan for ACK, with 5s timeout
|
||||
- **Thread Safety**: Coroutine-based with managed scan start jobs
|
||||
|
||||
### MultiDeviceConfigurator
|
||||
|
||||
Sequential device configuration:
|
||||
|
||||
- **Mutex-Protected**: Ensures one BLE config operation at a time
|
||||
- **Progress Callbacks**: Reports per-device success/failure and overall progress
|
||||
- **Error Handling**: Catches exceptions per-device, continues with remaining devices
|
||||
|
||||
### KTagPacket
|
||||
|
||||
Packet system with sealed class hierarchy:
|
||||
|
||||
- 7 packet types: InstigateGame, Event, Tag, Console, Status, Parameters, Hello
|
||||
- `byteArrayToKTagPacket()`: Deserializes raw bytes to typed packets
|
||||
- `kTagPacketToByteArray()`: Serializes packets to BLE advertisement bytes
|
||||
- Packet generators with auto-incrementing event counters
|
||||
|
||||
## Game Configuration
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|---------------------------|---------|---------------------------------|
|
||||
| `gameDurationMin` | 10 | Game duration in minutes |
|
||||
| `timeUntilCountdownS` | 30 | Pregame countdown in seconds |
|
||||
| `numRounds` | 2 | Number of rounds |
|
||||
| `maxHealth` | 10 | Maximum player health |
|
||||
| `specialWeaponsOnReentry` | 1 | Special weapons on game reentry |
|
||||
|
||||
## How to Add a New Configurable Parameter to a Device
|
||||
|
||||
Each configurable parameter follows the same "set then confirm" pattern: the app holds a *desired* value and a *broadcasted* (device-confirmed) value. The accent bar and checkmark on a device card turn green only when every parameter's desired value matches its broadcasted value.
|
||||
|
||||
The steps below use `fooBar` / `broadcastedFooBar` as the example parameter name.
|
||||
|
||||
### 1. Add the protocol key — `core/.../Packet.java`
|
||||
|
||||
Parameter keys are defined in the [KTag BLE Protocol Specification](https://ktag.clubk.club/Technology/BLE/). Check there first — the key you need may already exist as a `PARAMETER_KEY_*` constant in `Packet.java`. If it does not, add it:
|
||||
|
||||
```java
|
||||
public static final int PARAMETER_KEY_FOO_BAR = <key_id_from_spec>;
|
||||
```
|
||||
|
||||
Each Parameters BLE packet carries two key-value slots. If both slots in the last packet are already occupied, the new parameter needs its own packet (see step 6).
|
||||
|
||||
### 2. Add fields to the device model — `Device.kt`
|
||||
|
||||
```kotlin
|
||||
var fooBar: Int? = null,
|
||||
var broadcastedFooBar: Int? = null
|
||||
```
|
||||
|
||||
`fooBar` is the value the operator wants to send. `broadcastedFooBar` is the value last acknowledged by the device.
|
||||
|
||||
### 3. Register the parameter for match-checking — `Device.kt`
|
||||
|
||||
Add one line to `allSettingsMatch()`:
|
||||
|
||||
```kotlin
|
||||
fun allSettingsMatch(): Boolean =
|
||||
paramMatches(team, broadcastedTeam) &&
|
||||
paramMatches(maxHealth, broadcastedMaxHealth) &&
|
||||
paramMatches(specialWeaponsOnReentry, broadcastedSpecialWeaponsOnReentry) &&
|
||||
paramMatches(fooBar, broadcastedFooBar) // ← add this
|
||||
```
|
||||
|
||||
### 4. Register the parameter for configure confirmation — `StateMachineViewModel.kt`
|
||||
|
||||
In `configureDevices()`, two `confirmOnSuccess` lambdas are built in parallel with the packet list. Each lambda is applied to the device immediately when its specific packet is ACK'd. Add `broadcastedFooBar` to whichever lambda corresponds to the packet that carries `PARAMETER_KEY_FOO_BAR`:
|
||||
|
||||
```kotlin
|
||||
confirmOnSuccess += { d -> d.copy(
|
||||
broadcastedTeam = d.team ?: d.broadcastedTeam,
|
||||
broadcastedMaxHealth = d.maxHealth ?: d.broadcastedMaxHealth,
|
||||
broadcastedFooBar = d.fooBar ?: d.broadcastedFooBar // ← add this
|
||||
)}
|
||||
```
|
||||
|
||||
This ensures a failed packet never blocks confirmation of packets that succeeded.
|
||||
|
||||
### 5. Preserve both fields when refreshing a device — `StateMachineViewModel.kt`
|
||||
|
||||
`addOrRefreshDevice()` merges incoming scan data with the existing device record. Add two lines following the same pattern as the other parameters:
|
||||
|
||||
```kotlin
|
||||
newDevice.fooBar = oldDevice.fooBar ?: newDevice.fooBar
|
||||
newDevice.broadcastedFooBar = newDevice.broadcastedFooBar ?: oldDevice.broadcastedFooBar
|
||||
```
|
||||
|
||||
### 6. Initialize and send the parameter — `StateMachineViewModel.kt`
|
||||
|
||||
**Initialize** `fooBar` when a Hello packet is received (typically from game config defaults, same as `maxHealth`):
|
||||
|
||||
```kotlin
|
||||
scannedDevice.fooBar = _currentGameConfig.value.fooBar
|
||||
```
|
||||
|
||||
**Send** it during configure. In `configureDevices()`, the `flatMap` builds one `Pair<String, Packet.Parameters>` per BLE packet. Each Parameters packet has two key-value slots. Either fill an empty slot in an existing packet or add a new one:
|
||||
|
||||
```kotlin
|
||||
Pair(device.address, Packet.Parameters(targetAddr, Packet.PARAMETER_SUBTYPE_REQUEST_CHANGE,
|
||||
Packet.PARAMETER_KEY_FOO_BAR, device.fooBar ?: gameCfg.fooBar,
|
||||
Packet.PARAMETER_KEY_NONE, 0))
|
||||
```
|
||||
|
||||
If the device also broadcasts `fooBar` in its Status packets, parse it in the Status handler and set `scannedDevice.broadcastedFooBar` there (same as `broadcastedTeam`). This provides an ongoing truth-check from the device in addition to the immediate confirmation from the ACK packet.
|
||||
|
||||
### 7. Add an update function — `StateMachineViewModel.kt`
|
||||
|
||||
```kotlin
|
||||
fun updateDeviceFooBar(deviceAddress: String, value: Int) =
|
||||
updateDeviceField(deviceAddress) { it.copy(fooBar = value) }
|
||||
```
|
||||
|
||||
`updateDeviceField` handles the `_devices` update and sets `_pendingApply = true` automatically.
|
||||
|
||||
### 8. Add a UI field to the device detail dialog — `KonfiguratorActivity.kt`
|
||||
|
||||
In `DeviceDetailDialog`, add a state variable and an `IntegerTextField`:
|
||||
|
||||
```kotlin
|
||||
var fooBarText by remember { mutableStateOf(device.fooBar?.toString() ?: "") }
|
||||
var fooBarError by remember { mutableStateOf(false) }
|
||||
|
||||
IntegerTextField(
|
||||
value = fooBarText,
|
||||
onValueChange = { fooBarText = it; fooBarError = false },
|
||||
label = "Foo Bar",
|
||||
isError = fooBarError
|
||||
)
|
||||
```
|
||||
|
||||
Update the `onSave` lambda signature to include the new value, validate it in the Save button, and call `stateMachine.updateDeviceFooBar(device.address, fooBar)` alongside the other update calls.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Jetpack Compose (Material3)
|
||||
- ViewModel + Compose integration
|
||||
- BLE (BluetoothLeScanner, BluetoothLeAdvertiser)
|
||||
- SoundPool (audio feedback)
|
||||
Loading…
Add table
Add a link
Reference in a new issue