-
• #2
Nice to get a summary! May I suggest you edit in links to the sources with the
[info](url)
format? -
• #3
Sources added.
-
• #4
Gordon: you can get all the absolute heart rate values for each heartbeat
Yes - just
Bangle.on("HRM-raw", ...)
And yes, I think now we've gone down the route of using a binary blob we could potentially pull in the other binary blobs for different data, however the sensor needs setting up in a different way (faster HRM measurement and interleaving env measurements) so it's not as easy as just dumping it in.
And looking again at the HRM binary blobs I see Sp02 but I have I don't see any Blood pressure algorithm, and even though the original watch firmware has an option for Blood pressure I can't seem to get it working.
-
• #5
Yes - just Bangle.on("HRM-raw", ...)
It's hard though because the data can be a bit all over the place, even when filtered using our algorithms (see attachments - green: raw, magenta: adjusted for bias, red: filtered). While the new algorithm for measuring BPM is very accurate, it's going to be hard to compete with its ability to count beats.
Also after I spent a bit looking through the Espruino code trying to understand some stuff, I have some questions: Where does raw come from with the vc31? The only place I see raw getting set is in heartrate.c, and I thought heartrate_vc31_binary.c is used instead with the vc31, so raw shouldn't be set. And in theory could you modify the manufacturer code to expose their filtered version?
4 Attachments
-
• #6
Where does raw come from with the vc31?
Interesting - do you still see useful values in
raw
? As you say it looks almost like it's not set anywhere. It should probably be set withhrmInfo.raw = hrmValue;
inheartrate_vc31_binary
too.And in theory could you modify the manufacturer code to expose their filtered version?
Not really - it's a binary blob so can't be modified or examined (apart from with a decompiler). I think it works in a different way anyway - by taking blocks of HRM data and examining each block, rather than by 'streaming' it in as our algorithm did.
-
• #7
Interesting - do you still see useful values in raw? As you say it looks almost like it's not set anywhere. It should probably be set with hrmInfo.raw = hrmValue; in heartrate_vc31_binary too.
You're right.
.raw
is always 0 (as such the magenta line is just displaying the inverse of the average). Should I make a PR or do you want to add that line?I think it works in a different way anyway - by taking blocks of HRM data and examining each block, rather than by 'streaming' it in as our algorithm did.
Decompiled it and yeah I have no idea what it does ¯\_(ツ)_/¯
Also here's the decompiled version for anyone who wants it https://ktibow.github.io/blog/vcareexplanation/ -
• #8
Thanks - but no problem, I just pushed the change
.. and thanks for the info about it. Did that explanation all come from GPT-4? It's quite impressive
-
• #9
Yeah it was all from GPT-4. Note that it did hallucinate a bit (the arguments are hallucinated given that the decompiler doesn't have the type defs and I didn't provide the type defs, and it got the hex decoding wrong once), but when I tried providing more info I found the other explanations lower quality than the first one.
-
• #10
Hi I originally made a HRV app for the Bangle 1, which I can probably check and change if it's busted since I last updated it. I can definitely make it more efficient. I've been looking at making a similar one for the Bangle 2, which I recently got; it's a great watch but the PPG signal is definitely harder to work with either raw or filtered, signal moves around a lot probably because of the auto-adjustments it seems to make. Will try get something working for it though, I'm trying different options around polling rates and locking the LED auto-adjustment to see if I can get it more stable.
For Sp02, I doubt this would give anything useful - SP02 is usually red + IR light you need to measure not green because your blood absorbs green. I've seen notes on research using different shades of green but not green + IR. You basically do comparisons of the 2 signals to see light absorption at different wavelengths in the same kind of range but because green and IR are so far apart I'm not sure it would work. Datasheet doesn't suggest it has a red LED. It's possible they have a different similar model that uses a red LED instead of the green tailored more for Sp02.
For blood pressure, it looks like you need quite a clean, stationary signal for this. I actually think it's possible to get blood pressure from recorded data out of Bangle 1 easily. The caveat is the algorithm would probably have to be done outside of the watch so would need to use a phone or something that can handle more power and transfer the data by bluetooth or do it retrospectively. The easiest methods I've seen take PPG signals directly and run them through a neural net. The Bangle can run tensorflow so theoretically, if you get a small enough tflite model that can take a low 25Hz signal it could work but I think the pre-filtering you need to do beforehand as well with it might all be too much.
-
• #11
While the signal does move around, I think one of the reported values (
.raw
?) does have some filtering to smooth out the big jerks caused by the LED power changing over time. If that's run through a filter to remove the low frequency parts it should be reasonably good. -
• #12
There are the .filt values that is bandpass filtered but it has issues with clipping out I guess because they are all 16 bit. It also bandpasses the wrong frequencies at 25Hz I think? I might have a better approach for filtering from the raw signal that I've put a write up on in more detail here:
https://www.majorinput.co.uk/post/bangle-js-2-dealing-with-noisy-ppg-signals
Basically you take a sample of .raw data then:
- apply differencing to remove low frequency noise (subract last value by current in each array element)
- apply differencing again (I didn't mention it in the blog post but it works better doing it twice)
- convolve with a gaussian filter.
With the above, you won't get clipping issues because the values reduce as it's filtered down and you get a much better fourier transform. You could derive the HR by FFT from this even with shorter windows or calculate by peak detection and then based on the average interval/gap in the window. I've got a javascript app that runs on the bangle alligned to this but step 3 in the above is running a bit slow, I'll try getting it to run faster or even try your test harness thing and see how it works at firmware level. To be honest I think the proprietary HR algorythm seems fine so wouldn't change it for HR but this could be useful for HRV if it works ok and replace the filter algorythm.
- apply differencing to remove low frequency noise (subract last value by current in each array element)
Hope this doesn't give off the wrong vibe or anything, just making this post to see what others think and possibly help people looking for a summary.