Skip to content

Conversation

@ahrmn
Copy link

@ahrmn ahrmn commented Dec 23, 2025

I reverse-engineered the settings of the Meter Pro (CO2) and implemented them here.
I have no means of testing if this work for the other Meter models.
An example script to fully configure a device using this would be for example

import asyncio

from switchbot.devices.meter import SwitchbotMeter
from switchbot.discovery import GetSwitchbotDevices
from switchbot.const import SwitchbotModel

BLE_MAC='XX:XX:XX:XX:XX:XX'
METER_MODEL=SwitchbotModel.METER_PRO_C

async def main():
    switchbotDevices = await GetSwitchbotDevices().discover()
    meter_advertisement = switchbotDevices[BLE_MAC]
    print(meter_advertisement.data)
    meter = SwitchbotMeter(device=meter_advertisement.device, model=METER_MODEL)
    await meter.show_battery_level(False)
    await meter.set_co2_thresholds(900, 1200)
    await meter.set_comfortlevel(19, 22, 40, 70)
    await meter.set_temperature_update_interval(10)
    await meter.set_co2_update_interval(10)
    await meter.set_button_function(False, False)
    await meter.set_alert_sound(False, 2)
    await meter.set_alert_temperature_humidity(True, 17, 28, False,
                                               True, 40, 70, False)
    await meter.set_alert_co2(True, 400, 1200, False)
    
asyncio.run(main())

Additionally one can trigger a new measurement regardless of the frequency using
await meter.force_new_CO2_measurement() and calibrate the sensor (at fresh air!) using
await meter.calibrate_co2_sensor().

@ahrmn
Copy link
Author

ahrmn commented Dec 23, 2025

This may solve #416.
@jacekowski Can you test if this synchronises time for the Meter Pro (without CO2 sensor)?

@zerzhang
Copy link
Collaborator

zerzhang commented Jan 7, 2026

Additional unit tests are needed. It is recommended to create a base class MeterPro and move the CO2 settings to a separate class MeterProCO2.

@ahrmn ahrmn reopened this Jan 25, 2026
@ahrmn
Copy link
Author

ahrmn commented Jan 25, 2026

@elgris @zerzhang I integrated my code into the new structure, removed my own time-implementation and started implementing unit-tests (more are coming when i have more time).

It is recommended to create a base class MeterPro and move the CO2 settings to a separate class MeterProCO2.

I have no way to verify which methods would actually work on a MeterPro, so i am not sure abbout that.


COMMAND_TEMPERATURE_UPDATE_INTERVAL = f"{SETTINGS_HEADER}070105"
COMMAND_CO2_UPDATE_INTERVAL = f"{SETTINGS_HEADER}0b06"
COMMAND_FORCE_NEW_CO2_Measurement = f"{SETTINGS_HEADER}0b04"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(style) it should be all uppercase COMMAND_FORCE_NEW_CO2_MEASUREMENT.

+ absolute_humidity_low_bytes
)

async def set_alert_co2(self, on: bool, co2_low: int, co2_high: max, reverse: bool):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: co2_high: int,

"""
Sets the CO2-Alert.
on: Turn CO2-Alert on or off
lower and upper: The provided range (between 400ppm and 2000ppm in 100ppm steps)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

between 400ppm and 2000ppm in 100ppm steps: worth validating this values in the code?


mode = 0x00 if not on else (0x04 if reverse else 0x03)
await self._send_command(
COMMAND_ALERT_CO2 + f"{mode:02x}" + f"{co2_high:04x}" + f"{co2_low:04x}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to send these limits if on is False? I haven't tried this with the device, but I'd assume that it expects all zeroes. If so, then we can probably make set default values for co2_high and co2_low args to 0 and drop their validation when on is False.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this would be a reasonable design, but is not what the original app does.
The test I pushed for this method contains only payloads from the original App.
When a mode gets turned off, all values remain set as they were before. And all of them get transmitted.
I tried to stick as close to this as possible. I did not test how the device would react to your proposal.

Sets the interval in which temperature and humidity are measured in battery powered mode.
Original App assumes minutes in {5, 10, 30}
"""
seconds = minutes * 60
Copy link
Contributor

@elgris elgris Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to @zerzhang and @bdraco, but I'd just pass seconds as an arg and let the caller do the minutes->seconds conversion. The same for other _update_interval methods.


async def set_button_function(self, change_unit: bool, change_data_source: bool):
"""
Sets the function of te top button:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: te -> the

@elgris
Copy link
Contributor

elgris commented Jan 26, 2026

I'd also suggest checking ruff output, it also found some problems. You can also run it locally to verify that everything looks good.

@ahrmn
Copy link
Author

ahrmn commented Jan 27, 2026

I added more tests and fixed the typos you found.
After the Auto-Fixes I find some of the test parameters rather hard to read....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants