├── LICENSE ├── readme.md ├── script.js └── index.html /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andy Allsopp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## TP-Link HS100 Web Client 2 | 3 | ### Why? 4 | 5 | TP-Link give you access to your HS-100 range devices through the KASA app on IOS / Android. That's fine, but what if you're 6 | in front of your desktop / tablet / etc, and don't have your phone to hand? How annoying to have to fish it out of your pocket 7 | just to flick a switch. 8 | 9 | This angular material web-page provides the service TP-Link omitted. You can log into the TP-Link API to generate a service token, 10 | have it look up the devices stored against your account, monitor and set their power state. 11 | 12 | If you've just added TP-Link devices to a smart home network and want to be able to control them all from one place, you can 13 | examine the source of this project and utilise the calls within your own pages. Or just use this one :) 14 | 15 | 16 | ### Features: 17 | 18 | - generates a UUID 19 | - capture credentials. 20 | - authenticates against the tp-link api service to get a secure authentication token 21 | - uses that token to: 22 | - list the devices stored against your account 23 | - send on/off commands to them. 24 | - reflect whether the devices are currently on or not. 25 | - optionally stores the token (and/or credentials) so that you can jump straight to it next time. 26 | 27 | 28 | ### Notes: 29 | 30 | If you opt to store your token or credentials, these are held in a browser cookie on *your* machine. In use, they are sent directly from your 31 | browser to the TP-Link API endpoint via https, and are not sent to any other web host. 32 | 33 | ### To do: 34 | 35 | Look for further features. 36 | 37 | ### Implementation: 38 | 39 | See codepen at: https://codepen.io/arallsopp/pen/pdZQWG -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('myApp', ['ngMaterial','ngCookies']); 2 | 3 | myDash.$inject = ['$scope', '$mdToast','$http','$interval','$cookies']; 4 | 5 | angular.module('myApp').controller('dash', myDash) 6 | .config(['$mdThemingProvider',function($mdThemingProvider) { 7 | $mdThemingProvider.theme('custom').primaryPalette('blue-grey').accentPalette('deep-orange'); 8 | }]); 9 | 10 | function myDash($scope, $mdToast, $http, $interval, $cookies) { 11 | 12 | $scope.getUUID = function () { 13 | var d = new Date().getTime(); 14 | if (window.performance && typeof window.performance.now === "function") { 15 | d += performance.now(); 16 | //use high-precision timer if available 17 | } 18 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 19 | var r = (d + Math.random() * 16) % 16 | 0; 20 | d = Math.floor(d / 16); 21 | return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); 22 | }); 23 | return uuid; 24 | }; 25 | 26 | $scope.showToast = function (msg) { 27 | $mdToast.show($mdToast.simple().textContent(msg).position('top right')) 28 | }; 29 | 30 | $scope.tpl_authenticate = function () { 31 | $scope.tpl.UUID = $scope.getUUID(); 32 | 33 | var auth_obj = { 34 | "method": "login", "params": { 35 | "appType": "Kasa_Android", 36 | "cloudUserName": $scope.tpl.username, 37 | "cloudPassword": $scope.tpl.password, 38 | "terminalUUID": $scope.tpl.UUID 39 | } 40 | }; 41 | 42 | $http.post("https://wap.tplinkcloud.com/", auth_obj).then(function mySuccess(response) { 43 | var now = new Date(); 44 | var exp = new Date(now.getFullYear()+1, now.getMonth(), now.getDate()); 45 | 46 | $scope.tpl.token = response.data.result.token; 47 | 48 | $cookies.put('tpl_uuid', $scope.tpl.UUID,{expires: exp}); 49 | if($scope.tpl.store_credentials) { 50 | $cookies.put('tpl_username', $scope.tpl.username,{ expires: exp}); 51 | $cookies.put('tpl_password', $scope.tpl.password,{ expires: exp}); 52 | $cookies.put('tpl_store_credentials',true,{ expires: exp}); 53 | 54 | }else{ 55 | $cookies.remove('tpl_username'); 56 | $cookies.remove('tpl_password'); 57 | $cookies.remove('tpl_store_credentials'); 58 | } 59 | 60 | if($scope.tpl.store_token) { 61 | $cookies.put('tpl_token', $scope.tpl.token,{ expires: exp}); 62 | $cookies.put('tpl_store_token',true,{ expires: exp}); 63 | 64 | }else{ 65 | $cookies.remove('tpl_token'); 66 | $cookies.remove('tpl_store_token'); 67 | } 68 | 69 | $scope.tpl_refreshDevices(); 70 | 71 | }, function myError(response) { 72 | $scope.myWelcome = response.statusText; 73 | }); 74 | 75 | }; 76 | 77 | $scope.tpl_refreshDevices = function () { 78 | var request_obj = {"method": "getDeviceList"}; 79 | 80 | $http.post("https://wap.tplinkcloud.com?token=" + $scope.tpl.token, request_obj).then(function mySuccess(response) { 81 | $scope.tpl.devices = (response.data.result.deviceList); 82 | console.log($scope.tpl.devices); 83 | if ($scope.tpl.devices.length) { 84 | for (var i = 0; i < $scope.tpl.devices.length; i++) { 85 | $scope.tpl_getState(i); 86 | } 87 | 88 | $scope.selected_tab_index = 0; 89 | } 90 | }); 91 | 92 | 93 | }; 94 | 95 | $scope.tpl_getState = function (device_index) { 96 | var url = $scope.tpl.devices[device_index].appServerUrl; 97 | var deviceId = $scope.tpl.devices[device_index].deviceId; 98 | 99 | var request_obj = { 100 | "method": "passthrough", "params": { 101 | "deviceId": deviceId, 102 | "requestData": "{\"system\":{\"get_sysinfo\":null},\"emeter\":{\"get_realtime\":null}}" 103 | } 104 | }; 105 | $http.post(url + "?token=" + $scope.tpl.token, request_obj).then(function mySuccess(response) { 106 | window.response = response; 107 | var testval = JSON.parse(response.data.result.responseData).system.get_sysinfo.relay_state; 108 | console.log(device_index, testval, response); 109 | 110 | $scope.tpl.devices[device_index].is_powered = (testval == true); 111 | }); 112 | }; 113 | 114 | $scope.tpl_setState = function (device_index, device_state) { 115 | var url = $scope.tpl.devices[device_index].appServerUrl; 116 | var deviceId = $scope.tpl.devices[device_index].deviceId; 117 | 118 | var request_obj = { 119 | "method": "passthrough", "params": { 120 | "deviceId": deviceId, 121 | "requestData": "{\"system\":{\"set_relay_state\":{\"state\":" + (device_state ? 1 : 0 ) + "}}}" 122 | } 123 | }; 124 | $http.post(url + "?token=" + $scope.tpl.token, request_obj).then(function mySuccess(response) { 125 | window.response = response; 126 | console.log(response); 127 | }); 128 | }; 129 | 130 | $scope.tpl = {}; 131 | $scope.tpl.refresh_rate = 5; //check every 5 seconds. Set this as you fancy. 132 | 133 | $scope.tpl.devices = []; 134 | $scope.tpl.store_token = $cookies.get('tpl_store_token') == "true"; 135 | $scope.tpl.store_credentials = $cookies.get('tpl_store_credentials') == "true"; 136 | $scope.tpl.UUID = $cookies.get('tpl_uuid'); 137 | 138 | if($scope.tpl.store_credentials){ 139 | $scope.tpl.username = $cookies.get('tpl_username'); 140 | $scope.tpl.password = $cookies.get('tpl_password'); 141 | } 142 | if($scope.tpl.store_token) { 143 | $scope.tpl.token = $cookies.get('tpl_token'); 144 | } 145 | $scope.selected_tab_index = 0; 146 | 147 | 148 | if (typeof ($scope.tpl.token) === "undefined") { 149 | $scope.tpl.token = ''; 150 | } else { 151 | $scope.tpl_refreshDevices(); 152 | } 153 | 154 | $interval(function () { 155 | for (var i = 0; i < $scope.tpl.devices.length; i++) { 156 | $scope.tpl_getState(i); 157 | } 158 | }, $scope.tpl.refresh_rate * 1000); 159 | 160 | } 161 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |{{device.deviceModel}} - {{device.deviceName}}
29 |50 | Please provide the credentials used to connect to your TP-Link account. 51 |
52 | 53 |Found {{tpl.devices.length}} devices using token: {{tpl.token}}
This webpage uses angular services to build a web control surface for the TP-Link series of smart 112 | plugs. TP-Link's own official app (Kasa) only runs on iOS and Android devices, so those of you 113 | with other tablets, mobiles, or desktops get left in the cold. This page attempts to address that.
114 | 115 |In use, the Kasa app generates a unique ID, then combines this with the registration information you 118 | supply at sign-up to retrieve a secure token from a TP-Link hosted API service. Requests for smart plug 119 | availability, status and relay states are sent with this token to protect your privacy.
120 | 121 |This application is able to generate a random UUID of its own, capture your credentials, and pass these 122 | to the TP-Link hosted API service to obtain a genuine token. That token can then be used to list the devices 123 | which are present, indicate their status, and allow you to switch them on/off from anywhere you like.
124 | 125 |Information is only sent to and from the TP-Link APIs, and is not exposed to any other server. If you 126 | opt to retain your login credentials and/or authentication token, these are stored in cookies within your 127 | browser. They are not released to me or anyone else.
128 | 129 |To access your plugs, make sure they have remote control enabled, and are registered with the 130 | official Kasa App from TP-Link (Get 132 | Kasa here). Unfortunately, I don't know a way to perform the registration without (at least) 133 | temporary access to the official app, but once registered you can send requests from any modern browser.
134 | 135 |The full source for this project is available for free, online at GitHub. 137 |
138 | 139 |I hope you are able to make use of it.
140 |Andy Allsopp
143 | 144 |