diff --git a/.devcontainer.json b/.devcontainer.json index 7fc0f55..a0b4970 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -1,48 +1,53 @@ { - "name": "ludeeus/integration_blueprint", - "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11", - "postCreateCommand": "scripts/setup", - "forwardPorts": [ - 8123, - 5678 - ], - "portsAttributes": { - "8123": { - "label": "Home Assistant" - - //"onAutoForward": "notify" - } - "5678": { - "label": "Debugger" - //"onAutoForward": "notify" - } + "name": "ludeeus/integration_blueprint", + "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11", + "postCreateCommand": "scripts/setup", + "forwardPorts": [8123, 5678], + "mounts": [ + { + "source": "/home/agittins/.ssh/id_ed25519", + "target": "/home/vscode/.ssh/id_ed25519", + "type": "bind" }, - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "github.vscode-pull-request-github", - "ryanluker.vscode-coverage-gutters", - "ms-python.vscode-pylance" - ], - "settings": { - "files.eol": "\n", - "editor.tabSize": 4, - "python.pythonPath": "/usr/bin/python3", - "python.analysis.autoSearchPaths": false, - //"python.linting.pylintEnabled": true, - //"python.linting.enabled": true, - //"python.formatting.provider": "black", - //"python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "editor.formatOnPaste": false, - "editor.formatOnSave": true, - "editor.formatOnType": true, - "files.trimTrailingWhitespace": true - } - } + { + "source": "/home/agittins/.ssh/id_ed25519.pub", + "target": "/home/vscode/.ssh/id_ed25519.pub", + "type": "bind" + } + ], + "portsAttributes": { + "8123": { + "label": "Home Assistant", + "onAutoForward": "silent" }, - "remoteUser": "vscode", - "features": { - "ghcr.io/devcontainers/features/rust:1": {} + "5678": { + "label": "Debugger", + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "github.vscode-pull-request-github", + "ryanluker.vscode-coverage-gutters", + "ms-python.vscode-pylance", + "github.vscode-github-actions" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 4, + "python.pythonPath": "/usr/bin/python3", + "python.analysis.autoSearchPaths": false, + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } } -} \ No newline at end of file + }, + "remoteUser": "vscode", + "features": { + "ghcr.io/devcontainers/features/rust:1": {} + } +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bfcfaa8..27ad912 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,27 +17,27 @@ jobs: - name: Check out repository uses: actions/checkout@v1 -# - name: Download Lokalise CLI -# run: | -# curl -sfL https://raw.githubusercontent.com/lokalise/lokalise-cli-2-go/master/install.sh | sh -# -# - name: Download latest translations with Lokalise -# run: | -# ./bin/lokalise2 \ -# --token "${{ secrets.lokalise_token }}"\ -# --project-id "260939135f7593a05f2b79.75475372" \ -# file download \ -# --format json \ -# --unzip-to /tmp/lokalise \ -# --export-empty-as skip \ -# --export-sort a_z \ -# --original-filenames=false \ -# --bundle-structure %LANG_ISO%.%FORMAT% -# -# - name: Move downloaded translations -# run: | -# mkdir -p "${{ env.BERMUDA_ROOT_DIR }}/translations/ -# cp /tmp/lokalise/* "${{ env.BERMUDA_ROOT_DIR }}/translations/" + # - name: Download Lokalise CLI + # run: | + # curl -sfL https://raw.githubusercontent.com/lokalise/lokalise-cli-2-go/master/install.sh | sh + # + # - name: Download latest translations with Lokalise + # run: | + # ./bin/lokalise2 \ + # --token "${{ secrets.lokalise_token }}"\ + # --project-id "260939135f7593a05f2b79.75475372" \ + # file download \ + # --format json \ + # --unzip-to /tmp/lokalise \ + # --export-empty-as skip \ + # --export-sort a_z \ + # --original-filenames=false \ + # --bundle-structure %LANG_ISO%.%FORMAT% + # + # - name: Move downloaded translations + # run: | + # mkdir -p "${{ env.BERMUDA_ROOT_DIR }}/translations/ + # cp /tmp/lokalise/* "${{ env.BERMUDA_ROOT_DIR }}/translations/" - name: Set release version number in files run: | diff --git a/.vscode/launch.json b/.vscode/launch.json index 27adb82..cc5337a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,4 +31,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index bc29c3f..152aa4a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,4 +9,4 @@ "editor.defaultFormatter": "ms-python.black-formatter" }, "python.formatting.provider": "none" -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f9c43f6..7e2ea33 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -32,4 +32,4 @@ "problemMatcher": [] } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index 0b71a13..cc35ad3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Triangulate your lost objects using ESPHome bluetooth proxies! [![Discord][discord-shield]][discord] [![Community Forum][forum-shield]][forum] -**STATUS: Early days! +##STATUS: Early days! + - Can replace bluetooth_ble_tracker by creating entities for home/not_home for selected BLE devices, which can be used for Person home/away sensing. This is the "Zone" element of homeassistant localisation, where "home" is @@ -43,6 +44,7 @@ measuring the angles, but instead measuring distances. The bottom line is that triangulation is more likely to hit people's search terms. This integration gives you two forms of presence tracking. + - Simple Home/Away detection using the device_tracker integration. This is not much different to the already working bluetooth_le_tracker integration in that regard, but was an easy step along the way to... @@ -51,28 +53,34 @@ This integration gives you two forms of presence tracking. and "where's my phone/toothbrush?" ## FAQ + Isn't mmWave better? -: mmWave is definitely *faster*, but it will only tell you "someone" has entered -a space, while Bermuda can tell you *who* is in a space. + +: mmWave is definitely _faster_, but it will only tell you "someone" has entered +a space, while Bermuda can tell you _who_ is in a space. + What about PIR / Infrared? + : It's also likely faster than bluetooth, but again it only tells you that someone / something is present, but doesn't tell you who/what. So how does that help? + : If the home knows who is in a given room, it can set the thermostat to their personal preferences, or perhaps their lighting settings. This might be particularly useful for testing automations for yourself before unleashing them on to your housemates, so they don't get annoyed while you iron out the bugs :-) + : If you have BLE tags on your pets you can have automations specifically for them, and/or you can exclude certain automations, for example don't trigger a light from an IR sensor if it knows it's just your cat, say. How quickly does it react? + : That will mainly depend on how often your beacon transmits advertisements, however right now the integration only re-calculates on a timed basis. This should be changed to a realtime recalculation based on incoming advertisements soon. - ## What you need - HomeAssistant, with the `bluetooth` integration enabled @@ -97,6 +105,7 @@ bluetooth proxies, and from that tries to make some guesses about how far The plan is to experiment with multiple algorithms to find the best ways to establish a device's location. In the first instace the methods are: + - If a device is close (within a few metres) to a receiver, consider it to be in the same Area as that receiver. (Working) - Attempt to "solve" a 2D map for all beacons and receivers based on the triangles @@ -109,7 +118,7 @@ devices in your home that are sending broadcasts. The implemented results are: (important to note here that VERY FEW of these boxes are ticked yet!) [x] A raw listing of values returned when you call the `bermuda.dump_devices` service - [x] `area` if a device is within a max distance of a receiver +[x] `area` if a device is within a max distance of a receiver [] An interface to choose which devices should have sensors created for them [x] Sensors created for selected devices, showing their estimated location [] Algo to "solve" the 2D layout of devices @@ -128,11 +137,10 @@ devices in your home that are sending broadcasts. The implemented results are: [] Support some way to "pin" more than two proxies/tags, and have it not break. [] Create entities (use `device_tracker`? or create own?) for each detected beacon [] Experiment with some of - [these algo's](https://mdpi-res.com/d_attachment/applsci/applsci-10-02003/article_deploy/applsci-10-02003.pdf?version=1584265508) - for improving accuracy (too much math for me!). Particularly weighting shorter - distances higher and perhaps the cosine similarity fingerprinting, possibly against - fixed beacons as well to smooth environmental rssi fluctuations. - +[these algo's](https://mdpi-res.com/d_attachment/applsci/applsci-10-02003/article_deploy/applsci-10-02003.pdf?version=1584265508) +for improving accuracy (too much math for me!). Particularly weighting shorter +distances higher and perhaps the cosine similarity fingerprinting, possibly against +fixed beacons as well to smooth environmental rssi fluctuations. ## Hacking tips @@ -165,12 +173,11 @@ a fair amount of ESPrescense's wheel. **This component will set up the following platforms.** -| Platform | Description | -| --------------- | ------------------------------------------------------------------------- | -| `binary_sensor` | Nothing yet. | +| Platform | Description | +| --------------- | -------------- | +| `binary_sensor` | Nothing yet. | | `sensor` | Nor here, yet. | -| `switch` | Nope. | - +| `switch` | Nope. | ## Installation @@ -192,7 +199,6 @@ The instructions below are the generic notes from the template: 6. Restart Home Assistant 7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Bermuda BLE Triangulation" - ## Contributions are welcome! @@ -204,7 +210,7 @@ If you want to contribute to this please read the [Contribution guidelines](CONT This project was generated from [@oncleben31](https://github.com/oncleben31)'s [Home Assistant Custom Component Cookiecutter](https://github.com/oncleben31/cookiecutter-homeassistant-custom-component) template. Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [integration_blueprint][integration_blueprint] template -[Cookiecutter User Guide](https://cookiecutter-homeassistant-custom-component.readthedocs.io/en/stable/quickstart.html)** +[Cookiecutter User Guide](https://cookiecutter-homeassistant-custom-component.readthedocs.io/en/stable/quickstart.html)\*\* --- diff --git a/TODO.md b/TODO.md index 32ad563..95f8900 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ [x] DeviceTracker sensors [x] New Area sensors [] Config Flow to define device_trackers - (because dt uses hostname as key which is evil) +(because dt uses hostname as key which is evil) [] Config flow for exposed Area sensors [] Work out how to automate version numbering in manifest, const etc. [] Make first release diff --git a/custom_components/bermuda/__init__.py b/custom_components/bermuda/__init__.py index b2d2223..6ab8837 100644 --- a/custom_components/bermuda/__init__.py +++ b/custom_components/bermuda/__init__.py @@ -22,16 +22,17 @@ from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import slugify -from homeassistant.util.dt import monotonic_time_coarse, now +from homeassistant.util.dt import monotonic_time_coarse +from homeassistant.util.dt import now -#from .const import CONF_PASSWORD -#from .const import CONF_USERNAME from .const import DOMAIN from .const import PLATFORMS from .const import STARTUP_MESSAGE - from .entity import BermudaEntity +# from .const import CONF_PASSWORD +# from .const import CONF_USERNAME + SCAN_INTERVAL = timedelta(seconds=10) MONOTONIC_TIME: Final = monotonic_time_coarse @@ -52,8 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) _LOGGER.info(STARTUP_MESSAGE) - #username = entry.data.get(CONF_USERNAME) - #password = entry.data.get(CONF_PASSWORD) + # username = entry.data.get(CONF_USERNAME) + # password = entry.data.get(CONF_PASSWORD) coordinator = BermudaDataUpdateCoordinator(hass) await coordinator.async_refresh() @@ -73,6 +74,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entry.add_update_listener(async_reload_entry) return True + def rssi_to_metres(rssi): """Convert instant rssi value to a distance in metres @@ -106,12 +108,10 @@ class BermudaDeviceScanner(dict): Note that details on a scanner itself are BermudaDevice instances in their own right. """ + def __init__( - self, - device_address: str, - scandata: BluetoothScannerDevice, - area_id: str - ): + self, device_address: str, scandata: BluetoothScannerDevice, area_id: str + ): self.name = scandata.scanner.name self.area_id = area_id self.adapter = scandata.scanner.adapter @@ -124,8 +124,8 @@ def __init__( def to_dict(self): """Convert class to serialisable dict for dump_devices""" out = {} - for ( var, val) in vars(self).items(): - if var == 'adverts': + for var, val in vars(self).items(): + if var == "adverts": val = {} for ad, thebytes in self.adverts: val[ad] = thebytes.hex() @@ -133,7 +133,6 @@ def to_dict(self): return out - class BermudaDevice(dict): """This class is to represent a single bluetooth "device" tracked by Bermuda. @@ -144,31 +143,32 @@ class BermudaDevice(dict): We're not storing this as an Entity because we don't want all devices to become entities in homeassistant, since there might be a _lot_ of them. """ + def __init__(self): """Initial (empty) data""" self.address = None - self.unique_id = None # mac address formatted. + self.unique_id = None # mac address formatted. self.name = None self.local_name = None - self.prefname = None # "preferred" name - ideally local_name + self.prefname = None # "preferred" name - ideally local_name self.area_id = None self.area_name = None - self.area_distance = None # how far this dev is from that area - self.location = None # home or not_home + self.area_distance = None # how far this dev is from that area + self.location = None # home or not_home self.manufacturer = None self.connectable = False self.is_scanner = False - self.entry_id = None # used for scanner devices - self.send_tracker_see = False # Create/update device_tracker entity - self.create_sensor = False # Create/update a sensor for this device - self.last_seen = 0 # stamp from most recent scanner spotting + self.entry_id = None # used for scanner devices + self.send_tracker_see = False # Create/update device_tracker entity + self.create_sensor = False # Create/update a sensor for this device + self.last_seen = 0 # stamp from most recent scanner spotting self.scanners: dict[str, BermudaDeviceScanner] = {} def to_dict(self): """Convert class to serialisable dict for dump_devices""" out = {} - for ( var, val) in vars(self).items(): - if var == 'scanners': + for var, val in vars(self).items(): + if var == "scanners": scanout = {} for address, scanner in self.scanners.items(): scanout[address] = scanner.to_dict() @@ -214,7 +214,6 @@ class BermudaDataUpdateCoordinator(DataUpdateCoordinator): def __init__( self, hass: HomeAssistant, - ) -> None: """Initialize.""" self.platforms = [] @@ -224,10 +223,8 @@ def __init__( self.ar = area_registry.async_get(hass) # TODO: These settings are to be moved into the config flow - self.max_area_radius = 3.0 # maximum distance to consider "in the area" - self.timeout_not_home = 60 # seconds to wait before declaring "not_home" - - + self.max_area_radius = 3.0 # maximum distance to consider "in the area" + self.timeout_not_home = 60 # seconds to wait before declaring "not_home" hass.services.async_register( DOMAIN, @@ -239,7 +236,6 @@ def __init__( super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - async def _async_update_data(self): """Update data on known devices. @@ -255,7 +251,7 @@ async def _async_update_data(self): # Get/Create a device entry if service_info.address not in self.devices: - #Initialise an empty device + # Initialise an empty device self.devices[service_info.address] = BermudaDevice() device: BermudaDevice = self.devices[service_info.address] @@ -278,17 +274,16 @@ async def _async_update_data(self): device.prefname = service_info.device.name else: # we tried. Fall back to boring... - device.prefname = 'bermuda_' + slugify(service_info.address) + device.prefname = "bermuda_" + slugify(service_info.address) # Work through the scanner entries... for discovered in bluetooth.async_scanner_devices_by_address( self.hass, service_info.address, False ): - if discovered.scanner.source not in self.devices: self._refresh_scanners() - #FIXME: Find a method or request one be added for this + # FIXME: Find a method or request one be added for this # pylint: disable-next=protected-access stamps = discovered.scanner._discovered_device_timestamps scanner_stamp = stamps[service_info.address] @@ -299,15 +294,15 @@ async def _async_update_data(self): device.scanners[discovered.scanner.source] = BermudaDeviceScanner( device.address, discovered, - self.devices[discovered.scanner.source].area_id + self.devices[discovered.scanner.source].area_id, ) - #FIXME: This should be configurable... + # FIXME: This should be configurable... if device.address in [ - "EE:E8:37:9F:6B:54", # infinitime, main watch - "C7:B8:C6:B0:27:11", # pinetime, devwatch - "A4:C1:38:C8:58:91", # bthome thermo, with reed switch - ]: + "EE:E8:37:9F:6B:54", # infinitime, main watch + "C7:B8:C6:B0:27:11", # pinetime, devwatch + "A4:C1:38:C8:58:91", # bthome thermo, with reed switch + ]: device.send_tracker_see = True device.create_sensor = True @@ -342,9 +337,9 @@ async def _send_device_tracker_see(self, device): # Check if the device has been seen recently rightnow = MONOTONIC_TIME() if rightnow - device.last_seen > self.timeout_not_home: - location_name = 'not_home' + location_name = "not_home" else: - location_name = 'home' + location_name = "home" # If mac is set, dt will: # slugify the hostname (if set) or mac, and use that as the dev_id. @@ -353,14 +348,14 @@ async def _send_device_tracker_see(self, device): # So, we will not set mac, but use bermuda_[mac] as dev_id and prefname # for host_name. await self.hass.services.async_call( - domain='device_tracker', - service='see', + domain="device_tracker", + service="see", service_data={ - 'dev_id': 'bermuda_' + slugify(device.address), - #'mac': device.address, - 'host_name': device.prefname, - 'location_name': location_name, - } + "dev_id": "bermuda_" + slugify(device.address), + # 'mac': device.address, + "host_name": device.prefname, + "location_name": location_name, + }, ) def dt_mono_to_datetime(self, stamp): @@ -368,8 +363,6 @@ def dt_mono_to_datetime(self, stamp): age = MONOTONIC_TIME() - stamp return now() - timedelta(seconds=age) - - def _refresh_areas_by_min_distance(self): """Set area for ALL devices based on closest beacon""" for device in self.devices.values(): @@ -383,14 +376,16 @@ def _refresh_area_by_min_distance(self, device: BermudaDevice): for scanner in device.scanners.values(): # whittle down to the closest beacon inside max range - if scanner.rssi_distance < self.max_area_radius: # potential... - if closest_scanner is None \ - or scanner.rssi_distance < closest_scanner.rssi_distance: + if scanner.rssi_distance < self.max_area_radius: # potential... + if ( + closest_scanner is None + or scanner.rssi_distance < closest_scanner.rssi_distance + ): closest_scanner = scanner if closest_scanner is not None: # We found a winner device.area_id = closest_scanner.area_id - areas = self.ar.async_get_area(device.area_id).name # potentially a list. + areas = self.ar.async_get_area(device.area_id).name # potentially a list. if len(areas) == 1: device.area_name = areas[0] else: @@ -403,14 +398,13 @@ def _refresh_area_by_min_distance(self, device: BermudaDevice): device.area_name = None device.area_distance = None - - def _refresh_scanners(self, address = None): + def _refresh_scanners(self, address=None): """Refresh our local list of scanners (BLE Proxies)""" - #FIXME: Really? This can't possibly be a sensible nesting of loops. - for dev_entry in self.hass.data['device_registry'].devices.data.values(): + # FIXME: Really? This can't possibly be a sensible nesting of loops. + for dev_entry in self.hass.data["device_registry"].devices.data.values(): if len(dev_entry.connections) > 0: for dev_connection in dev_entry.connections: - if dev_connection[0] == 'mac': + if dev_connection[0] == "mac": if address is None or address == dev_connection[1]: found_address = dev_connection[1] self.devices[found_address] = BermudaDevice() @@ -424,7 +418,6 @@ def _refresh_scanners(self, address = None): scandev.name = dev_entry.name scandev.is_scanner = True - async def service_dump_devices(self, call): # pylint: disable=unused-argument; """Return a dump of beacon advertisements by receiver""" out = {} @@ -432,6 +425,7 @@ async def service_dump_devices(self, call): # pylint: disable=unused-argument; out[address] = device.to_dict() return out + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle removal of an entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] diff --git a/custom_components/bermuda/binary_sensor.py b/custom_components/bermuda/binary_sensor.py index 71e10b6..91ccff4 100644 --- a/custom_components/bermuda/binary_sensor.py +++ b/custom_components/bermuda/binary_sensor.py @@ -4,14 +4,15 @@ from .const import BINARY_SENSOR from .const import BINARY_SENSOR_DEVICE_CLASS from .const import DEFAULT_NAME -#from .const import DOMAIN from .entity import BermudaEntity +# from .const import DOMAIN + async def async_setup_entry(hass, entry, async_add_devices): """Setup binary_sensor platform.""" - #coordinator = hass.data[DOMAIN][entry.entry_id] - #AJG async_add_devices([BermudaBinarySensor(coordinator, entry)]) + # coordinator = hass.data[DOMAIN][entry.entry_id] + # AJG async_add_devices([BermudaBinarySensor(coordinator, entry)]) class BermudaBinarySensor(BermudaEntity, BinarySensorEntity): diff --git a/custom_components/bermuda/config_flow.py b/custom_components/bermuda/config_flow.py index 098f366..a1276a0 100644 --- a/custom_components/bermuda/config_flow.py +++ b/custom_components/bermuda/config_flow.py @@ -2,13 +2,14 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback -#from homeassistant.helpers.aiohttp_client import async_create_clientsession from .const import CONF_PASSWORD from .const import CONF_USERNAME from .const import DOMAIN from .const import PLATFORMS +# from homeassistant.helpers.aiohttp_client import async_create_clientsession + class BermudaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for bermuda.""" @@ -62,9 +63,9 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen async def _test_credentials(self, username, password): """Return true if credentials is valid.""" try: - #session = async_create_clientsession(self.hass) - #client = BermudaApiClient(username, password, session) - #await client.async_get_data() + # session = async_create_clientsession(self.hass) + # client = BermudaApiClient(username, password, session) + # await client.async_get_data() return True except Exception: # pylint: disable=broad-except pass diff --git a/custom_components/bermuda/const.py b/custom_components/bermuda/const.py index cdaf9af..2d5888a 100644 --- a/custom_components/bermuda/const.py +++ b/custom_components/bermuda/const.py @@ -18,8 +18,8 @@ BINARY_SENSOR = "binary_sensor" SENSOR = "sensor" SWITCH = "switch" -#PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH] -PLATFORMS = [ SENSOR ] +# PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH] +PLATFORMS = [SENSOR] # Configuration and options CONF_ENABLED = "enabled" diff --git a/custom_components/bermuda/entity.py b/custom_components/bermuda/entity.py index 98a8aec..861ff40 100644 --- a/custom_components/bermuda/entity.py +++ b/custom_components/bermuda/entity.py @@ -3,8 +3,8 @@ from typing import TYPE_CHECKING -from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers import area_registry +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ATTRIBUTION from .const import DOMAIN @@ -13,7 +13,9 @@ if TYPE_CHECKING: from . import BermudaDataUpdateCoordinator - #from . import BermudaDevice + + # from . import BermudaDevice + class BermudaEntity(CoordinatorEntity): """Co-ordinator for Bermuda data. @@ -21,17 +23,16 @@ class BermudaEntity(CoordinatorEntity): Gathers the device infor for receivers and transmitters, calculates distances etc. """ + def __init__( - self, - coordinator: BermudaDataUpdateCoordinator, - config_entry, - address: str - ): + self, coordinator: BermudaDataUpdateCoordinator, config_entry, address: str + ): super().__init__(coordinator) self.coordinator = coordinator self.config_entry = config_entry self._device = coordinator.devices[address] self.ar = area_registry.async_get(coordinator.hass) + @property def unique_id(self): """Return a unique ID to use for this entity.""" diff --git a/custom_components/bermuda/manifest.json b/custom_components/bermuda/manifest.json index b3348b4..91496f6 100644 --- a/custom_components/bermuda/manifest.json +++ b/custom_components/bermuda/manifest.json @@ -1,19 +1,12 @@ { "domain": "bermuda", "name": "Bermuda BLE Triangulation", - "codeowners": [ - "@agittins" - ], + "codeowners": ["@agittins"], "config_flow": true, - "dependencies": [ - "bluetooth_adapters", - "device_tracker" - ], + "dependencies": ["bluetooth_adapters", "device_tracker"], "documentation": "https://github.com/agittins/bermuda", "iot_class": "calculated", "issue_tracker": "https://github.com/agittins/bermuda/issues", - "requirements": [ - "pandas" - ], + "requirements": ["pandas"], "version": "0.0.1" -} \ No newline at end of file +} diff --git a/custom_components/bermuda/sensor.py b/custom_components/bermuda/sensor.py index 61acf0f..71d89d4 100644 --- a/custom_components/bermuda/sensor.py +++ b/custom_components/bermuda/sensor.py @@ -1,24 +1,24 @@ """Sensor platform for Bermuda BLE Triangulation.""" - from collections.abc import Mapping from typing import Any -from homeassistant import config_entries +from homeassistant import config_entries from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -#from .const import DEFAULT_NAME +from . import BermudaDataUpdateCoordinator from .const import DOMAIN -#from .const import ICON -#from .const import SENSOR from .entity import BermudaEntity -from . import BermudaDataUpdateCoordinator +# from .const import DEFAULT_NAME +# from .const import ICON +# from .const import SENSOR + async def async_setup_entry( hass: HomeAssistant, entry: config_entries.ConfigEntry, - async_add_devices: AddEntitiesCallback + async_add_devices: AddEntitiesCallback, ) -> None: """Setup sensor platform.""" coordinator: BermudaDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] @@ -28,9 +28,10 @@ async def async_setup_entry( for device in coordinator.devices.values(): if device.create_sensor: entities.append(BermudaSensor(coordinator, entry, device.address)) - #async_add_devices([BermudaSensor(coordinator, entry)]) + # async_add_devices([BermudaSensor(coordinator, entry)]) async_add_devices(entities, True) + class BermudaSensor(BermudaEntity): """bermuda Sensor class.""" @@ -49,8 +50,8 @@ def state(self): # return self.coordinator.data.get("body") return self._device.area_name - #@property - #def icon(self): + # @property + # def icon(self): # """Return the icon of the sensor.""" # return ICON @@ -62,8 +63,8 @@ def device_class(self): @property def extra_state_attributes(self) -> Mapping[str, Any] | None: return { - 'last_seen': self.coordinator.dt_mono_to_datetime(self._device.last_seen), - 'area_id': self._device.area_id, - 'area_name': self._device.area_name, - 'area_distance': self._device.area_distance, + "last_seen": self.coordinator.dt_mono_to_datetime(self._device.last_seen), + "area_id": self._device.area_id, + "area_name": self._device.area_name, + "area_distance": self._device.area_distance, } diff --git a/custom_components/bermuda/services.yaml b/custom_components/bermuda/services.yaml index abfd9ee..fe6c28f 100644 --- a/custom_components/bermuda/services.yaml +++ b/custom_components/bermuda/services.yaml @@ -1,3 +1,3 @@ dump_devices: name: Dump devices - description: Returns the internal structure of devices, useful for debugging or building templates etc. \ No newline at end of file + description: Returns the internal structure of devices, useful for debugging or building templates etc. diff --git a/custom_components/bermuda/switch.py b/custom_components/bermuda/switch.py index 7e135ca..992a832 100644 --- a/custom_components/bermuda/switch.py +++ b/custom_components/bermuda/switch.py @@ -2,16 +2,17 @@ from homeassistant.components.switch import SwitchEntity from .const import DEFAULT_NAME -#from .const import DOMAIN from .const import ICON from .const import SWITCH from .entity import BermudaEntity +# from .const import DOMAIN + async def async_setup_entry(hass, entry, async_add_devices): """Setup sensor platform.""" - #coordinator = hass.data[DOMAIN][entry.entry_id] - #AJG async_add_devices([BermudaBinarySwitch(coordinator, entry)]) + # coordinator = hass.data[DOMAIN][entry.entry_id] + # AJG async_add_devices([BermudaBinarySwitch(coordinator, entry)]) class BermudaBinarySwitch(BermudaEntity, SwitchEntity): @@ -19,13 +20,13 @@ class BermudaBinarySwitch(BermudaEntity, SwitchEntity): async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument """Turn on the switch.""" - #await self.coordinator.api.async_set_title("bar") - #await self.coordinator.async_request_refresh() + # await self.coordinator.api.async_set_title("bar") + # await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument """Turn off the switch.""" - #await self.coordinator.api.async_set_title("foo") - #await self.coordinator.async_request_refresh() + # await self.coordinator.api.async_set_title("foo") + # await self.coordinator.async_request_refresh() @property def name(self): diff --git a/custom_components/bermuda/translations/en.json b/custom_components/bermuda/translations/en.json index f95a46d..e0414b0 100644 --- a/custom_components/bermuda/translations/en.json +++ b/custom_components/bermuda/translations/en.json @@ -30,8 +30,8 @@ }, "services": { "dump_beacons": { - "name": "Dump Beacons", - "description": "Returns all detected advertisements and their rssi for each scanner" + "name": "Dump Devices", + "description": "Returns all detected advertisements, scanners and their rssi for each scanner" } } -} \ No newline at end of file +} diff --git a/hacs.json b/hacs.json index 82af4c4..10726b6 100644 --- a/hacs.json +++ b/hacs.json @@ -4,4 +4,4 @@ "iot_class": "local_polling", "homeassistant": "2022.10", "render_readme": true -} \ No newline at end of file +} diff --git a/requirements.txt b/requirements.txt index 381c132..2a6da42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ colorlog==6.7.0 homeassistant>=2023.8.1 pip>=23.2.1,<23.3 -ruff==0.0.282 \ No newline at end of file +ruff==0.0.282 diff --git a/requirements_test.txt b/requirements_test.txt index 170688a..e3ce4e9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,5 @@ -r requirements.txt pytest-homeassistant-custom-component #==0.13.50 -pre-commit \ No newline at end of file +pre-commit +reorder-python-imports diff --git a/scripts/develop b/scripts/develop index 20366e8..89eda50 100755 --- a/scripts/develop +++ b/scripts/develop @@ -17,4 +17,4 @@ fi export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components" # Start Home Assistant -hass --config "${PWD}/config" --debug \ No newline at end of file +hass --config "${PWD}/config" --debug diff --git a/scripts/lint b/scripts/lint index 752d23a..c45b470 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,4 +4,6 @@ set -e cd "$(dirname "$0")/.." -ruff check . --fix \ No newline at end of file +#ruff check . --fix + +pre-commit run --all-files diff --git a/scripts/setup b/scripts/setup index 46be32f..e9d3353 100755 --- a/scripts/setup +++ b/scripts/setup @@ -9,4 +9,5 @@ python3 -m pip install --upgrade setuptools wheel # Fix urllib3 issue https://github.com/home-assistant/core/issues/95192 python3 -m pip install git+https://github.com/boto/botocore -python3 -m pip install --requirement requirements.txt \ No newline at end of file +python3 -m pip install --requirement requirements.txt +python3 -m pip install --requirement requirements_test.txt diff --git a/scripts/test b/scripts/test index bcda0d1..9ce1411 100755 --- a/scripts/test +++ b/scripts/test @@ -6,4 +6,4 @@ source venv/bin/activate # Install requirements pip install -r requirements_test.txt # Run tests and get a summary of successes/failures and code coverage -pytest --durations=10 --cov-report term-missing --cov=custom_components.bermuda tests \ No newline at end of file +pytest --durations=10 --cov-report term-missing --cov=custom_components.bermuda tests diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 15c909b..dab1f4a 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -30,7 +30,10 @@ @pytest.fixture(autouse=True) def bypass_setup_fixture(): """Prevent setup.""" - with patch("custom_components.bermuda.async_setup", return_value=True,), patch( + with patch( + "custom_components.bermuda.async_setup", + return_value=True, + ), patch( "custom_components.bermuda.async_setup_entry", return_value=True, ):