# Linux_GateWay **Repository Path**: dlk666/linux_-gate-way ## Basic Information - **Project Name**: Linux_GateWay - **Description**: linux网关项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-09 - **Last Updated**: 2026-04-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Linux_GateWay #### 介绍 linux网关项目 让不能联网的设备能间接连接上网络,实现与远程客户端的双向通信。 让多个不同设备A(不能联网),借助“智能网关”及其所在的本地设备B(能联网),实现与远程服务器和远程设备C(能联网)进行实时双向通信。 #### 软件架构 软件架构说明 app:应用主功能程序 daemon:守护进程程序 init:开机自启动shell脚本配置 ota:在线升级程序 test:测试程序 thirdparty:第三方工具包 .gitignore:git的忽略配置 main.c:应用入口主程序 Makefile:自动化构建make配置 **消息模块** - 消息数据的组成: 连接类型: 1 代表蓝牙连接 设备id:“XX" 消息体: "abcd" - 消息数据的2种格式 字符数组: 1 2 4 XXabcd json字符串: {"conn_type": 1, "id": "5858", "msg": "61626364"} - 字符数组格式的消息 字节1:连接类型 字节2:设备id长度 字节3:消息体长度 字节4~n:设备id和消息体 - json字符串格式的消息 conn_type: 连接类型 id:下游设备的id msg:消息体 注意:id和msg的值是十六进制字符串 - 字符数组与十六进制字符串的相互转换 长度关系:2倍 chars => hexstr: sprintf(hex_char_pointer, "%02X", char) hexstr => chars: sscanf(hex_char_pointer, "%02X", char_pointer) 可以在app_common中定义好转换的工具函数 app_common_charsToHexstr app_common_hexstrToChars - 2种格式消息的相互转换 应用消息模块: app_message app_message_charsToJson: 字符数组格式转为json格式 app_message_jsonToChars:json格式转为字符数组格式 - 测试 **MQTT协议** - 搭建MQTT服务器(Mosquitto) 安装: windows版本 配置:mosquitto.conf 指定端口号和任意IP的客户端都可以访问 指定可以匿名访问 重启电脑,自动启动服务 - 使用LLCOM工具进行测试 测试是否能连接 测试订阅与发布主题 - 开发MQTT客户端应用测试 下载客户端库:libpaho-mqtt-dev 根据文档编写测试代码 创建客户端: create 设置回调: setCallbacks 连接 connect 订阅主题 subscribe 发布主题 publishMessage 断开连接与销毁 disconnect / destroy **线程池模块** - 线程池: 管理和重用多个线程的设计模式 让多个线程可以不断的执行,而不是执行完一个任务后就销毁,提高性能 - 线程模块 多线程异步处理其它模块注册的多个待执行的任务 线程池 + 消息队列 创建消息(任务)队列:存储待执行的N个任务(函数和参数数据) 创建N个线程,在线程函数中循环取出任务队列中的任务执行,如果队列中没有任务,就进入阻塞状态 其它模块可以向线程模块注册任务,也就是发送到队列中 - app_pool struct Task app_pool_init() app_pool_close() app_pool_registerTask() **缓冲模块** - 区别缓存(Cache)与缓冲(Buffer) 都是开辟一段内存,在其中临时存储一些数据 缓存 在内存中存储一份最近要高频使用的数据,以提高反复读取数据的速度 缓冲 主要为了平衡数据传输速度不匹配的问题 一方面不断将多份(帧)数据快速保存到缓冲区内存中(快),另一方面利用多线程来不断读取缓冲区的各个帧数据进行处理(慢)。 - 理解双缓冲机制 利用缓冲区来存储多帧数据,向缓冲区写数据和从缓存区数据都是多线程执行的,为了数据安全我们会对读写操作进行加锁操作,以防止出现竞态条件。 单缓冲(环形缓冲): 一个缓冲内存,那样读和写操作的是同一块空间,那就意味着读时不能写,写时也不能读 缺点:速度慢,优点:占用空间小 适用:存储空间有限,对速度要求不高的设备 双缓冲: 二个缓冲内存,一块内存写数据,另一块内存读数据,如果读时发现没有,切换读写内存 优点:速度快,缺点:占用空间大 适用:存储空间足够,对速度有一定要求的设备 一个缓冲区包含2个存储数据的小缓冲区(一个用于读,另一个用于写) 每个小/子缓冲 SubBuffer: char *ptr int total_size int len 整个缓冲区: Buffer SubBuffer *sub_buffers[2] int read_index = 1 int write_index = 0 pthread_mutex_t read_lock pthread_mutex_t write_lock - app_buffer.h 小缓冲区结构体 unsigned char *ptr; // 数据指针 int total_size; // 空间大小 int len; // 数据长度 大缓冲区结构体 SubBuffer *sub_buffers[2]; // 读和写缓冲区的数组 int read_index; // 读缓冲区索引 int write_index; // 写缓冲区索引 pthread_mutex_t read_lock; // 读锁 pthread_mutex_t write_lock; // 写锁 功能函数 app_buffer_init(): 初始化缓冲区 app_buffer_free(): 释放缓冲区 app_buffer_write(): 向缓冲区写入数据 app_buffer_read(): 从缓冲区读取数据 - app_buffer.c 写入数据时: 写入数据帧前需要限制被写入的数据帧的长度不要超过255 原因:我们在写入数据帧时,会将其长度用一个字节的空间保存在数据帧的帧头 =》方便读取 读取数据时: 在读取前,如果读缓冲区为空,切换缓冲区 加锁处理 在读缓冲区的数据前后需要进行加锁和解锁 加读锁 =》 不能同时有2个线程读 在写缓冲区的数据前后需要进行加锁和解锁 加写锁 =》 不能同时有2个线程写 在切换缓冲区前后需要进行加锁和解锁 加写锁 =》 写入时,不能切换 **设备模块** - 整个项目最核心模块,依赖(使用)了前面完成的模块 工具模块 app_common 消息模块 app_message MQTT客户端模块 app_mqtt 线程池模块 app_pool 缓冲模块 app_buffer - 主要包含2个方向的数据流操作 上行:下游设备 =》网关 =》远程服务器/用户设备(手机) 下行:远程用户设备/服务器 =》网关 =》下游设备 - 需要通过分线程执行的操作 不断读取下游设备传输过来的数据 =》 创建一个线程不断从对应的串口文件中读取 上行过程中发送N个消息数据到远程 =》 线程池 下行过程中MQTT接收远程消息的回调函数 =》 分线程 下行过程中将接收的N个消息数据写入串口文件中 =》 线程池 - 上行程序操作流程 启动读取下游设备线程的线程,在线程函数中 read_thread_func 不断读取下流设备通过蓝牙连接传输过来的数据 read(fd) 对蓝牙数据进行处理转换为字符数组消息 post_read() 将字符数组消息保存到上行缓冲区 app_buffer_write(up_buffer) 将发消息数据到远程的任务交给线程池执行 app_pool_registerTask(send_task_func) 在任务函数中 send_task_func 从上行缓冲中读取出一个字符数组消息 app_buffer_read(up_buffer) 将字符数组消息转换为json格式消息 app_message_charsToJson() 将json消息发送给远程 app_mqtt_send(json) - 下行程序操作流程 注册接收远程消息的回调函数 app_mqtt_registerRecvCallback(receive_msg_callback) 将json格式消息转换为字符数组消息 app_message_jsonToChars() 将其保存到下行缓冲中 app_buffer_write(down_buffer) 将消息数据保存到设备文件的任务交给线程池执行 app_pool_regiserTask(write_task_func) 在任务函数中 write_task_func 从下行缓冲中读取出一个字符数组消息 app_buffer_read(down_buffer) 将字符数组消息转换为蓝牙需要的格式数据 pre_write() 将数据保存到蓝牙对应的串口文件中,传递给下游设备 write(fd) - 设备结构体 typedef struct { char *filename; // 设备文件 接收了下游设备发过来数据的文件 int fd; // 文件描述符 Buffer *up_buffer; // 上行缓冲区 Buffer *down_buffer; // 下行缓冲区 pthread_t read_thread; // 读设备数据的线程 int is_running; // 读线程是否运行 0 / 1 long last_write_time; // 上次写数据的时间 // 从设备读取数据后,对数据的处理函数,将数据处理成字符数组消息 int (*post_read)(char *data, int len); // 写数据前,对数据的处理函数,将字符数组消息处理成设备需要的格式 int (*pre_write)(char *data, int len); } Device; **蓝牙模块的preWrite与postRead** - 向蓝牙写入数据前的处理 preWrite 字符数组消息: 例子:1 2 3 XX abc 格式:conn_type id_len msg_len id msg 蓝牙发送数据格式例子: 例子:41 54 2b 4d 45 53 48 00 ff ff 61 62 63 0d 0a 41 54 2b 4d 45 53 48 00: AT+MESH(固定头部) ff ff: 对端的MADDR(如果是FFFF代表群发) 61 62 63: 要发送的数据(不超过12字节) 0d 0a:\r\n(固定结尾) AT+MESH XX abc \r\n - 从蓝牙读取到数据后的处理 postRead 接收方得到数据(3 + [2]):f1 dd 07 23 23 ff ff 41 42 43 f1 dd : 固定的头部 07: 之后数据的长度(5-16之间) 23 23:对端(发送方)的MADDR ff ff: 我的MADDR或ffff(群发) 41 42 43:发送的数据 处理后的数据格式:conn_type id_len msg_len id msg - preWrite 计算出蓝牙数据的长度,并创建临时存储蓝牙数据的空数组 向数组中依次写入:固定头部、id、msg和固定结尾 将蓝牙数据拷贝到传入的data中 返回蓝牙数据长度 - postRead 思路:一份完整的蓝牙数据可能是需要多次读取才能得到的,所有需要将多个读取的数据合并保存到一个缓存容器中,再从中提取出一个完整结构的蓝牙数据,再生成对应的字符数组消息数据 将data写入到缓存数组中,与前面的数据合并在一起 遍历缓存数组 如果找到的是以f1dd开头的蓝牙数据, 删除已遍历的数据 取出各个数据,以字符数组消息格式保存到data中 将数据从缓存中删除 返回字符数组的长度 遍历结束,删除缓存中已遍历的数据 **串口模块** - termios库:linux系统中一个用来操作终端设备的标准接口库(linux中串口也是终端) struct termios: 包含串口相关属性的结构体 tcgetattr(fd, *termios): 获取属性 tcsetaatr(fd, TCSAFLUSH, *termios): 设置属性,但暂时不生效,需要flush时生效 cfsetspeed(*termios, speed_t): 设备输入和输出的波特率 cfmakeraw(*termios): 将串口终端设置为原始模式 tcflush(fd, TCIOFLUSH):flush串口,让设置的属性生效 设置校验位: parity = 0; // 无校验位 parity = PARENB | PARODD // 奇校验位 parity = PARENB // 偶校验位 termios.c_cflag &= ~(PARENB| PARODD); // 清除校验位 termios.c_cflag |= parity; // 指定校验位 设置停止位: stop_bits = 0, // 1位停止位 stop_bits = CSTOPB, // 2位停止位 termios.c_cflag &= ~CSTOPB; // 清除停止位 termios.c_cflag |= stop_bits; // 设置停止位 设置阻塞模式: termios.c_cc[VMIN] = 1; // 至少读取一个字符才返回 termios.c_cc[VTIME] = 0; // 没有读到数据会一直等待 设置非阻塞模式: termios.c_cc[VMIN] = 0; // 最少读取字符数为0 termios.c_cc[VTIME] = 2; // 单位为100ms 等待时间为0.2秒 - 串口模块 enum BraudRate // 波特率枚举 enum Parity // 校验位枚举 enum StopBits // 停止位枚举 app_serial_init():初始化 app_serial_setBraudRate():设置波特率 app_serial_setParity():设置校验位 app_serial_setStopBits():设置停止位 app_serial_setBlock(): 设置串口是否是阻塞模式 app_serial_setRaw(): 设置串口解析数据模式为原始模式:不解析特殊字符 ** 蓝牙模块** - 蓝牙 Mesh: 是一种基于蓝牙技术的网络协议 支持多对多设备通信,适用于需要大量设备协同工作的场景,如智能家居、工业自动化和商业照明等 蓝牙信号也是广播的(与LORA一样) 多个需要相互通信的蓝牙设备指定相同的NETID来组成一个网络,每个设备就是网络的一个节点,他们可以相互通信,一个网络中的每个节点的MADDR要不同 - JDY-24M蓝牙通信: 初始化指令: AT 测试蓝牙是否可用 AT+NAMEatguigu 修改蓝牙名称 AT+BAUD4:设置波特率为9600 AT+BAUD8 设置波特率为115200 AT+RESET 重置(修改的配置才能生效) MESH初始化指令: AT+NETID 需要相互通信的蓝牙设备的NETID一样 AT+MADDR 同个网络中的各个蓝牙设备的MADDR要不同 MESH通信指令: AT+MESH 注意: 发送指令: 最后2位总是\r\n 操作成功返回:OK\r\n - 蓝牙模块 enum BTBaudRate 蓝牙波特率枚举 app_bt_status() 判断蓝牙是否连接 app_bt_rename() 蓝牙重命名 app_bt_setBraudRate() 设置蓝牙波特率 app_bt_reset() 重启蓝牙 app_bt_setNetid() 设置蓝牙网络ID app_bt_setMaddr() 设置蓝牙MAC地址 app_bt_init() 初始化蓝牙 app_bt_postRead() 从蓝牙读取数据后处理函数 app_bt_preWrite() 写入蓝牙数据前处理函数 **OTA模块** 功能:实现应用的在线自动升级 实现流程: 1. 请求获取远程固件相关信息:版本号和文件哈希值 json 2. 解析json, 得到远程的各级版本号和文件哈希值 3. 比较远程和本地的版本号,如果远程版本号不大于本地版本号,则无需更新 4. 如果远程版本号大于本地版本号,则下载远程程序固件 5. 下载完成后,进行哈希校验,来确认下载的文件没问题 生成下载完成固件的哈希(使用MD5或SHA加密算法生成),与远程文件哈希比较 6. 如果不同,校验不通过,删除文件,更新失败 7. 如果相同,校验通过,重启系统,加载新下载的程序执行 ota_http模块 请求指定url获取json数据:ota_http_getJson() 请求指定url下载文件:ota_http_download() ota_version模块 实现全流程的版本检查更新:ota_version_checkUpdate() 每隔一天检查一次:ota_version_checkUpdateDaily() 输出当前版本号:ota_version_printVersion() **守护进程模块** 功能:专门用来守护各个业务功能进程的daemon进程,它不处理任何业务逻辑,业务进程是守护进程的子进程,守护进程主要有2个工作 1. 启动各个业务子进程(处理业务逻辑) 2. 在业务进程意外结束后,自动启动业务进程执行(定时检查) daemon_sub_process模块 被守护子进程的结构体SubProcess: pid: 进程id cmd_param: 命令参数 fail_count: 失败次数 进程结构体初始化:daemon_sub_process_init() 不启动子进程 检查启动子进程:daemon_sub_process_checkStart() 检查子进程是否正在运行,如果没有启动子进程,并在子进程中执行应用程序,并携带指定参数 结束子进程:daemon_sub_process_stop() daemon_runner模块: daemon进程初始运行:daemon_runner_run() 将当前进程变为守护进程:daemon() 关闭标准输入,将标准输出重定向指定的日志文件: close()/open() 注册进程kill的信号监听,用于守护进程结束前结束被守护的子进程 初始化所有子进程(app/ota)信息 每隔2秒检查每个子进程状态,如果没有运行则将其启动起来 结束前关闭所有子进程 #### 安装教程 项目交叉编译 准备编译烧录工具: output.tar.gz 、 sysroot.tar.gz 、 toolchain.tar.gz 拷贝到项目根目录下,并解压到根目录下 烧录系统 USB线连接刷机口 让USB线连接上虚拟机 刷机 操作开发板的Linux系统 USB线连接调试口 让USB线连接上主机(windows) 利用Xshell通过USB端口连接上开发板Linux系统 查看开发板Linux系统的IP 利用Xshell通过IP连接上开发板Linux系统 烧录应用 编译脚本Makefile, 修改IP为开发板的IP 执行交叉编译指令:cross-compile 查看烧录上的程序:ll /usr/bin/gateway #### 使用说明 开机自启动 系统启动时,会自动运行/etc/init.d/Sxxx的shell来启动一些程序进程,比如:网络、时针等 那我们也可以模仿已有配置文件的写法来开机自启动我们的程序:S99gateway 开机启动配置的start函数: 如果OTA程序存在, 将当前程序移动到备份文件位 将OTA程序移动到当前程序位置 给其添加执行权限 启动当前程序,传入daemon参数 将配置文件传输到开机启动执行的配置文件夹,并给其添加执行权限 reboot重启系统 查看网关进程:有3个,daemon/app/ota, 杀死app或ota后查看依然存在,杀死daemon,再查看app和ota都不在了 #### 参与贡献 me