# gateway_240513 **Repository Path**: skiinder/gateway_240513 ## Basic Information - **Project Name**: gateway_240513 - **Description**: gateway_240513 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2024-10-19 - **Last Updated**: 2024-10-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## day01 ### 智能网关的作用: ``` 通过蓝牙、LORA、串口、CAN等接口,与那些无法直接连接网络的设备建立联系,并与远程服务器进行数据交互 ``` ### 创建项目目录 ``` app:应用主功能程序 daemon:守护进程程序 init:初始化shell脚本配置 ota:在线升级程序 test:测试程序 thirdparty:第三方工具包 .gitignore:git的忽略配置 main.c:应用入口主程序 Makefile:自动化构建make配置 ``` ### 使用git管理项目 ``` - 配置git忽略 - 创建本地仓库 - 创建远程仓库 - 将本地仓库推送到远程仓库 - 修改工作区代码,先提交到本地仓库,再推送到远程仓库 ``` ### 使用第三方库:log.c ``` - 基本语法: log_xxx() 多个级别的日志输出 log_set_level(level) - 引入库的源文件:log.c/log.h - 编写测试程序 - 在vscode中将thirdparty配置为include的库目录:设置 =》 搜索 include => 在include path中添加thirdparty目录 - 在Makefile中告诉gcc将thirdparty作为库目录:给gcc命令添加 -Ithirdparty - 利用Makefile编译运行测试 ``` ### 通用工具模块:包含一些多个模块需要的工具函数或宏定义 ``` - app/app_common.h & app_common.c - app_common_getCurrentTime(): 获取当前时间戳毫秒值 - 测试: test/app_common_test.c 配置Makefile ``` ### 缓存模块: ``` - 定义缓存结构体和缓存操作的相关函数 - app/app_buffer.h & app_buffer.c - 缓存结构体Buffer char *ptr int total_size int len int offset - 环形缓存 写入:从缓存数据尾部的下一位开始写入数据,如果超过了最大位置,继续从0位写入 =》形成逻辑上的环形写 读取:从offset位置读取,如果读取到最大位置还没有读够,继续从0位读取 =》形成逻辑上的环形读 - 操作的函数 app_buffer_init() app_buffer_free() app_buffer_write() app_buffer_read() ``` ### json ``` 特定格式的字符串 {}: json对象 []: json数组 {key1:value1, key2:value2} [value1, value2] key的类型:字符串 必须用双引号包含 value的类型: 字符串,数值,true/false, null, [], {} ``` ### cJSON ``` json数据解析和生成的c语言库 将cJSON的cJSON.h和cJSON.c拷贝到工程的thirdparty/cJSON目录下 操作1:使用cJSON解析JSON字符串后,读取出其中特定属性的数据 操作2:使用cJSON生成一个包含特定属性的JSON字符串 ``` ## day02 ### 消息模块 ``` - 消息数据:连接类型conn_type 、标识ID和消息数据msg - 消息数据的三种形态 - JSON: {"conn_type": 1, "id": "5858", "msg": "61626364"} - 二进制:1 2 4 xxabcd - 结构体: conn_type:1, id_len:2, msg_len:3,payload:xxabcd - 消息结构体: typedef struct { ConnectionType conn_type; // 连接类型的枚举 unsigned char *payload; // id与msg组成的内容 int id_len; // id的长度 int msg_len; // msg的长度 } Message; - 定义并实现相关函数 - app_message_initByBinary() - app_message_initByJson() - app_message_toBinary() - app_message_toJson() - app_message_free() - 十六进制字符串与二进制数据相互转换的函数 hex_to_binary() binary_to_hex() ``` ### MQTT模块 ``` - 理解MQTT 轻量级的基于发布/订阅模式的通信协议, 在物联网和传感器应用广泛使用 - 搭建MQTT服务器(Mosquitto) 安装 配置:mosquitto.conf 指定端口号和任意IP的客户端都可以访问 指定可以匿名访问 重启电脑,自动启动服务 利用LLCOM进行测试 测试是否能连接 测试订阅与发布主题 - 开发MQTT客户端应用 下载客户端库:libpaho-mqtt-dev 文档地址:https://eclipse.github.io/paho.mqtt.c/MQTTClient/html/ 根据文档编写测试代码 创建客户端 设置回调 连接 订阅主题 发布主题 断开连接与销毁 - 定义并实现相关函数 app_mqtt_init() app_mqtt_close() app_mqtt_send() app_mqtt_registerRecvCallback() ``` ## day03 ### 线程池模块 ``` - 理解: 利用包含多个线程和线程池和消息队列,实现多个任务进行多线程的高效运行, 复用线程不断执行任务 - 消息队列的操作 创建消息队列:mq_open() 发送消息:mq_send() 接收消息:mq_receive() - 线程的操作: 创建线程:pthread_create() 取消线程:pthread_cancel() 等待线程结束:pthread_join() - 任务结构体 typedef struct { void (*func)(void *); // 任务函数 void *arg;               // 任务参数 } Task; - 相关函数 app_pool_init() 创建消息队列 创建多个线程,并保存到线程数组中,形成线程池 每个线程函数都是不断循环取出队列中的任务执行,如果没有等待 app_pool_close() 取消结束线程池中所有线程 释放线程池空间 删除队列 app_pool_registerTask() 创建一个新的任务结构体 将任务发送到队列等待线程获取执行任务中的函数 ``` ### 通用设备模块 ``` - 理解 网关端本地数据交互设备有好一些,比如SERIAL/CAN/IIC,针对不同的设备我们需要有不同的实现,但这些设备有很多相同的特性,我们可以把相同的部分定义在通用设备模块,将不同的部分定义在特定的设备模块中。 特定设备模块中的设备要具备通用设备的所有能力,并在此基础上进行扩展。(面向对象编程中的继承特点) - 相关枚举和结构体类型 enum DeviceType 设备类型 struct Device结构体 char *name; //设备名称 int fd; //设备文件描述符 DeviceType type; //设备类型 ConnectionType conn_type; //连接类型 Buffer *read_buffer; //设备缓冲区 Buffer *write_buffer; //设备缓冲区 pthread_t read_thread; //读取设备数据的线程 int running; // 设备是否正在运行 0: 未运行 1: 运行 long last_write_time; // 上一次写入数据的时间 DeviceFuncs *funcs; //设备函数表 struct DeviceFuncs 包含一些由子类模块或其它模块实现的函数 // 读取到设备文件数据后执行的后处理函数 由子类实现 int (*post_read)(Device *, void *, int); // 向设备文件写入数据前执行的前处理函数 由子类实现 int (*pre_write)(Device *, void *, int); // 发送消息给远程服务器的函数, 由其它模块来实现 int (*send_msg)(Device *, void *, int); - 相关函数 app_device_init() app_device_start() app_device_stop() app_device_close() app_device_recvMsg() app_device_read() app_device_sendTask() app_device_writeTask() ``` ### 路由模块 ``` - 理解 是设备与远程服务器之间的桥梁, 注册了所有需要与远程通信的设备,设备通过路由调用MQTT客户端将消息发送到远程 注册接收远程消息的回调,并调用设备的接收函数来将数据交给设备处理 - 相关函数 app_router_init() app_router_close() app_router_registerDevice() ``` ​## day04 发送消息流程 ``` 初始化设备(app_device_init) 打开设备得到文件描述符fd(后面可以进行读写数据) 创建设备读缓存(app_buffer_init) 创建设备写缓存(app_buffer_init) 设置设备读线程还没有运行的标识为0 初始化线程池(app_pool_init) 创建任务队列 创建线程池 创建多个线程(线程函数:thread_func),并保存每个线程的标识 线程函数中:不断的接收读取队列中的任务(任务函数和参数device), 执行任务函数 初始路由器(app_router_init) 初始化mqtt客户端(app_mqtt_init) 连接服务器 订阅pull主题的消息来接收服务器发过来的对应消息 将设备注册到路由器中(app_router_registerDevice) 保存设备 将路由器中的发送消息的函数(router_send_msg)保存到设备中(funcs.send_msg) 启动设备,也就启动设备读线程(app_device_start) 设备读线程(app_device_start)中 将线程标识为运行(running = 1) 创建启动设备读线程,指定线程函数(read_thread_fun) 线程函数(read_thread_fun)中: 只要标识为真(running==1),就不断的读取出设备数据(read(fd)) 调用由具体设备指定读后处理函数来处理数据(funcs.post_read) 将数据保存到设备的读缓存(app_buffer_write(device.read_buffer)) 注册一个发送的任务(任务函数:send_msg),(app_pool_registerTask) 在分线程执行任务函数(send_msg)中: 从设备的读缓存中取出一段完整的数据(3 + n) 调用设备中的send_msg发送消息-- 本质是调用路由器中的发送消息的函数(router_send_msg) 路由器中的发送消息的函数(router_send_msg)中: 将二进制数据转换Message结构体(app_message_initByBinary) 将message结构体转换为json数据(app_message_toJson) 利用mqtt客户端发送json数据到服务器(app_mqtt_send) ``` 接收消息流程 ``` **接收到任务前 初始化设备(app_device_init) 为设备创建了一个设备写缓存read_buffer(app_buffer_init) 初始化路由器 初始化mqtt客户端(app_mqtt_init) 订阅了pull主题的消息,指定了接收消息的回调函数(msgarrvd) 在回调函数中会调用下面注册的路由接收消息的回调(router_recv_callback) 将接收消息的函数router_recv_callback注册到mqtt客户端(app_mqtt_setRecvCallback) **接收到任务 路由器中的msgarrvd回调函数执行,得到服务器发过来的数据(json) 调用路由器中的接收消息的函数(app_mqtt_setRecvCallback)处理 将json数据转换为message结构体 找到对应的设备(当前是根据连接类型,后面会完善为设备id)找到对应的设备 找到设备后,将message结构体转换为二进制数据 调用设备的接收消息函数(app_device_recvMsg)准备将数据写入到设备文件中 在接收消息函数(app_device_recvMsg)中 将数据保存到设备的设备写缓存中(app_buffer_write) 注册一个设备写任务(任务函数:write_task_fun)来做后续工作(app_pool_registerTask) 在设备写任务函数(write_task_fun)中 从设备的设备写缓存中读取出一段完整的数据(3+n) 调用具体模块中指定的前置写函数(pre_write)对数据进行处理 将处理后的数据写入到设备文件中 ```