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

打印 上一主题 下一主题

主题 1639|帖子 1639|积分 4917

一、项目概述

学了俩月嵌入式Linux却感觉什么都不会做,看到韦东山老师的《嵌入式Linux应用开发实行班(快速入门)》课程做一个最简朴的产物:
① 在屏幕上点击图标控制 LED
② 在屏幕上显示温湿度的值
③ 使用OneNet物联网云平台远程控制这个产物
   花了5天做完项目,在此仅是个人学习记载、错误不少,请各位指正
  以这个功能为例,抛弃临时没必要穷究的知识,把产物做出来。然后在它的底子上扩展功能:该补什么知识就去补 !!!
干中学!!!
干中学!!!
干中学!!!
   韦东山老师课程链接(后续课程收费,但腾讯云平台收费了,后续也没法跟着课程做了,这部分我查阅大佬的教程使用OneNet平台做的):百问网韦东山嵌入式Linux应用开发入门实行班(快!做生产物!而不是学一堆知识点!!)哔哩哔哩bilibili
  1、硬件框架


2、软件框架


3、开发情况设置

开发情况:ubuntu18.04、Linux4.9.88、Qt5.12.9
使用100ask imx6ULLPro开发板;
使用rfs从ubuntu传输文件到单板;
毗连OneNet平台需要单板毗连到外网,这里使用的WIFI毗连,需要插上天线;
   详细设置流程参考韦东山老师文档《嵌入式Linux应用开发实行班(快速入门)》
  二、使用文件IO操纵

1、操纵LED


在 Linux 体系里,我们可以使用 2 种方法去操纵上面的 LED:
① 使用 GPIO SYSFS 体系:
这需要一定的硬件知识,需要设置引脚的方向、数值
IMX6ULL 使用的 GPIO5_3 引脚编号是 131,可以如下操纵:
写GPIO:
  1. echo 131 > /sys/class/gpio/export
  2. echo out > /sys/class/gpio/gpio131/direction
  3. echo 1 > /sys/class/gpio/gpio131/value
  4. echo 131 > /sys/class/gpio/unexport  
复制代码
读GPIO:
  1. echo 131 > /sys/class/gpio/export
  2. echo in > /sys/class/gpio/gpio131/direction
  3. cat /sys/class/gpio/gpio131/value
  4. echo 131 > /sys/class/gpio/unexport
复制代码
② 使用驱动程序:
无需硬件知识,使用 open/read/write 接口调用驱动即可
把驱动程序 led_drv.ko、 led_test 通过 ADB 上传到开发板,然后执行如下下令可以看到灯亮、灭:
移除QT程序,否则它占用LED引脚导致无法安装驱动
  1. mv /etc/init.d/S99myqt /root
  2. reboot
  3. 重启后执行如下命令
  4. insmod /root/led_drv.ko
  5. ls /dev/100ask_led
  6. /root/led_test 0 on
  7. /root/led_test 0 off
复制代码
最简朴的驱动程序:led_drc.c
  1. #include "asm-generic/errno-base.h"
  2. #include "asm-generic/gpio.h"
  3. #include "asm/uaccess.h"
  4. #include <linux/module.h>
  5. #include <linux/poll.h>
  6. #include <linux/fs.h>
  7. #include <linux/errno.h>
  8. #include <linux/miscdevice.h>
  9. #include <linux/kernel.h>
  10. #include <linux/major.h>
  11. #include <linux/mutex.h>
  12. #include <linux/proc_fs.h>
  13. #include <linux/seq_file.h>
  14. #include <linux/stat.h>
  15. #include <linux/init.h>
  16. #include <linux/device.h>
  17. #include <linux/tty.h>
  18. #include <linux/kmod.h>
  19. #include <linux/gfp.h>
  20. #include <linux/gpio/consumer.h>
  21. #include <linux/platform_device.h>
  22. #include <linux/of_gpio.h>
  23. #include <linux/of_irq.h>
  24. #include <linux/interrupt.h>
  25. #include <linux/irq.h>
  26. #include <linux/slab.h>
  27. #include <linux/fcntl.h>
  28. #include <linux/timer.h>
  29. struct gpio_desc{
  30.         int gpio;
  31.         int irq;
  32.     char *name;
  33.     int key;
  34.         struct timer_list key_timer;
  35. } ;
  36. static struct gpio_desc gpios[2] = {
  37.     {131, 0, "led0", },
  38.     //{132, 0, "led1", },
  39. };
  40. /* 主设备号                                                                 */
  41. static int major = 0;
  42. static struct class *gpio_class;
  43. /* 实现对应的open/read/write等函数,填入file_operations结构体                   */
  44. static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
  45. {
  46.         char tmp_buf[2];
  47.         int err;
  48.     int count = sizeof(gpios)/sizeof(gpios[0]);
  49.         if (size != 2)
  50.                 return -EINVAL;
  51.         err = copy_from_user(tmp_buf, buf, 1);
  52.         if (tmp_buf[0] >= count)
  53.                 return -EINVAL;
  54.         tmp_buf[1] = gpio_get_value(gpios[tmp_buf[0]].gpio);
  55.         err = copy_to_user(buf, tmp_buf, 2);
  56.        
  57.         return 2;
  58. }
  59. static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
  60. {
  61.     unsigned char ker_buf[2];
  62.     int err;
  63.     if (size != 2)
  64.         return -EINVAL;
  65.     err = copy_from_user(ker_buf, buf, size);
  66.    
  67.     if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
  68.         return -EINVAL;
  69.     gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
  70.     return 2;   
  71. }
  72. /* 定义自己的file_operations结构体                                              */
  73. static struct file_operations gpio_key_drv = {
  74.         .owner         = THIS_MODULE,
  75.         .read    = gpio_drv_read,
  76.         .write   = gpio_drv_write,
  77. };
  78. /* 在入口函数 */
  79. static int __init gpio_drv_init(void)
  80. {
  81.     int err;
  82.     int i;
  83.     int count = sizeof(gpios)/sizeof(gpios[0]);
  84.    
  85.         printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  86.        
  87.         for (i = 0; i < count; i++)
  88.         {               
  89.                 /* set pin as output */
  90.                 err = gpio_request(gpios[i].gpio, gpios[i].name);
  91.                 if (err < 0) {
  92.                         printk("can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);
  93.                         return -ENODEV;
  94.                 }
  95.                
  96.                 gpio_direction_output(gpios[i].gpio, 1);
  97.         }
  98.         /* 注册file_operations         */
  99.         major = register_chrdev(0, "100ask_led", &gpio_key_drv);  /* /dev/gpio_desc */
  100.         gpio_class = class_create(THIS_MODULE, "100ask_led_class");
  101.         if (IS_ERR(gpio_class)) {
  102.                 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  103.                 unregister_chrdev(major, "100ask_led_class");
  104.                 return PTR_ERR(gpio_class);
  105.         }
  106.         device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /* /dev/100ask_gpio */
  107.        
  108.         return err;
  109. }
  110. /* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
  111. */
  112. static void __exit gpio_drv_exit(void)
  113. {
  114.     int i;
  115.     int count = sizeof(gpios)/sizeof(gpios[0]);
  116.    
  117.         printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  118.         device_destroy(gpio_class, MKDEV(major, 0));
  119.         class_destroy(gpio_class);
  120.         unregister_chrdev(major, "100ask_led");
  121.         for (i = 0; i < count; i++)
  122.         {
  123.                 gpio_free(gpios[i].gpio);               
  124.         }
  125. }
  126. /* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
  127. module_init(gpio_drv_init);
  128. module_exit(gpio_drv_exit);
  129. MODULE_LICENSE("GPL");
复制代码
测试文件
  1. #include <stdlib.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <poll.h>
  9. #include <signal.h>
  10. static int fd;
  11. //int led_on(int which);
  12. //int led_off(int which);
  13. //int led_status(int which);
  14. /*
  15. * ./led_test <0|1|2|..>  on
  16. * ./led_test <0|1|2|..>  off
  17. * ./led_test <0|1|2|..>
  18. */
  19. int main(int argc, char **argv)
  20. {
  21.         int ret;
  22.         char buf[2];
  23.         int i;
  24.        
  25.         /* 1. 判断参数 */
  26.         if (argc < 2)
  27.         {
  28.                 printf("Usage: %s <0|1|2|...> [on | off]\n", argv[0]);
  29.                 return -1;
  30.         }
  31.         /* 2. 打开文件 */
  32.         fd = open("/dev/100ask_led", O_RDWR);
  33.         if (fd == -1)
  34.         {
  35.                 printf("can not open file /dev/100ask_led\n");
  36.                 return -1;
  37.         }
  38.         if (argc == 3)
  39.         {
  40.                 /* write */
  41.                 buf[0] = strtol(argv[1], NULL, 0);
  42.                 if (strcmp(argv[2], "on") == 0)
  43.                         buf[1] = 0;
  44.                 else
  45.                         buf[1] = 1;
  46.                
  47.                 ret = write(fd, buf, 2);
  48.         }
  49.         else
  50.         {
  51.                 buf[0] = strtol(argv[1], NULL, 0);
  52.                 ret = read(fd, buf, 2);
  53.                 if (ret == 2)
  54.                 {
  55.                         printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");
  56.                 }
  57.         }
  58.        
  59.         close(fd);
  60.        
  61.         return 0;
  62. }
复制代码
知识增补:
Linux 的 sysfs 文件体系用于管理和控制多种硬件装备和体系资源。sysfs 是一个虚拟文件体系,它将内核中的装备和驱动信息以文件的情势暴露给用户空间,允许用户通过文件操纵来设置和控制硬件;
优缺点对比
方法优点缺点sysfs简朴易用:不需要编写复杂的驱动代码,得当快速开发和调试。
无需编译内核:直接在用户空间操纵,无需修改内核代码服从较低:每次操纵都需要通过文件体系,服从不如直接操纵驱动。
功能有限:仅支持基本的 GPIO 操纵,无法实现复杂的硬件特性驱动高效:直接与硬件交互,服从更高。
功能强大:可以实现复杂的硬件特性(如定时触发、亮度控制等)。
体系集成:更得当嵌入式体系和需要高性能的场景开发复杂:需要编写内核驱动代码,开发难度较高。
需要编译内核:每次修改驱动都需要重新编译内核 2、操纵dht11

把驱动程序 dht11_drv.ko、 dht11_test 通过rfs上传到开发板,然后执行如下下令可以看到不停打印温湿度的值:
执行如下下令
  1. insmod /root/dht11_drv.ko
  2. ls /dev/mydht11 /root/dht11_test /dev/mydht11
复制代码

dht11_drv.c:
  1. #include "asm-generic/errno-base.h"
  2. #include "asm-generic/gpio.h"
  3. #include "linux/jiffies.h"
  4. #include <linux/module.h>
  5. #include <linux/poll.h>
  6. #include <linux/delay.h>
  7. #include <linux/fs.h>
  8. #include <linux/errno.h>
  9. #include <linux/miscdevice.h>
  10. #include <linux/kernel.h>
  11. #include <linux/major.h>
  12. #include <linux/mutex.h>
  13. #include <linux/proc_fs.h>
  14. #include <linux/seq_file.h>
  15. #include <linux/stat.h>
  16. #include <linux/init.h>
  17. #include <linux/device.h>
  18. #include <linux/tty.h>
  19. #include <linux/kmod.h>
  20. #include <linux/gfp.h>
  21. #include <linux/gpio/consumer.h>
  22. #include <linux/platform_device.h>
  23. #include <linux/of_gpio.h>
  24. #include <linux/of_irq.h>
  25. #include <linux/interrupt.h>
  26. #include <linux/irq.h>
  27. #include <linux/slab.h>
  28. #include <linux/fcntl.h>
  29. #include <linux/timer.h>
  30. struct gpio_desc{
  31.         int gpio;
  32.         int irq;
  33.     char *name;
  34.     int key;
  35.         struct timer_list key_timer;
  36. } ;
  37. static struct gpio_desc gpios[] = {
  38.     {115, 0, "dht11", },
  39. };
  40. /* 主设备号                                                                 */
  41. static int major = 0;
  42. static struct class *gpio_class;
  43. static u64 g_dht11_irq_time[84];
  44. static int g_dht11_irq_cnt = 0;
  45. /* 环形缓冲区 */
  46. #define BUF_LEN 128
  47. static char g_keys[BUF_LEN];
  48. static int r, w;
  49. struct fasync_struct *button_fasync;
  50. static irqreturn_t dht11_isr(int irq, void *dev_id);
  51. static void parse_dht11_datas(void);
  52. #define NEXT_POS(x) ((x+1) % BUF_LEN)
  53. static int is_key_buf_empty(void)
  54. {
  55.         return (r == w);
  56. }
  57. static int is_key_buf_full(void)
  58. {
  59.         return (r == NEXT_POS(w));
  60. }
  61. static void put_key(char key)
  62. {
  63.         if (!is_key_buf_full())
  64.         {
  65.                 g_keys[w] = key;
  66.                 w = NEXT_POS(w);
  67.         }
  68. }
  69. static char get_key(void)
  70. {
  71.         char key = 0;
  72.         if (!is_key_buf_empty())
  73.         {
  74.                 key = g_keys[r];
  75.                 r = NEXT_POS(r);
  76.         }
  77.         return key;
  78. }
  79. static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
  80. // static void key_timer_expire(struct timer_list *t)
  81. static void key_timer_expire(unsigned long data)
  82. {
  83.         // 解析数据, 放入环形buffer, 唤醒APP
  84.         parse_dht11_datas();
  85. }
  86. /* 实现对应的open/read/write等函数,填入file_operations结构体                   */
  87. static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
  88. {
  89.         int err;
  90.         char kern_buf[2];
  91.         if (size != 2)
  92.                 return -EINVAL;
  93.         g_dht11_irq_cnt = 0;
  94.         /* 1. 发送18ms的低脉冲 */
  95.         err = gpio_request(gpios[0].gpio, gpios[0].name);
  96.         gpio_direction_output(gpios[0].gpio, 0);
  97.         gpio_free(gpios[0].gpio);
  98.         mdelay(18);
  99.         gpio_direction_input(gpios[0].gpio);  /* 引脚变为输入方向, 由上拉电阻拉为1 */
  100.         /* 2. 注册中断 */
  101.         err = request_irq(gpios[0].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[0].name, &gpios[0]);
  102.         mod_timer(&gpios[0].key_timer, jiffies + 20);       
  103.         /* 3. 休眠等待数据 */
  104.         wait_event_interruptible(gpio_wait, !is_key_buf_empty());
  105.         free_irq(gpios[0].irq, &gpios[0]);
  106.         //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
  107.         /* 设置DHT11 GPIO引脚的初始状态: output 1 */
  108.         err = gpio_request(gpios[0].gpio, gpios[0].name);
  109.         if (err)
  110.         {
  111.                 printk("%s %s %d, gpio_request err\n", __FILE__, __FUNCTION__, __LINE__);
  112.         }
  113.         gpio_direction_output(gpios[0].gpio, 1);
  114.         gpio_free(gpios[0].gpio);
  115.         /* 4. copy_to_user */
  116.         kern_buf[0] = get_key();
  117.         kern_buf[1] = get_key();
  118.         printk("get val : 0x%x, 0x%x\n", kern_buf[0], kern_buf[1]);
  119.         if ((kern_buf[0] == (char)-1) && (kern_buf[1] == (char)-1))
  120.         {
  121.                 printk("get err val\n");
  122.                 return -EIO;
  123.         }
  124.         err = copy_to_user(buf, kern_buf, 2);
  125.        
  126.         return 2;
  127. }
  128. static int dht11_release (struct inode *inode, struct file *filp)
  129. {
  130.         return 0;
  131. }
  132. /* 定义自己的file_operations结构体                                              */
  133. static struct file_operations dht11_drv = {
  134.         .owner         = THIS_MODULE,
  135.         .read    = dht11_read,
  136.         .release = dht11_release,
  137. };
  138. static void parse_dht11_datas(void)
  139. {
  140.         int i;
  141.         u64 high_time;
  142.         unsigned char data = 0;
  143.         int bits = 0;
  144.         unsigned char datas[5];
  145.         int byte = 0;
  146.         unsigned char crc;
  147.         printk("g_dht11_irq_cnt = %d\n", g_dht11_irq_cnt);
  148.         /* 数据个数: 可能是81、82、83、84 */
  149.         if (g_dht11_irq_cnt < 81)
  150.         {
  151.                 /* 出错 */
  152.                 put_key(-1);
  153.                 put_key(-1);
  154.                 // 唤醒APP
  155.                 wake_up_interruptible(&gpio_wait);
  156.                 g_dht11_irq_cnt = 0;
  157.                 return;
  158.         }
  159.         // 解析数据
  160.         for (i = g_dht11_irq_cnt - 80; i < g_dht11_irq_cnt; i+=2)
  161.         {
  162.                 high_time = g_dht11_irq_time[i] - g_dht11_irq_time[i-1];
  163.                 data <<= 1;
  164.                 if (high_time > 50000) /* data 1 */
  165.                 {
  166.                         data |= 1;
  167.                 }
  168.                 bits++;
  169.                 if (bits == 8)
  170.                 {
  171.                         datas[byte] = data;
  172.                         data = 0;
  173.                         bits = 0;
  174.                         byte++;
  175.                 }
  176.         }
  177.         // 放入环形buffer
  178.         crc = datas[0] + datas[1] + datas[2] + datas[3];
  179.         if (crc == datas[4])
  180.         {
  181.                 put_key(datas[0]);
  182.                 put_key(datas[2]);
  183.         }
  184.         else
  185.         {
  186.                 printk("dht11 crc err\n");
  187.                 put_key(-1);
  188.                 put_key(-1);
  189.         }
  190.         g_dht11_irq_cnt = 0;
  191.         // 唤醒APP
  192.         wake_up_interruptible(&gpio_wait);
  193. }
  194. static irqreturn_t dht11_isr(int irq, void *dev_id)
  195. {
  196.         struct gpio_desc *gpio_desc = dev_id;
  197.         u64 time;
  198.        
  199.         /* 1. 记录中断发生的时间 */
  200.         time = ktime_get_ns();
  201.         g_dht11_irq_time[g_dht11_irq_cnt] = time;
  202.         /* 2. 累计次数 */
  203.         g_dht11_irq_cnt++;
  204.         /* 3. 次数足够: 解析数据, 放入环形buffer, 唤醒APP */
  205.         if (g_dht11_irq_cnt == 84)
  206.         {
  207.                 del_timer(&gpio_desc->key_timer);
  208.                 parse_dht11_datas();
  209.         }
  210.         return IRQ_HANDLED;
  211. }
  212. /* 在入口函数 */
  213. static int __init dht11_init(void)
  214. {
  215.     int err;
  216.     int i;
  217.     int count = sizeof(gpios)/sizeof(gpios[0]);
  218.    
  219.         printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  220.        
  221.         for (i = 0; i < count; i++)
  222.         {               
  223.                 gpios[i].irq  = gpio_to_irq(gpios[i].gpio);
  224.                 /* 设置DHT11 GPIO引脚的初始状态: output 1 */
  225.                 err = gpio_request(gpios[i].gpio, gpios[i].name);
  226.                 gpio_direction_output(gpios[i].gpio, 1);
  227.                 gpio_free(gpios[i].gpio);
  228.                 setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
  229.                  //timer_setup(&gpios[i].key_timer, key_timer_expire, 0);
  230.                 //gpios[i].key_timer.expires = ~0;
  231.                 //add_timer(&gpios[i].key_timer);
  232.                 //err = request_irq(gpios[i].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);
  233.         }
  234.         /* 注册file_operations         */
  235.         major = register_chrdev(0, "100ask_dht11", &dht11_drv);  /* /dev/gpio_desc */
  236.         gpio_class = class_create(THIS_MODULE, "100ask_dht11_class");
  237.         if (IS_ERR(gpio_class)) {
  238.                 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  239.                 unregister_chrdev(major, "100ask_dht11");
  240.                 return PTR_ERR(gpio_class);
  241.         }
  242.         device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "mydht11"); /* /dev/mydht11 */
  243.        
  244.         return err;
  245. }
  246. /* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
  247. */
  248. static void __exit dht11_exit(void)
  249. {
  250.     int i;
  251.     int count = sizeof(gpios)/sizeof(gpios[0]);
  252.    
  253.         printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  254.         device_destroy(gpio_class, MKDEV(major, 0));
  255.         class_destroy(gpio_class);
  256.         unregister_chrdev(major, "100ask_dht11");
  257.         for (i = 0; i < count; i++)
  258.         {
  259.                 //free_irq(gpios[i].irq, &gpios[i]);
  260.                 //del_timer(&gpios[i].key_timer);
  261.         }
  262. }
  263. /* 7. 其他完善:提供设备信息,自动创建设备节点                                     */
  264. module_init(dht11_init);
  265. module_exit(dht11_exit);
  266. MODULE_LICENSE("GPL");
复制代码
dht11_test.c
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include <poll.h>
  8. #include <signal.h>
  9. static int fd;
  10. /*
  11. * ./button_test /dev/mydht11
  12. *
  13. */
  14. int main(int argc, char **argv)
  15. {
  16.         char buf[2];
  17.         int ret;
  18.         int i;
  19.        
  20.         /* 1. 判断参数 */
  21.         if (argc != 2)
  22.         {
  23.                 printf("Usage: %s <dev>\n", argv[0]);
  24.                 return -1;
  25.         }
  26.         /* 2. 打开文件 */
  27.         fd = open(argv[1], O_RDWR | O_NONBLOCK);
  28.         if (fd == -1)
  29.         {
  30.                 printf("can not open file %s\n", argv[1]);
  31.                 return -1;
  32.         }
  33.         while (1)
  34.         {
  35.                 if (read(fd, buf, 2) == 2)
  36.                 {
  37.                         printf("get Humidity: %d, Temperature : %d\n", buf[0], buf[1]);
  38.                         sleep(1);
  39.                 }
  40.         }
  41.         //sleep(30);
  42.         close(fd);
  43.        
  44.         return 0;
  45. }
复制代码
三、使用JsonRPC实现前后台分离

对于比较复杂的程序,前台界面显示、后台程序由不同的团队进行开发,两边定义好交互的接口即可。这样,前台、后台程序可以分别独立开发,降低相互之间的依赖。好比:
① 当更改硬件,好比更换LED引脚时,前台程序无需改变,只需要修改后台程序
② 想调整界面时,只需要修改前台程序,无需修改后台程序
前台程序、后台程序分别属于不同的“进程”,它们之间的交互需要通过“进程间通讯”来实现,好比:网络通讯、管道、共享内存等等。
本课程使用基于网络通讯的“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 。
  1. 例子:
  2. JSON解析:"{"method": "add", "params": [2,4], "id": "2" }"
  3. 或者
  4. {
  5.     "title":"JSON Example",
  6.     "author": {
  7.         "name":"John Doe",
  8.         "age": 35,
  9.         "isVerified":true
  10.     },
  11.     "tags":["json", "syntax", "example"],
  12.     "rating": 4.5,
  13.     "isPublished":false,
  14.     "comments": null
  15. }
复制代码
3、JsonRPC哀求相应示例

哀求示例
一个JsonRPC哀求由以下几个部分构成:
  1. {
  2.   "jsonrpc": "2.0",
  3.   "method": "methodName",
  4.   "params": [param1, param2, ...],
  5.   "id": 1
  6. }
复制代码


  • jsonrpc:指定JsonRPC版本,通常为"2.0"。
  • method:指定要调用的远程方法名。
  • params:包罗要传递给远程方法的参数列表。
  • id:哀求的唯一标识符,用于将哀求和相应进行匹配。
相应示例
一个JsonRPC相应由以下几个部分构成:
  1. {
  2.   "jsonrpc": "2.0",
  3.   "result": "resultValue",
  4.   "error": {
  5.    "code": 100, "message": "errorMessage"},
  6.   "id": 1
  7. }
复制代码


  • jsonrpc:指定JsonRPC版本,通常为"2.0"。
  • result:包罗远程方法调用的效果值。
  • error:包罗错误信息,如果哀求执行过程中发生错误。
  • id:与哀求中的标识符相匹配,用于将相应与哀求进行匹配。
成功和失败相应示例
成功的JsonRPC相应示例:
  1. {
  2.   "jsonrpc": "2.0",
  3.   "result": "Hello, world!",
  4.   "id": 1
  5. }
复制代码
失败的JsonRPC相应示例:
  1. {
  2.   "jsonrpc": "2.0",
  3.   "error": {
  4.       "code": -32601,
  5.       "message": "Method not found"},
  6.   "id": 1
  7. }
复制代码
参数的数据类型
JsonRPC支持以下基本数据类型作为参数和效果值:


  • 字符串(String)
  • 数字(Number)
  • 布尔值(Boolean)
  • 数组(Array)
  • 对象(Object)
  • 空值(Null)
当参数或者返回效果中包罗字节数组的时候需要注意,由于JSON是一种文本格式,以是在序列化和反序列化字节数组时,会将其转换为Base64编码的字符串。这种转换会增加数据存储的巨细和处理时间。因此,对于大型字节数组,传递原始二进制数据的方式大概会更高效,而不是通过JSON进行编码和解码。在这种情况下,其他二进制传输协议(如gRPC或自定义的二进制协议)大概更得当处理字节数组的传递。
4、 基于JsonRPC分离前后端

cJSON 是由 Dave Gamble 创建的开源 JSON 处理库,实用于需要轻量级 JSON 解析和生成的场景。这里我们使用开源的cJOSN,需要编译 libev 库、编译 jsonrpc 库 ,而且将其添加到交错编译链 。
这里主要讲后端服务器操纵硬件,通过JsonRPC通讯,向前端qtAPP提供服务(控制LED、读取温湿度的值)。

rpc_server.c:
  1. #include <jsonrpc-c.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <sys/socket.h>
  6. #include <string.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <stdio.h>
  11. #include <errno.h>
  12. #include <stdlib.h>
  13. #include "rpc.h"
  14. #include "led.h"
  15. #include "dht11.h"
  16. static struct jrpc_server my_server;
  17. /* 参数: {"params" : [0|1]} */
  18. cJSON * server_led_control(jrpc_context * ctx, cJSON * params, cJSON *id) {
  19.     cJSON * status = cJSON_GetArrayItem(params,0);
  20.     led_control(status->valueint);       
  21.     return cJSON_CreateNumber(0);
  22. }
  23. /* 参数: {"params" : null} */
  24. cJSON * server_dht11_read(jrpc_context * ctx, cJSON * params, cJSON *id) {
  25.     int array[2];
  26.     array[0] = array[1] = 0;
  27.     while (0 != dht11_read((char *)&array[0], (char *)&array[1]));
  28.     return cJSON_CreateIntArray(array, 2);
  29. }
  30. int RPC_Server_Init(void)
  31. {
  32.     int err;
  33.    
  34.     err = jrpc_server_init(&my_server, PORT);
  35.     if (err)
  36.     {
  37.         printf("jrpc_server_init err : %d\n", err);
  38.     }
  39.    
  40.     jrpc_register_procedure(&my_server, server_led_control, "led_control", NULL );
  41.     jrpc_register_procedure(&my_server, server_dht11_read, "dht11_read", NULL );
  42.     jrpc_server_run(&my_server);
  43.     jrpc_server_destroy(&my_server);
  44.     return 0;
  45. }
  46. int main(int argc, char **argv)
  47. {
  48.     //初始化硬件(open设备节点)
  49.     led_init();
  50.     dht11_init();
  51.     //启动服务端
  52.     RPC_Server_Init();   
  53.     return 0;
  54. }
复制代码
前端qt客户端调用:rpc_client.cpp
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <sys/socket.h>
  5. #include <string.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <unistd.h>
  9. #include <stdio.h>
  10. #include <errno.h>
  11. #include <stdlib.h>
  12. #include "cJSON.h"
  13. #include "rpc.h"
  14. static int g_iSocketClient;
  15. //发送消息以使用客户端的“led_control”
  16. int rpc_led_control(int on)
  17. {
  18.     char buf[100];
  19.     int iLen;
  20.     int ret = -1;
  21.     int iSocketClient = g_iSocketClient;
  22.     sprintf(buf, "{"method": "led_control", "params": [%d], "id": "2" }", on);
  23.     iLen = send(iSocketClient, buf, strlen(buf), 0);
  24.     if (iLen ==  strlen(buf))
  25.     {
  26.         while (1)
  27.         {
  28.             iLen = read(iSocketClient, buf, sizeof(buf));
  29.             buf[iLen] = 0;
  30.             if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))
  31.                 continue;
  32.             else
  33.                 break;
  34.         }
  35.         if (iLen > 0)
  36.         {
  37.             cJSON *root = cJSON_Parse(buf);
  38.             cJSON *result = cJSON_GetObjectItem(root, "result");
  39.             ret = result->valueint;
  40.             cJSON_Delete(root);
  41.             return ret;
  42.         }
  43.         else
  44.         {
  45.             printf("read rpc reply err : %d\n", iLen);
  46.             return -1;
  47.         }
  48.     }
  49.     else
  50.     {
  51.         printf("send rpc request err : %d, %s\n", iLen, strerror(errno));
  52.         return -1;
  53.     }
  54. }
  55. //发送消息以使用客户端的“dht11_read”
  56. int rpc_dht11_read(char *humi, char *temp)
  57. {
  58.     char buf[300];
  59.     int iLen;
  60.     int iSocketClient = g_iSocketClient;
  61.     sprintf(buf, "{"method": "dht11_read"," \
  62.                    ""params": [0], "id": "2" }");
  63.     iLen = send(iSocketClient, buf, strlen(buf), 0);
  64.     if (iLen ==  strlen(buf))
  65.     {
  66.         while (1)
  67.         {
  68.             iLen = read(iSocketClient, buf, sizeof(buf));
  69.             buf[iLen] = 0;
  70.             if (iLen == 1 && (buf[0] == '\r' || buf[0] == '\n'))
  71.                 continue;
  72.             else
  73.                 break;
  74.         }
  75.         if (iLen > 0)
  76.         {
  77.             cJSON *root = cJSON_Parse(buf);
  78.             cJSON *result = cJSON_GetObjectItem(root, "result");
  79.             if (result)
  80.             {
  81.                 cJSON * a = cJSON_GetArrayItem(result,0);
  82.                 cJSON * b = cJSON_GetArrayItem(result,1);
  83.                 *humi = a->valueint;
  84.                 *temp = b->valueint;
  85.                 cJSON_Delete(root);
  86.                 return 0;
  87.             }
  88.             else
  89.             {
  90.                 cJSON_Delete(root);
  91.                 return -1;
  92.             }
  93.         }
  94.         else
  95.         {
  96.             printf("read rpc reply err : %d\n", iLen);
  97.             return -1;
  98.         }
  99.     }
  100.     else
  101.     {
  102.         printf("send rpc request err : %d, %s\n", iLen, strerror(errno));
  103.         return -1;
  104.     }
  105. }
  106. /* 连接RPC Server
  107. * 返回值: (>0)socket, (-1)失败
  108. */
  109. int RPC_Client_Init(void)
  110. {
  111.     int iSocketClient;
  112.     struct sockaddr_in tSocketServerAddr;
  113.     int iRet;
  114.     iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
  115.     tSocketServerAddr.sin_family      = AF_INET;
  116.     tSocketServerAddr.sin_port        = htons(PORT);
  117.     inet_aton("127.0.0.1", &tSocketServerAddr.sin_addr);
  118.     memset(tSocketServerAddr.sin_zero, 0, 8);
  119.     iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
  120.     if (-1 == iRet)
  121.     {
  122.         printf("connect error!\n");
  123.         return -1;
  124.     }
  125.     g_iSocketClient = iSocketClient;
  126.     return iSocketClient;
  127. }
复制代码
四、使用MQTT协议毗连OneNet实现远程控制

   这部分参考:【嵌入式linux开发】智能家居入门6:最新ONENET,物联网开放平台(QT、微信小程序、MQTT协议、ONENET云平台、旭日x3派)_onenet云端-CSDN博客
  ONENET云平台创建产物与装备参考下方视频:
   https://player.youku.com/embed/XNjQxNTE0OTAyNA==
  需要注意的点:
①数据协议:这里选择的是OneJson,当然也可以选择数据流,但是它们对应的发送数据和接收数据的topic是不一样的,以是如果想省事直接使用本文代码,那就选择OneJson。

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

1、测试前的准备

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

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

纠错:上图中的时间过小,在后面多加任意一个数字既可:2810295937232。
②OneJson数据协议对应的发布、订阅topic:
文档中有明确给出OneJson数据协议(物模子)的发布和订阅topic,如果数据协议选择数据流的小伙伴,在文档的这个界面今后翻翻就可以看到对应的。

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

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

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

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


这样双向测试就算完成了,接下来就是下位机与上位机的代码简介。
使用的库函数代码,代码放在工程中,后续调用里面的函数实现mqtt通讯:

五、QT APP编写

使用QtCreater工具简朴设计UI,重点是Qt的信号与槽函数的设计!!!
学到这里,可以去增补一下从c过渡到c++的基本语法,以及C++面向对象设计语法;
参考教程:
   【正点原子】嵌入式Qt5 C++开发视频_哔哩哔哩_bilibili
  C语言 转 C++ 简朴教程_哔哩哔哩_bilibili
  

1、代码

mainwindow.h
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QDebug>
  4. #include "rpc_client.h"
  5. #include "mqtt.h"
  6. #include "dht11_thread.h"
  7. MainWindow::MainWindow(QWidget *parent)
  8.     : QMainWindow(parent)
  9.     , ui(new Ui::MainWindow)
  10. {
  11.     ui->setupUi(this);
  12.     labelHumi = this->findChild<QLabel*>("label_4");//湿度
  13.     labelTemp = this->findChild<QLabel*>("label_3");//温度
  14.     //温湿度线程
  15.     // 创建线程对象并连接信号槽
  16. //    connect(dhtThread, SIGNAL(updateData(char,char)),this,SLOT(onUpdateData(char,char)));
  17.     /****************************** 连接服务器 ******************************************/
  18.     mqttClient = new MQTT_WorkClass();
  19.     // 设置MQTT服务器的地址和其他参数
  20.     mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
  21.     // 连接到MQTT服务器
  22.     mqttClient->run();
  23.     /******************************* 订阅主题 *******************************************/
  24.     mqttClient->slot_SubscribeTopic(topicToSubscribe);
  25.     /****************************** 信号与槽连接 *****************************************/
  26.     //断线重连槽函数
  27.     connect(mqttClient,SIGNAL(MQTT_ConnectState(bool)),this,SLOT(reconnect(bool)));
  28.     //接收到服务器下发数据的重写槽函数
  29.     connect(mqttClient,SIGNAL(ReceiveWechatData(QByteArray)),this,SLOT(receiveWechatData(QByteArray)));
  30. }
  31. /************************************槽函数**********************************************/
  32. //上报温湿度
  33. void MainWindow::onUpdateData(char humidity, char temperature)
  34. {
  35.     qDebug() << "Humidity:" << humidity << "Temperature:" << temperature<<endl;
  36.     labelHumi->setText(QString("%1%").arg((int)humidity));
  37.     labelTemp->setText(QString("%1°C").arg((int)temperature));
  38.     /**** 发布消息到主题 ****/
  39.     QMap<QString, double> dataMap;
  40.     dataMap["temp"] = (double)temperature; // 温度
  41.     dataMap["humi"] = (double)humidity;    //湿度
  42.     // 调用函数创建JSON对象
  43.     QJsonObject rootObject = createJsonObj(dataMap);
  44.     //将QJsonObject转换为QString
  45.     QString jsonString = convertObjectToJsonString(rootObject);
  46.     mqttClient->slot_PublishData(topicToPublish, jsonString);
  47.     qDebug() << "数据上传成功" << endl;
  48. }
  49. //断线重连槽函数
  50. void MainWindow::reconnect(bool)
  51. {
  52.     mqttClient = new MQTT_WorkClass();
  53.     // 设置MQTT服务器的地址和其他参数
  54.     mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
  55.     // 连接到MQTT服务器
  56.     mqttClient->run();
  57.     // 订阅主题
  58.     mqttClient->slot_SubscribeTopic(topicToSubscribe);
  59. }
  60. //解析服务器下发数据,槽函数
  61. void MainWindow::receiveWechatData(QByteArray data)
  62. {
  63.     //qDebug()<<"读取服务器发过来的数据:"<< data;
  64.     // 找到JSON对象的起始位置(第一个'{'符号)
  65.     int startPos = data.indexOf('{');
  66.     if (startPos != -1) {
  67.         // 删除起始位置之前的所有字符
  68.         data = data.mid(startPos);
  69.     } else {
  70.         qDebug() << "未找到JSON对象的起始位置";
  71.         //return -1; // 如果找不到起始位置,返回错误代码
  72.     }
  73.     parseJsonAndAction(data);
  74. }
  75. //将QJsonObject转换为QString,函数
  76. QString MainWindow::convertObjectToJsonString(const QJsonObject& object) {
  77.     // 将QJsonObject转换为QJsonDocument
  78.     QJsonDocument jsonDoc(object);
  79.     // 将QJsonDocument转换为QString,使用Compact模式
  80.     return jsonDoc.toJson(QJsonDocument::Compact);
  81. }
  82. //组织上传数据,函数
  83. QJsonObject MainWindow::createJsonObj(const QMap<QString, double>& dataMap) {
  84.     QJsonObject rootObject;
  85.     // 设置id字段
  86.     rootObject["id"] = "123";
  87.     // 创建params对象
  88.     QJsonObject paramsObject;
  89.     // 遍历数据映射,添加每个数据字段
  90.     for (auto it = dataMap.constBegin(); it != dataMap.constEnd(); ++it) {
  91.         QJsonObject valueObj;
  92.         valueObj["value"] = it.value(); // 设置数值
  93.         paramsObject[it.key()] = valueObj; // 添加到params对象
  94.     }
  95.     // 将params对象添加到rootObject
  96.     rootObject["params"] = paramsObject;
  97.     return rootObject;
  98. }
  99. //解析服务器下发指令并动作函数
  100. void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {
  101.     // 将QByteArray转换为QJsonDocument
  102.     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
  103.     if (jsonDoc.isNull()) {
  104.         qDebug() << "JSON解析失败";
  105.         return;
  106.     }
  107.     // 确保这是一个对象
  108.     if (!jsonDoc.isObject()) {
  109.         qDebug() << "JSON不是一个对象";
  110.         return;
  111.     }
  112.     // 获取JSON对象
  113.     QJsonObject jsonObject = jsonDoc.object();
  114.     // 进入params对象
  115.     if (jsonObject.contains("params"))
  116.     {
  117.         QJsonObject paramsObject = jsonObject["params"].toObject();
  118.         QDateTime currentDateTime = QDateTime::currentDateTime();
  119.         // 格式化日期时间为字符串,例如:"2024-08-16 18:32:10"
  120.         QString dateTimeStr = currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
  121.         // 提取led_ctl的值
  122.         if (paramsObject.contains("led_ctl"))
  123.         {
  124.             bool ledCtlValue = paramsObject["led_ctl"].toBool();
  125.             qDebug() << "led_ctl的值:" << ledCtlValue;
  126.             if(ledCtlValue == 0)
  127.             {
  128.                 QString message = dateTimeStr + " - 手机控制关闭客厅灯";
  129.                 ui->textBrowser->append(message);
  130.                 ui->radioButton->setText("客厅灯|离线");
  131.                 ui->radioButton->setChecked(0);
  132.                 rpc_led_control(0);
  133.             }
  134.             else if(ledCtlValue == 1)
  135.             {
  136.                 QString message = dateTimeStr + " - 手机控制打开客厅灯";
  137.                 ui->textBrowser->append(message);
  138.                 ui->radioButton->setText("客厅灯|在线");
  139.                 ui->radioButton->setChecked(1);
  140.                 rpc_led_control(1);
  141.             }
  142.         } else
  143.         {
  144.             qDebug() << "params中不包含led_ctl";
  145.         }
  146.     }
  147.     else
  148.     {
  149.         qDebug() << "JSON对象中不包含params";
  150.     }
  151. }
  152. MainWindow::~MainWindow()
  153. {
  154.     delete ui;
  155. }
  156. void MainWindow::on_pushButton_clicked()
  157. {
  158.     static int status = 1;
  159.     if (status)
  160.         qDebug()<<"LED clicked on";
  161.     else
  162.         qDebug()<<"LED clicked off";
  163.     rpc_led_control(status);
  164.     status = !status;
  165. }
复制代码
mainwindow.cpp
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QDebug>
  4. #include "rpc_client.h"
  5. #include "mqtt.h"
  6. #include "dht11_thread.h"
  7. MainWindow::MainWindow(QWidget *parent)
  8.     : QMainWindow(parent)
  9.     , ui(new Ui::MainWindow)
  10. {
  11.     ui->setupUi(this);
  12.     labelHumi = this->findChild<QLabel*>("label_4");//湿度
  13.     labelTemp = this->findChild<QLabel*>("label_3");//温度
  14.     //温湿度线程
  15.     // 创建线程对象并连接信号槽
  16. //    connect(dhtThread, SIGNAL(updateData(char,char)),this,SLOT(onUpdateData(char,char)));
  17.     /****************************** 连接服务器 ******************************************/
  18.     mqttClient = new MQTT_WorkClass();
  19.     // 设置MQTT服务器的地址和其他参数
  20.     mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
  21.     // 连接到MQTT服务器
  22.     mqttClient->run();
  23.     /******************************* 订阅主题 *******************************************/
  24.     mqttClient->slot_SubscribeTopic(topicToSubscribe);
  25.     /****************************** 信号与槽连接 *****************************************/
  26.     //断线重连槽函数
  27.     connect(mqttClient,SIGNAL(MQTT_ConnectState(bool)),this,SLOT(reconnect(bool)));
  28.     //接收到服务器下发数据的重写槽函数
  29.     connect(mqttClient,SIGNAL(ReceiveWechatData(QByteArray)),this,SLOT(receiveWechatData(QByteArray)));
  30. }
  31. /************************************槽函数**********************************************/
  32. //上报温湿度
  33. void MainWindow::onUpdateData(char humidity, char temperature)
  34. {
  35.     qDebug() << "Humidity:" << humidity << "Temperature:" << temperature<<endl;
  36.     labelHumi->setText(QString("%1%").arg((int)humidity));
  37.     labelTemp->setText(QString("%1°C").arg((int)temperature));
  38.     /**** 发布消息到主题 ****/
  39.     QMap<QString, double> dataMap;
  40.     dataMap["temp"] = (double)temperature; // 温度
  41.     dataMap["humi"] = (double)humidity;    //湿度
  42.     // 调用函数创建JSON对象
  43.     QJsonObject rootObject = createJsonObj(dataMap);
  44.     //将QJsonObject转换为QString
  45.     QString jsonString = convertObjectToJsonString(rootObject);
  46.     mqttClient->slot_PublishData(topicToPublish, jsonString);
  47.     qDebug() << "数据上传成功" << endl;
  48. }
  49. //断线重连槽函数
  50. void MainWindow::reconnect(bool)
  51. {
  52.     mqttClient = new MQTT_WorkClass();
  53.     // 设置MQTT服务器的地址和其他参数
  54.     mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
  55.     // 连接到MQTT服务器
  56.     mqttClient->run();
  57.     // 订阅主题
  58.     mqttClient->slot_SubscribeTopic(topicToSubscribe);
  59. }
  60. //解析服务器下发数据,槽函数
  61. void MainWindow::receiveWechatData(QByteArray data)
  62. {
  63.     //qDebug()<<"读取服务器发过来的数据:"<< data;
  64.     // 找到JSON对象的起始位置(第一个'{'符号)
  65.     int startPos = data.indexOf('{');
  66.     if (startPos != -1) {
  67.         // 删除起始位置之前的所有字符
  68.         data = data.mid(startPos);
  69.     } else {
  70.         qDebug() << "未找到JSON对象的起始位置";
  71.         //return -1; // 如果找不到起始位置,返回错误代码
  72.     }
  73.     parseJsonAndAction(data);
  74. }
  75. //将QJsonObject转换为QString,函数
  76. QString MainWindow::convertObjectToJsonString(const QJsonObject& object) {
  77.     // 将QJsonObject转换为QJsonDocument
  78.     QJsonDocument jsonDoc(object);
  79.     // 将QJsonDocument转换为QString,使用Compact模式
  80.     return jsonDoc.toJson(QJsonDocument::Compact);
  81. }
  82. //组织上传数据,函数
  83. QJsonObject MainWindow::createJsonObj(const QMap<QString, double>& dataMap) {
  84.     QJsonObject rootObject;
  85.     // 设置id字段
  86.     rootObject["id"] = "123";
  87.     // 创建params对象
  88.     QJsonObject paramsObject;
  89.     // 遍历数据映射,添加每个数据字段
  90.     for (auto it = dataMap.constBegin(); it != dataMap.constEnd(); ++it) {
  91.         QJsonObject valueObj;
  92.         valueObj["value"] = it.value(); // 设置数值
  93.         paramsObject[it.key()] = valueObj; // 添加到params对象
  94.     }
  95.     // 将params对象添加到rootObject
  96.     rootObject["params"] = paramsObject;
  97.     return rootObject;
  98. }
  99. //解析服务器下发指令并动作函数
  100. void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {
  101.     // 将QByteArray转换为QJsonDocument
  102.     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
  103.     if (jsonDoc.isNull()) {
  104.         qDebug() << "JSON解析失败";
  105.         return;
  106.     }
  107.     // 确保这是一个对象
  108.     if (!jsonDoc.isObject()) {
  109.         qDebug() << "JSON不是一个对象";
  110.         return;
  111.     }
  112.     // 获取JSON对象
  113.     QJsonObject jsonObject = jsonDoc.object();
  114.     // 进入params对象
  115.     if (jsonObject.contains("params"))
  116.     {
  117.         QJsonObject paramsObject = jsonObject["params"].toObject();
  118.         QDateTime currentDateTime = QDateTime::currentDateTime();
  119.         // 格式化日期时间为字符串,例如:"2024-08-16 18:32:10"
  120.         QString dateTimeStr = currentDateTime.toString("yyyy-MM-dd HH:mm:ss");
  121.         // 提取led_ctl的值
  122.         if (paramsObject.contains("led_ctl"))
  123.         {
  124.             bool ledCtlValue = paramsObject["led_ctl"].toBool();
  125.             qDebug() << "led_ctl的值:" << ledCtlValue;
  126.             if(ledCtlValue == 0)
  127.             {
  128.                 QString message = dateTimeStr + " - 手机控制关闭客厅灯";
  129.                 ui->textBrowser->append(message);
  130.                 ui->radioButton->setText("客厅灯|离线");
  131.                 ui->radioButton->setChecked(0);
  132.                 rpc_led_control(0);
  133.             }
  134.             else if(ledCtlValue == 1)
  135.             {
  136.                 QString message = dateTimeStr + " - 手机控制打开客厅灯";
  137.                 ui->textBrowser->append(message);
  138.                 ui->radioButton->setText("客厅灯|在线");
  139.                 ui->radioButton->setChecked(1);
  140.                 rpc_led_control(1);
  141.             }
  142.         } else
  143.         {
  144.             qDebug() << "params中不包含led_ctl";
  145.         }
  146.     }
  147.     else
  148.     {
  149.         qDebug() << "JSON对象中不包含params";
  150.     }
  151. }
  152. MainWindow::~MainWindow()
  153. {
  154.     delete ui;
  155. }
  156. void MainWindow::on_pushButton_clicked()
  157. {
  158.     static int status = 1;
  159.     if (status)
  160.         qDebug()<<"LED clicked on";
  161.     else
  162.         qDebug()<<"LED clicked off";
  163.     rpc_led_control(status);
  164.     status = !status;
  165. }
复制代码
dht11_thread.h
  1. #ifndef DHT11_THREAD_H
  2. #define DHT11_THREAD_H
  3. #include <QMainWindow>
  4. #include <QThread>
  5. #include <QLabel>
  6. class MainWindow;
  7. // 1. 创建继承自QThread的子类
  8. class DHT11Thread : public QThread {
  9.     Q_OBJECT
  10. public:
  11.     explicit DHT11Thread(QObject *parent = nullptr) : QThread(parent){}
  12.     void run() override ;
  13. signals:
  14.     void updateData(char humidity, char temperature);
  15. };
  16. #endif // DHT11_THREAD_H
复制代码
dht11_thread.c
  1. #include <QThread>
  2. #include <QDebug>
  3. #include "dht11_thread.h"
  4. #include "rpc_client.h"
  5. #include "stdio.h"
  6. // 2. 重写run()函数,这里是线程要执行的代码
  7. void DHT11Thread::run()
  8. {
  9.     char humi;
  10.     char temp;
  11.     while(1) {
  12.         if (0 == rpc_dht11_read(&humi, &temp))
  13.         {
  14.             emit updateData(humi, temp);
  15.             msleep(2000); // 线程休眠2秒
  16.         }
  17.     }
  18. }
复制代码
main.cpp
  1. #include "mainwindow.h"
  2. #include "rpc_client.h"
  3. #include <QApplication>
  4. #include "dht11_thread.h"
  5. int main(int argc, char *argv[])
  6. {
  7.     RPC_Client_Init();
  8.     DHT11Thread *dhtThread = new DHT11Thread();
  9.     dhtThread->start();
  10.     QApplication a(argc, argv);
  11.     MainWindow w;
  12.     QObject::connect(dhtThread, SIGNAL(updateData(char, char)),
  13.                          &w, SLOT(onUpdateData(char, char)));
  14.     w.show();
  15.     return a.exec();
  16. }
复制代码
记得在.pro文件里面添加交错编译工具链的include、lib等信息;以及添加使用的Qt模块;
  1. QT      += core gui network widgets concurrent
  2. SOURCES += \
  3.     MQTT.cpp \
  4.     cJSON.c \
  5.     dht11_thread.cpp \
  6.     main.cpp \
  7.     mainwindow.cpp \
  8.     rpc_client.cpp
  9. HEADERS += \
  10.     cJSON.h \
  11.     dht11_thread.h \
  12.     mainwindow.h \
  13.     mqtt.h \
  14.     rpc.h \
  15.     rpc_client.h
  16. FORMS += \
  17.     mainwindow.ui
  18. INCLUDEPATH += /home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdkbuildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include
复制代码
2、APP开机自启动相关设置

   这一部分如果想深入,需要学习一下Linux下 Shell脚本语言:这大概是B站讲的最好的Linux Shell脚本教程,3h打通Linux-shell全套教程,从入门到精通完整版_哔哩哔哩_bilibili
  创建一个文件: /etc/init.d/rCS;这个文件是一个初始化脚本,用于在体系启动时加载内核模块并执行其他初始化脚本。
  1. #!/bin/sh
  2. #("是一个 shebang 行,用于指定脚本文件的解释器。它告诉系统使用 /bin/sh 来执行脚本中的命令。")
  3. #加载驱动
  4. insmod  /root/led_drv.ko
  5. insmod  /root/dht11_drv.ko
  6. # Start all init scripts in /etc/init.d
  7. # executing them in numerical order.
  8. # 是一个启动画面程序,通常用于显示启动进度
  9. psplash -n &
  10. # 遍历 /etc/init.d/ 目录下所有以 S 开头的文件(通常是初始化脚本)。
  11. # 根据文件扩展名(如 .sh)或文件类型,执行这些脚本的 start 方法。
  12. # 如果脚本是 shell 脚本(以 .sh 结尾),则直接在当前 shell 中执行。
  13. # 如果是其他类型的脚本,则在子进程中执行。
  14. for i in /etc/init.d/S??* ;do
  15.      # Ignore dangling symlinks (if any).
  16.      [ ! -f "$i" ] && continue
  17.      case "$i" in
  18.         *.sh)
  19.             # Source shell script for speed.
  20.             (
  21.                 trap - INT QUIT TSTP
  22.                 set start
  23.                 . $i
  24.             )
  25.             ;;
  26.         *)
  27.             # No sh extension, so fork subprocess.
  28.             $i start
  29.             ;;
  30.     esac
  31. done
  32. # 这行代码的作用是根据 /etc/hostname 文件的内容设置系统的主机名。
  33. /bin/hostname -F /etc/hostname
复制代码
创建一个文件: /etc/init.d/S99myqt ,这个文件是一个详细的初始化脚本,用于启动你的应用程序 myapp 和 rpc_server。
  1. #!/bin/sh
  2. start() {
  3. #禁止LCD黑屏
  4. echo -e "\033[9;0]" > /dev/tty0
  5. #设置QT运行环境
  6. export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1
  7. export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0
  8. export QT_QPA_FONTDIR=/usr/lib/fonts/
  9. psplash-write "PROGRESS 95"
  10. psplash-write "QUIT"
  11. /root/rpc_server &
  12. sleep 5
  13. /root/myapp  &
  14. }
  15. stop() {
  16.     killall myapp
  17. }
  18. case "$1" in
  19.     start)
  20.         start
  21.         ;;
  22.     stop)
  23.         stop
  24.         ;;
  25.     *)
  26.         echo "Usage: $0 {start| stop|restart}"
  27.         exit 1
  28. esac
  29. exit $?
复制代码
看到这里有几个疑问:为什么开发板会开机启动自动运行你的程序
System V(缩写为 SysV)在大多数 Linux 发行版中使用最广泛,在 systemv中,在内核加载后运行的第 1 个程序被称为 init 程序。 Init 做一些事情,其中之一就是加载一系列脚本来启动各种体系服务,例如网络, ssh 守护程序等。 systemv 的题目在于它需要仔细调整。假设您有一个要在启动时运行的网络文件体系( NFS)客户端。在网络正常工作之前运行 NFS 没有任何意义。因此,您必须等待网络已经正常工作,才能启动 systemv。 Systemv init 这样做的方法是为服务启动设置严格的次序。每个服务都分配有一个优先级编号, init 会按优先级次序启动服务。
在基于 SysVinit 的 Linux 体系中,所有 System V 初始化脚本都存储在/etc/rc.d/init.d/或/etc/init.d目录中。这些脚本用于控制体系的启动和关闭 。这些脚本在体系启动时按次序执行,以完成体系的初始化工作。
脚本名称以 S 开头,表示这是一个启动脚本。脚本名称中的数字(如 S99)决定了执行次序。数字越大,执行越靠后。例如:


  • S01script 会在 S02script 之前执行。
  • S99myqt 是末了执行的脚本之一。
/etc/init.d/rCS 脚本负责执行 /etc/init.d/ 目录下所有以 S 开头的脚本。它会按次序执行这些脚本,完成体系的初始化。上面提到按照次序执行脚本的这个工作就是由rCS脚本来完成。
**注意:**从ubuntu通过rfs将文件传输到单板以后,记得讲个文件添加可执行权限
  1. chmod +x /root/rpc_server
  2. chmod +x /root/myapp
  3. chmod +x /etc/init.d/rcS
  4. chmod +x /etc/init.d/S99myqt
复制代码
3、单板毗连WIFI

参考韦东山教程,在目录:/home/book/100ask_imx6ull-sdk/Linux-4.9.88/drivers/net/wireless/rtl8723BU ,获得得当当前Linux体系,得当当前开发板上板载网卡型号的网卡驱动文件(你没有的话需要自己去搜索资料弄到合适的网卡驱动文件)。编译驱动文件,获得xxx.ko文件,复制该文件到开发板,然后在开发板安装驱动。经过开发板的wifi毗连工具相关设置以后,就可以毗连上WiFi了。
①、编译驱动要修改Makefile文件


我们如今在.ko的路径下,由于已经改好了Makefile文件了,那就直接输入 make 编译,得到.ko文件。然后通过你自己的方式将.ko文件发送到开发板,并在开发板装insmod 装载 .ko驱动文件。
  1. //进入开发
  2. //刷新一下系统环境变量
  3. source /etc/profile
  4. //关闭有线网卡
  5. ifconfig eth0 down
  6. //打开无线网卡也就是rtl8723bu
  7. ifconfig wlan0 up
  8. //WiFi连接配置设置,路径不可改
  9. vi /etc/wpa_supplicant.conf
  10. //修改wpa_supplicant.conf的内容如下:
  11. ctrl_interface=/var/run/wpa_supplicant
  12. ap_scan=1
  13. network={
  14.         ssid="Play_434_2.4G"  //wifi名称 ,这一行前面用tab代替空格
  15.         psk="434434434"       //wifi密码 ,这一行前面用tab代替空格
  16. }
  17. //配置udhcpc使其分配到动态ip后并配置到rtl8723bu网卡上
  18. //在Ubuntu上,使用本系统的busybox的udhcpc配置文件
  19. cd /home/book/100ask_imx6ull-sdk/Busybox_1.30.0/examples/udhcp
  20. cp simple.script /home/book/nfs_rootfs       //Ubuntu挂载在开发板上的nfs路径
  21. //在开发板上将simple.script更改名称并放在特定路径
  22. mv /mnt/simple.script /usr/share/udhcpc/default.script
  23. chmod 755 /usr/share/udhcpc/default.script
  24. //启动wpa_supplicant应用
  25. // 1 较新Linux系统使用以下这行
  26. wpa_supplicant -Dnl80211  -c /etc/wpa_supplicant.conf -i wlan0 &
  27. // 2 较老Linux系统使用以下这行
  28. wpa_supplicant -D wext -c /etc/wpa_supplicant.conf -i wlan0 &
  29. //搜索wifi并根据刚才的配置进行wifi连接
  30. udhcpc -i wlan0
  31. //使系统支持域名解析
  32. echo "nameserver 8.8.8.8" >> /etc/resolv.conf
复制代码
开发板开机自动毗连WiFi,看到了嘛,我们还是修改rCS,来设置开机启动时的工作。
  1. //在开发板上
  2. vi /etc/init.d/rcS
  3. //在该文件中的文末输入的代码效果相当于在shell中输入命令一样
  4. //输入以下命令
  5. source /etc/profile                                                       //刷新环境变量
  6. insmod /opt/gpio_driver.ko
  7. insmod /opt/8723bu.ko                                                     //安装网卡驱动
  8. ifconfig eth0 down                                                        //关闭有线网卡
  9. ifconfig wlan0 up                                                         //打开无线网卡
  10. wpa_supplicant -Dnl80211  -c /etc/wpa_supplicant.conf -i wlan0 &          //打开wifi工具
  11. udhcpc -i wlan0                                                           //连接wifi
  12. echo "nameserver 8.8.8.8" >> /etc/resolv.conf                             //解析域名功能配置
  13. cd /opt                                                                   //进入我的Qt目录
  14. /opt/1armQtProject &                                                      //执行QT程序
复制代码
六、项目效果

项目较为简朴,为期5天即可完成。但从理论走向实践,学了那么多东西,用不到很快就忘记了。
   “嵌入式开发中,底子知识太多了,学完 ABCDEFG,你都没办法做出一个最简朴的产物” --韦东山老师
  一次项目作为入门,作为框架,开启新阶段的学习。
继续沉淀。


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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

耶耶耶耶耶

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表