I hate reverse engineering.
You cannot underestimate how little I care that bit 3 of byte 2 of an 11 byte, binary message indicates whether the left flange of the Acme, Incorporated Doohickee 3500-XL is up or down. It just does not matter.
I don’t care to know the details of how someone implemented a device or protocol or whatever. Those details don’t matter.
But, this CMS50E is out of the Far East, so talking to it through the serial/USB port requires reverse engineering.
And a strong stomach.
Now, the CMS50E has a 1-button user interface: Beautifully done. A work of art in design and implementation.
The protocol? … … … Otherwise.
So, here goes:
The device talks to a PC through a serial-to-USB conversion cable. The PC program sets 19.2 8O1. Yes. Odd parity. And the PC program actually does a 4800/19200 dance at the top. Is this bug-clearing logic for the cable or device? Who knows.
The device sends a data stream to the PC when the “USB” menu item is “on”.
Let’s cover this first goof in the PC interface:
If the device is USB powered, then USB streaming should start and stop when the USB power is on or off. Duh. And, in any case and if the device does not use USB power, then a particular command message from the PC should turn streaming on – for a limited time so the battery isn’t drained from the streaming.
Poof. The menu item goes away.
USB powered devices should send identifying heartbeat messages in any case. This would allow a PC program to find the device by opening and only listening on the serial port. The heartbeat should include device identity information.
A menu choice tells the device to dump its recorded data.
Let’s cover this second goof:
Poof. The menu item goes away.
Turns out, the PC program sends two 0xf5 bytes when it begins the data dump. And it sends three 0xf6 bytes when it has received the dump. I cannot find any reason the PC program does this. The only effect they seem to have is to turn on data streaming. Note: the displayed state of the “USB” option does not visually change until the menu choice is actively scrolled to. No big deal, but this is the reason I’ve not tested the effects of all 256 byte values sent to the device.
Streaming data format:
The streaming data is composed of 5-byte messages sent 60 times a second:
- Byte 1:
128 / 0x80 means the “finger” is not in the device. Ignore the other 4 bytes. If the high bit is not set then this is not the first byte of a message. Wait for a byte with the high bit set.
0xf0 bits: Outside documentation indicates this is a “signal strength for pulsate” value. I have recorded only values from 0 through 9. No recorded values from 10 through 15. In all cases of a zero value but two, the heart rate and SpO2 values have been zero, but the waveform value has been valid, though also often zero. The two anamalous cases had a spurious heart rate of 132.
0x10 bit: Outside documentation indicates this bit means “searching too long” when set.
0x20 bit: Outside documentation indicates this bit means “dropping of SpO2” when set.
0x40 bit: is set when the device senses a heart “beat” – a peak in the waveform. This “beat” marker comes a few samples after the actual peak and seems to coincide with the beep sound the device makes. There are often two samples together with the beat markers.
- Byte 2:
The waveform value. 0..127. The high bit is not set. If it is set (and the high bit is set on byte 1, of course), then this is not a streaming data message, but rather a recorded data dump message. - Byte 3:
High bit of heart rate and certain status bits. The 0x40 bit is the heart rate high bit – allowing heart rates of up to 255 BPM.
0x0f bits: Apparent duplications of the top 4 bits of the waveform value. I tried to make sense of these bits. Were they a way to get at the instantaneous oxygen saturation? No luck so far. Outside documentation indicates that they are to be used for a bar-graph on a display. In any case the 0x08 bit is always zero as the 0x80 bit of the waveform data in byte 2 is always zero, too.
0x10 bit: Outside documentation indicates it may be “probe error” if set.
0x20 bit: Outside documentation indicates it may be “searching” if set.
I have no instances of the 0x30 bits being set.
0x80 bit: Must always be zero. Otherwise, this is not a regular sample.
- Byte 4:
Heart rate: 0..127. The low 7 bits of the heart rate, that is. If the third byte is 0xf2 and the fourth byte has its high bit set, then they are the first two bytes of a recorded data dump.
The heart rate appears to be a calculation on the time difference between the oldest and most recent “beat” in the last 30 seconds plus a few samples.
- Byte 5:
Oxygen saturation percentage. This value seems to be a 30 second average of some sort. Anyway, it lags by 30 seconds.
Data dump format:
A recorded data dump is composed of 3-byte messages telling the heart rate and oxygen saturation level once a second.
The first two messages sent contain the HH:MM time value set by the user when the recording was started.
The third message sent tells how many bytes are in the full data dump.
Subsequent messages are the dump, itself.
Once started, the dump continues until finished. I have not tested the effect of pulling the USB connection during a dump.
The three message types:
- Time value (from the menu HH:MM time, set by the user when recording was started).
Two of these messages are sent to start the data dump.
They can be recognized by:
(first_byte == 0xf2) and (second_byte & 0x80)
- Byte 1:
0xf2 - Byte 2:
High bit is set. The 0x1f bits are the hours: 0..23.
- Byte 3:
Minutes 0..59.
- Byte 1:
- The single message not starting with an 0xf2 value and following an 0xf2 message tells how many bytes of recorded data will be sent in the subsequent messages.
The calculation is:
((first_byte & 0x3f) < < 14) | ((second_byte & 0x7f) << 7) | third_byte
Note: There appear to be bugs in the device which makes this byte count subject to adjustments along the way. See the code for my current best guesses. Too. WordPress seems to render the shift-left 14 with an extra space.
- Recorded data.
- Byte 1:
0xf0 or 0xf1 (possibly 0xf2 and 0xf3, but I doubt it) The low bit (or two bits) are the high bit(s) of the heartrate.
If this byte masked with 0xf0 is not 0xf0, then see the code. It gets knarly.
The device appears to be directly dumping its flash memory and the data seems to be organized on 256 byte page boundaries. 256 / 3 (3 being the message length) is not an even number. So strange things happen 3 times every 256 data messages. It’s baffling why the engineer did things this way. But there it is. Perhaps extra information is encoded by special messages at these page boundaries, but it sure doesn’t look like it. The whole thing just looks incredibly sloppy. This feel of sloppiness is enhanced because there can be obvious glitches in the data and/or dumping during particular recording dumps. The glitches appear to be in memory rather than communications problems.
- Byte 2:
Low 7 bits of the heart rate. The 0x80 or 0x180 bits – the high bit(s) – of the heart rate are in the first byte’s low bit(s).
If byte 2 and byte 3 are both zero, then presumably the finger was out.
- Byte 3:
The oxygen saturation percentage: ?..99. I have never seen 100. At the first two 256-byte boundaries in the data dump for each 256 samples, this value is 255. The third 256-byte boundary seems to yield a regular streaming data sample message with bongoed heartrate – or something.
- Byte 1:
There you have it. Gosh, I hope the engineer responsible for this can say, “Hey, whadya want? I had an hour to do it in!”