Modify code to support minor API versions

This commit is contained in:
Sylvia van Os committed 2026-08-11 22:06:01 +02:00
1 parent 6ea1d9ec73
commit 3c917da56a
4 files changed
+47 -18

No files matched your search

@@ -148,10 +148,10 @@ class BluetoothServerService : Service() {
val firstLine = reader.readLine()?.trim() ?: return
if (firstLine == WearBluetoothProtocol.BT_CMD_VERSIONS) {
val versions = JSONArray().put(WearBluetoothProtocol.PROTOCOL_VERSION).toString()
val versions = JSONArray().put("${WearBluetoothProtocol.PROTOCOL_VERSION}.${WearBluetoothProtocol.PROTOCOL_MINOR_VERSION}").toString()
writer.println(versions)
writer.flush()
Log.d(TAG, "Sent supported versions to $deviceName")
Log.d(TAG, "Sent supported versions to $deviceName: $versions")
return
}
+27 -11
View File
@@ -14,7 +14,14 @@ The Bluetooth server is currently only used by the Catima WearOS companion app.
## API versions
There is currently only a single API version. Version 1.
For each API version, we have a major and a minor version. The rules for versioning are as follows:
1. A new minor version may add fields or endpoints, but must never modify or remove existing fields or endpoints.
2. A new minor version must not change any authentication systems compared to the previous minor version.
3. A device cannot request Catima to use a specific minor version, it will always return data as per the most recently supported minor version.
The main driver of compatibility is the major version.
Due to this system, a device only supporting API version 1.0 can talk to Catima supporting API version 1.1 with no issues, as long as it ignores fields it doesn't know. On the other side, a device wanting to use API version 1.1 with Catima on API version 1.0 has to accept certain fields may not be available. Which minor version introduces which additional fields and endpoints is documented in this file.
### Unversioned
@@ -22,11 +29,20 @@ There is currently only a single API version. Version 1.
Return type: JSON.
Value:
Returns all supported major versions and the most recent supported minor version.
Example request:
```
[1]
/VERSIONS
```
Example return value:
```
["1.0"]
```
In this example case, the phone supports only API version 1. Of API version 1, the most recent minor version supported is 0.
### V1
All V1 endpoints are authenticated and must be communicated with by sending 2 lines over an RfConn socket. First the full command, then a device-specific token (recommended: a base64-encoded version of a 32 characters long SecureRandom-generated string).
@@ -47,14 +63,14 @@ Request a single page of cards with page and total page details. The page ID is
Each card contains the following fields:
| Column | Type | Description |
|-------------------|-------------------|----------------------------|
| `id` | `int` | Unique card ID |
| `store` | `String` | Card name |
| `cardId` | `String` | Card ID. |
| `barcodeId` | `Nullable String` | Barcode value. If empty, it's the same as the card ID. |
| `barcodeType` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. |
| `headerColor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). |
| Column | Type | Description | Introduced in minor version |
|-------------------|-------------------|----------------------------| --------------------------- |
| `id` | `int` | Unique card ID | 0 |
| `store` | `String` | Card name | 0 |
| `cardId` | `String` | Card ID. | 0 |
| `barcodeId` | `Nullable String` | Barcode value. If empty, it's the same as the card ID. | 0 |
| `barcodeType` | `Nullable String` | The barcode type name, matching [com.google.zxing.BarcodeFormat](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html). When null, there is no barcode for this card. | 0 |
| `headerColor` | `int` | Header color, matching [Android color int](https://developer.android.com/reference/android/graphics/Color). | 0 |
Example request:
```
@@ -6,6 +6,7 @@ object WearBluetoothProtocol {
val BT_SERVICE_UUID: UUID = UUID.fromString("e5b4f020-3a7e-4b6d-9f2c-1a8c5d3e7f90")
const val BT_SERVICE_NAME = "CatimaWear"
const val PROTOCOL_VERSION = 1
const val PROTOCOL_MINOR_VERSION = 0
const val BT_CMD_VERSIONS = "/VERSIONS"
const val BT_CMD_TOKEN_PREFIX = "TOKEN:"
const val BT_CMD_VERSION_PREFIX = "/V$PROTOCOL_VERSION/"
@@ -97,13 +97,25 @@ object BluetoothCardClient {
socket.connect()
val supportedVersions = requestSupportedVersions(socket)
?: return null to SyncStatus.PHONE_NOT_REACHABLE
if (WearBluetoothProtocol.PROTOCOL_VERSION !in supportedVersions) {
Log.w(TAG, "Phone does not support API version ${WearBluetoothProtocol.PROTOCOL_VERSION}")
var majorVersionIsSupported = false
var mostRecentMinorVersion = -1
run breaking@{
supportedVersions.forEach {
val supportedVersionParts = it.split('.')
if (supportedVersionParts[0] == WearBluetoothProtocol.PROTOCOL_VERSION.toString()) {
majorVersionIsSupported = true
mostRecentMinorVersion = supportedVersionParts[1].toInt()
return@breaking
}
}
}
if (!majorVersionIsSupported) {
Log.w(TAG, "Phone does not support major API version ${WearBluetoothProtocol.PROTOCOL_VERSION}")
return null to SyncStatus.VERSION_INCOMPATIBLE
}
socket.close()
socket = null
Log.d(TAG, "Connected to $deviceName with API version ${WearBluetoothProtocol.PROTOCOL_VERSION}")
Log.d(TAG, "Connected to $deviceName with major API version ${WearBluetoothProtocol.PROTOCOL_VERSION}, phone supports up to minor API version ${mostRecentMinorVersion}, we can use up to ${WearBluetoothProtocol.PROTOCOL_MINOR_VERSION}")
socket = device.createRfcommSocketToServiceRecord(WearBluetoothProtocol.BT_SERVICE_UUID)
socket.connect()
@@ -173,7 +185,7 @@ object BluetoothCardClient {
writer.println("${WearBluetoothProtocol.BT_CMD_TOKEN_PREFIX}$token")
}
private fun requestSupportedVersions(socket: BluetoothSocket): Set<Int>? {
private fun requestSupportedVersions(socket: BluetoothSocket): Set<String>? {
val writer = PrintWriter(OutputStreamWriter(socket.outputStream, "UTF-8"), false)
val reader = BufferedReader(InputStreamReader(socket.inputStream, "UTF-8"))
writer.print("${WearBluetoothProtocol.BT_CMD_VERSIONS}\n")
@@ -183,7 +195,7 @@ object BluetoothCardClient {
val versions = JSONArray(response)
buildSet {
for (index in 0 until versions.length()) {
add(versions.getInt(index))
add(versions.getString(index))
}
}
} catch (e: Exception) {