正点原子W601 WIFI 物联网开发板应用——基于MQTT协议的远程温湿度监控Demo
应用介绍本Demo利用传感器采集环境的温度和湿度,通过MQTT协议传给服务器。以JS编写了一个MQTT Web客户端程序,接收上述温湿度数据,并将这些数据显示在网页上。用户可以在远程登录网站,查看传感器采集的温湿度数据。开发环境介绍正点原子W601 WIFI 物联网开发板正点原子W601开发板是由正点原子,RTThread和联盛德三方联合开发的一款T物联网开发板。W601芯片是一款支...
应用介绍
本Demo利用传感器采集环境的温度和湿度,通过MQTT协议传给服务器。以JS编写了一个MQTT Web客户端程序,接收上述温湿度数据,并将这些数据显示在网页上。用户可以在远程登录网站,查看传感器采集的温湿度数据。
开发环境介绍
- 正点原子W601 WIFI 物联网开发板
正点原子W601开发板是由正点原子,RTThread和联盛德三方联合开发的一款T物联网开发板。W601芯片是一款支持多功能的SOCW旧芯片,功能强大,外设多,芯片自带Wifi功能。对于使用WiFi开发T功能具有强大的成本优势。
该开发板上带有温度湿度传感器、光照传感器等多种外设,可用keil进行编程。主控芯片W601可与RT Thread OS完美搭配,并且提供完善的与物联网技术相关的开发资料,大大缩短了开发的进度。我在这周一拿到的板子,从零开始鼓捣了五六天就可以做出一个简单的Demo。它在本应用中采集温湿度并上传数据到MQTT服务器。
- PC1(ubuntu16.4+EMQ X Broker)
作为MQTT服务器。
- PC2(Win10)
作为MQTT客户端,订阅W601开发板发布的温湿度数据。利用Html和JS语言编辑了一个简单的网页显示温湿度数据,使用者可以在远程登录这个网页实时查看开发板采集的温湿度数据。
实现过程
- MQTT服务器搭建
请查看我的上一篇文章https://blog.csdn.net/u010495967/article/details/100182943,里面介绍了利用EMQ X搭建MQTT服务器的过程,比较简单。
- MQTT客户端1
制作了一个简单的网页,从MQTT服务端订阅相关主题,获取并显示温湿度数据。利用windows上的IIS把把它发布在电脑上,供使用者访问。网页如下图所示(时间仓促再加上本人是个菜鸟,所以做的很简陋)。
paho-mqtt提供了一个JS语言的mqtt library,使用WebSocket与mqtt服务器进行通信,上手很简单。建议大家看一下这位大牛的文章来学习mqtt的web应用https://www.thomaslaurenson.com/blog/2018/07/10/mqtt-web-application-using-javascript-and-websockets/
输入服务器IP和端口,点击连接,即可连通服务器并收到数据。因为是使用WebSocket服务通信,所以与EMQ搭建的的MQTT服务器的端口选择8083。
- MQTT客户端2(正点原子W601开发板)
我首先看了正点原子编写的裸机开发教程,首先学会如何用KEIL编写和烧录程序,这里把教程分享一下。
需要拷贝官方提供的下载算法 W60X_QFlash.FLM文件到 MDK安装目录。
这里注意RAM的容量改为0x2000
开发板部分的内容比较多,主要分为以下几个模块:
温湿度采集:与传感器通信,获取环境的温度和湿度,利用RT-Thread提供的软件包可以很简单地驱动多种多样的传感器。
显示模块:在开发板上的小LCD上显示提示信息和温湿度信息。
联网模块:W601芯片你自带WIFI功能,与RT-Thread操作系统相结合能够十分方便地使用WIFI功能。还值得一提的是W601开发板带有快速配网功能,调用几个API就可以实现利用Web配置联网信息。十分方便,并且大大缩短研发时间,降低研发难度。其过程如下:
应用工作流程:
1.初始化外设、操作系统和WIFI功能,等待用户配置网络。
根据提示信息,手机接入名为softap_73b2的 AP,然后打开浏览器,输入地址192.168.169.1(这个IP可以在程序中设置)就会打开一个网页,如下图。输入开发板将要接入的热点的ssid和密码,点击保存按钮,就可以将wifi的ssid和密码发送给控制芯片了,如果密码正确就会自动接入该热点,开发板成功联网。
联网成功后,开始连接MQTT服务器,MQTT服务器的地址和端口预先写在程序中。连接成功后,会在屏幕上连接的地址等信息,之后开启温湿度采集进程,每隔一段时间采集温湿度数据并上传到MQTT服务器。如下图。
然后,只要在手机或者电脑浏览器上,输入之前做的网页MQTT客户端的网址,就可以实时查看开发板采集到的温湿度数据啦。
代码
首先是网页端的代码。
HTML代码内容很少,js则引入了paho-mqtt的library。
<!DOCTYPE html>
<html>
<head>
<title>远程温湿度监控Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript" type="text/javascript"></script>
<script type="text/javascript" src="js/mqtt.js"></script>
</head>
<body>
<div class="wrapper">
<h1>远程温湿度监控Demo</h1>
<p>使用W601 WIFI 物联网开发板应用。 </p>
<p>https://blog.csdn.net/u010495967</p>
<p>请链接MQTT服务器</p>
<form id="connection-info-form">
<b>IP地址:</b>
<input id="host" type="text" name="host" value=""><br>
<b>端 口:</b>
<input id="port" type="text" name="port" value="8083"><br>
<b>订阅主题:</b>
<input id="topic" type="text" name="topic" value=""><br>
<br>
<input type="button" onclick="startConnect()" value="连接">
<input type="button" onclick="startDisconnect()" value="断开">
</form>
</form>
<div id="messages"></div>
<div id = "information"></div>
</div>
</body>
</html>
JS代码
// Called after form input is processed
function startConnect() {
// Generate a random client ID
clientID = "clientID-" + parseInt(Math.random() * 100);
// Fetch the hostname/IP address and port number from the form
host = document.getElementById("host").value;
port = document.getElementById("port").value;
// Print output for the user in the messages div
document.getElementById("messages").innerHTML = '<span>连接:' + host + ' : ' + port + '</span><br/>';
// document.getElementById("messages").innerHTML += '<span>Using the following client value: ' + clientID + '</span><br/>';
// Initialize new Paho client connection
client = new Paho.MQTT.Client(host, Number(port), clientID);
// Set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
// Connect the client, if successful, call onConnect function
client.connect({
onSuccess: onConnect,
});
}
// Called when the client connects
function onConnect() {
// Fetch the MQTT topic from the form
topic = document.getElementById("topic").value;
// Print output for the user in the messages div
document.getElementById("messages").innerHTML += '<span>订阅: ' + topic + '</span><br/>';
// Subscribe to the requested topic
client.subscribe(topic);
}
// Called when the client loses its connection
function onConnectionLost(responseObject) {
document.getElementById("messages").innerHTML += '<span>ERROR: Connection lost</span><br/>';
if (responseObject.errorCode !== 0) {
document.getElementById("messages").innerHTML += '<span>ERROR: ' + + responseObject.errorMessage + '</span><br/>';
}
}
// Called when a message arrives
function onMessageArrived(message) {
data = JSON.parse(message.payloadString);
console.log(data.temp);
console.log(data.humi);
document.getElementById("information").innerHTML = '<span>温度:' + data.temp + '<br/>湿度:' + data.humi + '</span><br/>';
}
// Called when the disconnection button is pressed
function startDisconnect() {
client.disconnect();
document.getElementById("messages").innerHTML = '<span>断开连接</span><br/>';
}
单片机代码:
内容太多,就不提供所有代码了,只把一些主要功能列出来。
注意:时间仓促,而且本人业务不精,代码写的很差,仅供参考,大家不要学习我的代码。
//main
int main(void)
{
struct rt_wlan_info info;
//初始化一个信号量
rt_sem_init(&board_ip_sem, "board_ip_sem", 0, RT_IPC_FLAG_FIFO);
//初始化LCD
lcd_init();
//创建采集和上传数据进程。
publish_sensor_data_tid = rt_thread_create("push_data",
publish_sensor_data_entry, RT_NULL,
1024,
10,
10);
//接入网络
if(RT_EOK == join_wifi())
{
//显示连接信息
lcd_clear(WHITE);
rt_wlan_get_info(&info);
lcd_show_string(0,0,16,"MAC:%02x:%02x:%02x:%02x:%02x:%02x",
info.bssid[0],
info.bssid[1],
info.bssid[2],
info.bssid[3],
info.bssid[4],
info.bssid[5]);
}
rt_sem_take(&board_ip_sem, NET_READY_TIME_OUT);
lcd_show_string(0,16,16,"IP:%s", board_ip);
//连接成功后,开始采集并上传数据
if (publish_sensor_data_tid == RT_NULL)
{
rt_kprintf("create thread failed");
return -1;
}
}
下面实现了连接网络,使用W601的快速联网功能。
static int join_wifi(void)
{
rt_err_t result = RT_EOK;
//注册回调函数,WIFI连接成功后,开启MQTT功能
rt_wlan_register_event_handler(RT_WLAN_EVT_READY, (void (*)(int, struct rt_wlan_buff *, void *))mq_start, RT_NULL);
//配置WIFI状态
rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
rt_wlan_set_mode(RT_WLAN_DEVICE_AP_NAME, RT_WLAN_AP);
rt_sem_init(&web_sem, "web_sem", 0, RT_IPC_FLAG_FIFO);
rt_wlan_start_ap(WIFI_AP_SSID, NULL);
//显示提示信息
lcd_show_string(0, 32, 32, "Connect wifi");
lcd_show_string(0, 64, 32, WIFI_AP_SSID);
rt_thread_mdelay(2000);
/* 一键配网:APWEB 模式 */
result = wm_oneshot_start(WM_APWEB, oneshot_result_cb);
if (result != 0)
{
LOG_E("web config wifi start failed");
return result;
}
lcd_show_string(0, 96, 32, "...");
LOG_D("web config wifi start...");
result = rt_sem_take(&web_sem, NET_READY_TIME_OUT);
if (result != RT_EOK)
{
LOG_E("connect error or timeout");
return result;
};
/* 配网结束,关闭 AP 热点 */
if (RT_TRUE == rt_wlan_ap_is_active())
rt_wlan_ap_stop();
/* 连接 WiFi */
result = rt_wlan_connect(ssid, passwd);
if (result != RT_EOK)
{
LOG_E("\nconnect ssid %s key %s error:%d!", ssid, passwd, result);
};
}
static void oneshot_result_cb(int state, unsigned char *ssid_i, unsigned char *passwd_i)
{
char *ssid_temp = (char *)ssid_i;
char *passwd_temp = (char *)passwd_i;
if (RT_TRUE == rt_wlan_ap_is_active())
rt_wlan_ap_stop();
/* 配网回调超时返回 */
if (state != 0)
{
LOG_E("Receive wifi info timeout(%d). exit!", state);
return;
}
if (ssid_temp == RT_NULL)
{
LOG_E("SSID is NULL. exit!");
return;
}
LOG_D("Receive ssid:%s passwd:%s", ssid_temp == RT_NULL ? "" : ssid_temp, passwd_temp == RT_NULL ? "" : passwd_temp);
strncpy(ssid, ssid_temp, strlen(ssid_temp));
if (passwd_temp)
{
strncpy(passwd, passwd_temp, strlen(passwd_temp));
}
/* 通知 ssid 与 key 接收完成 */
rt_sem_release(&web_sem);
}
下面是与MQTT有关的代码,主要是在W601开发板提供的例子上修改的。
static void mqtt_sub_callback(MQTTClient *c, MessageData *msg_data)
{
*((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0';
LOG_D("Topic: %.*s receive a message: %.*s",
msg_data->topicName->lenstring.len,
msg_data->topicName->lenstring.data,
msg_data->message->payloadlen,
(char *)msg_data->message->payload);
return;
}
static void mqtt_sub_default_callback(MQTTClient *c, MessageData *msg_data)
{
*((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0';
LOG_D("mqtt sub default callback: %.*s %.*s",
msg_data->topicName->lenstring.len,
msg_data->topicName->lenstring.data,
msg_data->message->payloadlen,
(char *)msg_data->message->payload);
return;
}
static void mqtt_connect_callback(MQTTClient *c)
{
LOG_I("Start to connect mqtt server");
lcd_show_string(0, 32, 16, "Start to connect mqtt server");
}
static void mqtt_online_callback(MQTTClient *c)
{
LOG_D("Connect mqtt server success");
lcd_show_string(0, 32, 16, "Connect mqtt server success ");
lcd_show_string(0, 48, 16, MQTT_URI);
LOG_D("Publish message: Hello,RT-Thread! to topic: %s", sup_pub_topic);
//mq_publish("Hello,RT-Thread!");
rt_thread_startup(publish_sensor_data_tid);
}
static void mqtt_offline_callback(MQTTClient *c)
{
LOG_I("Disconnect from mqtt server");
}
/* 创建与配置 mqtt 客户端 */
static void mq_start(void)
{
/* 初始 condata 参数 */
MQTTPacket_connectData condata = MQTTPacket_connectData_initializer;
static char cid[20] = {0};
static int is_started = 0;
if (is_started)
{
return;
}
/* 配置 MQTT 文本参数 */
{
client.isconnected = 0;
client.uri = MQTT_URI;
/* 生成随机客户端 ID */
rt_snprintf(cid, sizeof(cid), "rtthread%d", rt_tick_get());
rt_snprintf(sup_pub_topic, sizeof(sup_pub_topic), "%s%s", MQTT_PUBTOPIC, cid);
/* 配置连接参数 */
memcpy(&client.condata, &condata, sizeof(condata));
client.condata.clientID.cstring = cid;
client.condata.keepAliveInterval = 60;
client.condata.cleansession = 1;
client.condata.username.cstring = MQTT_USERNAME;
client.condata.password.cstring = MQTT_PASSWORD;
/* 配置 mqtt 参数 */
client.condata.willFlag = 0;
client.condata.will.qos = 1;
client.condata.will.retained = 0;
client.condata.will.topicName.cstring = sup_pub_topic;
client.buf_size = client.readbuf_size = 1024;
client.buf = malloc(client.buf_size);
client.readbuf = malloc(client.readbuf_size);
if (!(client.buf && client.readbuf))
{
LOG_E("no memory for MQTT client buffer!");
goto _exit;
}
/* 设置事件回调 */
client.connect_callback = mqtt_connect_callback;
client.online_callback = mqtt_online_callback;
client.offline_callback = mqtt_offline_callback;
/* 设置要订阅的 topic 和 topic 对应的回调函数 */
client.messageHandlers[0].topicFilter = sup_pub_topic;
client.messageHandlers[0].callback = mqtt_sub_callback;
client.messageHandlers[0].qos = QOS1;
/* 设置默认订阅回调函数 */
client.defaultMessageHandler = mqtt_sub_default_callback;
}
/* 启动 MQTT 客户端 */
LOG_D("Start mqtt client and subscribe topic:%s", sup_pub_topic);
paho_mqtt_start(&client);
is_started = 1;
_exit:
return;
}
static void mq_publish(const char *send_str)
{
MQTTMessage message;
const char *msg_str = send_str;
const char *topic = sup_pub_topic;
message.qos = QOS1;
message.retained = 0;
message.payload = (void *)msg_str;
message.payloadlen = strlen(message.payload);
MQTTPublish(&client, topic, &message);
return;
}
上传数据进程
static void publish_sensor_data_entry(void *parameter)
{
int ret = RT_EOK;
rt_device_t temp_dev, humi_dev;
struct rt_sensor_data temp_dev_data, humi_dev_data;
LOG_D("Temperature and Humidity Sensor Testing Start...");
/* 寻找并打开温度传感器 */
temp_dev = rt_device_find(TEMP_DEV);
if (temp_dev == RT_NULL)
{
LOG_E("can not find TEMP device: %s", TEMP_DEV);
ret = RT_ERROR;
}
else
{
if (rt_device_open(temp_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
LOG_E("open TEMP device failed!");
ret = RT_ERROR;
}
}
/* 寻找并打开湿度传感器 */
humi_dev = rt_device_find(HUMI_DEV);
if (humi_dev == RT_NULL)
{
LOG_E("can not find HUMI device: %s", HUMI_DEV);
ret = RT_ERROR;
}
else
{
if (rt_device_open(humi_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
LOG_E("open HUMI device failed!");
ret = RT_ERROR;
}
}
if(RT_EOK != ret)
{
lcd_show_string(0, 64, 32, "Sensor Error");
return;
}
while(1)
{
rt_device_read(temp_dev, 0, &temp_dev_data, 1);
lcd_show_string(0, 64, 32, "%d.%d C ", (int)(temp_dev_data.data.temp / 10), (int)(temp_dev_data.data.temp % 10));
rt_device_read(humi_dev, 0, &humi_dev_data, 1);
lcd_show_string(0, 96, 32, "%d.%d %%", (int)(humi_dev_data.data.humi / 10), (int)(humi_dev_data.data.humi % 10));
mq_publish(data_to_json(temp_dev_data.data.temp, humi_dev_data.data.humi));
rt_thread_mdelay(5000);
}
}
生成JSON格式数据,在通信过程中,采用JSON数据格式,RT-Thread提供了cJSON软件包,开发者可以很轻松地生成JSON格式数据。
static char* data_to_json(int temp, int humi)
{
char *p;
char temp_str[8]="";
char humi_str[8]="";
json_root = cJSON_CreateObject(); /* 创建根索引 json 对象 */
json_sub = cJSON_CreateObject(); /* 创建子索引 json 对象 */
json_array = cJSON_CreateArray(); /* 创建数组 json 对象 */
/* 判断创建 cjson 是否创建成功 */
if (RT_NULL == json_root || RT_NULL == json_sub || RT_NULL == json_array)
{
LOG_E("Fail to creat cjson");
rt_free(json_root);
rt_free(json_sub);
rt_free(json_array);
return RT_NULL;
}
rt_sprintf(temp_str, "%d.%d C", (int)(temp / 10), (int)(temp % 10));
rt_sprintf(humi_str, "%d.%d %%", (int)(humi / 10), (int)(humi % 10));
cJSON_AddStringToObject(json_root, "temp", temp_str);
cJSON_AddStringToObject(json_root, "humi", humi_str);
/* 生成无格式 json 字符串 */
p = cJSON_PrintUnformatted(json_root);
cJSON_Delete(json_root);
return p;
}
总结
这个开发板拿到手学习了几天,听说刚发售有活动,鼓励大家发博文,所以就做了一个简单的小应用出来献丑,请大家多多批评。
这个开发板用下来,我感觉它功能强大,学习资料丰富,而且价格便宜。联盛德,RTThread和正点原子三家公司也提供了丰富的软件开发包,帮助我们在研发过程中降低时间成本,减少研发难度,很适合用来学习物联网以及开发项目样机。下图是开发板的手册目录的一部分,可以看出有许多与物联网相关的学习例程,非常值得学习与研究。
参考资料
RT-THREAD W60X SDK 开发手册
W601开发指南V1.0裸机版本
更多推荐
所有评论(0)