diff --git a/README.md b/README.md index 3c3af1c..abcbcea 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This is a support package for interacting and obtaining information from [tp-lin * HS105 - Smart Plug * HS200 - Smart Switch +* KP200 - Smart Outlet ## Credit @@ -39,6 +40,8 @@ import "github.com/TheSp1der/tplink" ### Interacting with the device +#### SystemInfo + To obtain some basic information from the device use the example code below. To return other objects please examine the SysInfo struct. ``` go @@ -65,18 +68,51 @@ Name: Office Light Power: On ``` +#### Change power state + +##### HSXXX or single switch/outlet device + To turn a device on or off: ``` go // turn on -d := tplink.Tplink{Host: "light-office.tplink.example.com"} -if err := d.TurnOn(); err != nil { +d := tplink.Tplink{ + Host: "light-office.tplink.example.com", +} +if err := d.ChangeState(1); err != nil { log.Fatal(err) } // turn off -d := tplink.Tplink{Host: "light-office.tplink.example.com"} -if err := d.TurnOff(); err != nil { +d := tplink.Tplink{ + Host: "light-office.tplink.example.com", +} +if err := d.ChangeState(0); err != nil { + log.Fatal(err) +} +``` + +##### KPXXX or multi switch/outlet device + +The SwitchID is typically number starting at 0 for the first outlet/switch on the hardware. +The following code identifies the first outlet/switch on the device: + +``` go +// turn on +d := tplink.Tplink{ + Host: "light-office.tplink.example.com", + SwitchID: 1, +} +if err := d.ChangeStateMultiSwitch(1); err != nil { + log.Fatal(err) +} + +// turn off +d := tplink.Tplink{ + Host: "light-office.tplink.example.com", + SwitchID: 1, +} +if err := d.ChangeStateMultiSwitch(0); err != nil { log.Fatal(err) } ``` diff --git a/examples/remote-control.go b/examples/remote-control.go deleted file mode 100644 index 01662f8..0000000 --- a/examples/remote-control.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "strings" - - "github.com/TheSp1der/tplink" -) - -func main() { - var ( - host string - getState bool - turnOn bool - turnOff bool - ) - - flag.StringVar(&host, "host", "", "hostname of device") - flag.BoolVar(&getState, "get-state", false, "get device power state") - flag.BoolVar(&turnOn, "on", false, "turn the device on") - flag.BoolVar(&turnOff, "off", false, "turn the device off") - flag.Parse() - - if len(strings.TrimSpace(host)) == 0 { - fmt.Println("host must be defined") - flag.PrintDefaults() - os.Exit(100) - } - - if !getState && !turnOn && !turnOff { - fmt.Println("an action must be defined") - flag.PrintDefaults() - os.Exit(100) - } - - if getState && (turnOn || turnOff) { - fmt.Println("only one action may be defined") - flag.PrintDefaults() - os.Exit(100) - } - - if turnOn && turnOff { - fmt.Println("only one action may be defined") - flag.PrintDefaults() - os.Exit(100) - } - - device := tplink.Tplink{Host: host} - - if getState { - response, err := device.SystemInfo() - if err != nil { - fmt.Println("unable to communicate with device") - os.Exit(100) - } - if response.System.GetSysinfo.RelayState == 1 { - fmt.Println(host + " is ON") - } else { - fmt.Println(host + " is OFF") - } - } - - if turnOn { - if err := device.TurnOn(); err != nil { - fmt.Println("unable to communicate with device") - os.Exit(100) - } - } - if turnOff { - if err := device.TurnOff(); err != nil { - fmt.Println("unable to communicate with device") - os.Exit(100) - } - } -} diff --git a/tplink.go b/tplink.go index 73c1529..4518291 100644 --- a/tplink.go +++ b/tplink.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "io" + "log" "net" "time" @@ -12,6 +13,10 @@ import ( "encoding/json" ) +// This is the key by which all bytes sent/received from tp-link hardware are +// obfuscated. +const hashKey byte = 0xAB + // SysInfo A type used to return information from tplink devices type SysInfo struct { System struct { @@ -61,7 +66,8 @@ type SysInfo struct { // Tplink Device host identification type Tplink struct { - Host string + Host string + SwitchID int } type getSysInfo struct { @@ -79,7 +85,7 @@ type changeState struct { } `json:"system"` } -type changeStateChild struct { +type changeStateMultiSwitch struct { Context struct { ChildIds []string `json:"child_ids"` } `json:"context"` @@ -93,10 +99,12 @@ type changeStateChild struct { func encrypt(plaintext string) []byte { n := len(plaintext) buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, uint32(n)) + if err := binary.Write(buf, binary.BigEndian, uint32(n)); err != nil { + log.Printf("[WARNING] tplink.go: Unable to write to buffer %v\n", err) + } ciphertext := buf.Bytes() - key := byte(0xAB) + key := hashKey payload := make([]byte, n) for i := 0; i < n; i++ { payload[i] = plaintext[i] ^ key @@ -112,7 +120,7 @@ func encrypt(plaintext string) []byte { func decrypt(ciphertext []byte) string { n := len(ciphertext) - key := byte(0xAB) + key := hashKey var nextKey byte for i := 0; i < n; i++ { nextKey = ciphertext[i] @@ -126,25 +134,31 @@ func send(host string, dataSend []byte) ([]byte, error) { var header = make([]byte, 4) // establish connection (two second timeout) - conn, err := net.DialTimeout("tcp", host+":9999", time.Second*2) + conn, err := net.DialTimeout("tcp", host+":9999", time.Second*2) //nolint:gomnd if err != nil { - return []byte(""), err + return []byte{}, err } - defer conn.Close() + defer func() { + if err := conn.Close(); err != nil { //nolint:govet + log.Fatalf("[ERROR] tplink.go: Unable to close connection: %v\n", err) + } + }() // submit data to device writer := bufio.NewWriter(conn) _, err = writer.Write(dataSend) if err != nil { - return []byte(""), err + return []byte{}, err + } + if err := writer.Flush(); err != nil { //nolint:govet + return []byte{}, err } - writer.Flush() // read response header to determine response size - headerReader := io.LimitReader(conn, int64(4)) + headerReader := io.LimitReader(conn, int64(4)) //nolint:gomnd _, err = headerReader.Read(header) if err != nil { - return []byte(""), err + return []byte{}, err } // read response @@ -153,7 +167,7 @@ func send(host string, dataSend []byte) ([]byte, error) { var response = make([]byte, respSize) _, err = respReader.Read(response) if err != nil { - return []byte(""), err + return []byte{}, err } return response, nil @@ -188,25 +202,17 @@ func (s *Tplink) SystemInfo() (SysInfo, error) { return jsonResp, nil } -// TurnOn Device state change to turn remote device on -func (s *Tplink) TurnOn() error { +// ChangeState changes the power state of a single port device +// True = on +// False = off +func (s *Tplink) ChangeState(state bool) error { var payload changeState - payload.System.SetRelayState.State = 1 - - j, _ := json.Marshal(payload) - data := encrypt(string(j)) - if _, err := send(s.Host, data); err != nil { - return err + if state { + payload.System.SetRelayState.State = 1 + } else { + payload.System.SetRelayState.State = 0 } - return nil -} - -// TurnOff Device state change to turn remote device off -func (s *Tplink) TurnOff() error { - var payload changeState - - payload.System.SetRelayState.State = 0 j, _ := json.Marshal(payload) data := encrypt(string(j)) @@ -216,37 +222,23 @@ func (s *Tplink) TurnOff() error { return nil } -// TurnOnChild Device state change to turn remote device on with multiple controls -func (s *Tplink) TurnOnChild(id int) error { - var payload changeStateChild +// ChangeStateMultiSwitch changes the power state of a device on with multiple outlets/switches +// True = on +// False = off +func (s *Tplink) ChangeStateMultiSwitch(state bool) error { + var payload changeStateMultiSwitch devID, err := getDevID(s) if err != nil { return err } - payload.Context.ChildIds = append(payload.Context.ChildIds, devID+fmt.Sprintf("%02d", id)) - payload.System.SetRelayState.State = 1 - - j, _ := json.Marshal(payload) - data := encrypt(string(j)) - if _, err := send(s.Host, data); err != nil { - return err + payload.Context.ChildIds = append(payload.Context.ChildIds, devID+fmt.Sprintf("%02d", s.SwitchID)) + if state { + payload.System.SetRelayState.State = 1 + } else { + payload.System.SetRelayState.State = 0 } - return nil -} - -// TurnOffChild Device state change to turn remote device off with multiple controls -func (s *Tplink) TurnOffChild(id int) error { - var payload changeStateChild - - devID, err := getDevID(s) - if err != nil { - return err - } - - payload.Context.ChildIds = append(payload.Context.ChildIds, devID+fmt.Sprintf("%02d", id)) - payload.System.SetRelayState.State = 0 j, _ := json.Marshal(payload) data := encrypt(string(j))