plasticrake / tplink-smarthome-api

TP-Link Smarthome WiFi API
MIT License
1.03k stars 142 forks source link

LB120(US) lightbulb support #6

Closed konsumer closed 7 years ago

konsumer commented 7 years ago

I have 2 LB120(US) lightbulbs that work correctly in the Kasa app. When I run this demo code, it only finds 1 of them:

const Hs100Api = require('hs100-api')
const client = new Hs100Api.Client()
client.startDiscovery().on('plug-new', p => console.log(p.name))
konsumer commented 7 years ago

Also, possibly unrelated, this code doesn't make the lights flash (it doesn't do anything):

const Hs100Api = require('hs100-api')
const client = new Hs100Api.Client()

const plug1 = client.getPlug({host: '10.0.0.200'})
const plug2 = client.getPlug({host: '10.0.0.81'})
let toggle = true

setInterval(() => {
  toggle = !toggle
  plug1.setPowerState(toggle)
  plug2.setPowerState(!toggle)
}, 3000)
konsumer commented 7 years ago

I also tried this:

const Hs100Api = require('hs100-api')
const client = new Hs100Api.Client()
client.startDiscovery()
  .on('plug-online', plug => {
    console.log('on', plug.name)
  })
  .on('plug-offline', plug => {
    console.log('off', plug.name)
  })
  .on('plug-new', plug => {
    console.log('new', plug.name)
  })

And I got this, after running it for a few minutes:

new Front Lamp
on Front Lamp
on Front Lamp
on Front Lamp
on Front Lamp
on Front Lamp

Front Lamp seems to be the only one it detects.

Happy to do anything else that helps troubleshoot.

konsumer commented 7 years ago

If it's helpful, the Firmware Version in Kasa app is 1.0.9. Maybe I need to do some wiresharking to reverse it?

konsumer commented 7 years ago

I must be doing something wrong with my wiresharking. I filter eth.src == xx:xx:xx:xx:xx:xx (where xx:xx:xx:xx:xx:xx is MAC in Kasa app) and trigger power in app and it doesn't show anything. Any suggestions would be awesome.

konsumer commented 7 years ago

Ok, so I setup a wifi AP on a raspberry pi, and captured just traffic for the mac address of the bulb with tcpdump. I analyzed it with this code:

const pcapp = require('pcap-parser')

const decrypt = require('hs100-api/lib/utils').decrypt

const parser = pcapp.parse('front-lamp.pcap')

parser.on('packet', packet => {
  console.log(decrypt(packet.data).toString())
})

I am getting lots of gibberish mixed with JSON snippets, like this:

�.�;�x�l!EE,.,.@F��a̴nH(��!G)Y����~r�;�e�
{�x�l!�.�;EE(t
              @@F�(�nôa��(����G)Vh@�Kr�
@@F����nôa��(����G)VhH��ji�"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":0,"transition_period":0}}}
�.�;�x�l!EE(*-/@F��̴nH(��!G)V����N@�a�
�̴nH(��!G)V���F�@ ��+
�.�;�x�l!EE��31@F�N�a̴nH(��!G)V���F�H   ���j"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":0,"dft_on_state":{"mode":"normal","hue":0,"saturation":0,"color_temp":2700,"brightness":90},"err_code":0}}}
{�x�l!�.�;EE(t@@F�.�nôa��(���F�G)��@�L(�
{�x�l!�.�;EE(t@@F�-�nôa��(���F�G)��A�L)�
�.�;�x�l!EE(*02@F��a̴nH(��!G)�P��F�@�5�
�.�;�x�l!EE(*13@F��a̴nH(��!G)�P��A�@ ��l
�.�;�x�l!EE(*64@F��a̴nH(��!G)�P��A�A�7�
{�x�l!�.�;EE(��|@@F
�8�nôa��(���A�G)��@E',y'
{�x�l!�.�;EE<��'@@FG��nôa��(��*�����rr���
�^�

�.�;�x�l!EE,.75@F��a̴nH(��9J�e�*��r��S]�
{�x�l!�.�;EE(��(@@FG)Ĵnôa��(��*��J��@�ԕ�
{�x�l!�.�;EE�
             �)@@FF�V�nôa��(��*��J��H��^i�"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":1,"transition_period":0}}}
�.�;�x�l!EE(*46@F��a̴nH(��9J�d�*��@��
�.�;�x�l!EE(*57@F��a̴nH(��9J�d�5F@ 3>#
�.�;�x�l!EE��:8@F�@�a̴nH(��9J�d�5FH >9�y"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":1,"mode":"normal","hue":0,"saturation":0,"color_temp":2700,"brightness":90,"err_code":0}}}
{�x�l!�.�;EE(��*@@FG'ʴnôa��(��5J<2@�Վ�
{�x�l!�.�;EE(��+@@FG$ɴnôa��(��5J<2A�Չ�
�.�;�x�l!EE(*;9@F��a̴nH(��9J<��5F@��
�.�;�x�l!EE(*8:@F��a̴nH(��9J<��5G@ 2iu
�.�;�x�l!EE(*9;@F��a̴nH(��9J<��5GA��
{�x�l!�.�;EE(��:@@F
Z��nôa��(��5J=3@E�V�

Should I be analyzing it differently?

plasticrake commented 7 years ago

I don't have any TPLink lightbulbs to test. If you can figure out the API I might be able to add support, but hard for me to do anything without a lightbulb of my own.

konsumer commented 7 years ago

I hear that. It'd be cool if your lib could auto-detect API version and use the right decrypt function. I will keep playing with it and see if I can reverse it's API. Do you have any suggestions for getting started?

konsumer commented 7 years ago

I'm happy to send you pcaps of anything, or anything else that might help.

konsumer commented 7 years ago

This wireshark dissector actually worked out all the lower-level protocol stuff, and I can now get nice JSON output for all the data sent to/from the Kasa app. I'll have a look this weekend, and see if I can reverse the protocol a bit.

DaveGut commented 7 years ago

Konsumer, I am also interested in the LB120 bulb. I have one of these as well as HS200 switches and an HS100 plug. I am pretty much convinced that the ports are the same; however, the commands vary. I ran an experiment with one of each device type. All five of my devices were discover; however, only the HS100 and HS200 turned on. I borrowed heavily from what you did above for this (thanks).

const Hs100Api = require('hs100-api') const client = new Hs100Api.Client()

// Define the three test devices. Use the HS200 and HS100 as controls and the LB120 as the device under test. const LB120 = client.getPlug({host: '192.168.1.131'}) // Bedroom Light const HS200 = client.getPlug({host: '192.168.1.133'}) // Den Fan const HS100 = client.getPlug({host: '192.168.1.135'}) // Den Fan

// Verify that I discover the all my TP Link Devices client.startDiscovery().on('plug-new', p => console.log(p.name))

HS200.setPowerState(true) // Worked HS100.setPowerState(true) // Worked LB120.setPowerState(true) // Did not work

Dave G

konsumer commented 7 years ago

@DaveGut I ended up making this which is specialized to the lightbulb (but should work fine with other JSON protocols, once they are worked out) and includes an improved wireshark dissector. Here are some instructions for using it. I'd be happy to add support for more devices/commands if you want to send me a pcap or just JSON dumps that are being sent.

DaveGut commented 7 years ago

Thanks. I will look at it! First look is a lot of good work.

DaveGut commented 7 years ago

Konsumer, I have been experimenting. One experiment was getting a system status using the sysinfo query. I finally fully parsed the basic command with the below being the result. image Essentially, I am able to query most of the status attributes and display them individually. If you are interested, I can post the code and give you a link (I know this is way below your level of expertise; but, I am learning, Not bad for a 68 yo)

konsumer commented 7 years ago

Cool! Yeah, I'd love to see the code.

DaveGut commented 7 years ago

Below is the code. I will someday get into github (just starting out). Sorry for the inconvenience. START HERE // Testing of the getSysInfo command for the LB1xx light bulb

/* Notes This script uses the upreviously developed, unmodified HS100 API. It was completed as an exercise to fully understand how the system is controlled.

Issue: The program sometimes fails when the light is OFF. Works when ON and right after turning off. /*

const Hs100Api = require('hs100-api') const client = new Hs100Api.Client()

// Define the bulb, using the HS100 API const BRLight = client.getPlug({host: '192.168.0.131'})

// Get the System Information and parsing into Attributes var j = Promise.resolve(BRLight.getSysInfo()) j.then(function(data){ var sysinfo = (data); sw_ver = sysinfo.sw_ver; hw_ver = sysinfo.hw_ver; model = sysinfo.model; description = sysinfo.description; //Description of bulb alias = sysinfo.alias; // local name for bulb mic_type = sysinfo.mic_type dev_state = sysinfo.dev_state mic_mac = sysinfo.mic_mac //MAC of bulb devId = sysinfo.devId oemId = sysinfo.oemId hwId = sysinfo.hwId disco_ver = sysinfo.disco_ver prot_name = sysinfo.ctrl_protocols.name prot_version = sysinfo.ctrl_protocols.version on_off = sysinfo.light_state.on_off mode = sysinfo.light_state.mode brightness = sysinfo.light_state.brightness color_temp = sysinfo.light_state.color_temp hue = sysinfo.light_state.hue saturation = sysinfo.light_state.saturation

// Display the full set of attributes collected onto the console console.log(" LB1XX Bulb Information ") console.log(" Alias: " + alias) console.log(" Model: " + model) console.log(" Description: " + description) console.log(" Device Type: " + mic_type) console.log(" Device State: " + dev_state) console.log("Device MAC Adress: " + mic_mac) console.log(" SW Version: " + sw_ver) console.log(" HW Version: " + hw_ver) console.log(" ") console.log(" Currrent Bulb State ") console.log(" Light On/off: " + on_off) console.log(" Bulb Mode: " + mode) console.log(" Bulb Brightness: " + brightness) console.log("Color_Temperature: " + color_temp) console.log(" Color Hue: "+ hue) console.log(" Color Saturation: " + saturation) console.log(" ") console.log(" LB1XX Bulb Other Information ") // A repository for additional attributes as I decide to capture them console.log(" Device ID: " + devId) console.log(" OEM ID: " + oemId) console.log(" HW ID: " + hwId) console.log(" Disco Version: " + disco_ver) console.log("Control Protocol: " + prot_name +" Ver "+ prot_version) })

konsumer commented 7 years ago

Cool, I put it up here for readability. You can fork it and edit/save, if you wanna make changes. Looks good.

konsumer commented 7 years ago

I reimplemented it using my library, ES6 template strings & object destructuring for readability. I noticed a few of my info object-keys are different than yours for some reason, specifically:

deviceId: devId
light_state: {on_off, dft_on_state: {mode, brightness, color_temp, hue, saturation}}

When I run it, I get the same sort of output you get:

LB1XX Bulb Information
 Alias: Front Lamp
 Model: LB120(US)
 Description: Smart Wi-Fi LED Bulb with Tunable White Light
 Device Type: IOT.SMARTBULB
 Device State: normal
 Device MAC Adress: --REMOVED---
 SW Version: 1.0.9 Build 160524 Rel.100333
 HW Version: 1.0

Currrent Bulb State
 Light On/off: 0
 Bulb Mode: normal
 Bulb Brightness: 100
 Color_Temperature: 2700
 Color Hue: 0
 Color Saturation: 0

LB1XX Bulb Other Information
 Device ID: --REMOVED---
 OEM ID: --REMOVED---
 HW ID: --REMOVED---
 Disco Version: 1.0
 Control Protocol: Linkie Ver 1.0
konsumer commented 7 years ago

And talking to you reminded me to implement an info() function, so thanks!

DaveGut commented 7 years ago

I have modified the HS100.API (new name LB1nn) and created the device handler for SmartThings. It works. Can send as zip file if you wish.

DaveGut commented 7 years ago

Konsumer. I have now found and tested 17 commands on the API. Including energy monitor functions. I know there are others. My data is now at: https://github.com/DaveGut/TP-Link-Bulbs

akath20 commented 7 years ago

@DaveGut what happened to that repo?

DaveGut commented 7 years ago

I have reloaded the program for any interested in the API aspects. The SmartThings integration is depreciated; however, i believe it still works. My new SmartThings integration is at https://github.com/DaveGut/TP-Link-to-SmartThings-Integration

Why: a. single Node.js file eliminating calls to the api files (and error potentials) b. more informative user interface with indications when command fail, or bulbs disconnect. c. has hooks to the bridge integration (SmartThing-PC-Bridge) which provided mechanism to reboot the bridge from SmartThings.

FYI - I am working on a ST Service Manager and associated Device Handlers that will discover the PC Bridge and then the TP-Link devices and install them automatically (after the user installs the service manager and related device handlers). I should also follow the bulbs so that static ports will not the necessary. Probably a couple of weeks until I am happy. Then back to the API with things I have learned.

plasticrake commented 7 years ago

If anyone in this thread is interested: I created a TPLink device simulator so I could have automated testing of my code. It also allowed me to improve bulb support for hs100-api as I can test bulbs even though I don't own any.