Navigation

    全志在线开发者论坛

    • Register
    • Login
    • Search
    • Categories
    • Tags
    • 在线文档
    • 社区主页

    【XR806开发板试用】基于MQTT实现手机与XR806互联——XR806开发板开发部分

    Wireless & Analog Series
    1
    1
    1032
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • bigbear2021
      大目熊 LV 3 last edited by

      • 小项目背景
        鸿蒙系统也听说发布一段时间了,一直没去了解。但是勒,既然是华为发布的项目,还是想找个时间了解下的。刚好在公众号看到免费申请鸿蒙开发板试用,那就顺便学习下吧。
        鸿蒙系统的特点是万物互联,那么申请的开发板我也准备用来做一个物联网小项目,至于做啥呢?...思量许久,感觉无论用来物联网控制啥东西,都需要额外成本,那就暂时放弃外设控制的想法,先完成简单的Android手机与开发板通过MQTT互联吧。

      • 简述
        本项目主要涉及如下方面:

        1. XR806的单片机固件开发
          XR806的开发主要涉及鸿蒙LiteOS、XR806本身的SDK包、MQTT,在本贴中,会较详细地讲述。
          在MQTT的实现上,我一直在纠结,本人之前开发单片机程序的时候其实是没接触过LWIP和MQTT这部分涉及到嵌入式网络相关的,因此在开发前是有一部分时间去了解MQTT的,目前我看到鸿蒙相关的开发板都是使用基于Paho的开源MQTT(可能是我没找到其他的哈),但是我发现LWIP本身是有实现MQTT客户端的源码的,既然如此,为啥不直接用LWIP本身的呢?MQTT是应用层的协议,因此移植其实特别简单,基于传输层的TCP移植就ok了。不过,如果是使用LWIP本身实现的MQTT,那很可能连移植都不需要去做,因此,我打算直接使用LWIP内部的MQTT客户端实现。
        2. Android的App开发
          Android的开发也很简单,主要是搭建个简单的界面,并通过开源的MQTT库实现MQTT客户端。本贴中也会讲述这部分内容。
          本贴中,Android的MQTT是基于Paho的开源库实现的。
      • 整体框架
        1641134347(1).png
        如上图,项目实现的MQTT的传输流程便是如此。本贴是在Ubuntu搭建的MQTT服务器,Android手机订阅“sensor_data”主题的消息,并且会发送“sensor_cmd”主题的信息;XR806订阅“sensor_cmd”主题的信息,并且会发送定时发送“sensor_data”的信息,这样就达到一个Android手机用户获取传感器数据并且还能实现控制设备(比如控灯)的目的。

      • XR806开发

      1. 鸿蒙系统的了解
        关于鸿蒙的源码如何编译,恩...还是自行查资料吧,其实个人感觉这个编译系统类似android的编译,初学者多踩踩坑是可以很容易上手的。
        通过查看鸿蒙的官网资料,在这里说下本人的一些理解:
        首先,像XR806这种单片机开发板使用的是鸿蒙的轻量型系统(LiteOS_m),鸿蒙还有小型系统和标准系统,小型系统应该是用在imx6ull这种级别的芯片上,而标准系统就是用在手机芯片这种级别。
        既然我们用的是LiteOS_m,那么就把关注范围缩小到LiteOS_m上,主要是LiteOS_m的内容比较少。
        我们先看鸿蒙的目录结构,如下:
        Screenshot from 2022-01-02 23-47-02.png
        通过这个目录,我们其实大致可以猜到以下(仅提供参考):
        -- applications是存放应用的位置;
        -- driver应该是存放驱动的位置,但其实和我们无关,因为该目录存放的是针对小型系统(LiteOS_a)的驱动(HDI接口);
        -- base存放鸿蒙系统中无关平台无关硬件的通用基础构成(不包含内核),比如基本外设的中间件接口、关于安全的源码、代码升级相关的源码,当然,可能并不适用于LiteOS_m系统,或者说不全适用;
        -- build和developtools存放编译系统源码工具和开发相关工具;
        -- device应该是存放具体不同设备的特定源码,也就是不同的设备型号需要适配的部分;
        -- vendor应该要存放不同厂商芯片的特定源码,也就是不同系列芯片需要适配的部分;
        -- out目录存放编译输出文件;
        -- third_party存放第三方源码和库;
        理论上,通过鸿蒙系统可以实现:无论哪个系列的芯片,哪个型号的设备,只要适配了鸿蒙系统,那么编写出来的应用程序就是通用的。
        这是因为鸿蒙在应用和具体厂商设备之间提供了中间层,因此,对于应用层来说,如果底层适配完成,只需要调用中间层的通用api即可,底层硬件和应用业务逻辑分开。
        这里举一个例子,比如说鸿蒙系统LiteOS_m基本外设的使用,我们可以随便打开applications目录下的一个例程,我这里打开目录“OpenHarmony/release_1.0.1/applications/sample/wifi-iot/app/iothardware”下的"led_example"例程,主要相关源码如下:
      static void *LedTask(const char *arg)
      {
          (void)arg;
          while (1) {
              switch (g_ledState) {
                  case LED_ON:
                      IoTGpioSetOutputVal(LED_TEST_GPIO, 1);
                      usleep(LED_INTERVAL_TIME_US);
                      break;
                  case LED_OFF:
                      IoTGpioSetOutputVal(LED_TEST_GPIO, 0);
                      usleep(LED_INTERVAL_TIME_US);
                      break;
                  case LED_SPARK:
                      IoTGpioSetOutputVal(LED_TEST_GPIO, 0);
                      usleep(LED_INTERVAL_TIME_US);
                      IoTGpioSetOutputVal(LED_TEST_GPIO, 1);
                      usleep(LED_INTERVAL_TIME_US);
                      break;
                  default:
                      usleep(LED_INTERVAL_TIME_US);
                      break;
              }
          }
      
          return NULL;
      }
      
      

      可以看到,控制IO口是通过IoTGpioSetOutputVal函数API实现的,该函数其实就是在“OpenHarmony/release_1.0.1/base/iot_hardware/peripheral/interfaces/kits
      ”目录下的“iot_gpio.h”声明的,在该目录下还能看到其它基本外设API的声明头文件。
      那IoTGpioSetOutputVal函数是在哪里定义具体实现的呢?这个就是根据不同设备不同平台其实现是可能不同的,因此该函数的实现应放置在device目录下。例如hi3861的该函数实现放置在“OpenHarmony/release_1.0.1/device/hispark_pegasus/hi3861_adapter/hals/iot_hardware/wifiiot_lite/hal_iot_gpio.c”文件中。
      当然,一个系统的适配还包括其它很多东西,比如说系统基本组件(任务、互斥量、信号量、队列等),蓝牙、wifi等具备相关协议栈的适配等等。
      2. XR806的适配部分
      事实上,在了解完鸿蒙系统后,再来看XR806部分的源码时,我是有点懵的。怎么个懵法呢?我从一个XR806的例程入手。我们看到“OpenHarmony/release_1.0.1/device/xradio/xr806/ohosdemo/iot_peripheral
      ”这个例子,先从入口函数来看,代码如下:

      void PeripheralTestMain(void)
      {
      	printf("\r\nPeripheral Test Start\r\n");
      
      	if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL,
      			    OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
      		printf("[ERR] Create MainThread Failed\n");
      	}
      }
      
      SYS_RUN(PeripheralTestMain);
      

      从以上代码可以看到,创建多任务使用的是OS_ThreadCreate函数。但是,看过鸿蒙开发者文档的读者都知道,鸿蒙创建多任务的API其实是LOS_TaskCreate函数,这是怎么回事呢?
      其实答案很简单,XR806是在OS_ThreadCreate的函数实现中调用了LOS_TaskCreate,而且不止线程创建的接口,其他系统基础组件的接口实现都对鸿蒙的标准接口进行封装,具体在目录“OpenHarmony/release_1.0.1/device/xradio/xr806/os”下。这样做的目的是什么呢?我猜测可能是为了兼容以前的旧freertos项目。但这样做之后,XR806的应用程序就不能无修改跨芯片(适配完毕的)通用了,所以XR806的应用程序目录也放置在device目录下。
      当然,XR806关于外设的接口实现,wifi、蓝牙的接口实现都是基于鸿蒙的标准接口的,具体可查看device相关目录。
      另外一方面,XR806还将其他一些实现(即目录"OpenHarmony/release_1.0.1/device/xradio/xr806/xr_skylark")编译成库了,我们在编译时,执行"make menuconfig"其实就是在进行选择编译,可以将一些不需要的实现筛选掉。对于本项目来说,需要实现mqtt需要将lwip编译进来,通过生成的.config和“OpenHarmony/release_1.0.1/device/xradio/xr806/liteos_m”目录下的配置文件确认,lwip已经默认编译。也可以通过编译生成的库文件确实是否有编译。
      3. MQTT实现
      本贴使用的MQTT基于LWIP,XR806使用的基于wifi的网络,LWIP与wifi之间应该需要做相关适配,当平台已经完成适配,暂无需关心。
      因此,MQTT的实现其实很简单:第一,连接wifi热点;第二,连接MQTT服务器。前者已经有一个官方例程了,后者呢,前面已经提过基于LWIP中的MQTT来实现了。
      首先将mqtt的实现源码包含进来(因为默认编译生成的lwip库没有包含应用层的实现),BUILD.gn相关源码包含如下:

         sources = [
            "src/main.c",
            "//device/xradio/xr806/xr_skylark/src/net/lwip-2.1.2/src/apps/mqtt/mqtt.c",
         ]
      

      然后就是找到lwip的官方文档,看是否有对mqtt实现的说明文档,一看还真有,在目录“OpenHarmony/release_1.0.1/device/xradio/xr806/xr_skylark/src/net/lwip-2.1.2/doc”下。直接跟着文档走,复制——粘贴,一个简单的单片机固件即完成。当然,其实过程还是有一些坑需要查看lwip源码才能了解用法,我这里呢直接给出固件源码(比较乱,初版程序,直接堆在一个文件,还好代码量较小),也就不进行解释了,主要是我困了哈,代码如下:

      #include <stdio.h>
      #include <stdlib.h>
      #include "ohos_init.h"
      #include "kernel/os/os.h"
      #include "lwip/apps/mqtt.h"
      #include "wifi_device.h"
      
      #define WIFI_DEVICE_CONNECT_AP_SSID "Android_Sen"
      #define WIFI_DEVICE_CONNECT_AP_PSK "250638cks!"
      
      static OS_Thread_t g_main_thread;
      static OS_Thread_t g_wifi_thread;
      static int inpub_id;
      static int isConnected = 0;
      static int isMQTTConnected = 0;
      
      static void iot_client_do_connect(mqtt_client_t *client);
      static void mqtt_pub_request_cb(void *arg, err_t result);
      void iot_publish(mqtt_client_t *client, void *arg);
      
      
      /* To test the following interfaces:
      *        IsWifiActive()
      *        EnableWifi()
      *        Scan()
      *        GetScanInfoList()
      *        AddDeviceConfig()
      *        ConnectTo()
      *        GetLinkedInfo()
      *        GetDeviceMacAddress()
      *        Disconnect()
      *        DisableWifi()
      */
      #define NET_IF_STATE_DBG(netif)                              \
      	do {                                                       \
      		if (netif_is_link_up(netif)) {                           \
      			printf("netif is up.\n");                              \
      		} else {                                                 \
      			printf("netif is down.\n");                            \
      		}                                                        \
      	} while (0)
      
      extern struct netif *g_wlan_netif;
      
      /* To test the following interfaces:
      *        RegisterWifiEvent()
      *        UnRegisterWifiEvent()
      */
      WifiEvent sta_event;
      
      void Scan_done_deal(int state, int size)
      {
      	if (state == WIFI_STATE_AVALIABLE) {
      		printf("======== Callback: scan done, the num of bss: %d\n",
      		       size);
      	}
      }
      
      void Connected_deal(int state, WifiLinkedInfo *info)
      {
      	if (state == WIFI_STATE_AVALIABLE) {
          isConnected = 1;
      		printf("======== Callback: connected\n");
      	} else if (state == WIFI_STATE_NOT_AVALIABLE) {
          isConnected = 0;
      		printf("======== Callback: disconnected\n");
      	}
      }
      
      void wifi_device_connect()
      {
      	printf("\n=========== Event Test Start ===========\n");
      	sta_event.OnWifiScanStateChanged = Scan_done_deal;
      	sta_event.OnWifiConnectionChanged = Connected_deal;
      
      	if (WIFI_SUCCESS != RegisterWifiEvent(&sta_event)) {
      		printf("Error: RegisterWifiEvent fail\n");
      		return;
      	}
      
      	if (WIFI_SUCCESS != EnableWifi()) {
      		printf("Error: EnableWifi fail\n");
      		return;
      	}
      
      	OS_Sleep(1);
      	while (1) {
          if (!isConnected) {
      
            if (WIFI_SUCCESS != Scan()) {
              printf("Error: Scan fail.\n");
              continue;
            }
      
            const char ssid_want_connect[] = WIFI_DEVICE_CONNECT_AP_SSID;
            const char psk[] = WIFI_DEVICE_CONNECT_AP_PSK;
            WifiScanInfo scan_results[30];
            unsigned int scan_num = 30;
      
            if (WIFI_SUCCESS != GetScanInfoList(scan_results, &scan_num)) {
              printf("Error: GetScanInfoList fail.\n");
              continue;
            }
      
            WifiDeviceConfig config = { 0 };
            int netId = 0;
      
            int i;
            for (i = 0; i < scan_num; i++) {
              printf("ssid: %s    ", scan_results[i].ssid);
              printf("securityType: %d\n", scan_results[i].securityType);
              if (0 == strcmp(scan_results[i].ssid, ssid_want_connect)) {
                memcpy(config.ssid, scan_results[i].ssid,
                      WIFI_MAX_SSID_LEN);
                memcpy(config.bssid, scan_results[i].bssid,
                      WIFI_MAC_LEN);
                strcpy(config.preSharedKey, psk);
                config.securityType = scan_results[i].securityType;
                config.wapiPskType = WIFI_PSK_TYPE_ASCII;
                config.freq = scan_results[i].frequency;
                break;
              }
            }
      
            if (i >= scan_num) {
              printf("Error: No found ssid in scan_results\n");
              OS_Sleep(3);
              continue;
            }
      
            if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
              printf("Error: AddDeviceConfig Fail\n");
            }
            printf("Config Success\n");
      
      
            if (WIFI_SUCCESS != ConnectTo(netId)) {
              printf("Error: ConnectTo Fail\n");
            } 
      
            OS_Sleep(10);
          } else {
            OS_Sleep(30);
          }
        }
      }
      
      
      static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
      {
        printf("Incoming publish at topic %s with total length %u\n", topic, (unsigned int)tot_len);
      
        /* Decode topic string into a user defined reference */
        if(strcmp(topic, "print_payload") == 0) {
          inpub_id = 0;
        } else if(topic[0] == 'A') {
          /* All topics starting with 'A' might be handled at the same way */
          inpub_id = 1;
        } else {
          /* For all other topics */
          inpub_id = 2;
        }
      }
      
      static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
      {
        printf("Incoming publish payload with length %d, flags %u\n", len, (unsigned int)flags);
      
        if(flags & MQTT_DATA_FLAG_LAST) {
          /* Last fragment of payload received (or whole part if payload fits receive buffer
             See MQTT_VAR_HEADER_BUFFER_LEN)  */
      
          /* Call function or do action depending on reference, in this case inpub_id */
          if(inpub_id == 0) {
            /* Don't trust the publisher, check zero termination */
            if(data[len-1] == 0) {
              printf("mqtt_incoming_data_cb: %s\n", (const char *)data);
            }
          } else if(inpub_id == 1) {
            /* Call an 'A' function... */
          } else {
            printf("mqtt_incoming_data_cb: Ignoring payload...\n");
          }
        } else {
          /* Handle fragmented payload, store in buffer, write to file or whatever */
        }
      }
      
      static void mqtt_sub_request_cb(void *arg, err_t result)
      {
      
        printf("Subscribe result: %d\n", result);
      }
      
      static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
      {
        err_t err;
        if(status == MQTT_CONNECT_ACCEPTED) {
          printf("mqtt_connection_cb: Successfully connected\n");
          
          /* Setup callback for incoming publish requests */
          mqtt_set_inpub_callback(client, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, arg);
          
          /* Subscribe to a topic named "subtopic" with QoS level 1, call mqtt_sub_request_cb with result */ 
          err = mqtt_subscribe(client, "sensor_cmd", 1, mqtt_sub_request_cb, arg);
      
          if(err != ERR_OK) {
            printf("mqtt_subscribe return: %d\n", err);
          }
      
          iot_publish(client, NULL);
        } else {
          printf("mqtt_connection_cb: Disconnected, reason: %d\n", status);
          
          /* Its more nice to be connected, so try to reconnect */
          //iot_client_do_connect(client);
        }  
      }
      
      static void iot_client_do_connect(mqtt_client_t *client)
      {
        struct mqtt_connect_client_info_t ci;
        ip_addr_t host_ip_addr;
        err_t err;
      
        IP4_ADDR(ip_2_ip4(&host_ip_addr), 192, 168, 8, 105);
        
        /* Setup an empty client info structure */
        memset(&ci, 0, sizeof(ci));
        
        /* Minimal amount of information required is client identifier, so set it here */ 
        ci.client_id = "lwip_test";
        ci.keep_alive = 5;
        /* Initiate client and connect to server, if this fails immediately an error code is returned
           otherwise mqtt_connection_cb will be called with connection result after attempting 
           to establish a connection with the server. 
           For now MQTT version 3.1.1 is always used */
        
        err = mqtt_client_connect(client, &host_ip_addr, 1883, mqtt_connection_cb, 0, &ci);
        
        /* For now just print the result code if something goes wrong */
        if(err != ERR_OK) {
          printf("mqtt_connect return %d\n", err);
        }
      }
      
      /* Called when publish is complete either with sucess or failure */
      static void mqtt_pub_request_cb(void *arg, err_t result)
      {
        if(result != ERR_OK) {
          printf("Publish result: %d\n", result);
        }
      }
      
      
      void iot_publish(mqtt_client_t *client, void *arg)
      {
        char pub_payload[10]= {0};
        err_t err;
        u8_t qos = 2; /* 0 1 or 2, see MQTT specification */
        u8_t retain = 0; /* No don't retain such crappy payload... */
        int sensor_data = rand() % 100;
        sprintf(pub_payload, "%d\n", sensor_data);
      
        err = mqtt_publish(client, "sensor_data", pub_payload, strlen(pub_payload), qos, retain, mqtt_pub_request_cb, arg);
        if(err != ERR_OK) {
          printf("Publish err: %d\n", err);
        }
      }
      
      static void MainThread(void *arg)
      {
      
      	mqtt_client_t *client = mqtt_client_new();
        int count = 0;
      
        while (1)
        {
          if(client != NULL) {
            if (isConnected && !isMQTTConnected) {
              printf("connect time: %d\n", count++);
              iot_client_do_connect(client);
            } 
            OS_Sleep(10);
          }
        }
      }
      
      static void WifiThread(void *arg)
      {
        wifi_device_connect();
      }
      
      void IOTMain(void)
      {
      	printf("\r\nIOT Sensor Start\r\n");
      
      	if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL,
      			    OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
      		printf("[ERR] Create MainThread Failed\n");
      	}
      
        if (OS_ThreadCreate(&g_wifi_thread, "WifiThread", WifiThread, NULL,
      			    OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
      		printf("[ERR] Create WifiThread Failed\n");
      	}
      }
      
      SYS_RUN(IOTMain);
      
      

      通过以上的程序,其实可以看到实现的逻辑就是每隔一段时间发布主题为“sensor_data”的消息,因此,我在Ubuntu上搭建好MQTT服务器后,并在Ubuntu上建立一个MQTT客户端订阅“sensor_data”主题的消息,可以定时接收到开发板发送过来的消息。效果如下:
      Screenshot from 2022-01-03 01-57-57.png
      MQTT服务器的搭建相信各位随便搜一下就能解决,就不再赘述。

      • 总结
        本篇是对XR806与MQTT服务器连接实现涉及的相关知识点描述,后续会更改程序相关逻辑以更适合物联网项目,同时后续会再发布一篇Android MQTT客户端的贴(有空的话)。
      1 Reply Last reply Reply Quote Share 1
      • 1 / 1
      • First post
        Last post

      Copyright © 2024 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号

      行为准则 | 用户协议 | 隐私权政策