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