I wrote the following for a similar application.
It's part of an add-on for an IKEA PM2.5 air quality monitor
static constexpr byte PM25_MSB {5};
static constexpr byte PM25_LSB {6};
enum RX_STATE {WAIT_PREAMBLE,
HANDLE_PAYLOAD,
CHECK_MESSAGE} rxState;
template <class Serial_T>
void IKEA_PM25<Serial_T>::resetRx ()
{
pm25 = invalidReading;
rxState = WAIT_PREAMBLE;
checksum = 0;
messageIndex = 0;
}
/****************************************************
*
* Protocol details
* Controller: 11 02 0B 01 E1
* Sensor: 16 11 0B DF1-DF4 DF5-DF8 DF9-DF12 DF13 DF14 DF15 DF16 [CS]
* PM2.5 (ug/m^3)= DF3*256 + DF4
*
*/
template <class Serial_T>
void IKEA_PM25<Serial_T>::handleSensor()
{
uint32_t now = millis ();
// check here for sensor timeout? ToDo
while (serial.available()) {
uint8_t rxChar = (uint8_t) serial.read ();
checksum += rxChar;
switch (rxState) {
case WAIT_PREAMBLE:
if (rxChar == preamble [messageIndex++]) {
if (messageIndex == preambleLength)
rxState = HANDLE_PAYLOAD;
}
else
resetRx ();
break;
case HANDLE_PAYLOAD: // we've had a valid preamble, all we can do is grab / ignore the rest
if (messageIndex == PM25_MSB)
pm25 = rxChar << 8;
if (messageIndex == PM25_LSB)
pm25 |= rxChar;
messageIndex++;
if (messageIndex == messageSize)
rxState = CHECK_MESSAGE;
break;
case CHECK_MESSAGE:
if (checksum == 0) {
sensorOnline = true;
rollingSum -= samples [sampleIndex];
rollingSum += samples [sampleIndex++] = pm25;
if (sampleIndex >= nSamples) {
wrapped = true;
sampleIndex = 0;
}
int divisor = wrapped ? nSamples : sampleIndex;
avgPM25 = rollingSum / divisor;
lastReading = now;
}
resetRx ();
break;
default:
break;
}
}
}
Ignore the template stuff, it's just a fudge to allow soft or hard UARTs.
Here, the checksum is simply that, not an XOR, and it's over all of the message.