Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to toggle the LEDs on the router, and made it possible to read the current state of the LEDs #29

Merged
merged 11 commits into from
Aug 30, 2024

Conversation

swwgames
Copy link
Contributor

It is now possible to get the current status of the LEDs with

led = status.led

And set the LEDs with

router.set_led(True)

This code works for the TP-Link archer C1200 V2
I am positive it will work on other routers.

tp link c1200 v2 does not have ```/admin/smart_network?form=game_accelerator&operation=loadDevice```

it responds with: ```No root node was registered, this usually happens if no module was installed.
Install luci-mod-admin-full and retry. If the module is already installed, try removing the /tmp/luci-indexcache file.```

This causes an error, because json is expected.
I added self._smart_network = False to class TplinkC1200Router
Added the option to read out the status of  the leds on the router, and the ability to toggle them on and off.
Added led to the dataclass.
@AlexandrErohin
Copy link
Owner

Hi.
It is breaking compatibility. It is better to add 2 methods get_led and set_led to TplinkC1200Router

Set get_led and set_led under the Archer C1200 class.
@swwgames
Copy link
Contributor Author

swwgames commented Jul 19, 2024

Thank you for your reply!
I hope I fixed the compatibility issue correctly now.

@AlexandrErohin
Copy link
Owner

@swwgames Hi
One more question
Is it would be enough?

 def get_led(self) -> bool:

What do you think?

@swwgames
Copy link
Contributor Author

I think you are right.
I will fix that real quick.

@AlexandrErohin
Copy link
Owner

@swwgames Could you add tests for new methods to test_client.py?

@swwgames
Copy link
Contributor Author

Ok, I will try to do that.

@swwgames
Copy link
Contributor Author

Do I need to place the new test under the TPLinkRouterTest class, or do I need to create a new class or function specifically for the Archer C1200?

@AlexandrErohin
Copy link
Owner

Yes, I think it would be better to create a new file test_client_c1200.py and ceate a new class class TestTPLinkC1200Client(unittest.TestCase):

@swwgames
Copy link
Contributor Author

Right now I have the tests for the led_status and wifi_status functions. Do I need to add all the other tests or is this enough?

This is my code so far:

import unittest
import json
import macaddress
import ipaddress
from tplinkrouterc6u import (
    TplinkC1200Router,
    Connection,
    ClientException
)


class TestTPLinkC1200Client(unittest.TestCase):

    def test_led_status(self) -> None:

        response_led_general_read = '''
        {
            "enable": "on",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''

        response_led_general_write = '''
        {
            "enable": "off",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''
        
        class TPLinkRouterTest(TplinkC1200Router):
            def request(self, path: str, data: str,
                        ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
                if path == 'admin/ledgeneral?form=setting&operation=read':
                    return json.loads(response_led_general_read)
                elif path == 'admin/ledgeneral?form=setting&operation=write':
                    self.captured_path = path
                    return json.loads(response_led_general_write)
                raise ClientException()

        client = TPLinkRouterTest('', '')

        led_status = client.get_led()
        self.assertTrue(led_status)

        client.set_led(False)

        expected_path = "admin/ledgeneral?form=setting&operation=write"
        
        self.assertEqual(client.captured_path, expected_path)
    
class TestTPLinkClient(unittest.TestCase):

    def test_set_wifi(self) -> None:

        class TPLinkRouterTest(TplinkC1200Router):
            def request(self, path: str, data: str,
                        ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:

                self.captured_path = path
                self.captured_data = data

        client = TPLinkRouterTest('', '')
        client.set_wifi(
            Connection.HOST_5G,
            enable=True,
            ssid="TestSSID",
            hidden="no",
            encryption="WPA3-PSK",
            psk_version="2",
            psk_cipher="AES",
            psk_key="testkey123",
            hwmode="11ac",
            htmode="VHT20",
            channel=36,
            txpower="20",
            disabled_all="no"
        )
        
        expected_data = ("operation=write&enable=on&ssid=TestSSID&hidden=no&encryption=WPA3-PSK&"
                         "psk_version=2&psk_cipher=AES&psk_key=testkey123&hwmode=11ac&"
                         "htmode=VHT20&channel=36&txpower=20&disabled_all=no")
        expected_path = f"admin/wireless?form=wireless_5g&{expected_data}"
        
        self.assertEqual(client.captured_path, expected_path)
        self.assertEqual(client.captured_data, expected_data)


if __name__ == '__main__':
    unittest.main()

@AlexandrErohin
Copy link
Owner

You dont need to declare

class TestTPLinkClient(unittest.TestCase):

And it would be great if you write tests

  • def test_set_led_on(self)
  • def test_set_led_off(self)
  • def test_get_led(self)

in this MR you dont need test_set_wifi as here is no method for that

@swwgames
Copy link
Contributor Author

So, is this correct?

import unittest
import json
from tplinkrouterc6u import (
    TplinkC1200Router,
    ClientException
)


class TestTPLinkC1200Client(unittest.TestCase):

    def test_set_led_on(self) -> None:

        response_led_general_read = '''
        {
            "enable": "off",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''

        response_led_general_write = '''
        {
            "enable": "on",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''

        class TPLinkRouterTest(TplinkC1200Router):
            def request(self, path: str, data: str,
                        ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
                if path == 'admin/ledgeneral?form=setting&operation=read':
                    return json.loads(response_led_general_read)
                elif path == 'admin/ledgeneral?form=setting&operation=write':
                    self.captured_path = path
                    return json.loads(response_led_general_write)
                raise ClientException()

        client = TPLinkRouterTest('', '')
        
        client.set_led(True)

        expected_path = "admin/ledgeneral?form=setting&operation=write"
        
        self.assertEqual(client.captured_path, expected_path)

    def test_set_led_off(self) -> None:

        response_led_general_read = '''
        {
            "enable": "on",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''

        response_led_general_write = '''
        {
            "enable": "off",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''

        class TPLinkRouterTest(TplinkC1200Router):
            def request(self, path: str, data: str,
                        ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
                if path == 'admin/ledgeneral?form=setting&operation=read':
                    return json.loads(response_led_general_read)
                elif path == 'admin/ledgeneral?form=setting&operation=write':
                    self.captured_path = path
                    return json.loads(response_led_general_write)
                raise ClientException()

        client = TPLinkRouterTest('', '')

        client.set_led(False)

        expected_path = "admin/ledgeneral?form=setting&operation=write"
        
        self.assertEqual(client.captured_path, expected_path)

    def test_led_status(self) -> None:

        response_led_general_read = '''
        {
            "enable": "on",
            "time_set": "yes",
            "ledpm_support": "yes"
        }
        '''
        
        class TPLinkRouterTest(TplinkC1200Router):
            def request(self, path: str, data: str,
                        ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
                if path == 'admin/ledgeneral?form=setting&operation=read':
                    return json.loads(response_led_general_read)
                raise ClientException()

        client = TPLinkRouterTest('', '')

        led_status = client.get_led()
        self.assertTrue(led_status)

if __name__ == '__main__':
    unittest.main()

@AlexandrErohin
Copy link
Owner

from test_set_led_on and test_set_led_off delete please response_led_general_read and this block

if path == 'admin/ledgeneral?form=setting&operation=read':
                    return json.loads(response_led_general_read)
``

@swwgames
Copy link
Contributor Author

That part is needed, because it is used in the set_led function.

    def set_led(self, enable: bool) -> None:
        current_state = self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read').get('enable', 'off') == 'on'
        if current_state != enable:
            self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write')

The code checks the current state, if the state is not in the desired state, a request is made to toggle the state. You can't specify to which state it needs to change.

@AlexandrErohin
Copy link
Owner

So setting ON or OFF can be done just by one request self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write') ? it works like switch when you request self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write') it switches to apposite value?

@swwgames
Copy link
Contributor Author

Yes, self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write') toggles the state. I wanted to be able to specify to which state the led needs to change. Because of that there also needs to be a request made to self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read') so that the function can check if it needs to send a request to self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write').
I hope this clarifies how this function works.

Do you think the test script is correct right now? If so, I can add it to my fork and we will be able to merge.

@AlexandrErohin
Copy link
Owner

Thank you for the clarification

I have no more question for test script. Lets add it

@swwgames
Copy link
Contributor Author

I added the test file to my fork.
I think the fork is ready to merge.

@AlexandrErohin AlexandrErohin merged commit 0752fab into AlexandrErohin:main Aug 30, 2024
3 checks passed
@AlexandrErohin
Copy link
Owner

@swwgames Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants