TP-Link-Archer-C6U icon indicating copy to clipboard operation
TP-Link-Archer-C6U copied to clipboard

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

Open swwgames opened this issue 1 year ago • 2 comments

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.

swwgames avatar Jul 19 '24 10:07 swwgames

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

AlexandrErohin avatar Jul 19 '24 11:07 AlexandrErohin

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

swwgames avatar Jul 19 '24 17:07 swwgames

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

 def get_led(self) -> bool:

What do you think?

AlexandrErohin avatar Aug 29 '24 08:08 AlexandrErohin

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

swwgames avatar Aug 29 '24 08:08 swwgames

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

AlexandrErohin avatar Aug 29 '24 08:08 AlexandrErohin

Ok, I will try to do that.

swwgames avatar Aug 29 '24 08:08 swwgames

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?

swwgames avatar Aug 29 '24 08:08 swwgames

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):

AlexandrErohin avatar Aug 29 '24 08:08 AlexandrErohin

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()

swwgames avatar Aug 29 '24 10:08 swwgames

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

AlexandrErohin avatar Aug 29 '24 11:08 AlexandrErohin

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()

swwgames avatar Aug 29 '24 12:08 swwgames

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)
``

AlexandrErohin avatar Aug 29 '24 13:08 AlexandrErohin

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.

swwgames avatar Aug 29 '24 13:08 swwgames

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?

AlexandrErohin avatar Aug 29 '24 13:08 AlexandrErohin

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.

swwgames avatar Aug 29 '24 13:08 swwgames

Thank you for the clarification

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

AlexandrErohin avatar Aug 29 '24 14:08 AlexandrErohin

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

swwgames avatar Aug 29 '24 14:08 swwgames

@swwgames Thank you!

AlexandrErohin avatar Aug 30 '24 09:08 AlexandrErohin