compit-inext-api-beta 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of compit-inext-api-beta might be problematic. Click here for more details.

@@ -0,0 +1,30 @@
1
+ """Module HTTP communication with the Inext Compit api."""
2
+
3
+ from .device_definitions import DeviceDefinitionsLoader
4
+ from .api import CompitAPI, CannotConnect, InvalidAuth
5
+ from .types.DeviceState import DeviceState, Param, DeviceInstance
6
+ from .types.DeviceDefinitions import DeviceDefinitions, Parameter, ParameterDetails
7
+ from .types.SystemInfo import SystemInfo, Gate, Device
8
+ from .consts import CompitHVACMode, CompitParameter, CompitFanMode, CompitPresetMode
9
+ from .connector import CompitApiConnector
10
+
11
+ __all__ = [
12
+ "DeviceDefinitionsLoader",
13
+ "CompitAPI",
14
+ "DeviceState",
15
+ "Param",
16
+ "DeviceInstance",
17
+ "DeviceDefinitions",
18
+ "Parameter",
19
+ "ParameterDetails",
20
+ "SystemInfo",
21
+ "Gate",
22
+ "Device",
23
+ "InvalidAuth",
24
+ "CannotConnect",
25
+ "CompitHVACMode",
26
+ "CompitParameter",
27
+ "CompitFanMode",
28
+ "CompitPresetMode",
29
+ "CompitApiConnector"
30
+ ]
@@ -0,0 +1,240 @@
1
+ """The Compit api connector."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Any
6
+
7
+ import aiohttp
8
+
9
+ from compit_inext_api.consts import CompitParameter
10
+
11
+ from .types.DeviceState import DeviceState
12
+ from .types.SystemInfo import SystemInfo
13
+
14
+ TIMEOUT = 10
15
+ _LOGGER: logging.Logger = logging.getLogger(__package__)
16
+ HEADERS = {"Content-type": "application/json; charset=UTF-8"}
17
+ API_URL = "https://inext.compit.pl/mobile/v2/compit"
18
+
19
+ class CompitAPI:
20
+ """API client for Compit."""
21
+
22
+ def __init__(self, email, password, session: aiohttp.ClientSession) -> None:
23
+ """Initialize the CompitAPI class."""
24
+ self.email = email
25
+ self.password = password
26
+ self.token = None
27
+ self._api_wrapper = ApiWrapper(session)
28
+
29
+ async def authenticate(self) -> SystemInfo | None:
30
+ """Authenticate the user."""
31
+ try:
32
+ response = await self._api_wrapper.post(
33
+ f"{API_URL}/authorize",
34
+ {
35
+ "email": self.email,
36
+ "password": self.password,
37
+ "uid": "HomeAssistant",
38
+ "label": "HomeAssistant",
39
+ },
40
+ )
41
+
42
+ if response.status == 401:
43
+ raise InvalidAuth()
44
+
45
+ if response.status == 422:
46
+ result = await self.get_result(response, ignore_response_code=True)
47
+ self.token = result["token"]
48
+ response = await self._api_wrapper.post(
49
+ f"{API_URL}/clients",
50
+ {
51
+ "fcm_token": None,
52
+ "uid": "HomeAssistant",
53
+ "label": "HomeAssistant",
54
+ },
55
+ auth=self.token,
56
+ )
57
+
58
+ result = await self.get_result(response)
59
+ return await self.authenticate()
60
+ elif response.status >= 400:
61
+ return None
62
+
63
+ result = await self.get_result(response)
64
+ self.token = result["token"]
65
+ return SystemInfo.from_json(result)
66
+ except aiohttp.ClientError as e:
67
+ _LOGGER.error(e)
68
+ raise CannotConnect()
69
+
70
+ async def get_gates(self) -> SystemInfo | None:
71
+ """Get the gates from the Compit API."""
72
+ try:
73
+ response = await self._api_wrapper.get(f"{API_URL}/gates", {}, self.token)
74
+
75
+ return SystemInfo.from_json(await self.get_result(response))
76
+ except aiohttp.ClientError as e:
77
+ _LOGGER.error(e)
78
+ return None
79
+
80
+ async def get_state(self, device_id: int) -> DeviceState | None:
81
+ """Get the state of a device."""
82
+ try:
83
+ response = await self._api_wrapper.get(
84
+ f"{API_URL}/devices/{device_id}/state", {}, self.token
85
+ )
86
+
87
+ return DeviceState.from_json(await self.get_result(response))
88
+
89
+ except aiohttp.ClientError as e:
90
+ _LOGGER.error(e)
91
+ return None
92
+
93
+ async def update_device_parameter(
94
+ self, device_id: int, parameter: CompitParameter, value: str | float
95
+ ) -> Any:
96
+ """Update the parameter of a device.
97
+
98
+ Args:
99
+ device_id (int): The ID of the device.
100
+ parameter (CompitParameter): The parameter to update.
101
+ value (str | float): The new value of the parameter.
102
+
103
+ Returns:
104
+ Any: The result of the update operation.
105
+
106
+ """
107
+ try:
108
+ _LOGGER.info("Set %s to %s for device %s", parameter, value, device_id)
109
+
110
+ data = {"values": [{"code": parameter.value, "value": value}]}
111
+
112
+ response = await self._api_wrapper.put(
113
+ f"{API_URL}/devices/{device_id}/params", data=data, auth=self.token
114
+ )
115
+ return await self.get_result(response)
116
+
117
+ except aiohttp.ClientError as e:
118
+ _LOGGER.error(e)
119
+ return None
120
+
121
+ async def get_result(
122
+ self,
123
+ response: aiohttp.ClientResponse | None,
124
+ ignore_response_code: bool = False,
125
+ ) -> Any:
126
+ """Get the result from the response.
127
+
128
+ Args:
129
+ response (aiohttp.ClientResponse): The response object.
130
+ ignore_response_code (bool, optional): Whether to ignore the response code. Defaults to False.
131
+
132
+ Returns:
133
+ Any: The result from the response.
134
+
135
+ Raises:
136
+ Exception: If the server returns an error response.
137
+
138
+ """
139
+ if response is None:
140
+ raise ValueError("Server empty response")
141
+ if response.ok or ignore_response_code:
142
+ return await response.json()
143
+
144
+ raise ValueError(f"Server returned: {response.status}, {response.reason}")
145
+
146
+
147
+ class ApiWrapper:
148
+ """Helper wrapper class."""
149
+
150
+ def __init__(self, session: aiohttp.ClientSession) -> None:
151
+ """Initialize the Helper class.
152
+
153
+ Args:
154
+ session (aiohttp.ClientSession): The aiohttp client session.
155
+
156
+ """
157
+ self._session = session
158
+
159
+ async def get(
160
+ self, url: str, headers: dict | None = None, auth: Any = None
161
+ ) -> aiohttp.ClientResponse:
162
+ """Run http GET method."""
163
+ if headers is None:
164
+ headers = {}
165
+
166
+ if auth:
167
+ headers["Authorization"] = auth
168
+
169
+ return await self.api_wrapper("get", url, headers=headers, auth=None)
170
+
171
+ async def post(
172
+ self,
173
+ url: str,
174
+ data: dict | None = None,
175
+ headers: dict | None = None,
176
+ auth: Any = None,
177
+ ) -> aiohttp.ClientResponse:
178
+ """Run http POST method."""
179
+ if headers is None:
180
+ headers = {}
181
+
182
+ if auth:
183
+ headers["Authorization"] = auth
184
+
185
+ return await self.api_wrapper(
186
+ "post", url, data=data, headers=headers, auth=None
187
+ )
188
+
189
+ async def put(
190
+ self,
191
+ url: str,
192
+ data: dict | None = None,
193
+ headers: dict | None = None,
194
+ auth: Any = None,
195
+ ) -> aiohttp.ClientResponse:
196
+ """Run http PUT method."""
197
+ if headers is None:
198
+ headers = {}
199
+
200
+ if auth:
201
+ headers["Authorization"] = auth
202
+
203
+ return await self.api_wrapper("put", url, data=data, headers=headers, auth=None)
204
+
205
+ async def api_wrapper(
206
+ self,
207
+ method: str,
208
+ url: str,
209
+ data: dict | None = None,
210
+ headers: dict | None = None,
211
+ auth: Any = None,
212
+ ) -> Any:
213
+ """Get information from the API."""
214
+ try:
215
+ async with asyncio.timeout(TIMEOUT):
216
+ if method == "get":
217
+ return await self._session.get(url, headers=headers, auth=auth)
218
+
219
+ if method == "post":
220
+ return await self._session.post(
221
+ url, headers=headers, data=data, auth=auth
222
+ )
223
+ if method == "put":
224
+ return await self._session.put(
225
+ url, headers=headers, json=data, auth=auth
226
+ )
227
+
228
+ except TimeoutError as exception:
229
+ _LOGGER.error(
230
+ "Timeout error fetching information from %s - %s",
231
+ url,
232
+ exception,
233
+ )
234
+
235
+ class CannotConnect(Exception):
236
+ """Error to indicate we cannot connect."""
237
+
238
+
239
+ class InvalidAuth(Exception):
240
+ """Error to indicate there is invalid auth."""
@@ -0,0 +1,66 @@
1
+ import aiohttp
2
+ import logging
3
+
4
+ from compit_inext_api.api import CompitAPI
5
+ from compit_inext_api.consts import CompitParameter
6
+ from compit_inext_api.device_definitions import DeviceDefinitionsLoader
7
+ from compit_inext_api.types.DeviceState import DeviceInstance, DeviceState, Param
8
+
9
+
10
+ _LOGGER: logging.Logger = logging.getLogger(__package__)
11
+
12
+
13
+ class CompitApiConnector:
14
+ """Connector class for Compit API."""
15
+
16
+ devices: dict[int, DeviceInstance] = {}
17
+
18
+ def __init__(self, session: aiohttp.ClientSession) -> None:
19
+ self.session = session
20
+
21
+ async def init(self, email: str, password: str, lang: str = "en") -> bool:
22
+ self.api = CompitAPI(email, password, self.session)
23
+ self.systemInfo = await self.api.authenticate()
24
+ if self.systemInfo is None:
25
+ _LOGGER.error("Failed to authenticate with Compit API")
26
+ return False
27
+
28
+ for gates in self.systemInfo.gates:
29
+ for device in gates.devices:
30
+ try:
31
+ self.devices[device.type] = DeviceInstance(await DeviceDefinitionsLoader.get_device_definition(device.type, lang))
32
+ state = await self.api.get_state(device.id)
33
+ if state and isinstance(state, DeviceState):
34
+ self.devices[device.type].state = state
35
+ else:
36
+ _LOGGER.error("Failed to get state for device %s", device.id)
37
+ except ValueError:
38
+ _LOGGER.warning("No definition found for device with code %d", device.type)
39
+ return True
40
+
41
+ async def update_state(self, device_id: int | None) -> None:
42
+ if device_id is None:
43
+ for device in self.devices.keys():
44
+ await self.update_state(device)
45
+ return
46
+
47
+ device = self.devices.get(device_id)
48
+ if device is None:
49
+ _LOGGER.warning("No device found with ID %d", device_id)
50
+ return
51
+
52
+ state = await self.api.get_state(device_id)
53
+ if state and isinstance(state, DeviceState):
54
+ device.state = state
55
+ else:
56
+ _LOGGER.error("Failed to get state for device %s", device_id)
57
+
58
+ def get_device_parameter(self, device_id: int, parameter: CompitParameter) -> Param | None:
59
+ return self.devices[device_id].state.get_parameter_value(parameter.value)
60
+
61
+ async def set_device_parameter(self, device_id: int, parameter: CompitParameter, value: str | float) -> bool:
62
+ result = await self.api.update_device_parameter(device_id, parameter, value)
63
+ if result:
64
+ self.devices[device_id].state.set_parameter_value(parameter.value, value)
65
+ return True
66
+ return False
@@ -0,0 +1,37 @@
1
+ from enum import Enum
2
+
3
+
4
+ class CompitHVACMode(Enum):
5
+ """Enum for available HVAC modes."""
6
+
7
+ HEAT = 0
8
+ OFF = 1
9
+ COOL = 2
10
+
11
+ class CompitParameter(Enum):
12
+ """Enum for Compit device parameters."""
13
+
14
+ PRESET_MODE = "__trybpracytermostatu"
15
+ FAN_MODE = "__trybaero"
16
+ HVAC_MODE = "__trybpracyinstalacji"
17
+ CURRENT_TEMPERATURE = "__tpokojowa"
18
+ TARGET_TEMPERATURE = "__tpokzadana"
19
+ SET_TARGET_TEMPERATURE = "__tempzadpracareczna"
20
+
21
+ class CompitFanMode(Enum):
22
+ """Enum for available fan modes."""
23
+
24
+ OFF = 0
25
+ LOW = 1
26
+ MEDIUM = 2
27
+ HIGH = 3
28
+ HOLIDAY = 4
29
+ AUTO = 5
30
+
31
+ class CompitPresetMode(Enum):
32
+ """Enum for available preset modes."""
33
+
34
+ AUTO = 0
35
+ HOLIDAY = 1
36
+ MANUAL = 2
37
+ AWAY = 3
@@ -0,0 +1 @@
1
+ # Device definitions package