MQTT协议使用
服务端
因为使用的发布/订阅模式,似乎不需要自己写服务端,只要安装现成开源服务器即可,这里选了EMQX
#到官网,选择服务器版本,下载安装,已centos7为例: #下载源码包 wget https://www.emqx.com/zh/downloads/broker/5.6.1/emqx-5.6.1-el7-amd64.tar.gz #安装 mkdir -p emqx && tar -zxvf emqx-5.6.1-el7-amd64.tar.gz -C emqx #运行 ./emqx/bin/emqx start
安装启动完成后,默认通过ip:18083进入管理后台,默认账号admin密码public,可以通过管理后台查看客户端信息以及使用在线websocket客户端。
授权
需要防止别人恶性伪装成客户端来请求,需要增加授权机制,待续
客户端(go)
package main import ( "fmt" MQTT "github.com/eclipse/paho.mqtt.golang" "log" "os" "os/signal" "time" ) func main() { client() } func client() { // 创建 MQTT 客户端配置 opts := MQTT.NewClientOptions() //设置mqtt服务器地址 opts.AddBroker("tcp://服务器地址:1883") //设置当前客户端的id opts.SetClientID("go-mqtt-client") opts.SetUsername("adminUser1") // 设置用户名 //opts.SetPassword("your_password") // 设置密码,如果有的话 //opts.SetCleanSession(true) //不接收之前的历史消息,实际未测试到应用场景,因为不会过滤掉遗嘱消息 // 设置遗嘱消息,会在客户端断开时发送,1代表至少发送一次,false代表不保留,如果用true,那么其他设备一旦订阅这个主题,就会收到该消息 //opts.SetWill("full/topic/LWT", "Admin Offline", 1, false) // 创建 MQTT 客户端实例 client := MQTT.NewClient(opts) // 连接到 MQTT 服务器 if token := client.Connect(); token.Wait() && token.Error() != nil { log.Fatal(token.Error()) } // 设置消息接收回调函数 messageHandler := func(client MQTT.Client, msg MQTT.Message) { fmt.Printf("【%s】:%s\n", msg.Topic(), msg.Payload()) } // 订阅主题并设置消息处理函数,接收主题推送 if token := client.Subscribe("elec/sys/#", 0, messageHandler); token.Wait() && token.Error() != nil { log.Fatal(token.Error()) } time.Sleep(3 * time.Second) //发送消息,让电源开关打开 sendMsg(client, "elec/sys/id1/cmnd/Power", "on") // 创建一个用于接收操作系统中断信号的通道 c := make(chan os.Signal, 1) // 注册通道以接收操作系统中断信号(通常是 Ctrl+C) signal.Notify(c, os.Interrupt) // 阻塞程序,直到收到中断信号 <-c // 中断信号已收到,执行清理工作并断开与 MQTT 服务器的连接 client.Disconnect(250) } func sendMsg(client MQTT.Client, topic, msg string) { token := client.Publish(topic, 0, false, msg) token.Wait() fmt.Printf("发出给【%s】消息:%s\n", topic, msg) }
客户端(设备)
买的是插座,所以:
1、先插到电源上;
2、长按开关,然后用手机在wifi列表里,找到他的名字,连接;
3、连接后会自动打开wifi选择,选择家里wif,输入账号密码,连接wifi成功
4、连接成功后,就可以用内网ip,直接访问设备后台
5、在后台首页Configure进去,然后选择Configure MQTT进去,然后配置服务器的ip、端口等各信息,可以参考官网,示例如下:
Host:服务器ip地址 Port:默认一般是1883 Client:应该是设备标识,目前不知道如何应用 User:用户名,后台会显示,并且如果要校验账号,则会用到 Password:对应密码,如果要校验账号时就必须要 Topic: 官网说时设备唯一主题,但实际测试设置啥都感觉没影响?比如我设置了id1 Full Topic:完整主题,假如我设置elec/sys/id1,那么设备就会忘这个主题推下面接收消息的案例,并且如果要往设备推,也要用这个
设置完毕后,等待他重启即可。然后去服务器后台,应该就能看到这个设备连接上了
发送消息说明
#假设设备上配置的完整主题是elec/sys/id1,那么开启电源就是 主题:elec/sys/id1/cmnd/Power 内容:1或者on都可以
接收消息说明
#假设设备上配置的完整主题是elec/sys,并且在管理端的客户端上,订阅了elec/sys/#主题,则会收到下面消息 主题:elec/sys/id1/LWT 内容:Online或Offline (目前不知道是不是通过遗嘱消息来实现,只要一订阅里吗会收到) 主题:elec/sys/id1/STATE 内容:应该是设备状态信息 示例:{"Time":"2024-05-11T15:42:43","Uptime":"0T00:05:09","UptimeSec":309,"Heap":25,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":1,"POWER":"OFF","Wif{"AP":1,"SSId":"TP-LINK_DANDAN","BSSId":"68:77:24:51:2B:26","Channel":6,"Mode":"11n","RSSI":84,"Signal":-58,"LinkCount":1,"Downtime":"0T00:00:03"}} 主题:elec/sys/id1/SENSOR 内容:应该也是插座一些信息,暂不知道含义 示例:{"Time":"2024-05-11T15:42:43","ENERGY":{"TotalStartTime":"2024-05-11T15:30:12","Total":0.000,"Yesterday":0.000,"Today":0.000,"Period":0,"Power":0,"Apparenter":0,"ReactivePower":0,"Factor":0.00,"Voltage":0,"Current":0.000}} 主题:elec/sys/id1/RESULT 内容:有电源变化,其他还未知 示例:{"POWER":"ON"} 主题:elec/sys/id1/POWER 内容:电源状态,不知道和上面这个差异在哪 示例:ON
目前困惑
设备发消息和收消息的主题前缀是一样的,那么管理端要接收消息,就得订阅通配主题elec/sys/# ,然后服务端控制电源就会往elec/sys/id1/cmnd/Power发,这导致服务端也收到这个消息了,这是本身mqtt设计如此,应用中自己忽略吗?还是怎么回事? 感觉额外在接收多余的信息了。
实际生产可能是通过规则引擎,在服务器端收到消息的时候,先进行过滤,服务端只收需要的信息,并且用比如kafka的架构,让消息先到kafka,然后再消费来处理业务,那这样发消息的客户端 和 收消息的业务处理端就分开了,未实践, 如果设备量不大,就这样写一个客户端自己处理似乎也能跑。