Source code for aioafero.util

"""Generic utils for interacting with Afero IoT API or the responses."""

from datetime import UTC, datetime
from typing import Any


[docs] def get_afero_base_time_ms() -> int: """Return the current time as epoch milliseconds for v1 API state timestamps.""" return int(datetime.now(UTC).timestamp() * 1000)
[docs] def normalize_afero_last_update_time_ms(last_update_time: int | None) -> int: """Normalize v1 lastUpdateTime values to epoch milliseconds.""" if last_update_time is None: return get_afero_base_time_ms() if last_update_time < 10_000_000_000: return last_update_time * 1000 return last_update_time
[docs] def percentage_to_ordered_list_item[T](ordered_list: list[T], percentage: int) -> T: """Find the item that most closely matches the percentage in an ordered list. When using this utility for fan speeds, do not include "off" Given the list: ["low", "medium", "high", "very_high"], this function will return the following when the item is passed in: 1-25: low 26-50: medium 51-75: high 76-100: very_high """ if not (list_len := len(ordered_list)): raise ValueError("The ordered list is empty") for offset, speed in enumerate(ordered_list): list_position = offset + 1 upper_bound = (list_position * 100) // list_len if percentage <= upper_bound: return speed return ordered_list[-1]
[docs] def ordered_list_item_to_percentage[T](ordered_list: list[T], item: T) -> int: """Determine the percentage of an item in an ordered list. When using this utility for fan speeds, do not include "off" Given the list: ["low", "medium", "high", "very_high"], this function will return the following when the item is passed in: low: 25 medium: 50 high: 75 very_high: 100 """ if item not in ordered_list: raise ValueError(f'The item "{item}" is not in "{ordered_list}"') list_len = len(ordered_list) list_position = ordered_list.index(item) + 1 return (list_position * 100) // list_len
[docs] def process_range(range_vals: dict) -> list[Any]: """Process a range to determine what's supported. :param range_vals: Result from functions["values"][x] """ supported_range = [] range_min = range_vals["range"]["min"] range_max = range_vals["range"]["max"] range_step = range_vals["range"]["step"] if range_min == range_max: supported_range.append(range_max) else: supported_range = list(float_range(range_min, range_max, range_step)) if range_max not in supported_range: supported_range.append(range_max) return supported_range
[docs] def float_range(start, stop, step): """Create a generator that yields a range as float.""" while start < stop: yield start start += step
[docs] def process_function( functions: list[dict], func_class: str, func_instance: str | None = None ) -> list[Any]: """Generate a list of whatever you are searching for. :param functions: List of functions for the given device :param func_class: functionClass to search :param func_instance: functionInstance to search """ results = [] for function in functions: if function["functionClass"] != func_class: continue if func_instance and function.get("functionInstance") != func_instance: continue if function["type"] == "numeric": results = process_range(function["values"][0]) elif function["type"] == "category": for value in function["values"]: results.append(value["name"]) break return results