ZYB ARTICLES REPOS

用go语言实现在腾讯云iothub的MQTT交互

发送方

每隔5秒推送一次数据

package main

import (
	"crypto/tls"
	"fmt"
	MQTT "github.com/eclipse/paho.mqtt.golang"
	"math/rand"
	"time"
)

const qos = 0
const productID = "这里填产品ID"
const deviceName = "Sender" // 设备名称

var topic = productID + "/" + deviceName + "/event"
var clientid = productID + deviceName
var username = "需要按算法生成,参见下文"
var password = "需要按算法生成,参见下文"
var server = "tcp://" + productID + ".iotcloud.tencentdevices.com:1883"

func main() {
	fmt.Println(topic)

	connOpts := MQTT.NewClientOptions().AddBroker(server).SetClientID(clientid).SetCleanSession(true)
	connOpts.SetUsername(username)
	connOpts.SetPassword(password)

	tlsConfig := &tls.Config{InsecureSkipVerify: true, ClientAuth: tls.NoClientCert}
	connOpts.SetTLSConfig(tlsConfig)

	client := MQTT.NewClient(connOpts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		fmt.Println(token.Error())
		return
	}
	fmt.Printf("Connected to %s\n", server)

	for {
		tmstr := time.Now().Format("2006-01-02 15:04:05")

		var message string

		// 如果要用数据模板,可参考数据模板协议:
		// https://cloud.tencent.com/document/product/1081/34916
		message = fmt.Sprintf(`
		{
		  "clientToken": "Server",
		  "method": "report",
		  "params": {
			"data": "%v",
			"time": "%v"
		  }
		}
		`, rand.Intn(10000), tmstr)

		// 或者直接自定义数据
		message = fmt.Sprintf("{\"data\":\"%v\", \"time\":\"%v\"}", rand.Intn(10000), tmstr)
		fmt.Printf("MQTT SENDER: %s\n", message)
		client.Publish(topic, byte(qos), false, message)
		time.Sleep(5 * time.Second)
	}
}

接收方

package main

import (
	"crypto/tls"
	MQTT "github.com/eclipse/paho.mqtt.golang"
	"log"
	"os"
	"os/signal"
	"syscall"
)

const qos = 0
const productID = "这里填产品ID"
const deviceName = "Receiver"  // 设备名称

var topic = productID + "/" + deviceName + "/data"
var clientid = productID + deviceName
var username = "需要按算法生成,参见下文"
var password = "需要按算法生成,参见下文"
var server = "tcp://" + productID + ".iotcloud.tencentdevices.com:1883"

func main() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	sub_topics := []string{
		topic,
	}

	connOpts := MQTT.NewClientOptions().AddBroker(server).SetClientID(clientid).SetCleanSession(true)
	connOpts.SetUsername(username)
	connOpts.SetPassword(password)

	tlsConfig := &tls.Config{InsecureSkipVerify: true, ClientAuth: tls.NoClientCert}
	connOpts.SetTLSConfig(tlsConfig)
	connOpts.SetDefaultPublishHandler(onMessageReceived)

	client := MQTT.NewClient(connOpts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		log.Println(token.Error())
		return
	}
	log.Printf("Connected to %s\n", server)

	for _, topic := range sub_topics {
		if token := client.Subscribe(topic, byte(qos), nil); token.Wait() && token.Error() != nil {
			log.Println(token.Error())
			return
		}
	}

	<-c
}

var onMessageReceived MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
	log.Printf("MQTT RECEIVER: topic: %s  Message: %s\n", msg.Topic(), msg.Payload())
}

建立数据转发规则

要使发送方的数据能正常到达接收方,还需要在腾讯云iothub平台的规则引擎里添加并启用一条规则,就是: 把Sender发往topic为xxx/Sender/event的数据转发到Receiver能接收的topic: xxx/Receiver/data

生成认证信息的方法

上文代码中的usernamepassword的生成算法

// TencentIotHubMQTTAuthInfo 腾讯云 Iot Hub MQTT 认证信息
type TencentIotHubMQTTAuthInfo struct {
	ClientID string `json:"clientid"`
	UserName string `json:"username"`
	Password string `json:"password"`
}

// GenerateTencentIotMQTTAuthInfo 生成腾讯云iot的mqtt密码
// https://cloud.tencent.com/document/product/634/32546
// productID 产品号
// deviceName 设备名
// psk 设备对称密钥
func GenerateTencentIotMQTTAuthInfo(productID, deviceName, psk string) (TencentIotHubMQTTAuthInfo, error) {
	var ai TencentIotHubMQTTAuthInfo
	var err error

	// 1. 生成 connid 为一个随机字符串,方便后台定位问题
	connid := randConnID(5)

	// 2. 生成过期时间,表示签名的过期时间,从纪元1970年1月1日 00:00:00 UTC 时间至今秒数的 UTF8 字符串
	expiry := time.Now().Add(365 * 24 * time.Hour).Unix()

	// 3. 生成 MQTT 的 clientid 部分, 格式为 ${productid}${devicename}
	ai.ClientID = productID + deviceName

	// 4. 生成 MQTT 的 username 部分, 格式为 ${clientid};${sdkappid};${connid};${expiry}
	ai.UserName = fmt.Sprintf("%v;%v;%v;%v", ai.ClientID, 12010126, connid, expiry)

	// 5. 对 username 进行签名,生成token
	key, err := base64.StdEncoding.DecodeString(psk)
	if err != nil {
		return ai, err
	}

	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(ai.UserName))
	token := hex.EncodeToString(mac.Sum(nil))

	// 6. 根据物联网通信平台规则生成 password 字段
	ai.Password = token + ";hmacsha256"

	return ai, err
}

// ToJSONString 将TencentIotHubMQTTAuthInfo转换为json string
func (ai TencentIotHubMQTTAuthInfo) ToJSONString() string {
	data, _ := json.Marshal(ai)
	return string(data)
}

// randConnID 随机产生一个长度为n的connid
func randConnID(n int) string {
	const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	s := make([]byte, n)

	for i := range s {
		s[i] = letters[rand.Intn(len(letters))]
	}

	return string(s)
}