Skip to content

Commit 850b5c7

Browse files
committed
Adhere new exceptions
1 parent 6dbe600 commit 850b5c7

File tree

1 file changed

+67
-28
lines changed

1 file changed

+67
-28
lines changed

airos/airos8.py

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
from .data import AirOS8Data as AirOSData
1414
from .exceptions import (
15-
ConnectionAuthenticationError,
16-
ConnectionSetupError,
17-
DataMissingError,
18-
DeviceConnectionError,
19-
KeyDataMissingError,
15+
AirOSConnectionAuthenticationError,
16+
AirOSConnectionSetupError,
17+
AirOSDataMissingError,
18+
AirOSDeviceConnectionError,
19+
AirOSKeyDataMissingError,
2020
)
2121

2222
_LOGGER = logging.getLogger(__name__)
@@ -52,6 +52,7 @@ def __init__(
5252
self._login_url = f"{self.base_url}/api/auth" # AirOS 8
5353
self._status_cgi_url = f"{self.base_url}/status.cgi" # AirOS 8
5454
self._stakick_cgi_url = f"{self.base_url}/stakick.cgi" # AirOS 8
55+
self._provmode_url = f"{self.base_url}/api/provmode" # AirOS 8
5556
self.current_csrf_token = None
5657

5758
self._use_json_for_login_post = False
@@ -103,10 +104,10 @@ async def login(self) -> bool:
103104
) as response:
104105
if response.status == 403:
105106
_LOGGER.error("Authentication denied.")
106-
raise ConnectionAuthenticationError from None
107+
raise AirOSConnectionAuthenticationError from None
107108
if not response.cookies:
108109
_LOGGER.exception("Empty cookies after login, bailing out.")
109-
raise ConnectionSetupError from None
110+
raise AirOSConnectionSetupError from None
110111
else:
111112
for _, morsel in response.cookies.items():
112113
# If the AIROS_ cookie was parsed but isn't automatically added to the jar, add it manually
@@ -159,15 +160,15 @@ async def login(self) -> bool:
159160
_LOGGER.exception(
160161
"COOKIE JAR IS EMPTY after login POST. This is a major issue."
161162
)
162-
raise ConnectionSetupError from None
163+
raise AirOSConnectionSetupError from None
163164
for cookie in self.session.cookie_jar: # pragma: no cover
164165
if cookie.key.startswith("AIROS_"):
165166
airos_cookie_found = True
166167
if cookie.key == "ok":
167168
ok_cookie_found = True
168169

169170
if not airos_cookie_found and not ok_cookie_found:
170-
raise ConnectionSetupError from None # pragma: no cover
171+
raise AirOSConnectionSetupError from None # pragma: no cover
171172

172173
response_text = await response.text()
173174

@@ -178,18 +179,18 @@ async def login(self) -> bool:
178179
return True
179180
except json.JSONDecodeError as err:
180181
_LOGGER.exception("JSON Decode Error")
181-
raise DataMissingError from err
182+
raise AirOSDataMissingError from err
182183

183184
else:
184185
log = f"Login failed with status {response.status}. Full Response: {response.text}"
185186
_LOGGER.error(log)
186-
raise ConnectionAuthenticationError from None
187+
raise AirOSConnectionAuthenticationError from None
187188
except (
188189
aiohttp.ClientError,
189190
aiohttp.client_exceptions.ConnectionTimeoutError,
190191
) as err:
191192
_LOGGER.exception("Error during login")
192-
raise DeviceConnectionError from err
193+
raise AirOSDeviceConnectionError from err
193194

194195
def derived_data(
195196
self, response: dict[str, Any] | None = None
@@ -202,7 +203,7 @@ def derived_data(
202203

203204
# No interfaces, no mac, no usability
204205
if not interfaces:
205-
raise KeyDataMissingError from None
206+
raise AirOSKeyDataMissingError from None
206207

207208
for interface in interfaces:
208209
if interface["enabled"]: # Only consider if enabled
@@ -227,7 +228,7 @@ async def status(self) -> AirOSData:
227228
"""Retrieve status from the device."""
228229
if not self.connected:
229230
_LOGGER.error("Not connected, login first")
230-
raise DeviceConnectionError from None
231+
raise AirOSDeviceConnectionError from None
231232

232233
# --- Step 2: Verify authenticated access by fetching status.cgi ---
233234
authenticated_get_headers = {**self._common_headers}
@@ -248,14 +249,14 @@ async def status(self) -> AirOSData:
248249
airos_data = AirOSData.from_dict(adjusted_json)
249250
except (MissingField, InvalidFieldValue) as err:
250251
_LOGGER.exception("Failed to deserialize AirOS data")
251-
raise KeyDataMissingError from err
252+
raise AirOSKeyDataMissingError from err
252253

253254
return airos_data
254255
except json.JSONDecodeError:
255256
_LOGGER.exception(
256257
"JSON Decode Error in authenticated status response"
257258
)
258-
raise DataMissingError from None
259+
raise AirOSDataMissingError from None
259260
else:
260261
log = f"Authenticated status.cgi failed: {response.status}. Response: {response_text}"
261262
_LOGGER.error(log)
@@ -264,33 +265,32 @@ async def status(self) -> AirOSData:
264265
aiohttp.client_exceptions.ConnectionTimeoutError,
265266
) as err:
266267
_LOGGER.exception("Error during authenticated status.cgi call")
267-
raise DeviceConnectionError from err
268+
raise AirOSDeviceConnectionError from err
268269

269270
async def stakick(self, mac_address: str = None) -> bool:
270271
"""Reconnect client station."""
271272
if not self.connected:
272273
_LOGGER.error("Not connected, login first")
273-
raise DeviceConnectionError from None
274+
raise AirOSDeviceConnectionError from None
274275
if not mac_address:
275276
_LOGGER.error("Device mac-address missing")
276-
raise DataMissingError from None
277+
raise AirOSDataMissingError from None
277278

278-
kick_request_headers = {**self._common_headers}
279+
request_headers = {**self._common_headers}
279280
if self.current_csrf_token:
280-
kick_request_headers["X-CSRF-ID"] = self.current_csrf_token
281+
request_headers["X-CSRF-ID"] = self.current_csrf_token
281282

282-
kick_payload = {"staif": "ath0", "staid": mac_address.upper()}
283+
payload = {"staif": "ath0", "staid": mac_address.upper()}
283284

284-
kick_request_headers["Content-Type"] = (
285+
request_headers["Content-Type"] = (
285286
"application/x-www-form-urlencoded; charset=UTF-8"
286287
)
287-
post_data = kick_payload
288288

289289
try:
290290
async with self.session.post(
291291
self._stakick_cgi_url,
292-
headers=kick_request_headers,
293-
data=post_data,
292+
headers=request_headers,
293+
data=payload,
294294
) as response:
295295
if response.status == 200:
296296
return True
@@ -302,5 +302,44 @@ async def stakick(self, mac_address: str = None) -> bool:
302302
aiohttp.ClientError,
303303
aiohttp.client_exceptions.ConnectionTimeoutError,
304304
) as err:
305-
_LOGGER.exception("Error during reconnect stakick.cgi call")
306-
raise DeviceConnectionError from err
305+
_LOGGER.exception("Error during reconnect request call")
306+
raise AirOSDeviceConnectionError from err
307+
308+
async def provmode(self, active: bool = False) -> bool:
309+
"""Set provisioning mode."""
310+
if not self.connected:
311+
_LOGGER.error("Not connected, login first")
312+
raise AirOSDeviceConnectionError from None
313+
314+
request_headers = {**self._common_headers}
315+
if self.current_csrf_token:
316+
request_headers["X-CSRF-ID"] = self.current_csrf_token
317+
318+
action = "stop"
319+
if active:
320+
action = "start"
321+
322+
payload = {"action": action}
323+
324+
request_headers["Content-Type"] = (
325+
"application/x-www-form-urlencoded; charset=UTF-8"
326+
)
327+
328+
try:
329+
async with self.session.post(
330+
self._provmode_url,
331+
headers=request_headers,
332+
data=payload,
333+
) as response:
334+
if response.status == 200:
335+
return True
336+
response_text = await response.text()
337+
log = f"Unable to change provisioning mode response status {response.status} with {response_text}"
338+
_LOGGER.error(log)
339+
return False
340+
except (
341+
aiohttp.ClientError,
342+
aiohttp.client_exceptions.ConnectionTimeoutError,
343+
) as err:
344+
_LOGGER.exception("Error during provisioning mode call")
345+
raise AirOSDeviceConnectionError from err

0 commit comments

Comments
 (0)