Add support for the Colmi R02/R03/R06 smart rings #3896
No reviewers
Labels
No labels
device mi band 7
activity post processing
activity/health
Android 12
Android 13
android integrations
architecture
Bangle.js
blocked
bug
changes requested
charts
dependency
deprecation notice
details not provided
developer documentation
device amazfit band 5
device amazfit bip
device amazfit cor
device Casio
device colmi r0x
device fossil
device garmin
device gtr 2e
device gts 2 mini
device h30
device hplus
device huami
device Huawei
device liveview
device mi band
device mi band 2
device mi band 3
device mi band 4
device mi band 5
device mi band 6
device no.1 f1
device pace
device pebble
device pebble 2
device pinetime infinitime
device request
device sony
device support
device watch 9
device xiaomi
discussion
documentation
duplicate
enhancement
feature request
Gadgetbridge
good first issue
help wanted
i am developing my own app can you help
icebox
intent api
internationalisation
invalid
needs work
network companion app
new device
no feedback
not a bug
notifications
pairing/connecting
potentially fixed / confirm and close
question
reconnection
regression
research
security
seems abandoned
Solved, waiting for F-Droid release
suggest to close
task
user interface / UX
wear os
weather
wontfix
Zepp OS
No milestone
No project
No assignees
17 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Freeyourgadget/Gadgetbridge#3896
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "colmi-r02"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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:
Possible features but not planned for this PR:
@ -0,0 +1,56 @@
/* Copyright (C) 2024 José Rebelo
The script will replace this because of git, if you want to update it right away :p
@ -0,0 +122,4 @@
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
getDevice().setFirmwareVersion("N/A");
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
:private String cachedFirmwareVersion = null;
But we need to eventually figure out why this is being unset.
Thanks, I just copied this from the example and other implementations. It's definitely something to investigate.
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?
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());
}
@ -0,0 +126,4 @@
getDevice().setFirmwareVersion2("N/A");
deviceInfoProfile.requestDeviceInfo(builder);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
Should this actually be moved to
postConnectInitialization
?I don't think so, because the commands in
postConnectInitialization
are not waiting for the responses from the device. MovingINITIALIZED
to there would effectively make no difference.@ -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);
I don't have the btsnoops at hand here, but do we get incoming notifications in the write characteristic, or should this be removed?
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.
@ -0,0 +255,4 @@
case ColmiR02Constants.CMD_GOALS:
ByteBuffer stepsData = ByteBuffer.allocate(4);
stepsData.order(ByteOrder.LITTLE_ENDIAN);
stepsData.put(0, value[2]);
I feel tempted to extract these to a
toBeUint24
similar toBLETypeConversions
but big-endian, would simplify these.Yes!
@ -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);
Same here, I think we should just have
int calories = toBeUint16(value, 7);
or something.@ -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],
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)
Need to check the output, but if it's the same, it's indeed much more readable.
@ -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>
nitpick, but do we really need the "Smart Ring" suffix?
Probably not...
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.
884a0cf11f
toe301ec0bb2
@ -0,0 +188,4 @@
int levelResponse = value[1];
LOG.info("Received battery level response: {}%", levelResponse);
GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
batteryEvent.state = BatteryState.BATTERY_NORMAL;
value[2]
is 1 when charging@ -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();
When the ring is stationary, Gadgetbridge will sometimes crash:
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)
Okay, I hope this change will resolve this crash:
b5cd97f25f..1b5a45496b
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.
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.
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!
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.
@arjan5 I did some reverse engineering and here are my findings:
List of services:
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.
@ -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;
There are three more HR commands:
@ -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;
Careful, there are two commands for that:
It stops by itself after a few seconds (10 or so), so no harm keeping it this way
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 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});
@ -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});
@ -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});
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.
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.
@ -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});
@ -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});
@ -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});
Note that the second byte of the packet can be set to
0x02
instead of0x01
which means disable the HR measurement:@ -0,0 +589,4 @@
@Override
public void onPowerOff() {
byte[] poweroffPacket = buildPacket(new byte[]{ColmiR02Constants.CMD_POWER_OFF, 0x01});
Does it really power off the ring or does it reboot it?
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.
Good to know thanks!
@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.
@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).
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.
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.
@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.
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.
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.
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
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).
e301ec0bb2
toac01b228fc
ac01b228fc
to3223edceda
3223edceda
tof4a4e25168
Someone published the protocol details here: https://colmi.puxtril.com/
f4a4e25168
to9df13713ba
9df13713ba
toacacfce6be
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.
@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.
How do i download the latest test build?
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.
acacfce6be
toe045000a86
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.
e045000a86
to0b9b6f1baf
0b9b6f1baf
to64e608797f
WIP: Add support for the Colmi R02 smart ringto WIP: Add support for the Colmi R02/R03/R06 smart rings@arjan5 I just compiled 64e608797fbffdf9f454ee861e0838f34dfab08f and I confirm that it also solved the issue on my end. Well done! 🎉
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
64e608797f
tob08070621b
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)
I compiled b08070621b9a120a1bdbea4e6a11a43dbd281026 and tried it.
I guess some database migration didn't apply?
I sent you the Gadgebridge log to your @anymore.nl email address.
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. :)
I confirm that it solved my issue. Sleep data now properly synchronizes and no crashes anymore. 🎉
b08070621b
toea87228a0d
ea87228a0d
to3e3b6ae7db
3e3b6ae7db
tob5cd97f25f
b5cd97f25f
to1b5a45496b
1b5a45496b
tobaa84b10dc
baa84b10dc
to4b388379bc
WIP: Add support for the Colmi R02/R03/R06 smart ringsto Add support for the Colmi R02/R03/R06 smart rings4b388379bc
to8534f8e5a5
8534f8e5a5
to65e8f6b7f0
@ -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])));
Are you sure this is right? Integer.valueOf expects a decimal, but the value is being formatted as hex. Same in the 2 below.
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
@ -0,0 +243,4 @@
}
break;
case ColmiR0xConstants.CMD_SET_HR_INTERVAL:
LOG.info("Received HR interval: {} minutes, enabled={}", value[3], value[2] == 0x01);
Shouldn't we update the device preferences so that the UI matches what's configured on the ring?
See
GBDeviceEventUpdatePreferences
Yes, obviously! I had planned to do that, but somehow forgot it...
Done!
65e8f6b7f0
to6da3c12b80
6da3c12b80
tob8484bf17e
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)) {
If
result
is neither anActivitySample
nor aHeartRateSample
thenheartRate
keeps its initialization value of0
, which is a validheartRate
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):
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.
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?
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.
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?
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.
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.
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:
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.
@arjan5 I just sent the logs to your committer email. I used the
gb_colmi_r02.apk
from this comment for testing.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.
The sleep sync crash is now fixed in 6e6133927d. Still no idea about your weird HR charts.
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 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 to0
@devnoname120 ?@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 to1
fixes the issue.How come there are so many
0
values? And how come that changing the scale of the graph cuts them out?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 thatisValidHeartRateValue
, 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;
I suggest you to rename it to something like
CMD_AUTO_HR
because this setting can both be read (using0x01
as the first arg) and be written (using0x02
as the first arg).Thanks, done!
@ -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;
Likewise, I suggest you to rename it to something like
CMD_AUTO_SPO2
because this setting can both be read (using0x01
as the first arg) and be written (using0x02
as the first arg).Thanks, done!
@ -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;
Likewise, I suggest you to rename it to something like maybe
CMD_AUTO_STRESS
because this setting can both be read (using0x01
as the first arg) and be written (using0x02
as the first arg).Thanks, done!
@ -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;
nit: is the
(byte)
cast needed?Yes, for some reason 0xbc is an int while the rest are bytes...
@ -0,0 +61,4 @@
QueryBuilder<?> qb;
qb = session.getColmiActivitySampleDao().queryBuilder();
qb.where(ColmiActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
nit: for clarity I would suggest to skip a line after each
.executeXYZ()
.Sorry, I think it's clear enough as it is :)
@ -0,0 +102,4 @@
@Override
protected Pattern getSupportedDeviceName() {
return Pattern.compile("R0[2-6]_.*");
nit: so far only the
R02
,R03
, andR06
models exist. If they release aR04
orR05
in the future they won't necessarily use the same hardware and/or commands. I'd suggest to use something likeR0[236]_.*
or maybe(R02|R03|R06)_.*
, whichever is clearer. What do you think?I'm afraid you've reviewed an old version of the PR, this is already solved in my latest update.
Interestingly, the
R04
andR05
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
I currently just want to add devices I know actually exist. Once users report these, it'll be very easy to add them.
I agree — I meant it more in the sense of “wow this is surprising/interesting!” 🙂
@ -0,0 +107,4 @@
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
Just for my own sanity — can you confirm that the ring doesn't support bonding?
Yes, I've tested that extensively when starting on this
@ -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);
There is also:
toUint16(value[11], value[12])
.toUint16(value[13], value[14])
.And it's complete now.
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.
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.
You're right. Added.
@ -0,0 +65,4 @@
}
public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
int hrResponse = value[3] & 0xff;
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 thevalue[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.Thanks, done!
@ -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);
I double-checked and I confirm that there is nothing more than steps, calories, and distance — so it's complete already.
@ -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) {
@ -0,0 +143,4 @@
}
}
public static void historicalStress(GBDevice device, byte[] value) {
@ -0,0 +195,4 @@
}
}
public static void historicalSpo2(GBDevice device, byte[] value) {
@ -0,0 +242,4 @@
}
}
public static void historicalSleep(GBDevice gbDevice, Context context, byte[] value) {
@ -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:
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:
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:
case
later.@ -0,0 +308,4 @@
break;
}
break;
case ColmiR0xConstants.CMD_BIG_DATA_V2:
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,
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).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.
Note that in your case the official app did send the
CMD_AUTO_HRV_PREF = 0x38
command withPREF_READ
action when connecting to the ring. See: #3896 (comment)I'm not sure of the significance of this though. I wonder if sending
CMD_AUTO_HRV_PREF = 0x38
command withPREF_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.
@ -0,0 +442,4 @@
byte[] userPrefsPacket = buildPacket(new byte[]{
ColmiR0xConstants.CMD_PREFERENCES,
0x02,
0x00,
This argument is for the time format:
0
means 24-hour format.1
means 12-hour format.@ -0,0 +444,4 @@
0x02,
0x00,
(byte) ("metric".equals(measurementSystem) ? 0x00 : 0x01),
0x00,
This argument is sex:
0
is male.1
is female.2
is “others”.Thanks, done!
@ -0,0 +448,4 @@
(byte) user.getAge(),
(byte) user.getHeightCm(),
(byte) user.getWeightKg()
});
It's missing those three byte values at the end:
120
).90
).160
).And now the list is exhaustive.
Looks like we don't allow the user to set these, so I've added them as 0x00 with a comment to describe them.
@ -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});
CMD
.@ -0,0 +546,4 @@
private void fetchHistorySpo2() {
getDevice().setBusyTask("spo2HistoryRequest");
byte[] spo2HistoryRequest = new byte[]{
CMD
.@ -0,0 +561,4 @@
private void fetchHistorySleep() {
getDevice().setBusyTask("sleepHistoryRequest");
byte[] sleepHistoryRequest = new byte[]{
CMD
.b8484bf17e
to5213f90006
5213f90006
to264290b7be
264290b7be
toded0e3c820
@ -0,0 +107,4 @@
public static void liveHeartRate(GBDevice device, Context context, byte[] value) {
int errorCode = value[2];
if (errorCode == 1) {
I'd suggest to ignore the HR values whenever
errorCode != 0
, not only whenerrorCode == 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 whenerrorCode == 1
, and anunknown error code: 123
kind of toast for other non-zero error codes. What do you think?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.
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!ded0e3c820
tofcae024c0e
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.
@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.
fcae024c0e
to59b848fcb1
59b848fcb1
to601db54be0
Current testing apk
601db54be0
tof9ce167f01
f9ce167f01
to6e6133927d
@ -0,0 +389,4 @@
BluetoothGattCharacteristic characteristic = getCharacteristic(ColmiR0xConstants.CHARACTERISTIC_WRITE);
if (characteristic != null) {
builder.write(characteristic, contents);
builder.queue(getQueue());
This is crashing Gadgetbridge sometimes:
Logs
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...This is still the same as #3903:
postConnectInitialization
is scheduled 2s in the futurepostConnectInitialization
happenspostConnectInitialization
finally runs, the queue is null by nowSolution should be to unschedule everything from
backgroundTasksHandler
ondispose
.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
6e6133927d
to2ddb03b523
2ddb03b523
to3e1c621cc5
3e1c621cc5
tod7608a0f2b
d7608a0f2b
tod36a78aec4
@ -127,6 +127,12 @@ public class GBDaoGenerator {
addWena3StressSample(schema, user, device);
addFemometerVinca2TemperatureSample(schema, user, device);
addMiScaleWeightSample(schema, user, device);
addColmiActivitySample(schema, user, device);
Reminder to bump the schema version, I think the rebase erased it.
You're right, thanks!
d36a78aec4
to5bd46259cb
@ -0,0 +509,4 @@
byte[] userPrefsPacket = buildPacket(new byte[]{
ColmiR0xConstants.CMD_PREFERENCES,
ColmiR0xConstants.PREF_WRITE,
0x00, // 24h format, 0x01 is 12h format
And add
devicesettings_timeformat
in the coordinator.:) but it's a ring, so this is irrelevant.
It is irrelevant indeed, and may even break the (weird) time setting packet parser :)
@ -0,0 +577,4 @@
}
private void fetchHistoryActivity() {
getDevice().setBusyTask("activityHistoryRequest");
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.
Done, thanks!
5bd46259cb
toe23caa3ee6
@arjan5 Congrats! 🎉
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?
I'm planning on adding it to the website this weekend!
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.
@devnoname120 No problem and thank you for your time! You have definitely provided very useful information!
Done:
37a953d34b
. Will be online tomorrow.The next release is not planned yet I'm afraid.
Hello!
First, thanks for your work, it's impressive!
I just have few questions :
RY03_V3.0 hw and RY03_3.00.18_240930
and oneR02_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!
@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
@arjan5 If it helps I can look into reverse engineering HRV.
@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. :)
Let's move to #4187 for the
RY0*
rings.And for HRV see here: #4222
Ah, sry @devnoname120 it was not my intention to steal your show :> Seems we all had the same idea at the same time :D
@LordOfTheRats No worries at all!
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
@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.
@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.
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.
@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.
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 aminutes
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 @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.
I see, thanks Arjan. By live activity, you're talking about this, right?

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
andstartcommand: 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?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.
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.
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.
Just added feature request 4391. Sorry for the delay, and thanks for adding this device to begin with!
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/
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
@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.
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?
If you tap the ring twice, it will send a bluetooth packet to GB. We're not doing anything with that yet.
Hi, I just started with Gadgedbridge and am quite impressed.
I have the Colmi R09 and playing around with it.
I noticed the following:
Does anyone have experience with this ring?
Issue:
#4491 (comment)
@pronext see #4451, that should be fixed in the nightly.
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.
@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...).
@pronext wrote in #3896 (comment):
Hi,
I have this model too, but temp is ok.
My firmware is rt09 3.10.16
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 :)
@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
@arjan5 Thankyou for the pointers, I will read these and see if they help.
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, 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?
@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.
@arjan5 Thanks, that's what I believe also, wanted another opinion to be sure.