New Wemos board

This looks like an interesting board. May be a good upgrade for the 8266


Yes I saw that too. Apparently, pin compatible. I will try and get a board and see how it works.

I have gotten the sketch working on ESP32. So it should be pretty simple to get working.

I have a new board on the way that uses a smaller form factor MCU. It will support either Seeed XIAO RP2040 or Adafruit QT Py ESP32-S2.

I haven’t yet worked on running the sketch on the Seeed XIAO, but as it’s pin compatible with the QT Py and runs Ardiuno code, it should be trivial to get working. I like these boards as they are the size of a postage stamp and as powerful, maybe more so than the Wemos boards.

1 Like

Apparently there’s a hardware issue with V1.0.0

Currently testing V2.1.0 (received it yesterday) and it’s great so far.
Any news about compatibility with airgradient board? I’m waiting mine to arrive from JLCPCB

We are working on making the AirGradient Arduino library compatible with ESP32 boards.
The current library does not work with these chips.

I’m really interested in this. Using esphome, it should be possible to use it for not just airgradient, but adding bluetooth_proxy which could be handy given airgradients are likely scattered around. 2 tasks with one device.

I received by Pro (non soldered) kit very recently and have been having trouble uploading firmware with ESPHome as it times out very often. I have absolutely no time out issues with any of my other ~30 ESP boards. The board that came with the kit is a Lolin D1 ‘Mimi’ which is based on the 8266 so I figured I’d try to replace it with a pin compatible ESP32 S2 Mini or an ESP32-WROOM-32 ESP Mini 32 (no idea on brand or proper name…).

ESP32 S2:
The OLED display, PMS5003 and SHT3X all worked fine but I just could not get UART1 for the CO2 sensor to work.

ESP32 WROOM 32 (Mini)
Everything works perfectly and it is pin compatible so no mods necessary.

Note: Sorry for terrible pic… camera on my S20Ultra is awful.


Nevermind… it works but now I cannot close the enclosure without modifying it as the corner where the LEDs are interferes with the recessed part of the enclosure or removing the PCB corner where the power led is (likely not an issue to do so but I am not sure I want to). The ESP32 S2 fits perfectly but I can’t get the UART1 to work… :frowning:

Edit2: Creative and careful use of a Dremel on both the ESP32 board and the enclosure solved the problem.

This is very interesting as we want to make the board ESP32 compatible.

There are D1 ESP32 based modules that are smaller and should not have the problem that we need to adjust the enclosure, e.g.

S2 Mini


C3 Mini

Would you like to share the code adjustments you made?

@AirGradient - I have an ESP32 S2 Mini but I could not get UART1 to work as stated above. I did not see any errors so I did not know what else to try.

I do not have a C3 Mini and I did not find any on amazon so I can’t test that one.

My ESPHome code is for the ESP32-WROOM-32 that is a bit too large to fit (I modified both the board and the case and it fits now):

Note: Code is still work in progress…

  devicename: "air-quality-sensor"
  devicename_no_dashes: "air_quality_sensor"
  friendly_devicename: "Air Quality Sensor"
  device_description: "Air Gradient Air Quality Sensor"
  update_interval_s: "2s"
  update_interval_wifi: "120s"

  name: "${devicename}"
  comment: "${device_description}"
  # Automatically add the mac address to the name
  # so you can use a single firmware for all devices
  # name_add_mac_suffix: true

  board: nodemcu-32s
    type: arduino

# Enable logging

# Enable Home Assistant API
  password: !secret api_pwd

  password: !secret ota_pwd

  - ssid: !secret iot_wifi_ssid
    password: !secret iot_wifi_password
  reboot_timeout: 15min

#Faster than DHCP. Also use if can't reach because of name change

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
#  use_address:

  # Enable fallback hotspot (captive portal) in case wifi connection fails
    ssid: "${devicename}"
    password: !secret iot_wifi_password

  port: 80
  include_internal: false

  - platform: restart
    name: "${friendly_devicename}: Restart"

  - platform: template
    name: "Calibrate CO2 Sensor"
    id : "calibrate_co2_sensor"
    disabled_by_default: true
        - senseair.background_calibration: co2_sensor
        - logger.log: "CO2 Sensor Calibration Triggered! Must be done OUTDOORS!"
  - platform: template
    name: "CO2 Sensor Calibration Result"
    id : co2_sensor_calibration_result
    disabled_by_default: true
        - senseair.background_calibration_result: co2_sensor

  - platform: safe_mode
    name: "${friendly_devicename}: Restart (Safe Mode)"


# Sync time with Home Assistant
  - platform: homeassistant
    id: ha_time

  - platform: wifi_info
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
      update_interval: ${update_interval_wifi}
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

globals: ##to set default reboot behavior
  # Wifi variables
  - id: wifi_connection
    type: bool
    restore_value: no
    initial_value: "false"

  sda: 21 #D2
  scl: 22 #D1

  - rx_pin: 18 #D5
    tx_pin: 19 #D6
    baud_rate: 9600
    id: uart1

  - rx_pin: 16 #D4
    tx_pin: 17 #D3
    baud_rate: 9600
    id: uart2

  # gfonts://family[@weight]
  - file: "gfonts://Roboto"
    id: roboto
    size: 12

  - file: "gfonts://Roboto"
    id: roboto_symbols
    size: 12
    glyphs: [
      "\U000000B5", #µ
      "\U00000067"  #g

  - file: "gfonts://Roboto"
    id: roboto_medium
    size: 16
  - file: "gfonts://Roboto"
    id: roboto_large
    size: 32
  - file: "fonts/materialdesignicons-webfont.ttf"
    id: wifi_icon_font
    size: 12
    glyphs: [
      "\U000F05A9", #wifi
      "\U000F05AA"  #no wifi

  - file: "fonts/materialdesignicons-webfont.ttf"
    id: face_icon_font
    size: 48
    glyphs: [
      "\U000F01F5", #mdi-emoticon-happy-outline
      "\U000F01F6", #mdi-emoticon-neutral-outline
      "\U000F01F8"  #mdi-emoticon-sad-outline

# ~400 ppm 	background (normal) outdoor air levels
# 400- 1,000 ppm 	typical levels found in occupied spaces with good air exchange
# 1,000 – 2,000 ppm	levels associated with complaints of drowsiness and poor air
# 2,000 – 5,000 ppm	levels associated with headaches, sleepiness, and stagnant, stale, stuffy air,
# poor concentration, loss of attention, increased heart rate and slight nausea may also be present
# >5,000 ppm	Exposure may lead to serious oxygen deprivation symptoms

  - platform: ssd1306_i2c
    id: device_display
    model: "SH1106 128x64"
    address: 0x3C
    rotation: 180
    flip_x: false
    flip_y: false
    offset_y: 0
    offset_x: 0
    external_vcc: true
    update_interval: 1s
      - id: page1
        lambda: |-
          it.rectangle(0, 0, 128, 64);
          it.printf(  6,  8, id(roboto_medium), "CO2 ");
          it.printf( 92,  8, id(roboto_medium), TextAlign::TOP_RIGHT, "%5.0f", id(co2).state);
          it.printf(120, 11, id(roboto), TextAlign::TOP_RIGHT, "ppm");
          it.line( 0, 32, 128,  32);
          it.line(64, 32,  64, 128);
          it.printf(  4, 34, id(roboto), "C");
          it.printf( 68, 34, id(roboto), "RH");
          it.printf( 54, 40, id(roboto_medium), TextAlign::TOP_RIGHT, "%3.1f°", id(temp).state);
          it.printf(120, 40, id(roboto_medium), TextAlign::TOP_RIGHT, "%2.0f%%", id(humidity).state);
      - id: page2
        lambda: |-
          it.rectangle(0, 0, 128, 64);
          it.printf(4, 4, id(roboto_medium), "PM 1: ");
          it.printf(105, 4, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm1_0).state);
          it.printf(124, 7, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
          it.printf(4, 23, id(roboto_medium), "PM 2.5: ");
          it.printf(105, 23, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm2_5).state);
          it.printf(124, 26, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
          it.printf(4, 42, id(roboto_medium), "PM 10: ");
          it.printf(105, 42, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm10_0).state);
          it.printf(124, 45, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
      - id: page3
        lambda: |-
          it.rectangle(0, 0, 128, 64);
          if (id(co2).state <= 1000.0 && id(pm2_5).state < 35 ) {
            it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F5");   //mdi-emoticon-happy-outline
            it.printf(76, 14, id(roboto_medium), "ALL");
            it.printf(68, 34, id(roboto_medium), "GOOD");
          } else if (id(co2).state > 1000.0 && id(co2).state < 2000.0 || id(pm2_5).state >= 35 && id(pm2_5).state <= 50) {
            it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F6");   //mdi-emoticon-neutral-outline
            it.printf(74, 14, id(roboto_medium), "NOT");
            it.printf(68, 34, id(roboto_medium), "GOOD");
          } else {
            it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F8");   //mdi-emoticon-sad-outline
            it.printf(72, 14, id(roboto_medium), "NOT");
            it.printf(68, 34, id(roboto_medium), "SAFE");

  - interval: 10s
      - device_display
      - component.update: device_display

  - interval: 20s
          - globals.set:
              id: wifi_connection
              value: 'true'
          - globals.set:
              id: wifi_connection
              value: 'false'

  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: ${update_interval_wifi}
    device_class: signal_strength

  - platform: sht3xd
      id: temp
      name: ${friendly_devicename} Temperature
      id: humidity
      name: ${friendly_devicename} Humidity
    address: 0x44
    update_interval: 10s
  - platform: pmsx003
    type: PMSX003
    uart_id: uart1
      id: pm1_0
      name: "${friendly_devicename}: Particulate <1.0µm"
      id: pm2_5
      name: "${friendly_devicename}: Particulate <2.5µm"
      id: pm10_0
      name: "${friendly_devicename}: Particulate <10.0µm"

  - platform: senseair
    id: co2_sensor
    uart_id: uart2
      id: co2
      name: "${friendly_devicename} CO2"
    update_interval: 60s

1 Like

I certainly want to come back and try out your esphome code!