Skip to content

Commit 0cbe560

Browse files
committed
v.1.2.8
### Major Features: - ✅ Added support for TLV binary protocol devices - ✅ Automatic report mode switching based on USB power state - ✅ Device-specific configuration entities (offsets, intervals, modes) - ✅ Dynamic offline timeouts (5min real-time, 15min historic) - ✅ Improved device discovery for both JSON and TLV devices - ✅ Temperature unit switching via MQTT - ✅ CO₂ ASC and calibration controls ### Supported Device Expansion: - Added CGP22W, CGP23W, CGP22C, CGR1AD ### Bug Fixes: - [#46](#46) Type 13 reports to be ignored on JSON devices. Changed DEFAULT_DURATION to 6 hours from 24. - [#45](#45) Devices classes will now be set dynamically based unit measurement. - Fixed battery sensor for TLV devices - Fixed pressure unit conversion (displays in kPa) - Improved historical data handling (always uses most recent reading)
1 parent c8af392 commit 0cbe560

File tree

12 files changed

+1868
-312
lines changed

12 files changed

+1868
-312
lines changed
Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
"""Support for Qingping CGSx button entities."""
22
from __future__ import annotations
33

4-
import json
5-
import logging
6-
7-
from homeassistant.components import mqtt
84
from homeassistant.components.button import ButtonEntity
5+
from homeassistant.components import mqtt
96
from homeassistant.config_entries import ConfigEntry
107
from homeassistant.const import CONF_NAME, CONF_MAC, CONF_MODEL
118
from homeassistant.core import HomeAssistant
129
from homeassistant.helpers.entity_platform import AddEntitiesCallback
10+
from homeassistant.helpers.update_coordinator import CoordinatorEntity
1311
from homeassistant.helpers.entity import EntityCategory
14-
from homeassistant.exceptions import HomeAssistantError
1512

16-
from .const import DOMAIN, MQTT_TOPIC_PREFIX
17-
18-
_LOGGER = logging.getLogger(__name__)
13+
from .const import DOMAIN, TLV_MODELS
14+
from .tlv_encoder import tlv_encode
1915

2016
async def async_setup_entry(
2117
hass: HomeAssistant,
@@ -26,6 +22,7 @@ async def async_setup_entry(
2622
mac = config_entry.data[CONF_MAC]
2723
name = config_entry.data[CONF_NAME]
2824
model = config_entry.data[CONF_MODEL]
25+
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
2926

3027
device_info = {
3128
"identifiers": {(DOMAIN, mac)},
@@ -34,40 +31,40 @@ async def async_setup_entry(
3431
"model": model,
3532
}
3633

37-
buttons = []
34+
entities = []
3835

39-
# CGDN1-specific buttons
40-
if model == "CGDN1":
41-
buttons.append(
42-
QingpingCGSxManualCalibrationButton(config_entry, mac, name, device_info)
36+
# Add CO2 calibration button for TLV devices with CO2 sensor
37+
if model in TLV_MODELS and model in ["CGP22C", "CGR1AD"]:
38+
entities.append(
39+
QingpingTLVCO2CalibrationButton(coordinator, config_entry, mac, name, device_info)
4340
)
4441

45-
if buttons:
46-
async_add_entities(buttons)
42+
if entities:
43+
async_add_entities(entities)
4744

4845

49-
class QingpingCGSxManualCalibrationButton(ButtonEntity):
50-
"""Button to trigger manual CO2 calibration."""
46+
class QingpingTLVCO2CalibrationButton(CoordinatorEntity, ButtonEntity):
47+
"""Representation of a Qingping TLV device CO2 calibration button."""
5148

52-
def __init__(self, config_entry, mac, name, device_info):
49+
def __init__(self, coordinator, config_entry, mac, name, device_info):
5350
"""Initialize the button."""
51+
super().__init__(coordinator)
5452
self._config_entry = config_entry
5553
self._mac = mac
56-
self._attr_name = f"{name} Manual Calibration"
57-
self._attr_unique_id = f"{mac}_manual_calibration"
54+
self._attr_name = f"{name} CO2 Calibration"
55+
self._attr_unique_id = f"{mac}_co2_calibration"
5856
self._attr_device_info = device_info
5957
self._attr_entity_category = EntityCategory.CONFIG
60-
self._attr_icon = "mdi:tune-vertical"
58+
self._attr_icon = "mdi:tune-vertical-variant"
6159

6260
async def async_press(self) -> None:
6361
"""Handle the button press."""
64-
try:
65-
payload = {"type": "29"}
66-
topic = f"{MQTT_TOPIC_PREFIX}/{self._mac}/down"
67-
68-
_LOGGER.info("Triggering manual calibration for %s", self._mac)
69-
await mqtt.async_publish(self.hass, topic, json.dumps(payload))
70-
71-
except Exception as err:
72-
_LOGGER.error("Failed to trigger manual calibration: %s", err)
73-
raise HomeAssistantError(f"Failed to trigger calibration: {err}")
62+
# Send TLV command to device (KEY 0x41)
63+
# 1 = Perform calibration
64+
packets = {
65+
0x41: bytes([1])
66+
}
67+
payload = tlv_encode(0x32, packets)
68+
69+
topic = f"qingping/{self._mac}/down"
70+
await mqtt.async_publish(self.hass, topic, payload)

custom_components/qingping_cgs1/config_flow.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Config flow for Qingping CGSx integration."""
1+
"""Config flow for Qingping CGxx integration."""
22
from __future__ import annotations
33

44
import voluptuous as vol
@@ -22,7 +22,7 @@ def clean_mac_address(mac: str) -> str:
2222
return mac.replace(":", "")
2323

2424
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
25-
"""Handle a config flow for Qingping CGSx."""
25+
"""Handle a config flow for Qingping CGxx."""
2626

2727
VERSION = 1
2828

@@ -77,7 +77,7 @@ async def async_step_user(
7777
return self.async_create_entry(title=validated_data[CONF_NAME], data=validated_data)
7878

7979
except Exception as ex:
80-
_LOGGER.error("Unexpected exception in Qingping CGSx config flow: %s", ex)
80+
_LOGGER.error("Unexpected exception in Qingping CGxx config flow: %s", ex)
8181
errors["base"] = "unknown"
8282
return self.async_show_form(
8383
step_id="user",
@@ -145,7 +145,7 @@ async def async_step_manual(
145145
)
146146

147147
async def _async_discover_devices(self):
148-
"""Discover available Qingping CGSx devices via MQTT."""
148+
"""Discover available Qingping CGxx devices via MQTT."""
149149
try:
150150
# Get list of already configured devices
151151
configured_devices = {
@@ -155,25 +155,36 @@ async def _async_discover_devices(self):
155155
def _handle_message(msg):
156156
"""Handle received MQTT messages."""
157157
try:
158-
# Extract MAC address from the topic
159-
mac = clean_mac_address(msg.topic.split('/')[-2])
160-
if mac and mac not in configured_devices and mac not in self._discovered_devices:
161-
self._discovered_devices[mac] = f"Qingping CGSx ({mac})"
158+
# Extract MAC address from the topic (works for both JSON and TLV)
159+
# Topic format: qingping/{MAC}/up
160+
topic_parts = msg.topic.split('/')
161+
if len(topic_parts) >= 2:
162+
mac = clean_mac_address(topic_parts[-2])
163+
if mac and mac not in configured_devices and mac not in self._discovered_devices:
164+
# Check if it's TLV format (binary starting with 'CG')
165+
if msg.payload[:2] == b'CG':
166+
device_name = f"Qingping TLV Device ({mac})"
167+
else:
168+
# JSON format
169+
device_name = f"Qingping JSON ({mac})"
170+
171+
self._discovered_devices[mac] = device_name
172+
_LOGGER.info(f"Discovered device: {device_name}")
162173
except Exception as ex:
163174
_LOGGER.error("Error handling MQTT message: %s", ex)
164175

165176
# Subscribe to the MQTT topic
166177
await mqtt.async_subscribe(
167-
self.hass, f"{MQTT_TOPIC_PREFIX}/#", _handle_message
178+
self.hass, f"{MQTT_TOPIC_PREFIX}/#", _handle_message, 1, encoding=None
168179
)
169180

170181
# Wait for a short time to collect messages
171-
await asyncio.sleep(10) # Increased to 10 seconds for better discovery
182+
await asyncio.sleep(10) # Wait 10 seconds to collect messages
172183

173-
_LOGGER.info(f"Discovered {len(self._discovered_devices)} new Qingping CGSx devices")
184+
_LOGGER.info(f"Discovered {len(self._discovered_devices)} new Qingping devices (JSON + TLV)")
174185

175186
except HomeAssistantError as ex:
176-
_LOGGER.error("Error discovering Qingping CGSx devices: %s", ex)
187+
_LOGGER.error("Error discovering Qingping devices: %s", ex)
177188
except Exception as ex:
178189
_LOGGER.error("Unexpected error in device discovery: %s", ex)
179190

custom_components/qingping_cgs1/const.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Constants for the Qingping CGSx integration."""
1+
"""Constants for the Qingping integration."""
22

33
DOMAIN = "qingping_cgs1"
44
CONF_MAC = "mac"
@@ -14,6 +14,7 @@
1414
SENSOR_TEMPERATURE = "temperature"
1515
SENSOR_TVOC = "tvoc"
1616
SENSOR_ETVOC = "tvoc_index"
17+
SENSOR_TLV_ETVOC = "tvoc"
1718
SENSOR_NOISE = "noise"
1819

1920
# Unit of measurement
@@ -23,12 +24,22 @@
2324
PPB = "ppb"
2425
DB = "dB"
2526
CONF_TVOC_UNIT = "tvoc_unit"
27+
CONF_TEMPERATURE_UNIT = "temperature_unit"
2628
CONF_ETVOC_UNIT = "etvoc_unit"
29+
CONF_PRESSURE_OFFSET = "pressure_offset"
30+
CONF_LED_INDICATOR = "led_indicator"
2731

2832
# Offsets
2933
CONF_TEMPERATURE_OFFSET = "temperature_offset"
3034
CONF_HUMIDITY_OFFSET = "humidity_offset"
3135
CONF_UPDATE_INTERVAL = "update_interval"
36+
# TLV device intervals
37+
CONF_REPORT_INTERVAL = "report_interval" # Minutes (KEY 0x04)
38+
CONF_SAMPLE_INTERVAL = "sample_interval" # Seconds (KEY 0x05)
39+
# TLV device report modes
40+
CONF_REPORT_MODE = "report_mode"
41+
REPORT_MODE_HISTORIC = "historic"
42+
REPORT_MODE_REALTIME = "realtime"
3243

3344
# Default values for offsets and update interval
3445
DEFAULT_OFFSET = 0
@@ -43,13 +54,31 @@
4354
ATTR_DURATION = "duration"
4455

4556
DEFAULT_TYPE = "12"
46-
DEFAULT_DURATION = "86400"
57+
DEFAULT_DURATION = "21600"
4758

48-
QP_MODELS = ["CGS1", "CGS2", "CGDN1"]
59+
SENSOR_PRESSURE = "pressure"
60+
SENSOR_LIGHT = "light"
61+
SENSOR_SIGNAL_STRENGTH = "signal_strength"
62+
# Device models
63+
# JSON format devices (old protocol)
64+
JSON_MODELS = ["CGS1", "CGS2", "CGDN1"]
65+
# TLV binary format devices (new protocol)
66+
TLV_MODELS = ["CGP22C", "CGP23W", "CGP22W", "CGR1AD"]
67+
# All supported models
68+
QP_MODELS = JSON_MODELS + TLV_MODELS
4969
DEFAULT_MODEL = "CGS1"
5070

71+
# Model to Product ID mapping (for TLV devices)
72+
MODEL_PRODUCT_IDS = {
73+
"CGP22C": 93, # CO₂ & Temp & RH Monitor
74+
"CGP23W": 38, # Temp & RH Barometer Pro S
75+
"CGP22W": 92, # Temp & RH Monitor Pro S
76+
"CGR1AD": 96, # Indoor Environment Monitor
77+
}
78+
5179
# Device-specific settings
5280
CONF_CO2_ASC = "co2_asc"
81+
CONF_CO2_CALIBRATION = "co2_calibration"
5382
CONF_CO2_OFFSET = "co2_offset"
5483
CONF_PM25_OFFSET = "pm25_offset"
5584
CONF_PM10_OFFSET = "pm10_offset"
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"domain": "qingping_cgs1",
3-
"name": "Qingping Pro AQM",
3+
"name": "Qingping",
44
"codeowners": ["@mash2k3"],
55
"config_flow": true,
66
"dependencies": ["mqtt"],
77
"documentation": "https://github.com/mash2k3/qingping_cgs1",
88
"iot_class": "local_push",
99
"issue_tracker": "https://github.com/mash2k3/qingping_cgs1/issues",
1010
"requirements": [],
11-
"version": "1.2.7"
11+
"version": "1.2.8"
1212
}

0 commit comments

Comments
 (0)