Payload-SDK自动升级

Payload-SDK自动升级

前言

自动升级旨在通过无人机更新负载上的软件,包括不限于:Payload-SDK应用、配置文件等。对于文件的传输,大疆的Payload-SDK给我们提供了两种方式:使用FTP协议和使用大疆自研的DCFTP。我们实现的自动升级是基于FTP。所以自动升级的实现可以分成3个部分:

  • FTP服务的搭建
  • Payload-SDK应用的修改
  • sh包的制作与sh打包脚本的编写

FTP服务的搭建

参考大疆官方做法:链接。

首先我们需要在负载上搭建一个vsftp服务。我司使用的是正点原子的rk3588,而rk3588默认已经在buildroot当中引入vsftp服务,我们重点只需要配置ftp配置文件:/etc/vsftpd.conf即可。配置参考如下(完整配置,可直接copy使用):

# 允许匿名用户
anonymous_enable=YES
# 允许本地用户登录
local_enable=YES
# 允许用户写(即上传文件)
write_enable=YES
# 允许为目录配置显示信息,显示每个目录下面的message_file文件的内容
dirmessage_enable=YES# 是否让系统自动维护上传和下载的日志文件
xferlog_enable=YES
# 是否设定FTP服务器将启用FTP数据端口的连接请求
connect_from_port_20=YES# 是否禁止用户离开设置的根目录
chroot_local_user=NO
chroot_list_enable=NO
listen=YES
allow_writeable_chroot=YES

然后,参考dji本地升级文档,增加一个用户,该用户会被大疆无人机所使用:

adduser psdk_payload_ftp --home /upgrade# 用户密码设置为:DJi_#$31# 可以使用该命令删除用户,但经过测试,正点原子的rk3588上并不支持该命令。
userdel -r

重启开发板,使用 ps aux | grep vsftpd 可看到运行起来的vsftpd服务。客户端使用FileZilla软件可使用用户账号psdk_payload_ftp登录ftp。

在修改Payload-SDK应用前,需要将应用设置为开机自启动,方法如下:

# 新建一个文件,文件前面的数字代表优先级,数值越小优先级越大,
# 脚本当中添加如下内容:/usr/local/bin/dji_sdk_demo_linux&
# 将开发的Payload-SDK应用放到/usr/local/bin/,每次开发板启动
# 时会自动在后台启动应用。
vi /etc/init.d/S99autorun.shchmod +x /etc/init.d/S99autorun.sh

Payload-SDK应用的修改

我们使用的SDK版本为:3.11.1,确保manifold2/application/dji_sdk_config.h定义了CONFIG_MODULE_SAMPLE_UPGRADE_ON。然后,在main函数当中,找到自动升级初始化的代码,主要做如下修改:

T_DjiTestUpgradeConfig testUpgradeConfig = {.firmwareVersion = firmwareVersion,.transferType = DJI_FIRMWARE_TRANSFER_TYPE_DCFTP,.needReplaceProgramBeforeReboot = true
};||VT_DjiTestUpgradeConfig testUpgradeConfig = {.firmwareVersion = firmwareVersion,.transferType = DJI_FIRMWARE_TRANSFER_TYPE_FTP,.needReplaceProgramBeforeReboot = true
};

将transferType修改为DJI_FIRMWARE_TRANSFER_TYPE_FTP,文件使用vsftp传输。

对于升级功能,我们主要将注意集中在upgrade目录下的代码。

在test_upgrade.c/h当中,首先注册了四个回调:

T_DjiUpgradeHandler s_upgradeHandler = {.EnterUpgradeMode = DjiTest_EnterUpgradeMode,.CheckFirmware = DjiTest_CheckFirmware,.StartUpgrade = DjiTest_StartUpgrade,.FinishUpgrade = DjiTest_FinishUpgrade
};

DjiTest_EnterUpgradeMode、DjiTest_CheckFirmware回调主要做升级前预处理。可根据实际情况实现。

DjiTest_StartUpgrade函数在升级包被FTP传输完毕后会被回调。

DjiTest_FinishUpgrade ???在升级被用户打断被调用???

DjiTest_StartUpgrade函数实现如下:

static T_DjiReturnCode DjiTest_StartUpgrade(void)
{T_DjiOsalHandler *osalHandler = DjiPlatform_GetOsalHandler();osalHandler->MutexLock(s_upgradeStateMutex);s_upgradeState.upgradeOngoingInfo.upgradeProgress = 0;s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_ONGOING;osalHandler->MutexUnlock(s_upgradeStateMutex);return DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
}

做了两件事:首先将升级进度条置为0,然后将状态置为ONGOING。这儿状态的改变会被 DjiTest_UpgradeStartService 函数最后创建的 DjiTest_UpgradeProcessTask 线程所探测到。后面将详细讨论 DjiTest_UpgradeProcessTask 线程。

在 DjiTest_UpgradeStartService 当中,还有一段微妙而重要的代码,如下:

/* ... */returnCode = DjiTest_GetUpgradeRebootState(&isUpgradeReboot, &upgradeEndInfo);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {USER_LOG_ERROR("Get upgrade reboot state error");isUpgradeReboot = false;
}returnCode = DjiTest_CleanUpgradeRebootState();
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {USER_LOG_ERROR("Clean upgrade reboot state error");
}osalHandler->MutexLock(s_upgradeStateMutex);
if (isUpgradeReboot == true) {s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_END;s_upgradeState.upgradeEndInfo = upgradeEndInfo;
} else {s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_IDLE;
}
osalHandler->MutexUnlock(s_upgradeStateMutex);/* ... */

DjiTest_GetUpgradeRebootState 和 DjiTest_CleanUpgradeRebootState 函数实现在:upgrade/test_upgrade_platform_opt.c/h -> linux/commom/upgrade_platform_opt/upgrade_platform_opt_linux.c/h。

这段代码先读了一个本地文件,从里面获取一些升级状态,读完后立马将状态文件删除,如果文件不存在,说明是一次正常的开启启动,而不是因上一次升级而启动;如果文件存在,说明是应自动升级而重启,我们需要获取最后一次升级的状态,然后修改升级状态为END,同样的,DjiTest_UpgradeProcessTask线程会探测到升级状态的改变,然后向无人机报告自动升级结束。电脑上的DJI Assistant 2软件就会显示升级成功的画面。

因为重启的过程也算自动升级的一部分,所以存在这样的设计:重启前保存升级状态,重启后读取上一次升级状态,通知无人机升级完成。

下面详细看看 DjiTest_UpgradeProcessTask 函数的实现:

static void *DjiTest_UpgradeProcessTask(void *arg)
{T_DjiOsalHandler *osalHandler = DjiPlatform_GetOsalHandler();T_DjiUpgradeState tempUpgradeState;T_DjiUpgradeEndInfo upgradeEndInfo;T_DjiReturnCode returnCode;while (1) {osalHandler->MutexLock(s_upgradeStateMutex);tempUpgradeState = s_upgradeState;osalHandler->MutexUnlock(s_upgradeStateMutex);if (tempUpgradeState.upgradeStage == DJI_UPGRADE_STAGE_ONGOING) {if (s_isNeedReplaceProgramBeforeReboot) {// Step 1 : 替换最新文件returnCode = DjiTest_ReplaceOldProgram();osalHandler->TaskSleepMs(1000);osalHandler->MutexLock(s_upgradeStateMutex);s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_ONGOING;s_upgradeState.upgradeOngoingInfo.upgradeProgress = 20;DjiUpgrade_PushUpgradeState(&s_upgradeState);osalHandler->MutexUnlock(s_upgradeStateMutex);// Step 2 : 清空升级目录returnCode = DjiTest_CleanUpgradeProgramFileStoreArea();osalHandler->TaskSleepMs(1000);osalHandler->MutexLock(s_upgradeStateMutex);s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_ONGOING;s_upgradeState.upgradeOngoingInfo.upgradeProgress = 30;DjiUpgrade_PushUpgradeState(&s_upgradeState);osalHandler->MutexUnlock(s_upgradeStateMutex);}// Step 3 :模拟升级过程do {osalHandler->TaskSleepMs(1000);osalHandler->MutexLock(s_upgradeStateMutex);s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_ONGOING;s_upgradeState.upgradeOngoingInfo.upgradeProgress += 10;tempUpgradeState = s_upgradeState;DjiUpgrade_PushUpgradeState(&s_upgradeState);osalHandler->MutexUnlock(s_upgradeStateMutex);} while (tempUpgradeState.upgradeOngoingInfo.upgradeProgress < 100);// Step 4 :将升级状态持久化保存到状态文件当中。osalHandler->MutexLock(s_upgradeStateMutex);s_upgradeState.upgradeStage = DJI_UPGRADE_STAGE_DEVICE_REBOOT;s_upgradeState.upgradeRebootInfo.rebootTimeout = DJI_TEST_UPGRADE_REBOOT_TIMEOUT;DjiUpgrade_PushUpgradeState(&s_upgradeState);osalHandler->MutexUnlock(s_upgradeStateMutex);osalHandler->TaskSleepMs(1000); // sleep 1000ms to ensure push send terminal.upgradeEndInfo.upgradeEndState = DJI_UPGRADE_END_STATE_SUCCESS;returnCode = DjiTest_SetUpgradeRebootState(&upgradeEndInfo);// Step 5 :重启设备returnCode = DjiTest_RebootSystem();while (1) {osalHandler->TaskSleepMs(500);}} else if (s_upgradeState.upgradeStage == DJI_UPGRADE_STAGE_END) {// Step 6 :升级重启完成,升级完成被探测到。通知无人机,反馈到DJI Assistant 2 (Enterprise Series),反馈升级成功完成。osalHandler->MutexLock(s_upgradeStateMutex);DjiUpgrade_PushUpgradeState(&s_upgradeState);osalHandler->MutexUnlock(s_upgradeStateMutex);}osalHandler->TaskSleepMs(500);}
}

完整的升级流程是:

  1. FTP文件传输完毕

  2. 回调DjiTest_StartUpgrade,设置升级进度为0,升级状态为ONGOING。

  3. DjiTest_UpgradeProcessTask线程探测到升级状态为ONGOING。

  4. 升级应用。反馈进度。

  5. 清空升级临时文件。反馈进度。

  6. 保存升级状态到状态文件。

  7. 重启系统。

  8. 初始化时DjiTest_UpgradeStartService读取状态文件。并设置升级状态为END。

  9. DjiTest_UpgradeProcessTask线程探测到升级状态为END。

  10. 反馈进度,DJI Assistant 2收到反馈,显示升级完成。

因为我们的升级包使用的shell嵌入二进制数据的方式,所以,相应的,DjiTest_ReplaceOldProgram的逻辑被替换为:将升级包命名为upgrade.sh,并赋予可执行权限,然后执行sh脚本,sh脚本会自动将各个文件替换为最新的。由于rk3588 reboot命令并不支持任何参数,所以需要将upgrade_platform_opt_linux.c当中DjiUpgradePlatformLinux_RebootSystem函数实现的reboot命令后面的参数删除掉。还需要注意的是,升级包一定要命名为 PSDK_APPALIAS_V01.00.00.00.bin 形式,后面的版本用户可以根据实际情况修改。

sh包的制作与sh打包脚本的编写

所谓sh包,就是将我们的二进制文件(不管是可执行文件,还是tarboll等)嵌入到shell脚本当中,简单来说,就是一个开头是一小段是shell脚本,末尾都是二进制数据(也可以是base64加密后的数据)的文件。

通常来说,开头的那一段shell脚本是固定套路如下:

#!/bin/sh
PATH=/usr/bin:/bin
umask 022
md5=3e2ec953a6505b0ef8ad4e53babd4b43
pre_install()
{echo "Preparing installation environment (simplified)..."mkdir ./install.tmp.$$
}
check_sum()
{if [ -x /usr/bin/md5sum ]&&[ -f "install.tmp.$$/extract.$$" ]; thenecho "Checking md5..."sum_tmp=$(/usr/bin/md5sum install.tmp.$$/extract.$$ | awk '{print $1}')if [ $sum_tmp != $md5 ]; thenecho "File md5 mismatch, please check file integrity, exiting!"exit 1fielseecho "Cannot find md5sum command or file not extracted, exiting"exit 1fi
}
extract()
{echo "Extracting files from script"line_number=`awk '/^__BIN_FILE_BEGIN__/ {print NR + 1; exit 0; }' "$0"`    # tail -n +$line_number "$0" >./install.tmp.$$/extract.$$tail -n +$line_number "$0" >./install.tmp.$$/extract_tmp.$$base64 -d ./install.tmp.$$/extract_tmp.$$ >./install.tmp.$$/extract.$$
}
install()
{echo "Installing (simplified)..."mv install.tmp.$$/extract.$$ install.tmp.$$/extract.tar.gztar -xvf install.tmp.$$/extract.tar.gz -C install.tmp.$$/# do something# install file...# 将旧文件替换为./install.tmp.$$/dc_app/目录下的文件。# mv、cp等,如下:# mv -f ./install.tmp.$$/dc_app/dji_sdk_demo_linux /usr/local/bin/dji_sdk_demo_linux# chmod 777 /usr/local/bin/dji_sdk_demo_linux
}
post_install()
{echo "Configuring (simplified)..." echo "Cleaning up temporary files"rm -rf install.tmp.$$
}main()
{pre_installextractcheck_suminstallpost_installexit 0
}main
#The binary file starts below
__BIN_FILE_BEGIN__

最后的一个回车换行不要删除!!!

我们以tarboll为例,将需要升级的文件按约定好的命名,并放到dc_app(名字可以随便取)目录下面,使用tar命令打包

tar -czvf dc_app.tar.gz ./dc_app

sh包制作流程如下:

  1. 新建一个文件object.sh,将上面这段代码包括末尾的回车换行拷贝到object.sh当中。

  2. 计算tarboll的md5值:

    lunar@lunar-ThinkStation-K-C2N00:~/workspace/uav_temp/build_package$ md5sum ./dc_app.tar.gz
    e12bfd83e61975032cbc03bc570002ec  ./dc_app.tar.gz
    
  3. 将上面sh脚本当中的全局变量md5,替换为e12bfd83e61975032cbc03bc570002ec。

  4. 使用base64命令,将tarboll进行base64编码,并追加到sh文件末尾。

    base64 dc_app.tar.gz >> object.sh
    
  5. 将object.sh更名为PSDK_APPALIAS_V01.00.00.01.bin。

最后,拿到PSDK_APPALIAS_V01.00.00.01.bin升级包后,可以通过DJI Assistant 2 连接到无人机对负载进行升级。

当然,上面1~5打包的过程可以另外编写一个shell脚本,输入需要升级的文件,然后直接输出.bin文件。参考如下:

#!/bin/bash# =============================================
# Script: create_pack.sh
# Description: Package files, calculate MD5, modify object.sh and append base64 encoded data
# Usage: ./script.sh <version> <file1> <file2> <file3> ...
# Example: ./create_pack.sh V01.02.03.04 app.cpp app app.conf
# =============================================# Validate arguments
if [ $# -lt 2 ]; thenecho "Error: Insufficient arguments!"echo "Usage: $0 <version> <file1> <file2> ..."echo "Example: $0 V01.02.03.04 app.cpp app app.conf"exit 1
fiVERSION="$1"
shift # Remove version argument, remaining are files
SOURCE_FILES=("$@")
OBJECT_SCRIPT="object.sh"
TEMP_DIR="temp_pack_dir_$$"
OUTPUT_NAME="PSDK_APPALIAS_${VERSION}.bin"# Check required tools
if ! command -v md5sum &> /dev/null || ! command -v base64 &> /dev/null; thenecho "Error: Required tools (md5sum/base64) not found!"exit 1
fi# Create temporary directory structure
mkdir -p "${TEMP_DIR}/dc_app" || { echo "Error: Failed to create temp directory"; exit 1; }echo "Step 1/6: Copying files to temporary directory..."
for file in "${SOURCE_FILES[@]}"; doif [ ! -f "$file" ]; thenecho "Error: File $file not found!"exit 1ficp -v "$file" "${TEMP_DIR}/dc_app/" || exit 1
doneecho "Step 2/6: Creating compressed archive..."
TARBALL_NAME="dc_app_${VERSION}.tar.gz"
tar -czvf "$TARBALL_NAME" -C "$TEMP_DIR" dc_app || { echo "Error: Compression failed"; exit 1; }echo "Step 3/6: Calculating MD5 checksum..."
NEW_MD5=$(md5sum "$TARBALL_NAME" | awk '{print $1}')
echo "Generated MD5: $NEW_MD5"if [ ! -f "$OBJECT_SCRIPT" ]; thenecho "Error: ${OBJECT_SCRIPT} not found!"exit 1
fiecho "Step 4/6: Updating MD5 in ${OBJECT_SCRIPT}..."
cp -p "$OBJECT_SCRIPT" "${OBJECT_SCRIPT}.bak" || exit 1
sed -i "s/^md5=.*/md5=${NEW_MD5}/" "$OBJECT_SCRIPT" || { echo "Error: MD5 replacement failed"; exit 1; }echo "Step 5/6: Generating final output ${OUTPUT_NAME}..."
{cat "$OBJECT_SCRIPT"base64 "$TARBALL_NAME" || exit 1
} > "$OUTPUT_NAME" || { echo "Error: Failed to create output file"; exit 1; }chmod +x "$OUTPUT_NAME"echo "Step 6/6: Cleaning temporary files..."
rm -rf "$TEMP_DIR" "$TARBALL_NAME"echo "========================================"
echo "Operation completed successfully!"
echo "Output file: ${OUTPUT_NAME}"
echo "MD5 checksum: ${NEW_MD5}"
echo "Backup created: ${OBJECT_SCRIPT}.bak"
echo "========================================"

本章完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/912182.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/912182.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第五代移动通信新型调制及非正交多址传输技术研究与设计

第五代移动通信新型调制及非正交多址传输技术研究与设计 一、新型调制技术研究与实现 1. FBMC (滤波器组多载波) 调制实现 import numpy as np import matplotlib.pyplot as plt from scipy.fft import fft, ifft, fftshift from scipy.signal import get_window

AI 智能运维,重塑大型企业软件运维:从自动化到智能化的进阶实践​

一、引言&#xff1a;企业软件运维的智能化转型浪潮​ 在数字化转型加速的背景下&#xff0c;大型企业软件架构日益复杂&#xff0c;微服务、多云环境、分布式系统的普及导致传统运维模式面临效率瓶颈。AI 技术的渗透催生了智能运维&#xff08;AIOps&#xff09;的落地&#x…

Apache CXF安装详细教程(Windows)

本章教程,主要介绍,如何在Windows上安装Apache CXF,JDK版本是使用的1.8. 一、下载Apache CXF Apache CXF(Apache Celtix Fireworks)是一个开源的 Web 服务框架,用于 构建和开发服务端与客户端的 Web 服务应用程序。它支持多种 Web 服务标准,尤其是 SOAP(基于 XML 的协议…

逆向入门(22)程序逆向篇-TraceMe

界面看起来很普通 也没有壳&#xff0c;直接搜索字符串找到关键代码处 但是发现这些都是赋值&#xff0c;并没有实现跳转相关的函数。这里通过给弹窗函数下断点&#xff0c;追一下返回函数来找触发点。 再次点击check&#xff0c;触发断点&#xff0c;接着按ctrlF9返回到函数…

中文PDF解析准确率排名

市面上的文档解析工具种类各异&#xff0c;包括更适用于论文解析的&#xff0c;专精于表格数据提取的&#xff0c;针对手写体优化的&#xff0c;适用于技术文档的&#xff0c;擅长处理复杂多语言混排文档的&#xff0c;专门处理政府招标文档表格的&#xff0c;以及擅长金融类表…

Conformal LEC:官方学习教程

相关阅读 Conformal LEChttps://blog.csdn.net/weixin_45791458/category_12993839.html?spm1001.2014.3001.5482 本文是对Conformal Equivalence Checking User Guide中附录实验的翻译&#xff08;有删改&#xff09;&#xff0c;实验文件可见安装目录Conformal/share/cfm/l…

【Torch】nn.Embedding算法详解

1. 定义 nn.Embedding 是 PyTorch 中的 查表式嵌入层&#xff08;lookup‐table&#xff09;&#xff0c;用于将离散的整数索引&#xff08;如词 ID、实体 ID、离散特征类别等&#xff09;映射到一个连续的、可训练的低维向量空间。它通过维护一个形状为 (num_embeddings, emb…

cdq 三维偏序应用 / P4169 [Violet] 天使玩偶/SJY摆棋子

最近学了 cdq 分治想来做做这道题&#xff0c;结果被有些毒瘤的代码恶心到了。 /ll 题目大意&#xff1a;一开始给定一些平面中的点。然后给定一些修改和询问&#xff1a; 修改&#xff1a;增加一个点。询问&#xff1a;给定一个点&#xff0c;求离这个点最近&#xff08;定义…

System.Threading.Tasks 库简介

System.Threading.Tasks 是 .NET 中任务并行库(Task Parallel Library, TPL)的核心组件&#xff0c;它提供了基于任务的异步编程模型&#xff0c;是现代 .NET 并发编程的基础。 设计原理 1. 核心目标 抽象并发工作&#xff1a;将并发操作抽象为"任务"概念 资源高效…

Python爬虫实战:研究jieba相关技术

1. 引言 1.1 研究背景与意义 随着互联网技术的飞速发展,网络新闻已成为人们获取信息的主要渠道之一。每天产生的新闻文本数据量呈爆炸式增长,如何从海量文本中高效提取有价值的信息,成为信息科学领域的重要研究课题。文本分析技术通过对文本内容的结构化处理和语义挖掘,能…

github 淘金技巧

1. 效率&#xff0c;搜索&#xff0c;先不管。后面再说。 2. 分享的话&#xff0c; 其实使用默认的分享功能也行。也是后面再说。此 app &#xff0c; 今天先做到这里。 下面我们再聊点其他东西。其实我还想问&#xff0c;这个事情&#xff0c;其他人是否也做了&#xff0c; ht…

RAG技术发展综述

摘要 检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技术已成为大语言模型应用的核心技术栈。RAG有效解决了LLM的幻觉问题、知识截止和实时更新挑战&#xff0c;目前正处于全面产业化阶段。本文系统性地分析RAG的全栈技术架构&#xff0c;包括检索…

集群聊天服务器---muduo库(3)

使用muduo网络库进行编译和链接的示例 项目的目录结构 bin: 存放可执行文件。 lib: 存放库文件。 include: 存放头文件。 src: 存放源代码文件。 build: 存放编译生成的中间文件。 example: 存放示例代码。 thirdparty: 存放第三方库。 CMakeLists.txt: CMake构建系统…

双核SOC/5340 应用和网络核间通讯

1&#xff1a; 可以在 nRF Connect SDK 文件夹结构的 samples/ipc/ipc_service 下找到示例&#xff0c;应用和网络核心在由 CONFIG_APP_IPC_SERVICE_SEND_INTERVAL 选项指定的时隙内相互发送数据。可以更改该值并观察每个核心的吞吐量如何变化 nRF5340 DK 可以使用 RPMsg 或 IC…

Spring Cloud Ribbon核心负载均衡算法详解

Ribbon 作为 Spring Cloud 生态中的客户端负载均衡工具&#xff0c;提供多种动态负载均衡算法&#xff0c;根据后端服务状态智能分配请求。其核心算法及适用场景如下&#xff1a; &#x1f9e0; 一、Ribbon 负载均衡算法 算法名称工作原理引用来源轮询 (RoundRobinRule)按服务…

网站图片过于太大影响整体加载响应速度怎么办? Typecho高级图像处理插件

文章目录 LeleImges - Typecho高级图像处理插件 🖼️插件介绍 📝插件架构 🏗️主要功能 ✨性能优势 🚀系统要求 📋安装方法 📥详细配置说明 ⚙️图片质量设置 🎚️最大宽度/高度限制 📏压缩格式选择 🗜️压缩方法选择 🔧GIF处理方式 🎞️备份源文件 💾…

VUE3入门很简单(1)--- 响应式对象

前言 重要提示&#xff1a;文章只适合初学者&#xff0c;不适合专家&#xff01;&#xff01;&#xff01; 什么是响应式对象&#xff1f; 在Vue3中&#xff0c;响应式对象就是这种智能温控器。当你修改JavaScript对象的数据时&#xff0c;Vue会自动更新网页上显示的内容&am…

广州华锐互动携手中石油:AR 巡检系统实现重大突破​

广州华锐互动在 AR 技术领域的卓越成就&#xff0c;通过一系列与知名企业、机构的成功合作案例得以充分彰显。其中&#xff0c;与中石油的合作项目堪称经典&#xff0c;展现了广州华锐互动运用 AR 技术解决实际难题、达成目标的强大实力。​ 中石油作为能源行业的巨擘&#xff…

权威认证!华宇TAS应用中间件荣获CCRC“中间件产品安全认证”

近日&#xff0c;华宇TAS应用中间件顺利通过了中国网络安全审查认证和市场监管大数据中心(CCRC)的信息安全认证&#xff0c;获得了IT产品信息安全认证证书。此次获证&#xff0c;标志着华宇TAS应用中间件在安全性、可靠性及合规性等方面达到行业领先水平&#xff0c;可以为政企…

BI财务分析 – 反映盈利水平利润占比的指标如何分析(下)

之前的文章重点把构成销售净利率、主营业务利润率、成本费用利润率、营业利润率、销售毛利率的分母像销售收入、营业收入、主营业务收入净额、成本费用总额做了比较细致的说明&#xff0c;把这几个基本的概念搞明白后&#xff0c;再来看这几个指标就比较容易理解了。 销售净利…