File: model_map.py

package info (click to toggle)
python-airos 0.6.4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 704 kB
  • sloc: python: 2,967; sh: 19; makefile: 3
file content (154 lines) | stat: -rw-r--r-- 6,244 bytes parent folder | download
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}")