package main import ( "context" "encoding/binary" "fmt" "log" "net" "time" "strings" ) const ( MAX_PAYLOAD = 4294967295 // TP-Link WiFi plug protocol: max. 2^32-1 bytes TPLink_HS100_ON = true TPLink_HS100_OFF = false ) func TPLinkRun(config TPLinkConfigs, tx chan MQTTMessage, route Route) { for _, tplink := range config { tx <- MQTTMessage{fmt.Sprintf("plug/%s", tplink.ID), []byte("exists"), true} } for message := range route.Destination { ip, action, err := tplinkParseMessage(config, message) if err != nil { log.Println(err) continue } err = set(*ip, action) if err != nil { log.Printf("Could not send action '%v' to %v: %v", action, ip, err) } } } func tplinkParseMessage(config TPLinkConfigs, m MQTTMessage) (ip *net.IP, action bool, err error) { elements := strings.Split(m.Topic, "/") if len(elements) != 3 { return nil, false, fmt.Errorf( "Expected three topic levels but got %d in '%s'.", len(elements), m.Topic, ) } if elements[0] != "plug" || elements[2] != "action" { return nil, false, fmt.Errorf("Expected plug//action but got: %s", m.Topic) } switch string(m.Payload) { case "on": action = TPLink_HS100_ON case "off": action = TPLink_HS100_OFF default: return nil, false, fmt.Errorf("Invalid payload '%s'.", m.Payload) } id := elements[1] for _, c := range config { if c.ID == id { ip := net.ParseIP(c.IP) return &ip, action, nil } } return nil, false, fmt.Errorf("Got message for unknown plug '%s'", id) } 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 } 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 } func set(ip net.IP, state bool) error { cmd := "" switch state { case TPLink_HS100_ON: cmd = `{"system":{"set_relay_state":{"state":1}}}` case TPLink_HS100_OFF: cmd = `{"system":{"set_relay_state":{"state":0}}}` } address := fmt.Sprintf("%s:9999", ip.String()) data, err := encrypt([]byte(cmd)) if err != nil { return err } err = send(address, data) return err }