当前位置: 首页 > news >正文

IoT/HCIP实验-5/基于NB-IoT的智慧农业实验(平台侧开发+端侧编码+基础调试分析)

文章目录

  • 概述
  • 扩展板 E53_IA1
  • 智慧农业平台测开发
    • 功能定义/模型开发
    • 编解码插件开发-消息
    • 编解码插件开发-关联
    • 编解码插件开发-部署
    • 注册实际设备
  • 智慧农业端侧编码
    • 工程配置
    • 数据结构定义
    • 数据收集任务
    • 数据上报任务
    • 设备接入过程
    • 正确设置接入参数
    • 命令响应任务
  • 程序调试
  • 其他

概述

本实验基于NB-IoT实现智慧农业案例,实现实时数据采集,实现命令下发和响应,实现端云互通。实验目的包括:

  • 掌握NB-IoT通信方式的配置。
  • 掌握智慧农业案例的开发过程。
  • 平台侧模型定义和编解码插件开发。
  • 端侧嵌入式程序开发与程序调试。(设备接入、数据上报、命令接收等)
  • LwM2M/CoAP协议与NB-IoT的AT指令存在怎样的关联呢?

@NOTE
转载请标明出处,https://blog.csdn.net/quguanxin/category_12929470.html

@HISTORY
从AT指令实验的那个时候开始,我就有一个疑问?物联网端侧程序是要通过AT指令与通信模组交互吗,目标板卡上的应用程序难道要给模组应用核内的程序发送串口指令?
@HISTORY
我曾试图在哪里找到关于智慧农业的直接可复制源码,省的再敲键盘,浪费时间,可惜没找到。
在LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Demos\oc_agriculture_template、
或在新建的 LiteOS Studio工程时生成的demo中(.demos\agriculture),
都只是存在部分代码的片段,也不与实验手册一致,参考价值不大。算了还是自己敲吧,有了Trae AI等插件的加持,也不费事。

扩展板 E53_IA1

E53_IA1是一款遵循E53标准接口规范的智慧农业扩展板,专为物联网开发设计,适配多种开发板,用于快速实现农业环境监测与控制的原型开发。“E”代表扩展(Expansion),“53”表示板卡尺寸为5×3 cm,具备统一物理接口,支持跨平台适配。IA 应用场景Intelligent Agriculture(智慧农业)的简称,标识该扩展板专用于农业监测场景。子类编号 1 代表智慧农业类别中的基础农业监测功能,如,温湿度、光照监测与控制。
在这里插入图片描述
小熊派社区提供了 E53_IA1扩展板驱动,主要宏定义和接口声明如下,

#ifndef __E53_IA1_H__
#define __E53_IA1_H__
#include "stm32l4xx_hal.h"/* 控制设备IO口定义 ------------------------------------------------------------*/
#define IA1_Motor_Pin GPIO_PIN_8
#define IA1_Motor_GPIO_Port GPIOB
#define IA1_Motor_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define IA1_Light_Pin GPIO_PIN_0
#define IA1_Light_GPIO_Port GPIOA
#define IA1_Light_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()/* E53_IA1传感器数据类型定义 ------------------------------------------------------------*/
typedef struct {float    Lux;			  //光照强度float    Humidity;        //湿度float    Temperature;     //温度
} E53_IA1_Data_TypeDef;extern E53_IA1_Data_TypeDef E53_IA1_Data;/* 寄存器宏定义 --------------------------------------------------------------------*/
#define I2C_OWN_ADDRESS     0x0A
//
#define SHT30_Addr 0x44
//
#define BH1750_Addr 0x46
#define BH1750_ON   0x01
#define BH1750_CON  0x10
#define BH1750_ONE  0x20
#define BH1750_RSET 0x07void Init_E53_IA1(void);
void E53_IA1_Read_Data(void);#endif

智慧农业平台测开发

参见 <HCIP IoT Developer V2.5 实验手册> 1 物联网平台开发实验,实验任务配置步骤,搭建智慧农业平台侧。

功能定义/模型开发

部分内容请参考 #<IoT/HCIP实验-1/物联网开发平台实验Part1(快速入门,MQTT.fx对接IoTDA># 其中的编解码插件开发相关章节。本章只做简述。
在这里插入图片描述
创建产品–协议类型 LwM2M/CoAP–数据格式–二进制码流,
在这里插入图片描述
创建产品成功后,点击产品名称或产品详情进入,在基本信息卡页面点击自定义模型进入,
在这里插入图片描述
参见前文服务和属性列表,5分钟就可以完成产品模型定义/功能定义。结果如下,
在这里插入图片描述

编解码插件开发-消息

部分内容请参考 #<IoT/HCIP实验-1/物联网开发平台实验Part2(HCIP-IoT实验手册版)># 其中的编解码插件开发相关章节。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从产品详情界面,进入插件开发,使用图形化开发方式,按照上文消息和字段说明逐个定义。
在这里插入图片描述
如上消息创建工作即完成。这里再回顾一下mid响应标识的概念。通过消息建立过程和现象可知,在IoTDA下命令和命令的响应是两种消息,它们的消息标识messageid通常分别为n和n+1。在华为云 IoTDA 的命令下发机制中,mid(Message Identifier)字段是消息的唯一追踪标识符,其核心作用是实现命令请求与响应的精准匹配,并解决异步通信场景下的消息关联难题。mid字段的核心作用,可从如下3点来理解,命令与响应的逐一对应、防止重复处理与消息去重、状态追踪与调试。mid是0-65536之间的一个动态变动的值,

// 平台下发命令(带mid)
{"messageId": "SET_TEMPERATURE","mid": "20231108001","params": {"temp": 25}
}// 设备响应(返回相同mid)
{"messageId": "SET_TEMPERATURE_RESPONSE","mid": "20231108001","result": 0  // 0表示成功
}

编解码插件开发-关联

在这里插入图片描述
如上,通过拖拽方式,管理消息字段和产品模型中的属性、命令字段、响应字段。

编解码插件开发-部署

一定不要忘了部署编解码插件。之后最好使用虚拟设备先验证你的模型和编解码插件。
在这里插入图片描述

注册实际设备

依次操作左侧选项卡中的-所有设备-注册设备,进入单设备注册界面,录入相关信息。设备标识码使用 IMEI 国际移动设备标识码,可以在通信模组封装上看到,也可以使用 AT+CGSN=1 进行查询。好久不去练习AT指令了,我在这里又搞了一次乌龙。我将串口拨码开关拨到AT PC侧,在串口终端中输入AT,点击发送却没有任何反应。呢?尝试了10几分钟,才恍然大悟,是波特率的问题。AT指令收发操作时,使用的串口波特率是9600,而端侧MCU程序开发中配置的串口波特率都是115200。
在这里插入图片描述
还要继续参考 #<IoT/基于NB28-A/BC28-CNV通信模组使用AT指令连接华为云IoTDA平台(HCIP-IoT实验2)># 和 #<IoT/HCIP实验-2/基于NB-IoT模组的AT指令实验(小熊派IoT+NB28-A模组)># 等文章,完成设备创建信息的填写,尤其是密钥信息的录入。

智慧农业端侧编码

代码模版参考LiteOS_Lab_HCIP示例工程下的targets\STM32L431_BearPi\Demos\oc_agriculture_template 下的模版文件。

工程配置

关于Kconfig相关配置,可以直接修改 LiteOS_Lab_HCIP\targets\STM32L431_BearPi 根目录下的 .config 文件,可以可以使用示例工程目录下的 defaults.sdkconfig 配置文件覆盖它,只要修改配置为,

CONFIG_Demo_Agriculture=y
CONFIG_USER_DEMO="oc_agriculture_template"

在这里插入图片描述
KConfig体系下的.mk文件(该后缀名可能是Makefile的缩写)是Linux内核及基于KConfig的构建系统中实现源代码编译规则与配置联动的核心组件。它们通过解析.config文件中的符号(如CONFIG_FOO),动态控制代码的编译方式、模块生成及目录递归构建流程。在.config内配置 CONFIG_USER_DEMO 宏的取值后,与智慧农业相关的驱动层和应用层源码就会被动态的包含到最终的Makefile文件中。

同样还需要修改的是与 .config 同一目录下的 iot_config.h 文件,理论上在 genconfig 工具下,可以将 .config 转换为 iot_config.h。这里不再尝试,只是按照手册简单的修改头文件下的 CONFIG_USER_DEMO 宏定义与 .config 保持一致即可。@note 关注实验内容,不关注IDE。

数据结构定义

端侧程序中使用的C语言数据结构,其与平台编解码插件开发中的消息定义要完全匹配。

//消息结构体
#pragma pack(1)
//上行 //数据上报
typedef struct {int8u  messageId;int8u  Temperature;int8u  Humidity;int16u Luminance;  //注意字段顺序和字节数
} tag_app_Agricultural;
//上行 //光照开关控制-响应数据
typedef struct {int8u messageId;int8u mid;int8u errcode;int8u Light_State;
} tag_app_Response_Agricultural_Control_Light;
//上行 //电机开关控制-响应数据
typedef struct {int8u messageId;int8u mid;int8u errcode;int8u Motor_State;
} tag_app_Response_Agricultural_Control_Motor; 
//下行 //光照开关控制
typedef struct {int8u  messageId;int16u mid;string Light[3];
} tag_app_Agricultural_Control_Light;
//下行 //电机开关控制
typedef struct {int8u  messageId;int16u mid;string Motor[3];
} tag_app_Agricultural_Control_Motor;
#pragma pack()

数据收集任务

//数据收集任务
static int app_data_collect_task(void *usr_data) {//扩展板初始化Init_E53_IA1();//实时读取和显示传感器或控制器数据while (1)  {//更新传感器值/驱动中导出的全局变量E53_IA1_Read_Data();//在串口中输出#if 0printf("** RTT Value:Lux(%2.2f), Humidity(%2.2f), Temperature(%2.2f) \r\n", E53_IA1_Data.Lux, E53_IA1_Data.Humidity, E53_IA1_Data.Temperature);#endif//显示在屏幕上LCD_ShowString(10, 140, 200, 16, 16, "Temperature:");LCD_ShowNum(140, 140, (int)E53_IA1_Data.Temperature, 5, 16);LCD_ShowString(10, 170, 200, 16, 16, "Humidity:");LCD_ShowNum(140, 170, (int)E53_IA1_Data.Humidity, 5, 16);LCD_ShowString(10, 200, 200, 16, 16, "Lux:");LCD_ShowNum(140, 200, (int)E53_IA1_Data.Lux, 5, 16);//刷新频率osal_task_sleep(2*1000);}return 0;
}

每次任务循环都要执行 E53_IA1_Read_Data() 操作,该函数并没有设置输入输出参数,这是因为E53_IA1驱动程序的接口头文件中直接导出了 E53_IA1_Data 这个全局变量,我对这种大写字母开头的变量定义,真是极度的不适应的。E53_IA1_Read_Data 函数被调用时,其内部会 读取扩展板各传感器的值并直接赋值给 E53_IA1_Data 变量的各个字段。

数据上报任务

数据上报任务中,干了3件事情。注册平台指令的接收函数app_msg_deal、设备接入过程 oc_lwm2m_config、采集数据的上报。

//数据上报任务
static int app_data_report_task(void *usr_data) {int ret = -1;//oc_ 代表OpenCPU开发模式 /Part2会继续讨论//允许开发者直接在通信模组(如NB-IoT、4G模组)的微控制器(MCU)上运行应用程序逻辑oc_config_param_t oc_config;tag_app_Agricultural  Agricultural;(void) memset(&oc_config,0,sizeof(oc_config));//设置引导模式oc_config.boot_mode = en_oc_boot_strap_mode_factory;oc_config.rcv_func = app_msg_deal;oc_config.usr_data = NULL;oc_config.app_server.address = cn_app_server;oc_config.app_server.port = cn_app_port;oc_config.app_server.ep_id = cn_endpoint_id;    #if 1 //CoAPS/DTLS加密oc_config.app_server.psk = (char *)cn_app_psk;oc_config.app_server.psk_len = cn_app_psklen;oc_config.app_server.psk_id = cn_endpoint_id;#endifret = oc_lwm2m_config(&oc_config);if (0 != ret) {printf("oc lwm2m_config failure code %d", ret);return ret;}else {//在我的测试环境下/等待成功的时间在20s左右printf("oc lwm2m_config successful..");}while(1) //--TODO ,you could add your own code here{Agricultural.messageId = cn_app_msg_Agricultural;//多字节数据要转大端模式Agricultural.Luminance   = htons((int16_t)E53_IA1_Data.Lux);Agricultural.Humidity    = (int8_t)E53_IA1_Data.Humidity;Agricultural.Temperature = (int8_t)E53_IA1_Data.Temperature;//执行数据上报操作oc_lwm2m_report( (char *)&Agricultural, sizeof(Agricultural), 1000);//osal_task_sleep(4*1000);}return ret;
}

数据上报任务本身并不复杂,就是将E53_IA1_Data数据转换为平台消息格式并调用lwM2M接口上报。设备接入/LwM2M初始化过程、app_msg_deal 平台命令接收过程,会在后续的两个小节中分别讲述。

设备接入过程

在一些资料中提及 oc_lwm2m_config(&oc_config) 函数是阻塞的,但通过这里的示例代码来看,它显然不是,至少不完全是。也有的地方提到,该函数可以工作在阻塞或非阻塞两种模式下,但并不是通过某种配置来选择。在华为LiteOS的物联网开发框架中,该函数的阻塞/非阻塞行为取决于底层对接模式的选择,而非通过参数直接配置。
在这里插入图片描述

//F:\0hwi\LiteOS_Lab_HCIP\iot_link\oc\oc_lwm2m\oc_lwm2m.mk
ifeq ($(CONFIG_OCLWM2MTINY_ENABLE), y)include $(iot_link_root)/oc/oc_lwm2m/atiny_lwm2m/atiny_lwm2m.mk
else ifeq ($(CONFIG_BOUDICA150_ENABLE),y)include $(iot_link_root)/oc/oc_lwm2m/boudica150_oc/boudica150_oc.mk            
endif

而事实上,以上两种说法都是片面的。在NB通信下,上述初始化过程最终调用的函数如下,

static bool_t boudica150_boot(const char *plmn, const char *apn, const char *bands,const char *server,const char *port)
{//(void) memset(&s_boudica150_oc_cb,0,sizeof(s_boudica150_oc_cb));at_oobregister("qlwevind",cn_urc_qlwevtind,strlen(cn_urc_qlwevtind),urc_qlwevtind,NULL);at_oobregister("boudica150rcv",cn_boudica150_rcvindex,strlen(cn_boudica150_rcvindex),boudica150_rcvdeal,NULL);while(1) {s_boudica150_oc_cb.lwm2m_observe = false;boudica150_reboot();...boudica150_set_nnmi(1);//if(false == boudica150_check_netattach(16)) {continue;}//函数内部,检查不通过时将执行osal_task_sleep(1000)操作if(false == boudica150_check_observe(16))  {continue; //we should do the reboot for the nB}break;} //while...
}

通过实际程序调试,可以看到,当oc连接参数不正确时,boudica150_check_observe 函数内部会执行osal_task_sleep(1000)操作,挂起线程,并对多执行16次,进而使得 boudica150_boot 函数和其上层的 oc_lwm2m_config 函数无法返回。直到检查通过。该函数是 LiteOS 中为 Boudica150 NB-IoT 芯片设计的 LwM2M 协议栈辅助函数,主要用于验证 LwM2M 服务器是否成功注册了对设备资源的观察请求。

正确设置接入参数

参见 #<IoT/基于NB28-A/BC28-CNV通信模组使用AT指令连接华为云IoTDA平台(HCIP-IoT实验2)># 中的描述,进行IP和秘钥等设置。

//连接平台基本变量/所有以下配置务必要与平台测配置一致哈
#define cn_endpoint_id   "6850b867d582f20018321e88_860xxxxxxxxxx03" 
#define cn_app_server    "124.70.30.197"
#define cn_app_port      "5684"
#define cn_app_pskid     "860xxxxxxxxxx03"       
#define cn_app_psk       "xxxxxxxxxxxxxxxxxxxx"  
#define cn_app_psklen     20

从IoTDA实例详情中获取 CoAPS 服务端地址,并通过ping指令转换为IP地址,
在这里插入图片描述参见上文连接,保证CDP配置正确、DTLS开启、PSK配置位数大于16位16进制数字。这些所有的配置最好也通过PC模式下的AT指令进行测试,直至 AT+NMGS=5,00193C0064 可以正常发送数据,之后再回到代码测试中来。

命令响应任务

在模板中已经存在了接收平台命令的回调函数的实现,且使用了行缓冲区和信号量。

//if your command is very fast,please use a queue here--TODO
#define cn_app_rcv_buf_len 128
static int             s_rcv_buffer[cn_app_rcv_buf_len];
static int             s_rcv_datalen;
static osal_semp_t     s_rcv_sync;//理论上可以在此函数内直接处理平台下发的指令/但通常不建议这个干
static int app_msg_deal(void *usr_data, en_oc_lwm2m_msg_t type, void *data, int len) {unsigned char *msg;msg = data;int ret = -1;if(len <= cn_app_rcv_buf_len)    {//0xAAAA是平台对端侧数据上报的反馈/如果开启了的话if (msg[0] == 0xaa && msg[1] == 0xaa)   {printf("OC respond message received! \n\r");return ret;}//向行缓冲区存放指令数据memcpy(s_rcv_buffer,msg,len);s_rcv_datalen = len;//信号量+1(void) osal_semp_post(s_rcv_sync);//调试打印printf("recv msgID[%d] and semp_post....\r\n", msg[0]);//返回处理正常ret = 0;}return ret;
}

//下发命令处理任务(从队列中取数据) //app_msg_deal负责向队列中写数据
static int app_cmd_deal_task(void *usr_data) {int ret = -1;//用于响应平台操作tag_app_Response_Agricultural_Control_Light Response_Control_Light;tag_app_Response_Agricultural_Control_Motor Response_Control_Motor;//用于转换收到到指令tag_app_Agricultural_Control_Light *pt_Light_cmd;tag_app_Agricultural_Control_Motor *pt_Motor_cmd;//消息标识int8u msgid = 0;while (1){   //函数返回bool值if (osal_semp_pend(s_rcv_sync, cn_osal_timeout_forever)) {//_rcv_buffer是int数组/以下获取其低字节/也即其第1字段的值/注意是位与运算不是逻辑与运算msgid = s_rcv_buffer[0] & 0x000000FF;//调试打印printf("get msgID[%d] and semp_pend..\r\n", msgid);//指令消息IDswitch (msgid) {case cn_app_msg_Agricultural_Control_Light:pt_Light_cmd = (tag_app_Agricultural_Control_Light *)&s_rcv_buffer;//注意要将来自网络的大端数据转换为本地小端模式printf("Agricultural_Control_Light: msgid:%d mid:%d Light:%s \n\r", pt_Light_cmd->messageId, ntohs(pt_Light_cmd->mid), pt_Light_cmd->Light);//指令为打开操作if (pt_Light_cmd->Light[0] == 'O' && pt_Light_cmd->Light[1] == 'N'){HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_SET);//设置应答数据Response_Control_Light.messageId = cn_app_rspnd_Agricultural_Control_Light;Response_Control_Light.mid = pt_Light_cmd->mid;Response_Control_Light.errcode = 0;Response_Control_Light.Light_State = 1;//执行数据上报操作oc_lwm2m_report( (char *)&Response_Control_Light, sizeof(Response_Control_Light), 1000);}if (pt_Light_cmd->Light[0] == 'O' && pt_Light_cmd->Light[1] == 'F' && pt_Light_cmd->Light[2] == 'F'){HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_RESET);//设置应答数据Response_Control_Light.messageId = cn_app_rspnd_Agricultural_Control_Light;Response_Control_Light.mid = pt_Light_cmd->mid;Response_Control_Light.errcode = 0;Response_Control_Light.Light_State = 0;//执行数据上报操作oc_lwm2m_report( (char *)&Response_Control_Light, sizeof(Response_Control_Light), 1000);}   break;case cn_app_msg_Agricultural_Control_Motor:pt_Motor_cmd = (tag_app_Agricultural_Control_Motor *)&s_rcv_buffer;//注意要将来自网络的大端数据转换为本地小端模式printf("Agricultural_Control_Motor: msgid:%d mid:%d Motor:%s \n\r", pt_Motor_cmd->messageId, ntohs(pt_Motor_cmd->mid), pt_Motor_cmd->Motor);//指令为打开操作if (pt_Motor_cmd->Motor[0] == 'O' && pt_Motor_cmd->Motor[1] == 'N'){HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET);//设置应答数据Response_Control_Motor.messageId = cn_app_rspnd_Agricultural_Control_Motor;Response_Control_Motor.mid = pt_Motor_cmd->mid;Response_Control_Motor.errcode = 0;Response_Control_Motor.Motor_State = 1;//执行数据上报操作oc_lwm2m_report( (char *)&Response_Control_Motor, sizeof(Response_Control_Motor), 1000);}if (pt_Motor_cmd->Motor[0] == 'O' && pt_Motor_cmd->Motor[1] == 'F' && pt_Motor_cmd->Motor[2] == 'F'){HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_RESET);//设置应答数据Response_Control_Motor.messageId = cn_app_rspnd_Agricultural_Control_Motor;Response_Control_Motor.mid = pt_Motor_cmd->mid;Response_Control_Motor.errcode = 0;Response_Control_Motor.Motor_State = 0;//执行数据上报操作oc_lwm2m_report( (char *)&Response_Control_Motor, sizeof(Response_Control_Motor), 1000);}break;default:printf("Err command recved!");break;} // switch} //if semp pendelse {printf("osal_semp pend failurre!");}} //whilereturn ret;
}

在 oc_lwm2m_config 函数的输入参数中,我们传递了 app_msg_deal 函数指针进去。
在这里插入图片描述
平台下发的指令到达端侧LwM2M底层后,通过回调app_msg_deal,将指令消息传递到用户应用层。在我们的上述代码中,这些数据被存储到 s_rcv_buffer 缓冲区,在信号量的计数控制下,我们在命令处理任务中从 s_rcv_buffer 取出数据,判定不同消息ID后分别处理。

程序调试

首先要通过串口打印或LCD屏幕显示,确保程序基础运行是正常的。然后确定设备连接华为云平台成功,
在这里插入图片描述
确认平台侧收到了设备上报的实时数据,
在这里插入图片描述
数据下发操作和响应,
在这里插入图片描述
扩展板灯光控制效果,
在这里插入图片描述

其他

实验进行到现在这个阶段,就已经可以断定,在NB-IoT通信模组的情况下,主板程序在应用层调用LwM2M的接口进行初始化、数据上报和命令接收过程,在某个层次上确有AT指令参与工作。至于LwM2M和NB模组如何协同,我们下一篇再详谈,或单独开篇。HCIP实验-5/基于NB-IoT的智慧农业实验,完整工程源码,从CSDN资源下载。本文只是从面上实践了一个例程,还有很长的路要走。

下一篇,
#<IoT/HCIP实验-5/基于NB-IoT / WIFI的智慧农业实验(探寻LwM2M/CoAP与AT指令、Wifi、NB-IoT之间的关联)>#

http://www.lqws.cn/news/524053.html

相关文章:

  • LOOP如何让长周期交互LLM代理在复杂环境中实现突破?
  • 正则表达式匹配实现
  • Boosting:从理论到实践——集成学习中的偏差征服者
  • Prompt:面向目标的提示词
  • WeakAuras Lua Script [ICC BOSS 12 - The Lich King]
  • Objective-C面向对象编程:类、对象、方法详解(保姆级教程)
  • 自动驾驶数据特征提取实战:用Python打开智能驾驶的新视角
  • 深入理解残差网络(ResNet):原理与PyTorch实现
  • Mysql数据库操作大全万字详解
  • 【Redis】Redis的下载安装和配置
  • 检查StringBuilder是否包含字符串
  • ARM内核之CMSIS
  • 【机器学习】非参数贝叶斯回归方法 GPR
  • ipfs在windows下载和安装
  • JSON框架转化isSuccess()为sucess字段
  • C++(智能指针)
  • Liunx操作系统笔记2
  • linux-修改文件命令(补充)
  • IT运维效率提升: 当IT监控遇上3D可视化
  • 三步实现B站缓存视频转MP4格式
  • 记一次AWS 中RDS优化费用使用的案例
  • Postman鉴权动态传参?对比脚本变量vs环境变量!
  • 理论加案例,一文读懂数据分析中的分类建模
  • 通过pyqt5学习MVC
  • 代理型 AI 重塑营销格局:国产 R²AIN SUITE 如何破解数据与技术瓶颈,实现 AI 赋能全链路提效
  • VScode常用快捷键【个人总结】
  • 2024年AEI SCI1区TOP,强化学习人工兔优化算法RLTARO+山地森林地形无人机编队路径规划,深度解析+性能实测
  • Dify、n8n、Coze、FastGPT 和 Ragflow 对比分析:如何选择最适合你的智能体平台?
  • Wpf的Binding
  • 数据库1.0