基於rt-thread使用ESP8266實現onenet平臺上報

目錄

 

一、前言

二、硬件環境

三、功能描述

四、組件與軟件包列表

1、SAL 組件

2、netdev 組件

3、AT 組件

4、at device軟件包

5、pahomqtt軟件包

6、onenet軟件包

五、應用實現

1、nrf24l01溫度數據採集

2、onenet數據上報

六、結果展示

1、平臺設備數據流展示

2、平臺應用展示


一、前言

其實在2019年8月份就寫了一篇《基於rt-thread使用nrf24l01實現多點通信》,詳細記錄了怎麼修改nrf24l01軟件包實現多點通訊,來採集多個18B20節點的溫度數據的功能。使用文件系統進行數據存儲、使用OneNet軟件包與OneNet雲端交互也是在那個時候就完成的。

那爲什麼當時沒寫呢?沒別的原因 ,就是 賴 賴 賴!!!!!拖 拖 拖 !!!!

那爲什麼現在又要寫了呢?是因爲當我現在再來看之前做的這個項目的時候,都不敢相信是自己做的,對於使用的各個軟件包功能、實時過程中的一些細節等問題當時是記得非常清楚理得非常順的,但時間一久到現在完全忘了,如同過眼雲煙,再去理的時候相當痛苦。再加上最近想在單片機上實現一個web功能,所以準備重拾當時的這個項目,藉此文檔重新梳理一遍。備忘!!!

 

二、硬件環境

STM32F103ZET6:512KFALSH、64KSRAM。正點原子精英開發板

SD Card、ESP8266模塊、NRF24L01模塊

 

三、功能描述

1、利用nrf24l01無線組網實現多點通訊,採集多個18B20節點溫度數據

2、使用文件系統,存儲採集的溫度數據

3、使用esp8266-wifi模塊將接收節點的數據傳輸至OneNet雲

4、OneNet雲的簡單應用開發,實現遠程監控

 

四、組件與軟件包列表

SAL組件

該組件完成對不同網絡協議棧或網絡實現接口的抽象並對上層提供一組標準的 BSD Socket API。這樣開發者只需要關心和使用網絡應用層提供的網絡接口,而無需關心底層具體網絡協議棧類型和實現,方便開發者完成協議棧的適配和網絡相關的開發。主要功能如下:

● 抽象、統一多種網絡協議棧接口;

● 提供 Socket 層面的 TLS 加密傳輸特性;

● 支持標準 BSD Socket API;

● 統一的 FD 管理,便於使用 read/write poll/select 來操作網絡功能;

netdev組件

netdev 組件主要作用是解決設備多網卡連接時網絡連接問題,用於統一管理各個網卡信息與網絡連接狀態,並且提供統一的網卡調試命令接口。 其主要功能特點如下所示:

● 抽象網卡概念,每個網絡連接設備可註冊唯一網卡。

● 提供多種網絡連接信息查詢,方便用戶實時獲取當前網卡網絡狀態;

● 建立網卡列表和默認網卡,可用於網絡連接的切換;

● 提供多種網卡操作接口(設置 IP、DNS 服務器地址,設置網卡狀態等);

● 統一管理網卡調試命令(ping、ifconfig、netstat、dns 等命令);

AT 組件1.3.0

AT 組件是基於 RT-Thread 系統的 AT Server 和 AT Client 的實現,組件完成 AT 命令的發送、命令格式及參數判斷、命令的響應、響應數據的接收、響應數據的解析、URC 數據處理等整個 AT 命令數據交互流程。而AT Socket是作爲 AT Client 功能的延伸,使用 AT 命令收發作爲基礎,實現標準的 BSD Socket API,完成數據的收發功能,使用戶通過 AT 命令完成設備連網和數據通訊。

at_device-V2.0.0

該軟件包是不同模塊針對AT組件的移植文件,來實現的AT Socket功能,進而實現的AT設備Socket功能。V1.X.0版本與V2.X.X版本,實現差異比較大,主要用於適配 AT 組件和系統的改動。

cJSON-V1.0.2

該軟件包用於解析和打包JSON數據,用於MQTT通訊時數據的解析和打包

pahomqtt-V1.1.0

該軟件包是在 Eclipse paho-mqtt 源碼包的基礎上設計的一套 MQTT 客戶端程序,源碼包只是提供一套MQTT在發佈數據和訂閱主題時對MQTT協議的解析和封包,用戶需要自己實現TCP連接建立與斷開、TCP數據發送與接收功能。正是基於這些需求,pahomqtt軟件包不僅基於socket實現了網絡連接與斷開、網絡數據包的發送接收等功能,還實現了只需簡單配置MQTT連接參數就可輕鬆連雲、主題發佈與訂閱等功能

onenet-V0.1.1

該軟件包基於pahomqtt軟件包實現了OneNet 平臺MQTT連接、數據的發送、數據的接收、設備的註冊和控制等功能

   

1、SAL 組件

簡介 https://www.rt-thread.org/document/site/programming-manual/sal/sal/

 

2、netdev 組件

簡介 https://www.rt-thread.org/document/site/programming-manual/netdev/netdev/

Socket 套接字描述符的創建建立在 netdev 網卡基礎上,所以每個創建的 Socket 對應唯一的網卡。協議簇、網卡和 socket 之間關係如下圖所示:

3、AT 組件

簡介 https://www.rt-thread.org/document/site/programming-manual/at/at/

通過 AT 組件,設備可以作爲 AT Client 使用串口連接其他設備發送並接收解析數據,可以作爲 AT Server 讓其他設備甚至電腦端連接完成發送數據的響應,也可以在本地 shell 啓動 CLI 模式使設備同時支持 AT Server 和 AT Client 功能,該模式多用於設備開發調試。

4、at device軟件包

簡介 https://github.com/RT-Thread-packages/at_device

示例說明 https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/

依賴

  • RT_Thread 4.0.2+
  • RT_Thread AT 組件 1.3.0+
  • RT_Thread SAL 組件
  • RT-Thread netdev 組件

版本號說明

AT device 軟件包目前已經發布多個版本,各個版本之間選項配置方式和其對應的系統版本有所不同,下面主要列出當前可使用的軟件包版本信息:

V1.2.0:適用於 RT-Thread 版本小於 V3.1.3,AT 組件版本等於 V1.0.0;

V1.3.0:適用於 RT-Thread 版本小於 V3.1.3,AT 組件版本等於 V1.1.0;

V1.4.0:適用於 RT-Thread 版本小於 V3.1.3或等於 V4.0.0, AT 組件版本等於 V1.2.0;

V1.5.0:適用於 RT-Thread 版本小於 V3.1.3 或等於 V4.0.0, AT 組件版本等於 V1.2.0;

V1.6.0:適用於 RT-Thread 版本等於 V3.1.3 或等於 V4.0.1, AT 組件版本等於 V1.2.0;

V2.0.0/V2.0.1:適用於 RT-Thread 版本大於 V4.0.1 或者大於 3.1.3, AT 組件版本等於 V1.3.0;

laster:只適用於 RT-Thread 版本大於V4.0.1 或者大於 3.1.3, AT 組件版本等於 V1.3.0;

注意事項

1、AT device 軟件包適配的模塊暫時不支持作爲 TCP Server 完成服務器相關操作(如 accept 等);

2、AT device 軟件包默認設備類型爲未選擇,使用時需要指定使用設備型號;

3、laster 版本支持多個選中多個 AT 設備接入實現 AT Socket 功能,V1.X.X 版本只支持單個 AT 設備接入。

4、AT device 軟件包目前多個版本主要用於適配 AT 組件和系統的改動,推薦使用最新版本 RT-Thread 系統,並在 menuconfig 選項中選擇 latest 版本

at device與AT 組件(AT Client & AT Socket)、SAL組件、netdev組件的關係

對於AT設備,應用層在調用SAL 提供的BSP網絡接口的時候,其實調用的就是 AT Socket(AT設備標準的 BSD Socket API),而AT Socket是藉助於netdev組件來實現的,並不是在 at device軟件包中封裝了at_device(AT設備的句柄)中的3個ops功能就直接提供給了AT Socket;AT Device是先基於AT Client實現了at_device中3個ops功能 , 再將at_device 註冊到 at_device_list / at_device.c鏈表中,最後通過netdev組件抽象網卡的功能將at_device中的netdev註冊到網卡列表 netdev_list 中; 通常應用程序在使用AT設備的BSP Socket(AT Socket)的時候,會先申請一個socket,這個時候會從默認網卡netdev_default 或在 網卡列表netdev_list 中根據協議族去找到對應的網卡,再根據網卡信息從at_device_list 鏈表中獲取到該網卡對應的at_device,接下來的AT Socket操作就是利用at_device中的3個ops來實現的。 

拿ec20設備來具體分析下實現過程:

第一步:在ec20的class文件at_device_ec20.c和at_socket_ec20.c中,基於at client分別實現了ec20_device_ops、ec20_netdev_ops 、ec20_socket_ops 結構中的功能。

const struct at_device_ops ec20_device_ops = 
{
    ec20_init,
    ec20_deinit,
    ec20_control,
};
const struct netdev_ops ec20_netdev_ops =
{
    ec20_netdev_set_up,
    ec20_netdev_set_down,

    RT_NULL,
    ec20_netdev_set_dns_server,
    RT_NULL,

#ifdef NETDEV_USING_PING
    ec20_netdev_ping,
#endif
#ifdef NETDEV_USING_NETSTAT
    ec20_netdev_netstat,
#endif
};

static const struct at_socket_ops ec20_socket_ops = 
{
    ec20_socket_connect,
    ec20_socket_close,
    ec20_socket_send,
    ec20_domain_resolve,
    ec20_socket_set_event_cb,
};

第二步:在at_device_ec20.c中,系統自動初始化 INIT_DEVICE_EXPORT(ec20_device_class_register) 將 at_device_class(ec20_device_ops +ec20_socket_ops )自動註冊到了at_device_class_list / at_device.c 鏈表中

struct at_device_class
{
    uint16_t class_id;                           /* AT device class ID */
    const struct at_device_ops *device_ops;      /* AT device operaiotns */
#ifdef AT_USING_SOCKET
    uint32_t socket_num;                         /* The maximum number of sockets support */
    const struct at_socket_ops *socket_ops;      /* AT device socket operations */
#endif
    rt_slist_t list;                             /* AT device class list */
};

第三步:在應用程序中調用 at_device_register / at_device.c ,調用過程如下:

1、在應用程序中定義at_device_ec20 的實例 e0

struct at_device_ec20
{     
    char *device_name;
    char *client_name;

    int reset_pin;//2019、9、10 add by denghengli
    int power_pin;
    int power_status_pin;
    size_t recv_line_num;
    struct at_device device;

    void *socket_data;
    void *user_data;
};

2、調用 at_device_register / at_device.c 完成了 e0 中 at_device 中 at_socket 內存申請、將 at_device_class (c20_socket_ops + ec20_device_ops)at_device_class_list中讀出賦值 at_device中的class、將 at_device 註冊到 at_device_list / at_device.c鏈表中

struct at_device
{
    char name[RT_NAME_MAX];                      /* AT device name */
    rt_bool_t is_init;                           /* AT device initialization completed */
    struct at_device_class *class;               /* AT device class object */
    struct at_client *client;                    /* AT Client object for AT device */
    struct netdev *netdev;                       /* Network interface device for AT device */
#ifdef AT_USING_SOCKET
    rt_event_t socket_event;                     /* AT device socket event */
    struct at_socket *sockets;                   /* AT device sockets list */
#endif
    rt_slist_t list;                             /* AT device list */
	
	char (*init_succ_ind)(void);
	
    void *user_data;                             /* User-specific data */
};

3、調用ec20_init的函數指針,完成 e0 中 at_device 中 at_client 的初始化、netdev 網卡的註冊、ec20入網使能

  • at_client的初始化,創建了client_parser線程,作用爲ec20與串口之間的數據交互,是ops功能實現的前提
struct at_client
{
    rt_device_t device;

    at_status_t status;
    char end_sign;

    /* the current received one line data buffer */
    char *recv_line_buf;
    /* The length of the currently received one line data */
    rt_size_t recv_line_len;
    /* The maximum supported receive data length */
    rt_size_t recv_bufsz;
    rt_sem_t rx_notice;
    rt_mutex_t lock;

    at_response_t resp;
    rt_sem_t resp_notice;
    at_resp_status_t resp_status;

    struct at_urc_table *urc_table;
    rt_size_t urc_table_size;

    rt_thread_t parser;
};
  • netdev網卡註冊,netdev_ops = ec20_netdev_ops 和 sal_user_data 的賦值
/* network interface device object */
struct netdev
{
    rt_slist_t list; 
    
    char name[RT_NAME_MAX];                            /* network interface device name */
    ip_addr_t ip_addr;                                 /* IP address */
    ip_addr_t netmask;                                 /* subnet mask */
    ip_addr_t gw;                                      /* gateway */
#if NETDEV_IPV6
    ip_addr_t ip6_addr[NETDEV_IPV6_NUM_ADDRESSES];     /* array of IPv6 addresses */
#endif /* NETDEV_IPV6 */
    ip_addr_t dns_servers[NETDEV_DNS_SERVERS_NUM];     /* DNS server */
    uint8_t hwaddr_len;                                /* hardware address length */
    uint8_t hwaddr[NETDEV_HWADDR_MAX_LEN];             /* hardware address */
    
    uint16_t flags;                                    /* network interface device status flag */
    uint16_t mtu;                                      /* maximum transfer unit (in bytes) */
    const struct netdev_ops *ops;                      /* network interface device operations */
    
    netdev_callback_fn status_callback;                /* network interface device flags change callback */
    netdev_callback_fn addr_callback;                  /* network interface device address information change callback */

#ifdef RT_USING_SAL
    void *sal_user_data;                               /* user-specific data for SAL */
#endif /* RT_USING_SAL */
    void *user_data;                                   /* user-specific data */
};
  • ec20入網使能,創建了ec20_init_thread_entry線程設備聯網,聯網成功後退出。在聯網成功後接着創建了ec20_check_link_status_entry線程用爲監測網絡狀態並更新網卡狀態,可以在這裏做一些網絡連接異常後硬件復位、重新初始化模塊,通過監測網絡狀態來實現掉卡重連的功能,但是一般這裏監測的網絡狀態不是實時的,因爲一般蜂窩模塊設備 SIM 卡去掉之後不會實時同步模塊內部狀態,只有等待模塊內部信息改變了,netdev 網卡信息纔會同步,所以最好還是通過心跳機制來監測鏈路的異常情況。

5、pahomqtt軟件包

簡介 https://github.com/RT-Thread-packages/paho-mqtt

工作原理 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/principle.md

使用指南 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/user-guide.md

示例說明 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/samples.md

6、onenet軟件包

簡介 https://github.com/RT-Thread-packages/onenet

工作原理 https://github.com/RT-Thread-packages/onenet/blob/master/docs/principle.md

使用指南 https://github.com/RT-Thread-packages/onenet/blob/master/docs/user-guide.md

示例說明 https://github.com/RT-Thread-packages/onenet/blob/master/docs/samples.md

注意事項

1、設置命令響應回調函數之前必須要先調用onenet_mqtt_init函數,在初始化函數裏會將回調函數指向RT_NULL。

2、命令響應回調函數裏存放響應內容的 buffer 必須是 malloc 出來的,在發送完響應內容後,程序會將這個 buffer 釋放掉。

static void onenet_cmd_rsp(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size) { char resp_buf[] = { "cmd is received!\n" }; LOG_D("recv data is %.*s\n", recv_size, recv_data); /* user have to malloc memory for response data */ *resp_data = (uint8_t *) rt_malloc(strlen(resp_buf)); strncpy((char *)*resp_data, resp_buf, strlen(resp_buf)); *resp_size = strlen(resp_buf); }

3、在向主題發佈數據的時候,並不能在onenet_mqtt_init 初始化成功之後就開始肆無忌憚的發佈數據了,需要判斷MQTT真正連接成功執行mqtt_online_callback上線回調函數之後才能開始發佈,一旦MQTT連接斷執行mqtt_offline_callback下線回調函數之後則停止發佈數據,否則就會發布失敗出現各種問題 如下圖,有可能MQTT就一直連接不成功。具體原因看看onenet_mqtt_init 初始化中到底幹了啥

  • 獲取onenet連接時需要認證的參數。如 產品ID、產品祕鑰、設備ID 等。
  • 配置MQTT連接參數如三元組、遺囑信息、訂閱主題信息等信息,註冊連接上線下線成功回調函數

創建paho_mqtt_thread線程,這個線程纔是真正的去連接MQTT服務器、向主題發佈數據、訂閱主題並接受主題下發數據。在開始連接的時候會執行 mqtt_connect_callback 、MQTTQ服務器連接成功的時候會執行mqtt_online_callback 、MQTT斷開連接的時候會執行mqtt_offline_callback

五、應用實現

1、nrf24l01溫度數據採集

static void nrf24l01_thread_entry(void* parameter)
{
	struct nrf_msg     nrf24l01_msg;
	struct temp_data   temp_data;
	struct nrf24_cfg   nrf24l01_cfg;
    int   rlen;
    char  rbuf[32 + 1], tmp_buf[50];
    char  tbuf[32] = "first\r\n";
    char  res=0,cnt = 0;
	uint8_t chnum=0; 
	
    nrf24l01_dev = (rt_spi_wire_device_t)rt_device_find(SPI_WIRE_DEV_NAME);
    if (nrf24l01_dev == RT_NULL)
    {		
        LOG_E("Can't find device:%s\n", SPI_WIRE_DEV_NAME);
        return;
    }

	nrf24l01_default_param(&nrf24l01_cfg);
	nrf24l01_cfg.selchx = 0X3F;
	nrf24l01_cfg.use_irq = 1;
	nrf24l01_cfg.role = ROLE_PRX;
	nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_PIN);
	
    while (1)
    {
        rt_memset(rbuf, 0, sizeof(rbuf));
        rlen = nrf24l01_dev->nrf24l01.ops->prx_cycle((uint8_t*)rbuf, (uint8_t*)tbuf, rt_strlen((char *)tbuf), &chnum);
        if (rlen > 0) // received data (also indicating that the previous frame of data was sent complete)
        {       
            /* 打印接收到的數據 */
            rt_memset(tmp_buf, 0, sizeof(tmp_buf));
            rt_sprintf(tmp_buf, "nrf24l01 pipe(%d) data:%s\n", (char)chnum, rbuf);
            LOG_D((char *)tmp_buf);
            /* 準備應答的數據 */
            rt_sprintf((char *)tbuf, "i-am-PRX:%dth\n", cnt++);
            
            /* 通過sscnaf解析收到的數據 */
            res = sscanf((char *)rbuf, "%d,+%f", &temp_data.timestamp, &temp_data.temperature);
            if(res != 2)
            {
                /* 通過sscnaf解析收到的數據 */
                if(sscanf((char *)rbuf, "%d,-%f", &temp_data.timestamp, &temp_data.temperature) != 2)
                {
                    continue;
                }
                temp_data.temperature = -temp_data.temperature;
            }
            rt_memset(tmp_buf, 0, sizeof(tmp_buf));
            sprintf(tmp_buf, "%d,%f\n", temp_data.timestamp, temp_data.temperature);
			
            /* 將數據存放到ringbuffer裏 */
            if (chnum == 0)      rt_ringbuffer_put(nrf_p0_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            else if (chnum == 1) rt_ringbuffer_put(nrf_p1_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            else if (chnum == 2) rt_ringbuffer_put(nrf_p2_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            else if (chnum == 3) rt_ringbuffer_put(nrf_p3_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            else if (chnum == 4) rt_ringbuffer_put(nrf_p4_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            else if (chnum == 5) rt_ringbuffer_put(nrf_p5_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf));
            
            /* 收到數據,並將數據存放到ringbuffer裏後,才發送事件, 用於通知DFS線程存儲數據 */
            rt_event_send(nrf_event, WRITE_EVENT);

            /* 判斷onenet是否上報完成,不會等待 */
            if (rt_sem_take(onenet_upok_sem, 0) == RT_EOK)
            {
                nrf24l01_msg.chnum = chnum;
                nrf24l01_msg.data.timestamp = temp_data.timestamp;
                nrf24l01_msg.data.temperature = temp_data.temperature;
                rt_mq_send(nrf_msg_mq, &nrf24l01_msg, sizeof(struct nrf_msg));
            }
        }
        else // no data
        {  
//            LOG_E("nrf not received\n"); 
        }
		
        rt_thread_mdelay(100);
    }
}

2、onenet數據上報

static void onenet_trans_thread_entry(void* parameter)
{
	struct nrf_msg  nrf24l01_msg;
    char tmp_buf[100], stream_name[20] = "";
	
    /* 永久等待方式接收信號量,若收不到,該線程會一直掛起 */
    rt_sem_take(mqttinit_sem, RT_WAITING_FOREVER);
    /* 後面用不到這個信號量了,把它刪除了,回收資源 */
    rt_sem_delete(mqttinit_sem);
    
	rt_sem_release(onenet_upok_sem);
	
    while (1)
    {        
        if (rt_mq_recv(nrf_msg_mq, &nrf24l01_msg, sizeof(struct nrf_msg), RT_WAITING_FOREVER) == RT_EOK)
        {
                rt_sprintf(stream_name, "temperature_p%d", nrf24l01_msg.chnum);
			
            /* 上傳發送節點1的數據到OneNet服務器,數據流名字是temperature_p0 */
            if (onenet_mqtt_upload_digit(stream_name, nrf24l01_msg.data.temperature) != RT_EOK)
            {
                /* 打印接收到的數據 */
                rt_memset(tmp_buf, 0, sizeof(tmp_buf));
                sprintf(tmp_buf, "upload %s has an error, try again\n", stream_name);
                LOG_D((char*)tmp_buf);
            }
            else
            {
                rt_memset(tmp_buf, 0, sizeof(tmp_buf));
                sprintf(tmp_buf, "upload %s OK >>> temp:%f\n", stream_name, nrf24l01_msg.data.temperature);
                LOG_D((char*)tmp_buf);
            }             
			
                rt_thread_delay(rt_tick_from_millisecond(1000));
			
                /* onenet上報成功之後,釋放信號量告知nrf線程可以往消息隊列發送消息了 */
                rt_sem_release(onenet_upok_sem);
        }		
    }
}
static void onenet_init_thread_entry(void* parameter)
{
	char onenet_mqtt_init_failed_times = 0;
	
	/* mqtt初始化 */
    while (1)
    {
        if (!onenet_mqtt_init())
        {
            /* 註冊平臺下行命令響應回調函數 */
            onenet_set_cmd_rsp_cb(onenet_cmd_rsp);
			
            /* mqtt初始化成功之後,釋放信號量告知onenet_upload_data_thread線程可以上傳數據了 */
            rt_sem_release(mqttinit_sem);
            return;
        }
        rt_thread_mdelay(100);
        LOG_E("onenet mqtt init failed %d times", onenet_mqtt_init_failed_times++);
    }
}

 

六、結果展示

1、平臺設備數據流展示

2、平臺應用展示

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章