Trouble reading out Senseair S8 UART with BZ25

I’m going to dive into debugging and compare it to the source code. I can’t find much info on the mitm suggestion, so I’m letting that rest for now. Breadboards and cheap duponts are indeed not ideal, but I wanted to test the whole setup before soldering it all together. :slight_smile:

1 Like

I agree perfectly, but at least make direct dupont connections without breadboard and try to “feel” that connections are tight.

2 Likes

Connected them directly to the D1’s ports before, but had the same results. In the meantime I’m going to try to make sense out of the debug logs.

2 Likes

If you can figure out how to just recieve the raw uart messages as presumably requested by the other device you may even find you are better off just trying to selectively read just the co2 measurements without having a sensair component.

The source code and uart messages will tell you thier structure. Then you could parse them into a sensor like this.

Just an idea.

1 Like

Using the debugger with the Trotec attached I get the following logs (see quotes):

Every reading starts with xFE x04 and looking to the source code, this shouldn’t trigger the ‘invalid preamble’ message…Right? Without the Trotec attached, the readings start with xFE x04 as well. :person_shrugging: I do see some kind of ‘stacking’ of readings after every ‘invalid preamble’ error.

INFO ESPHome 2024.10.2
INFO Reading configuration /config/esphome/test.yaml...
INFO Starting log output from ---------- using esphome API
INFO Successfully connected to test @ -------- in 0.009s
INFO Successful handshake with test @ -------- in 2.433s
[22:47:39][I][app:100]: ESPHome version 2024.10.2 compiled on Nov  8 2024, 22:43:04
[22:47:39][C][wifi:600]: WiFi:
[22:47:39][C][wifi:428]:   Local MAC: -------
[22:47:39][C][wifi:433]:   SSID: '---------'[redacted]
[22:47:39][C][wifi:436]:   IP Address: --------
[22:47:39][C][wifi:439]:   BSSID: --------[redacted]
[22:47:39][C][wifi:441]:   Hostname: 'test'
[22:47:39][C][wifi:443]:   Signal strength: -70 dB ▂▄▆█
[22:47:39][C][wifi:447]:   Channel: 11
[22:47:39][C][wifi:448]:   Subnet: 255.255.255.0
[22:47:39][C][wifi:449]:   Gateway: --------
[22:47:39][C][wifi:450]:   DNS1: ---------
[22:47:39][C][wifi:451]:   DNS2: ---------
[22:47:39][C][logger:185]: Logger:
[22:47:39][C][logger:186]:   Level: DEBUG
[22:47:39][C][logger:188]:   Log Baud Rate: 0
[22:47:39][C][logger:189]:   Hardware UART: UART0
[22:47:39][C][uart.arduino_esp8266:118]: UART Bus:
[22:47:39][C][uart.arduino_esp8266:119]:   TX Pin: GPIO15
[22:47:39][C][uart.arduino_esp8266:120]:   RX Pin: GPIO13
[22:47:39][C][uart.arduino_esp8266:122]:   RX Buffer Size: 256
[22:47:39][C][uart.arduino_esp8266:124]:   Baud Rate: 9600 baud
[22:47:39][C][uart.arduino_esp8266:125]:   Data Bits: 8
[22:47:39][C][uart.arduino_esp8266:126]:   Parity: NONE
[22:47:39][C][uart.arduino_esp8266:127]:   Stop bits: 1
[22:47:39][C][uart.arduino_esp8266:129]:   Using hardware serial interface.
[22:47:39][C][senseair:144]: SenseAir:
[22:47:39][C][senseair:145]:   CO2 'SenseAir CO2 Value'
[22:47:39][C][senseair:145]:     Device Class: 'carbon_dioxide'
[22:47:39][C][senseair:145]:     State Class: 'measurement'
[22:47:39][C][senseair:145]:     Unit of Measurement: 'ppm'
[22:47:39][C][senseair:145]:     Accuracy Decimals: 0
[22:47:39][C][senseair:145]:     Icon: 'mdi:molecule-co2'
[22:47:39][C][captive_portal:089]: Captive Portal:
[22:47:39][C][mdns:116]: mDNS:
[22:47:39][C][mdns:117]:   Hostname: test
[22:47:39][C][esphome.ota:073]: Over-The-Air updates:
[22:47:39][C][esphome.ota:074]:   Address: test.local:8266
[22:47:39][C][esphome.ota:075]:   Version: 2
[22:47:39][C][esphome.ota:078]:   Password configured
[22:47:39][C][safe_mode:018]: Safe Mode:
[22:47:39][C][safe_mode:019]:   Boot considered successful after 60 seconds
[22:47:39][C][safe_mode:021]:   Invoke after 10 boot attempts
[22:47:39][C][safe_mode:022]:   Remain in safe mode for 300 seconds
[22:47:39][C][api:140]: API Server:
[22:47:39][C][api:141]:   Address: test.local:6053
[22:47:39][C][api:143]:   Using noise encryption: YES
[22:47:41][W][senseair:049]: SenseAir checksum doesn't match: 0x2F66!=0x8A70
[22:47:41][D][uart_debug:158]: <<< "\xFE\x04\x02\x04f/\xCE\xFE\x04\x02\x04f/"
[22:47:46][W][senseair:030]: Invalid preamble from SenseAir! cefe0402 04662fce fe040204 66
[22:47:46][D][uart_debug:158]: <<< "\xCE\xFE\x04\x02\x04f/\xCE\xFE\x04\x02\x04f/\xCE\xFE\x04\x02\x04W\xEE\x1A\xFE\x04\x02\x04W\xEE\x1A\xFE\x04\x02\x04W\xEE\x1A\xFE\x04\x02\x04W\xEE\x1A\xFE\x04\x02\x04J.\x13\xFE\x04\x02\x04J.\x13"
[22:47:51][W][senseair:049]: SenseAir checksum doesn't match: 0x2E4A!=0x8A70
[22:47:51][D][uart_debug:158]: <<< "\xFE\x04\x02\x04J.\x13\xFE\x04\x02\x04J."
[22:47:56][W][senseair:030]: Invalid preamble from SenseAir! 13fe0402 0444afd7 fe040204 44
[22:47:56][D][uart_debug:158]: <<< "\x13\xFE\x04\x02\x04D\xAF\xD7\xFE\x04\x02\x04D\xAF\xD7\xFE\x04\x02\x04D\xAF\xD7\xFE\x04\x02\x04D\xAF\xD7\xFE\x04\x02\x048\xAE6\xFE\x04\x02\x048\xAE6\xFE\x04\x02\x048\xAE6\xFE\x04\x02\x048\xAE6"

Screenshot of logs without Trotec attached:
Schermafbeelding 2024-11-08 220305

@Mahko_Mahko That makes sense, but my programming skills are not quite where they need to be for this I guess. But every man can learn of course… :sweat_smile:

1 Like

Try totally removing the sensor and showing what you get in the debug logs.

What you need would probably be similar to this example, which if you spend a little time figuring out what it does I’m sure you could read and maybe adjust.

Per the source code, you would probably check the length of the message:
static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13;
then check a few bytes
(response[0] != 0xFE || response[1] != 0x04

You’d probably skip the checksum check and then figure out where the C02 data is:
const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]);

And publish that to a sensor per the example. Might sound hard but it’s not too bad.

This is assuming my approach is a decent one - maybe there is a better way to get both devices reading the sensor ok using the native component. I don’t know.

1 Like

The opposite. it will be triggered when those two bytes aren’t present.

1 Like

Whoopsy, typo. I meant that it’s strange that I only get the error with the Trotec attached and not without, whilst in both situations the message starts with the same values.

Your approach indeed sounds good, thank you for explaining the logic. I was hoping for a ‘standard’ solution, but it’s not the end of the world if it takes some programming to get it working. I can also ‘just’ use the PWM output of the S8 sensor, but I had trouble reading it out too. But that’s probably a hardware issue, since my multimeter read out nice readings between 0-3.3V. Perhaps leaving the UART for what it is and reading the PWM might be the easy but less rewarding option. :joy:

In the meanwhile I tried debugging/logging without a sensor component, but there is no data showing up in the logs then. Searched around to get that to work, but it’s close to bed time, so my brain is not functioning too fast.

1 Like

I’ve also had noise / corruption clean up in the past when I’ve soldered things and even moved from cheap boads to a better ESP32, being particularly dilegent with GNDs. There are some crappy D1 mini’s floating around.

Worth trying if it’s not a hassle.

1 Like

I’ve got S2 mini’s somewhere… Is that considered better?

Still no luck with debugging without a sensor component though, am I forgetting something?

I don’t know.
My assumption was that the other device would be requesting readings and so your debug would just need to passively listen for messages.
But my understanding of operations may be incorrect.

Did you notice how often the device seemed to update when used without esphome? If you breath on it does it change quickly or take a while?

1 Like

Oh yeah, the sensor is being read out every 3 seconds I think. The display constantly shows new values, so there should be data coming out. Nothing else changed in the code, only removed the sensor. I’m making a start with new code based on your example and info right now.

1 Like

Hmm yeah but if you can’t see debug messages coming in then that is weird and you will have nothing to parse.

Do you have dummy_receiver: true ?

I would personally give that a go too.

I think I’m the dummy. Forgot the dummy. :smiley:

Some of the data:

[00:10:35][D][uart_debug:114]: <<< FE:04:02:05:04:AF:B7
[00:10:37][D][uart_debug:114]: <<< FE:04:02:04:E5:6E:6F
[00:10:54][D][uart_debug:114]: <<< FE:04:02:04:94:AE:4B
[00:11:20][D][uart_debug:114]: <<< FE:04:02:06:E2:2E:CD

etc...

1 Like

Hooray!

With your help and the reference to the previous topic with a similar question, I managed to get it working to your approach! I also used ChatGPT to explain some of the operators and figuring out which bytes did represent the PPM value (I did not see the pattern in the sensor’s datasheet before lol). I still need to check if I need the code to be more solid, but with the bare minimum it works already. E.g. logging the size of the message did not seem to work properly, but I guess it’s not reallly helpful either. I’m super thankful to you!

uart:
  tx_pin: GPIO15
  rx_pin: GPIO13
  baud_rate: 9600
  debug:
    direction: RX
    dummy_receiver: true
    after:
      delimiter: "\r\n"
    sequence:
      - lambda: |-
          UARTDebug::log_hex(direction, bytes, ',');
          if (direction == UART_DIRECTION_RX)
            {
              if (bytes[0] == 0xFE && bytes[1] == 0x04)
              {
                int ppm = (bytes[3] << 8) | bytes[4];
                id(PPM_sensor).publish_state(ppm);
              }
            }

sensor:
  - platform: template
    name: "PPM"
    id: PPM_sensor
    device_class: carbon_dioxide
    state_class: measurement
    unit_of_measurement: "ppm"

Screenshot 2024-11-09 205223

1 Like

Nice one.

I think adding a check for the message length may be a good idea.

It’s possible that other messages might come through (perhaps relating to the automatic calibration etc), and not processing those might be a good idea.

You’d just want to parse ones of length= 7.

So adding in this check:

                if (bytes.size() == 7)                               // Check number of bytes

1 Like

Fair point, I’m going to try and add this check.

The line for checking the size in your example did not seem to work though. It only returned %d in the logs. I was expecting it to return the actual size.

ESP_LOGD("custom", "Bytes size: %d", bytes.size());

Hmm right. Not sure what’s going on there from top of head and no longer easy for me to check.

I think the size check probably works ok even if there’s something wrong with the logging statement.

1 Like

Right now I’m not using the ESP_LOGD line, only checking if (bytes.size() == 7), and that works! When I make it another number, data comes in, but doesn’t get published to the sensor. Again, thank you so much for your help!

Relevant code for reference:

logger:
  baud_rate: 0

uart:
  tx_pin: GPIO15
  rx_pin: GPIO13
  baud_rate: 9600
  debug:
    direction: RX
    dummy_receiver: true
    after:
      delimiter: "\r\n"
    sequence:
      - lambda: |-
          UARTDebug::log_hex(direction, bytes, ',');
          if (direction == UART_DIRECTION_RX)
            {
              if (bytes.size() == 7) 
              {
                if (bytes[0] == 0xFE && bytes[1] == 0x04)
                {
                int ppm = (bytes[3] << 8) | bytes[4];
                id(PPM_sensor).publish_state(ppm);
                }
              }
            }

sensor:
  - platform: template
    name: "PPM"
    id: PPM_sensor
    device_class: carbon_dioxide
    state_class: measurement
    accuracy_decimals: 0
    unit_of_measurement: "ppm"
    update_interval: 3s
1 Like

As a small clean-up you shouldn’t need the tx pin there now…

1 Like