Add support for the Colmi R02/R03/R06 smart rings #3896

Merged
arjan5 merged 1 commit from colmi-r02 into master 2024-08-23 23:54:07 +02:00
Owner

This PR adds support for the Colmi R02/R03/R06 smart ring (https://s.click.aliexpress.com/e/_DcyUheT).
The implementation is based on the analysis of the Bluetooth traffic of the official app (QRing), done with this Wireshark dissector: https://codeberg.org/Freeyourgadget/Gadgetbridge-tools/src/branch/main/colmi/wireshark

Features planned/done:

  • Bluetooth connection
  • Battery charge level
  • Device configuration
    • HR measurement interval
    • Stress measurement toggle
    • SpO2 measurement toggle
  • Synchronize, persist and display historical data
    • Steps
    • Heart rate
    • Sleep
    • SpO2
    • Stress level
  • Find device (turn on green LED for 10 seconds)
  • Power off device
  • Manual one-off HR measurement
  • Break up and modularize the code

Possible features but not planned for this PR:

  • Updating the firmware
  • Support for firmware 3.00.10
    • HRV data
    • REM sleep
  • Camera shutter gesture
  • Sleep As Android integration
This PR adds support for the Colmi R02/R03/R06 smart ring (https://s.click.aliexpress.com/e/_DcyUheT). The implementation is based on the analysis of the Bluetooth traffic of the official app (QRing), done with this Wireshark dissector: https://codeberg.org/Freeyourgadget/Gadgetbridge-tools/src/branch/main/colmi/wireshark Features planned/done: - [x] Bluetooth connection - [x] Battery charge level - [x] Device configuration - [x] HR measurement interval - [x] Stress measurement toggle - [x] SpO2 measurement toggle - [x] Synchronize, persist and display historical data - [x] Steps - [x] Heart rate - [x] Sleep - [x] SpO2 - [x] Stress level - [x] Find device (turn on green LED for 10 seconds) - [x] Power off device - [x] Manual one-off HR measurement - [x] Break up and modularize the code Possible features but not planned for this PR: - [ ] Updating the firmware - [ ] Support for firmware 3.00.10 - [ ] HRV data - [ ] REM sleep - [ ] Camera shutter gesture - [ ] Sleep As Android integration
@ -0,0 +1,56 @@
/* Copyright (C) 2024 José Rebelo
Owner

The script will replace this because of git, if you want to update it right away :p

The script will replace this because of git, if you want to update it right away :p
arjan5 marked this conversation as resolved
@ -0,0 +122,4 @@
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
getDevice().setFirmwareVersion("N/A");
Owner

I remember ashimokawa warning me that this causes some table in the database to grow on every connection, since it will be switching back and forth between N/A and the actual firmware version.

I have a workaround for this, just see the usages of cachedFirmwareVersion:

But we need to eventually figure out why this is being unset.

I remember ashimokawa warning me that this causes some table in the database to grow on every connection, since it will be switching back and forth between N/A and the actual firmware version. I have a workaround for this, just see the usages of `cachedFirmwareVersion`: https://codeberg.org/Freeyourgadget/Gadgetbridge/src/commit/00298cc5a056f412d9d4c82b3062d3360542d2fb/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java#L95 But we need to eventually figure out why this is being unset.
Author
Owner

Thanks, I just copied this from the example and other implementations. It's definitely something to investigate.

Thanks, I just copied this from the example and other implementations. It's definitely something to investigate.
Author
Owner

Looking into how you did this, I'm not sure how to proceed. Doesn't the device support class get destroyed with every disconnect? And if so, wouldn't that cause the firmware version cache to never be filled when connecting to the device?

Looking into how you did this, I'm not sure how to proceed. Doesn't the device support class get destroyed with every disconnect? And if so, wouldn't that cause the firmware version cache to never be filled when connecting to the device?
Owner

It gets filled in setContext, when we attach a device to the support class:

public void setContext(final GBDevice device, final BluetoothAdapter adapter, final Context context) {
// FIXME unsetDynamicState unsets the fw version, which causes problems..
if (device.getFirmwareVersion() != null) {
setCachedFirmwareVersion(device.getFirmwareVersion());
}

It gets filled in setContext, when we attach a device to the support class: https://codeberg.org/Freeyourgadget/Gadgetbridge/src/commit/404e432adfe2f3287c4ac3aef8c212d4c2f5f6be/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java#L189-L193
arjan5 marked this conversation as resolved
@ -0,0 +126,4 @@
getDevice().setFirmwareVersion2("N/A");
deviceInfoProfile.requestDeviceInfo(builder);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
Owner

Should this actually be moved to postConnectInitialization?

Should this actually be moved to `postConnectInitialization`?
Author
Owner

I don't think so, because the commands in postConnectInitialization are not waiting for the responses from the device. Moving INITIALIZED to there would effectively make no difference.

I don't think so, because the commands in `postConnectInitialization` are not waiting for the responses from the device. Moving `INITIALIZED` to there would effectively make no difference.
arjan5 marked this conversation as resolved
@ -0,0 +129,4 @@
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
builder.notify(getCharacteristic(ColmiR02Constants.CHARACTERISTIC_SERVICE), true);
builder.notify(getCharacteristic(ColmiR02Constants.CHARACTERISTIC_WRITE), true);
Owner

I don't have the btsnoops at hand here, but do we get incoming notifications in the write characteristic, or should this be removed?

I don't have the btsnoops at hand here, but do we get incoming notifications in the write characteristic, or should this be removed?
Author
Owner

Need to check, but I think so far I've indeed only observed incoming packets on both notification characteristics, not on the service and write characteristics.

Need to check, but I think so far I've indeed only observed incoming packets on both notification characteristics, not on the service and write characteristics.
arjan5 marked this conversation as resolved
@ -0,0 +255,4 @@
case ColmiR02Constants.CMD_GOALS:
ByteBuffer stepsData = ByteBuffer.allocate(4);
stepsData.order(ByteOrder.LITTLE_ENDIAN);
stepsData.put(0, value[2]);
Owner

I feel tempted to extract these to a toBeUint24 similar to BLETypeConversions but big-endian, would simplify these.

I feel tempted to extract these to a `toBeUint24` similar to `BLETypeConversions` but big-endian, would simplify these.
Author
Owner

Yes!

Yes!
arjan5 marked this conversation as resolved
@ -0,0 +343,4 @@
sampleCal.set(Calendar.HOUR_OF_DAY, value[4] / 4);
sampleCal.set(Calendar.MINUTE, 0);
sampleCal.set(Calendar.SECOND, 0);
ByteBuffer calories = ByteBuffer.allocate(2);
Owner

Same here, I think we should just have int calories = toBeUint16(value, 7); or something.

Same here, I think we should just have `int calories = toBeUint16(value, 7);` or something.
arjan5 marked this conversation as resolved
@ -0,0 +506,4 @@
Calendar now = GregorianCalendar.getInstance();
byte[] setDateTimePacket = buildPacket(new byte[]{
ColmiR02Constants.CMD_SET_DATE_TIME,
GB.hexStringToByteArray(String.format("%1$2s", now.get(Calendar.YEAR) % 100).replace(' ', '0'))[0],
Owner

So we want the actual numbers as hex strings - because why not..

I think this might be slightly simpler to understand:

Byte.parseByte(String.valueOf(now.get(Calendar.MONTH)), 16)

So we want the actual numbers as hex strings - because why not.. I think this might be slightly simpler to understand: `Byte.parseByte(String.valueOf(now.get(Calendar.MONTH)), 16)`
Author
Owner

Need to check the output, but if it's the same, it's indeed much more readable.

Need to check the output, but if it's the same, it's indeed much more readable.
arjan5 marked this conversation as resolved
@ -1614,6 +1614,7 @@
<string name="devicetype_redmi_watch_2_lite">Redmi Watch 2 Lite</string>
<string name="devicetype_redmi_smart_band_pro">Redmi Smart Band Pro</string>
<string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
<string name="devicetype_colmi_r02">Colmi R02 Smart Ring</string>
Owner

nitpick, but do we really need the "Smart Ring" suffix?

nitpick, but do we really need the "Smart Ring" suffix?
Author
Owner

Probably not...

Probably not...
arjan5 marked this conversation as resolved
Owner

Break up and modularize the code, the device support class is way too large now

off-topic, but we have much worse classes :')

$ find . -name "*Support.java" | while read f; do wc -l "$f"; done | sort -h -r | head -20    
4316 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
2233 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java
2131 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java
2109 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
1677 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java
1454 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java
1282 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
1265 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java
1226 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
1071 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java
1066 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java
1031 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java
909 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java
870 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java
851 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java
760 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java
724 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java
719 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java
718 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java
661 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java
> Break up and modularize the code, the device support class is way too large now off-topic, but we have much worse classes :') ``` $ find . -name "*Support.java" | while read f; do wc -l "$f"; done | sort -h -r | head -20 4316 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java 2233 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/zetime/ZeTimeDeviceSupport.java 2131 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java 2109 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java 1677 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsSupport.java 1454 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/fitpro/FitProDeviceSupport.java 1282 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java 1265 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java 1226 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java 1071 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java 1066 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/makibeshr3/MakibesHR3DeviceSupport.java 1031 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java 909 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java 870 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/GarminSupport.java 851 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java 760 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/withingssteelhr/WithingsSteelHRDeviceSupport.java 724 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gb6900/CasioGB6900DeviceSupport.java 719 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xiaomi/XiaomiSupport.java 718 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casio/gbx100/CasioGBX100DeviceSupport.java 661 ./app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java ```
Author
Owner

Break up and modularize the code, the device support class is way too large now

off-topic, but we have much worse classes :')

I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging.

> > Break up and modularize the code, the device support class is way too large now > > off-topic, but we have much worse classes :') I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging.
@ -0,0 +188,4 @@
int levelResponse = value[1];
LOG.info("Received battery level response: {}%", levelResponse);
GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
batteryEvent.state = BatteryState.BATTERY_NORMAL;
Owner

value[2] is 1 when charging

`value[2]` is 1 when charging
arjan5 marked this conversation as resolved
@ -0,0 +136,4 @@
// Delay initialization with 2 seconds to give the ring time to settle
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> {
postConnectInitialization();
Owner

When the ring is stationary, Gadgetbridge will sometimes crash:

21:16:40.724 [binder:10005_6] WARN  n.f.g.s.b.TransactionBuilder - Unable to read characteristic: null
21:16:40.727 [binder:10005_6] WARN  n.f.g.s.b.TransactionBuilder - Unable to notify characteristic: null
21:16:40.730 [binder:10005_6] WARN  n.f.g.s.b.TransactionBuilder - Unable to notify characteristic: null
21:16:40.737 [binder:10005_6] DEBUG n.f.g.s.b.BtLEQueue - about to add: 21:16:40: Transaction task: Initializing device with 12 actions
21:16:40.750 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: SetDeviceStateAction to INITIALIZING
21:16:40.763 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: ReadAction on characteristic: 00002a29-0000-1000-8000-00805f9b34fb
...
21:16:41.432 [binder:10005_4] INFO  n.f.g.s.AbstractDeviceSupport - Got event for VERSION_INFO: GBDeviceEventVersionInfo: fwVersion: R02_3.00.06_240523; fwVersion2: null; hwVersion: R02_V3.0
...
21:16:41.437 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: SetDeviceStateAction to INITIALIZED
21:16:41.441 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: NotifyAction on characteristic: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
21:16:41.442 [Gadgetbridge GATT Dispatcher] WARN  n.f.g.s.b.a.NotifyAction - Descriptor CLIENT_CHARACTERISTIC_CONFIGURATION for characteristic 6e400002-b5a3-f393-e0a9-e50e24dcca9e is null
21:16:41.447 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: NotifyAction on characteristic: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
...
21:16:42.138 [main] INFO  n.f.g.s.d.c.ColmiR02DeviceSupport - Set date/time request sent: 012406192116420000000000000000BD
21:16:42.140 [main] ERROR n.f.g.LoggingExceptionHandler - Uncaught exception: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference
	at nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder.queue(TransactionBuilder.java:152)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.sendWrite(ColmiR02DeviceSupport.java:490)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.setDateTime(ColmiR02DeviceSupport.java:517)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.postConnectInitialization(ColmiR02DeviceSupport.java:146)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.lambda$initializeDevice$0$nodomain-freeyourgadget-gadgetbridge-service-devices-colmi-ColmiR02DeviceSupport(ColmiR02DeviceSupport.java:139)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
	at android.os.Handler.handleCallback(Handler.java:958)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:230)
	at android.os.Looper.loop(Looper.java:319)
	at android.app.ActivityThread.main(ActivityThread.java:8919)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)

So I think we might want to check whether all characteristics are non-null before proceeding with initialization, and maybe unregister this handler if the support class got disposed (this is almost exactly the same issue as #3903)

When the ring is stationary, Gadgetbridge will sometimes crash: ``` 21:16:40.724 [binder:10005_6] WARN n.f.g.s.b.TransactionBuilder - Unable to read characteristic: null 21:16:40.727 [binder:10005_6] WARN n.f.g.s.b.TransactionBuilder - Unable to notify characteristic: null 21:16:40.730 [binder:10005_6] WARN n.f.g.s.b.TransactionBuilder - Unable to notify characteristic: null 21:16:40.737 [binder:10005_6] DEBUG n.f.g.s.b.BtLEQueue - about to add: 21:16:40: Transaction task: Initializing device with 12 actions 21:16:40.750 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: SetDeviceStateAction to INITIALIZING 21:16:40.763 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: ReadAction on characteristic: 00002a29-0000-1000-8000-00805f9b34fb ... 21:16:41.432 [binder:10005_4] INFO n.f.g.s.AbstractDeviceSupport - Got event for VERSION_INFO: GBDeviceEventVersionInfo: fwVersion: R02_3.00.06_240523; fwVersion2: null; hwVersion: R02_V3.0 ... 21:16:41.437 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: SetDeviceStateAction to INITIALIZED 21:16:41.441 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: NotifyAction on characteristic: 6e400002-b5a3-f393-e0a9-e50e24dcca9e 21:16:41.442 [Gadgetbridge GATT Dispatcher] WARN n.f.g.s.b.a.NotifyAction - Descriptor CLIENT_CHARACTERISTIC_CONFIGURATION for characteristic 6e400002-b5a3-f393-e0a9-e50e24dcca9e is null 21:16:41.447 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: July 19 at 21:16: NotifyAction on characteristic: 6e400003-b5a3-f393-e0a9-e50e24dcca9e ... 21:16:42.138 [main] INFO n.f.g.s.d.c.ColmiR02DeviceSupport - Set date/time request sent: 012406192116420000000000000000BD 21:16:42.140 [main] ERROR n.f.g.LoggingExceptionHandler - Uncaught exception: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference java.lang.NullPointerException: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference at nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder.queue(TransactionBuilder.java:152) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.sendWrite(ColmiR02DeviceSupport.java:490) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.setDateTime(ColmiR02DeviceSupport.java:517) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.postConnectInitialization(ColmiR02DeviceSupport.java:146) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport.lambda$initializeDevice$0$nodomain-freeyourgadget-gadgetbridge-service-devices-colmi-ColmiR02DeviceSupport(ColmiR02DeviceSupport.java:139) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR02DeviceSupport$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at android.os.Handler.handleCallback(Handler.java:958) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:230) at android.os.Looper.loop(Looper.java:319) at android.app.ActivityThread.main(ActivityThread.java:8919) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) ``` So I think we might want to check whether all characteristics are non-null before proceeding with initialization, and maybe unregister this handler if the support class got disposed (this is almost exactly the same issue as #3903)
Author
Owner

Okay, I hope this change will resolve this crash: b5cd97f25f..1b5a45496b

Okay, I hope this change will resolve this crash: https://codeberg.org/Freeyourgadget/Gadgetbridge/compare/b5cd97f25f6fa72a58e429ef61b4ff695e4bd2ab..1b5a45496bd68c63abdf8c5cee64ac459ff67b1e
arjan5 marked this conversation as resolved
Contributor

Break up and modularize the code, the device support class is way too large now

off-topic, but we have much worse classes :')

I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging.

as someone with the ring and much in favor of gadgetbridge, id prefer to somehow test your implemented features sooner than later, if that makes any sense. Maybe mark it WIP and merge it for people to test?

thanks so much for implementing this device, with the added attention also from hackaday, i assume this is something that many users are looking forward to!

> > > Break up and modularize the code, the device support class is way too large now > > > > off-topic, but we have much worse classes :') > > I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging. as someone with the ring and much in favor of gadgetbridge, id prefer to somehow test your implemented features sooner than later, if that makes any sense. Maybe mark it WIP and merge it for people to test? thanks so much for implementing this device, with the added attention also from hackaday, i assume this is something that many users are looking forward to!
First-time contributor

Break up and modularize the code, the device support class is way too large now

off-topic, but we have much worse classes :')

I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging.

as someone with the ring and much in favor of gadgetbridge, id prefer to somehow test your implemented features sooner than later, if that makes any sense. Maybe mark it WIP and merge it for people to test?

thanks so much for implementing this device, with the added attention also from hackaday, i assume this is something that many users are looking forward to!

Ditto - very happy to test the existing implementation and provide feedback. Big thanks for all your work! Excited about getting this ring working in Gadgetbridge.

> > > > Break up and modularize the code, the device support class is way too large now > > > > > > off-topic, but we have much worse classes :') > > > > I know :) My todo was mainly to bring more structure to the class, not necessarily to break it up. It's mostly coded as a proof of concept now, I really want to prettify it before (future) merging. > > as someone with the ring and much in favor of gadgetbridge, id prefer to somehow test your implemented features sooner than later, if that makes any sense. Maybe mark it WIP and merge it for people to test? > > thanks so much for implementing this device, with the added attention also from hackaday, i assume this is something that many users are looking forward to! Ditto - very happy to test the existing implementation and provide feedback. Big thanks for all your work! Excited about getting this ring working in Gadgetbridge.
Author
Owner

I can share my custom build containing the current changes. But it's signed with my personal development key, so it can't be used to upgrade/replace an existing GB installation, nor can it be installed alongside another GB build.

I can share my custom build containing the current changes. But it's signed with my personal development key, so it can't be used to upgrade/replace an existing GB installation, nor can it be installed alongside another GB build.
First-time contributor

I can share my custom build containing the current changes. But it's signed with my personal development key, so it can't be used to upgrade/replace an existing GB installation, nor can it be installed alongside another GB build.

Thanks! I'll give it a go and report back.

> I can share my custom build containing the current changes. But it's signed with my personal development key, so it can't be used to upgrade/replace an existing GB installation, nor can it be installed alongside another GB build. Thanks! I'll give it a go and report back.
First-time contributor

Received a $20 Colmi R02 this morning from temu, and just replaced my GadgetBridge install with your gb_colmi_r02.APK. It connected right away and I am currently collecting data! Nice work! I am interested in the getting the ring to continuously monitor my heart rate while I swim, but I am relatively new to GadgetBridge. I am excited to possibly develop my own solution, without having had to dig through the raw BLE myself, you rock! :) Please let me know if I can help test in any other way, thanks for working on this and sharing!

Received a $20 Colmi R02 this morning from temu, and just replaced my GadgetBridge install with your gb_colmi_r02.APK. It connected right away and I am currently collecting data! Nice work! I am interested in the getting the ring to continuously monitor my heart rate while I swim, but I am relatively new to GadgetBridge. I am excited to possibly develop my own solution, without having had to dig through the raw BLE myself, you rock! :) Please let me know if I can help test in any other way, thanks for working on this and sharing!
First-time contributor

Note that the Colmi R02, Colmi R03, and Colmi R06 all have exactly the same hardware so this pull request should work on all 3. Need to be tested just to be safe though.

See: https://www.reddit.com/r/SmartRings/comments/1d43edc/another_clone_colmi_r06

Edit: I just tested and I confirm that it works without issues on my Colmi R06.

Note that the Colmi R02, Colmi R03, and Colmi R06 all have exactly the same hardware so this pull request should work on all 3. Need to be tested just to be safe though. See: https://www.reddit.com/r/SmartRings/comments/1d43edc/another_clone_colmi_r06 **Edit**: I just tested and I confirm that it works without issues on my Colmi R06.
First-time contributor

@arjan5 I did some reverse engineering and here are my findings:

List of services:

UUID_SERVICE = "6e40fff0-b5a3-f393-e0a9-e50e24dcca9e"
UUID_READ = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
UUID_WRITE = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
GATT_NOTIFY_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"
SERVICE_DEVICE_INFO = "0000180a-0000-1000-8000-00805f9b34fb"
CHAR_FIRMWARE_REVISION = "00002a26-0000-1000-8000-00805f9b34fb"
CHAR_HW_REVISION = "00002a27-0000-1000-8000-00805f9b34fb"
CHAR_SOFTWARE_REVISION = "00002a28-0000-1000-8000-00805f9b34fb"
SERIAL_PORT_SERVICE = "de5bf728-d711-4e47-af26-65e3012a5dc7"
SERIAL_PORT_CHARACTER_NOTIFY = "de5bf729-d711-4e47-af26-65e3012a5dc7"
SERIAL_PORT_CHARACTER_WRITE = "de5bf72a-d711-4e47-af26-65e3012a5dc7"

List of commands:

Note that QRing supports a variety of devices and only a subset of the following commands are implemented on the Colmi R02/R03/R06.

CMD_SET_DEVICE_TIME = 0x01;
CMD_TAKING_PICTURE = 0x02;
CMD_GET_DEVICE_ELECTRICITY_VALUE = 0x03;
CMD_SET_PHONE_OS = 0x04;
CMD_FANWAN = 0x05;
CMD_MUTE = 0x06;
CMD_GET_STEP_TOTAL_SOMEDAY = 0x07;
CMD_RE_BOOT = 0x08;
CMD_INTELL = 0x09;
CMD_GET_TIME_SETTING = 0x0a;
CMD_BP_TIMING_MONITOR_SWITCH = 0x0c;
CMD_BP_TIMING_MONITOR_DATA = 0x0d;
CMD_HR_TIMING_MONITOR_DATA = 0x0d;
CMD_BP_TIMING_MONITOR_CONFIRM = 0x0e;
CMD_HR_TIMING_MONITOR_CONFIRM = 0x0e;
CMD_BIND_SUCCESS = 0x10;
CMD_PHONE_NOTIFY = 0x11;
CMD_DISPLAY_CLOCK = 0x12;
CMD_GET_SPORT = 0x13;
CMD_GET_BAND_PRESSURE = 0x14;
CMD_GET_HEART_RATE = 0x15;
CMD_HR_TIMING_MONITOR_SWITCH = 0x16;
CMD_GET_PERSONALIZATION_SETTING = 0x17;
CMD_GET_DEGREE_SWITCH = 0x19;
CMD_SEND_WEATHER_FORECAST = 0x1a;
CMD_GET_BRIGHTNESS = 0x1b;
CMD_GET_MUSIC_SWITCH = 0x1c;
CMD_MUSIC_COMMAND = 0x1d;
CMD_REAL_TIME_HEART_RATE = 0x1e;
CMD_DISPLAY_TIME = 0x1f;
CMD_CALIBRATION_RATE = 0x20;
CMD_TARGET_SETTING = 0x21;
CMD_FIND_THE_PHONE = 0x22;
CMD_SET_ALARM_CLOCK = 0x23;
CMD_GET_ALARM_CLOCK = 0x24;
CMD_SET_SIT_LONG = 0x25;
CMD_GET_SIT_LONG = 0x26;
CMD_SET_DRINK_TIME = 0x27;
CMD_GET_DRINK_TIME = 0x28;
CMD_ORIENTATION = 0x29;
CMD_DISPLAY_STYLE = 0x2a;
CMD_MENSTRUATION = 0x2b;
CMD_AUTO_BLOOD_OXYGEN = 0x2c;
CMD_BLACKLIST_LOCATION = 0x2d;
CMD_PACKAGE_LENGTH = 0x2f;
CMD_AGPS_SWITCH = 0x30;
CMD_DEVICE_AVATAR = 0x32;
CMD_PRESSURE_SETTING = 0x36;
CMD_PRESSURE = 0x37;
CMD_HRV_ENABLE = 0x38;
CMD_HRV = 0x39;
CMD_GET_STEP_SOMEDAY_DETAIL = 0x43;
CMD_GET_SLEEP = 0x44;
CMD_QUERY_DATA_DISTRIBUTION = 0x46;
CMD_GET_STEP_TODAY = 0x48;
CMD_ANTI_LOST_RATE = 0x50;
CMD_GPS_ONLINE = 0x54;
CMD_SET_ANCS_ON_OFF = 0x60;
CMD_GET_ANCS_ON_OFF = 0x61;
CMD_START_HEART_RATE = 0x69;
CMD_STOP_HEART_RATE = 0x6a;
CMD_HEALTH_ECG_START = 0x6c;
CMD_HEALTH_ECG_DATA = 0x6d;
CMD_HEALTH_PPG_DATA = 0x6e;
CMD_ECG_STATUS_DATA = 0x6f;
CMD_ECG_MEASURE_TIME = 0x70;
CMD_MSG_NOTIFY = 0x72;
CMD_PUSH_MSG = 0x72;
CMD_DEVICE_NOTIFY = 0x73;
CMD_TUNE_TIME_DIRECT = 0x73;
CMD_PHONE_GPS = 0x74;
CMD_TUNE_TIME_INVERSE = 0x74;
CMD_PHONE_SPORT = 0x77;
CMD_PHONE_SPORT_N0TIFY = 0x78;
CMD_MSG_GET_HW_AND_FW_VERSION = 0x93;
CMD_TEST_OPEN = 0xc9;
CMD_TEST_CLOSE = 0xca;
CMD_RE_STORE = 0xff;
@arjan5 I did some reverse engineering and here are my findings: **List of services:** ```c UUID_SERVICE = "6e40fff0-b5a3-f393-e0a9-e50e24dcca9e" UUID_READ = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" UUID_WRITE = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" GATT_NOTIFY_CONFIG = "00002902-0000-1000-8000-00805f9b34fb" SERVICE_DEVICE_INFO = "0000180a-0000-1000-8000-00805f9b34fb" CHAR_FIRMWARE_REVISION = "00002a26-0000-1000-8000-00805f9b34fb" CHAR_HW_REVISION = "00002a27-0000-1000-8000-00805f9b34fb" CHAR_SOFTWARE_REVISION = "00002a28-0000-1000-8000-00805f9b34fb" SERIAL_PORT_SERVICE = "de5bf728-d711-4e47-af26-65e3012a5dc7" SERIAL_PORT_CHARACTER_NOTIFY = "de5bf729-d711-4e47-af26-65e3012a5dc7" SERIAL_PORT_CHARACTER_WRITE = "de5bf72a-d711-4e47-af26-65e3012a5dc7" ``` **List of commands:** Note that QRing supports a variety of devices and only a subset of the following commands are implemented on the Colmi R02/R03/R06. ```c CMD_SET_DEVICE_TIME = 0x01; CMD_TAKING_PICTURE = 0x02; CMD_GET_DEVICE_ELECTRICITY_VALUE = 0x03; CMD_SET_PHONE_OS = 0x04; CMD_FANWAN = 0x05; CMD_MUTE = 0x06; CMD_GET_STEP_TOTAL_SOMEDAY = 0x07; CMD_RE_BOOT = 0x08; CMD_INTELL = 0x09; CMD_GET_TIME_SETTING = 0x0a; CMD_BP_TIMING_MONITOR_SWITCH = 0x0c; CMD_BP_TIMING_MONITOR_DATA = 0x0d; CMD_HR_TIMING_MONITOR_DATA = 0x0d; CMD_BP_TIMING_MONITOR_CONFIRM = 0x0e; CMD_HR_TIMING_MONITOR_CONFIRM = 0x0e; CMD_BIND_SUCCESS = 0x10; CMD_PHONE_NOTIFY = 0x11; CMD_DISPLAY_CLOCK = 0x12; CMD_GET_SPORT = 0x13; CMD_GET_BAND_PRESSURE = 0x14; CMD_GET_HEART_RATE = 0x15; CMD_HR_TIMING_MONITOR_SWITCH = 0x16; CMD_GET_PERSONALIZATION_SETTING = 0x17; CMD_GET_DEGREE_SWITCH = 0x19; CMD_SEND_WEATHER_FORECAST = 0x1a; CMD_GET_BRIGHTNESS = 0x1b; CMD_GET_MUSIC_SWITCH = 0x1c; CMD_MUSIC_COMMAND = 0x1d; CMD_REAL_TIME_HEART_RATE = 0x1e; CMD_DISPLAY_TIME = 0x1f; CMD_CALIBRATION_RATE = 0x20; CMD_TARGET_SETTING = 0x21; CMD_FIND_THE_PHONE = 0x22; CMD_SET_ALARM_CLOCK = 0x23; CMD_GET_ALARM_CLOCK = 0x24; CMD_SET_SIT_LONG = 0x25; CMD_GET_SIT_LONG = 0x26; CMD_SET_DRINK_TIME = 0x27; CMD_GET_DRINK_TIME = 0x28; CMD_ORIENTATION = 0x29; CMD_DISPLAY_STYLE = 0x2a; CMD_MENSTRUATION = 0x2b; CMD_AUTO_BLOOD_OXYGEN = 0x2c; CMD_BLACKLIST_LOCATION = 0x2d; CMD_PACKAGE_LENGTH = 0x2f; CMD_AGPS_SWITCH = 0x30; CMD_DEVICE_AVATAR = 0x32; CMD_PRESSURE_SETTING = 0x36; CMD_PRESSURE = 0x37; CMD_HRV_ENABLE = 0x38; CMD_HRV = 0x39; CMD_GET_STEP_SOMEDAY_DETAIL = 0x43; CMD_GET_SLEEP = 0x44; CMD_QUERY_DATA_DISTRIBUTION = 0x46; CMD_GET_STEP_TODAY = 0x48; CMD_ANTI_LOST_RATE = 0x50; CMD_GPS_ONLINE = 0x54; CMD_SET_ANCS_ON_OFF = 0x60; CMD_GET_ANCS_ON_OFF = 0x61; CMD_START_HEART_RATE = 0x69; CMD_STOP_HEART_RATE = 0x6a; CMD_HEALTH_ECG_START = 0x6c; CMD_HEALTH_ECG_DATA = 0x6d; CMD_HEALTH_PPG_DATA = 0x6e; CMD_ECG_STATUS_DATA = 0x6f; CMD_ECG_MEASURE_TIME = 0x70; CMD_MSG_NOTIFY = 0x72; CMD_PUSH_MSG = 0x72; CMD_DEVICE_NOTIFY = 0x73; CMD_TUNE_TIME_DIRECT = 0x73; CMD_PHONE_GPS = 0x74; CMD_TUNE_TIME_INVERSE = 0x74; CMD_PHONE_SPORT = 0x77; CMD_PHONE_SPORT_N0TIFY = 0x78; CMD_MSG_GET_HW_AND_FW_VERSION = 0x93; CMD_TEST_OPEN = 0xc9; CMD_TEST_CLOSE = 0xca; CMD_RE_STORE = 0xff; ```
@ -0,0 +29,4 @@
public static final byte CMD_BATTERY = 0x03;
public static final byte CMD_POWER_OFF = 0x08;
public static final byte CMD_PREFERENCES = 0x0a;
public static final byte CMD_SYNC_HEART_RATE = 0x15;
First-time contributor

There are three more HR commands:

CMD_HR_TIMING_MONITOR_DATA = 0x0d;
CMD_HR_TIMING_MONITOR_CONFIRM = 0x0e;

CMD_REAL_TIME_HEART_RATE = 0x1e;
There are three more HR commands: ```c CMD_HR_TIMING_MONITOR_DATA = 0x0d; CMD_HR_TIMING_MONITOR_CONFIRM = 0x0e; CMD_REAL_TIME_HEART_RATE = 0x1e; ```
arjan5 marked this conversation as resolved
@ -0,0 +37,4 @@
public static final byte CMD_SYNC_STRESS = 0x37;
public static final byte CMD_SYNC_ACTIVITY = 0x43;
public static final byte CMD_FIND_DEVICE = 0x50;
public static final byte CMD_MANUAL_HEART_RATE = 0x69;
First-time contributor

Careful, there are two commands for that:

CMD_START_HEART_RATE = 0x69;
CMD_STOP_HEART_RATE = 0x6a;
Careful, there are two commands for that: ```c CMD_START_HEART_RATE = 0x69; CMD_STOP_HEART_RATE = 0x6a; ```
Author
Owner

It stops by itself after a few seconds (10 or so), so no harm keeping it this way

It stops by itself after a few seconds (10 or so), so no harm keeping it this way
First-time contributor

I just had a weird issue where the green LED was continuously blinking without stopping. I opened Gadgetbridge and it showed the ring in a disconnected state. As soon as I connected the ring by clicking on it the green LED stopped blinking.

No action needed — this looks like a bug and it is probably unrelated to Gadgetbridge — but I just thought it would be interesting to report this data point.

Here again using the CMD_STOP_HEART_RATE command was not necessary, which gives extra confidence that skipping it is fine.

I just had a weird issue where the green LED was continuously blinking without stopping. I opened Gadgetbridge and it showed the ring in a disconnected state. As soon as I connected the ring by clicking on it the green LED stopped blinking. No action needed — this looks like a bug and it is probably unrelated to Gadgetbridge — but I just thought it would be interesting to report this data point. Here again using the `CMD_STOP_HEART_RATE` command was not necessary, which gives extra confidence that skipping it is fine.
arjan5 marked this conversation as resolved
devnoname120 left a comment
First-time contributor

I have more comments to make but I'm on the run right now. Will try tonight.

I have more comments to make but I'm on the run right now. Will try tonight.
@ -0,0 +149,4 @@
requestSettingsFromRing();
// Testing packets, these are sent by the official app, but don't seem to be necessary
// byte[] unknownRequest1 = buildPacket(new byte[]{0x61});
First-time contributor
CMD_GET_ANCS_ON_OFF = 0x61;
```c CMD_GET_ANCS_ON_OFF = 0x61; ```
arjan5 marked this conversation as resolved
@ -0,0 +152,4 @@
// byte[] unknownRequest1 = buildPacket(new byte[]{0x61});
// LOG.info("Unknown request sent: {}", StringUtils.bytesToHex(unknownRequest1));
// sendWrite("unknownRequest1", unknownRequest1);
// byte[] unknownRequest2 = buildPacket(new byte[]{0x48});
First-time contributor
CMD_GET_STEP_TODAY = 0x48;
```c CMD_GET_STEP_TODAY = 0x48; ```
arjan5 marked this conversation as resolved
@ -0,0 +155,4 @@
// byte[] unknownRequest2 = buildPacket(new byte[]{0x48});
// LOG.info("Unknown request sent: {}", StringUtils.bytesToHex(unknownRequest2));
// sendWrite("unknownRequest2", unknownRequest2);
// byte[] unknownRequest3 = buildPacket(new byte[]{0x38, 0x01});
First-time contributor
CMD_HRV_ENABLE = 0x38;
```c CMD_HRV_ENABLE = 0x38; ```
First-time contributor

I wonder if the HRV work? Some people reported that it was available after a firmware update. Maybe we could consider looking into it at a later point? IMO it's not a must-have though and it could be added with another PR down the road.

I wonder if the HRV work? Some people reported that it was available after a firmware update. Maybe we could consider looking into it at a later point? IMO it's not a must-have though and it could be added with another PR down the road.
Author
Owner

It doesn't with my ring, that's clear from the bluetooth dump. The setting (0x38) is enabled by QRing and even confirmed by the ring. But subsequent HRV data requests (0x39) go unanswered by the ring. I haven't looked into firmware updates yet, but will do that perhaps later. I'd rather get this PR over the finish line first.

It doesn't with my ring, that's clear from the bluetooth dump. The setting (0x38) is enabled by QRing and even confirmed by the ring. But subsequent HRV data requests (0x39) go unanswered by the ring. I haven't looked into firmware updates yet, but will do that perhaps later. I'd rather get this PR over the finish line first.
arjan5 marked this conversation as resolved
@ -0,0 +158,4 @@
// byte[] unknownRequest3 = buildPacket(new byte[]{0x38, 0x01});
// LOG.info("Unknown request sent: {}", StringUtils.bytesToHex(unknownRequest3));
// sendWrite("unknownRequest3", unknownRequest3);
// byte[] unknownRequest4 = buildPacket(new byte[]{0x0c, 0x01});
First-time contributor
CMD_BP_TIMING_MONITOR_SWITCH = 0x0c;
```c CMD_BP_TIMING_MONITOR_SWITCH = 0x0c; ```
arjan5 marked this conversation as resolved
@ -0,0 +161,4 @@
// byte[] unknownRequest4 = buildPacket(new byte[]{0x0c, 0x01});
// LOG.info("Unknown request sent: {}", StringUtils.bytesToHex(unknownRequest4));
// sendWrite("unknownRequest4", unknownRequest4);
// byte[] unknownRequest5 = buildPacket(new byte[]{0x0a, 0x01});
First-time contributor
CMD_GET_TIME_SETTING = 0x0a;
```c CMD_GET_TIME_SETTING = 0x0a; ```
arjan5 marked this conversation as resolved
@ -0,0 +548,4 @@
public void onSetHeartRateMeasurementInterval(int seconds) {
// Round to nearest 5 minutes and limit to 60 minutes due to device constraints
long hrIntervalMins = Math.min(Math.round(seconds / 60.0 / 5.0) * 5, 60);
byte[] hrIntervalPacket = buildPacket(new byte[]{ColmiR02Constants.CMD_SET_HR_INTERVAL, 0x02, 0x01, (byte) hrIntervalMins});
First-time contributor

Note that the second byte of the packet can be set to 0x02 instead of 0x01 which means disable the HR measurement:

buildPacket(new byte[]{ColmiR02Constants.CMD_SET_HR_INTERVAL, 0x02, 0x02, (byte) 5})
Note that the second byte of the packet can be set to `0x02` instead of `0x01` which means disable the HR measurement: ```java buildPacket(new byte[]{ColmiR02Constants.CMD_SET_HR_INTERVAL, 0x02, 0x02, (byte) 5}) ```
arjan5 marked this conversation as resolved
@ -0,0 +589,4 @@
@Override
public void onPowerOff() {
byte[] poweroffPacket = buildPacket(new byte[]{ColmiR02Constants.CMD_POWER_OFF, 0x01});
First-time contributor

Does it really power off the ring or does it reboot it?

CMD_RE_BOOT = 0x08;
Does it really power off the ring or does it reboot it? ```c CMD_RE_BOOT = 0x08; ```
Author
Owner

From my testing it appears to be both. If the ring is stationary when this command is sent, it will fully power off, requiring the charger to be connected to boot up again. Of the ring is moving (worn) when this command is sent, it will reboot.

From my testing it appears to be both. If the ring is stationary when this command is sent, it will fully power off, requiring the charger to be connected to boot up again. Of the ring is moving (worn) when this command is sent, it will reboot.
First-time contributor

Good to know thanks!

Good to know thanks!
arjan5 marked this conversation as resolved
Author
Owner

@devnoname120 Thank you for the valuable information! This will certainly help getting the remaining tasks done quicker.

Please be aware that you may not share code or even logic from the decompiled original app. We must steer clear from potential legal issues, which could arise from using copyrighted original code or logic. Decompilation is a sensitive thing in some jurisdictions, so we have to stay on the safe side. Just sharing protocol information obtained through decompilation for interoperability purposes like you have done is fine though.

@devnoname120 Thank you for the valuable information! This will certainly help getting the remaining tasks done quicker. Please be aware that you may not share code or even logic from the decompiled original app. We must steer clear from potential legal issues, which could arise from using copyrighted original code or logic. Decompilation is a sensitive thing in some jurisdictions, so we have to stay on the safe side. Just sharing protocol information obtained through decompilation for interoperability purposes like you have done is fine though.
First-time contributor

@arjan5 Thanks for the heads-up. If you need help figuring out how to use a specific command or how to parse the response don't hesitate to let me know!

Edit: oh and btw I patched the QRing app to enable the debug mode + logging. Not sure if I can post it there, let me know. It could help a bit (the logging isn't amazing though).

@arjan5 Thanks for the heads-up. If you need help figuring out how to use a specific command or how to parse the response don't hesitate to let me know! **Edit**: oh and btw I patched the QRing app to enable the debug mode + logging. Not sure if I can post it there, let me know. It could help a bit (the logging isn't amazing though).
First-time contributor

Some feedback:

I installed arjan5's custom Colmi R02 Gadgetbridge apk. I had not installed any version of Gadgetbridge on this phone before. I connected to the R02 fine (first app the ring had been connected to since taking it out of the box). Gadgetbridge took a heart rate reading - laughably inaccurate (about 30bpm out!). I pressed the fetch data button and some popups appeared at the bottom of the screen with very long hexadecimal numbers.

I checked the step counter but it said 0 steps. I could not work out why the steps data was not working.

I then connected the ring to the QRing app. Again it gave a heart rate reading the same as Gadgetbridge (ie still 30bpm out). The steps were increasing in real time as I shook my hand, but they would frequently reset to 0, like every 2 seconds or so. Whenever I checked the graphs in QRing it showed 0 steps.

My ring appears to be resetting the step counter very frequently. I'm not sure why. I only wanted it for the step counter! Is my ring broken or do they all do this?

Thanks arjan5 and others for all your efforts so far.

Some feedback: I installed arjan5's custom Colmi R02 Gadgetbridge apk. I had not installed any version of Gadgetbridge on this phone before. I connected to the R02 fine (first app the ring had been connected to since taking it out of the box). Gadgetbridge took a heart rate reading - laughably inaccurate (about 30bpm out!). I pressed the fetch data button and some popups appeared at the bottom of the screen with very long hexadecimal numbers. I checked the step counter but it said 0 steps. I could not work out why the steps data was not working. I then connected the ring to the QRing app. Again it gave a heart rate reading the same as Gadgetbridge (ie still 30bpm out). The steps were increasing in real time as I shook my hand, but they would frequently reset to 0, like every 2 seconds or so. Whenever I checked the graphs in QRing it showed 0 steps. My ring appears to be resetting the step counter very frequently. I'm not sure why. I only wanted it for the step counter! Is my ring broken or do they all do this? Thanks arjan5 and others for all your efforts so far.
Author
Owner

Well, that shouldn't be happening obviously. The steps counter doesn't reset on my ring.
It sounds like the hardware is kinda working though, so perhaps it could help to do a factory reset from QRing, and see if there is a newer firmware available?
Also, my HR measurements are incredibly close to those measured by my Skagen (Fossil) hybrid watch, so I think my ring works pretty well for that too.

Well, that shouldn't be happening obviously. The steps counter doesn't reset on my ring. It sounds like the hardware is kinda working though, so perhaps it could help to do a factory reset from QRing, and see if there is a newer firmware available? Also, my HR measurements are incredibly close to those measured by my Skagen (Fossil) hybrid watch, so I think my ring works pretty well for that too.
First-time contributor

@lollapalooza I don't have any of these issues with my ring. I'd recommend you to use QRing for a while before using GadgetBridge to make sure that these issues are GadgetBridge-specific.

@lollapalooza I don't have any of these issues with my ring. I'd recommend you to use QRing for a while before using GadgetBridge to make sure that these issues are GadgetBridge-specific.
First-time contributor

Just got the ring today. it is connecting to the app but it also disconnects frequently. Also none of the data is updating real time. I tried a one time measurement and it seems pretty off.

Thanks for your hard work and support on getting this done. it's really amazing.

Just got the ring today. it is connecting to the app but it also disconnects frequently. Also none of the data is updating real time. I tried a one time measurement and it seems pretty off. Thanks for your hard work and support on getting this done. it's really amazing.
Author
Owner

Yeah its Bluetooth range is very limited, so disconnects are to be expected. Also it seems to disconnect when the ring isn't moving, kind of like it has a low-power mode for when it isn't worn.

Regarding real time data, that isn't how GB works. It syncs health data from devices periodically, not in real time.

The one time HR measurement seems to work fine for me, giving the result I expect. However, YMMV due to different ring, different fit, different finger composition, etc.

Yeah its Bluetooth range is very limited, so disconnects are to be expected. Also it seems to disconnect when the ring isn't moving, kind of like it has a low-power mode for when it isn't worn. Regarding real time data, that isn't how GB works. It syncs health data from devices periodically, not in real time. The one time HR measurement seems to work fine for me, giving the result I expect. However, YMMV due to different ring, different fit, different finger composition, etc.
First-time contributor

Thanks for the clarification.
I have been testing the ring more and have found that it really does not work for o2 measurements at all. It also doesn't detect sleep at all. The pedometer also seems pretty wildly off

Thanks for the clarification. I have been testing the ring more and have found that it really does not work for o2 measurements at all. It also doesn't detect sleep at all. The pedometer also seems pretty wildly off
Author
Owner

I think you may have a defective device. Mine reports quite believable results. HR, SpO2 and steps are in line with what my watch reports, and sleep seems reasonable too, at least it records sleep and awake times correctly (I don't know about light/deep sleep though).

I think you may have a defective device. Mine reports quite believable results. HR, SpO2 and steps are in line with what my watch reports, and sleep seems reasonable too, at least it records sleep and awake times correctly (I don't know about light/deep sleep though).
Author
Owner

Someone published the protocol details here: https://colmi.puxtril.com/

Someone published the protocol details here: https://colmi.puxtril.com/
Author
Owner

Note for testers of this PR: with the latest commit I've changed the device name registration to COLMI_R0x, which causes GB to forget any previously connected COLMI_R02. You will have to re-add your ring to GB. I did this to accomodate for the R03 and R06 which are also supported.

Note for testers of this PR: with the latest commit I've changed the device name registration to COLMI_R0x, which causes GB to forget any previously connected COLMI_R02. You will have to re-add your ring to GB. I did this to accomodate for the R03 and R06 which are also supported.
Author
Owner

@devnoname120 Are you testing this PR? Does the continuous HR measuring work for you after connecting with Gadgetbridge? I have again put some time into that yesterday, but I still didn't manage to get it to work, even with your list of possible commands.

Edit 1: The ring seems to do the HR measurements fine, I see the green LED regularly, and it also reports to GB that new HR data is available to sync. But the sync itself, which I believe I implemented correctly, doesn't receive any data. And I believe the sync code is correct, because it syncs the HR data correctly if the ring has been connected to QRing before.

Edit 2: Thinking more on it, I think maybe the way I set the time on the ring is wrong, causing it to record HR samples with the wrong timestamp. When I then retrieve them with the correct current timestamp (unix timestamp), nothing is there. I'll test some variations of the time setting packet tonight to verify this idea.

@devnoname120 Are you testing this PR? Does the continuous HR measuring work for you after connecting with Gadgetbridge? I have again put some time into that yesterday, but I still didn't manage to get it to work, even with your list of possible commands. Edit 1: The ring seems to do the HR measurements fine, I see the green LED regularly, and it also reports to GB that new HR data is available to sync. But the sync itself, which I believe I implemented correctly, doesn't receive any data. And I believe the sync code is correct, because it syncs the HR data correctly if the ring has been connected to QRing before. Edit 2: Thinking more on it, I think maybe the way I set the time on the ring is wrong, causing it to record HR samples with the wrong timestamp. When I then retrieve them with the correct current timestamp (unix timestamp), nothing is there. I'll test some variations of the time setting packet tonight to verify this idea.
First-time contributor

How do i download the latest test build?

How do i download the latest test build?
First-time contributor

Does the continuous HR measuring work for you after connecting with Gadgetbridge?

I tried acacfce6bed9f0586b01dfce3074abe22577eb54 and I confirm that the LED blinks at regular intervals but no heartrate data is received or at least it's not shown.

For some reason the BTSnoop doesn't work on my phone (no logs) so it's hard to investigate on my end.

> Does the continuous HR measuring work for you after connecting with Gadgetbridge? I tried acacfce6bed9f0586b01dfce3074abe22577eb54 and I confirm that the LED blinks at regular intervals but no heartrate data is received or at least it's not shown. For some reason the BTSnoop doesn't work on my phone (no logs) so it's hard to investigate on my end.
Author
Owner

Historical HR data is now fixed. Turns out my hunch about setting the time wrongly was correct, it was a whole month off.
I already added SpO2 history syncing yesterday, so that leaves only sleep remaining.

Historical HR data is now fixed. Turns out my hunch about setting the time wrongly was correct, it was a whole month off. I already added SpO2 history syncing yesterday, so that leaves only sleep remaining.
arjan5 changed title from WIP: Add support for the Colmi R02 smart ring to WIP: Add support for the Colmi R02/R03/R06 smart rings 2024-08-15 23:17:19 +02:00
First-time contributor

@arjan5 I just compiled 64e608797fbffdf9f454ee861e0838f34dfab08f and I confirm that it also solved the issue on my end. Well done! 🎉

@arjan5 I just compiled 64e608797fbffdf9f454ee861e0838f34dfab08f and I confirm that it also solved the issue on my end. Well done! 🎉
First-time contributor

Unrelated but I'm curious to see how it performs with this custom firmware: https://github.com/atc1441/ATC_RF03_Ring/blob/main/R02_3.00.06_FasterRawValuesMOD.bin

Explanatory video: https://m.youtube.com/watch?v=IOMqtFrNpTI

Unrelated but I'm curious to see how it performs with this custom firmware: https://github.com/atc1441/ATC_RF03_Ring/blob/main/R02_3.00.06_FasterRawValuesMOD.bin Explanatory video: https://m.youtube.com/watch?v=IOMqtFrNpTI
Author
Owner

Unrelated but I'm curious to see how it performs with this custom firmware: https://github.com/atc1441/ATC_RF03_Ring/blob/main/R02_3.00.06_FasterRawValuesMOD.bin

Explanatory video: https://m.youtube.com/watch?v=IOMqtFrNpTI

AFAIK atc1441 only changed the raw accelerometer refresh timeout to something lower, not changed anything else. So I guess/think it will just work. But if you want to test it, that would still be interesting. :)

I haven't implemented firmware uploading to the ring (yet), so you'll have to do it with atc1441's web page: https://atc1441.github.io/ATC_RF03_Writer.html

> Unrelated but I'm curious to see how it performs with this custom firmware: https://github.com/atc1441/ATC_RF03_Ring/blob/main/R02_3.00.06_FasterRawValuesMOD.bin > > Explanatory video: https://m.youtube.com/watch?v=IOMqtFrNpTI AFAIK atc1441 only changed the raw accelerometer refresh timeout to something lower, not changed anything else. So I guess/think it will just work. But if you want to test it, that would still be interesting. :) I haven't implemented firmware uploading to the ring (yet), so you'll have to do it with atc1441's web page: https://atc1441.github.io/ATC_RF03_Writer.html
Author
Owner

Update: sleep data sync is added, so it's pretty much feature complete!
I have some more work to do and comments to address, but please test and provide feedback!
(attached a new apk for those that want it)

Update: sleep data sync is added, so it's pretty much feature complete! I have some more work to do and comments to address, but please test and provide feedback! (attached a new apk for those that want it)
First-time contributor

I compiled b08070621b9a120a1bdbea4e6a11a43dbd281026 and tried it.

  1. Whenever I press the little graph icon in Gadgetbridge it crashes:

FATAL EXCEPTION: main
Process: nodomain.freeyourgadget.gadgetbridge, PID: 28736
java.lang.NullPointerException: Attempt to invoke virtual method 'com.github.mikephil.charting.formatter.ValueFormatter nodomain.freeyourgadget.gadgetbridge.activities.charts.DefaultChartsData.getXValueFormatter()' on a null object reference
	at nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivitySleepChartFragment.updateChartsnUIThread(ActivitySleepChartFragment.java:139)
	at nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivitySleepChartFragment.updateChartsnUIThread(ActivitySleepChartFragment.java:49)
	at nodomain.freeyourgadget.gadgetbridge.activities.charts.AbstractChartFragment$RefreshTask.onPostExecute(AbstractChartFragment.java:354)
	at android.os.AsyncTask.finish(AsyncTask.java:771)
	at android.os.AsyncTask.-$$Nest$mfinish(Unknown Source:0)
	at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788)
	at android.os.Handler.dispatchMessage(Handler.java:107)
	at android.os.Looper.loopOnce(Looper.java:232)
	at android.os.Looper.loop(Looper.java:317)
	at android.app.ActivityThread.main(ActivityThread.java:8592)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
  1. And if I press the little 360 arrow to fetch the activity data it shows the following toast multiple times:

I guess some database migration didn't apply?

I compiled b08070621b9a120a1bdbea4e6a11a43dbd281026 and tried it. 1. Whenever I press the little graph icon in Gadgetbridge it crashes: <p align="center"><img src="/attachments/8ac39f99-147d-4544-9e58-efc9d699a24b" width="300"></p> ``` FATAL EXCEPTION: main Process: nodomain.freeyourgadget.gadgetbridge, PID: 28736 java.lang.NullPointerException: Attempt to invoke virtual method 'com.github.mikephil.charting.formatter.ValueFormatter nodomain.freeyourgadget.gadgetbridge.activities.charts.DefaultChartsData.getXValueFormatter()' on a null object reference at nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivitySleepChartFragment.updateChartsnUIThread(ActivitySleepChartFragment.java:139) at nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivitySleepChartFragment.updateChartsnUIThread(ActivitySleepChartFragment.java:49) at nodomain.freeyourgadget.gadgetbridge.activities.charts.AbstractChartFragment$RefreshTask.onPostExecute(AbstractChartFragment.java:354) at android.os.AsyncTask.finish(AsyncTask.java:771) at android.os.AsyncTask.-$$Nest$mfinish(Unknown Source:0) at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loopOnce(Looper.java:232) at android.os.Looper.loop(Looper.java:317) at android.app.ActivityThread.main(ActivityThread.java:8592) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878) ``` 2. And if I press the little 360 arrow to fetch the activity data it shows the following toast multiple times: <p align="center"><img src="/attachments/4812f52c-f718-47c9-9e94-9f937d6cebf9" width="300"></p> I guess some database migration didn't apply?
First-time contributor

I sent you the Gadgebridge log to your @anymore.nl email address.

I sent you the Gadgebridge log to your @anymore.nl email address.
Author
Owner

Oops, yes, you're absolutely right! I added two database tables but didn't commit the incremented database version, which means it'll never create those tables. You can increment it in the GBDaoGenerator class and compile again, or throw away your database and start clean. But since your screenshots show other devices, I guess that isn't an option. :)

Oops, yes, you're absolutely right! I added two database tables but didn't commit the incremented database version, which means it'll never create those tables. You can increment it in the GBDaoGenerator class and compile again, or throw away your database and start clean. But since your screenshots show other devices, I guess that isn't an option. :)
First-time contributor

I confirm that it solved my issue. Sleep data now properly synchronizes and no crashes anymore. 🎉

I confirm that it solved my issue. Sleep data now properly synchronizes and no crashes anymore. 🎉
arjan5 changed title from WIP: Add support for the Colmi R02/R03/R06 smart rings to Add support for the Colmi R02/R03/R06 smart rings 2024-08-17 23:02:18 +02:00
@ -0,0 +106,4 @@
} else {
// Unpack timestamp and data
Calendar sampleCal = Calendar.getInstance();
sampleCal.set(Calendar.YEAR, 2000 + Integer.valueOf(String.format("%02x", value[1])));
Owner

Are you sure this is right? Integer.valueOf expects a decimal, but the value is being formatted as hex. Same in the 2 below.

Are you sure this is right? Integer.valueOf expects a decimal, but the value is being formatted as hex. Same in the 2 below.
Author
Owner

Yes, I'm afraid it really is right...This date is for some reason transmitted as ints used as literal bytes: a date like 2024-08-18 would be transmitted as 0x24 0x08 0x18...
facepalm

Yes, I'm afraid it really is right...This date is for some reason transmitted as ints used as literal bytes: a date like 2024-08-18 would be transmitted as 0x24 0x08 0x18... *facepalm*
arjan5 marked this conversation as resolved
@ -0,0 +243,4 @@
}
break;
case ColmiR0xConstants.CMD_SET_HR_INTERVAL:
LOG.info("Received HR interval: {} minutes, enabled={}", value[3], value[2] == 0x01);
Owner

Shouldn't we update the device preferences so that the UI matches what's configured on the ring?

See GBDeviceEventUpdatePreferences

Shouldn't we update the device preferences so that the UI matches what's configured on the ring? See `GBDeviceEventUpdatePreferences`
Author
Owner

Yes, obviously! I had planned to do that, but somehow forgot it...

Yes, obviously! I had planned to do that, but somehow forgot it...
Author
Owner

Done!

Done!
arjan5 marked this conversation as resolved
devnoname120 left a comment
First-time contributor

This is a huge amount of work — well done!

I made some comments based on my reverse engineering. I haven't noticed any glaring issues but you may be interested in some of my findings.

This is a *huge* amount of work — well done! I made some comments based on my reverse engineering. I haven't noticed any glaring issues but you may be interested in some of my findings.
@ -85,0 +89,4 @@
HeartRateSample sample = (HeartRateSample) result;
heartRate = sample.getHeartRate();
}
if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
First-time contributor

If result is neither an ActivitySample nor a HeartRateSample then heartRate keeps its initialization value of 0, which is a valid heartRate value and thus gets displayed in the graph.

Is this really wanted? Shouldn't it be ignored instead or raise an error maybe?

I wonder if this could be the reason I'm witnessing this weird pattern from a few days back (I'm not sure if it uses the same code path):

If `result` is neither an `ActivitySample` nor a `HeartRateSample` then `heartRate` keeps its initialization value of `0`, which is a valid `heartRate` value and thus gets displayed in the graph. Is this really wanted? Shouldn't it be ignored instead or raise an error maybe? I wonder if this could be the reason I'm witnessing this weird pattern from a few days back (I'm not sure if it uses the same code path): <img src="/attachments/992c7fb6-cd95-4895-a0cd-8ef737bd425f" width=200>
286 KiB
Author
Owner

This must be a different problem indeed, the HeartRateDialog is the live heart rate dialog and isn't connected to the chart in your screenshot.

This must be a different problem indeed, the HeartRateDialog is the live heart rate dialog and isn't connected to the chart in your screenshot.
First-time contributor

Ah I see, hopefully it's due to measurement errors that we should have ignored? The graph on QRing looks fine. Is there a way to make Gadgetbridge delete the historical activity data in order to cleanly fetch it again?

Ah I see, hopefully it's due to measurement errors that we should have ignored? The graph on QRing looks fine. Is there a way to make Gadgetbridge delete the historical activity data in order to cleanly fetch it again?
Author
Owner

Yes, that's easy. Just delete the device from GB. The code (without newlines ;) ) at the top of the Coordinator takes care of deleting the device's historical data from the database. Then just re-add it and it will sync whatever the device still remembers.

Yes, that's easy. Just delete the device from GB. The code (without newlines ;) ) at the top of the Coordinator takes care of deleting the device's historical data from the database. Then just re-add it and it will sync whatever the device still remembers.
Author
Owner

Wait, don't do that! I think this is may be due to one of my changes. Do or did you have the HR interval set to something higher than 5 min?

Wait, don't do that! I think this is may be due to one of my changes. Do or did you have the HR interval set to something higher than 5 min?
First-time contributor

Do or did you have the HR interval set to something higher than 5 min?

I did not set it to higher than 5 minutes, except for 1-2s while clicking on the different options in quick succession. I don't remember the green LED blinking while doing that.

> Do or did you have the HR interval set to something higher than 5 min? I did not set it to higher than 5 minutes, except for 1-2s while clicking on the different options in quick succession. I don't remember the green LED blinking while doing that.
Author
Owner

The code I was thinking about can't cause this... If you are okay with losing data, it would be very interesting to see if syncing with a clean database still gives this result.

The code I was thinking about can't cause this... If you are okay with losing data, it would be very interesting to see if syncing with a clean database still gives this result.
First-time contributor

Before I was on: b08070621b9a120a1bdbea4e6a11a43dbd281026

I deleted the device as you suggested and then added it again, and did the synchronization. It got even worse with every day with sleep detected showing a lot of 0 values exactly during sleep, and at no other point during the day.

Here is one day for example:

Interestingly if I zoom in I can see that the 0 values show up every minute even though the HR interval can't be set to less than 5 minutes… you can see the problem here (zoom a lot at the bottom of this screenshot):

The issue starts at the same time exactly when sleep is detected during the night, and it stops exactly at the same time as when sleep is not detected anymore as evidenced in these screenshots:


I then compiled and installed 601db54be00065cd71e164f5c6c57d43f58b18c1.

I deleted the device, added it again and synchronized it again and now it's much worse. The bug now shows up every day, the entire day. Both during sleep and outside of sleep:

Before I was on: b08070621b9a120a1bdbea4e6a11a43dbd281026 I deleted the device as you suggested and then added it again, and did the synchronization. It got even worse with every day with sleep detected showing a lot of `0` values exactly during sleep, and at no other point during the day. Here is one day for example: <img width=200 src="/attachments/a523fa9f-e3a5-47ca-86c1-d2adc7040609"> Interestingly if I zoom in I can see that the `0` values show up every minute even though the HR interval can't be set to less than 5 minutes… you can see the problem here (zoom a lot at the bottom of this screenshot): <img width=200 src="/attachments/40735c8f-560d-4b98-b607-bf9a55f03ac2"> The issue starts at the same time exactly when sleep is detected during the night, and it stops exactly at the same time as when sleep is not detected anymore as evidenced in these screenshots: <img width=200 src="/attachments/563c945f-3404-4b20-aa8e-0633c241f110"> <img width=200 src="/attachments/e92087df-3d04-46d8-9492-061c45ff18c4"> ------------------ I then compiled and installed 601db54be00065cd71e164f5c6c57d43f58b18c1. I deleted the device, added it again and synchronized it again and now it's much worse. The bug now shows up every day, the entire day. Both during sleep and outside of sleep: <img width=200 src="/attachments/c7c2141e-3589-4ae3-a71c-4dd50215d4bb"> <img width=200 src="/attachments/67a88681-79a1-4335-9974-edb9d4322e6d">
Author
Owner

Something very weird is happening, I really don't understand this... Can you please share your Gadgetbridge log with me and make sure it contains the information from the first (or at least full) sync?
You can send it privately, as it contains personal details.

Something very weird is happening, I really don't understand this... Can you please share your Gadgetbridge log with me and make sure it contains the information from the first (or at least full) sync? You can send it privately, as it contains personal details.
First-time contributor

@arjan5 I just sent the logs to your committer email. I used the gb_colmi_r02.apk from this comment for testing.

@arjan5 I just sent the logs to your committer email. I used the `gb_colmi_r02.apk` from [this comment](https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2184137) for testing.
Author
Owner

Thanks for the logs! A quick first inspection doesn't show anything weird in the parsed data, but I'll look a bit better at it in the next few days.
Regarding the "hanging sync", that is actually a crash of the sleep data parser function. The same thing started happening today for me too, it seems to be related to having at least 6 days of sleep data in the ring, so probably the format changes when there is more data. I'll look into this too.

Thanks for the logs! A quick first inspection doesn't show anything weird in the parsed data, but I'll look a bit better at it in the next few days. Regarding the "hanging sync", that is actually a crash of the sleep data parser function. The same thing started happening today for me too, it seems to be related to having at least 6 days of sleep data in the ring, so probably the format changes when there is more data. I'll look into this too.
Author
Owner

The sleep sync crash is now fixed in 6e6133927d. Still no idea about your weird HR charts.

The sleep sync crash is now fixed in 6e6133927d. Still no idea about your weird HR charts.
Owner

I was worried this might have been caused by the recent ActivityKind changes that I have done, but I also can't reproduce this with my ring.

I think the culprit might be this piece of code:

if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, ts - 1));
}

I was worried this might have been caused by the recent ActivityKind changes that I have done, but I also can't reproduce this with my ring. I think the culprit might be this piece of code: https://codeberg.org/Freeyourgadget/Gadgetbridge/src/commit/ef1d7171e80b36f176c1eaf885b341dea57cf78d/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java#L283-L286
Author
Owner

I may read it incorrectly, but wouldn't this only trigger if the difference between the current sample's timestamp and the previous one is 1800*10 seconds, which is 300 minutes or 5 hours?

Another thing I was thinking about is the configured minimum heart rate. This is normally 10, but did you by any chance change it to 0 @devnoname120 ?

I may read it incorrectly, but wouldn't this only trigger if the difference between the current sample's timestamp and the previous one is `1800*10` seconds, which is 300 minutes or 5 hours? Another thing I was thinking about is the configured minimum heart rate. This is normally `10`, but did you by any chance change it to `0` @devnoname120 ?
First-time contributor

@arjan5 I confirm that I set the minimum to 0 because I like my graphs to be of proportional scale (otherwise variations are exaggerated). Changing the minimum heart rate to 1 fixes the issue.

How come there are so many 0 values? And how come that changing the scale of the graph cuts them out?

@arjan5 I confirm that I set the minimum to `0` because I like my graphs to be of proportional scale (otherwise variations are exaggerated). Changing the minimum heart rate to `1` fixes the issue. How come there are so many `0` values? And how come that changing the scale of the graph cuts them out?
Owner

Ah, interesting - the "unknown" heart rate values are being included because the minimum is used do determine the validity of HR values.

I pushed bb50796d0 to mitigate this, but we should review that isValidHeartRateValue, since it's used in a lot of places that I was not expecting it (eg. it means that values outside the range will not be exported in gpx files, even though they might be real HR values).

Ah, interesting - the "unknown" heart rate values are being included because the minimum is used do determine the validity of HR values. I pushed bb50796d0 to mitigate this, but we should review that `isValidHeartRateValue`, since it's used in a lot of places that I was not expecting it (eg. it means that values outside the range will not be exported in gpx files, even though they might be real HR values).
@ -0,0 +32,4 @@
public static final byte CMD_POWER_OFF = 0x08;
public static final byte CMD_PREFERENCES = 0x0a;
public static final byte CMD_SYNC_HEART_RATE = 0x15;
public static final byte CMD_SET_HR_INTERVAL = 0x16;
First-time contributor

I suggest you to rename it to something like CMD_AUTO_HR because this setting can both be read (using 0x01 as the first arg) and be written (using 0x02 as the first arg).

I suggest you to rename it to something like `CMD_AUTO_HR` because this setting can both be read (using `0x01` as the first arg) and be written (using `0x02` as the first arg).
Author
Owner

Thanks, done!

Thanks, done!
arjan5 marked this conversation as resolved
@ -0,0 +34,4 @@
public static final byte CMD_SYNC_HEART_RATE = 0x15;
public static final byte CMD_SET_HR_INTERVAL = 0x16;
public static final byte CMD_GOALS = 0x21;
public static final byte CMD_SET_SPO2_ENABLED = 0x2c;
First-time contributor

Likewise, I suggest you to rename it to something like CMD_AUTO_SPO2 because this setting can both be read (using 0x01 as the first arg) and be written (using 0x02 as the first arg).

Likewise, I suggest you to rename it to something like `CMD_AUTO_SPO2` because this setting can both be read (using `0x01` as the first arg) and be written (using `0x02` as the first arg).
Author
Owner

Thanks, done!

Thanks, done!
arjan5 marked this conversation as resolved
@ -0,0 +36,4 @@
public static final byte CMD_GOALS = 0x21;
public static final byte CMD_SET_SPO2_ENABLED = 0x2c;
public static final byte CMD_PACKET_SIZE = 0x2f;
public static final byte CMD_SET_STRESS_ENABLED = 0x36;
First-time contributor

Likewise, I suggest you to rename it to something like maybe CMD_AUTO_STRESS because this setting can both be read (using 0x01 as the first arg) and be written (using 0x02 as the first arg).

Likewise, I suggest you to rename it to something like maybe `CMD_AUTO_STRESS` because this setting can both be read (using `0x01` as the first arg) and be written (using `0x02` as the first arg).
Author
Owner

Thanks, done!

Thanks, done!
arjan5 marked this conversation as resolved
@ -0,0 +42,4 @@
public static final byte CMD_FIND_DEVICE = 0x50;
public static final byte CMD_MANUAL_HEART_RATE = 0x69;
public static final byte CMD_NOTIFICATION = 0x73;
public static final byte CMD_BIG_DATA_V2 = (byte) 0xbc;
First-time contributor

nit: is the (byte) cast needed?

nit: is the `(byte)` cast needed?
Author
Owner

Yes, for some reason 0xbc is an int while the rest are bytes...

Yes, for some reason 0xbc is an int while the rest are bytes...
arjan5 marked this conversation as resolved
@ -0,0 +61,4 @@
QueryBuilder<?> qb;
qb = session.getColmiActivitySampleDao().queryBuilder();
qb.where(ColmiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
First-time contributor

nit: for clarity I would suggest to skip a line after each .executeXYZ().

nit: for clarity I would suggest to skip a line after each `.executeXYZ()`.
Author
Owner

Sorry, I think it's clear enough as it is :)

Sorry, I think it's clear enough as it is :)
arjan5 marked this conversation as resolved
@ -0,0 +102,4 @@
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("R0[2-6]_.*");
First-time contributor

nit: so far only the R02, R03, and R06 models exist. If they release a R04 or R05 in the future they won't necessarily use the same hardware and/or commands. I'd suggest to use something like R0[236]_.* or maybe (R02|R03|R06)_.*, whichever is clearer. What do you think?

nit: so far only the `R02`, `R03`, and `R06` models exist. If they release a `R04` or `R05` in the future they won't necessarily use the same hardware and/or commands. I'd suggest to use something like `R0[236]_.*` or maybe `(R02|R03|R06)_.*`, whichever is clearer. What do you think?
Author
Owner

I'm afraid you've reviewed an old version of the PR, this is already solved in my latest update.

I'm afraid you've reviewed an old version of the PR, this is already solved in my latest update.
First-time contributor

Interestingly, the R04 and R05 are included in the filter that QRing applies on the Bluetooth device list!

Here is an (exhaustive?) list of accepted prefixes:

  • R01
  • R02
  • R03
  • R04
  • R05
  • R06
  • VK-5098
  • MERLIN
  • Hello Ring
  • RING1
  • boAtring
  • O_
  • q_
  • Q_
  • qc
  • QC
  • R3L
  • Wa
Interestingly, the `R04` and `R05` *are* included in the filter that QRing applies on the Bluetooth device list! Here is an (exhaustive?) list of accepted prefixes: - `R01` - `R02` - `R03` - `R04` - `R05` - `R06` - `VK-5098` - `MERLIN` - `Hello Ring` - `RING1` - `boAtring` - `O_` - `q_` - `Q_` - `qc` - `QC` - `R3L` - `Wa`
Author
Owner

I currently just want to add devices I know actually exist. Once users report these, it'll be very easy to add them.

I currently just want to add devices I know actually exist. Once users report these, it'll be very easy to add them.
First-time contributor

I agree — I meant it more in the sense of “wow this is surprising/interesting!” 🙂

I agree — I meant it more in the sense of “wow this is surprising/interesting!” 🙂
arjan5 marked this conversation as resolved
@ -0,0 +107,4 @@
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
First-time contributor

Just for my own sanity — can you confirm that the ring doesn't support bonding?

Just for my own sanity — can you confirm that the ring doesn't support bonding?
Author
Owner

Yes, I've tested that extensively when starting on this

Yes, I've tested that extensively when starting on this
arjan5 marked this conversation as resolved
@ -0,0 +61,4 @@
int steps = BLETypeConversions.toUint32(value[2], value[3], value[4], (byte) 0);
int calories = BLETypeConversions.toUint32(value[5], value[6], value[7], (byte) 0);
int distance = BLETypeConversions.toUint32(value[8], value[9], value[10], (byte) 0);
LOG.info("Received requested goals settings: {} steps, {} calories, {}m distance", steps, calories, distance);
First-time contributor

There is also:

  • sport (minutes): toUint16(value[11], value[12]).
  • sleep (minutes): toUint16(value[13], value[14]).

And it's complete now.

There is also: - sport (minutes): `toUint16(value[11], value[12])`. - sleep (minutes): `toUint16(value[13], value[14])`. And it's complete now.
Author
Owner

Those are both 0 in my bluetooth dumps, so I didn't know that. Also, it's just here for logging, we don't (want to) do anything with it.

Those are both 0 in my bluetooth dumps, so I didn't know that. Also, it's just here for logging, we don't (want to) do anything with it.
First-time contributor

IMO it would still be worth logging them just in case. And also to document them in the code later if we need to revisit those.

IMO it would still be worth logging them just in case. And also to document them in the code later if we need to revisit those.
Author
Owner

You're right. Added.

You're right. Added.
arjan5 marked this conversation as resolved
@ -0,0 +65,4 @@
}
public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
int hrResponse = value[3] & 0xff;
First-time contributor

FYI value[2] is an error code:

  • 0 means no error.
  • 1 means that the ring is worn incorrectly so the app stops the measurement and shows -- + a toast.
  • 2 I'm not 100% sure what it means… maybe “finished”? The app just ignores the value[3] in that case, it doesn't show a toast, and it stops the measurement. It keeps the most recent value displayed.

I'd suggest adding a condition that checks that the error code is 0 before going further, as well as show the error to the user.

FYI `value[2]` is an error code: - `0` means no error. - `1` means that the ring is worn incorrectly so the app stops the measurement and shows `--` + a toast. - `2` I'm not 100% sure what it means… maybe “finished”? The app just ignores the `value[3]` in that case, it doesn't show a toast, and it stops the measurement. It keeps the most recent value displayed. I'd suggest adding a condition that checks that the error code is `0` before going further, as well as show the error to the user.
Author
Owner

Thanks, done!

Thanks, done!
arjan5 marked this conversation as resolved
@ -0,0 +93,4 @@
public static void liveActivity(byte[] value) {
int steps = BLETypeConversions.toUint32(value[4], value[3], value[2], (byte) 0);
int calories = BLETypeConversions.toUint32(value[7], value[6], value[5], (byte) 0) / 10;
int distance = BLETypeConversions.toUint32(value[10], value[9], value[8], (byte) 0);
First-time contributor

I double-checked and I confirm that there is nothing more than steps, calories, and distance — so it's complete already.

I double-checked and I confirm that there is nothing more than steps, calories, and distance — so it's complete already.
arjan5 marked this conversation as resolved
@ -0,0 +97,4 @@
LOG.info("Received live activity notification: {} steps, {} calories, {}m distance", steps, calories, distance);
}
public static void historicalActivity(GBDevice device, byte[] value) {
First-time contributor
  • Self-reminder to review this function later.
- [ ] Self-reminder to review this function later.
@ -0,0 +143,4 @@
}
}
public static void historicalStress(GBDevice device, byte[] value) {
First-time contributor
  • Self-reminder to review this function later.
- [ ] Self-reminder to review this function later.
@ -0,0 +195,4 @@
}
}
public static void historicalSpo2(GBDevice device, byte[] value) {
First-time contributor
  • Self-reminder to review this function later.
- [ ] Self-reminder to review this function later.
@ -0,0 +242,4 @@
}
}
public static void historicalSleep(GBDevice gbDevice, Context context, byte[] value) {
First-time contributor
  • Self-reminder to review this function later.
- [ ] Self-reminder to review this function later.
@ -0,0 +173,4 @@
case ColmiR0xConstants.CMD_SET_DATE_TIME:
LOG.info("Received set date/time response: {}", StringUtils.bytesToHex(value));
break;
case ColmiR0xConstants.CMD_BATTERY:
First-time contributor
  • Self-reminder to review this case later.
- [ ] Self-reminder to review this `case` later.
@ -0,0 +188,4 @@
case ColmiR0xConstants.CMD_PREFERENCES:
LOG.info("Received user preferences response: {}", StringUtils.bytesToHex(value));
break;
case ColmiR0xConstants.CMD_SYNC_HEART_RATE:
First-time contributor
  • Self-reminder to review this case later.
- [ ] Self-reminder to review this `case` later.
@ -0,0 +292,4 @@
case ColmiR0xConstants.NOTIFICATION_NEW_STEPS_DATA:
LOG.info("Received notification from ring that new steps data is available to sync");
break;
case ColmiR0xConstants.NOTIFICATION_BATTERY_LEVEL:
First-time contributor
  • Self-reminder to review this case later.
- [ ] Self-reminder to review this `case` later.
@ -0,0 +308,4 @@
break;
}
break;
case ColmiR0xConstants.CMD_BIG_DATA_V2:
First-time contributor
  • Self-reminder to review this case later.
- [ ] Self-reminder to review this `case` later.
@ -0,0 +441,4 @@
// TODO: find out how to send gender and possibly other preferences
byte[] userPrefsPacket = buildPacket(new byte[]{
ColmiR0xConstants.CMD_PREFERENCES,
0x02,
First-time contributor

FYI:

  • 0x01 is read.
  • 0x02 is write.
  • 0x03 is delete.

You might want to extract those out to constants to improve code readability?

In addition to CMD_PREFERENCES, this is also valid for the following commands (among those that you implemented):

  • CMD_GOALS
  • CMD_SET_STRESS_ENABLED (which I suggest you to rename to e.g. CMD_AUTO_STRESS).
  • CMD_SET_SPO2_ENABLED (which I suggest you to rename to e.g. CMD_AUTO_SPO2).
  • CMD_SET_HR_INTERVAL (which I suggest you to rename to e.g. CMD_AUTO_HR).
  • CMD_HRV (0x38) (this command is not implemented yet in the PR but I put it here for future reference just in case because I heard that some people installed a firmware update through QRing that added this feature).
FYI: - `0x01` is read. - `0x02` is write. - `0x03` is delete. You might want to extract those out to constants to improve code readability? In addition to `CMD_PREFERENCES`, this is also valid for the following commands (among those that you implemented): - `CMD_GOALS` - `CMD_SET_STRESS_ENABLED` (which I suggest you to rename to e.g. `CMD_AUTO_STRESS`). - `CMD_SET_SPO2_ENABLED` (which I suggest you to rename to e.g. `CMD_AUTO_SPO2`). - `CMD_SET_HR_INTERVAL` (which I suggest you to rename to e.g. `CMD_AUTO_HR`). - `CMD_HRV` (`0x38`) (this command is not implemented yet in the PR but I put it here for future reference just in case because I heard that some people installed a firmware update through QRing that added this feature).
Author
Owner

Thanks, done!
HRV is something I haven't seen in QRing or my ring. For now it can remain outside the scope of this PR, it can always be added later.

Thanks, done! HRV is something I haven't seen in QRing or my ring. For now it can remain outside the scope of this PR, it can always be added later.
First-time contributor

Note that in your case the official app did send the CMD_AUTO_HRV_PREF = 0x38 command with PREF_READ action when connecting to the ring. See: #3896 (comment)

image

I'm not sure of the significance of this though. I wonder if sending CMD_AUTO_HRV_PREF = 0x38 command with PREF_WRITE would make the ring start to send HRV data on a regular basis?

Just some thoughts for later, I agree that it can definitely remain outside of the scope of this PR as you mentioned.

Note that in your case the official app *did* send the `CMD_AUTO_HRV_PREF = 0x38` command with `PREF_READ` action when connecting to the ring. See: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2182839 ![image](/attachments/115c706e-4e19-46bd-a315-b32e0b35b893) I'm not sure of the significance of this though. I wonder if sending `CMD_AUTO_HRV_PREF = 0x38` command with `PREF_WRITE` would make the ring start to send HRV data on a regular basis? Just some thoughts for later, I agree that it can definitely remain outside of the scope of this PR as you mentioned.
148 KiB
arjan5 marked this conversation as resolved
@ -0,0 +442,4 @@
byte[] userPrefsPacket = buildPacket(new byte[]{
ColmiR0xConstants.CMD_PREFERENCES,
0x02,
0x00,
First-time contributor

This argument is for the time format:

  • 0 means 24-hour format.
  • 1 means 12-hour format.
This argument is for the time format: - `0` means 24-hour format. - `1` means 12-hour format.
arjan5 marked this conversation as resolved
@ -0,0 +444,4 @@
0x02,
0x00,
(byte) ("metric".equals(measurementSystem) ? 0x00 : 0x01),
0x00,
First-time contributor

This argument is sex:

  • 0 is male.
  • 1 is female.
  • 2 is “others”.
This argument is sex: - `0` is male. - `1` is female. - `2` is “others”.
Author
Owner

Thanks, done!

Thanks, done!
arjan5 marked this conversation as resolved
@ -0,0 +448,4 @@
(byte) user.getAge(),
(byte) user.getHeightCm(),
(byte) user.getWeightKg()
});
First-time contributor

It's missing those three byte values at the end:

  • systolic blood pressure (e.g. 120).
  • diastolic blood pressure (e.g. 90).
  • heart rate value warning threshold: (e.g. 160).

And now the list is exhaustive.

It's missing those three byte values at the end: - systolic blood pressure (e.g. `120`). - diastolic blood pressure (e.g. `90`). - heart rate value warning threshold: (e.g. `160`). And now the list is exhaustive.
Author
Owner

Looks like we don't allow the user to set these, so I've added them as 0x00 with a comment to describe them.

Looks like we don't allow the user to set these, so I've added them as 0x00 with a comment to describe them.
arjan5 marked this conversation as resolved
@ -0,0 +513,4 @@
syncingDay.set(Calendar.HOUR_OF_DAY, 0);
syncingDay.set(Calendar.MINUTE, 0);
syncingDay.set(Calendar.SECOND, 0);
byte[] activityHistoryRequest = buildPacket(new byte[]{ColmiR0xConstants.CMD_SYNC_ACTIVITY, (byte) daysAgo, 0x0f, 0x00, 0x5f, 0x01});
First-time contributor
  • Self-reminder to review this CMD.
- [ ] Self-reminder to review this `CMD`.
@ -0,0 +546,4 @@
private void fetchHistorySpo2() {
getDevice().setBusyTask("spo2HistoryRequest");
byte[] spo2HistoryRequest = new byte[]{
First-time contributor
  • Self-reminder to review this CMD.
- [ ] Self-reminder to review this `CMD`.
@ -0,0 +561,4 @@
private void fetchHistorySleep() {
getDevice().setBusyTask("sleepHistoryRequest");
byte[] sleepHistoryRequest = new byte[]{
First-time contributor
  • Self-reminder to review this CMD.
- [ ] Self-reminder to review this `CMD`.
@ -0,0 +107,4 @@
public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
int errorCode = value[2];
if (errorCode == 1) {
First-time contributor

I'd suggest to ignore the HR values whenever errorCode != 0, not only when errorCode == 1. I haven't looked into the firmware of the Colmi R0x but there might be additional error codes in there. Toast could show the current message when errorCode == 1, and an unknown error code: 123 kind of toast for other non-zero error codes. What do you think?

I'd suggest to ignore the HR values whenever `errorCode != 0`, not only when `errorCode == 1`. I haven't looked into the firmware of the Colmi R0x but there might be additional error codes in there. Toast could show the current message when `errorCode == 1`, and an `unknown error code: 123` kind of toast for other non-zero error codes. What do you think?
Author
Owner

Okay. From your other comment on this I thought that either 0 or 2 meant OK and 1 meant ERROR. I'll change it to handle just 0 as OK and the rest as error. Showing the error code to users isn't very useful, so I'll make sure it gets logged instead.

Okay. From your other comment on this I thought that either 0 or 2 meant OK and 1 meant ERROR. I'll change it to handle just 0 as OK and the rest as error. Showing the error code to users isn't very useful, so I'll make sure it gets logged instead.
First-time contributor

Ah sorry for the confusion. Here is what I know so far (I'm using UInt8 for its representation here):

  • 0x00: no error.
  • 0x01: ring worn incorrectly, thus the measure was aborted.
  • 0x02: temporary error/missing data, the HR value from this notification should be silently ignored but the measure is still ongoing.
  • ≥ 0x03: other errors (i.e. not due to incorrectly wearing the ring), thus the measure was aborted.

If I understand correctly this code path is used when the user performs a manual live heartbeat measurement, so IMO it makes sense to inform the user that they were wearing it incorrectly (errorCode == 1) and should retry the measure.

If an unknown error code happened (errorCode >= 3), then I think that it also makes sense to ignore the HR value from this notification and it still makes sense to inform the user that the measure didn't work for an unknown reason, rather than hiding the error from the user (which may then not realize that their hardware is flaky (like @sysadminpower2019 encountered) rather than a Gadgetbridge bug). Let me know if it's still unclear!

Ah sorry for the confusion. Here is what I know so far (I'm using `UInt8` for its representation here): - `0x00`: no error. - `0x01`: ring worn incorrectly, thus the measure was aborted. - `0x02`: temporary error/missing data, the HR value from this notification should be silently ignored but the measure is still ongoing. - `≥ 0x03`: other errors (i.e. not due to incorrectly wearing the ring), thus the measure was aborted. If I understand correctly this code path is used when the user performs a manual live heartbeat measurement, so IMO it makes sense to inform the user that they were wearing it incorrectly (`errorCode == 1`) and should retry the measure. If an unknown error code happened (`errorCode >= 3`), then I think that it also makes sense to ignore the HR value from this notification and it still makes sense to inform the user that the measure didn't work for an unknown reason, rather than hiding the error from the user (which may then not realize that their hardware is flaky (like @sysadminpower2019 encountered) rather than a Gadgetbridge bug). Let me know if it's still unclear!
arjan5 marked this conversation as resolved
Author
Owner

Looks like the mentioned firmware update (3.00.10) is currently available for the R03 only and adds HRV and REM sleep. Source: https://www.reddit.com/r/SmartRings/comments/1es2q23/colmi_r03_firmware_update_30010/
This PR is based on firmware 3.00.06 which doesn't have those features yet.

Looks like the mentioned firmware update (3.00.10) is currently available for the R03 only and adds HRV and REM sleep. Source: https://www.reddit.com/r/SmartRings/comments/1es2q23/colmi_r03_firmware_update_30010/ This PR is based on firmware 3.00.06 which doesn't have those features yet.
First-time contributor

@arjan5 Thanks for the reference! Note that the previous versions of the firmware were interchangeable (i.e. can be installed on the other R0x rings without issues). Not sure about this specific version, but it might just be the same. Just a piece of information, I don't mean to argue in favor of looking into it right now.

@arjan5 Thanks for the reference! Note that the previous versions of the firmware were interchangeable (i.e. can be installed on the other R0x rings without issues). Not sure about this specific version, but it might just be the same. Just a piece of information, I don't mean to argue in favor of looking into it right now.
Author
Owner

Current testing apk

Current testing apk
@ -0,0 +389,4 @@
BluetoothGattCharacteristic characteristic = getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_WRITE);
if (characteristic != null) {
builder.write(characteristic, contents);
builder.queue(getQueue());
Owner

This is crashing Gadgetbridge sometimes:

Logs
13:45:46.293 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered unsupported service: Generic Access: 00001800-0000-1000-8000-00805f9b34fb
13:45:46.295 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered unsupported service: Generic Attribute: 00001801-0000-1000-8000-00805f9b34fb
13:45:46.297 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Device Information: 0000180a-0000-1000-8000-00805f9b34fb
13:45:46.299 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Manufacturer Name String: 00002a29-0000-1000-8000-00805f9b34fb
13:45:46.301 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Model Number String: 00002a24-0000-1000-8000-00805f9b34fb
13:45:46.303 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Serial Number String: 00002a25-0000-1000-8000-00805f9b34fb
13:45:46.304 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Hardware Revision String: 00002a27-0000-1000-8000-00805f9b34fb
13:45:46.307 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Firmware Revision String: 00002a26-0000-1000-8000-00805f9b34fb
13:45:46.309 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: System ID: 00002a23-0000-1000-8000-00805f9b34fb
13:45:46.311 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: IEEE 11073-20601 Regulatory Certification Data List: 00002a2a-0000-1000-8000-00805f9b34fb
13:45:46.312 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: PnP ID: 00002a50-0000-1000-8000-00805f9b34fb
13:45:46.314 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Unknown Service: 6e40fff0-b5a3-f393-e0a9-e50e24dcca9e
13:45:46.316 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Unknown Characteristic: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
13:45:46.318 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Unknown Characteristic: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
13:45:46.320 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Unknown Service: de5bf728-d711-4e47-af26-65e3012a5dc7
13:45:46.322 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Unknown Characteristic: de5bf72a-d711-4e47-af26-65e3012a5dc7
13:45:46.324 [binder:16654_5] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport -     characteristic: Unknown Characteristic: de5bf729-d711-4e47-af26-65e3012a5dc7
13:45:46.326 [binder:16654_5] WARN  n.f.g.s.b.TransactionBuilder - Unable to read characteristic: null
13:45:46.332 [binder:16654_5] DEBUG n.f.g.s.b.BtLEQueue - about to add: 13:45:46: Transaction task: Initializing device with 12 actions
13:45:46.339 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: SetDeviceStateAction to INITIALIZING
13:45:46.343 [AsyncTask #14] DEBUG n.f.g.d.g.GarminActivitySampleProvider - Found 2 sleep event samples between 1724238000 and 1724324399
13:45:46.346 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: ReadAction on characteristic: 00002a29-0000-1000-8000-00805f9b34fb
13:45:46.348 [AsyncTask #14] DEBUG n.f.g.d.g.GarminActivitySampleProvider - Found 18 sleep stage samples between 1724238000 and 1724324399
13:45:46.366 [binder:16654_1] DEBUG n.f.g.s.b.BtLEQueue - characteristic read: 00002a29-0000-1000-8000-00805f9b34fb (success)
13:45:46.368 [binder:16654_1] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - Device info: DeviceInfo{manufacturerName='Bluex', modelNumber='null', serialNumber='null', hardwareRevision='null', firmwareRevision='null', softwareRevision='null', systemId='null', regulatoryCertificationDataList='null', pnpId='null'}
13:45:46.370 [binder:16654_1] INFO  n.f.g.s.AbstractDeviceSupport - Got event for VERSION_INFO: GBDeviceEventVersionInfo: fwVersion: null; fwVersion2: null; hwVersion: null
13:45:46.382 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Last sleep stage before range: ts=1705487700000, stage=3
13:45:46.389 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Found 3 sleep samples between 1724281200 and 1724367599
13:45:46.395 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Last sleep stage before range: ts=1705487700000, stage=3
13:45:46.396 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: ReadAction on characteristic: 00002a24-0000-1000-8000-00805f9b34fb
13:45:46.402 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Found 3 sleep samples between 1724238000 and 1724324399
...
13:45:47.215 [binder:16654_1] DEBUG n.f.g.s.b.BtLEQueue - descriptor write: 00002902-0000-1000-8000-00805f9b34fb (success)
13:45:47.224 [main] INFO  n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true
13:45:47.237 [main] INFO  n.f.g.e.TimeChangeReceiver - Scheduling next periodic time sync in 158003000 millis (exact = false)
13:45:47.250 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:47.253 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:47.255 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:47.258 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:47.909 [main] INFO  n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true
13:45:48.224 [main] INFO  n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true
13:45:48.226 [main] DEBUG n.f.g.s.DeviceCommunicationService - device state update reason
13:45:48.230 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:48.233 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:48.235 [main] INFO  n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized!
13:45:48.275 [main] INFO  n.f.g.s.d.c.ColmiR0xDeviceSupport - Phone name sent: 04020A47420000000000000000000099
13:45:48.285 [main] ERROR n.f.g.LoggingExceptionHandler - Uncaught exception: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference
	at nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder.queue(TransactionBuilder.java:152)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.sendWrite(ColmiR0xDeviceSupport.java:392)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.setPhoneName(ColmiR0xDeviceSupport.java:420)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.postConnectInitialization(ColmiR0xDeviceSupport.java:156)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.lambda$initializeDevice$1$nodomain-freeyourgadget-gadgetbridge-service-devices-colmi-ColmiR0xDeviceSupport(ColmiR0xDeviceSupport.java:149)
	at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
	at android.os.Handler.handleCallback(Handler.java:958)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:230)
	at android.os.Looper.loop(Looper.java:319)
	at android.app.ActivityThread.main(ActivityThread.java:8919)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
13:45:50.069 [main] INFO  n.f.g.Logging - Gadgetbridge version: 0.81.0-6e6133927
This is crashing Gadgetbridge sometimes: <details> <summary>Logs</summary> ``` 13:45:46.293 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered unsupported service: Generic Access: 00001800-0000-1000-8000-00805f9b34fb 13:45:46.295 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered unsupported service: Generic Attribute: 00001801-0000-1000-8000-00805f9b34fb 13:45:46.297 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Device Information: 0000180a-0000-1000-8000-00805f9b34fb 13:45:46.299 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Manufacturer Name String: 00002a29-0000-1000-8000-00805f9b34fb 13:45:46.301 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Model Number String: 00002a24-0000-1000-8000-00805f9b34fb 13:45:46.303 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Serial Number String: 00002a25-0000-1000-8000-00805f9b34fb 13:45:46.304 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Hardware Revision String: 00002a27-0000-1000-8000-00805f9b34fb 13:45:46.307 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Firmware Revision String: 00002a26-0000-1000-8000-00805f9b34fb 13:45:46.309 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: System ID: 00002a23-0000-1000-8000-00805f9b34fb 13:45:46.311 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: IEEE 11073-20601 Regulatory Certification Data List: 00002a2a-0000-1000-8000-00805f9b34fb 13:45:46.312 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: PnP ID: 00002a50-0000-1000-8000-00805f9b34fb 13:45:46.314 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Unknown Service: 6e40fff0-b5a3-f393-e0a9-e50e24dcca9e 13:45:46.316 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Unknown Characteristic: 6e400002-b5a3-f393-e0a9-e50e24dcca9e 13:45:46.318 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Unknown Characteristic: 6e400003-b5a3-f393-e0a9-e50e24dcca9e 13:45:46.320 [binder:16654_5] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - discovered supported service: Unknown Service: de5bf728-d711-4e47-af26-65e3012a5dc7 13:45:46.322 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Unknown Characteristic: de5bf72a-d711-4e47-af26-65e3012a5dc7 13:45:46.324 [binder:16654_5] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - characteristic: Unknown Characteristic: de5bf729-d711-4e47-af26-65e3012a5dc7 13:45:46.326 [binder:16654_5] WARN n.f.g.s.b.TransactionBuilder - Unable to read characteristic: null 13:45:46.332 [binder:16654_5] DEBUG n.f.g.s.b.BtLEQueue - about to add: 13:45:46: Transaction task: Initializing device with 12 actions 13:45:46.339 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: SetDeviceStateAction to INITIALIZING 13:45:46.343 [AsyncTask #14] DEBUG n.f.g.d.g.GarminActivitySampleProvider - Found 2 sleep event samples between 1724238000 and 1724324399 13:45:46.346 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: ReadAction on characteristic: 00002a29-0000-1000-8000-00805f9b34fb 13:45:46.348 [AsyncTask #14] DEBUG n.f.g.d.g.GarminActivitySampleProvider - Found 18 sleep stage samples between 1724238000 and 1724324399 13:45:46.366 [binder:16654_1] DEBUG n.f.g.s.b.BtLEQueue - characteristic read: 00002a29-0000-1000-8000-00805f9b34fb (success) 13:45:46.368 [binder:16654_1] DEBUG n.f.g.s.d.c.ColmiR0xDeviceSupport - Device info: DeviceInfo{manufacturerName='Bluex', modelNumber='null', serialNumber='null', hardwareRevision='null', firmwareRevision='null', softwareRevision='null', systemId='null', regulatoryCertificationDataList='null', pnpId='null'} 13:45:46.370 [binder:16654_1] INFO n.f.g.s.AbstractDeviceSupport - Got event for VERSION_INFO: GBDeviceEventVersionInfo: fwVersion: null; fwVersion2: null; hwVersion: null 13:45:46.382 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Last sleep stage before range: ts=1705487700000, stage=3 13:45:46.389 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Found 3 sleep samples between 1724281200 and 1724367599 13:45:46.395 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Last sleep stage before range: ts=1705487700000, stage=3 13:45:46.396 [Gadgetbridge GATT Dispatcher] DEBUG n.f.g.s.b.BtLEQueue - About to run action: August 22 at 13:45: ReadAction on characteristic: 00002a24-0000-1000-8000-00805f9b34fb 13:45:46.402 [AsyncTask #14] DEBUG n.f.g.d.x.XiaomiSampleProvider - Found 3 sleep samples between 1724238000 and 1724324399 ... 13:45:47.215 [binder:16654_1] DEBUG n.f.g.s.b.BtLEQueue - descriptor write: 00002902-0000-1000-8000-00805f9b34fb (success) 13:45:47.224 [main] INFO n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true 13:45:47.237 [main] INFO n.f.g.e.TimeChangeReceiver - Scheduling next periodic time sync in 158003000 millis (exact = false) 13:45:47.250 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:47.253 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:47.255 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:47.258 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:47.909 [main] INFO n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true 13:45:48.224 [main] INFO n.f.g.s.DeviceCommunicationService - Setting broadcast receivers to: true 13:45:48.226 [main] DEBUG n.f.g.s.DeviceCommunicationService - device state update reason 13:45:48.230 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:48.233 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:48.235 [main] INFO n.f.g.s.r.AutoConnectIntervalReceiver - will reset connection delay, all devices are initialized! 13:45:48.275 [main] INFO n.f.g.s.d.c.ColmiR0xDeviceSupport - Phone name sent: 04020A47420000000000000000000099 13:45:48.285 [main] ERROR n.f.g.LoggingExceptionHandler - Uncaught exception: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference java.lang.NullPointerException: Attempt to invoke virtual method 'void nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue.add(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction)' on a null object reference at nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder.queue(TransactionBuilder.java:152) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.sendWrite(ColmiR0xDeviceSupport.java:392) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.setPhoneName(ColmiR0xDeviceSupport.java:420) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.postConnectInitialization(ColmiR0xDeviceSupport.java:156) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport.lambda$initializeDevice$1$nodomain-freeyourgadget-gadgetbridge-service-devices-colmi-ColmiR0xDeviceSupport(ColmiR0xDeviceSupport.java:149) at nodomain.freeyourgadget.gadgetbridge.service.devices.colmi.ColmiR0xDeviceSupport$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0) at android.os.Handler.handleCallback(Handler.java:958) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:230) at android.os.Looper.loop(Looper.java:319) at android.app.ActivityThread.main(ActivityThread.java:8919) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103) 13:45:50.069 [main] INFO n.f.g.Logging - Gadgetbridge version: 0.81.0-6e6133927 ``` </details>
Author
Owner

That's the same crash you reported earlier in this PR, and I hoped the if (characteristic != null) { would fix that... I can't reproduce it myself, and I don't know what else I can do here...

That's the same crash you reported earlier in this PR, and I hoped the `if (characteristic != null) {` would fix that... I can't reproduce it myself, and I don't know what else I can do here...
Owner

This is still the same as #3903:

  1. Ring connects
  2. postConnectInitialization is scheduled 2s in the future
  3. Ring disconnects before postConnectInitialization happens
  4. postConnectInitialization finally runs, the queue is null by now

Solution should be to unschedule everything from backgroundTasksHandler on dispose.

This is still the same as #3903: 1. Ring connects 2. `postConnectInitialization` is scheduled 2s in the future 3. Ring disconnects before `postConnectInitialization` happens 4. `postConnectInitialization` finally runs, the queue is null by now Solution should be to unschedule everything from `backgroundTasksHandler` on `dispose`.
Author
Owner

I could finally reliably reproduce the crash by quickly manually disconnecting the ring immediately after connection. Like you said, the Fossil solution fixed it here too: 3e1c621cc5..d7608a0f2b

I could finally reliably reproduce the crash by quickly manually disconnecting the ring immediately after connection. Like you said, the Fossil solution fixed it here too: https://codeberg.org/Freeyourgadget/Gadgetbridge/compare/3e1c621cc5d7030b4936378a14f08c25694620b8..d7608a0f2bbcaee233fc2b9cabb33d77b97ca562
arjan5 marked this conversation as resolved
@ -127,6 +127,12 @@ public class GBDaoGenerator {
addWena3StressSample(schema, user, device);
addFemometerVinca2TemperatureSample(schema, user, device);
addMiScaleWeightSample(schema, user, device);
addColmiActivitySample(schema, user, device);
Owner

Reminder to bump the schema version, I think the rebase erased it.

Reminder to bump the schema version, I think the rebase erased it.
Author
Owner

You're right, thanks!

You're right, thanks!
arjan5 marked this conversation as resolved
@ -0,0 +509,4 @@
byte[] userPrefsPacket = buildPacket(new byte[]{
ColmiR0xConstants.CMD_PREFERENCES,
ColmiR0xConstants.PREF_WRITE,
0x00, // 24h format, 0x01 is 12h format
Owner
final GBPrefs gbPrefs = new GBPrefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
final String timeFormat = gbPrefs.getTimeFormat();
final boolean is24hour = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H.equals(timeFormat);

And add devicesettings_timeformat in the coordinator.

```java final GBPrefs gbPrefs = new GBPrefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress())); final String timeFormat = gbPrefs.getTimeFormat(); final boolean is24hour = DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_24H.equals(timeFormat); ``` And add `devicesettings_timeformat` in the coordinator.
Owner

:) but it's a ring, so this is irrelevant.

:) but it's a ring, so this is irrelevant.
Author
Owner

It is irrelevant indeed, and may even break the (weird) time setting packet parser :)

It is irrelevant indeed, and may even break the (weird) time setting packet parser :)
joserebelo marked this conversation as resolved
@ -0,0 +577,4 @@
}
private void fetchHistoryActivity() {
getDevice().setBusyTask("activityHistoryRequest");
Owner

This does not update the UI, you need to call sendDeviceUpdateIntent after too.

Also, this shows in the UI, so maybe use a translatable string (busy_task_fetch_activity_data).

There are others for stress and what-not.

This does not update the UI, you need to call `sendDeviceUpdateIntent` after too. Also, this shows in the UI, so maybe use a translatable string (`busy_task_fetch_activity_data`). There are others for stress and what-not.
Author
Owner

Done, thanks!

Done, thanks!
arjan5 marked this conversation as resolved
arjan5 merged commit e23caa3ee6 into master 2024-08-23 23:54:07 +02:00
arjan5 deleted branch colmi-r02 2024-08-23 23:54:09 +02:00
First-time contributor

@arjan5 Congrats! 🎉

@arjan5 Congrats! 🎉
Contributor

Yeah thanks for the great effort! Really looking forward to the next release ;) btw @joserebelo when is the next one planned for?

Should we write some wiki page as well or is that auto generated from the device code?

Yeah thanks for the great effort! Really looking forward to the next release ;) btw @joserebelo when is the next one planned for? Should we write some wiki page as well or is that auto generated from the device code?
Author
Owner

I'm planning on adding it to the website this weekend!

I'm planning on adding it to the website this weekend!
First-time contributor

Note: I will skip reviewing the parts of the code that I commented as “[ ] Self-reminder to review” because my work is quite hectic these days. If weird bugs start popping up in these parts of the code don't hesitate to ask me to reverse engineer them to make sure that the implementation is entirely correct. It takes time for me to carefully reverse engineer things though — last review necessitated me to do around 3 hours of reverse engineering.

**Note**: I will skip reviewing the parts of the code that I commented as “[ ] Self-reminder to review” because my work is quite hectic these days. If weird bugs start popping up in these parts of the code don't hesitate to ask me to reverse engineer them to make sure that the implementation is entirely correct. It takes time for me to carefully reverse engineer things though — last review necessitated me to do around 3 hours of reverse engineering.
Author
Owner

@devnoname120 No problem and thank you for your time! You have definitely provided very useful information!

@devnoname120 No problem and thank you for your time! You have definitely provided very useful information!
Author
Owner

Yeah thanks for the great effort! Really looking forward to the next release ;) btw @joserebelo when is the next one planned for?

Should we write some wiki page as well or is that auto generated from the device code?

Done: 37a953d34b. Will be online tomorrow.
The next release is not planned yet I'm afraid.

> Yeah thanks for the great effort! Really looking forward to the next release ;) btw @joserebelo when is the next one planned for? > > Should we write some wiki page as well or is that auto generated from the device code? Done: https://codeberg.org/Freeyourgadget/website/commit/37a953d34b89676376224609fa0e7b15b1729219. Will be online tomorrow. The next release is not planned yet I'm afraid.
First-time contributor

Hello!
First, thanks for your work, it's impressive!
I just have few questions :

  • Is there any news about the 3.00.10 and newer features added to gadgetbridge?
  • I have three rings, two RY03_V3.0 hw and RY03_3.00.18_240930 and one R02_V3.0 hw and R02_3.00.11_240730. With the R02, everything works perfectly (except the not supported yet features of course, and also the battery drain very fast), but with the RY03, SpO2 doesn't work, it failed to retrieve data related to that features. Is the fact that is RY hw make a differences? I don't even know what it means, is it a knock-off, or a new version?
    Thanks you a lot for your software, it's the only way in my knowledge to have health report without giving data to big corps, it's very important imho.
    Have a nice day!
Hello! First, thanks for your work, it's impressive! I just have few questions : - Is there any news about the 3.00.10 and newer features added to gadgetbridge? - I have three rings, two `RY03_V3.0 hw and RY03_3.00.18_240930` and one `R02_V3.0 hw and R02_3.00.11_240730`. With the R02, everything works perfectly (except the not supported yet features of course, and also the battery drain very fast), but with the RY03, SpO2 doesn't work, it failed to retrieve data related to that features. Is the fact that is RY hw make a differences? I don't even know what it means, is it a knock-off, or a new version? Thanks you a lot for your software, it's the only way in my knowledge to have health report without giving data to big corps, it's very important imho. Have a nice day!
Author
Owner

@Froggy232 Thank you for your kind words!

Firmware 3.00.10 (and now 3.00.17) added two new features as far as I'm aware: REM sleep and HRV measurement. REM support was already added to Gadgetbridge and is functional in the nightly releases. HRV is currently a work in progress in a separate branch. I haven't fully figured out the bluetooth packet format yet, which is a requirement for the implementation.

Regarding the RY* types of rings, I have never seen those before. Are they a newer type or some clone? If you want them to be fully supported too, can you make Bluetooth dumps of them syncing SpO2 data with QRing (the official app)? More info can be found here if you need it: https://gadgetbridge.org/internals/development/bluetooth/#androids-bluetooth-logging

@Froggy232 Thank you for your kind words! Firmware 3.00.10 (and now 3.00.17) added two new features as far as I'm aware: REM sleep and HRV measurement. REM support was already added to Gadgetbridge and is functional in the nightly releases. HRV is currently a work in progress in a separate branch. I haven't fully figured out the bluetooth packet format yet, which is a requirement for the implementation. Regarding the RY* types of rings, I have never seen those before. Are they a newer type or some clone? If you want them to be fully supported too, can you make Bluetooth dumps of them syncing SpO2 data with QRing (the official app)? More info can be found here if you need it: https://gadgetbridge.org/internals/development/bluetooth/#androids-bluetooth-logging
First-time contributor

@arjan5 If it helps I can look into reverse engineering HRV.

@arjan5 If it helps I can look into reverse engineering HRV.
Author
Owner

@devnoname120 If you would do that, that would be great! I got a long way analyzing the packet format, but I'm missing some details. It would definitely speed me up if you could help with that. :)

@devnoname120 If you would do that, that would be great! I got a long way analyzing the packet format, but I'm missing some details. It would definitely speed me up if you could help with that. :)
Author
Owner

Let's move to #4187 for the RY0* rings.

Let's move to https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/4187 for the `RY0*` rings.
Author
Owner

And for HRV see here: #4222

And for HRV see here: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/4222
Contributor

Ah, sry @devnoname120 it was not my intention to steal your show :> Seems we all had the same idea at the same time :D

Ah, sry @devnoname120 it was not my intention to steal your show :> Seems we all had the same idea at the same time :D
First-time contributor

@LordOfTheRats No worries at all!

@LordOfTheRats No worries at all!
First-time contributor

Hi, my name is Phil and I am a newbie to this type of development, I have been writing code for 25 years now, primarily in the Microsoft stack and in the financial sector, its got boring over the last few years so recently I have been learning some android stuff mainly around the communication of data retrieved from devices.

If its of any use to any of you programmers I have the sdk for a couple of the colmi rings, R02 and the new one, not sure on the ref number, I also have an sdk for the CORE body temperature sensor if it would be of benefit for a gadget bridge integration, I am going to give it a go myself but it wont be of production grade code! :) just let me know if they would be of use.

Phil

Hi, my name is Phil and I am a newbie to this type of development, I have been writing code for 25 years now, primarily in the Microsoft stack and in the financial sector, its got boring over the last few years so recently I have been learning some android stuff mainly around the communication of data retrieved from devices. If its of any use to any of you programmers I have the sdk for a couple of the colmi rings, R02 and the new one, not sure on the ref number, I also have an sdk for the CORE body temperature sensor if it would be of benefit for a gadget bridge integration, I am going to give it a go myself but it wont be of production grade code! :) just let me know if they would be of use. Phil
Owner

@philod I am not sure what SDK you are referring to, but keep in mind that non-free / proprietary SDK and/or libraries can't be integrated in Gadgetbridge.

@philod I am not sure what SDK you are referring to, but keep in mind that non-free / proprietary SDK and/or libraries can't be integrated in Gadgetbridge.
First-time contributor

@joserebelo I was asked to scope the possibilities of using the colmi rings for a company who make high protein bars / butters, I asked colmi for any help in communicating with the R02 rings, they sent me an sdk and pdf guide. I didnt mean using the sdk for integration I meant more for understanding the comms between ring and app, however I see that the more common way is to sniff the connection and look at the data.

part of the same project used the Core sensor, that came with the source code for the app which is readily available on github.

@joserebelo I was asked to scope the possibilities of using the colmi rings for a company who make high protein bars / butters, I asked colmi for any help in communicating with the R02 rings, they sent me an sdk and pdf guide. I didnt mean using the sdk for integration I meant more for understanding the comms between ring and app, however I see that the more common way is to sniff the connection and look at the data. part of the same project used the Core sensor, that came with the source code for the app which is readily available on github.
Author
Owner

Hi @philod, thanks for writing in! What exactly do you want to accomplish with Gadgetbridge? We already have pretty much complete support for the R02/R03/R06/R10 rings, so we don't need the SDK ourselves.

Hi @philod, thanks for writing in! What exactly do you want to accomplish with Gadgetbridge? We already have pretty much complete support for the R02/R03/R06/R10 rings, so we don't need the SDK ourselves.
First-time contributor

@arjan5 what do I want to accomplish.... Learn and be able to ask questions when I dont understand how or why something is done, Gadgetbridge seems to have most functionality that I want to understand in more depth.

@arjan5 what do I want to accomplish.... Learn and be able to ask questions when I dont understand how or why something is done, Gadgetbridge seems to have most functionality that I want to understand in more depth.
First-time contributor

Hi all. Has anyone tried out integration with a sports tracking app as described in https://gadgetbridge.org/basics/integrations/sports/ ? The Device Specific Settings for my R06 doesn't include options for "3rd party realtime HR access" or "Visible while connected", and sure enough FitoTrack and OpenTracks can't find a heart rate sensor. I can see that the documentation is out of date in some other respects, so maybe those settings have moved. But if those settings haven't been implemented for this device, I figure this is the place to ask. Anyone able to weigh in?

Edit: I did some more reading and I suppose I misunderstood what gadgetbridge is trying to accomplish. I thought gadgetbridge would bridge gadgets to other apps and make e.g. HR data available app-to-app through Android; my new understanding is that FitoTrack and OpenTracks connect directly to their supported devices, and the GB settings I mentioned just make sure that GB isn't hogging the connection. Regardless of what's happening in GB, FitoTrack and OpenTracks don't list support for this ring, so can't expect a connection at this point. But, followup question: does the absence of those device-specific settings mean that this device never gets hogged by GB, or that there's no way to make GB not hog the connection for this device?

Finally, what sets the limit of one reading per 5 minutes? I see hrIntervalSettings receives a minutes int, so I'd think we could at least get down to 1 minute. But we also can manually trigger a measurement; doesn't that give the potential for something like a "sport mode" that measures as frequently as every 30s or something? For me, a lot of the utility in a wearable comes from real-time HR measurement while exercising so I can constantly ensure I'm in the right HR zone, and a 5 minute sampling time is too slow for that.

Hi all. Has anyone tried out integration with a sports tracking app as described in https://gadgetbridge.org/basics/integrations/sports/ ? The Device Specific Settings for my R06 doesn't include options for "3rd party realtime HR access" or "Visible while connected", and sure enough FitoTrack and OpenTracks can't find a heart rate sensor. I can see that the documentation is out of date in some other respects, so maybe those settings have moved. But if those settings haven't been implemented for this device, I figure this is the place to ask. Anyone able to weigh in? Edit: I did some more reading and I suppose I misunderstood what gadgetbridge is trying to accomplish. I thought gadgetbridge would **bridge gadgets** to other apps and make e.g. HR data available app-to-app through Android; my new understanding is that FitoTrack and OpenTracks connect directly to their supported devices, and the GB settings I mentioned just make sure that GB isn't hogging the connection. Regardless of what's happening in GB, FitoTrack and OpenTracks don't list support for this ring, so can't expect a connection at this point. But, followup question: does the absence of those device-specific settings mean that this device never gets hogged by GB, or that there's no way to make GB not hog the connection for this device? Finally, what sets the limit of one reading per 5 minutes? I see `hrIntervalSettings` receives a `minutes` int, so I'd think we could at least get down to 1 minute. But we also can manually trigger a measurement; doesn't that give the potential for something like a "sport mode" that measures as frequently as every 30s or something? For me, a lot of the utility in a wearable comes from real-time HR measurement while exercising so I can constantly ensure I'm in the right HR zone, and a 5 minute sampling time is too slow for that.
Author
Owner

Hi @ttshaw1

The conclusion you reached about HR is correct: apps like OpenTracks and FitoTrack can only receive HR data from gadgets that use the standardized Heart Rate GATT Profile, which the Colmi rings and most other devices sadly don't use. Gadgetbridge also cannot be used as an intermediary for that, although there has been an idea in the past to implement a generic API for that to be used by apps like OpenTracks and FitoTrack.

Regarding the measurement interval, you're mixing all-day monitoring with real-time monitoring. Both are possible, but all-day monitoring is indeed limited (by the ring's protocol) to once every 5 minutes or longer. I believe we also added real-time monitoring, which you can find under the "live activity" tab in the activities screen.

Hi @ttshaw1 The conclusion you reached about HR is correct: apps like OpenTracks and FitoTrack can only receive HR data from gadgets that use the standardized Heart Rate GATT Profile, which the Colmi rings and most other devices sadly don't use. Gadgetbridge also cannot be used as an intermediary for that, although there has been an idea in the past to implement a generic API for that to be used by apps like OpenTracks and FitoTrack. Regarding the measurement interval, you're mixing all-day monitoring with real-time monitoring. Both are possible, but all-day monitoring is indeed limited (by the ring's protocol) to once every 5 minutes or longer. I believe we also added real-time monitoring, which you can find under the "live activity" tab in the activities screen.
First-time contributor

I see, thanks Arjan. By live activity, you're talking about this, right?
Screenshot_20241121-093713_Gadgetbridge

It doesn't seem like anything is happening when I click on "Start your activity"; no changes in the GUI and the ring doesn't turn on any LEDs. I started a log file and tried "Start your activity" 3 times; it looks like startcommand: nodomain.freeyourgadget.gadgetbridge.devices.action.enable_realtime_steps and startcommand: nodomain.freeyourgadget.gadgetbridge.devices.action.realtime_hr_measurement executed three times but I didn't see anything else of interest in the log. What's the expected behavior?

I see, thanks Arjan. By live activity, you're talking about this, right? ![Screenshot_20241121-093713_Gadgetbridge](/attachments/89e58472-7942-4d3e-9558-71f198e904e3) It doesn't seem like anything is happening when I click on "Start your activity"; no changes in the GUI and the ring doesn't turn on any LEDs. I started a log file and tried "Start your activity" 3 times; it looks like `startcommand: nodomain.freeyourgadget.gadgetbridge.devices.action.enable_realtime_steps` and `startcommand: nodomain.freeyourgadget.gadgetbridge.devices.action.realtime_hr_measurement` executed three times but I didn't see anything else of interest in the log. What's the expected behavior?
Author
Owner

That's indeed what I'm talking about :)
But (I agree the interface is not perfect) the "Start your activity" is not a button, it's a notice to the user. When you start walking/running with this screen open, the data will be updated live.
However, since you say the HR LED isn't turning on, I think we never got around to implementing that for these rings. I wasn't sure about this, apparently I had another device in mind.
We could pretty easily add this though, as the ring has a manual HR request function that we could misuse for this purpose.

That's indeed what I'm talking about :) But (I agree the interface is not perfect) the "Start your activity" is not a button, it's a notice to the user. When you start walking/running with this screen open, the data will be updated live. However, since you say the HR LED isn't turning on, I think we never got around to implementing that for these rings. I wasn't sure about this, apparently I had another device in mind. We could pretty easily add this though, as the ring has a manual HR request function that we could misuse for this purpose.
First-time contributor

Ah, gotcha. My mistake on the UI.

Armed with that knowledge, I started a log and jogged in place for five minutes, which is also my current HR sampling interval for the device. About halfway through, the PPG LEDs came on for around 40 seconds, but then shut back off. I didn't see anything happen on the Live Activity screen at any point. Without looking into the code, it does seem like there's nothing implemented for this device.

I'm totally in favor of misusing the manual HR request. I'm much more a cyclist than a runner, so I'm very interested in fast HR sampling, and not interested in fast step count sampling. My use case would be to monitor my current (or <40 seconds old) HR and ensure I'm exerting myself enough to be at the heartrate I want. When implemented, will it be possible to start a Live activity and then flip over to one of the other tabs to read out my heart rate? Or can we put a HR display on the Live activity tab? And if the start of the Live activity is triggered by sensor readings, can we make sure a high heart rate and low steps (cycling conditions) will still trigger it?

Please let me know if you want me to make a new issue for this. I'm happy to help with rote C-like data processing and such, but when we start getting into the complexities of Android I'm afraid I'm out of my league.

Ah, gotcha. My mistake on the UI. Armed with that knowledge, I started a log and jogged in place for five minutes, which is also my current HR sampling interval for the device. About halfway through, the PPG LEDs came on for around 40 seconds, but then shut back off. I didn't see anything happen on the Live Activity screen at any point. Without looking into the code, it does seem like there's nothing implemented for this device. I'm totally in favor of misusing the manual HR request. I'm much more a cyclist than a runner, so I'm very interested in fast HR sampling, and not interested in fast step count sampling. My use case would be to monitor my current (or <40 seconds old) HR and ensure I'm exerting myself enough to be at the heartrate I want. When implemented, will it be possible to start a Live activity and then flip over to one of the other tabs to read out my heart rate? Or can we put a HR display on the Live activity tab? And if the start of the Live activity is triggered by sensor readings, can we make sure a high heart rate and low steps (cycling conditions) will still trigger it? Please let me know if you want me to make a new issue for this. I'm happy to help with rote C-like data processing and such, but when we start getting into the complexities of Android I'm afraid I'm out of my league.
Author
Owner

Hmm, I thought we had it for at least real time steps, but again I'm probably mixing up devices I've worked on.
I think the Live Activity already has a live HR chart, or perhaps it's merged with the steps chart... Would have to check that.
Anyway, as far as I know the Live Activity screen gets loaded empty every time you open it. But if it contains HR than I understand that's not really an issue. Triggering it may be an issue for you if it really depends on steps, but that's again something to check in the code.

And yes, please make a new issue for this new feature. This PR is already merged which means it's much less visible to others.

Hmm, I thought we had it for at least real time steps, but again I'm probably mixing up devices I've worked on. I think the Live Activity already has a live HR chart, or perhaps it's merged with the steps chart... Would have to check that. Anyway, as far as I know the Live Activity screen gets loaded empty every time you open it. But if it contains HR than I understand that's not really an issue. Triggering it may be an issue for you if it really depends on steps, but that's again something to check in the code. And yes, please make a new issue for this new feature. This PR is already merged which means it's much less visible to others.
First-time contributor

Just added feature request 4391. Sorry for the delay, and thanks for adding this device to begin with!

Just added [feature request 4391](https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/4391). Sorry for the delay, and thanks for adding this device to begin with!
First-time contributor

Colmi has listed a R11 on their website, which they say supports app "Da Rings", not QRings. Not personally invested in this model so I didn't make an issue, but might be of interest to some here.

https://www.colmi.com/colmi-r11-smart-ring-product/

Colmi has listed a R11 on their website, which they say supports app "Da Rings", not QRings. Not personally invested in this model so I didn't make an issue, but might be of interest to some here. https://www.colmi.com/colmi-r11-smart-ring-product/
First-time contributor

Also I had a quick look at reviews and it doesn't seem to be as good as the other rings, and the new app is apparently worse

Also I had a quick look at reviews and it doesn't seem to be as good as the other rings, and the new app is apparently worse
Author
Owner

@maya Thanks for notifying us! That's indeed a completely different device that can't be supported with the same code as the other Colmi rings so far. However, there is good news: the Da Rings app is made by MO YOUNG LTD, which means it is very likely to use the Moyoung communication protocol, for which we have an almost finished PR: #4070. So adding support for the R11 likely will be pretty easy.

@maya Thanks for notifying us! That's indeed a completely different device that can't be supported with the same code as the other Colmi rings so far. However, there is good news: the [Da Rings app](https://play.google.com/store/apps/details?id=com.moyoung.ring) is made by MO YOUNG LTD, which means it is very likely to use the Moyoung communication protocol, for which we have an almost finished PR: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/4070. So adding support for the R11 likely will be pretty easy.
First-time contributor

Indeed the Colmi R11 has the same Realtek chipset model RTL8762 ESF like the R10, and not the BlueX Micro RF03 from the Colmi R06, R03 or R02.
Sensor STK8321 & heart rate sensor Vcare VC30F are the same since the first launched ring.

I cannot figure out if the OEM official site it's colmi.com or colmi.info

I still have a question. From GB website: "In addition, this device supports temperature measurements and gestures. Support for gestures is not implemented in Gadgetbridge yet."
What are these gestures?

Indeed the Colmi R11 has the same Realtek chipset model RTL8762 ESF like the R10, and not the BlueX Micro RF03 from the Colmi R06, R03 or R02. Sensor STK8321 & heart rate sensor Vcare VC30F are the same since the first launched ring. I cannot figure out if the OEM official site it's colmi.com or colmi.info I still have a question. From [GB website](https://gadgetbridge.org/gadgets/wearables/colmi/#device__colmi_r09): _"In addition, this device supports temperature measurements and gestures. Support for gestures is not implemented in Gadgetbridge yet."_ What are these gestures?
Author
Owner

If you tap the ring twice, it will send a bluetooth packet to GB. We're not doing anything with that yet.

If you tap the ring twice, it will send a bluetooth packet to GB. We're not doing anything with that yet.
First-time contributor

Hi, I just started with Gadgedbridge and am quite impressed.

I have the Colmi R09 and playing around with it.

I noticed the following:

  • In the devices Tab the Ring is on "Fetching temperature data" status forever and does not change and no temp data is logged

Does anyone have experience with this ring?

Issue:
#4491 (comment)

Hi, I just started with Gadgedbridge and am quite impressed. I have the **Colmi R09** and playing around with it. _I noticed the following:_ - In the devices Tab the Ring is on "Fetching temperature data" status forever and does not change and no temp data is logged Does anyone have experience with this ring? Issue: https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/4491#issue-946390
Owner

@pronext see #4451, that should be fixed in the nightly.

@pronext see #4451, that should be fixed in the nightly.
First-time contributor

I'm looking for a smart ring that aligns with my privacy concerns and "threat model". I've been researching solutions to track my physical activity, health, and sleep, and I stumbled upon the GadgetBridge project. I appreciate the work of all the contributors.

In my search for an affordable smart ring, I found the Colmi brand, which seems to be supported by GadgetBridge. According to the project's page (https://gadgetbridge.org/gadgets/wearables/colmi/), the Colmi R02, R03, R06, R09, and R10 are supported, but the R11 is not (https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2569775).

Now, I'm considering buying one of these smart rings, but I'm unsure which one to choose. They all seem similar in their descriptions. Do you have any recommendations for the most qualitative option? Have you had any experience with these devices? It seems like the R09 and R10 are the most advanced models.

I'm leaning towards the R10 (https://www.colmi.info/products/colmi-r10-smart-ring). Additionally, the R12 (https://www.colmi.info/products/colmi-r12-smart-ring) has been recently released - do you think it would be compatible with GadgetBridge?

If you have any other suggestions for affordable and high-quality connected devices (such as the Mi Band 8, which seems to be a good compromise), I'm all ears and would appreciate your input. Please feel free to share your recommendations.

Thank you for your time, and I look forward to your responses.

I'm looking for a smart ring that aligns with my privacy concerns and "threat model". I've been researching solutions to track my physical activity, health, and sleep, and I stumbled upon the GadgetBridge project. I appreciate the work of all the contributors. In my search for an affordable smart ring, I found the Colmi brand, which seems to be supported by GadgetBridge. According to the project's page (https://gadgetbridge.org/gadgets/wearables/colmi/), the Colmi R02, R03, R06, R09, and R10 are supported, but the R11 is not ([https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2569775](https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2569775)). Now, I'm considering buying one of these smart rings, but I'm unsure which one to choose. They all seem similar in their descriptions. **Do you have any recommendations for the most qualitative option? Have you had any experience with these devices? It seems like the R09 and R10 are the most advanced models.** I'm leaning towards the R10 ([https://www.colmi.info/products/colmi-r10-smart-ring](https://www.colmi.info/products/colmi-r10-smart-ring)). Additionally, the R12 ([https://www.colmi.info/products/colmi-r12-smart-ring](https://www.colmi.info/products/colmi-r12-smart-ring)) has been recently released - **do you think it would be compatible with GadgetBridge?** If you have any other suggestions for affordable and high-quality connected devices (such as the Mi Band 8, which seems to be a good compromise), I'm all ears and would appreciate your input. Please feel free to share your recommendations. Thank you for your time, and I look forward to your responses.
Author
Owner

@miscaleb No problem! According to the product page, the R12 is supported by the QRing app, which makes it very likely that it will just work with Gadgetbridge too. It's not officially supported yet, so you would have to add it as a R10 for instance. And then let us know some info to make it officially supported.... ;)

Regarding the hardware, I believe the actual health sensors are (more or less) the same on all these models, so you can't really go wrong (or right... it's a budget brand after all...).

@miscaleb No problem! According to the product page, the R12 is supported by the QRing app, which makes it very likely that it will just work with Gadgetbridge too. It's not officially supported yet, so you would have to add it as a R10 for instance. And then let us know some info to make it officially supported.... ;) Regarding the hardware, I believe the actual health sensors are (more or less) the same on all these models, so you can't really go wrong (or right... it's a budget brand after all...).
First-time contributor

@pronext wrote in #3896 (comment):

Hi, I just started with Gadgedbridge and am quite impressed.

I have the Colmi R09 and playing around with it.

I noticed the following:

* In the devices Tab the Ring is on "Fetching temperature data" status forever and does not change and no temp data is logged

Does anyone have experience with this ring?

Issue: #4491 (commentaire)

Hi,
I have this model too, but temp is ok.
My firmware is rt09 3.10.16

@pronext wrote in https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3896#issuecomment-2579149: > Hi, I just started with Gadgedbridge and am quite impressed. > > I have the **Colmi R09** and playing around with it. > > _I noticed the following:_ > > * In the devices Tab the Ring is on "Fetching temperature data" status forever and does not change and no temp data is logged > > > Does anyone have experience with this ring? > > Issue: #4491 (commentaire) Hi, I have this model too, but temp is ok. My firmware is rt09 3.10.16
First-time contributor

Hi, I dont know if anyone can help, I am writing this into a lolin esp32 using the arduino IDE, I am not at all upto any sort of speed with C++ but have successfully connected an R09 ring, received notifications and can request things like battery level and anything that doesn't use byte 1-4 as the epoch date time. I am doing the below to try and request the heart rates

byte byteArray[4];
time_t now = std::time(nullptr);
struct tm * lt = localtime(&now);
lt->tm_mday -= 1;
lt->tm_hour = 0;
lt->tm_min = 0;
lt->tm_sec = 0;
time_t t = mktime(lt);
uint32_t t32 = t;
byteArray[0] = (byte) ((t32) & 0xFF) ;
byteArray[1] = (byte) ((t32 >> 8) & 0xFF) ;
byteArray[2] = (byte) ((t32 >> 16) & 0xFF) ;
byteArray[3] = (byte) ((t32 >> 24) & 0xFF) ;

std::vector<uint8_t> hrHistoryRequest = buildPacket({
0x15,
byteArray[0],
byteArray[1],
byteArray[2],
byteArray[3]
});

can anyone give me some pointers as to what I am doing wrong? I have been trying to sort this out for a few days now and need some expert help :)

Hi, I dont know if anyone can help, I am writing this into a lolin esp32 using the arduino IDE, I am not at all upto any sort of speed with C++ but have successfully connected an R09 ring, received notifications and can request things like battery level and anything that doesn't use byte 1-4 as the epoch date time. I am doing the below to try and request the heart rates byte byteArray[4]; time_t now = std::time(nullptr); struct tm * lt = localtime(&now); lt->tm_mday -= 1; lt->tm_hour = 0; lt->tm_min = 0; lt->tm_sec = 0; time_t t = mktime(lt); uint32_t t32 = t; byteArray[0] = (byte) ((t32) & 0xFF) ; byteArray[1] = (byte) ((t32 >> 8) & 0xFF) ; byteArray[2] = (byte) ((t32 >> 16) & 0xFF) ; byteArray[3] = (byte) ((t32 >> 24) & 0xFF) ; std::vector<uint8_t> hrHistoryRequest = buildPacket({ 0x15, byteArray[0], byteArray[1], byteArray[2], byteArray[3] }); can anyone give me some pointers as to what I am doing wrong? I have been trying to sort this out for a few days now and need some expert help :)
Author
Owner

@philod I don't know anything about coding in C++, but perhaps you can use our reverse engineering work?
Take a look at my (ugly but working) Wireshark dissector for the Colmi rings protocol: https://codeberg.org/Freeyourgadget/Gadgetbridge-tools/src/branch/main/colmi/wireshark/colmi_r0x.lua
Or our implementation here: https://codeberg.org/Freeyourgadget/Gadgetbridge/src/branch/master/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java#L231

@philod I don't know anything about coding in C++, but perhaps you can use our reverse engineering work? Take a look at my (ugly but working) Wireshark dissector for the Colmi rings protocol: https://codeberg.org/Freeyourgadget/Gadgetbridge-tools/src/branch/main/colmi/wireshark/colmi_r0x.lua Or our implementation here: https://codeberg.org/Freeyourgadget/Gadgetbridge/src/branch/master/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/colmi/ColmiR0xDeviceSupport.java#L231
First-time contributor

@arjan5 Thankyou for the pointers, I will read these and see if they help.

@arjan5 Thankyou for the pointers, I will read these and see if they help.
First-time contributor

HI, If it helps anyone, here is what works for me in C++ with an ESP32, it simply creates the first 5 bytes that are then passed to the packet builder.

std::vector<uint8_t> createt32DayTimeStamp(uint8_t cmd,int daysAgo){
time_t now = std::time(nullptr);
//no concept of timezone when converted back using mktime
//struct tm * dayt = localtime(&now);
//dayt->tm_mday -= daysAgo;
//dayt->tm_hour = 0;
//dayt->tm_min = 0;
//dayt->tm_sec = 0;
//time_t t = mktime(dayt);
//this works though
now -= 86400 * daysAgo;
uint32_t t32 = now;
//uint32_t t32 = t;
std::vector<uint8_t> byteArray(5);
byteArray[0] = (byte) cmd;
byteArray[1] = (byte) ((t32) & 0xFF) ;
byteArray[2] = (byte) ((t32 >> 8) & 0xFF) ;
byteArray[3] = (byte) ((t32 >> 16) & 0xFF) ;
byteArray[4] = (byte) ((t32 >> 24) & 0xFF) ;
return byteArray;
}

HI, If it helps anyone, here is what works for me in C++ with an ESP32, it simply creates the first 5 bytes that are then passed to the packet builder. std::vector<uint8_t> createt32DayTimeStamp(uint8_t cmd,int daysAgo){ time_t now = std::time(nullptr); //no concept of timezone when converted back using mktime //struct tm * dayt = localtime(&now); //dayt->tm_mday -= daysAgo; //dayt->tm_hour = 0; //dayt->tm_min = 0; //dayt->tm_sec = 0; //time_t t = mktime(dayt); //this works though now -= 86400 * daysAgo; uint32_t t32 = now; //uint32_t t32 = t; std::vector<uint8_t> byteArray(5); byteArray[0] = (byte) cmd; byteArray[1] = (byte) ((t32) & 0xFF) ; byteArray[2] = (byte) ((t32 >> 8) & 0xFF) ; byteArray[3] = (byte) ((t32 >> 16) & 0xFF) ; byteArray[4] = (byte) ((t32 >> 24) & 0xFF) ; return byteArray; }
First-time contributor

Hi, do you know if the R02 ring stores the raw accelometer data on the ring or it only uses it to calculate the steps, distance... and stores only these syncable results?

Hi, do you know if the R02 ring stores the raw accelometer data on the ring or it only uses it to calculate the steps, distance... and stores only these syncable results?
Author
Owner

@shanyh No, I don't believe the ring stores raw data, I think it only stores the syncable steps and stuff, as that's the most efficient thing to do.

@shanyh No, I don't believe the ring stores raw data, I think it only stores the syncable steps and stuff, as that's the most efficient thing to do.
First-time contributor

@arjan5 Thanks, that's what I believe also, wanted another opinion to be sure.

@arjan5 Thanks, that's what I believe also, wanted another opinion to be sure.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
17 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Freeyourgadget/Gadgetbridge#3896
No description provided.