Skip to content

Commit 897eaf4

Browse files
committed
Fix age field in Remote for nanostation 8.7.11
1 parent e82cb88 commit 897eaf4

File tree

3 files changed

+90
-83
lines changed

3 files changed

+90
-83
lines changed

airos/data.py

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ def _redact(d: dict):
9292
# Data class start
9393

9494

95+
class AirOSDataClass(DataClassDictMixin):
96+
"""A base class for all mashumaro dataclasses."""
97+
98+
pass
99+
100+
95101
def _check_and_log_unknown_enum_value(
96102
data_dict: dict[str, Any],
97103
key: str,
@@ -149,15 +155,15 @@ class NetRole(Enum):
149155

150156

151157
@dataclass
152-
class ChainName:
158+
class ChainName(AirOSDataClass):
153159
"""Leaf definition."""
154160

155161
number: int
156162
name: str
157163

158164

159165
@dataclass
160-
class Host:
166+
class Host(AirOSDataClass):
161167
"""Leaf definition."""
162168

163169
hostname: str
@@ -169,11 +175,11 @@ class Host:
169175
fwversion: str
170176
devmodel: str
171177
netrole: NetRole
172-
loadavg: float
178+
loadavg: float | int | None
173179
totalram: int
174180
freeram: int
175181
temperature: int
176-
cpuload: float
182+
cpuload: float | int | None
177183
height: int | None # Reported none on LiteBeam 5AC
178184

179185
@classmethod
@@ -184,7 +190,7 @@ def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
184190

185191

186192
@dataclass
187-
class Services:
193+
class Services(AirOSDataClass):
188194
"""Leaf definition."""
189195

190196
dhcpc: bool
@@ -195,7 +201,7 @@ class Services:
195201

196202

197203
@dataclass
198-
class Firewall:
204+
class Firewall(AirOSDataClass):
199205
"""Leaf definition."""
200206

201207
iptables: bool
@@ -205,23 +211,23 @@ class Firewall:
205211

206212

207213
@dataclass
208-
class Throughput:
214+
class Throughput(AirOSDataClass):
209215
"""Leaf definition."""
210216

211217
tx: int
212218
rx: int
213219

214220

215221
@dataclass
216-
class ServiceTime:
222+
class ServiceTime(AirOSDataClass):
217223
"""Leaf definition."""
218224

219225
time: int
220226
link: int
221227

222228

223229
@dataclass
224-
class Polling:
230+
class Polling(AirOSDataClass):
225231
"""Leaf definition."""
226232

227233
cb_capacity: int
@@ -238,7 +244,7 @@ class Polling:
238244

239245

240246
@dataclass
241-
class Stats:
247+
class Stats(AirOSDataClass):
242248
"""Leaf definition."""
243249

244250
rx_bytes: int
@@ -250,7 +256,7 @@ class Stats:
250256

251257

252258
@dataclass
253-
class EvmData:
259+
class EvmData(AirOSDataClass):
254260
"""Leaf definition."""
255261

256262
usage: int
@@ -259,7 +265,7 @@ class EvmData:
259265

260266

261267
@dataclass
262-
class Airmax:
268+
class Airmax(AirOSDataClass):
263269
"""Leaf definition."""
264270

265271
actual_priority: int
@@ -274,7 +280,7 @@ class Airmax:
274280

275281

276282
@dataclass
277-
class EthList:
283+
class EthList(AirOSDataClass):
278284
"""Leaf definition."""
279285

280286
ifname: str
@@ -287,38 +293,37 @@ class EthList:
287293

288294

289295
@dataclass
290-
class GPSData:
296+
class GPSData(AirOSDataClass):
291297
"""Leaf definition."""
292298

293-
lat: float | None = None
294-
lon: float | None = None
299+
lat: float | int | None = None
300+
lon: float | int | None = None
295301
fix: int | None = None
296302
sats: int | None = None # LiteAP GPS
297303
dim: int | None = None # LiteAP GPS
298-
dop: float | None = None # LiteAP GPS
299-
alt: float | None = None # LiteAP GPS
304+
dop: float | int | None = None # LiteAP GPS
305+
alt: float | int | None = None # LiteAP GPS
300306
time_synced: int | None = None # LiteAP GPS
301307

302308

303309
@dataclass
304-
class UnmsStatus:
310+
class UnmsStatus(AirOSDataClass):
305311
"""Leaf definition."""
306312

307313
status: int
308314
timestamp: str | None = None
309315

310316

311317
@dataclass
312-
class Remote:
318+
class Remote(AirOSDataClass):
313319
"""Leaf definition."""
314320

315-
age: int
316321
device_id: str
317322
hostname: str
318323
platform: str
319324
version: str
320325
time: str
321-
cpuload: float
326+
cpuload: float | int | None
322327
temperature: int
323328
totalram: int
324329
freeram: int
@@ -351,6 +356,7 @@ class Remote:
351356
mode: WirelessMode | None = None # Investigate why remotes can have no mode set
352357
ip6addr: list[str] | None = None # For v4 only devices
353358
height: int | None = None
359+
age: int | None = None # At least not present on 8.7.11
354360

355361
@classmethod
356362
def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
@@ -360,7 +366,7 @@ def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
360366

361367

362368
@dataclass
363-
class Disconnected:
369+
class Disconnected(AirOSDataClass):
364370
"""Leaf definition for disconnected devices."""
365371

366372
mac: str
@@ -374,7 +380,7 @@ class Disconnected:
374380

375381

376382
@dataclass
377-
class Station:
383+
class Station(AirOSDataClass):
378384
"""Leaf definition for connected/active devices."""
379385

380386
mac: str
@@ -413,7 +419,7 @@ class Station:
413419

414420

415421
@dataclass
416-
class Wireless:
422+
class Wireless(AirOSDataClass):
417423
"""Leaf definition."""
418424

419425
essid: str
@@ -465,7 +471,7 @@ def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
465471

466472

467473
@dataclass
468-
class InterfaceStatus:
474+
class InterfaceStatus(AirOSDataClass):
469475
"""Leaf definition."""
470476

471477
plugged: bool
@@ -486,7 +492,7 @@ class InterfaceStatus:
486492

487493

488494
@dataclass
489-
class Interface:
495+
class Interface(AirOSDataClass):
490496
"""Leaf definition."""
491497

492498
ifname: str
@@ -497,30 +503,30 @@ class Interface:
497503

498504

499505
@dataclass
500-
class ProvisioningMode:
506+
class ProvisioningMode(AirOSDataClass):
501507
"""Leaf definition."""
502508

503509
pass
504510

505511

506512
@dataclass
507-
class NtpClient:
513+
class NtpClient(AirOSDataClass):
508514
"""Leaf definition."""
509515

510516
pass
511517

512518

513519
@dataclass
514-
class GPSMain:
520+
class GPSMain(AirOSDataClass):
515521
"""Leaf definition."""
516522

517-
lat: float
518-
lon: float
523+
lat: float | int | None
524+
lon: float | int | None
519525
fix: int
520526

521527

522528
@dataclass
523-
class Derived:
529+
class Derived(AirOSDataClass):
524530
"""Contain custom data generated by this module."""
525531

526532
mac: str # Base device MAC address (i.e. eth0)
@@ -536,7 +542,7 @@ class Derived:
536542

537543

538544
@dataclass
539-
class AirOS8Data(DataClassDictMixin):
545+
class AirOS8Data(AirOSDataClass):
540546
"""Dataclass for AirOS v8 devices."""
541547

542548
chain_names: list[ChainName]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "airos"
7-
version = "0.2.6"
7+
version = "0.2.7a0"
88
license = "MIT"
99
description = "Ubiquity airOS module(s) for Python 3."
1010
readme = "README.md"

script/generate_ha_fixture.py

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Generate mock airos fixture for testing."""
1+
"""Generate mock airos fixtures for testing."""
22

33
import json
44
import logging
@@ -13,52 +13,53 @@
1313
if project_root_dir not in sys.path:
1414
sys.path.append(project_root_dir)
1515

16+
# NOTE: This assumes the airos module is correctly installed or available in the project path.
17+
# If not, you might need to adjust the import statement.
1618
from airos.airos8 import AirOS, AirOSData # noqa: E402
1719

18-
# Define the path to save the fixture
19-
fixture_dir = os.path.join(os.path.dirname(__file__), "../fixtures")
20-
userdata_dir = os.path.join(os.path.dirname(__file__), "../fixtures/userdata")
21-
new_fixture_path = os.path.join(fixture_dir, "airos_loco5ac_ap-ptp.json")
22-
base_fixture_path = os.path.join(userdata_dir, "loco5ac_ap-ptp.json")
23-
24-
with open(base_fixture_path) as source, open(new_fixture_path, "w") as new:
25-
source_data = json.loads(source.read())
26-
derived_data = AirOS.derived_data(None, source_data)
27-
new_data = AirOSData.from_dict(derived_data)
28-
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
29-
30-
new_fixture_path = os.path.join(fixture_dir, "airos_loco5ac_sta-ptp.json")
31-
base_fixture_path = os.path.join(userdata_dir, "loco5ac_sta-ptp.json")
32-
33-
with open(base_fixture_path) as source, open(new_fixture_path, "w") as new:
34-
source_data = json.loads(source.read())
35-
derived_data = AirOS.derived_data(None, source_data)
36-
new_data = AirOSData.from_dict(derived_data)
37-
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
38-
39-
new_fixture_path = os.path.join(fixture_dir, "airos_mocked_sta-ptmp.json")
40-
base_fixture_path = os.path.join(userdata_dir, "mocked_sta-ptmp.json")
41-
42-
with open(base_fixture_path) as source, open(new_fixture_path, "w") as new:
43-
source_data = json.loads(source.read())
44-
derived_data = AirOS.derived_data(None, source_data)
45-
new_data = AirOSData.from_dict(derived_data)
46-
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
47-
48-
new_fixture_path = os.path.join(fixture_dir, "airos_liteapgps_ap_ptmp_40mhz.json")
49-
base_fixture_path = os.path.join(userdata_dir, "liteapgps_ap_ptmp_40mhz.json")
50-
51-
with open(base_fixture_path) as source, open(new_fixture_path, "w") as new:
52-
source_data = json.loads(source.read())
53-
derived_data = AirOS.derived_data(None, source_data)
54-
new_data = AirOSData.from_dict(derived_data)
55-
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
56-
57-
new_fixture_path = os.path.join(fixture_dir, "airos_nanobeam5ac_sta_ptmp_40mhz.json")
58-
base_fixture_path = os.path.join(userdata_dir, "nanobeam5ac_sta_ptmp_40mhz.json")
59-
60-
with open(base_fixture_path) as source, open(new_fixture_path, "w") as new:
61-
source_data = json.loads(source.read())
62-
derived_data = AirOS.derived_data(None, source_data)
63-
new_data = AirOSData.from_dict(derived_data)
64-
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
20+
21+
def generate_airos_fixtures():
22+
"""Process all (intended) JSON files from the userdata directory to potential fixtures."""
23+
24+
# Define the paths to the directories
25+
fixture_dir = os.path.join(os.path.dirname(__file__), "../fixtures")
26+
userdata_dir = os.path.join(os.path.dirname(__file__), "../fixtures/userdata")
27+
28+
# Ensure the fixture directory exists
29+
os.makedirs(fixture_dir, exist_ok=True)
30+
31+
# Iterate over all files in the userdata_dir
32+
for filename in os.listdir(userdata_dir):
33+
if "mocked" in filename:
34+
continue
35+
if filename.endswith(".json"):
36+
# Construct the full paths for the base and new fixtures
37+
base_fixture_path = os.path.join(userdata_dir, filename)
38+
new_filename = f"airos_{filename}"
39+
new_fixture_path = os.path.join(fixture_dir, new_filename)
40+
41+
_LOGGER.info("Processing '%s'...", filename)
42+
43+
try:
44+
with open(base_fixture_path) as source:
45+
source_data = json.loads(source.read())
46+
47+
derived_data = AirOS.derived_data(None, source_data)
48+
new_data = AirOSData.from_dict(derived_data)
49+
50+
with open(new_fixture_path, "w") as new:
51+
json.dump(new_data.to_dict(), new, indent=2, sort_keys=True)
52+
53+
_LOGGER.info("Successfully created '%s'", new_filename)
54+
55+
except json.JSONDecodeError:
56+
_LOGGER.error("Skipping '%s': Not a valid JSON file.", filename)
57+
except Exception as e:
58+
_LOGGER.error("Error processing '%s': %s", filename, e)
59+
60+
61+
if __name__ == "__main__":
62+
logging.basicConfig(
63+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
64+
)
65+
generate_airos_fixtures()

0 commit comments

Comments
 (0)