Add support for Qingping Air Monitor model number CGS1

I have made detailed instructions as well for setting up MQTT that might be helpfull for others:

1 Like

In your example, as well as on the Russian site, an error was made.
The tvoc unit is “mg/m³” and not “µg/m³”.
For myself, I did not divide by 218.77 and round, but use the value sent by Qingping Air Monitor. And he indicated that the unit of measurement is ppb. I don’t even understand why, but somehow it’s closer to me.

I followed your instructions, but the airmonitor doesn’t appear in HA.
Here are my Mosquitto broker HA-Addon logs:

[18:33:10] INFO: Successfully send discovery information to Home Assistant.
[18:33:10] INFO: Successfully send service information to the Supervisor.
2023-10-23 18:33:32: New connection from 192.168.0.65:56605 on port 1883.
2023-10-23 18:33:32: New client connected from 192.168.0.65:56605 as qingping-582D340140AD (p2, c1, k30, u'homeassistant').
2023-10-23 18:33:32: Client qingping-582D340140AD disconnected.
2023-10-23 18:33:35: New connection from 172.30.32.1:33043 on port 1883.
2023-10-23 18:33:35: New client connected from 172.30.32.1:33043 as 5B8xCiLMCdE3hNZyUorIe9 (p2, c1, k60, u'homeassistant').
2023-10-23 18:34:02: New connection from 192.168.0.65:56606 on port 1883.
2023-10-23 18:34:02: New client connected from 192.168.0.65:56606 as qingping-582D340140AD (p2, c1, k30, u'homeassistant').
2023-10-23 18:34:21: New connection from 172.30.32.1:46279 on port 1883.
2023-10-23 18:34:21: New client connected from 172.30.32.1:46279 as 1ulw4OlKfryhWGB6963n88 (p2, c1, k60, u'homeassistant').
2023-10-23 18:34:55: New connection from 172.30.32.2:40632 on port 1883.
2023-10-23 18:34:55: Client <unknown> closed its connection.
2023-10-23 18:35:09: Client 2GhIwVS8iKclb6ffsKYVBU has exceeded timeout, disconnecting.
2023-10-23 18:35:51: Client 5B8xCiLMCdE3hNZyUorIe9 has exceeded timeout, disconnecting.
2023-10-23 18:36:46: Client qingping-582D340140AD disconnected.
2023-10-23 18:36:47: New connection from 192.168.0.65:56630 on port 1883.
2023-10-23 18:36:47: New client connected from 192.168.0.65:56630 as qingping-582D340140AD (p2, c1, k30, u'homeassistant').
2023-10-23 18:36:55: New connection from 172.30.32.2:40896 on port 1883.
2023-10-23 18:36:55: Client <unknown> closed its connection.

Sorry, can’t help with this. The instructions are limited to sending data to a MQTT broker, which seems to be happening in your case. Why it is not showing in HA, I don’t know :slight_smile: I’m using HA Core and to have it work there you need to add template sensors. Not sure, if you need that as well. If you do, check some samples in my repository under the issue section. Someone asked recently.

never mind, got it to work, thanks !
I’m just wondering, the CGS1 is not advertised to have PM10 detection, how come we can pull data from it?

Also here is my HA config: (replace MACID with the CGS1’s MAC ID, without dashes)

mqtt:
  sensor:
    - object_id: AIR_co2
      name: "Air CO2"
      state_topic: "qingping/MACID/up"
      device_class: carbon_dioxide
      unique_id: "qingping_dioxide"
      value_template: >-
        {{ value_json.sensorData.0.co2.value 
          if value_json.type=="12" }}
      unit_of_measurement: "ppm"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: AIR_pm25
      name: "Air pm25"
      state_topic: "qingping/MACID/up"
      device_class: pm25
      unique_id: "qingping_pm25"
      value_template: >-
        {{ value_json.sensorData.0.pm25.value 
          if value_json.type=="12" and value_json.sensorData.0.pm25.status==0 }}
      unit_of_measurement: "µg/m³"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: AIR_pm10
      name: "Air pm10"
      state_topic: "qingping/MACID/up"
      device_class: pm10
      unique_id: "qingping_pm10"
      value_template: >-
        {{ value_json.sensorData.0.pm10.value 
          if value_json.type=="12" }}
      unit_of_measurement: "µg/m³"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: AIR_tvoc
      name: "Air tvoc"
      state_topic: "qingping/MACID/up"
      device_class: volatile_organic_compounds
      unique_id: "qingping_tvoc"
      value_template: >-
        {{ (value_json.sensorData.0.tvoc.value/218.77)|round(3) 
          if value_json.type=="12" and value_json.sensorData.0.tvoc.status==0 }}
      unit_of_measurement: "ppb"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: AIR_hum
      name: "Air humidity"
      state_topic: "qingping/MACID/up"
      device_class: humidity
      unique_id: "qingping_hum"
      value_template: >-
        {{ value_json.sensorData.0.humidity.value|round(2)
          if value_json.type=="12" and value_json.sensorData.0.humidity.status==0 }}
      unit_of_measurement: "%"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: AIR_temp
      name: "Air temp"
      state_topic: "qingping/MACID/up"
      device_class: temperature
      unique_id: "qingping_temp"
      value_template: >-
        {{ value_json.sensorData.0.temperature.value|round(2)
            if value_json.type=="12" and value_json.sensorData.0.temperature.status==0 }}
      unit_of_measurement: "°C"
      device:
        identifiers: ["MACID"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"

I also gather that it is limited to pulling data every 15 minutes? no way of making that shorter?

1 Like

Actually, it is. You can use MQTT to send the configuration to your device: https://developer.qingping.co/main/private/public_mqtt#316-modify-data-report-interval
I set 15 seconds for mine, works without problems

You’re correct about the wrong unit, however the dividing by 218.77 is giving me the correct value.

Hello, can you describe how to send configuration to my device?
I’m too noob to understand your comment.

So, you configured yours to work with MQTT right? Take the MQTT client application, for example [https://mqtt-explorer.com/](https://MQTT Explorer). You be able to find your device reporting topic, it depends on how you configured it on the Private Access Config page. In my case, it is: qingping/<device_mac_address>/up.
To send a command to devise use qingping/<device_mac_address>/down topic
Send something like:

{
    "id": 1,
    "need_ack": 1,
    "type": "17",
    "setting": {
        "report_interval": 15,
        "collect_interval": 15,
        "co2_sampling_interval": 15,
        "pm_sampling_interval":15
    }
}

That should do the trick

1 Like

I can now see in MQTT explorer new data every 15 sec, but in HA MQTT it still only refreshes once every 15 minutes… :thinking:

Just unpacked my air sensor, found this thread, and got everything up and running. Same problem here that HA MQTT still refreshes once every 15 minutes. This is the configuration message what I sent via HA:

service: mqtt.publish
data:
  topic: qingping/<device_mac_address>/down
  payload: >-
    {"id":1,"need_ack":1,"type":"17","setting":{"report_interval":15,"collect_interval":15,"co2_sampling_interval":15,"pm_sampling_interval":15}}
1 Like

I worked it out. Problem is that type=="12" messages are only sent every 15 minutes, and the configuration message above only configures the interval for type=="17 messages. These contain a history of values but also the latest value. So this YAML works pretty well for me:

mqtt:
  sensor:
    - object_id: air_monitor_co2
      name: "CO2"
      unique_id: "qingping_air_monitor_<device_mac_address>_co2"
      device_class: carbon_dioxide
      unit_of_measurement: "ppm"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ value_json.sensorData.0.co2.value
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_co2_status
      name: "CO2 Status"
      unique_id: "qingping_air_monitor_<device_mac_address>_co2_status"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ {0: "normal", 1: "erhöht", 2: "hoch", 3: "sehr hoch"}[[1000, 2000, 3000] | select("<=", value_json.sensorData.0.co2.status | float(0)) | list | length]
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_pm25
      name: "PM2.5"
      unique_id: "qingping_air_monitor_<device_mac_address>_pm25"
      device_class: pm25
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ value_json.sensorData.0.pm25.value 
          if value_json.type=="17" else this.state }}
      unit_of_measurement: "µg/m³"
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_pm25_status
      name: "PM2.5 Status"
      unique_id: "qingping_air_monitor_<device_mac_address>_pm25_status"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ {0: "gut", 1: "moderat", 2: "leicht ungesund", 3: "ungesund", 4: "sehr ungesund", 5: "gefährlich"}[value_json.sensorData.0.pm25.status]
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_pm10
      name: "PM1.0"
      unique_id: "qingping_air_monitor_<device_mac_address>_pm10"
      device_class: pm10
      unit_of_measurement: "µg/m³"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ value_json.sensorData.0.pm10.value 
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_pm10_status
      name: "PM1.0 Status"
      unique_id: "qingping_air_monitor_<device_mac_address>_pm10_status"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ {0: "gut", 1: "moderat", 2: "leicht ungesund", 3: "ungesund", 4: "sehr ungesund", 5: "gefährlich"}[value_json.sensorData.0.pm10.status]
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_tvoc
      name: "TVOC"
      unique_id: "qingping_air_monitor_<device_mac_address>_tvoc"
      device_class: volatile_organic_compounds
      unit_of_measurement: "ppb"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ (value_json.sensorData.0.tvoc.value / 218.77) | round(3) 
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_tvoc_status
      name: "TVOC Status"
      unique_id: "qingping_air_monitor_<device_mac_address>_tvoc_status"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ {0: "ausgezeichnet", 1: "gut", 2: "erhöht", 3: "hoch", 4: "sehr hoch"}[[0.3, 1, 3, 9] | select("<=", (value_json.sensorData.0.tvoc.value / 218.77)| round(3)) | list | length]
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_humidity
      name: "Luftfeuchtigkeit"
      unique_id: "qingping_air_monitor_<device_mac_address>_humidity"
      device_class: humidity
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ value_json.sensorData.0.humidity.value|round(2)
          if value_json.type=="17" else this.state }}
      unit_of_measurement: "%"
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
    - object_id: air_monitor_temperature
      name: "Temperatur"
      unique_id: "qingping_air_monitor_<device_mac_address>_temperature"
      device_class: temperature
      unit_of_measurement: "°C"
      state_topic: "qingping/<device_mac_address>/up"
      value_template: >-
        {{ value_json.sensorData.0.temperature.value|round(2)
            if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/<device_mac_address>/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["<device_mac_address>"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"

I also worked out availability, graceful behavior against messages of type!="17 (the else this.state thing), and sensors for the different regimes of the sensor values (except the area in the T/rH diagram). Since the status can actually (and probably should, DRY principle!) be derived from the sensor value it can be implemented as plain template sensor (not platform: mqtt).

1 Like

This is the prefinal version with derived template sensors for the status values:

mqtt:
  sensor:
    - name: "CO2"
      object_id: air_monitor_co2
      device_class: carbon_dioxide
      unit_of_measurement: "ppm"
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ value_json.sensorData.0.co2.value
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: f0864ae1d3e66cdb5a29c894dd6a192f8ba528ec
    - name: "PM2.5"
      object_id: air_monitor_pm25
      device_class: pm25
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ value_json.sensorData.0.pm25.value 
          if value_json.type=="17" else this.state }}
      unit_of_measurement: "µg/m³"
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: 0c4f328dd042cdfc17f01ec2f880e7dd5317750b
    - name: "PM10"
      object_id: air_monitor_pm10
      device_class: pm10
      unit_of_measurement: "µg/m³"
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ value_json.sensorData.0.pm10.value 
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: 0e330464965310305e8df5187ba00e5a6b515471
    - name: "TVOC"
      object_id: air_monitor_tvoc
      device_class: volatile_organic_compounds
      unit_of_measurement: "mg/m³"
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ (value_json.sensorData.0.tvoc.value / 218.77) | round(3) 
          if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: 145ba4f594273ed8cfcf44f8df1983e3a229b0d1
    - name: "Luftfeuchtigkeit"
      object_id: air_monitor_humidity
      device_class: humidity
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ value_json.sensorData.0.humidity.value|round(2)
          if value_json.type=="17" else this.state }}
      unit_of_measurement: "%"
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: 5648fe503a9c4c7563643425de97f184a3282a66
    - object_id: air_monitor_temperature
      name: "Temperatur"
      device_class: temperature
      unit_of_measurement: "°C"
      state_topic: "qingping/582D34700B3E/up"
      value_template: >-
        {{ value_json.sensorData.0.temperature.value|round(2)
            if value_json.type=="17" else this.state }}
      availability:
        - topic: "qingping/582D34700B3E/up"
          payload_available: "true"
          payload_not_available: "false"
          value_template: >-
            {{ "true" if value_json else "false" }}
      device:
        identifiers: ["582D34700B3E"]
        name: "Air monitor"
        model: "CGS1"
        manufacturer: "qingping"
      unique_id: eeac725e7c2bc220e8ef3602b7a506a9855a36c6

template:
  - sensor:
      - name: "Air Monitor CO2 Status"
        unique_id: air_monitor_co2_status
        state: >
          {{ {0: "normal", 1: "erhöht", 2: "hoch", 3: "sehr hoch"}[
            [1000, 2000, 3000] | select("<=", states('sensor.air_monitor_co2') | float(0)) | list | length
          ] }}
        availability: >
          {{ not is_state('sensor.air_monitor_tvoc', 'unavailable') }}
        icon: mdi:molecule
      - name: "Air Monitor PM2.5 Status"
        unique_id: air_monitor_pm25_status
        state: >
          {{ {0: "gut", 1: "moderat", 2: "etwas ungesund", 3: "ungesund", 4: "sehr ungesund", 5: "gefährlich"}[
            [12, 35, 55, 150, 250] | select("<=", states('sensor.air_monitor_pm25') | float(0) ) | list | length
          ] }}
        availability: >
          {{ not is_state('sensor.air_monitor_pm25', 'unavailable') }}
        icon: mdi:molecule
      - name: "Air Monitor TVOC Status"
        unique_id: air_monitor_tvoc_status
        state: >
          {{ {0: "ausgezeichnet", 1: "gut", 2: "erhöht", 3: "hoch", 4: "sehr hoch"}[
            [0.3, 1, 3, 9] | select("<=", states('sensor.air_monitor_tvoc') | float(0) ) | list | length
          ] }}
        availability: >
          {{ not is_state('sensor.air_monitor_tvoc', 'unavailable') }}
        icon: mdi:molecule

I generated the unique IDs as follows (pbcopy is a Mac command):

$ date +%s | shasum | pbcopy

If anyone knows what the US thresholds and descriptions for PM10 are I’m happy to learn.

And this is a Lovelace card with colored status:

type: entities
entities:
  - entity: sensor.air_monitor_co2_status
    type: custom:multiple-entity-row
    name: CO₂
    secondary_info: false
    show_state: false
    show_header: false
    entities:
      - entity: sensor.air_monitor_co2
        name: false
      - icon: mdi:circle
    card_mod:
      style:
        hui-generic-entity-row $: ''
        .: |
          div.entity:nth-child(2) state-badge {
            color: {{ {"normal": "green", "erhöht": "yellow", "hoch": "orange", "sehr hoch": "red"}[states('sensor.air_monitor_co2_status')] }}
          }
  - entity: sensor.air_monitor_pm25_status
    type: custom:multiple-entity-row
    name: Feinstaub (PM2.5)
    secondary_info: false
    show_state: false
    show_header: false
    entities:
      - entity: sensor.air_monitor_pm25
        name: false
      - icon: mdi:circle
    card_mod:
      style:
        hui-generic-entity-row $: ''
        .: |
          div.entity:nth-child(2) state-badge {
            color: {{ {"gut": "green", "moderat": "yellow", "etwas ungesund": "orange", "ungesund": "red", "sehr ungesund": "purple", "gefährlich": "brown"}[states('sensor.air_monitor_pm25_status')] }}
          }
  - entity: sensor.air_monitor_tvoc_status
    type: custom:multiple-entity-row
    name: flüchtige organische Verbindungen
    secondary_info: false
    show_state: false
    show_header: false
    entities:
      - entity: sensor.air_monitor_tvoc
        name: false
      - icon: mdi:circle
    card_mod:
      style:
        hui-generic-entity-row $: ''
        .: |
          div.entity:nth-child(2) state-badge {
            color: {{ {"ausgezeichnet": "green", "gut": "green", "erhöht": "yellow", "hoch": "red", "sehr hoch": "brown"}[states('sensor.air_monitor_tvoc_status')] }}
          }

It makes use of the HACS frontend addons card-mod and Multiple Entity Row.
Bildschirmfoto 2023-11-05 um 13.42.47

Another possible approach that doesn’t require accounting for type 17 (history) vs type 12 (regular) messages within the sensor data is you can send:

MQTT Payload

{
    "type" : "12",
    "up_itvl" : "15",
    "duration": "86400"
}

Destination

qingping/replace_with_your_id/down

From the Qingping docs (https://developer.qingping.co/main/private/public_mqtt#312-server-send-setting-for-temporary-report-and-duration-time)

The original duration value was 60 seconds, so far the 86400 seconds (24 hours) value appears to be working, which I’ll send once a day with an HA automation. up_itvl was set at 15 seconds, which worked but I later changed to 60 to avoid logging too much data.

1 Like

jadz, Thanks. This is a great solution for me.

Remove the content type in the request. Not sure why but qingping will return an error when it’s set.

Thank you so much, i can add it
but i got problem, it refresh every 14-15minute, and when refresh the number is abnormal, when see at graph will show like this, how can i fix it

like this pic, it up down up down every 14-15 minute

Does anyone use the Qingping Air Monitor Gen 2 (CGS2) with HA? I’m new to HA and I’m wondering if it would be safer to buy CGS1 or officially supported Lite CGDN1. Thanks