From c9704c4bf4eaad7a3ebc0cce78eb3fae604b9dd1 Mon Sep 17 00:00:00 2001 From: Joe Kearney Date: Sun, 9 Feb 2025 17:42:21 -0600 Subject: [PATCH] Implemented all seven packet types. --- .../club/clubk/ktag/bletool/ByteCellData.kt | 5 +- .../club/clubk/ktag/bletool/MainActivity.kt | 136 +++++++------- .../clubk/ktag/bletool/PacketFieldUtils.kt | 170 ++++++++++++++++++ 3 files changed, 240 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/club/clubk/ktag/bletool/PacketFieldUtils.kt diff --git a/app/src/main/java/club/clubk/ktag/bletool/ByteCellData.kt b/app/src/main/java/club/clubk/ktag/bletool/ByteCellData.kt index 0c56e39..ef68de6 100644 --- a/app/src/main/java/club/clubk/ktag/bletool/ByteCellData.kt +++ b/app/src/main/java/club/clubk/ktag/bletool/ByteCellData.kt @@ -1,7 +1,10 @@ package club.clubk.ktag.bletool +import androidx.compose.ui.graphics.Color + data class ByteCellData( val value: Byte, val isHeader: Boolean, - val description: String + val description: String, + val backgroundColor: Color? = null ) \ No newline at end of file diff --git a/app/src/main/java/club/clubk/ktag/bletool/MainActivity.kt b/app/src/main/java/club/clubk/ktag/bletool/MainActivity.kt index 0cb959c..73bcc37 100644 --- a/app/src/main/java/club/clubk/ktag/bletool/MainActivity.kt +++ b/app/src/main/java/club/clubk/ktag/bletool/MainActivity.kt @@ -15,12 +15,15 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -49,12 +52,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat private const val TAG = "BLE Tool" @@ -95,7 +94,7 @@ val advertisementPresets = listOf( 0x00.toByte(), 0x00.toByte(), // Event Data ), - "KTag Instigate Game Packet" + "KTag Event Packet" ), AdvertisementPreset( "03 Tag", @@ -146,10 +145,64 @@ val advertisementPresets = listOf( ), "KTag Status Packet" ), + AdvertisementPreset( + "06 Configuration", + byteArrayOf( + 0x06.toByte(), // Packet Type: Configuration + 0x00.toByte(), // Event Number + 0xFF.toByte(), + 0xFF.toByte(), + 0xFF.toByte(), + 0xFF.toByte(), + 0xFF.toByte(), + 0xFF.toByte(), // Target Bluetooth Device Address + 0x02.toByte(), // Subtype: Request Parameter Change + 0x01.toByte(), + 0x00.toByte(), // Key 1: Team ID + 0x02.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), // Value 1: 2 + 0xFF.toByte(), + 0xFF.toByte(), // Key 2: Unused + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), // Value 2: Unused + ), + "KTag Configuration Packet" + ), + AdvertisementPreset( + "07 Hello", + byteArrayOf( + 0x07.toByte(), // Packet Type: Hello + 0x00.toByte(), // Event Number + 0x01.toByte(), // SystemK Major Version + 0x00.toByte(), // SystemK Minor Version + 0x02.toByte(), // + 0x00.toByte(), // Device Type: Mobile App + 0x02.toByte(), // Team ID + 0x4B.toByte(), // 'K' + 0x54.toByte(), // 'T' + 0x61.toByte(), // 'a' + 0x67.toByte(), // 'g' + 0x20.toByte(), // ' ' + 0x42.toByte(), // 'B' + 0x4C.toByte(), // 'L' + 0x45.toByte(), // 'E' + 0x20.toByte(), // ' ' + 0x54.toByte(), // 'T' + 0x6F.toByte(), // 'o' + 0x6F.toByte(), // 'o' + 0x6C.toByte(), // 'l' + 0x00.toByte(), // Device Name: "KTag BLE Tool" + ), + "KTag Hello Packet" + ), AdvertisementPreset( "Empty", ByteArray(23) { 0 }, - "All zeros - clear current data" + "All zeros (clear current data)" ) ) @@ -326,71 +379,13 @@ fun BleAdvertiserScreen() { // Create list of byte cells with their properties val byteCells = remember(advertisementData) { + val packetType = advertisementData[8] advertisementData.mapIndexed { index, byte -> ByteCellData( - value = byte, isHeader = index < N_HEADER_BYTES, description = when (index) { - 0 -> "Length" // Always "Length" - 1 -> "Type" // Always "Type" - 2 -> "Mfg ID" // Always "Mfg ID" - 3 -> "Mfg ID" // Always "Mfg ID" - 4 -> "'K'" // Always "'K'" - 5 -> "'T'" // Always "'T'" - 6 -> "'a'" // Always "'a'" - 7 -> "'g'" // Always "'g'" - 9 -> "Event Number" - else -> { - when (advertisementData[8]) { - 0x01.toByte() -> when(index){ - 8 -> "Instigate Game" // Packet Type 01 - else -> "" - } - 0x02.toByte() -> when(index){ - 8 -> "Event" // Packet Type 02 - else -> "" - } - 0x03.toByte() -> when(index){ - 8 -> "Tag" // Packet Type 03 - 10 -> "Tx Pwr (dBm)" - 11 -> "Protocol" - 12 -> "Team ID" - 13 -> "Player ID" - 14 -> "Damage" - 15 -> "Damage" - 16 -> "Color" - 17 -> "Color" - 18 -> "Color" - 19 -> "Color" - 20 -> "Target Address" - 21 -> "Target Address" - 22 -> "Target Address" - 23 -> "Target Address" - 24 -> "Target Address" - 25 -> "Target Address" - else -> "" - } - 0x04.toByte() -> when(index){ - 8 -> "Console" // Packet Type 04 - else -> "" - } - 0x05.toByte() -> when(index){ - 8 -> "Status" // Packet Type 05 - else -> "" - } - 0x06.toByte() -> when(index){ - 8 -> "Configuration" // Packet Type 06 - else -> "" - } - 0x07.toByte() -> when(index){ - 8 -> "Hello" // Packet Type 07 - else -> "" - } - else -> when(index){ - 8 -> "Packet Type" - else -> "" - } - } - } - } + value = byte, + isHeader = index < N_HEADER_BYTES, + description = PacketFieldUtils.getFieldDescription(packetType, index), + backgroundColor = PacketFieldUtils.getFieldColor(packetType, index) ) } } @@ -556,6 +551,7 @@ fun ByteCell( val backgroundColor = when { data.isHeader -> MaterialTheme.colorScheme.secondaryContainer isSelected -> MaterialTheme.colorScheme.primary + data.backgroundColor != null -> data.backgroundColor else -> MaterialTheme.colorScheme.primaryContainer } diff --git a/app/src/main/java/club/clubk/ktag/bletool/PacketFieldUtils.kt b/app/src/main/java/club/clubk/ktag/bletool/PacketFieldUtils.kt new file mode 100644 index 0000000..08ee071 --- /dev/null +++ b/app/src/main/java/club/clubk/ktag/bletool/PacketFieldUtils.kt @@ -0,0 +1,170 @@ +package club.clubk.ktag.bletool + +import androidx.compose.ui.graphics.Color + +object PacketFieldUtils { + fun getFieldDescription(packetType: Byte, index: Int): String { + return when (index) { + 0 -> "Length" // Always "Length" + 1 -> "Type" // Always "Type" + 2 -> "Mfg ID" // Always "Mfg ID" + 3 -> "Mfg ID" // Always "Mfg ID" + 4 -> "'K'" // Always "'K'" + 5 -> "'T'" // Always "'T'" + 6 -> "'a'" // Always "'a'" + 7 -> "'g'" // Always "'g'" + 8 -> when (packetType) { + 0x01.toByte() -> "Instigate Game" + 0x02.toByte() -> "Event" + 0x03.toByte() -> "Tag" + 0x04.toByte() -> "Console" + 0x05.toByte() -> "Status" + 0x06.toByte() -> "Configuration" + 0x07.toByte() -> "Hello" + else -> "Packet Type" + } + + 9 -> "Event Number" + else -> when (packetType) { + 0x01.toByte() -> when (index) { // Instigate Game packet fields + in 10..13 -> "Game Length (ms)" + in 14..17 -> "Time till Countdown (ms)" + else -> "" + } + + 0x02.toByte() -> when (index) { // Event packet fields + in 10..15 -> "Target Address" + in 16..19 -> "Event ID" + in 20..23 -> "Event Data" + else -> "" + } + + 0x03.toByte() -> when (index) { // Tag packet fields + 10 -> "Tx Pwr (dBm)" + 11 -> "Protocol" + 12 -> "Team ID" + 13 -> "Player ID" + 14, 15 -> "Damage" + in 16..19 -> "Color" + in 20..25 -> "Target Address" + else -> "" + } + + 0x04.toByte() -> when (index) { // Console packet fields + in 10..30 -> "Console String" + else -> "" + } + + 0x05.toByte() -> when (index) { // Status packet fields + 10 -> "Tx Pwr (dBm)" + 11 -> "Protocol" + 12 -> "Team ID" + 13 -> "Player ID" + 14, 15 -> "Health" + 16, 17 -> "Max Health" + in 18..21 -> "Primary Color" + in 22..25 -> "Secondary Color" + 26 -> "SystemK State" + else -> "" + } + + 0x06.toByte() -> when (index) { // Configuration packet fields + in 10..15 -> "Target Address" + 16 -> "Subtype" + 17, 18 -> "Key 1" + in 19..22 -> "Value 1" + 23, 24 -> "Key 2" + in 25..28 -> "Value 2" + else -> "" + } + + 0x07.toByte() -> when (index) { // Hello packet fields + 10 -> "SystemK Major Version" + 11 -> "SystemK Minor Version" + 12, 13 -> "Device Type" + 14 -> "Team ID" + in 15..30 -> "Device Name" + else -> "" + } + + else -> "" + } + } + } + + fun getFieldColor(packetType: Byte, index: Int): Color? { + // Header bytes always return null to use default header color + if (index < 8) return null + + // Packet type byte is always a distinct color + if (index == 8) return Color(0xFFE6F3FF) // Light blue + + // Event number is always the same color across all packet types + if (index == 9) return Color(0xFFE6FFE6) // Light green + + return when (packetType) { + 0x01.toByte() -> when (index) { // Instigate Game packet + in 10..13 -> Color(0xFFFFF3E6) // Game Length - Light orange + in 14..17 -> Color(0xFFE6FFF3) // Time until Countdown - Light cyan + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x02.toByte() -> when (index) { // Event packet + in 10..15 -> Color(0xFFE6F3FF) // Target Address - Light blue + in 16..19 -> Color(0xFFF3E6FF) // Event ID - Light purple + in 20..23 -> Color(0xFFE6FFE6) // Event Data - Light green + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x03.toByte() -> when (index) { // Tag packet + 10 -> Color(0xFFFFF3E6) // Tx Power - Light orange + 11 -> Color(0xFFE6FFF3) // Protocol - Light cyan + 12 -> Color(0xFFFFE6F3) // Team ID - Light pink + 13 -> Color(0xFFF3E6FF) // Player ID - Light purple + in 14..15 -> Color(0xFFFFE6E6) // Damage - Light red + in 16..19 -> Color(0xFFE6FFE6) // Color - Light green + in 20..25 -> Color(0xFFE6F3FF) // Target Address - Light blue + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x04.toByte() -> when (index) { // Console packet + in 10..30 -> Color(0xFFFFF3E6) // Console String - Light orange + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x05.toByte() -> when (index) { // Status packet + 10 -> Color(0xFFFFF3E6) // Tx Power - Light orange + 11 -> Color(0xFFE6FFF3) // Protocol - Light cyan + 12 -> Color(0xFFFFE6F3) // Team ID - Light pink + 13 -> Color(0xFFF3E6FF) // Player ID - Light purple + 14, 15 -> Color(0xFFFFE6E6) // Health - Light red + 16, 17 -> Color(0xFFE6FFE6) // Maximum Health - Light green + in 18..21 -> Color(0xFFE6F3FF) // Primary Color - Light blue + in 22..25 -> Color(0xFFF3E6FF) // Secondary Color - Light purple + 26 -> Color(0xFFFFF3E6) // SystemK State - Light orange + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x06.toByte() -> when (index) { // Configuration packet + in 10..15 -> Color(0xFFE6F3FF) // Target Address - Light blue + 16 -> Color(0xFFFFF3E6) // Subtype - Light orange + 17, 18 -> Color(0xFFF3E6FF) // Key 1 - Light purple + in 19..22 -> Color(0xFFE6FFE6) // Value 1 - Light green + 23, 24 -> Color(0xFFF3E6FF) // Key 2 - Light purple + in 25..28 -> Color(0xFFE6FFE6) // Value 2 - Light green + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + 0x07.toByte() -> when (index) { // Hello packet + 10 -> Color(0xFFFFF3E6) // SystemK Major Version - Light orange + 11 -> Color(0xFFE6FFF3) // SystemK Minor Version - Light cyan + 12, 13 -> Color(0xFFE6F3FF) // Device Type - Light blue + 14 -> Color(0xFFFFE6F3) // Team ID - Light pink + in 15..30 -> Color(0xFFF3E6FF) // Device Name - Light purple + else -> Color(0xFFF5F5F5) // Light gray for undefined fields + } + + else -> Color(0xFFF5F5F5) // Light gray for unknown packet types + } + } +} \ No newline at end of file