Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Set NotImplemented from device schema. #31

Merged
merged 30 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a9eb92c
Draft test example.
rainwoodman Dec 6, 2024
51629ee
comment about plans on senml parsing.
rainwoodman Dec 6, 2024
561a965
linter fixes.
rainwoodman Dec 6, 2024
71d77b5
Add tests for H35i and T10i.
rainwoodman Dec 11, 2024
c7122e1
lint fixes.
rainwoodman Dec 11, 2024
ca495e8
Update golden for H35i
rainwoodman Dec 11, 2024
063b361
Add a pytest workflow.
rainwoodman Dec 11, 2024
2c98f01
rename the workflow.
rainwoodman Dec 11, 2024
a79a985
add installation step.
rainwoodman Dec 11, 2024
994620b
Add a readme for the golden data generation.
rainwoodman Dec 11, 2024
94fa652
add Intermediate representation of AWS devices.
rainwoodman Dec 11, 2024
e5fa7d6
add tests.
rainwoodman Dec 11, 2024
88e82a6
Use SensorPack in device_aws.
rainwoodman Dec 11, 2024
4fdc899
Add a query_json helper to the ir.
rainwoodman Dec 11, 2024
dfaaf7a
Parse the device IR in refresh.
rainwoodman Dec 11, 2024
088a29b
ruff.
rainwoodman Dec 11, 2024
b2c83f5
Add NotImplemented detection
rainwoodman Dec 11, 2024
6041f93
use the new type statement.
rainwoodman Dec 11, 2024
ee126ed
Update to py3.12's type parameter syntax.
rainwoodman Dec 11, 2024
9490926
Add a few comments.
rainwoodman Dec 11, 2024
6ab880b
Ensure all fields are checked.
rainwoodman Dec 11, 2024
5f36adb
fix typing of running.
rainwoodman Dec 11, 2024
9a07c99
Add comment about specialness of AttributeType.
rainwoodman Dec 11, 2024
e802286
Rename ir_aws to intermediate_representation_aws.
rainwoodman Dec 13, 2024
c91b61b
rename usage to usage_percentage.
rainwoodman Dec 13, 2024
2c8f922
Redefine missing di fields as NotImplemented.
rainwoodman Dec 13, 2024
b025d21
Merge branch 'main' into senml
rainwoodman Dec 13, 2024
253870e
update pytest workflow.
rainwoodman Dec 13, 2024
e9c16d2
Remove the txt version files.
rainwoodman Dec 13, 2024
2e503f9
Comment about the assert helper.
rainwoodman Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/pytestPR.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: "PyTest PR"

on:
pull_request_target:
types:
- opened
- edited
- synchronize

jobs:
main:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]
steps:
- uses: actions/checkout@v3
- name: developer mode install
run: pip install -e .
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install pytest dependencies
run: pip install pytest pytest-md pytest-emoji
- uses: pavelzw/pytest-action@v2
with:
emoji: false
verbose: false
job-summary: true
165 changes: 100 additions & 65 deletions src/blueair_api/device_aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from .callbacks import CallbacksMixin
from .http_aws_blueair import HttpAwsBlueair
from .model_enum import ModelEnum
from .util import convert_api_array_to_dict, safely_get_json_value
from . import intermediate_representation_aws as ir

_LOGGER = logging.getLogger(__name__)

type AttributeType[T] = T | None | type[NotImplemented]

@dataclasses.dataclass(slots=True)
class DeviceAws(CallbacksMixin):
Expand All @@ -27,71 +28,94 @@ async def create_device(cls, api, uuid, name, mac, type_name, refresh=False):
return device_aws

api: HttpAwsBlueair
uuid: str = None
name: str = None
name_api: str = None
mac: str = None
type_name: str = None

sku: str = None
firmware: str = None
mcu_firmware: str = None
serial_number: str = None

brightness: int = None
child_lock: bool = None
fan_speed: int = None
fan_auto_mode: bool = None
running: bool = None
night_mode: bool = None
germ_shield: bool = None

pm1: int = None
pm2_5: int = None
pm10: int = None
tVOC: int = None
temperature: int = None
humidity: int = None
filter_usage: int = None # percentage
wifi_working: bool = None

# i35
wick_usage: int = None # percentage
wick_dry_mode: bool = None
water_shortage: bool = None
auto_regulated_humidity: int = None
uuid : str | None = None
name : str | None = None
name_api : str | None = None
mac : str | None = None
type_name : str | None = None

# Attributes are defined below.
# We mandate that unittests shall test all fields of AttributeType.
sku : AttributeType[str] = None
firmware : AttributeType[str] = None
mcu_firmware : AttributeType[str] = None
serial_number : AttributeType[str] = None

brightness : AttributeType[int] = None
child_lock : AttributeType[bool] = None
fan_speed : AttributeType[int] = None
fan_auto_mode : AttributeType[bool] = None
standby : AttributeType[bool] = None
night_mode : AttributeType[bool] = None
germ_shield : AttributeType[bool] = None

pm1 : AttributeType[int] = None
pm2_5 : AttributeType[int] = None
pm10 : AttributeType[int] = None
tVOC : AttributeType[int] = None
temperature : AttributeType[int] = None
humidity : AttributeType[int] = None
filter_usage_percentage : AttributeType[int] = None
wifi_working : AttributeType[bool] = None

wick_usage_percentage : AttributeType[int] = None
wick_dry_mode : AttributeType[bool] = None
water_shortage : AttributeType[bool] = None
auto_regulated_humidity : AttributeType[int] = None

async def refresh(self):
_LOGGER.debug(f"refreshing blueair device aws: {self}")
info = await self.api.device_info(self.name_api, self.uuid)
sensor_data = convert_api_array_to_dict(info["sensordata"])
self.pm1 = safely_get_json_value(sensor_data, "pm1", int)
self.pm2_5 = safely_get_json_value(sensor_data, "pm2_5", int)
self.pm10 = safely_get_json_value(sensor_data, "pm10", int)
self.tVOC = safely_get_json_value(sensor_data, "tVOC", int)
self.temperature = safely_get_json_value(sensor_data, "t", int)
self.humidity = safely_get_json_value(sensor_data, "h", int)

self.name = safely_get_json_value(info, "configuration.di.name")
self.firmware = safely_get_json_value(info, "configuration.di.cfv")
self.mcu_firmware = safely_get_json_value(info, "configuration.di.mfv")
self.serial_number = safely_get_json_value(info, "configuration.di.ds")
self.sku = safely_get_json_value(info, "configuration.di.sku")

states = convert_api_array_to_dict(info["states"])
self.running = safely_get_json_value(states, "standby") is False
self.night_mode = safely_get_json_value(states, "nightmode", bool)
self.germ_shield = safely_get_json_value(states, "germshield", bool)
self.brightness = safely_get_json_value(states, "brightness", int)
self.child_lock = safely_get_json_value(states, "childlock", bool)
self.fan_speed = safely_get_json_value(states, "fanspeed", int)
self.fan_auto_mode = safely_get_json_value(states, "automode", bool)
self.filter_usage = safely_get_json_value(states, "filterusage", int)
self.wifi_working = safely_get_json_value(states, "online", bool)
self.wick_usage = safely_get_json_value(states, "wickusage", int)
self.wick_dry_mode = safely_get_json_value(states, "wickdrys", bool)
self.auto_regulated_humidity = safely_get_json_value(states, "autorh", int)
self.water_shortage = safely_get_json_value(states, "wshortage", bool)
# ir.parse_json(ir.Attribute, ir.query_json(info, "configuration.da"))
ds = ir.parse_json(ir.Sensor, ir.query_json(info, "configuration.ds"))
dc = ir.parse_json(ir.Control, ir.query_json(info, "configuration.dc"))

sensor_data = ir.SensorPack(info["sensordata"]).to_latest_value()

def sensor_data_safe_get(key):
return sensor_data.get(key) if key in ds else NotImplemented

self.pm1 = sensor_data_safe_get("pm1")
self.pm2_5 = sensor_data_safe_get("pm2_5")
self.pm10 = sensor_data_safe_get("pm10")
self.tVOC = sensor_data_safe_get("tVOC")
self.temperature = sensor_data_safe_get("t")
self.humidity = sensor_data_safe_get("h")

def info_safe_get(path):
# directly reads for the schema. If the schema field is
# undefined, it is NotImplemented, not merely unavailable.
value = ir.query_json(info, path)
if value is None:
return NotImplemented
return value

self.name = info_safe_get("configuration.di.name")
self.firmware = info_safe_get("configuration.di.cfv")
self.mcu_firmware = info_safe_get("configuration.di.mfv")
self.serial_number = info_safe_get("configuration.di.ds")
self.sku = info_safe_get("configuration.di.sku")

states = ir.SensorPack(info["states"]).to_latest_value()

def states_safe_get(key):
return states.get(key) if key in dc else NotImplemented

# "online" is not defined in the schema.
self.wifi_working = states.get("online")

self.standby = states_safe_get("standby")
self.night_mode = states_safe_get("nightmode")
self.germ_shield = states_safe_get("germshield")
self.brightness = states_safe_get("brightness")
self.child_lock = states_safe_get("childlock")
self.fan_speed = states_safe_get("fanspeed")
self.fan_auto_mode = states_safe_get("automode")
self.filter_usage_percentage = states_safe_get("filterusage")
self.wick_usage_percentage = states_safe_get("wickusage")
self.wick_dry_mode = states_safe_get("wickdrys")
self.auto_regulated_humidity = states_safe_get("autorh")
self.water_shortage = states_safe_get("wshortage")

self.publish_updates()
_LOGGER.debug(f"refreshed blueair device aws: {self}")
Expand All @@ -106,11 +130,22 @@ async def set_fan_speed(self, value: int):
await self.api.set_device_info(self.uuid, "fanspeed", "v", value)
self.publish_updates()

async def set_running(self, running: bool):
self.running = running
await self.api.set_device_info(self.uuid, "standby", "vb", not running)
async def set_standby(self, value: bool):
self.standby = value
await self.api.set_device_info(self.uuid, "standby", "vb", value)
self.publish_updates()

# FIXME: avoid state translation at the API level and depreate running.
# replace with standby which is standard across aws devices.
@property
def running(self) -> AttributeType[bool]:
if self.standby is None or self.standby is NotImplemented:
return self.standby
return not self.standby

async def set_running(self, running: bool):
await self.set_standby(not running)

async def set_fan_auto_mode(self, fan_auto_mode: bool):
self.fan_auto_mode = fan_auto_mode
await self.api.set_device_info(self.uuid, "automode", "vb", fan_auto_mode)
Expand Down
Loading
Loading