嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

06-01 1095阅读

一、项目概述

学了俩月嵌入式Linux却感觉什么都不会做,看到韦东山老师的《嵌入式Linux应用开发实验班(快速入门)》课程做一个最简单的产品:

① 在屏幕上点击图标控制 LED

② 在屏幕上显示温湿度的值

③ 使用OneNet物联网云平台远程控制这个产品

花了5天做完项目,在此仅是个人学习记录、错误不少,请各位指正

以这个功能为例,抛弃暂时没必要深究的知识,把产品做出来。然后在它的基础上扩展功能:该补什么知识就去补 !!!

干中学!!!

干中学!!!

干中学!!!

韦东山老师课程链接(后续课程收费,但腾讯云平台收费了,后续也没法跟着课程做了,这部分我查阅大佬的教程使用OneNet平台做的):百问网韦东山嵌入式Linux应用开发入门实验班(快!做出产品!而不是学一堆知识点!!)哔哩哔哩bilibili

1、硬件框架

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

2、软件框架

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

3、开发环境配置

开发环境:ubuntu18.04、Linux4.9.88、Qt5.12.9

使用100ask imx6ULLPro开发板;

使用rfs从ubuntu传输文件到单板;

连接OneNet平台需要单板连接到外网,这里使用的WIFI连接,需要插上天线;

具体配置流程参考韦东山老师文档《嵌入式Linux应用开发实验班(快速入门)》

二、使用文件IO操作

1、操作LED

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

在 Linux 系统里,我们可以使用 2 种方法去操作上面的 LED:

① 使用 GPIO SYSFS 系统:

这需要一定的硬件知识,需要设置引脚的方向、数值

IMX6ULL 使用的 GPIO5_3 引脚编号是 131,可以如下操作:

写GPIO:

echo 131 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio131/direction 
echo 1 > /sys/class/gpio/gpio131/value
echo 131 > /sys/class/gpio/unexport  

读GPIO:

echo 131 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio131/direction 
cat /sys/class/gpio/gpio131/value
echo 131 > /sys/class/gpio/unexport

② 使用驱动程序:

无需硬件知识,使用 open/read/write 接口调用驱动即可

把驱动程序 led_drv.ko、 led_test 通过 ADB 上传到开发板,然后执行如下命令可以看到灯亮、灭:

移除QT程序,否则它占用LED引脚导致无法安装驱动

mv /etc/init.d/S99myqt /root
reboot
重启后执行如下命令
insmod /root/led_drv.ko 
ls /dev/100ask_led 
/root/led_test 0 on 
/root/led_test 0 off

最简单的驱动程序:led_drc.c

#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/uaccess.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;
static struct gpio_desc gpios[2] = {
    {131, 0, "led0", },
    //{132, 0, "led1", },
};
/* 主设备号                  */
static int major = 0;
static struct class *gpio_class;
/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	char tmp_buf[2];
	int err;
    int count = sizeof(gpios)/sizeof(gpios[0]);
	if (size != 2)
		return -EINVAL;
	err = copy_from_user(tmp_buf, buf, 1);
	if (tmp_buf[0] >= count)
		return -EINVAL;
	tmp_buf[1] = gpio_get_value(gpios[tmp_buf[0]].gpio);
	err = copy_to_user(buf, tmp_buf, 2);
	
	return 2;
}
static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;
    if (size != 2)
        return -EINVAL;
    err = copy_from_user(ker_buf, buf, size);
    
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;
    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
    return 2;    
}
/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
};
/* 在入口函数 */
static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i  

测试文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
static int fd;
//int led_on(int which);
//int led_off(int which);
//int led_status(int which);
/*
 * ./led_test   on 
 * ./led_test   off
 * ./led_test 
 */
int main(int argc, char **argv)
{
	int ret;
	char buf[2];
	int i;
	
	/* 1. 判断参数 */
	if (argc  

知识补充:

Linux 的 sysfs 文件系统用于管理和控制多种硬件设备和系统资源。sysfs 是一个虚拟文件系统,它将内核中的设备和驱动信息以文件的形式暴露给用户空间,允许用户通过文件操作来配置和控制硬件;

优缺点对比

方法优点缺点
sysfs简单易用:不需要编写复杂的驱动代码,适合快速开发和调试。

无需编译内核:直接在用户空间操作,无需修改内核代码

效率较低:每次操作都需要通过文件系统,效率不如直接操作驱动。

功能有限:仅支持基本的 GPIO 操作,无法实现复杂的硬件特性

驱动高效:直接与硬件交互,效率更高。
功能强大:可以实现复杂的硬件特性(如定时触发、亮度控制等)。
系统集成:更适合嵌入式系统和需要高性能的场景
开发复杂:需要编写内核驱动代码,开发难度较高。
需要编译内核:每次修改驱动都需要重新编译内核

2、操作dht11

把驱动程序 dht11_drv.ko、 dht11_test 通过rfs上传到开发板,然后执行如下命令可以看到不断打印温湿度的值:

执行如下命令

insmod /root/dht11_drv.ko 
ls /dev/mydht11 /root/dht11_test /dev/mydht11

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

dht11_drv.c:

#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "linux/jiffies.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;
static struct gpio_desc gpios[] = {
    {115, 0, "dht11", },
};
/* 主设备号                  */
static int major = 0;
static struct class *gpio_class;
static u64 g_dht11_irq_time[84];
static int g_dht11_irq_cnt = 0;
/* 环形缓冲区 */
#define BUF_LEN 128
static char g_keys[BUF_LEN];
static int r, w;
struct fasync_struct *button_fasync;
static irqreturn_t dht11_isr(int irq, void *dev_id);
static void parse_dht11_datas(void);
#define NEXT_POS(x) ((x+1) % BUF_LEN)
static int is_key_buf_empty(void)
{
	return (r == w);
}
static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}
static void put_key(char key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}
static char get_key(void)
{
	char key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	// 解析数据, 放入环形buffer, 唤醒APP
	parse_dht11_datas();
}
/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kern_buf[2];
	if (size != 2)
		return -EINVAL;
	g_dht11_irq_cnt = 0;
	/* 1. 发送18ms的低脉冲 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	gpio_direction_output(gpios[0].gpio, 0);
	gpio_free(gpios[0].gpio);
	mdelay(18);
	gpio_direction_input(gpios[0].gpio);  /* 引脚变为输入方向, 由上拉电阻拉为1 */
	/* 2. 注册中断 */
	err = request_irq(gpios[0].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[0].name, &gpios[0]);
	mod_timer(&gpios[0].key_timer, jiffies + 20);	
	/* 3. 休眠等待数据 */
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());
	free_irq(gpios[0].irq, &gpios[0]);
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 设置DHT11 GPIO引脚的初始状态: output 1 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err)
	{
		printk("%s %s %d, gpio_request err\n", __FILE__, __FUNCTION__, __LINE__);
	}
	gpio_direction_output(gpios[0].gpio, 1);
	gpio_free(gpios[0].gpio);
	/* 4. copy_to_user */
	kern_buf[0] = get_key();
	kern_buf[1] = get_key();
	printk("get val : 0x%x, 0x%x\n", kern_buf[0], kern_buf[1]);
	if ((kern_buf[0] == (char)-1) && (kern_buf[1] == (char)-1))
	{
		printk("get err val\n");
		return -EIO;
	}
	err = copy_to_user(buf, kern_buf, 2);
	
	return 2;
}
static int dht11_release (struct inode *inode, struct file *filp)
{
	return 0;
}
/* 定义自己的file_operations结构体                                              */
static struct file_operations dht11_drv = {
	.owner	 = THIS_MODULE,
	.read    = dht11_read,
	.release = dht11_release,
};
static void parse_dht11_datas(void)
{
	int i;
	u64 high_time;
	unsigned char data = 0;
	int bits = 0;
	unsigned char datas[5];
	int byte = 0;
	unsigned char crc;
	printk("g_dht11_irq_cnt = %d\n", g_dht11_irq_cnt);
	/* 数据个数: 可能是81、82、83、84 */
	if (g_dht11_irq_cnt  

dht11_test.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
static int fd;
/*
 * ./button_test /dev/mydht11
 *
 */
int main(int argc, char **argv)
{
	char buf[2];
	int ret;
	int i;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s \n", argv[0]);
		return -1;
	}
	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}
	while (1)
	{
		if (read(fd, buf, 2) == 2)
		{
			printf("get Humidity: %d, Temperature : %d\n", buf[0], buf[1]);
			sleep(1);
		}
	}
	//sleep(30);
	close(fd);
	
	return 0;
}

三、使用JsonRPC实现前后台分离

对于比较复杂的程序,前台界面显示、后台程序由不同的团队进行开发,双方定义好交互的接口即可。这样,前台、后台程序可以分别独立开发,降低相互之间的依赖。比如:

① 当更改硬件,比如更换LED引脚时,前台程序无需改变,只需要修改后台程序

② 想调整界面时,只需要修改前台程序,无需修改后台程序

前台程序、后台程序分别属于不同的“进程”,它们之间的交互需要通过“进程间通信”来实现,比如:网络通信、管道、共享内存等等。

本课程使用基于网络通信的“JsonRPC远程调用”来实现前后台程序的交互:

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

详细内容见具体配置流程参考韦东山老师文档《嵌入式Linux应用开发实验班(快速入门)》,这里只做知识性总结归纳

知识补充:

1、RPC介绍

RPC(远程过程调用)是一种用于实现分布式系统中不同进程或不同计算机之间通信的技术。RPC 的核心思想是允许我们像调用本地函数一样调用远程计算机上的函数,使得分布式系统的开发变得更加简单和高效。

JsonRPC是一种基于JSON(JavaScript Object Notation)的轻量级远程过程调用协议。与其他RPC协议相比,JsonRPC使用简单的文本格式进行通信,易于阅读和编写,广泛应用于Web服务和分布式系统中。除了JsonRPC,还有其他一些常见的RPC协议,例如:

  • XML-RPC:使用XML作为通信格式的RPC协议。
  • SOAP:基于XML的通信协议,支持多种传输协议。
  • gRPC:由Google开发的高性能、开源的RPC框架,支持多种编程语言和传输协议。

    JsonRPC协议定义了一种简单的请求-响应模型,通信双方通过发送和接收JSON格式的消息进行交互。

    2、JSON格式

    JSON(JavaScript Object Notation, JavaScript 对象表示法)是基于 ECMAScript 的一个子集设计的,是一种开放标准的文件格式和数据交换格式,它易于人阅读和编写,同时也易于机器解析和生成。 JSON 独立于语言设计,很多编程语言都支持 JSON 格式的数据交换。 JSON 是一种常用的数据格式,在电子数据交换中有多种用途,包括与服务器之间的Web 应用程序的数据交换。其简洁和清晰的层次结构有效地提升了网络传输效率,使其成为理想的数据交换语言。其文件通常使用扩展名.json 。

    例子:
    JSON解析:"{\"method\": \"add\", \"params\": [2,4], \"id\": \"2\" }"
    或者
    {
        "title":"JSON Example",
        "author": {
            "name":"John Doe",
            "age": 35,
            "isVerified":true
        },
        "tags":["json", "syntax", "example"],
        "rating": 4.5,
        "isPublished":false,
        "comments": null
    }
    

    3、JsonRPC请求响应示例

    请求示例

    一个JsonRPC请求由以下几个部分组成:

    {
      "jsonrpc": "2.0",
      "method": "methodName",
      "params": [param1, param2, ...],
      "id": 1
    }
    
    • jsonrpc:指定JsonRPC版本,通常为"2.0"。
    • method:指定要调用的远程方法名。
    • params:包含要传递给远程方法的参数列表。
    • id:请求的唯一标识符,用于将请求和响应进行匹配。

      响应示例

      一个JsonRPC响应由以下几个部分组成:

      {
        "jsonrpc": "2.0",
        "result": "resultValue",
        "error": {
         "code": 100, "message": "errorMessage"},
        "id": 1
      }
      
      • jsonrpc:指定JsonRPC版本,通常为"2.0"。
      • result:包含远程方法调用的结果值。
      • error:包含错误信息,如果请求执行过程中发生错误。
      • id:与请求中的标识符相匹配,用于将响应与请求进行匹配。

        成功和失败响应示例

        成功的JsonRPC响应示例:

        {
          "jsonrpc": "2.0",
          "result": "Hello, world!",
          "id": 1
        }
        

        失败的JsonRPC响应示例:

        { 
          "jsonrpc": "2.0",
          "error": {
              "code": -32601, 
              "message": "Method not found"},
          "id": 1
        }
        

        参数的数据类型

        JsonRPC支持以下基本数据类型作为参数和结果值:

        • 字符串(String)
        • 数字(Number)
        • 布尔值(Boolean)
        • 数组(Array)
        • 对象(Object)
        • 空值(Null)

          当参数或者返回结果中包含字节数组的时候需要注意,由于JSON是一种文本格式,所以在序列化和反序列化字节数组时,会将其转换为Base64编码的字符串。这种转换会增加数据存储的大小和处理时间。因此,对于大型字节数组,传递原始二进制数据的方式可能会更高效,而不是通过JSON进行编码和解码。在这种情况下,其他二进制传输协议(如gRPC或自定义的二进制协议)可能更适合处理字节数组的传递。

          4、 基于JsonRPC分离前后端

          cJSON 是由 Dave Gamble 创建的开源 JSON 处理库,适用于需要轻量级 JSON 解析和生成的场景。这里我们使用开源的cJOSN,需要编译 libev 库、编译 jsonrpc 库 ,并且将其添加到交叉编译链 。

          这里主要讲后端服务器操作硬件,通过JsonRPC通信,向前端qtAPP提供服务(控制LED、读取温湿度的值)。

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          rpc_server.c:

          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include "rpc.h"
          #include "led.h"
          #include "dht11.h"
          static struct jrpc_server my_server;
          /* 参数: {"params" : [0|1]} */
          cJSON * server_led_control(jrpc_context * ctx, cJSON * params, cJSON *id) {
              cJSON * status = cJSON_GetArrayItem(params,0);
              led_control(status->valueint);	
              return cJSON_CreateNumber(0);
          }
          /* 参数: {"params" : null} */
          cJSON * server_dht11_read(jrpc_context * ctx, cJSON * params, cJSON *id) {
              int array[2];
              array[0] = array[1] = 0;
              while (0 != dht11_read((char *)&array[0], (char *)&array[1]));
              return cJSON_CreateIntArray(array, 2);
          }
          int RPC_Server_Init(void) 
          {
              int err;
              
              err = jrpc_server_init(&my_server, PORT);
              if (err)
              {
                  printf("jrpc_server_init err : %d\n", err);
              }
              
              jrpc_register_procedure(&my_server, server_led_control, "led_control", NULL );
              jrpc_register_procedure(&my_server, server_dht11_read, "dht11_read", NULL );
              jrpc_server_run(&my_server);
              jrpc_server_destroy(&my_server);
              return 0;
          }
          int main(int argc, char **argv)
          {
              //初始化硬件(open设备节点)
              led_init();
              dht11_init();
              //启动服务端
              RPC_Server_Init();   
              return 0;
          }
          

          前端qt客户端调用:rpc_client.cpp

          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include "cJSON.h"
          #include "rpc.h"
          static int g_iSocketClient;
          //发送消息以使用客户端的“led_control”
          int rpc_led_control(int on)
          {
              char buf[100];
              int iLen;
              int ret = -1;
              int iSocketClient = g_iSocketClient;
              sprintf(buf, "{\"method\": \"led_control\", \"params\": [%d], \"id\": \"2\" }", on);
              iLen = send(iSocketClient, buf, strlen(buf), 0);
              if (iLen ==  strlen(buf))
              {
                  while (1)
                  {
                      iLen = read(iSocketClient, buf, sizeof(buf));
                      buf[iLen] = 0;
                      if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))
                          continue;
                      else
                          break;
                  }
                  if (iLen > 0)
                  {
                      cJSON *root = cJSON_Parse(buf);
                      cJSON *result = cJSON_GetObjectItem(root, "result");
                      ret = result->valueint;
                      cJSON_Delete(root);
                      return ret;
                  }
                  else
                  {
                      printf("read rpc reply err : %d\n", iLen);
                      return -1;
                  }
              }
              else
              {
                  printf("send rpc request err : %d, %s\n", iLen, strerror(errno));
                  return -1;
              }
          }
          //发送消息以使用客户端的“dht11_read”
          int rpc_dht11_read(char *humi, char *temp)
          {
              char buf[300];
              int iLen;
              int iSocketClient = g_iSocketClient;
              sprintf(buf, "{\"method\": \"dht11_read\"," \
                             "\"params\": [0], \"id\": \"2\" }");
              iLen = send(iSocketClient, buf, strlen(buf), 0);
              if (iLen ==  strlen(buf))
              {
                  while (1)
                  {
                      iLen = read(iSocketClient, buf, sizeof(buf));
                      buf[iLen] = 0;
                      if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))
                          continue;
                      else
                          break;
                  }
                  if (iLen > 0)
                  {
                      cJSON *root = cJSON_Parse(buf);
                      cJSON *result = cJSON_GetObjectItem(root, "result");
                      if (result)
                      {
                          cJSON * a = cJSON_GetArrayItem(result,0);
                          cJSON * b = cJSON_GetArrayItem(result,1);
                          *humi = a->valueint;
                          *temp = b->valueint;
                          cJSON_Delete(root);
                          return 0;
                      }
                      else
                      {
                          cJSON_Delete(root);
                          return -1;
                      }
                  }
                  else
                  {
                      printf("read rpc reply err : %d\n", iLen);
                      return -1;
                  }
              }
              else
              {
                  printf("send rpc request err : %d, %s\n", iLen, strerror(errno));
                  return -1;
              }
          }
          /* 连接RPC Server
           * 返回值: (>0)socket, (-1)失败
           */
          int RPC_Client_Init(void)
          {
              int iSocketClient;
              struct sockaddr_in tSocketServerAddr;
              int iRet;
              iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
              tSocketServerAddr.sin_family      = AF_INET;
              tSocketServerAddr.sin_port        = htons(PORT);
              inet_aton("127.0.0.1", &tSocketServerAddr.sin_addr);
              memset(tSocketServerAddr.sin_zero, 0, 8);
              iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
              if (-1 == iRet)
              {
                  printf("connect error!\n");
                  return -1;
              }
              g_iSocketClient = iSocketClient;
              return iSocketClient;
          }
          

          四、使用MQTT协议连接OneNet实现远程控制

          这部分参考:【嵌入式linux开发】智能家居入门6:最新ONENET,物联网开放平台(QT、微信小程序、MQTT协议、ONENET云平台、旭日x3派)_onenet云端-CSDN博客

          ONENET云平台创建产品与设备参考下方视频:

          https://player.youku.com/embed/XNjQxNTE0OTAyNA==

          需要注意的点:

          ①数据协议:这里选择的是OneJson,当然也可以选择数据流,但是它们对应的发送数据和接收数据的topic是不一样的,所以如果想省事直接使用本文代码,那就选择OneJson。

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          ②新版三元组:

          按照视频创建完毕后,进入设备管理的详情中,可以看到后续会用到的三个参数:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          1、测试前的准备

          ①token:

          产品与设备创建完成之后,按照文档指示,需要计算token,在连接时会用到:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          这里的clienid和username就是前面截图中包含的两个参数:设备名称、产品ID,这里的password就是使用官方软件计算生成的token。接下来视频演示如何计算token:

          重点如下:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          纠错:上图中的时间过小,在后面多加随便一个数字既可:2810295937232。

          ②OneJson数据协议对应的发布、订阅topic:

          文档中有明确给出OneJson数据协议(物模型)的发布和订阅topic,如果数据协议选择数据流的小伙伴,在文档的这个界面往后翻翻就可以看到对应的。

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          在连上服务器之后,对这两个topic操作就可以上传和接收数据啦!

          2、测试

          下载客户端软件,提取码:q1a1,这是DS小龙哥开源的,填入自己的信息:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          ①数据上传测试:

          点击登录即可连接上服务器,然后点击发布主题就可以把数据发布到特定的物模型中,可以在onenet云平台中观察是否成功:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          ②数据下发测试:

          onenet中进入设备调试界面,将fan_ctl设置为true,然后点击属性期望值设置。客户端软件同样连接服务器之后,点击订阅主题,观察是否收到服务器下发的消息:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          这样双向测试就算完成了,接下来就是下位机与上位机的代码简介。

          使用的库函数代码,代码放在工程中,后续调用里面的函数实现mqtt通讯:

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          五、QT APP编写

          使用QtCreater工具简单设计UI,重点是Qt的信号与槽函数的设计!!!

          学到这里,可以去补充一下从c过渡到c++的基本语法,以及C++面向对象设计语法;

          参考教程:

          【正点原子】嵌入式Qt5 C++开发视频_哔哩哔哩_bilibili

          C语言 转 C++ 简单教程_哔哩哔哩_bilibili

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          1、代码

          mainwindow.h

          #ifndef MAINWINDOW_H
          #define MAINWINDOW_H
          #include 
          #include 
          #include 
          #include 
          //日期时间
          #include 
          #include 
          #include 
          //tcp客户端通过http协议连接云服务器
          #include 
          #include 
          #include 
          #include 
          #include 
          #include "dht11_thread.h"
          #include 
          QT_BEGIN_NAMESPACE
          namespace Ui { class MainWindow; }
          QT_END_NAMESPACE
          class MainWindow : public QMainWindow
          {
              Q_OBJECT
          public:
              MainWindow(QWidget *parent = nullptr);
              ~MainWindow();
              QString serverIp = "183.230.40.96"; // 替换为服务器IP
              quint16 serverPort = 1883; // 替换为服务器端口
              QString clientId = "dev1"; // 替换为设备名称
              QString username = "2Vx5G6or93"; // 替换为产品ID
              QString password = "version=2018-10-31&res=products%2F2Vx5G6or93%2Fdevices%2Fdev1&et=21025937232&method=md5&sign=GdHTauIRt2Twis6BZgoIaA%3D%3D"; // 替换为token
              QString topicToSubscribe = "$sys/2Vx5G6or93/dev1/thing/property/set"; // 替换为要订阅的主题
              QString topicToPublish = "$sys/2Vx5G6or93/dev1/thing/property/post"; // 替换为要发布消息的主题
              //三个处理函数
              QString convertObjectToJsonString(const QJsonObject& object);
              QJsonObject createJsonObj(const QMap& dataMap);
              void parseJsonAndAction(const QByteArray& jsonData);
          private:
              Ui::MainWindow *ui;
              QDateTimeEdit *dateTimeEdit;
              QTimer *timer;
              MQTT_WorkClass *mqttClient;
              QLabel *labelHumi;
              QLabel *labelTemp;
          private slots:
              void on_pushButton_clicked();//led开关
              // 槽函数用于上报温湿度信息
              void onUpdateData(char humidity, char temperature);
              //断线重连
              void reconnect(bool);
              //接收服务器下发的数据
              void receiveWechatData(QByteArray);
          };
          #endif // MAINWINDOW_H
          

          mainwindow.cpp

          #include "mainwindow.h"
          #include "ui_mainwindow.h"
          #include 
          #include "rpc_client.h"
          #include "mqtt.h"
          #include "dht11_thread.h"
          MainWindow::MainWindow(QWidget *parent)
              : QMainWindow(parent)
              , ui(new Ui::MainWindow)
          {
              ui->setupUi(this);
              labelHumi = this->findChild("label_4");//湿度
              labelTemp = this->findChild("label_3");//温度
              //温湿度线程
              // 创建线程对象并连接信号槽
          //    connect(dhtThread, SIGNAL(updateData(char,char)),this,SLOT(onUpdateData(char,char)));
              /****************************** 连接服务器 ******************************************/
              mqttClient = new MQTT_WorkClass();
              // 设置MQTT服务器的地址和其他参数
              mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
              // 连接到MQTT服务器
              mqttClient->run();
              /******************************* 订阅主题 *******************************************/
              mqttClient->slot_SubscribeTopic(topicToSubscribe);
              /****************************** 信号与槽连接 *****************************************/
              //断线重连槽函数
              connect(mqttClient,SIGNAL(MQTT_ConnectState(bool)),this,SLOT(reconnect(bool)));
              //接收到服务器下发数据的重写槽函数
              connect(mqttClient,SIGNAL(ReceiveWechatData(QByteArray)),this,SLOT(receiveWechatData(QByteArray)));
          }
          /************************************槽函数**********************************************/
          //上报温湿度
          void MainWindow::onUpdateData(char humidity, char temperature)
          {
              qDebug() 
              mqttClient = new MQTT_WorkClass();
              // 设置MQTT服务器的地址和其他参数
              mqttClient-Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
              // 连接到MQTT服务器
              mqttClient-run();
              // 订阅主题
              mqttClient-slot_SubscribeTopic(topicToSubscribe);
          }
          //解析服务器下发数据,槽函数
          void MainWindow::receiveWechatData(QByteArray data)
          {
              //qDebug()
                  // 删除起始位置之前的所有字符
                  data = data.mid(startPos);
              } else {
                  qDebug() 
              // 将QJsonObject转换为QJsonDocument
              QJsonDocument jsonDoc(object);
              // 将QJsonDocument转换为QString,使用Compact模式
              return jsonDoc.toJson(QJsonDocument::Compact);
          }
          //组织上传数据,函数
          QJsonObject MainWindow::createJsonObj(const QMap
              QJsonObject rootObject;
              // 设置id字段
              rootObject["id"] = "123";
              // 创建params对象
              QJsonObject paramsObject;
              // 遍历数据映射,添加每个数据字段
              for (auto it = dataMap.constBegin(); it != dataMap.constEnd(); ++it) {
                  QJsonObject valueObj;
                  valueObj["value"] = it.value(); // 设置数值
                  paramsObject[it.key()] = valueObj; // 添加到params对象
              }
              // 将params对象添加到rootObject
              rootObject["params"] = paramsObject;
              return rootObject;
          }
          //解析服务器下发指令并动作函数
          void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {
              // 将QByteArray转换为QJsonDocument
              QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
              if (jsonDoc.isNull()) {
                  qDebug() 
                  qDebug() 
                  QJsonObject paramsObject = jsonObject["params"].toObject();
                  QDateTime currentDateTime = QDateTime::currentDateTime();
                  // 格式化日期时间为字符串,例如:"2024-08-16 18:32:10"
                  QString dateTimeStr = currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
                  // 提取led_ctl的值
                  if (paramsObject.contains("led_ctl"))
                  {
                      bool ledCtlValue = paramsObject["led_ctl"].toBool();
                      qDebug() 
                          QString message = dateTimeStr + " - 手机控制关闭客厅灯";
                          ui-textBrowser-append(message);
                          ui-radioButton-setText("客厅灯|离线");
                          ui-radioButton-setChecked(0);
                          rpc_led_control(0);
                      }
                      else if(ledCtlValue == 1)
                      {
                          QString message = dateTimeStr + " - 手机控制打开客厅灯";
                          ui-textBrowser-append(message);
                          ui-radioButton-setText("客厅灯|在线");
                          ui->radioButton->setChecked(1);
                          rpc_led_control(1);
                      }
                  } else
                  {
                      qDebug() 
                  qDebug() 
              delete ui;
          }
          void MainWindow::on_pushButton_clicked()
          {
              static int status = 1;
              if (status)
                  qDebug()
          #禁止LCD黑屏
          echo -e "\033[9;0]"  /dev/tty0
          #设置QT运行环境
          export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1
          export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0
          export QT_QPA_FONTDIR=/usr/lib/fonts/
          psplash-write "PROGRESS 95"
          psplash-write "QUIT"
          /root/rpc_server &
          sleep 5
          /root/myapp  &
          }
          stop() {
              killall myapp
          }
          case "$1" in
              start)
                  start
                  ;;
              stop)
                  stop
                  ;;
              *)
                  echo "Usage: $0 {start| stop|restart}"
                  exit 1
          esac
          exit $?
          
                  ssid="Play_434_2.4G"  //wifi名称 ,这一行前面用tab代替空格
                  psk="434434434"       //wifi密码 ,这一行前面用tab代替空格
          }
           
           
          //配置udhcpc使其分配到动态ip后并配置到rtl8723bu网卡上
          //在Ubuntu上,使用本系统的busybox的udhcpc配置文件
          cd /home/book/100ask_imx6ull-sdk/Busybox_1.30.0/examples/udhcp
          cp simple.script /home/book/nfs_rootfs       //Ubuntu挂载在开发板上的nfs路径
           
           
          //在开发板上将simple.script更改名称并放在特定路径
          mv /mnt/simple.script /usr/share/udhcpc/default.script
          chmod 755 /usr/share/udhcpc/default.script
           
           
          //启动wpa_supplicant应用 
          // 1 较新Linux系统使用以下这行
          wpa_supplicant -Dnl80211  -c /etc/wpa_supplicant.conf -i wlan0 &
           
          // 2 较老Linux系统使用以下这行
          wpa_supplicant -D wext -c /etc/wpa_supplicant.conf -i wlan0 &
           
           
          //搜索wifi并根据刚才的配置进行wifi连接
          udhcpc -i wlan0
           
           
          //使系统支持域名解析
          echo "nameserver 8.8.8.8"  /etc/resolv.conf
          
          p开发板开机自动连接WiFi,看到了嘛,我们还是修改rCS,来设置开机启动时的工作。/p pre class="brush:python;toolbar:false"//在开发板上 vi /etc/init.d/rcS //在该文件中的文末输入的代码效果相当于在shell中输入命令一样 //输入以下命令 source /etc/profile //刷新环境变量 insmod /opt/gpio_driver.ko insmod /opt/8723bu.ko //安装网卡驱动 ifconfig eth0 down //关闭有线网卡 ifconfig wlan0 up //打开无线网卡 wpa_supplicant -Dnl80211 -c /etc/wpa_supplicant.conf -i wlan0 & //打开wifi工具 udhcpc -i wlan0 //连接wifi echo "nameserver 8.8.8.8" > /etc/resolv.conf //解析域名功能配置 cd /opt //进入我的Qt目录 /opt/1armQtProject & //执行QT程序

          六、项目结果

          项目较为简单,为期5天即可完成。但从理论走向实践,学了那么多东西,用不到很快就忘记了。

          “嵌入式开发中,基础知识太多了,学完 ABCDEFG,你都没办法做出一个最简单的产品” --韦东山老师

          一次项目作为入门,作为框架,开启新阶段的学习。

          继续沉淀。

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

          注:参考资料均已备注,如有侵权,立即删除。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码