package main import ( "context" "encoding/binary" "fmt" "net" "time" ) const ( MAX_PAYLOAD = 4294967295 // TP-Link WiFi plug protocol: max. 2^32-1 bytes ) // Hs100 bundles every data associated with one TP-Link HS100 smart plug. type Hs100 struct { Config Hs100Conf } // Hs100Conf is the configuration of one TP-Link HS100 smart plug. type Hs100Conf struct { Ip net.IP Name string } // encrypt() encrypts data for a TP-Link WiFi plug. func encrypt(data []byte) ([]byte, error) { if len(data) > MAX_PAYLOAD { return []byte{}, fmt.Errorf("Too many bytes to encrypt (%d > %d)!\n", len(data), MAX_PAYLOAD) } length := uint32(len(data)) out := make([]byte, 4) binary.BigEndian.PutUint32(out, length) key := byte(171) for _, value := range data { key = key ^ value out = append(out, byte(key)) } return out, nil } // decrypt() decrypts data coming from a TP-Link WiFi plug. func decrypt(data []byte) []byte { // TODO check if length given in header is correct data = data[4:] key := byte(171) for index, value := range data { data[index] = key ^ value key = value } return data } // send() sends data via TCP to an address (like "192.168.1.42:9999"). func send(address string, data []byte) error { var d net.Dialer ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() conn, err := d.DialContext(ctx, "tcp", address) if err != nil { return fmt.Errorf("Failed to dial: %v", err) } defer conn.Close() _, err = conn.Write(data) if err != nil { return fmt.Errorf("Could not write data: %v", err) } return nil } // set() sets the relay state of a TP-Link WiFi plug. func set(host string, state string) error { cmd := "" if state == "on" { cmd = `{"system":{"set_relay_state":{"state":1}}}` } else if state == "off" { cmd = `{"system":{"set_relay_state":{"state":0}}}` } else { return fmt.Errorf("set() just accepts values 'on' and 'off'!") } address := fmt.Sprintf("%s:9999", host) data, err := encrypt([]byte(cmd)) if err != nil { return err } err = send(address, data) return err }