Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

Commit

Permalink
Merge pull request #28 from zha-ng/dev
Browse files Browse the repository at this point in the history
0.3.0 Release
  • Loading branch information
Adminiuga committed Sep 23, 2020
2 parents fcad4a7 + 7d2ebd4 commit 6af95c9
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 132 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/hassfest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Validate with hassfest

on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- uses: home-assistant/actions/hassfest@master
119 changes: 29 additions & 90 deletions custom_components/zha_map/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def mkdir(dir):

async def setup_scanner(_now):
async_track_time_interval(hass, builder.time_tracker, AWAKE_INTERVAL)
await builder.time_tracker()
await builder.preempt_build()

async_call_later(hass, CONFIG_INITIAL_SCAN_DELAY, setup_scanner)

Expand Down Expand Up @@ -101,7 +101,6 @@ def __init__(self, hass, zha_gw):
self._in_process = None
self._seen = {}
self._current = {}
self._failed = {}
self._timestamp = 0

@property
Expand All @@ -116,71 +115,35 @@ def timestamp(self):

async def time_tracker(self, time=None):
"""Awake periodically."""
if self._in_process and not self._in_process.done():
return
self._in_process = self._hass.async_create_task(self.build())
await self.build()

async def preempt_build(self):
"""Start a new scan, preempting the current one in progress."""
if self._in_process and not self._in_process.done():
self.debug("Cancelling a neighbour scan in progress")
self._in_process.cancel()
self._in_process = self._hass.async_create_task(self.build())
await self.build()
self._in_process = self._hass.async_create_task(self.zigpy_scan_wrapper())

async def zigpy_scan_wrapper(self) -> None:
"""Scan zigpy then build."""
await self._app.application_controller.topology.scan()
await self.build()

async def build(self):
self._seen.clear()
self._failed.clear()

seed = self._app.application_controller.get_device(nwk=0x0000)
self.debug("Building topology starting from coordinator")
try:
await self.scan_device(seed)
except zigpy_exc.ZigbeeException as exc:
self.error("failed to scan %s device: %s", seed.ieee, exc)
return

pending = self._pending()
while pending:
for nei in pending:
try:
await nei.scan()
except (zigpy_exc.ZigbeeException, asyncio.TimeoutError):
self.warning("Couldn't scan %s neighbours", nei.ieee)
self._failed[nei.ieee] = nei
nei.offline = True
continue
await self.process_neighbour_table(nei)
pending = self._pending()
self.debug("Building topology")
for device in self._app.application_controller.devices.values():
nei = await Neighbour.scan_device(device)
self._seen[nei.ieee] = nei
if device.node_desc.logical_type in (0, 1):
await self.save_neighbours(nei)

await self.sanity_check()
self._current = {**self._seen}
self._timestamp = time.time()

def _pending(self):
"""Return neighbours still pending a scan."""
pending = [
n
for n in self._seen.values()
if not n.neighbours
and n.supported
and n.device is not None
and n.device_type
in (NeighbourType.Coordinator.name, NeighbourType.Router.name)
and n.ieee not in self._failed
]

if pending:
self.debug(
"continuing neighbour scan. Neighbours discovered: %s",
[n.ieee for n in pending],
)
else:
self.debug(
"Finished neighbour scan pass. Failed: %s",
[k for k in self._failed.keys()],
)
return pending

async def sanity_check(self):
"""Check discovered neighbours vs Zigpy database."""
# do we have extra neighbours
Expand All @@ -195,44 +158,20 @@ async def sanity_check(self):
if dev.ieee in self._seen:
continue

if dev.ieee in self._failed:
self.debug(
(
"%s (%s %s) was discovered in the neighbours "
"tables, but did not respond"
),
dev.ieee,
dev.manufacturer,
dev.model,
)
else:
self.debug(
"%s (%s %s) was not found in the neighbours tables",
dev.ieee,
dev.manufacturer,
dev.model,
)
nei = Neighbour(dev.ieee, f"0x{dev.nwk:04x}", "unk")
nei.device = dev
nei.model = dev.model
nei.manufacturer = dev.manufacturer
nei.offline = True
if dev.node_desc.logical_type is not None:
nei.device_type = dev.node_desc.logical_type.name
self._seen[dev.ieee] = nei

async def scan_device(self, device):
"""Scan device neigbours."""
nei = await Neighbour.scan_device(device)
await self.process_neighbour_table(nei)

async def process_neighbour_table(self, nei):
for entry in nei.neighbours:
if entry.ieee in self._seen:
continue
self.debug("Adding %s to all neighbours", entry.ieee)
self._seen[entry.ieee] = entry
await self.save_neighbours(nei)
self.debug(
"%s (%s %s) was not found in the neighbours tables",
dev.ieee,
dev.manufacturer,
dev.model,
)
nei = Neighbour(dev.ieee, f"0x{dev.nwk:04x}", "unk")
nei.device = dev
nei.model = dev.model
nei.manufacturer = dev.manufacturer
nei.offline = True
if dev.node_desc.logical_type is not None:
nei.device_type = dev.node_desc.logical_type.name
self._seen[dev.ieee] = nei

async def save_neighbours(self, nei):
suffix = str(nei.ieee).replace(":", "")
Expand Down
56 changes: 14 additions & 42 deletions custom_components/zha_map/neighbour.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import asyncio
import enum
import logging
import random

import attr
import zigpy.zdo.types as zdo_t
from zigpy.exceptions import DeliveryError
from zigpy.util import retryable

from .helpers import LogMixin

Expand Down Expand Up @@ -49,7 +45,10 @@ def new_from_record(cls, record):

r.device_type = record.struct.device_type.name
r.rx_on_when_idle = record.struct.rx_on_when_idle.name
r.relation = record.struct.relationship.name
if record.struct.relationship == zdo_t.Neighbor.RelationShip.NoneOfTheAbove:
r.relation = "None_of_the_above"
else:
r.relation = record.struct.relationship.name
r.new_joins_accepted = record.permit_joining.name
r.depth = record.depth
r.lqi = record.lqi
Expand All @@ -62,6 +61,7 @@ def _update_info(self):
self.nwk = "0x{:04x}".format(self.device.nwk)
self.model = self.device.model
self.manufacturer = self.device.manufacturer
self.device_type = self.device.node_desc.logical_type.name

@classmethod
async def scan_device(cls, device):
Expand All @@ -77,43 +77,15 @@ async def scan_device(cls, device):

async def scan(self):
"""Scan for neighbours."""
idx = 0
while True:
status, val = await self.device.zdo.Mgmt_Lqi_req(idx, tries=3, delay=1)
self.debug("neighbor request Status: %s. Response: %r", status, val)
if zdo_t.Status.SUCCESS != status:
self.supported = False
self.debug("device does not support 'Mgmt_Lqi_req'")
return

neighbors = val.neighbor_table_list
for neighbor in neighbors:
new = self.new_from_record(neighbor)

if repr(new.ieee) in (
"00:00:00:00:00:00:00:00",
"ff:ff:ff:ff:ff:ff:ff:ff",
):
self.debug("Ignoring invalid neighbour: %s", new.ieee)
idx += 1
continue

try:
new.device = self.device.application.get_device(new.ieee)
new._update_info()
except KeyError:
self.warning("neighbour %s is not in 'zigbee.db'", new.ieee)
self.neighbours.append(new)
idx += 1
if idx >= val.entries:
break
if len(neighbors) <= 0:
idx += 1
self.debug("Neighbor count is 0 (idx : %d)", idx)

await asyncio.sleep(random.uniform(1.0, 1.5))
self.debug("Querying next starting at %s", idx)

for neighbor in self.device.neighbors:
new = self.new_from_record(neighbor.neighbor)
try:
new.device = self.device.application.get_device(new.ieee)
new._update_info()
except KeyError:
self.warning("neighbour %s is not in 'zigbee.db'", new.ieee)

self.neighbours.append(new)
self.debug("Done scanning. Total %s neighbours", len(self.neighbours))

def log(self, level, msg, *args):
Expand Down

0 comments on commit 6af95c9

Please sign in to comment.