pax_global_header00006660000000000000000000000064147062145560014524gustar00rootroot0000000000000052 comment=d67cf9dd0dc5903afa6bc7e499cda30ea65cc646 python-myuplink-0.6.0/000077500000000000000000000000001470621455600147165ustar00rootroot00000000000000python-myuplink-0.6.0/PKG-INFO000066400000000000000000000017771470621455600160270ustar00rootroot00000000000000Metadata-Version: 2.1 Name: myuplink Version: 0.6.0 Summary: API package for myUplink Home-page: https://github.com/pajzo/myuplink Author: Peter Winkler Author-email: peter@fluxi.dk Project-URL: Bug Tracker, https://github.com/pajzo/myuplink/issues Project-URL: repository, https://github.com/pajzo/myuplink Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Requires-Python: >=3.9 Description-Content-Type: text/markdown Requires-Dist: aiohttp>=3.7.3 # myUplink Package for getting data from the myUplink API. Primary usage is for [Home Assistant](https://www.home-assistant.io/integrations/myuplink/) Package is published to [PyPI](https://pypi.org/project/myuplink/#history) ## Supported features - Invoke ping and protected ping - Get systems - Get notifications - Get devices on a system - Get data points on a device ## Requirements - aiohttp ## Test before commit ``` pip install . python examples/example1.py ``` python-myuplink-0.6.0/README.md000066400000000000000000000007331470621455600162000ustar00rootroot00000000000000# myUplink Package for getting data from the myUplink API. Primary usage is for [Home Assistant](https://www.home-assistant.io/integrations/myuplink/) Package is published to [PyPI](https://pypi.org/project/myuplink/#history) ## Supported features - Invoke ping and protected ping - Get systems - Get notifications - Get devices on a system - Get data points on a device ## Requirements - aiohttp ## Test before commit ``` pip install . python examples/example1.py ``` python-myuplink-0.6.0/pyproject.toml000066400000000000000000000001241470621455600176270ustar00rootroot00000000000000[build-system] requires = ['setuptools>=42'] build-backend = 'setuptools.build_meta'python-myuplink-0.6.0/setup.cfg000066400000000000000000000013401470621455600165350ustar00rootroot00000000000000[metadata] name = myuplink version = 0.6.0 author = Peter Winkler author_email = peter@fluxi.dk description = API package for myUplink long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/pajzo/myuplink project_urls = Bug Tracker = https://github.com/pajzo/myuplink/issues repository = https://github.com/pajzo/myuplink classifiers = Programming Language :: Python :: 3 License :: OSI Approved :: MIT License Operating System :: OS Independent [options] package_dir = = src packages = find: python_requires = >=3.9 install_requires = aiohttp >= 3.7.3 [options.package_data] myuplink = py.typed [options.packages.find] where = src [egg_info] tag_build = tag_date = 0 python-myuplink-0.6.0/src/000077500000000000000000000000001470621455600155055ustar00rootroot00000000000000python-myuplink-0.6.0/src/myuplink/000077500000000000000000000000001470621455600173555ustar00rootroot00000000000000python-myuplink-0.6.0/src/myuplink/__init__.py000066400000000000000000000007101470621455600214640ustar00rootroot00000000000000"""Library for myUplink.""" from .api import MyUplinkAPI # noqa: F401 from .auth import Auth # noqa: F401 from .auth_abstract import AbstractAuth # noqa: F401 from .models import ( # noqa: F401 Device, DeviceConnectionState, DevicePoint, EnumValue, Paging, System, SystemDevice, SystemNotification, SystemNotificationStatus, ) from .names import get_model, get_manufacturer, get_series, get_system_name # noqa:F401 python-myuplink-0.6.0/src/myuplink/api.py000066400000000000000000000150611470621455600205030ustar00rootroot00000000000000"""Define the MyUplinkAPI class.""" from .auth import AbstractAuth from .models import System, SystemNotification, Device, DevicePoint, Paging, Zone class MyUplinkAPI: """Class to communicate with the myUplink API.""" def __init__(self, auth: AbstractAuth): """Initialize the API and store the auth so we can make requests.""" self.auth = auth async def async_ping(self) -> bool: """Return true or false, depending on ping status.""" resp = await self.auth.request("get", "v2/ping", include_access_token=False) if 200 <= resp.status < 300: return True else: return False async def async_ping_protected(self) -> bool: """Return true or false, depending on protected ping status.""" resp = await self.auth.request("get", "v2/protected-ping") if 200 <= resp.status < 300: return True else: return False async def async_get_systems(self) -> list[System]: """Return systems.""" json = await self.async_get_systems_json() array = json["systems"] return [System(system_data) for system_data in array] async def async_get_systems_json(self) -> dict: """Return systems as json.""" resp = await self.auth.request("get", "v2/systems/me") resp.raise_for_status() return await resp.json() async def async_get_device(self, device_id) -> Device: """Return the device.""" json = await self.async_get_device_json(device_id) return Device(json) async def async_get_device_json(self, device_id) -> dict: """Return the device as json.""" resp = await self.auth.request("get", f"v2/devices/{device_id}") resp.raise_for_status() return await resp.json() async def async_get_device_points( self, device_id, language: str = "en-GB", points: list[str] | None = None ) -> list[DevicePoint]: """Return device points.""" array = await self.async_get_device_points_json(device_id, language, points) return [DevicePoint(point_data) for point_data in array] async def async_get_device_points_json( self, device_id, language: str = "en-GB", points: list[str] | None = None ) -> dict: """Return device points as json.""" headers = {"Accept-Language": language} if points is None: points = [] params = "" if len(points) > 0: params = "?parameters=" + ",".join(points) resp = await self.auth.request( "get", f"v2/devices/{device_id}/points{params}", headers=headers ) resp.raise_for_status() return await resp.json() async def async_set_device_points(self, device_id, data: dict) -> dict: """Return device points.""" headers = {"Content-Type": "application/json-patch+json"} resp = await self.auth.request( "patch", f"v2/devices/{device_id}/points", json=data, headers=headers ) resp.raise_for_status() array = await resp.json() return array async def async_get_system_notifications( self, system_id, only_active: bool = True, page: int = 1, items_per_page=10, language: str = "en-GB", ) -> Paging[SystemNotification]: """Return device points.""" json = await self.async_get_system_notifications_json( system_id=system_id, only_active=only_active, page=page, items_per_page=items_per_page, language=language, ) json_array = json["notifications"] model_array = [SystemNotification(notification) for notification in json_array] paging = Paging( page_number=json["page"], items_per_page=json["itemsPerPage"], total_items=json["numItems"], items=model_array, ) return paging async def async_get_system_notifications_json( self, system_id, only_active: bool = True, page: int = 1, items_per_page=10, language: str = "en-GB", ) -> dict: """Return system notifications as json.""" headers = {"Accept-Language": language} active_suffix = "" if only_active: active_suffix = "/active" resp = await self.auth.request( "get", f"v2/systems/{system_id}/notifications{active_suffix}" f"?page={page}&itemsPerPage={items_per_page}", headers=headers, ) resp.raise_for_status() return await resp.json() async def async_get_smart_home_mode_json(self, system_id: str) -> dict: """Return smart_home_mode as json.""" resp = await self.auth.request("get", f"v2/systems/{system_id}/smart-home-mode") resp.raise_for_status() return await resp.json() async def async_get_smart_home_mode(self, system_id: str) -> dict: """Return smart_home_mode.""" json = await self.async_get_smart_home_mode_json(system_id) return json async def async_set_smart_home_mode(self, system_id: str, data: dict) -> dict: """Return device points.""" headers = {"Content-Type": "application/json-patch+json"} resp = await self.auth.request( "put", f"v2/devices/{system_id}/smart-home-mode", json=data, headers=headers ) resp.raise_for_status() array = await resp.json() return array async def async_get_smart_home_categories_json(self, device_id: str) -> dict: """Return smart home categories for device as json.""" resp = await self.auth.request( "get", f"v2/devices/{device_id}/smart-home-categories" ) resp.raise_for_status() return await resp.json() async def async_get_smart_home_categories(self, device_id: str) -> dict: """Return smart home categories for device.""" data = await self.async_get_smart_home_categories_json(device_id) return data async def async_get_smart_home_zones_json(self, device_id: str) -> dict: """Return smart home zones for device as json.""" resp = await self.auth.request( "get", f"v2/devices/{device_id}/smart-home-zones" ) resp.raise_for_status() return await resp.json() async def async_get_smart_home_zones(self, device_id: str) -> list[Zone]: """Return smart home zones for device.""" data = await self.async_get_smart_home_zones_json(device_id) return [Zone(zone_data) for zone_data in data] python-myuplink-0.6.0/src/myuplink/auth.py000066400000000000000000000013421470621455600206700ustar00rootroot00000000000000from .auth_abstract import AbstractAuth from aiohttp import ClientSession class Auth(AbstractAuth): def __init__(self, websession: ClientSession, host: str, token_manager): """Initialize the auth.""" super().__init__(websession, host) self.token_manager = token_manager async def async_get_access_token(self) -> str: """Return a valid access token.""" if (isinstance(self.token_manager, str)): return self.token_manager if self.token_manager.is_token_valid(): return self.token_manager.access_token await self.token_manager.fetch_access_token() await self.token_manager.save_access_token() return self.token_manager.access_tokenpython-myuplink-0.6.0/src/myuplink/auth_abstract.py000066400000000000000000000017751470621455600225650ustar00rootroot00000000000000from abc import ABC, abstractmethod from aiohttp import ClientSession, ClientResponse class AbstractAuth(ABC): """Abstract class to make authenticated requests.""" def __init__(self, websession: ClientSession, host: str): """Initialize the auth.""" self.websession = websession self.host = host @abstractmethod async def async_get_access_token(self) -> str: """Return a valid access token.""" async def request(self, method, url, include_access_token: bool = True, **kwargs) -> ClientResponse: """Make a request.""" headers = kwargs.get("headers") if headers is None: headers = {} else: headers = kwargs.pop("headers") if (include_access_token): access_token = await self.async_get_access_token() headers["authorization"] = f"Bearer {access_token}" return await self.websession.request( method, f"{self.host}/{url}", **kwargs, headers=headers, ) python-myuplink-0.6.0/src/myuplink/models.py000066400000000000000000000324151470621455600212170ustar00rootroot00000000000000"""Data models for myuplink.""" from typing import TypeVar, Generic from datetime import datetime from enum import Enum T = TypeVar("T") # Deprecated 2024-04-30 # pylint: disable=invalid-name class Paging(Generic[T]): """Paging object.""" def __init__( self, page_number: int, items_per_page: int, total_items: int, items: list[T] ): """Initialize a paging object.""" self.page_number = page_number self.items_per_page = items_per_page self.total_items = total_items self.items = items class SystemDevice: """System device object.""" def __init__(self, raw_data: dict): """Initialize a system device object.""" self.raw_data = raw_data @property def id(self) -> str: """Return the ID of the device.""" return self.raw_data["id"] @property def deviceId(self) -> str: """Return the ID of the device. Deprecated 2024-04-30.""" return self.id @property def product_name(self) -> str: """Return the system name.""" return self.raw_data["product"]["name"] @property def product_serial_number(self) -> str: """Return the product serial number.""" return self.raw_data["product"]["serialNumber"] @property def current_fw_version(self) -> str: """Return the current firmware version.""" return self.raw_data["currentFwVersion"] @property def raw(self) -> dict: """Return the raw data.""" return self.raw_data class SystemNotificationStatus(Enum): """Enums for notification status.""" NoStatus = 0 Active = 1 DismissedByDevice = 2 ResetByUserOnDevice = 3 ResetByUserFromCloud = 4 class SystemNotification: """System notification object.""" def __init__(self, raw_data: dict): """Initialize a system notification object.""" self.raw_data = raw_data @property def notification_id(self) -> str: """Return the ID of the notification.""" return self.raw_data["id"] @property def notificationId(self) -> str: """Return the ID of the notification. Deprecated 2024-04-30.""" return self.notification_id @property def device_id(self) -> str: """Return the device ID of the notification.""" return self.raw_data["deviceId"] @property def created(self) -> datetime: """Return the date and time of the notification.""" return datetime.strptime( self.raw_data["createdDatetime"][:-2], "%Y-%m-%dT%H:%M:%S" ) @property def alarm_number(self) -> int: """Return the alarm number.""" return self.raw_data["alarmNumber"] @property def alarmNumber(self) -> int: """Return the alarm number. Deprecated 2024-04-30""" return self.alarm_number @property def severity(self) -> int: """Return the severity of the notification.""" return self.raw_data["severity"] @property def status(self) -> SystemNotificationStatus | None: """Return the status of the notification.""" status_str = self.raw_data.get("status", "Unknown").replace("None", "NoStatus") if status_str in SystemNotificationStatus.__members__: return SystemNotificationStatus[status_str] else: return None @property def header(self) -> str: """Return the header of the notification.""" return self.raw_data["header"] @property def description(self) -> str: """Return the description of the notification.""" return self.raw_data["description"] @property def equip_name(self) -> str: """Return the equipName of the notification.""" return self.raw_data["equipName"] @property def equipName(self) -> str: """Return the equipName of the notification. Deprecated 2024-04-30.""" return self.equip_name @property def raw(self) -> dict: """Return raw data.""" return self.raw_data class System: """System object.""" def __init__(self, raw_data: dict): """Initialize a system object.""" self.raw_data = raw_data @property def system_id(self) -> str: """Return the system ID.""" return self.raw_data["systemId"] @property def id(self) -> str: """Return the ID of the system. Deprecated 2024-04-30""" return self.system_id @property def name(self) -> str: """Return the name of the system.""" return self.raw_data["name"] @property def has_alarm(self) -> bool: """Return if the system has alarm.""" return self.raw_data["hasAlarm"] @property def hasAlarm(self) -> bool: """Return if the system has alarm. Deprecated 2024-04-30""" return self.has_alarm @property def security_level(self) -> str: """ "Return the security level.""" return self.raw_data["securityLevel"] @property def country(self) -> str: """ "Return the country.""" return self.raw_data["country"] @property def devices(self) -> list[SystemDevice]: """Return devices on the system.""" return [SystemDevice(device_data) for device_data in self.raw_data["devices"]] @property def raw(self) -> dict: """Return the raw data.""" return self.raw_data class DeviceConnectionState(Enum): """Enums for device connection state.""" Disconnected = 0 Connected = 1 class Device: """Define device object.""" def __init__(self, raw_data: dict): """Initialize a device object.""" self.raw_data = raw_data @property def id(self) -> str: """Return the ID of the device.""" return self.raw_data["id"] @property def product_name(self) -> str: """Return the name of the device product.""" return self.raw_data["product"]["name"] @property def productName(self) -> str: """Return the name of the device product. Deprecated 2024-04-30.""" return self.product_name @property def product_serial_number(self) -> str: """Return the serialno. of the device product.""" return self.raw_data["product"]["serialNumber"] @property def productSerialNumber(self) -> str: """Return the serialno. of the device product. Deprecated 2024-04-30.""" return self.product_serial_number @property def curret_firmware_version(self) -> str: """Return the current firmware version.""" return self.raw_data["firmware"]["currentFwVersion"] @property def firmwareCurrent(self) -> str: """Return the current firmware version. Deprecated 2024-04-30.""" return self.curret_firmware_version @property def desired_firmware_version(self) -> str: """Return the desired firmware version.""" return self.raw_data["firmware"]["desiredFwVersion"] @property def firmwareDesired(self) -> str: """Return the desired firmware version. Deprecated 2024-04-30.""" return self.desired_firmware_version @property def available_features(self) -> dict[str, bool]: """Return dict with available features.""" return self.raw_data["availableFeatures"] @property def connectionState(self) -> DeviceConnectionState | None: """Return the connection state.""" connection_state_str = self.raw_data.get("connectionState", "Unknown") if connection_state_str in DeviceConnectionState.__members__: return DeviceConnectionState[connection_state_str] else: return None @property def raw(self) -> dict: """Return the raw data.""" return self.raw_data class EnumValue: """Define EnumValue object.""" def __init__(self, raw_data: dict): """Initialize an EnumValue object.""" self.raw_data = raw_data @property def value(self) -> str | None: """Return the value.""" return self.raw_data["value"] @property def text(self) -> str | None: """Return the text.""" return self.raw_data["text"] @property def icon(self) -> str | None: """Return the icon.""" return self.raw_data["icon"] class DevicePoint: """Define device point object.""" def __init__(self, raw_data: dict): """Initialize a device point.""" self.raw_data = raw_data @property def category(self) -> str: """Return the category.""" return self.raw_data["category"] @property def parameter_id(self) -> str: """Return the parameter id.""" return self.raw_data["parameterId"] @property def parameter_name(self) -> str: """Return the parameter name.""" return self.raw_data["parameterName"] @property def parameter_unit(self) -> str: """Return the parameter unit.""" return self.raw_data["parameterUnit"] @property def timestamp(self) -> str: """Return the timestamp.""" return self.raw_data["timestamp"] # todo: Wait with sharp typing until integration is upgraded # change to value_t in integration, when everything is stable # change value() to typed, update lib and then change back to # value() in the intergation @property def value(self): """Return the value.""" return self.raw_data["value"] @property def value_t(self) -> str | float | int | None: """Return the value.""" return self.raw_data["value"] @value.setter def value(self, new_value: str | float | int | None): """Set the value.""" self.raw_data["value"] = new_value @property def min_value(self) -> float | None: """Return the minimum value.""" return self.raw_data["minValue"] @property def max_value(self) -> float | None: """Return the maximum value.""" return self.raw_data["maxValue"] @property def step_value(self) -> float | None: """Return the step value.""" return self.raw_data["stepValue"] @property def scale_value(self) -> float | None: """Return the scaling factor for min and max values.""" return self.raw_data["scaleValue"] @property def enum_values_list(self) -> list[EnumValue]: """Return the enumValues as list.""" return [EnumValue(enum_data) for enum_data in self.raw_data["enumValues"]] # todo: Don't sharpen typing until integration is updated # @property # def enum_values(self) -> list[EnumValue] @property def enum_values(self): """Return the enumValues as dict.""" return self.raw_data["enumValues"] @property def smart_home_categories(self) -> list[str]: """Return smart_home_categories as list.""" return self.raw_data["smartHomeCategories"] @property def writable(self) -> bool: """Return writable status.""" return self.raw_data["writable"] @property def zone_id(self) -> str | None: """Return the zone id.""" return self.raw_data["zoneId"] @property def raw(self) -> dict: """Return the raw data.""" return self.raw_data class Zone: """Define Zone object.""" def __init__(self, raw_data: dict): """Init the Zone object.""" self.raw_data = raw_data @property def zone_id(self) -> str | None: """Return the zone id.""" return self.raw_data["zoneId"] @property def name(self) -> str | None: """Return the name.""" return self.raw_data["name"] @property def command_only(self) -> bool: """Return command_only.""" return self.raw_data["commandOnly"] @property def supported_modes(self) -> str | None: """Return the supported modes.""" return self.raw_data["supportedModes"] @property def mode(self) -> str | None: """Return the mode.""" return self.raw_data["mode"] @property def temperature(self) -> float | None: """Return the temperature.""" return self.raw_data["temperature"] @property def setpoint(self) -> float | None: """Return the setpoint.""" return self.raw_data["setpoint"] @property def setpoint_heat(self) -> float | None: """Return the heat setpoint.""" return self.raw_data["setpointHeat"] @property def setpoint_cool(self) -> float | None: """Return the coolsetpoint.""" return self.raw_data["setpointCool"] @property def setpoint_range_max(self) -> int | None: """Return the max range setpoint.""" return self.raw_data["setpointRangeMax"] @property def setpoint_range_min(self) -> int | None: """Return the min range setpoint.""" return self.raw_data["setpointRangeMin"] @property def is_celcius(self) -> bool: """Return is celcius.""" return self.raw_data["isCelcius"] @property def indoor_co2(self) -> int | None: """Return the indoor CO2 level.""" return self.raw_data["indoorCo2"] @property def indoor_humidity(self) -> float | None: """Return the indoor humidity.""" return self.raw_data["indoorHumidity"] @property def raw(self) -> dict: """Return the raw data.""" return self.raw_data python-myuplink-0.6.0/src/myuplink/names.py000066400000000000000000000034571470621455600210430ustar00rootroot00000000000000"""Methods for getting names etc from API data.""" import re from .models import Device, System MAP_NIBEF = {"manufacturer": "Nibe", "series": "F"} MAP_NIBES = {"manufacturer": "Nibe", "series": "S"} NAME_MAP = { "F1145": MAP_NIBEF, "F1155": MAP_NIBEF, "F1245": MAP_NIBEF, "F1255": MAP_NIBEF, "F1345": MAP_NIBEF, "F1355": MAP_NIBEF, "F370": MAP_NIBEF, "F470": MAP_NIBEF, "F730": MAP_NIBEF, "F750": MAP_NIBEF, "SMO20": MAP_NIBEF, "SMO 20": MAP_NIBEF, "SMO40": MAP_NIBEF, "SMO 40": MAP_NIBEF, "VVM225": MAP_NIBEF, "VVM310": MAP_NIBEF, "VVM320": MAP_NIBEF, "VVM325": MAP_NIBEF, "VVM500": MAP_NIBEF, "S1155": MAP_NIBES, "S1255": MAP_NIBES, "S1256": MAP_NIBES, "S320": MAP_NIBES, "S325": MAP_NIBES, "S735": MAP_NIBES, "S2125": MAP_NIBES, "SMOS40": MAP_NIBES, "SMOS 40": MAP_NIBES, } def get_system_name(system: System) -> str | None: """Return system name.""" return system.name def get_manufacturer(device: Device) -> str | None: """Return manufacturer name.""" for model, data in NAME_MAP.items(): if re.search(model, device.product_name): return data.get("manufacturer") return device.productName.split()[0] def get_model(device: Device) -> str | None: """Return model name.""" for model in NAME_MAP: if re.search(model, device.product_name): return model name_list = device.product_name.split() if len(name_list) == 1: model = name_list[0] else: model = " ".join(name_list[1:]) return model def get_series(device: Device) -> str | None: """Return product series.""" for model, data in NAME_MAP.items(): if re.search(model, device.product_name): return data.get("series") return None python-myuplink-0.6.0/src/myuplink/py.typed000066400000000000000000000000001470621455600210420ustar00rootroot00000000000000