Fix Fully Kiosk Browser merging entities when MAC empty (#87743)

* Fix FKB merging entities when MAC empty

Fully Kiosk Browser will sometimes return an empty MAC address in the
device_info API call. When two Fully Kiosk devices are added with
missing MAC addresses, HA will assume the two devices are the same and
merge the entities. This fixes that problem.

Fixes #77722

* Update homeassistant/components/fully_kiosk/entity.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Mike Heath 2023-02-10 01:19:09 -07:00 committed by GitHub
parent 3057dd241a
commit b5dfd83c46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 249 additions and 4 deletions

View File

@ -17,12 +17,16 @@ class FullyKioskEntity(CoordinatorEntity[FullyKioskDataUpdateCoordinator], Entit
def __init__(self, coordinator: FullyKioskDataUpdateCoordinator) -> None:
"""Initialize the Fully Kiosk Browser entity."""
super().__init__(coordinator=coordinator)
self._attr_device_info = DeviceInfo(
device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.data["deviceID"])},
name=coordinator.data["deviceName"],
manufacturer=coordinator.data["deviceManufacturer"],
model=coordinator.data["deviceModel"],
sw_version=coordinator.data["appVersionName"],
configuration_url=f"http://{coordinator.data['ip4']}:2323",
connections={(CONNECTION_NETWORK_MAC, coordinator.data["Mac"])},
)
if "Mac" in coordinator.data and coordinator.data["Mac"]:
device_info["connections"] = {
(CONNECTION_NETWORK_MAC, coordinator.data["Mac"])
}
self._attr_device_info = device_info

View File

@ -0,0 +1,79 @@
{
"deviceName": "Test Kiosk 1",
"batteryLevel": 100,
"isPlugged": true,
"SSID": "\"freewifi\"",
"Mac": "",
"ip4": "192.168.1.1",
"ip6": "FE80::1874:2EFF:FEA2:1111",
"hostname4": "192.168.1.1",
"hostname6": "fe80::1874:2eff:fea2:1111%p2p0",
"wifiSignalLevel": 7,
"isMobileDataEnabled": true,
"screenOrientation": 90,
"screenBrightness": 9,
"screenLocked": false,
"screenOn": true,
"batteryTemperature": 27,
"plugged": true,
"keyguardLocked": false,
"locale": "en_US",
"serial": "ABCDEF1234567890",
"build": "cm_douglas-userdebug 5.1.1 LMY49M 731a881f9d test-keys",
"androidVersion": "5.1.1",
"webviewUA": "Mozilla/5.0 (Linux; Android 5.1.1; KFDOWI Build/LMY49M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.117 Safari/537.36",
"motionDetectorStatus": 0,
"isDeviceAdmin": true,
"isDeviceOwner": false,
"internalStorageFreeSpace": 11675512832,
"internalStorageTotalSpace": 12938534912,
"ramUsedMemory": 1077755904,
"ramFreeMemory": 362373120,
"ramTotalMemory": 1440129024,
"appUsedMemory": 24720592,
"appFreeMemory": 59165440,
"appTotalMemory": 83886080,
"displayHeightPixels": 800,
"displayWidthPixels": 1280,
"isMenuOpen": false,
"topFragmentTag": "",
"isInDaydream": false,
"isRooted": true,
"isLicensed": true,
"isInScreensaver": false,
"kioskLocked": true,
"isInForcedSleep": false,
"maintenanceMode": false,
"kioskMode": true,
"startUrl": "https://homeassistant.local",
"currentTabIndex": 0,
"mqttConnected": true,
"deviceID": "abcdef-111111",
"appVersionCode": 875,
"appVersionName": "1.42.5",
"androidSdk": 22,
"deviceModel": "KFDOWI",
"deviceManufacturer": "amzn",
"foregroundApp": "de.ozerov.fully",
"currentPage": "https://homeassistant.local",
"lastAppStart": "8/13/2022 1:00:47 AM",
"sensorInfo": [
{
"type": 8,
"name": "PROXIMITY",
"vendor": "MTK",
"version": 1,
"accuracy": -1
},
{
"type": 5,
"name": "LIGHT",
"vendor": "MTK",
"version": 1,
"accuracy": 3,
"values": [0, 0, 0],
"lastValuesTime": 1660435566561,
"lastAccuracyTime": 1660366847543
}
]
}

View File

@ -0,0 +1,79 @@
{
"deviceName": "Test Kiosk 2",
"batteryLevel": 100,
"isPlugged": true,
"SSID": "\"freewifi\"",
"Mac": "",
"ip4": "192.168.1.2",
"ip6": "FE80::1874:2EFF:FEA2:2222",
"hostname4": "192.168.1.2",
"hostname6": "fe80::1874:2eff:fea2:2222%p2p0",
"wifiSignalLevel": 7,
"isMobileDataEnabled": true,
"screenOrientation": 90,
"screenBrightness": 9,
"screenLocked": false,
"screenOn": true,
"batteryTemperature": 27,
"plugged": true,
"keyguardLocked": false,
"locale": "en_US",
"serial": "ABCDEF1234567890",
"build": "cm_douglas-userdebug 5.1.1 LMY49M 731a881f9d test-keys",
"androidVersion": "5.1.1",
"webviewUA": "Mozilla/5.0 (Linux; Android 5.1.1; KFDOWI Build/LMY49M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.117 Safari/537.36",
"motionDetectorStatus": 0,
"isDeviceAdmin": true,
"isDeviceOwner": false,
"internalStorageFreeSpace": 11675512832,
"internalStorageTotalSpace": 12938534912,
"ramUsedMemory": 1077755904,
"ramFreeMemory": 362373120,
"ramTotalMemory": 1440129024,
"appUsedMemory": 24720592,
"appFreeMemory": 59165440,
"appTotalMemory": 83886080,
"displayHeightPixels": 800,
"displayWidthPixels": 1280,
"isMenuOpen": false,
"topFragmentTag": "",
"isInDaydream": false,
"isRooted": true,
"isLicensed": true,
"isInScreensaver": false,
"kioskLocked": true,
"isInForcedSleep": false,
"maintenanceMode": false,
"kioskMode": true,
"startUrl": "https://homeassistant.local",
"currentTabIndex": 0,
"mqttConnected": true,
"deviceID": "abcdef-222222",
"appVersionCode": 875,
"appVersionName": "1.42.5",
"androidSdk": 22,
"deviceModel": "KFDOWI",
"deviceManufacturer": "amzn",
"foregroundApp": "de.ozerov.fully",
"currentPage": "https://homeassistant.local",
"lastAppStart": "8/13/2022 1:00:47 AM",
"sensorInfo": [
{
"type": 8,
"name": "PROXIMITY",
"vendor": "MTK",
"version": 1,
"accuracy": -1
},
{
"type": 5,
"name": "LIGHT",
"vendor": "MTK",
"version": 1,
"accuracy": 3,
"values": [0, 0, 0],
"lastValuesTime": 1660435566561,
"lastAccuracyTime": 1660366847543
}
]
}

View File

@ -1,15 +1,18 @@
"""Tests for the Fully Kiosk Browser integration."""
import asyncio
from unittest.mock import MagicMock
import json
from unittest.mock import MagicMock, patch
from fullykiosk import FullyKioskError
import pytest
from homeassistant.components.fully_kiosk.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, load_fixture
async def test_load_unload_config_entry(
@ -51,3 +54,83 @@ async def test_config_entry_not_ready(
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def _load_config(
hass: HomeAssistant,
config_entry: MockConfigEntry,
device_info_fixture: str,
) -> None:
with patch(
"homeassistant.components.fully_kiosk.coordinator.FullyKiosk",
autospec=True,
) as client_mock:
client = client_mock.return_value
client.getDeviceInfo.return_value = json.loads(
load_fixture(device_info_fixture, DOMAIN)
)
client.getSettings.return_value = json.loads(
load_fixture("listsettings.json", DOMAIN)
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
async def test_multiple_kiosk_with_empty_mac(
hass: HomeAssistant,
) -> None:
"""Test that multiple kiosk devices with empty MAC don't get merged."""
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
config_entry1 = MockConfigEntry(
title="Test device 1",
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.1",
CONF_PASSWORD: "mocked-password",
CONF_MAC: "",
},
unique_id="111111",
)
await _load_config(hass, config_entry1, "deviceinfo_empty_mac1.json")
assert len(device_registry.devices) == 1
config_entry2 = MockConfigEntry(
title="Test device 2",
domain=DOMAIN,
data={
CONF_HOST: "127.0.0.2",
CONF_PASSWORD: "mocked-password",
CONF_MAC: "",
},
unique_id="22222",
)
await _load_config(hass, config_entry2, "deviceinfo_empty_mac2.json")
assert len(device_registry.devices) == 2
state1 = hass.states.get("sensor.test_kiosk_1_battery")
assert state1
state2 = hass.states.get("sensor.test_kiosk_2_battery")
assert state2
entry1 = entity_registry.async_get("sensor.test_kiosk_1_battery")
assert entry1
assert entry1.unique_id == "abcdef-111111-batteryLevel"
entry2 = entity_registry.async_get("sensor.test_kiosk_2_battery")
assert entry2
assert entry2.unique_id == "abcdef-222222-batteryLevel"
assert entry1.device_id != entry2.device_id
device1 = device_registry.async_get(entry1.device_id)
assert device1
device2 = device_registry.async_get(entry2.device_id)
assert device2
assert device1 != device2