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

Support HS300 Power Strip #131

Closed
jimboca opened this issue Oct 11, 2018 · 19 comments
Closed

Support HS300 Power Strip #131

jimboca opened this issue Oct 11, 2018 · 19 comments

Comments

@jimboca
Copy link
Contributor

jimboca commented Oct 11, 2018

Would like support for the Power Strip:
https://www.tp-link.com/us/products/details/cat-5516_HS300.html

Discover currently fails:

pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py 
No host name given, trying discovery..
Discovering devices for 3 seconds
== TP-LINK_Power Strip_2CA9 - HS300(US) ==
OFF
Host/IP: 192.168.86.45
Traceback (most recent call last):
  File "pyHS100/cli.py", line 267, in <module>
    cli()
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 1043, in invoke
    return Command.invoke(self, ctx)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "pyHS100/cli.py", line 59, in cli
    ctx.invoke(discover)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "pyHS100/cli.py", line 89, in discover
    ctx.invoke(state)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args[1:], **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "pyHS100/cli.py", line 129, in state
    for k, v in dev.state_information.items():
  File "/usr/local/lib/python3.5/dist-packages/pyHS100/smartplug.py", line 206, in state_information
    'On since': self.on_since
  File "/usr/local/lib/python3.5/dist-packages/pyHS100/smartplug.py", line 200, in on_since
    datetime.timedelta(seconds=self.sys_info["on_time"])
TypeError: unsupported type for timedelta seconds component: NoneType

Because on_time is down inside children:

== System info ==
defaultdict(<function SmartDevice.sys_info.<locals>.<lambda> at 0x767d35d0>,
            {'alias': 'TP-LINK_Power Strip_2CA9',
             'child_num': 6,
             'children': [{'alias': 'Home Plug 1',
                           'id': 'xx00',
                           'next_action': {'type': -1},
                           'on_time': 63142,
                           'state': 1},
                          {'alias': 'Home Plug 2',
                           'id': 'xx01',
                           'next_action': {'type': -1},
                           'on_time': 0,
                           'state': 0},
                          {'alias': 'Home Plug 3',
                           'id': 'xx02',
                           'next_action': {'type': -1},
                           'on_time': 0,
                           'state': 0},
                          {'alias': 'Home Plug 4',
                           'id': 'xx03',
                           'next_action': {'type': -1},
                           'on_time': 0,
                           'state': 0},
                          {'alias': 'Home Plug 5',
                           'id': 'xx04',
                           'next_action': {'type': -1},
                           'on_time': 0,
                           'state': 0},
                          {'alias': 'Home Plug 6',
                           'id': 'xx05',
                           'next_action': {'type': -1},
                           'on_time': 0,
                           'state': 0}],
             'deviceId': 'xxx',
             'feature': 'TIM:ENE',
             'hwId': 'xxx',
             'hw_ver': '1.0',
             'latitude_i': xxx,
             'led_off': 0,
             'longitude_i': -xxx,
             'mac': 'xxx,
             'mic_type': 'IOT.SMARTPLUGSWITCH',
             'model': 'HS300(US)',
             'oemId': 'xxx',
             'rssi': -45,
             'sw_ver': '1.0.6 Build 180627 Rel.081000',
             'updating': 0})

This is the only device I have. I can fork and work on it, but may need some guidance on how a multi-plug device should be handled.

@kirichkov
Copy link
Collaborator

I suggest you fork and start working on it.

From here on, I see two approaches for development:

  1. Treat this as a completely new class of device (like bulbs and switches) and create a completely new class e.g. SmartPowerStrip, that inherits from SmartDevice
  2. Rework SmartPlug (pretty much a complete rewrite) to handle multiple plugs for a single device.

I'm in favor of approach 1, to isolate the changes to the new class, and not mess with the SmartPlug code.

It's also possible that SmartPowerStrip holds internally an array of SmartPlug objects, but looking at the response JSON, this will probably also require changes in SmartPlug, so I think it'd be easier to simply implement SmartPowerStrip as a new class and the turn_on() and turn_off() methods should accept an additional argument - the index identifying the correct socket.

Let's wait for @rytilahti to give his opinion as well :-)

@rytilahti
Copy link
Collaborator

What a nice device, just exactly what I have been looking for (a power strip with energy consumption)! The price is a bit steep, but I hope they will release this soon to european markets, too.

The option 1 is definitely the preferred one. Furthermore I think this new SmartPowerStrip should contain internally a list of smartplugs as @kirichkov said, but for that you will need to analyze how the communication with them works (and if there are changes required to SmartPlug class or not). A good starting point would be to capture the traffic and analyze it with https://github.com/softScheck/tplink-smartplug#wireshark-dissector .

For discovery I think it makes sense to use the "children" or "child_num" key (in discovery.py). For fixing the pyhs100 command, your new SmartPowerStrip info will need to override the state_info to return the wanted information.

If you have any questions or encounter any problems with the implementation, please feel free to ask!

@jimboca
Copy link
Contributor Author

jimboca commented Oct 12, 2018

Thanks both of you. Yes, it's a nice device, and to me the price is justified for 6 smart plugs. Personally I just wish the usb port power could be controlled as well to power my RPi's and allow remote reboot if they are hung.

Yes, I agree SmartPowerStrip is a new class, but I was wondering if it should be a more generic name like MultiSmartPlug which could support the HS107 as well that has 2 plugs?

BTW, I'm planning to use this for an ISY Node Server for https://github.com/UniversalDevicesInc/polyglot-v2

Thanks again, hopefully will have time this weekend.

@rytilahti
Copy link
Collaborator

Please also add a fake output to fakes.py and create tests for that :-)

@newAM
Copy link
Contributor

newAM commented Oct 19, 2018

I'm working on this now, I fixed discovery, but I am running into some trouble with my network getting a wire shark capture of the queries for turning on/off an individual outlet. Would anyone happen to have a capture available?

@newAM
Copy link
Contributor

newAM commented Oct 19, 2018

Found my problem; had to set some additional settings on my new switch. The app presented another obnoxious problem - there is no "local only" mode for the HS300. Disabling WAN access for the device forced it to fail over into local mode.

Here's the good part:
cap

@newAM
Copy link
Contributor

newAM commented Oct 19, 2018

Got individual outlet control working (with some dirty code). I pushed it to a dirty branch here:

https://github.com/newAM/pyHS100/tree/dirty

Plug index is not integrated into the CLI, but the functions work. I still need to get a capture for the individual outlet energy monitoring, but I imagine it is a similar JSON call.

@jimboca
Copy link
Contributor Author

jimboca commented Oct 19, 2018

Nice! I'll try it out tomorrow.

@newAM
Copy link
Contributor

newAM commented Oct 19, 2018

Did a basic check of emetering, it's uses the same notation for indexing the outlets as setting the relay state.

DEBUG:pyHS100.protocol:> (106) {"context": {"child_ids": ["xx04"]}, "emeter": {"get_realtime": {}}}
DEBUG:pyHS100.protocol:< (103) {"emeter":{"get_realtime":{"voltage_mv":118074,"current_ma":4,"power_mw":0,"total_wh":0,"err_code":0}}}

I added an index to the CLI for power on/off, so that works for basic functionality tests. I probably wont be able to do a proper implementation & pull request for a couple weeks, so if anyone else can get it done sooner please go ahead :D

@jimboca
Copy link
Contributor Author

jimboca commented Oct 19, 2018

I'll try to get time to look at it, did everyone want to call it smartstrip? I had said in a previous comment; I was wondering if it should be a more generic name like MultiSmartPlug which could support the HS107 as well that has 2 plugs? and @rytilahti had given 👍

@rytilahti
Copy link
Collaborator

Smartstrip sounds better to my ear than MultiSmartPlug, and furthermore the names are relevant only for developers, so I don't see a big problem with reusing that for HS107.

@jimboca
Copy link
Contributor Author

jimboca commented Oct 19, 2018

no problem, sounds better to me as well, just wanted to make sure.

@jimboca
Copy link
Contributor Author

jimboca commented Oct 19, 2018

I've merged your dirty branch to mine. The device state starts plug index at 1, but turning on excepts zero based. Seems that starting with 1 makes the most sense?

== TP-LINK_Power Strip_2CA9 - HS300(US) ==
Device state: ON
Host/IP: blah
Plug 1 on since: 2018-10-13 12:08:26.945427
Plug 6 on since: 2018-10-19 15:26:33.671853
Plug 4 on since: 2018-10-13 12:15:16.381538
Plug 5 on since: 2018-10-13 12:08:32.526485
Plug 2 on since: 2018-10-13 12:09:07.092094
LED state: True
Plug 3 on since: 2018-10-13 12:10:18.236126
== Generic information ==
Time:         2018-10-19 15:26:56
Hardware:     1.0
Software:     1.0.6 Build 180627 Rel.081000
MAC (rssi):   blah (-55)
Location:     {'latitude': blah, 'longitude': blah}
== Emeter ==
Current state: {'voltage_mv': 118638, 'power_mw': 9006, 'total_wh': 1307, 'current_ma': 112}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host blah --strip on 5
Turning on..
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host blah --strip on 6
Turning on..
Traceback (most recent call last):
  File "pyHS100/cli.py", line 279, in <module>
    cli()
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/decorators.py", line 64, in new_func
    return ctx.invoke(f, obj, *args[1:], **kwargs)
  File "/home/pi/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "pyHS100/cli.py", line 255, in on
    plug.turn_on_plug(index)
  File "/home/pi/development/pyHS100/pyHS100/smartstrip.py", line 179, in turn_on_plug
    self._index_to_id(index))
  File "/home/pi/development/pyHS100/pyHS100/smartstrip.py", line 200, in _index_to_id
    return self.sys_info["children"][index]["id"]
IndexError: list index out of range

@jimboca
Copy link
Contributor Author

jimboca commented Oct 19, 2018

For now I've changed it to use 1-6 we can keep it that way, or make it 1-6 for cli and 0-5 for api access?

emeter is also working.
https://github.com/jimboca/pyHS100

pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 6
== Emeter ==
Current state: {'power_mw': 20571, 'total_wh': 2793, 'current_ma': 256, 'voltage_mv': 116931}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter
== Emeter ==
Current state: {'current_ma': 113, 'total_wh': 1317, 'voltage_mv': 117611, 'power_mw': 8788}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter
== Emeter ==
Current state: {'current_ma': 112, 'power_mw': 9011, 'voltage_mv': 117677, 'total_wh': 1317}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 1
== Emeter ==
Current state: {'voltage_mv': 117594, 'power_mw': 8843, 'total_wh': 1317, 'current_ma': 113}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 2
== Emeter ==
Current state: {'current_ma': 41, 'voltage_mv': 117594, 'power_mw': 868, 'total_wh': 321}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 3
== Emeter ==
Current state: {'total_wh': 272, 'current_ma': 25, 'voltage_mv': 116965, 'power_mw': 1915}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 4
== Emeter ==
Current state: {'total_wh': 578, 'current_ma': 48, 'voltage_mv': 117346, 'power_mw': 3925}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 5
== Emeter ==
Current state: {'current_ma': 79, 'power_mw': 6334, 'voltage_mv': 117478, 'total_wh': 941}
pi@rpi3-2:~/development/pyHS100 $ python3 pyHS100/cli.py --host 192.168.86.45 --strip emeter 6
== Emeter ==
Current state: {'voltage_mv': 116849, 'power_mw': 20733, 'total_wh': 2793, 'current_ma': 255}

@newAM
Copy link
Contributor

newAM commented Oct 20, 2018

Nice job with the emeter!

There's no good answer for the 0 index or 1 index problem... You can see I was conflicted with it. On one hand zero index makes more sense programmatically, on the other hand the physical device is labeled 1-6. The HS107 is also physically indexed at 1.

@jimboca
Copy link
Contributor Author

jimboca commented Oct 20, 2018

Thanks, your trail blazing made it easier. I'm conflicted as well so not sure. Also I was wondering if the get_id_by_index should be handled in smartdevice otherwise we have to add a bunch of methods in smartstrip and call super with the id.

@newAM
Copy link
Contributor

newAM commented Oct 20, 2018

Yeah - that's a better solution to put it there. I imagine the same child selection structure will be used for future TP-Link devices.

jimboca added a commit to jimboca/pyHS100 that referenced this issue Oct 20, 2018
@rytilahti
Copy link
Collaborator

Do you mind rebasing on top of the current HEAD and creating a WIP pull request for this already? I will then do a brief initial review on it.

@jimboca
Copy link
Contributor Author

jimboca commented Oct 20, 2018

Hope I did it correctly, still not a git expert #137

rytilahti pushed a commit that referenced this issue Jan 8, 2019
* discover runs, prints on since of device 0

* added preliminary support for HS300

* forgot to add smartdevice to commit

* added index to CLI

* clean up dirty code

* added fake sysinfo_hs300

* changed device alias to match MAC

* #131 Move _id_to_index into smartstrip so everyone can pass index

* Update pyHS100/discover.py

Co-Authored-By: jimboca <jimboca3@gmail.com>

* refactoring to deduplicate code between smarplug and smartstrip

* fixing CI failures for devices without children

* incorporating feedback from pull request.

* fixing hound violation

* changed internal store from list of dicts to dict

* changed other methods to dictionary store as well

* removed unused optional type from imports

* changed plugs to Dict, remove redundant sys_info calls

* added more functionality for smart strip, added smart strip tests

* updated FakeTransportProtocol for devices with children

* corrected hound violations

* add click-datetime
rytilahti pushed a commit to rytilahti/pyHS100 that referenced this issue Jun 9, 2019
* discover runs, prints on since of device 0

* added preliminary support for HS300

* forgot to add smartdevice to commit

* added index to CLI

* clean up dirty code

* added fake sysinfo_hs300

* changed device alias to match MAC

* GadgetReactor#131 Move _id_to_index into smartstrip so everyone can pass index

* Update pyHS100/discover.py

Co-Authored-By: jimboca <jimboca3@gmail.com>

* refactoring to deduplicate code between smarplug and smartstrip

* fixing CI failures for devices without children

* incorporating feedback from pull request.

* fixing hound violation

* changed internal store from list of dicts to dict

* changed other methods to dictionary store as well

* removed unused optional type from imports

* changed plugs to Dict, remove redundant sys_info calls

* added more functionality for smart strip, added smart strip tests

* updated FakeTransportProtocol for devices with children

* corrected hound violations

* add click-datetime
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

No branches or pull requests

4 participants