|
90 | 90 | BLE_MANUFACTURER_DATA_NO_RPC = { |
91 | 91 | 0x0BA9: bytes([0x01, 0x02, 0x00]) |
92 | 92 | } # Flags without RPC bit |
| 93 | +BLE_MANUFACTURER_DATA_WITH_MAC = { |
| 94 | + 0x0BA9: bytes.fromhex("0105000b30100a70d6c297bacc") |
| 95 | +} # Flags (0x01, 0x05, 0x00), Model (0x0b, 0x30, 0x10), MAC (0x0a, 0x70, 0xd6, 0xc2, 0x97, 0xba, 0xcc) |
| 96 | +# Device WiFi MAC: 70d6c297bacc (little-endian) -> CCBA97C2D670 (reversed to big-endian) |
| 97 | +# BLE MAC is typically device MAC + 2: CCBA97C2D670 + 2 = CC:BA:97:C2:D6:72 |
| 98 | + |
| 99 | +BLE_MANUFACTURER_DATA_WITH_MAC_UNKNOWN_MODEL = { |
| 100 | + 0x0BA9: bytes.fromhex("0105000b99990a70d6c297bacc") |
| 101 | +} # Flags (0x01, 0x05, 0x00), Model (0x0b, 0x99, 0x99) - unknown model ID, MAC (0x0a, 0x70, 0xd6, 0xc2, 0x97, 0xba, 0xcc) |
93 | 102 |
|
94 | 103 | BLE_DISCOVERY_INFO = BluetoothServiceInfoBleak( |
95 | 104 | name="ShellyPlus2PM-C049EF8873E8", |
|
151 | 160 | tx_power=-127, |
152 | 161 | ) |
153 | 162 |
|
| 163 | +BLE_DISCOVERY_INFO_MAC_IN_MANUFACTURER_DATA = BluetoothServiceInfoBleak( |
| 164 | + name="CC:BA:97:C2:D6:72", # BLE address as name (newer devices) |
| 165 | + address="CC:BA:97:C2:D6:72", # BLE address may differ from device MAC |
| 166 | + rssi=-32, |
| 167 | + manufacturer_data=BLE_MANUFACTURER_DATA_WITH_MAC, |
| 168 | + service_uuids=[], |
| 169 | + service_data={}, |
| 170 | + source="local", |
| 171 | + device=generate_ble_device( |
| 172 | + address="CC:BA:97:C2:D6:72", |
| 173 | + name="CC:BA:97:C2:D6:72", |
| 174 | + ), |
| 175 | + advertisement=generate_advertisement_data( |
| 176 | + manufacturer_data=BLE_MANUFACTURER_DATA_WITH_MAC, |
| 177 | + ), |
| 178 | + time=0, |
| 179 | + connectable=True, |
| 180 | + tx_power=-127, |
| 181 | +) |
| 182 | + |
| 183 | +BLE_DISCOVERY_INFO_MAC_UNKNOWN_MODEL = BluetoothServiceInfoBleak( |
| 184 | + name="CC:BA:97:C2:D6:72", # BLE address as name (newer devices) |
| 185 | + address="CC:BA:97:C2:D6:72", # BLE address may differ from device MAC |
| 186 | + rssi=-32, |
| 187 | + manufacturer_data=BLE_MANUFACTURER_DATA_WITH_MAC_UNKNOWN_MODEL, |
| 188 | + service_uuids=[], |
| 189 | + service_data={}, |
| 190 | + source="local", |
| 191 | + device=generate_ble_device( |
| 192 | + address="CC:BA:97:C2:D6:72", |
| 193 | + name="CC:BA:97:C2:D6:72", |
| 194 | + ), |
| 195 | + advertisement=generate_advertisement_data( |
| 196 | + manufacturer_data=BLE_MANUFACTURER_DATA_WITH_MAC_UNKNOWN_MODEL, |
| 197 | + ), |
| 198 | + time=0, |
| 199 | + connectable=True, |
| 200 | + tx_power=-127, |
| 201 | +) |
| 202 | + |
154 | 203 | BLE_DISCOVERY_INFO_NO_DEVICE = BluetoothServiceInfoBleak( |
155 | 204 | name="ShellyPlus2PM-C049EF8873E8", |
156 | 205 | address="00:00:00:00:00:00", # Invalid address that won't be found |
@@ -2057,6 +2106,53 @@ async def test_bluetooth_discovery_invalid_name( |
2057 | 2106 | assert result["reason"] == "invalid_discovery_info" |
2058 | 2107 |
|
2059 | 2108 |
|
| 2109 | +@pytest.mark.usefixtures("mock_zeroconf") |
| 2110 | +async def test_bluetooth_discovery_mac_in_manufacturer_data( |
| 2111 | + hass: HomeAssistant, |
| 2112 | +) -> None: |
| 2113 | + """Test bluetooth discovery with MAC in manufacturer data (newer devices).""" |
| 2114 | + # Inject BLE device so it's available in the bluetooth scanner |
| 2115 | + inject_bluetooth_service_info_bleak( |
| 2116 | + hass, BLE_DISCOVERY_INFO_MAC_IN_MANUFACTURER_DATA |
| 2117 | + ) |
| 2118 | + |
| 2119 | + result = await hass.config_entries.flow.async_init( |
| 2120 | + DOMAIN, |
| 2121 | + data=BLE_DISCOVERY_INFO_MAC_IN_MANUFACTURER_DATA, |
| 2122 | + context={"source": config_entries.SOURCE_BLUETOOTH}, |
| 2123 | + ) |
| 2124 | + |
| 2125 | + # Should successfully extract MAC from manufacturer data |
| 2126 | + assert result["type"] is FlowResultType.FORM |
| 2127 | + assert result["step_id"] == "bluetooth_confirm" |
| 2128 | + # MAC from manufacturer data: 70d6c297bacc (reversed) = CC:BA:97:C2:D6:70 = CCBA97C2D670 |
| 2129 | + # Model ID 0x1030 = Shelly 1 Mini Gen4 |
| 2130 | + # Device name should use model name from model ID: Shelly1MiniGen4-<MAC> |
| 2131 | + assert result["description_placeholders"]["name"] == "Shelly1MiniGen4-CCBA97C2D670" |
| 2132 | + |
| 2133 | + |
| 2134 | +@pytest.mark.usefixtures("mock_zeroconf") |
| 2135 | +async def test_bluetooth_discovery_mac_unknown_model( |
| 2136 | + hass: HomeAssistant, |
| 2137 | +) -> None: |
| 2138 | + """Test bluetooth discovery with MAC but unknown model ID.""" |
| 2139 | + # Inject BLE device so it's available in the bluetooth scanner |
| 2140 | + inject_bluetooth_service_info_bleak(hass, BLE_DISCOVERY_INFO_MAC_UNKNOWN_MODEL) |
| 2141 | + |
| 2142 | + result = await hass.config_entries.flow.async_init( |
| 2143 | + DOMAIN, |
| 2144 | + data=BLE_DISCOVERY_INFO_MAC_UNKNOWN_MODEL, |
| 2145 | + context={"source": config_entries.SOURCE_BLUETOOTH}, |
| 2146 | + ) |
| 2147 | + |
| 2148 | + # Should successfully extract MAC from manufacturer data |
| 2149 | + assert result["type"] is FlowResultType.FORM |
| 2150 | + assert result["step_id"] == "bluetooth_confirm" |
| 2151 | + # MAC from manufacturer data: 70d6c297bacc (reversed) = CC:BA:97:C2:D6:70 = CCBA97C2D670 |
| 2152 | + # Model ID 0x9999 is unknown - should fall back to generic "Shelly-<MAC>" |
| 2153 | + assert result["description_placeholders"]["name"] == "Shelly-CCBA97C2D670" |
| 2154 | + |
| 2155 | + |
2060 | 2156 | @pytest.mark.usefixtures("mock_rpc_device", "mock_zeroconf") |
2061 | 2157 | async def test_bluetooth_discovery_already_configured( |
2062 | 2158 | hass: HomeAssistant, |
|
0 commit comments