kennedyshead / aioasuswrt

MIT License
25 stars 24 forks source link

Could not parse row #8

Closed halkeye closed 6 years ago

halkeye commented 6 years ago
018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=70] Requesting new SSH session
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=70]   Command: arp -n
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=70] Received exit status 0
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=70] Received channel close
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=70] Channel closed
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.142) at 00:18:DD:04:E2:FA [ether]  on br0
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.1) at 00:08:A2:0D:54:B8 [ether]  on br0
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.125) at 60:01:94:41:C4:21 [ether]  on br0
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.61) at 00:09:B0:A1:51:3C [ether]  on br0
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.2) at 00:25:90:12:2D:90 [ether]  on br0
2018-11-11 15:27:09 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row:
2018-11-11 15:27:09 DEBUG (MainThread) [asyncssh] [conn=0, chan=71] Set write buffer limits: low-water=16384, high-water=65536
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=71] Requesting new SSH session
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=71]   Command: ip neigh
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=71] Received exit status 0
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=71] Received channel close
2018-11-11 15:27:09 INFO (MainThread) [asyncssh] [conn=0, chan=71] Channel closed

I tried adding the row to the ARP_DATA tests, but its not failing, so I'm confused whats going on.

This is Home Assistant 0.82.0 which should have the new version in it.

kennedyshead commented 6 years ago

Then we are 2 confused people at the moment :/ I'm not 100% sure if it's the correct version, but it should be.

kennedyshead commented 6 years ago

Yes its 1.1.2 and that one contains the updated code... ? Will do more tests

halkeye commented 6 years ago

aioasuswrt                        1.1.2```

Thats whats installed in the docker image
halkeye commented 6 years ago
admin@RT-N66U-8038:/tmp/home/root# arp -n
? (172.16.10.142) at 00:18:DD:04:E2:FA [ether]  on br0
? (172.16.10.1) at 00:08:A2:0D:54:B8 [ether]  on br0
? (172.16.10.125) at 60:01:94:41:C4:21 [ether]  on br0
? (172.16.10.61) at 00:09:B0:A1:51:3C [ether]  on br0
? (172.16.10.2) at 00:25:90:12:2D:90 [ether]  on br0
admin@RT-N66U-8038:/tmp/home/root# arp -n | hexdump
0000000 203f 3128 3237 312e 2e36 3031 312e 3234
0000010 2029 7461 3020 3a30 3831 443a 3a44 3430
0000020 453a 3a32 4146 5b20 7465 6568 5d72 2020
0000030 6e6f 6220 3072 3f0a 2820 3731 2e32 3631
0000040 312e 2e30 2931 6120 2074 3030 303a 3a38
0000050 3241 303a 3a44 3435 423a 2038 655b 6874
0000060 7265 205d 6f20 206e 7262 0a30 203f 3128
0000070 3237 312e 2e36 3031 312e 3532 2029 7461
0000080 3620 3a30 3130 393a 3a34 3134 433a 3a34
0000090 3132 5b20 7465 6568 5d72 2020 6e6f 6220
00000a0 3072 3f0a 2820 3731 2e32 3631 312e 2e30
00000b0 3136 2029 7461 3020 3a30 3930 423a 3a30
00000c0 3141 353a 3a31 4333 5b20 7465 6568 5d72
00000d0 2020 6e6f 6220 3072 3f0a 2820 3731 2e32
00000e0 3631 312e 2e30 2932 6120 2074 3030 323a
00000f0 3a35 3039 313a 3a32 4432 393a 2030 655b
0000100 6874 7265 205d 6f20 206e 7262 0a30     
000010e
kennedyshead commented 6 years ago

This is so strange!!! could it be encoding?

halkeye commented 6 years ago

How can I test that?

On Sun., Nov. 11, 2018, 10:45 p.m. kennedyshead <notifications@github.com wrote:

This is so strange!!! could it be encoding?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/kennedyshead/aioasuswrt/issues/8#issuecomment-437773867, or mute the thread https://github.com/notifications/unsubscribe-auth/AAGuBziU5HVZF1HaN6pNtkApovGEAunaks5uuRj2gaJpZM4YYwIG .

kennedyshead commented 6 years ago

Try this:

async def _parse_lines(lines, regex):
    """Parse the lines using the given regular expression.

    If a line can't be parsed it is logged and skipped in the output.
    """
    results = []
    if inspect.iscoroutinefunction(lines):
        lines = await lines
    for line in lines:
        if line:
            match = regex.search(line)
            if not match:
                _LOGGER.debug("Could not parse row: %s", line)
                _LOGGER.debug(type(line))
                _LOGGER.debug(chardet.detect(line)['encoding'])
                continue
            results.append(match.groupdict())
    return results
kennedyshead commented 6 years ago

And oh import chardet ;)

halkeye commented 6 years ago
2018-11-11 23:19:44 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row:
2018-11-11 23:19:44 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:19:44 ERROR (MainThread) [homeassistant.components.device_tracker] Error setting up platform asuswrt
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/components/device_tracker/__init__.py", line 174, in async_setup_platform
    hass, {DOMAIN: p_config})
  File "/usr/src/app/homeassistant/components/device_tracker/asuswrt.py", line 46, in async_get_scanner
    await scanner.async_connect()
  File "/usr/src/app/homeassistant/components/device_tracker/asuswrt.py", line 71, in async_connect
    data = await self.connection.async_get_connected_devices()
  File "/usr/local/lib/python3.6/site-packages/aioasuswrt/asuswrt.py", line 160, in async_get_connected_devices
    devices.update(await self.async_get_wl())
  File "/usr/local/lib/python3.6/site-packages/aioasuswrt/asuswrt.py", line 99, in async_get_wl
    result = _parse_lines(lines, _WL_REGEX)
  File "/usr/local/lib/python3.6/site-packages/aioasuswrt/asuswrt.py", line 66, in _parse_lines
    _LOGGER.debug(chardet.detect(line)['encoding'])
  File "/usr/local/lib/python3.6/site-packages/chardet/__init__.py", line 34, in detect
    '{0}'.format(type(byte_str)))
TypeError: Expected object of type bytes or bytearray, got: <class 'str'>

changed it to:

def _parse_lines(lines, regex):
    """Parse the lines using the given regular expression.

    If a line can't be parsed it is logged and skipped in the output.
    """
    results = []
    for line in lines:
        match = regex.search(line)
        if not match:
            _LOGGER.debug("Could not parse row: %s", line)
            import chardet
            _LOGGER.debug(type(line))
            if isinstance(line, str):
                line = str.encode(line)
            _LOGGER.debug(chardet.detect(line)['encoding'])
            continue
        results.append(match.groupdict())
    return results

got

2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.142) at 00:18:DD:04:E2:FA [ether]  on br0
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] ascii
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.1) at 00:08:A2:0D:54:B8 [ether]  on br0
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] ascii
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.125) at 60:01:94:41:C4:21 [ether]  on br0
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] ascii
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.61) at 00:09:B0:A1:51:3C [ether]  on br0
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] ascii
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row: ? (172.16.10.2) at 00:25:90:12:2D:90 [ether]  on br0
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] ascii
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] Could not parse row:
2018-11-11 23:23:49 DEBUG (MainThread) [aioasuswrt.asuswrt] <class 'str'>
kennedyshead commented 6 years ago

Test this:

"""Moddule for Asuswrt."""
import inspect
import logging
import math
import re
from collections import namedtuple
from datetime import datetime

from aioasuswrt.connection import SshConnection, TelnetConnection
from aioasuswrt.helpers import convert_size

_LOGGER = logging.getLogger(__name__)

CHANGE_TIME_CACHE_DEFAULT = 5  # Default 60s

_LEASES_CMD = 'cat /var/lib/misc/dnsmasq.leases'
_LEASES_REGEX = re.compile(
    r'\w+\s' +
    r'(?P<mac>(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})))\s' +
    r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' +
    r'(?P<host>([^\s]+))')

# Command to get both 5GHz and 2.4GHz clients
_WL_CMD = 'for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done'
_WL_REGEX = re.compile(
    r'\w+\s' +
    r'(?P<mac>(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))')

_IP_NEIGH_CMD = 'ip neigh'
_IP_NEIGH_REGEX = re.compile(
    r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3}|'
    r'([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){1,7})\s'
    r'\w+\s'
    r'\w+\s'
    r'(\w+\s(?P<mac>(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2}))))?\s'
    r'\s?(router)?'
    r'\s?(nud)?'
    r'(?P<status>(\w+))')

_ARP_CMD = 'arp -n'
_ARP_REGEX = re.compile(
    r'.+\s' +
    r'\((?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' +
    r'.+\s' +
    r'(?P<mac>(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})))' +
    r'\s' +
    r'.*')

_IFCONFIG_CMD = 'ifconfig eth0 |grep bytes'
_IFCONFIG_REGEX = re.compile(
    r'(?P<data>[\d]{4,})')

_IP_LINK_CMD = "ip -rc 1024 -s link"

Device = namedtuple('Device', ['mac', 'ip', 'name'])

async def _parse_lines(lines, regex):
    """Parse the lines using the given regular expression.

    If a line can't be parsed it is logged and skipped in the output.
    """
    results = []
    if inspect.iscoroutinefunction(lines):
        lines = await lines
    for line in lines:
        if line:
            match = regex.search(line)
            if not match:
                _LOGGER.debug("Could not parse row: %s", line)
                _LOGGER.debug(type(line))
                continue
            results.append(match.groupdict())
    return results

class AsusWrt:
    """This is the interface class."""

    def __init__(self, host, port, use_telnet=False, username=None,
                 password=None, ssh_key=None, mode='router', require_ip=False,
                 time_cache=CHANGE_TIME_CACHE_DEFAULT):
        """Init function."""
        self.require_ip = require_ip
        self.mode = mode
        self._rx_latest = None
        self._tx_latest = None
        self._latest_transfer_check = None
        self._cache_time = time_cache
        self._trans_cache_timer = None
        self._transfer_rates_cache = None

        if use_telnet:
            self.connection = TelnetConnection(
                host, port, username, password)
        else:
            self.connection = SshConnection(
                host, port, username, password, ssh_key)

    async def async_get_wl(self):
        lines = await self.connection.async_run_command(_WL_CMD)
        if not lines:
            return {}
        result = await _parse_lines(lines, _WL_REGEX)
        devices = {}
        for device in result:
            mac = device['mac'].upper()
            devices[mac] = Device(mac, None, None)
        return devices

    async def async_get_leases(self, cur_devices):
        lines = await self.connection.async_run_command(_LEASES_CMD)
        if not lines:
            return {}
        lines = [line for line in lines if not line.startswith('duid ')]
        result = await _parse_lines(lines, _LEASES_REGEX)
        devices = {}
        for device in result:
            # For leases where the client doesn't set a hostname, ensure it
            # is blank and not '*', which breaks entity_id down the line.
            host = device['host']
            if host == '*':
                host = ''
            mac = device['mac'].upper()
            if mac in cur_devices:
                devices[mac] = Device(mac, device['ip'], host)
        return devices

    async def async_get_neigh(self, cur_devices):
        lines = await self.connection.async_run_command(_IP_NEIGH_CMD)
        if not lines:
            return {}
        result = _parse_lines(lines, _IP_NEIGH_REGEX)
        devices = {}
        for device in await result:
            status = device['status']
            if status is None or status.upper() != 'REACHABLE':
                continue
            if device['mac'] is not None:
                mac = device['mac'].upper()
                old_device = cur_devices.get(mac)
                old_ip = old_device.ip if old_device else None
                devices[mac] = Device(mac, device.get('ip', old_ip), None)
        return devices

    async def async_get_arp(self):
        lines = await self.connection.async_run_command(_ARP_CMD)
        if not lines:
            return {}
        result = await _parse_lines(lines, _ARP_REGEX)
        devices = {}
        for device in result:
            if device['mac'] is not None:
                mac = device['mac'].upper()
                devices[mac] = Device(mac, device['ip'], None)
        return devices

    async def async_get_connected_devices(self):
        """Retrieve data from ASUSWRT.

        Calls various commands on the router and returns the superset of all
        responses. Some commands will not work on some routers.
        """
        devices = {}
        dev = await self.async_get_wl()
        devices.update(dev)
        dev = await self.async_get_arp()
        devices.update(dev)
        dev = await self.async_get_neigh(devices)
        devices.update(dev)
        if not self.mode == 'ap':
            dev = await self.async_get_leases(devices)
            devices.update(dev)

        ret_devices = {}
        for key in devices:
            if not self.require_ip or devices[key].ip is not None:
                ret_devices[key] = devices[key]
        return ret_devices

    async def async_get_packets_total(self, use_cache=True):
        """Retrieve total packets from ASUSWRT."""
        now = datetime.utcnow()
        if use_cache and self._trans_cache_timer and self._cache_time > \
                (now - self._trans_cache_timer).total_seconds():
            return self._transfer_rates_cache

        data = await self.connection.async_run_command(_IP_LINK_CMD)
        _LOGGER.info(data)
        i = 0
        rx = 0
        tx = 0
        for line in data:
            if 'eth0' in line:
                rx = data[i+3].split(' ')[4]
                tx = data[i+5].split(' ')[4]
                break
            i += 1
        return int(rx), int(tx)

    async def async_get_rx(self, use_cache=True):
        """Get current RX total given in bytes."""
        data = await self.async_get_packets_total(use_cache)
        return data[0]

    async def async_get_tx(self, use_cache=True):
        """Get current RX total given in bytes."""
        data = await self.async_get_packets_total(use_cache)
        return data[1]

    async def async_get_current_transfer_rates(self, use_cache=True):
        """Gets current transfer rates calculated in per second in bytes."""
        now = datetime.utcnow()
        data = await self.async_get_packets_total(use_cache)
        if self._rx_latest is None or self._tx_latest is None:
            self._latest_transfer_check = now
            self._rx_latest = data[0]
            self._tx_latest = data[1]
            return

        time_diff = now - self._latest_transfer_check
        if time_diff.total_seconds() < 30:
            return (
                math.ceil(
                    self._rx_latest / time_diff.total_seconds()
                ) if self._rx_latest > 0 else 0,
                math.ceil(
                    self._tx_latest / time_diff.total_seconds()
                ) if self._tx_latest > 0 else 0)

        self._latest_transfer_check = now

        rx = data[0] - self._rx_latest
        tx = data[1] - self._tx_latest

        self._rx_latest = data[0]
        self._tx_latest = data[1]

        return (
            math.ceil(rx / time_diff.total_seconds()) if rx > 0 else 0,
            math.ceil(tx / time_diff.total_seconds()) if tx > 0 else 0)

    async def async_current_transfer_human_readable(
            self, use_cache=True):
        """Gets current transfer rates in a human readable format."""
        rx, tx = await self.async_get_current_transfer_rates(use_cache)

        return "%s/s" % convert_size(rx), "%s/s" % convert_size(tx)

    @property
    def is_connected(self):
        return self.connection.is_connected
kennedyshead commented 6 years ago

That is it... just tested it... I'm not really sure why everything was broken because it actually works for me without the uc match... strange

halkeye commented 6 years ago

so just tested 1.1.10 and things seem to be coming in now. So woo!

kennedyshead commented 6 years ago

:+1: