1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
"""List of airOS products."""
from .exceptions import AirOSMultipleMatchesFoundException
# Generated list from https://store.ui.com/us/en/category/wireless
SITE_MODELS: dict[str, str] = {
"Wave MLO5": "Wave-MLO5",
"airMAX Rocket Prism 5AC": "RP-5AC-Gen2",
"airFiber 5XHD": "AF-5XHD",
"airMAX Lite AP GPS": "LAP-GPS",
"airMAX PowerBeam 5AC": "PBE-5AC-Gen2",
"airMAX PowerBeam 5AC ISO": "PBE-5AC-ISO-Gen2",
"airMAX PowerBeam 5AC 620": "PBE-5AC-620",
"airMAX LiteBeam 5AC": "LBE-5AC-GEN2",
"airMAX LiteBeam 5AC Long-Range": "LBE-5AC-LR",
"airMAX NanoBeam 5AC": "NBE-5AC-GEN2",
"airMAX NanoStation 5AC": "NS-5AC",
"airMAX NanoStation 5AC Loco": "Loco5AC",
"LTU Rocket": "LTU-Rocket",
"LTU Instant (5-pack)": "LTU-Instant",
"LTU Pro": "LTU-PRO",
"LTU Long-Range": "LTU-LR",
"LTU Extreme-Range": "LTU-XR",
"airMAX NanoBeam 2AC": "NBE-2AC-13",
"airMAX PowerBeam 2AC 400": "PBE-2AC-400",
"airMAX Rocket AC Lite": "R5AC-LITE",
"airMAX LiteBeam M5": "LBE-M5-23",
"airMAX PowerBeam 5AC 500": "PBE-5AC-500",
"airMAX PrismStation 5AC": "PS-5AC",
"airMAX IsoStation 5AC": "IS-5AC",
"airMAX Lite AP": "LAP-120",
"airMAX PowerBeam M5 400": "PBE-M5-400",
"airMAX PowerBeam M5 300 ISO": "PBE-M5-300-ISO",
"airMAX PowerBeam M5 300": "PBE-M5-300",
"airMAX PowerBeam M2 400": "PBE-M2-400",
"airMAX Bullet AC": "B-DB-AC",
"airMAX Bullet AC IP67": "BulletAC-IP67",
"airMAX Bullet M2": "BulletM2-HP",
"airMAX IsoStation M5": "IS-M5",
"airMAX NanoStation M5": "NSM5",
"airMAX NanoStation M5 loco": "LocoM5",
"airMAX NanoStation M2 loco": "LocoM2",
"UISP Horn": "UISP-Horn",
"UISP Dish": "UISP-Dish",
"UISP Dish Mini": "UISP-Dish-Mini",
"airMAX AC 5 GHz, 31 dBi RocketDish": "RD-5G31-AC",
"airMAX 5 GHz, 30 dBi RocketDish LW": "RD-5G30-LW",
"airMAX AC 5 GHz, 30/34 dBi RocketDish": "RD-5G",
"airPRISM 3x30° HD Sector": "AP-5AC-90-HD",
"airMAX 5 GHz, 16/17 dBi Sector": "AM-5G1",
"airMAX PrismStation Horn": "Horn-5",
"airMAX 5 GHz, 10 dBi Omni": "AMO-5G10",
"airMAX 5 GHz, 13 dBi, Omni": "AMO-5G13",
"airMAX Sector 2.4 GHz Titanium": "AM-V2G-Ti",
"airMAX AC 5 GHz, 21 dBi, 60º Sector": "AM-5AC21-60",
"airMAX AC 5 GHz, 22 dBi, 45º Sector": "AM-5AC22-45",
"airMAX 2.4 GHz, 16 dBi, 90º Sector": "AM-2G16-90",
"airMAX 900 MHz, 13 dBi, 120º Sector": "AM-9M13-120",
"airMAX 900 MHz, 16 dBi Yagi": "AMY-9M16x2",
"airMAX NanoBeam M5": "NBE-M5-16",
"airMAX Rocket Prism 2AC": "R2AC-PRISM",
"airFiber 5 Mid-Band": "AF-5",
"airFiber 5 High-Band": "AF-5U",
"airFiber 24": "AF-24",
"airFiber 24 Hi-Density": "AF-24HD",
"airFiber 2X": "AF-2X",
"airFiber 11": "AF-11",
"airFiber 11 Low-Band Backhaul Radio with Dish Antenna": "AF11-Complete-LB",
"airFiber 11 High-Band Backhaul Radio with Dish Antenna": "AF11-Complete-HB",
"airMAX LiteBeam 5AC Extreme-Range": "LBE-5AC-XR",
"airMAX PowerBeam M5 400 ISO": "PBE-M5-400-ISO",
"airMAX NanoStation M2": "NSM2",
"airFiber X 5 GHz, 23 dBi, Slant 45": "AF-5G23-S45",
"airFiber X 5 GHz, 30 dBi, Slant 45": "AF-5G30-S45",
"airFiber X 5 GHz, 34 dBi, Slant 45": "AF-5G34-S45",
"airMAX 5 GHz, 19/20 dBi Sector": "AM-5G2",
"airMAX 2.4 GHz, 10 dBi Omni": "AMO-2G10",
"airMAX 2.4 GHz, 15 dBi, 120º Sector": "AM-2G15-120",
}
# Manually added entries for common unofficial names
# When adding a LiteBeam: update tests/test_model_map.py assert for count as well
MANUAL_MODELS: dict[str, str] = {
"LiteAP AC": "LAP-120", # Shortened name for airMAX Lite Access Point AC, Issue 137
"LiteAP GPS": "LAP-GPS", # Shortened name for airMAX Lite Access Point GPS
"LiteBeam 5AC 23": "LBE-5AC-23",
"NanoStation loco M5": "LocoM5", # XM firmware version 6 - note the reversed names
}
MODELS: dict[str, str] = {**SITE_MODELS, **MANUAL_MODELS}
class UispAirOSProductMapper:
"""Utility class to map product model names to SKUs and vice versa."""
def __init__(self) -> None:
"""Provide reversed map for SKUs."""
self._SKUS = {v: k for k, v in MODELS.items()}
def get_sku_by_devmodel(self, devmodel: str) -> str:
"""Retrieves the SKU for a given device model name."""
devmodel = devmodel.strip()
if devmodel in MODELS:
return MODELS[devmodel]
match_key: str | None = None
matches_found: int = 0
best_match_key: str | None = None
best_match_is_prefix = False
lower_devmodel = devmodel.lower()
for model_name in MODELS:
lower_model_name = model_name.lower()
if lower_model_name.endswith(lower_devmodel):
if not best_match_is_prefix or len(lower_model_name) == len(
lower_devmodel
):
best_match_key = model_name
best_match_is_prefix = True
matches_found = 1
match_key = model_name
else:
matches_found += 1
best_match_key = None
elif not best_match_is_prefix and lower_devmodel in lower_model_name:
matches_found += 1
match_key = model_name
if best_match_key and best_match_is_prefix and matches_found == 1:
# If a unique prefix match was found ("LiteBeam 5AC" -> "airMAX LiteBeam 5AC")
return MODELS[best_match_key]
if best_match_key and best_match_is_prefix and matches_found > 1:
pass # fall through exception
if match_key is None or matches_found == 0:
raise KeyError(f"No product found for devmodel: {devmodel}")
if match_key and matches_found == 1:
return MODELS[match_key]
raise AirOSMultipleMatchesFoundException(
f"Partial model '{devmodel}' matched multiple ({matches_found}) products."
)
def get_devmodel_by_sku(self, sku: str) -> str:
"""Retrieves the full device model name for an exact SKU match."""
if sku in self._SKUS:
return self._SKUS[sku]
raise KeyError(f"No product found for SKU: {sku}")
|