Open z3ugma opened 1 year ago
Continued sleuthing - in the ViHealth APK (Android app), this LepuBle
Kotlin library is called.
In this file - https://github.com/viatomEcho/LepuBle/blob/master/BLE_SDK_API.md
We can see references to oxyGetPpgRt
and oxyGetWave
that specify it can be done on O2Ring.
------------------------------------------------------------------------O2Ring,BabyO2-------------------------------------------------------------------
oxyGetPpgRt(model: Int) :获取实时原始数据,支持设备O2Ring,BabyO2
oxyGetWave(model: Int) :获取实时波形数据,支持设备O2Ring,BabyO2
oxyGetRtParam(model: Int) :获取实时参数值,支持设备O2Ring,BabyO2
updateSetting(model: Int, type: String, value: Any) :更新设备设置,支持设备O2Ring,BabyO2
and this commit: https://github.com/viatomEcho/LepuBle/commit/2db64621969b436fcef5da992bd8728d72513b10 looks like it modifies some ability to fetch PpgRT (ppg realtime?)
Here we see the list of commands, like your above public static int OXY_CMD_RT_PARAM_DATA = 0x17;
-> 0x17
being the command to CMD_READ_SENSORS
https://github.com/viatomEcho/LepuBle/commit/2db64621969b436fcef5da992bd8728d72513b10#diff-a48a1d358b8776c9470e3c4f7381072461b2a1eb28659c1352428b0f836d6210R22
public static int OXY_CMD_INFO = 0x14; //20
public static int OXY_CMD_PARA_SYNC = 0x16; //22
public static int OXY_CMD_RT_DATA = 0x1B; //27
public static int OXY_CMD_RT_PARAM_DATA = 0x17; //21
public static int OXY_CMD_RESET = 0x18; //24
//File read
public static int OXY_CMD_READ_START = 0x03; //3
public static int OXY_CMD_READ_CONTENT = 0x04; //4
public static int OXY_CMD_READ_END = 0x05; //5
If so it appears that command 27
is the realtime data: public static int OXY_CMD_RT_DATA = 0x1B; //27
This function describes the command string
public static byte[] getRtWave() {
int len = 1;
byte[] buf = new byte[8 + len];
buf[0] = (byte) 0xAA; //aa
buf[1] = (byte) OXY_CMD_RT_DATA; //1b
buf[2] = (byte) ~OXY_CMD_RT_DATA; // ff-1b = e4
buf[5] = (byte) len; //01
buf[6] = (byte) (len >> 8);
buf[7] = (byte) 0; // 0 -> 125hz; 1-> 62.5hz
buf[8] = BleCRC.calCRC8(buf);
return buf;
}
b'\xaa\x1b\xe4\x00\x00\x01\x00\x00\\<CRC>
^^ I'll try to implement the command for 1B
Additional findings: public static int OXY_CMD_PPG_RT_DATA = 0x1C;
public static byte[] getPpgRt() {
int len = 1;
byte[] buf = new byte[8 + len];
buf[0] = (byte) 0xAA;
buf[1] = (byte) OXY_CMD_PPG_RT_DATA;
buf[2] = (byte) ~OXY_CMD_PPG_RT_DATA;
buf[5] = (byte) len;
buf[6] = (byte) (len >> 8);
buf[7] = (byte) 0X00;
buf[8] = BleCRC.calCRC8(buf);
return buf;
}
This does correctly get a response back from the ring.
@ExperimentalUnsignedTypes
@Parcelize
class RtWave(var byteArray: ByteArray) : Parcelable {
val waveData: ByteArray
val waveIntData: IntArray
init {
waveData = byteArray.copyOfRange(0, 5).toList().asSequence().map { (it.toInt() and 0x7f).toByte() }.toList().toByteArray()
waveIntData = byteArray.copyOfRange(0, 5).toList().asSequence().map { (it.toInt() and 0x7f)}.toList().toIntArray()
}
}
Defined:
CMD_RT_DATA = 27 # 0x1B rx len = <specified>, tx no data
CMD_PPG_RT_DATA = 28 # 0x1C
self.send_func(o2pkt(CMD_PPG_RT_DATA, data="\x00\x01"))
...
elif pkt.cmd == CMD_RT_DATA:
print("Realtime | ", pkt.recv_data, "\n")
elif pkt.cmd == CMD_PPG_RT_DATA:
print("PPG | ", pkt.recv_data.hex(), "\n")
print("PPG | ", pkt.recv_data, "\n")
Success!
I'll make a pull request based on CMD_RT_DATA
, which pulls in the 125Hz PPG samples.
Thank you very much for this, @z3ugma ! Excellent work!
I got the original list of commands from capturing Bluetooth packets and giving them names which sounded descriptive. That LepuBle source is an awesome find.
Hi there! I'd like to help implement realtime read of the PPG/Pleth signal (as per https://github.com/MackeyStingray/o2r/blob/master/README.md?plain=1#L57 ), and I'm wondering what experiments you've done toward this end.
My ultimate goal would be to be able to gather the PPG waveform in realtime:
I can put a few things I know!
The PO2 ring aka Wellue O2Ring from Viatom is used by SleepImage (https://www.virtuox.net/DynDocs/Documents/SleepImagePatientInstructions.pdf) and SleepImage does record the Pleth waveform. Maybe there's custom firmware?
It appears that the PO2 ring can send pleth waveform with the Remote Linker
The ring advertises these BLE GATT services, characteristics, and descriptors
You've implemented Commands 3,4,5,20,21,22,23,24 - are there other commands? How did you originally discover this list of commands?
Maybe some other command reads the realtime pleth waveform?
Let me know if you'd be interested in working on this together, or have any hints!