home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
69.71k stars 28.87k forks source link

ruckus_unleashed: integration "Failed to set up" on 2023.6.0 #94264

Closed pcmoore closed 10 months ago

pcmoore commented 1 year ago

The problem

After upgrading my HA instance to v2023.6.0 yesterday my Ruckus Unleashed integration stopped working with the "Failed to set up" error on the integration page; reloading the integration, restarting HA, and rebooting the entire device did not resolve the problem.

If I enable debug logging for the integration, reload the integration, and check the logs I see the following two entries which appear relevant.

The first entry is coming from the Ruckus integration itself:

Logger: homeassistant.config_entries
Source: components/ruckus_unleashed/__init__.py:31
First occurred: June 7, 2023 at 10:13:26 PM (3 occurrences)
Last logged: 10:09:47 AM

Error setting up entry mesh-XXXXXX for ruckus_unleashed
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 387, in async_setup
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/ruckus_unleashed/__init__.py", line 31, in async_setup_entry
    ruckus = await Ruckus.create(
             ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pyruckus/__init__.py", line 44, in create
    await ruckus.connect()
  File "/usr/local/lib/python3.11/site-packages/pyruckus/__init__.py", line 50, in connect
    result = await ssh.login(
             ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pyruckus/RuckusSSH.py", line 48, in login
    i = await self.expect(login_regex_array, timeout=login_timeout, async_=True)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 340, in expect
    return self.expect_list(compiled_pattern_list,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 366, in expect_list
    from ._async import expect_async
  File "/usr/local/lib/python3.11/site-packages/pexpect/_async.py", line 6, in <module>
    @asyncio.coroutine
     ^^^^^^^^^^^^^^^^^
AttributeError: module 'asyncio' has no attribute 'coroutine'

The second entry doesn't appear to be directly related to the Ruckus integration, but it appears similar to other outstanding GH issues for the integration:

Logger: homeassistant.util.async_
Source: util/async_.py:166
First occurred: 10:00:24 AM (4 occurrences)
Last logged: 10:09:47 AM

Detected blocking call to sleep inside the event loop. This is causing stability issues. Please report issue for config doing blocking calls at homeassistant/components/config/config_entries.py, line 112: await hass.config_entries.async_reload(entry_id)

What version of Home Assistant Core has the issue?

core-2023.6.0

What was the last working version of Home Assistant Core?

core-2023.5.4

What type of installation are you running?

Home Assistant OS

Integration causing the issue

Ruckus Unleashed

Link to integration documentation on our website

https://www.home-assistant.io/integrations/ruckus_unleashed

Diagnostics information

No response

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

No response

home-assistant[bot] commented 1 year ago

Hey there @gabe565, mind taking a look at this issue as it has been labeled with an integration (ruckus_unleashed) you are listed as a code owner for? Thanks!

Code owner commands Code owners of `ruckus_unleashed` can trigger bot actions by commenting: - `@home-assistant close` Closes the issue. - `@home-assistant rename Awesome new title` Renames the issue. - `@home-assistant reopen` Reopen the issue. - `@home-assistant unassign ruckus_unleashed` Removes the current integration label and assignees on the issue, add the integration domain after the command.

(message by CodeOwnersMention)


ruckus_unleashed documentation ruckus_unleashed source (message by IssueLinks)

Coder84619 commented 1 year ago

I'm seeing the same issue with 2023.6.

andornaut commented 1 year ago

Related issue on pyruckus

bjerem commented 1 year ago

Same issue with Home Assistant 2023.6.0 docker container. Here is debug logs :

2023-06-08 17:56:27.175 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Mesh-Backbone for ruckus_unleashed Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/config_entries.py", line 387, in async_setup result = await component.async_setup_entry(hass, self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/components/ruckus_unleashed/init.py", line 31, in async_setup_entry ruckus = await Ruckus.create( ^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pyruckus/init.py", line 44, in create await ruckus.connect() File "/usr/local/lib/python3.11/site-packages/pyruckus/init.py", line 50, in connect result = await ssh.login( ^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pyruckus/RuckusSSH.py", line 48, in login i = await self.expect(login_regex_array, timeout=logintimeout, async=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 343, in expect return self.expect_list(compiled_pattern_list, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 369, in expect_list from ._async import expect_async File "/usr/local/lib/python3.11/site-packages/pexpect/_async.py", line 7, in @asyncio.coroutine ^^^^^^^^^^^^^^^^^ AttributeError: module 'asyncio' has no attribute 'coroutine'

This problem was not present before the update to HA 2023.6.0

faithless01 commented 1 year ago

As per above:

2023-06-09 05:48:12.511 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry Mesh-Backbone for ruckus_unleashed Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/config_entries.py", line 387, in async_setup result = await component.async_setup_entry(hass, self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/components/ruckus_unleashed/init.py", line 31, in async_setup_entry ruckus = await Ruckus.create( ^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pyruckus/init.py", line 44, in create await ruckus.connect() File "/usr/local/lib/python3.11/site-packages/pyruckus/init.py", line 50, in connect result = await ssh.login( ^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pyruckus/RuckusSSH.py", line 48, in login i = await self.expect(login_regex_array, timeout=logintimeout, async=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 340, in expect return self.expect_list(compiled_pattern_list, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/pexpect/spawnbase.py", line 366, in expect_list from ._async import expect_async File "/usr/local/lib/python3.11/site-packages/pexpect/_async.py", line 6, in @asyncio.coroutine ^^^^^^^^^^^^^^^^^ AttributeError: module 'asyncio' has no attribute 'coroutine'

pcmoore commented 1 year ago

I can confirm that downgrading HA core to v2023.5.4 restores the Ruckus Unleashed integration.

veno77 commented 1 year ago

I can confirm same issue exists also on 2023.6.1

Coder84619 commented 1 year ago

Any hope of a resolution to this in the near future?

toxic0berliner commented 1 year ago

One more confirmation if needed on 2023.06.1 same error log, I removed the integration and am unable to add it again with the very same error log. I confirmed the user/pass is still valid on ssh. Will wait and hope someone fixes this as I am happy having setup my NAs in 2023.6 so not really willing to downgrade but thx for confirming this is a viable fix.

faithless01 commented 1 year ago

@andornaut - I'm no py dev, but I can't follow the bouncing ball here... Trying to follow the breadcrumbs of the linked PIP libraries... and it looks ... fine (?!)

As you've suggested - the @asyncio.coroutine needs to be updated to the replacement - async def ...and this appears to be the case. (unless I'm looking at the wrong branches, ?!)

I don't understand where is the deprecated @asyncio.coroutine being called from? Unfortunately, I run HA in the OS(?) mode, and don't have access to the python libraries in the shell to see what's happening, versions, etc... Do we need to spin up HA in Supervised or Core mode or similar to debug ?

Bubbgump209 commented 1 year ago

The issue is 100% in pexpect within _async.py. I monkey patched _async.py to

/usr/local/lib/python3.11/site-packages/pexpect/_async.py

import asyncio
import errno

from pexpect import EOF

async def expect_async(expecter, timeout=None):
    # First process data that was previously read - if it maches, we don't need
    # async stuff.
    previously_read = expecter.spawn.buffer
    expecter.spawn._buffer = expecter.spawn.buffer_type()
    expecter.spawn._before = expecter.spawn.buffer_type()
    idx = expecter.new_data(previously_read)
    if idx is not None:
        return idx
    if not expecter.spawn.async_pw_transport:
        pw = PatternWaiter()
        pw.set_expecter(expecter)
        transport, pw = await asyncio.get_event_loop()\
            .connect_read_pipe(lambda: pw, expecter.spawn)
        expecter.spawn.async_pw_transport = pw, transport
    else:
        pw, transport = expecter.spawn.async_pw_transport
        pw.set_expecter(expecter)
        transport.resume_reading()
    try:
        return (await asyncio.wait_for(pw.fut, timeout))
    except asyncio.TimeoutError as e:
        transport.pause_reading()
        return expecter.timeout(e)

class PatternWaiter(asyncio.Protocol):
    transport = None

    def set_expecter(self, expecter):
        self.expecter = expecter
        self.fut = asyncio.Future()

    def found(self, result):
        if not self.fut.done():
            self.fut.set_result(result)
            self.transport.pause_reading()

    def error(self, exc):
        if not self.fut.done():
            self.fut.set_exception(exc)
            self.transport.pause_reading()

    def connection_made(self, transport):
        self.transport = transport

    def data_received(self, data):
        spawn = self.expecter.spawn
        s = spawn._decoder.decode(data)
        spawn._log(s, 'read')

        if self.fut.done():
            spawn._buffer.write(s)
            return

        try:
            index = self.expecter.new_data(s)
            if index is not None:
                # Found a match
                self.found(index)
        except Exception as e:
            self.expecter.errored()
            self.error(e)

    def eof_received(self):
        # N.B. If this gets called, async will close the pipe (the spawn object)
        # for us
        try:
            self.expecter.spawn.flag_eof = True
            index = self.expecter.eof()
        except EOF as e:
            self.error(e)
        else:
            self.found(index)

    def connection_lost(self, exc):
        if isinstance(exc, OSError) and exc.errno == errno.EIO:
            # We may get here without eof_received being called, e.g on Linux
            self.eof_received()
        elif exc is not None:
            self.error(exc)

This works perfectly. The bigger issue seems to be upstream. There is some blocker in pexpect where they are waiting for Pypy to get updated so that unit tests will pass. Here's the Pypy issue https://foss.heptapod.net/pypy/pypy/-/issues/3931 which is blocking the pexpect issue https://github.com/pexpect/pexpect/pull/732.

Essentially this is a bit of dependency hell.

The quick fix is my monkey patch if folks feel comfortable running a shell into the homeassistant container. EDIT: Understand, if you do monkey patch, you'll need to monkey patch after every HA upgrade until the upstream is fixed as the new container will nuke the change. /EDIT.

The longer fix is wait for the whole dependency deal to work itself out OR rewrite the integration to use an alternative to pexpect (Fabric?) that is better maintained OR fork pyexpect... or 100 other work arounds.

faithless01 commented 1 year ago

Thanks @Bubbgump209 - am happy to clobber the _async.py file ... Stupid question. Can this be done with the HassOS installation? When I use the SSH AddOn from within the UI, (I guess), I'm only accessing the SSH AddOn container. The /usr/local/lib directory is relatively empty. If I access the console and type 'login' to get a root shell - there isn't even a /usr/local folder ?! Not being a docker pundit, where are the python libraries even located?!? Are you doing this from HassOS, or another installation method? (apologies if I've messed up my terminologies)

Bubbgump209 commented 1 year ago

In my case I am running supervised. So I connected to the container:

docker exec -d homeassistant /bin/bash

Then edit /usr/local/lib/python3.11/site-packages/pexpect/_async.py. I did have to install an editor using apk add nano because I am too dumb to be good at vim.

I don't know what the HassOS install looks like. I imagine if you can SSH into the base OS that is running Docker in HassOS, then the same can be done.

andornaut commented 1 year ago

One way to workaround this issue is by installling pexpect from the 'master' branch:

pip install https://github.com/pexpect/pexpect/archive/master.zip

Here's an example from an Ansible role that works around this issue:

- name: "Install pexpect from 'master' branch. Workaround 1/2 for: https://github.com/home-assistant/core/issues/94264"
  community.docker.docker_container_exec:
    container: homeassistant
    argv:
      - /bin/bash
      - "-c"
      - "pip install https://github.com/pexpect/pexpect/archive/master.zip"

- name: "Restart the homeassistant container. Workaround 2/2 for: https://github.com/home-assistant/core/issues/94264"
  community.docker.docker_container:
    name: homeassistant
    state: started
    restart: true
faithless01 commented 1 year ago

Thanks @Bubbgump209 I'll try and make sense of the docker rabbit holes in the HassOS to find the offending file. A 'find' from the console login returns 16 instances of _async.py in a number of containers. I'll try to find the 'homeassistant' container and work from there.

Must be a generational thing. I've 20 years of vi experience being my preferred *nix IDE. These days I generally don't bother with volatile languages like python, and tend to prefer hypervisors to containers.

Thanks for the fix!

@andornaut - are you doing this from the HassOS installation, or a supervised install, like @Bubbgump209 ?

Bubbgump209 commented 1 year ago

@andornaut You're a god. I was about to start creating a playbook for this as I have a feeling this won't be fixed upstream for quite some time.

andornaut commented 1 year ago

@andornaut - are you doing this from the HassOS installation, or a supervised install, like @Bubbgump209 ?

I'm running Home Assistant in a Docker container. A similar approach should work for a HassOS installation, but I haven't used HassOS myself, sorry.

faithless01 commented 1 year ago

Thanks @andornaut I'll consider a supervised install, as the HassOS appears to have been obfuscated to disuade ludites like myself from messing around.

faithless01 commented 1 year ago

I'll second your sentiments @Bubbgump209 : @andornaut - You're a god :)

For anybody using HassOS Go to the console ha > login

docker exec -it homeassistant /bin/bash

homeassistant:/config# pip install https://github.com/pexpect/pexpect/archive/master.zip
homeassistant:/config# exit
# shutdown -r now

Thanks for the solutions!

Coder84619 commented 1 year ago

I'll second your sentiments @Bubbgump209 : @andornaut - You're a god :)

For anybody using HassOS Go to the console ha > login # docker exec -it homeassistant /bin/bash homeassistant:/config# pip install https://github.com/pexpect/pexpect/archive/master.zip homeassistant:/config# exit # shutdown -r now

Thanks for the solutions!

Is that a "safe" package to install, meaning it won't be likely to cause other errors or issues? Will we need to back out this change down the road?

andornaut commented 1 year ago

Is that a "safe" package to install, meaning it won't be likely to cause other errors or issues? Will we need to back out this change down the road?

@Coder84619 This package - but an older version - is already installed by the Ruckus integration. The new version hasn't caused any issues for me personally, and I don't think it's likely that it will be any more likely to cause problems than the version that is already installed.

No one can answer this question definitively, unfortunately.

Will we need to back out this change down the road?

The change discussed above is a workaround. If the underlying issue is addressed in the future, then it'd be a good idea to back out this change (or, more precisely: not apply this workaround on top of the updated version of this integration).

Bubbgump209 commented 1 year ago

Is that a "safe" package to install, meaning it won't be likely to cause other errors or issues? Will we need to back out this change down the road?

You will not need to back this out. Home Assistant is entirely Docker based so when you run an upgrade to Home Assistant it will overwrite this change. See my warning above how this fix will need to be applied every time one upgrades as it will be overwritten.

faithless01 commented 1 year ago

Exactly as @Bubbgump209 said. The update of the Core Docker container to 2023.6.2 bought with it the older pexpect 4.6.0, breaking the Ruckus Integration. The fix provided by @andornaut was quickly reapplied, bumping the pexpect PIP module to 4.8.0 and all is well with the Ruckus Integration again. Thankfully I don't appear to have any Integrations that rely on the older pexpect 4.6.0 Do we know what facilitated the change to pexpect 4.6.0 ? Can we, as Ruckus users, challenge those-that-broke-our-Integration to a cage match or something? Winner gets their preferred pexpect version in the Core Docker container? Apologies if I'm offending a structured, democratic method of dependancy conflict resolution, I'm new to HA / github / docker / comptuery things.

faithless01 commented 1 year ago

Looking in https://github.com/home-assistant/core/blob/dev/requirements_all.txt Lines 1403 - 1407 . # homeassistant.components.aruba . # homeassistant.components.cisco_ios . # homeassistant.components.pandora . # homeassistant.components.unifi_direct pexpect==4.6.0

Again - not a python dev, but are the commented Integragtions above the PIP the Integrations that reply on the library?

EDIT: I guess not, because the same entry exists in the 2023.5.4 branch where thr Ruckus Integration was working: https://github.com/home-assistant/core/blob/2023.5.4/requirements_all.txt

... walking away.

Bubbgump209 commented 1 year ago

@faithless01 as I explained above, this is an issue with upstream Python. Python changed how they do things in newer versions. And turn this broke Pexpect which is a library that all these integrations are using to facilitate SSH and pulling back data structures. Pexpect is a generally available Python library and has no relation whatsoever to Home Assistant. Interestingly enough the master branch is ahead of the 4.8 release though still reports as 4.8. Otherwise it would be a very simple fix - bump the version requirement in pyruckus.

So see above as far as what the options are. Wait for pexpect to merge the fix and put out a new release (4.9?), rewrite the integration to use different libraries, or monkey patch while waiting.

faithless01 commented 1 year ago

Thanks @Bubbgump209 . From years of dabbling with Python, enough to pull something apart, written with an unmaintained library that has been deprcated, and is now broken due to security updates of the core language, then recode that company dependant tool in perl/CPAN or Bash, I've tried to avoid Python. I'm not a dev by trade, and Python has always seemed very volitile, mostly with very funky libraries written by transient developers, abandoned as they graduate and move to corporate roles leaving their libraries unmaintained. As @andornaut wrote: https://github.com/gabe565/pyruckus/issues/21#issuecomment-1589580374

Updating the requirement.txt file to point to pexpect @ https://github.com/pexpect/pexpect/archive/2532721.zip fixes the problem. I was hoping we could find why the requirements file in the HA Core Docker container refernces the older 4.6.0 But you're saying the version pf pexpect used has nothing to do with HA? I'm confused as to why the Core Docker requirements file couldn't use the link @andornaut referenced.

As you've suggested, I'll leave it to someone smarter to correct the problem and continue to clobber the library with a version that works in the interim. Thanks Again!

lanrat commented 1 year ago

I've been working on a python library to parse connected clients from Ruckus Unleashed APs using the web API, instead of scraping the SSH output like pyruckus does. So far I've only tested it with my R500 and R600, but if anyone else wants to help me test it, it should be far more stable than pyruckus and I can submit a PR to use it here.

https://github.com/lanrat/ruckus-clients

faithless01 commented 1 year ago

Thanks @lanrat I had a problem with line 14 of the example.py script, but once I removed that, the API calls did their thing. I have a couple of R600's. I made the call to the floating Unleashed master IP with an "admin" account. user@debian:~/Dev$ ./example.py {'name': 'KL130B', 'mac': '68:ff:7b:xx:xx:xx'} {'name': 'Cam2', 'mac': 'e8:ca:c8:xx:xx:xx'} {'name': 'Wiz Bedroom Candle 01', 'mac': '44:4f:8e:xx:xx:xx'} {'name': '', 'mac': '10:08:c1:xx:xx:xx'} {'name': '', 'mac': 'e0:98:06:xx:xx:xx'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx'} {'name': 'HS100', 'mac': '1c:3b:f3:xx:xx:xx'} {'name': '', 'mac': '50:02:91:xx:xx:xx'} {'name': '', 'mac': '44:d5:cc:xx:xx:xx'} {'name': 'Study Desk Lamp', 'mac': 'b0:95:75:xx:xx:xx'} {'name': 'JT-Note8', 'mac': '04:d6:aa:xx:xx:xx'} {'name': 'Cam1', 'mac': '04:39:26:xx:xx:xx'} {'name': 'HS100', 'mac': '1c:3b:f3:xx:xx:xx'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx'} {'name': 'KL110', 'mac': 'd8:0d:17:xx:xx:xx'} {'name': '', 'mac': 'a4:cf:12:xx:xx:xx'} {'name': 'HS110', 'mac': '74:da:88:xx:xx:xx'} {'name': '', 'mac': 'a4:cf:12:xx:xx:xx'} {'name': 'ESP VR Motion Detection', 'mac': 'a4:cf:12:xx:xx:xx'} {'name': 'KL110B', 'mac': 'cc:32:e5:xx:xx:xx'} {'name': 'Wiz Bedroom Candle 02', 'mac': 'd8:a0:11:xx:xx:xx'} {'name': 'Dyson Fan', 'mac': 'c8:ff:77:xx:xx:xx'} {'name': 'KL130B', 'mac': '84:d8:1b:xx:xx:xx'} {'name': '', 'mac': 'b8:5f:98:xx:xx:xx'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx'}

I'm new to github, and python, so I'll take this offline for a while and do some learnin'. I'll figure out how to contact you via your github account / repo ... Apologies if its an indutry standard or similar, but is the Elmtree XML parser as long-term reliable as basic json for API calls?

Can you extend the functionality of ruckus.py to allow 302 redirects as well as 200 ok for login_resp ?

EDIT: There is a bit of useful information in the returned api to help diagnose which AP devices are connected, which ssid they're on, their subnet, signal strength, etc

{'name': 'KL130B', 'mac': '68:ff:7b:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'Cam2', 'mac': 'e8:ca:c8:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'Wiz Bedroom Candle 01', 'mac': '44:4f:8e:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': '', 'mac': '10:08:c1:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-1', 'signal': 'excellent'} {'name': '', 'mac': 'e0:98:06:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'HS100', 'mac': '1c:3b:f3:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': '', 'mac': '50:02:91:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': '', 'mac': '44:d5:cc:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'Study Desk Lamp', 'mac': 'b0:95:75:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'JT-Note8', 'mac': '04:d6:aa:xx:xx:xx', 'ip': '192.168.100.xxx', 'ssid': 'FranklinManor', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'Cam1', 'mac': '04:39:26:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'HS100', 'mac': '1c:3b:f3:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'KL110', 'mac': 'd8:0d:17:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': '', 'mac': 'a4:cf:12:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'HS110', 'mac': '74:da:88:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': '', 'mac': 'a4:cf:12:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'ESP VR Motion Detection', 'mac': 'a4:cf:12:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'KL110B', 'mac': 'cc:32:e5:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'} {'name': 'Wiz Bedroom Candle 02', 'mac': 'd8:a0:11:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'Dyson Fan', 'mac': 'c8:ff:77:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-0', 'signal': 'excellent'} {'name': 'KL130B', 'mac': '84:d8:1b:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-0', 'signal': 'excellent'} {'name': '', 'mac': 'b8:5f:98:xx:xx:xx', 'ip': '192.168.101.xxx', 'ssid': 'FranklinWing', 'AP': 'R600-0', 'signal': 'excellent'} {'name': '', 'mac': 'e8:db:84:xx:xx:xx', 'ip': '192.168.102.xxx', 'ssid': 'FranklinThings', 'AP': 'R600-1', 'signal': 'excellent'}

This is a great tool you're creating @lanrat - kudos!

ms264556 commented 1 year ago

I created a library which is completely async, using aiohttp, specifically so it could replace pyruckus here (and I put AP LED, client blocking and WLAN disabling into the API). If you don't want to reinvent the wheel then you might want to have a look.

https://pypi.org/project/aioruckus/

tsunglung commented 1 year ago

@ms264556 You did a great job. If use ssh cli, there are some limitation. For example, can not set block clients in system acl-list. Your solution can do. This help me to solve to create a switch by blocking client.

lanrat commented 1 year ago

aioruckus looks better than my code and is what I think would be a good path forward.

I can start a draft PR to move this component to it, unless @ms264556 wants to do it.

ms264556 commented 1 year ago

I don't particularly want to do the PR - I had an earlier try and it wasn't accepted. So if you're keen then that'd be great.

I can definitely make any changes which would make your job easier (e.g. if you want a synchronous context manager so that it's more plug-and-play with existing pyruckus code). I even have some nasty "pyruckus compatibility" methods which I can commit, if you need the easiest possible switch.

I can add you as a maintainer of aioruckus if you think that'd help, but I'm generally pretty quick at fixing issues, and I have a large collection of APs to test changes against.

lanrat commented 1 year ago

@ms264556 thats fine, I'll work on the PR.

Do you mind linking the earlier PR you mentioned you did that was not accepted so that I can avoid any of the same mistakes?

ms264556 commented 1 year ago

It was against an older version of aioruckus, which looked quite different (actually, looked quite like your current code 😀), and I killed it when I got feedback that the tests were no good and I should update pyruckus rather than start a new library.

You can see my attempt here:-

https://github.com/home-assistant/core/compare/dev...ms264556:home-assistant-core:ruckus_unleashed_updates

lanrat commented 1 year ago

@ms264556 I'm working on a PR that is currently heavily based on your prior PR you linked to. However I am getting an exception in what appears to be one of the dependencies of aioruckus when I call ruckus.api.get_active_clients()

Do you have any insights into this? I can provide my full code if you want, but its pretty minimal just requesting the client list so far.

Screenshot from 2023-06-16 18-17-47

I can't figure out where that missing str should be set.

ms264556 commented 1 year ago

Can you easily see what version of aiohttp is in site-packages?

I can setup a HA dev environment so I can investigate. Will take a little while. If you're able to commit your changes to a branch on your fork then that'd make it faster for me to look.

ms264556 commented 1 year ago

Nevermind. I can repro the issue if I try to call AjaxSession.async_create() directly instead of using it as an async context manager.

Let me work up some code which works in that way.

Otherwise I'll create a new aioruckus release which includes a non-async context, so this usage will work OK.

lanrat commented 1 year ago

I'm testing with a R600 and R500 running version 200.7.10.202.

@ms264556 I was able to fix the bug from my prior comment. I found the fix was to explicitly call await ruckus.login() after AjaxSession.async_create(...)

lanrat commented 1 year ago

@ms264556 I also noticed some of the dictionary keys from my APs and clients are different than the ones from your prior PR.

I've changed the relevant ones to use the keys from my setup, but from your experience, is this something that may be different for other users?

For example, you used serial-number for the AP serial number, but on my setup, its just serial, I'm also missing firmware-version and instead just have version and no hardware-version, as well as a few others.

ms264556 commented 1 year ago

Yes, was just coming to write the same: if you're not inside an async with then you need an await ruckus.login(). Also try to call await ruckus.close() if you're killing the session.

The original PR used a minimal pyruckus compatibility shim, which transformed the AJAX results so they looked like what pyruckus had produced.
So yes, the properties will all have different names than what pyruckus used.

Here are the shims I wrote so you can see the mappings:-

async def system_info(self) -> dict:
    warn("Use get_system_info()", DeprecationWarning)
    sysinfo = await self.get_system_info(SystemStat.SYSINFO, SystemStat.IDENTITY)
    return {"system_overview": {"name": sysinfo["identity"]["name"], "version": sysinfo["sysinfo"]["version"], "serial_number": sysinfo["sysinfo"]["serial"]}}

async def mesh_info(self) -> dict:
    warn("Use get_mesh_info() or get_system_info(SystemStat.MESH_POLICY)", DeprecationWarning)
    meshinfo = await self.get_mesh_info()
    meshpolicy = await self.get_system_info(SystemStat.MESH_POLICY)
    return {"mesh_settings": {"mesh_status": "Enabled" if meshpolicy["mesh-policy"]["enabled"] == "true" else "Disabled", "mesh_name_essid": meshinfo["name"], "zero_touch_mesh_pre_approved_serial_number_list": {"serial_number": "unsupported"}}}

async def mesh_name(self) -> str:
    warn("Use get_mesh_info()['name']", DeprecationWarning)
    mesh_info = await self.get_mesh_info()
    return mesh_info["name"] if "name" in mesh_info else "Ruckus Mesh"

async def current_active_clients(self) -> dict:
    warn("Use get_active_client_info()", DeprecationWarning)
    clientstats = await self.get_active_client_info()
    return {"current_active_clients": {"clients": [{"mac_address": c["mac"], "host_name": c["hostname"], "user_ip": c["ip"], "access_point": c["vap-mac"]} for c in clientstats]}}

async def ap_info(self) -> dict:
    warn("Use get_ap_info()", DeprecationWarning)
    apstats = await self.get_ap_info()
    return {"ap": {"id": {a["id"]: {"mac_address": a["mac"], "device_name": a["devname"], "model": a["model"], "network_setting": {"gateway": a["gateway"]}} for a in apstats}}}
ms264556 commented 1 year ago

Regarding the keys being different for other users...

I test on ZoneDirector 9.10, 9.13, 10.1, 10.3, 10.5.1 & Unleashed 200.7, 200.12, 200.13, 200.14, 200.14 Dedicated Master.
These releases cover ~8 years, and these keys have never changed, so I'm pretty confident we're OK.

lanrat commented 1 year ago

@ms264556 thanks for the info. I added await ruckus.close() too to be safe.

I'm not using any of the methods you mentioned for the compatibility shim, just using your functions directly.

Do your APs return firmware-version and hardware-version from api.get_aps()? Mine do not.

ms264556 commented 1 year ago

Sorry, misunderstood what you were asking for.

api.get_aps() is a config call so won't show those properties. If you call api.get_ap_stats() then it'll include the extra information you want.

lanrat commented 1 year ago

I'm not sure we need api.get_ap_stats(), when I call api.get_aps() I get everything I need. It just had a few things named a little differently.

lanrat commented 1 year ago

I have a branch that as far as I can tell, works perfectly for my home setup with a lot of manual testing.

I'm now working on updating the automated tests. @ms264556 I see that you tried to use a RuckusApi object to mimic your API responses. How should it be instantiated for mocks? The old commits you linked to don't seem to have it defined anywhere.

lanrat commented 1 year ago

@ms264556 I've pushed my current WIP changes to my fork here: https://github.com/lanrat/hass_core/tree/ruckus_unleashed_py3.11

ms264556 commented 1 year ago

You should be able to subclass AbcSession and have the api() method return your mock.

ms264556 commented 1 year ago

Honestly, I didn't 100% understand what HA required to be tested, so I failed miserably at that part of the PR.

lanrat commented 1 year ago

You should be able to subclass AbcSession and have the api() method return your mock.

@ms264556 I have very little experience with python mock tests. I'm trying to figure this out, but I'm lost so far. Any other hints you can give me would be appreciated. I'll push the updated tests I have so far to my branch, but they all fail as I'm not sure what to do about RuckusApi yet.

ms264556 commented 1 year ago

I have very little experience with python mock tests

Ha! I have very little experience with python, so I definitely feel your pain.
I've only learned enough python to write this library + a couple of other hacky bits and pieces.

If you commit the broken tests to your branch then I'll patch them up so they work. I'm not at home, so it won't be for 3 or 4 hours sorry.