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

单段时间最优S型速度规划算法

一,背景

在做机械臂轨迹规划的单段路径的速度规划时,除了参考《Trajectory Planning for Automatic Machines and Robots》等文献之外,还在知乎找到了这位大佬 @韩冰 写的在线规划方法:
https://zhuanlan.zhihu.com/p/585253101/editzhuanlan.zhihu.com/p/585253101/edit

从其输入条件和结果来看,在线规划的方法还是有一些精度、特殊条件情况的问题。

另外还有两个库,Reflexxes Motion Libraries 和 Ruckig ,它们都有开源的内容和商业化的内容。但是受限于我的开发平台,很难去调用、复现它们。

而以上文献中的离线规划方法限制性更大,只能实现起始结束条件速度加速度都为0的情况。

本文在以上文献中的离线规划方法的基础上,实现了更广泛的适用条件,并且在求解速度、最优解选择上进行了一定的优化。最终达到了以下效果:
1,可任意设置起始、结束状态的速度、加速度。可以等于或不为0、大于0、小于0、超出限制值等。
2,本文方法的主要思路还是和以上文献里的迭代搜索一样,设定在迭代终止条件匀速段时间小于1ms的情况下,各种测试用例的迭代次数最大12次,总的运算耗时少于20us。
3,迭代搜索实际上会有多个区间,各自有最优值。本文方法并没有求出所有解,但是根据公式特性尽可能的选择了一种最优解。

二,问题描述

可能对“速度规划”感兴趣的同学多少都了解这个问题的定义,但我在这里还是简单描述一下。

**已知:**一段路径有起点、终点(它无关坐标系、也无关路径形状轨迹),起点的位置值Ps,终点的位置值Pe。起点的速度Vs、加速度As,终点的速度Ve、加速度Ae,并限制整段路径的速度绝对值不超过Vlim,加速度绝对值不超过Alim,加加速度的绝对值不超过Jlim。

**求解:**时间最优的速度规划结果。

怎样理解这个“规划结果”呢?按照《Trajectory Planning … 》的内容,其求解结果应当包含7段位置关于时间的分段函数,一般情况如下图所示:
在这里插入图片描述

它由7段分段函数构成,如下:

加速区间(加加速段——匀加速段——减加速段)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

匀速区间 ( Ta <= t <= Ta + Tv )
在这里插入图片描述

减速区间(加减速段——匀减速段——减减速段)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
它可以理解为一系列的“时间-位置”插值点。在线规划方法的结果可以是一种计算方法,能够在已知当前点求出下一个周期时刻的位置点。而离线规划方法的结果也是一种计算方法/或数据,它能够用一系列的分段函数来表示位置关于时间的曲线函数。

再次强调下,
1,此类方法中速度规划的输入的位置是一段或多段路径的曲线距离,它无关坐标系、也无关路径形状轨迹。可以认为是一维的曲线距离关于时间的规划函数求解。
2,在插补运动中的应用场景可以参考后文——空间三维插补和速度规划。
3,而路径和速度规划的关系可以理解为:路径是上层确定的输入,在做速度规划时不会改变其路径;路径就像是铁轨,而速度规划则是控制铁轨上的火车时快时慢的沿着铁轨的运动。而无论火车跑得快或慢,都不影响铁轨的形状。

该问题的输入在应用上还有一点注意事项:路径和自由度的不同。
1,自由度可以理解为一个直线电机,它可以在导轨上任意的正向/负向运动,也就是说问题的输入Ps,Pe,Vs,Ve等可以是任意的正负值。
2,但是路径不同,路径可以认为是基于参数从0到100%的描述,这意味着其位置值是在路径的维度里相对0%点的曲线距离。
3,那么在应用上,路径的距离不能为负数,因为从0%往-100%的路径是不确定的,所以负的距离也失去了意义。
4,在大多数应用上,速度也最好没有负数,因为负数的速度意味着运动点将在中间一段路径上折返多次,这对于大多数应用是有害的。

三,梯形速度规划

首先我们简化问题,引入梯形速度规划,也可称为三段式。与如上问题的条件一样,但是不对加加速度进行限制,那么意味着允许加速度可以限制值内任意的快速变化。此部分可以直接参考教科书的3.2节。书中一般的规划结果如下图:
在这里插入图片描述

在此引入3段式速度规划,是因为它与7段式的问题有很多相似相关的地方,三段式的求解方式大致是:
1,假定规划的最大速度是否能达到速度限制值,若能达到则可根据理想公式(梯形模型)求解。
2,若不能达到则加速段的结尾速度应该等于减速段的起始速度,即最大速度,可根据三角形模型求解。

**相似性:**可以认为这个最大速度是一个未知数,计算出其他所需的物理量,得到代数方程,再求解这个方程即可。区别只是梯形规划的方程很简单,而S型规划的方程较复杂,但是整个流程是可以借鉴的。

**相关性:**S型规划可以分为3大段(加速段-匀速段-减速段),而其中的加速大段或减速大段每段里面又是3小段,在“速度——加速度——加加速度”层面的“加速度增大——加速度恒定——加速度减小”的过程,类似于三段式“位置——速度——加速度”层面的“速度增大——速度恒定——速度减小”的对应关系。计算公式也很类似。

三段式求解
按照以上求解方式,计算流程中主要存在分支[1]:按梯形模型计算 或者 按三角形模型计算。实际应用上还要考虑分支[2]:梯形/三角的开口方向是向上的还是向下的。开口方向并不是显然的根据距离的正负而决定的,例如以下两个例子:

例1:PosEnd - PosStart = 10.0; 结果如下,蓝色曲线为位置关于时间的变化,绿色曲线为速度关于时间的变化。
在这里插入图片描述
在这里插入图片描述

例2:PosEnd - PosStart = 5.0; 其他参数和例1完全相同,结果如下:
在这里插入图片描述

可以看到例1例2的距离都是正数,但是速度曲线的三角型开口一个朝上一个朝下。

我们可以用以下这种方式来判别速度梯形/三角形的开口方向:
1,先只做速度关于时间的规划,假设以最大加速度从起始速度规划到结束速度(速度曲线是一条直线,斜率为最大加速度),可以计算出此规划的运动距离。
2,若假设的运动距离 < 结束位置-起始位置,则说明还存在速度为正的区间,而为了时间最优,这个正速度应当尽量的大,即应当等于速度三角型模型的尖角速度或梯形模型的平台速度,同时也说明梯形/三角模型的开口应该朝下。
3,若假设的运动距离 > 结束位置-起始位置,则说明还存在速度为负的区间,而为了时间最优,这个负速度绝对值应当尽量的大,即应当等于速度三角型模型的尖角速度或梯形模型的平台速度,同时也说明梯形/三角模型的开口应该朝上。

整体解算过程如下:
1,首先判断1:若 VelStart > VelEnd,则预设加速度 Acc’ 为 -LimAcc,且;否则预设加速度 Acc’ 为 LimAcc。
2,然后只做从 VelStart 到 VelEnd 的匀变速规划,根据以下公式 1 计算预设的运动距离 Dist:
D i s t = V e l E n d 2 − V e l S t a r t 2 2 A c c (1) Dist = \frac {{VelEnd}^2-{VelStart}^2} {2{Acc}} \tag {1} Dist=2AccVelEnd2VelStart2(1)
在这里插入图片描述

3,判断2-1:若 Dist <= PosEnd - PosStart,则梯形/三角模型开口朝下,接下来判断是梯形模型还是三角模型.假设是梯形模型,根据以下公式 2 代入 Acc’ = -LimAcc, Vel’ = LimVel 计算匀速段耗时:
t 2 = P o s E n d − P o s S t a r t V e l ′ − V e l ′ 2 − V e l E n d 2 2 V e l ′ ∗ A c c ′ − V e l ′ 2 − V e l S t a r t 2 2 V e l ′ ∗ A c c ′ (2) t2 = \frac {PosEnd-PosStart} {Vel'} - \frac {{Vel'}^2 - VelEnd^2} {2 Vel' * Acc'} -\frac {{Vel'}^2 - VelStart^2} {2 Vel' * Acc'} \tag {2} t2=VelPosEndPosStart2VelAccVel2VelEnd22VelAccVel2VelStart2(2)
在这里插入图片描述

4,判断3-1:若 t2 >=0.0 ,则确定模型就是梯形模型。若 t2 < 0.0,则确定模型为三角模型。需要使用以下公式 3 进一步计算三角型的尖峰速度:
M a x V e l = A c c ′ ( P o s S t a r t − P o s E n d ) + 1 2 ( V e l E n d 2 + V e l S t a r t 2 ) (3) MaxVel = \sqrt {Acc' {(PosStart-PosEnd) }+ \frac{1}{2} ({{VelEnd}}^2+{{VelStart}}^2)} \tag {3} MaxVel=Acc(PosStartPosEnd)+21(VelEnd2+VelStart2) (3)
在这里插入图片描述

5,经过以上分支的计算,轨迹的其他信息都可以简单的求得。

6,判断2-2:若 Dist > PosEnd - PosStart,则梯形/三角模型开口朝上,接下来判断是梯形模型还是三角模型.假设是梯形模型,根据以上公式 2 代入 Acc’ = LimAcc, Vel’ = -LimVel 计算匀速段耗时。

7,判断3-2:在判断2-2的基础上,若 t2 >=0.0 ,则确定模型就是梯形模型。若 t2 < 0.0,则确定模型为三角模型。需要使用以上公式 3 进一步计算三角型的尖峰速度:-MaxVel。

这样就完成了梯形速度规划的求解。

四,S型速度规划编辑此区域

匀速段距离关于速度的函数
以上相似性已经分析过,两者的求解流程相似,可以认为匀速段的速度是一个未知数,计算出其他所需的物理量,得到代数方程,再求解这个方程即可。
1,同样的也是先假设匀速段速度能达到±LimVel,若能达到,则可以直接计算出规划结果,并且匀速段的速度尽可能的大,所以整体耗时会更小。
2,若无法达到则是三角型模型,匀速段的耗时接近于0,且当匀速段的速度尽可能大的时候,整体耗时会更小。所以需要求解以上这个方程,方程的自变量是假设的匀速段速度,需要求解当匀速段耗时=0时绝对值最大的解。
3,而考虑到匀速段速度定义域包含0,时间 = 距离 / 速度,那么匀速段耗时关于匀速段速度的函数将不连续,更一般的情况,建立匀速段距离关于匀速段速度的函数 Sc = F(Vc)。

加速区间/减速区间
以上相关性已经分析过,梯形速度规划的三段式,类似于S型速度规划里的加速区间/减速区间,加速区间/减速区间可以称为三段式梯形加速度规划。其计算方式几乎完全一样。只用将梯形速度规划里的时间,位置,速度,加速度,替换为自变量,因变量,一阶导,二阶导这样一个更通用的数学形式;然后再在S型速度规划的加速区间/减速区间里用物理量再替换回来,即替换回:时间,速度,加速度,加加速度。而通过以上 Sc = F(Vc) 的定义,Vc变成了自变量或者说已知量,所以可以直接使用梯形速度规划的计算方式来求解梯形加速度规划,进一步积分计算得规划中的各位置量。

即,假设已知匀速段速度Vc,可计算得唯一的Sacc和Sdec表示加速区间和减速区间的距离增量。这样也就求出了Sc = PosEnd - PosStart - Sacc(Vc) - Sdec(Vc) = F(Vc)。

而除了位置积分之外不再需要额外的计算公式,位置积分公式如下:
P ( t ) = P 0 + V 0 t + A 0 t 2 2 + J t 3 6 (4) P(t)=P_{0}+V_{0} t + \frac {A_{0} {t}^2} {2} + \frac {J {t}^3} {6} \tag {4} P(t)=P0+V0t+2A0t2+6Jt3(4)
在这里插入图片描述

典型函数图像
一般而言,以上F(Vc) = 0的求解存在多解的情况。尤其是直接在 [-LimVel , LimVel] 搜索可能并不能得到更优的解。

为了优化求解过程,并且进一步理解 Sc = F(Vc) 函数,可以观察一下函数图像。

例1,输入参数:
在这里插入图片描述
Sc = F(Vc) 函数图像结果:
在这里插入图片描述
上图蓝线为Vc,绿线为Sc,两尖角处Vc为 -47.75 和 125。这两个Vc点也是该组数据计算时的分段函数区间分界点,Vc = -47.75 对应“加速区间”计算时的判断2-1和2-2的临界点,即“加速区间”的梯形或三角形模型是开口朝上还是朝下;而 Vc = 125 对应“减速区间”计算时的判断2-1和2-2的临界点,即“减速区间”的梯形或三角形模型是开口朝上还是朝下.

例2,输入数据:
在这里插入图片描述
Sc = F(Vc) 函数图像结果:
在这里插入图片描述
上图蓝线为Vc,绿线为Sc,两尖角处Vc为 95.416667 和 141.666667,也是该组输入数据的“加速区间”和“减速区间”开口朝上朝下的临界点。放大细节如下:
在这里插入图片描述
红线为匀速段的拟定耗时,正数为有效的结果。

例3,输入数据:
在这里插入图片描述
Sc = F(Vc) 函数图像结果:
在这里插入图片描述
上图蓝线为Vc,绿线为Sc,两尖角处Vc为 73.333333 和 -85.666667,也是该组输入数据的“加速区间”和“减速区间”开口朝上朝下的临界点。放大细节如下:
在这里插入图片描述
通过观察以上典型图像,对搜索的区间进行了优化,在一般情况下可以提高计算效率(缩小了搜索范围), 且得到更优时间的解。具体的原理/证明等并不严格,具体实现参考下文“示例代码——三 搜索范围的确定”。

五,数值方法求解

基于以上内容已求得函数 Sc = F(Vc) = PosEnd - PosStart - Sacc(Vc) - Sdec(Vc)。其中Sacc(Vc)和Sdec(Vc)都是4段分段函数组合成的连续函数,其中涉及 根号Vc 的表达式。

则 F(Vc) 也是由分段函数组合成的连续函数,根据Sacc(Vc)和Sdec(Vc)的分段组合,它有4x4=16种具体的组合形式,且在一具体函数种同时最多存在7种组合,其中也包含根号Vc的复杂表达式。直接求解其解析解是较为困难的,结合《Trajectory Planning … 》的内容,可以使用以下这样一种数值计算方法:
1,首先设 Vc1 = LimVel,若求得 Sc1 >= 0 则直接得到了时间最优解,否则下一步;
2,设Vc2 = -LimVel,若求得 Sc2 <= 0 则直接得到了时间最优解,否则下一步;
3,由于函数 F(Vc) 在定义域 [-LimVel , LimVel] 内连续,且存在 F(LimVel) < 0 和 F(-LimVel ) >0,根据中值定理,在区间 [-LimVel , LimVel] 内必定存在一个Vc使得 F(Vc) = 0,也就是要求解的结果。
4,在区间内的求解,最起码的可以采用二分法进行迭代搜索。还可以采用其他经典数值计算的求根方法,加快求解效率。

割线法
割线法,又称弦割法、弦法,是基于牛顿法的一种改进,基本思想是用弦的斜率近似代替目标函数的切线斜率,并用割线与横轴交点的横坐标作为方程式的根的近似。适用于原函数不方便求导的情况。

割线方程:给定函数 f(x) 上两点 x[n-1] , y[n-1] 和 x[n] , y[n],这两点所在的直线即为割线,方程为:

y - y[n] = (y[n] - y[n-1]) / (x[n] - x[n-1]) * (x - x[n]);

割线法即通过以上两点 x[n-1] 和 x[n] 及其割线方程,求得割线与X轴的交点横坐标作为 x[n+1],求得此点的函数值 y[n+1],再代入 x[n] 和 x[n+1] 不断迭代,直至精度满足需求。

搜索优化
牛顿法存在不一定收敛的情况,除开函数本身可能无解,迭代搜索过程中的计算也可能导致不收敛。牛顿下山法是对牛顿法的一种改进,它确保每一步迭代的范围都是缩小的。

在以上割线法中也有类似不收敛的问题,也可以采用类似下山法的思路进行优化,具体实现查看附件代码里的方法 M_Iteration。

其他方法
还有很多其他求根求最值的数值方法。例如 [Boost.Math](Chapter 10. Root Finding & Minimization Algorithms - 1.85.0 (boost.org)) 里的 TOMS Algorithm 748 算法,Brent-Dekker algorithm 布伦特方法,Muller Method 米勒法等等。

六,示例代码

代码见附件 Code_VelPlanS7Single.pdf 文档。代码中的Class_VelPlanS7_Single为规划处理,VP_S7_Time2Position为按时间插值位置,Test为两个单元测试例程。
一 初始化
代码中为 M_Init 方法内容,进行初始化参数、范围判定等。注意以下内容:

1,支持设置 StartAcc, EndAcc。需要在 [-LimAcc , LimAcc] 内。也可以超出,超出要另外处理。2,temp1、temp3、temp4、temp6、temp8、temp9 等是后续的迭代计算中的一些不变量, 为了减少每次迭代计算量而做的化简。3,VpeakStart、VpeakEnd 也是由已知条件得到的常量。它们的意义在于,若已知7段式的匀速段速度V,则可以计算出7段式所有的信息,记计算出的匀速段距离为S2,加速段距离为S1,减速段路径为S3. 即把问题理解为一个函数 :(S1,S2,S3) = function( Vmax )。也对应代码中的 M_V2S 方法。而这个函数是基于两个小函数 S1 = F(Vmax) 和 S3 = G(Vmax)。各自有4种情况,而VpeakStart、VpeakEnd就是这各自4种情况中的2类的分界点,即以上“加速区间”/“减速区间”梯形或三角性模型开口朝上/开口朝下的临界点。

二 极限速度判断
7段式时间最优速度规划的思路是,使得匀速段的速度尽可能的大。所以一开始先假设能达到正负极限速度,看能不能满足条件( 匀速段的时间 > 0)。若满足条件,则直接得到最优结果。若不满足条件,则需要在 ( -LimVel , LimVel ) 之间搜索。由于函数的连续性,在此区间内一定有解。
在这里插入图片描述

三 搜索范围的确定
代码 line 18 ~ 108,搜索范围这里根据函数的特性,采用了一定方式进行处理,较快较优的得到一个更合适的范围。

背景是这样一个分段函数 Sc = F(Vc) 函数图像如上图。然后要找函数的零点,且在多个零点中找 V值绝对值更大的。上图只是示意,实际横纵坐标轴可能根据输入条件平移。该方法并没有找出所有的零点,而是根据函数的性质,将所有区间分为3部分 [ -LimVel, Vpeak1 ] , [ Vpeak1 , Vpeak2 ] , [ Vpeak2 , LimVel ]。 先去看看例如Vpeak2~LimVel 里面有没有根。没有就去看看Vpeak1~-LimVel。都没有那就在 [ Vpeak1 , Vpeak2 ] 里面。

也可以直接在 ( -LimVel , LimVel )之间搜索,但数据测试来看以上方法效果更好。

四 迭代搜索
方法 M_Iteration 内为迭代搜索。还是以上说明的函数:(S1,S2,S3) = function( Vmax )

可以认为是单输入单输出的,输入Vmax,输出S2。求使得S2=0的解即是最优的结果。

函数本身是连续的,且搜索的上下界一正一负,所以各种搜索方法应当都可以搜索得到一个解。

这里采用割线法,且排除掉了割线范围变大的情况。结果效果比较好,迭代收敛速度快。

五 按时间插值位置
VP_S7_Time2Position为按时间插值位置的函数,即输入时间,输出位置等。即7段式的7段不同函数。

注意看下规划的结果、此函数的输入:
在这里插入图片描述
规划结果基本包含了所有信息,以减少插值函数的计算量。包括各段的时间 t1~t7, 加加速度 jerk 、加速度、速度、位置。

七,后文

所有解
以上数值方法求解中由提到,“…分段组合,它有4x4=16种具体的组合形式,且在一具体函数种同时最多存在7种组合,其中也包含根号Vc的复杂表达式…”。针对具体的16种组合的函数表达式,求出所有的解,再判断解的可行性/时间最优,也是一种规划求解方式。

“背景”中介绍的 Ruckig 基本采用这种思路,它根据Jerk的正负零的情况以及是否达到LimVel/LimAcc/LimDec的情况,将所有的类型分为32种,依次计算每一种,并提到其中最复杂的单一种类可变为6次多项式的求根。它在求出所有类型的解后,用每个解中的每一小段时间是否大于0等条件进行可行性判断,并再在所有可行解中选择时间最优的解。

实际上可以很容易的排除 ±LimVel 的情况,将总数量总32种减少为16种,并且在其中一些涉及根式的计算中,不再设定关于匀速段速度Vc的方程,而是设定关于最大加速度/减速度的方程,可以转为更低次的方程求解。

这种求出所有解的方式能够得到真正的时间最优解,但是在计算时间上并不一定有优势。在一些需要定时规划的应用场景中,可能会更有优势。

连续多段速度规划
单段式速度规划是连续多段速度规划的基础,连续多段速度规划也是轨迹规划中需要解决的问题之一。一般来说都需要采用速度前瞻的处理方式进行规划。

一种连续多段轨迹的速度规划算法原理为:
1,在算法的初始阶段设第一段的起始速度=0,最后一段的结束速度=0;假设相邻段的衔接速度可以不为0,而衔接加速度=0。
2,在每一段内,按照从起始速度V0加速到结束速度V1,计算能够加速到的最大V1。最大V1的目标值 TargetV[i] = min( LimVel[i] , LimVel[i+1] )。
3,每一段内的加速曲线仍然是3段式的(梯形加速度曲线),只用判断梯形模型/三角模型的临界情况,即可算得实际能加速的最大V1.
4,若 TargetV[i] < TargetV[i+1] ,则用以上的加速计算,确定结束速度V[i+1];若 TargetV[i] >= TargetV[i+1],则不用计算,暂时设V[i+1] = TargetV[i+1].
5,按i从小到大的顺序,即正向依次算得所有V[i]。
6,再按照以上类似的方式,按i从大到小的顺序,即反向依次再算一遍V[i],该过程中 TargetV[i] 为正向计算得到的 V[i]。这样在正向计算中“减速模式”未精确计算的V[i]都得到了计算。所有的V[i]都正向/反向可达且为最大值。
7,这样正向计算+反向计算后,就确定了所有衔接点的速度,且设定加速度=0,这样每一段进行单段式求解的条件就都具备了,按单段式依次求解即可。

若连续多段速度规划不是预先已知所有段的轨迹,而是通过缓存队列的方式动态加载的(有称为DMA功能),则还需要进一步修改算法,并考虑实时插补的运动耗时与前瞻规划处理的计算耗时之间的同步影响。

空间三维插补和速度规划
以上做的都是速度规划,它已知的是一段轨迹/路径的距离,而不用考虑轨迹/路径的形状。如何将速度规划与空间中轨迹规划联系起来呢?

首先通过一个简单的例子,空间中的三维直线路径的插补,来展示它们之间的协作。

空间中的三维直线路径可以有很多种方程表示,在此可以用参数方程的方式表示为:
在这里插入图片描述
u是参数从0到1变化,其他的ab为系数,由起点终点计算得到。

xyz(t)的空间三维关于时间的插值问题可以转为u(t)参数关于时间的插值问题,也就是速度规划问题。直线插补的合成最大速度/最大加速度为输入参数,直线距离确定。可以转到参数u,即u的值从0到1,并且等效计算u的最大速度/最大加速度等,这样就可以进行 u(t) 的速度规划了。然后在实时插补中,每一个插补时刻t对应速度规划结果u(t)的一个u值,再通过以上路径的参数方程求出x/y/z,即得到了 xyz(t) 的空间直线插值结果。

其他路径的计算流程也是差不多的,例如空间圆弧路径,通过静态的坐标转换可以表示为某平面内的圆弧,进一步表示为xyz关于圆心角theta的参数方程。
在这里插入图片描述
在这里插入图片描述

然后也是如上流程:
1,从路径得到整体的路径参数方程,及路径长度,速度限制,加速度限制等;
2,按路径长度进行速度规划,得到路径距离Dist(t);
3,按照速度规划结果进行实时插值,并由Dist计算得到参数值u;
4,再由参数值计算路径上的坐标值。
这套方法实际上是把空间多维路径归一化到共同的一个参数。它适用于任意空间三维位置路径的插补。一些复杂路径需要额外做一些工作,包括:1 计算复杂路径的欧式空间积分距离;2 在实时速度插值时,已知路径上的距离Dist(t),求参数u。

多维度时间同步速度规划
空间中的三维位置(x,y,z)所构成的路径,可以变为一维的曲线距离,按照一维进行速度规划。但是有时涉及多个独立维度的时间同步速度规划,这时需要处理的一个基本问题是:两组/多组速度规划曲线如何时间同步,或者说确定时间的速度规划。

一般来说,可以通过以上“所有解”的方式,确定所有可行的区间,从Sc=F(Vc)来说,它是自变量Vc的定义域区间,同时也是按Vc求得的整段耗时t 的值域区间。多个自由度的可行耗时 t 区间如下图所示:
在这里插入图片描述
若是多个自由度需要进行时间同步,则如上图所示按照可行区间的边界值从小到大进行判定,是否在所有可行区间内,灰色为不可行的实时间区间,最终得到粗黑线T3a为这三个自由度的最小可同步时间。这样来确定最小的同步时间

若是单个自由度需要进行确定时间的规划,则先看设定的耗时是否在可行区间内,不在则没有解,若在则可以在区间内通过数值迭代的方式求解,或通过确定的(以上"所有解"中16种组合之一)解析式直接求解。

以上只是单段的多自由度时间同步,在解决以上这些基础问题后,还要考虑的是连续多段的多维度同步规划如何让整体的时间更优,这才是真正的难点。

相关文章:

单段时间最优S型速度规划算法

一&#xff0c;背景 在做机械臂轨迹规划的单段路径的速度规划时&#xff0c;除了参考《Trajectory Planning for Automatic Machines and Robots》等文献之外&#xff0c;还在知乎找到了这位大佬 韩冰 写的在线规划方法&#xff1a; https://zhuanlan.zhihu.com/p/585253101/e…...

pom文件-微服务项目结构

一、微服务项目结构 my-microservices-project/ ├── pom.xml <!-- 父模块的pom.xml --> ├── ry-system/ │ ├── pom.xml <!-- 子模块ry-system的pom.xml --> │ └── src/main/java/com/example/rysystem/ │ └── RySystemApplication.…...

解析Kotlin中的Nothing【笔记摘要】

1.Nothing的本质 Nothing 的源码很简单&#xff1a; public class Nothing private constructor()可以看到它是个class&#xff0c;但它的构造函数是 private 的&#xff0c;这就导致我们没法创建它的实例&#xff0c;并且在源码里 Kotlin 也没有帮我们创建它的实例。 基于这…...

toRefs 和 toRef

文章目录 toRefs 和 toReftoRefstoRef toRefs 和 toRef toRefs toRefs 把一个由reactive对象的值变为一个一个ref的响应式的值 import { ref, reactive, toRefs, toRef } from vue; let person reactive({name: 张三,age: 18, }); // toRefs 把一个由reactive对象的值变为一…...

Vision Transformer论文阅读笔记

目录 An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale -- Vision Transformer摘要Introduction—简介RELATED WORK—相关工作METHOD—方法VISION TRANSFORMER (VIT)—视觉Transformer(ViT) 分析与评估PRE-TRAINING DATA REQUIREMENTS—预训练数据…...

MapReduce的执行流程排序

MapReduce 是一种用于处理大规模数据集的分布式计算模型。它将作业分成多个阶段&#xff0c;以并行处理和分布式存储的方式来提高计算效率。以下是 MapReduce 的执行流程以及各个阶段的详细解释&#xff1a; 1. 作业提交&#xff08;Job Submission&#xff09; 用户通过客户端…...

雅思词汇及发音积累 2024.7.3

银行 check &#xff08;美&#xff09;支票 cheque /tʃek/ &#xff08;英&#xff09;支票 ATM 自动取款机 cashier 收银员 teller /ˈtelə(r)/ &#xff08;银行&#xff09;出纳员 loan 贷款 draw/withdraw money 提款 pin number/passsword/code …...

Vue2和Vue3的区别Vue3的组合式API

一、Vue2和Vue3的区别 1、创建方式的不同&#xff1a; &#xff08;1&#xff09;、vue2:是一个构造函数&#xff0c;通过该构造函数创建一个Vue实例 new Vue({})&#xff08;2&#xff09;、Vue3:是一个对象。并通过该对象的createApp()方法&#xff0c;创建一个vue实例。 Vue…...

ML307R OpenCPU HTTP使用

一、函数介绍 二、示例代码 三、代码下载地址 一、函数介绍 具体函数可以参考cm_http.h文件,这里给出几个我用到的函数 1、创建客户端实例 /*** @brief 创建客户端实例** @param [in] url 服务器地址(服务器地址url需要填写完整,例如(服务器url仅为格式示…...

【状态估计】线性高斯系统的状态估计——离散时间的递归滤波

前两篇文章介绍了离散时间的批量估计、离散时间的递归平滑&#xff0c;本文着重介绍离散时间的递归滤波。 前两篇位置&#xff1a;【状态估计】线性高斯系统的状态估计——离散时间的批量估计、【状态估计】线性高斯系统的状态估计——离散时间的递归平滑。 离散时间的递归滤波…...

架构设计上中的master三种架构,单节点,主从节点,多节点分析

文章目录 背景单节点优点缺点 主从节点优点缺点 多节点优点缺点 多节点&#xff0c;多backup设计优点缺点 总结 背景 在很多分布式系统里会有master,work这种结构。 master 节点负责管理资源&#xff0c;分发任务。下面着重讨论下master 数量不同带来的影响 单节点 优点 1.设…...

如何在 SQL 中删除一条记录?

如何在 SQL 中删除一条记录&#xff1f; 在 SQL 中&#xff0c;您可以使用DELETE查询和WHERE子句删除表中的一条记录。在本文中&#xff0c;我将向您介绍如何使用DELETE查询和WHERE子句删除记录。我还将向您展示如何一次从表中删除多条记录 如何在 SQL 中使用 DELETE 这是使…...

JavaSE (Java基础):面向对象(上)

8 面向对象 面向对象编程的本质就是&#xff1a;以类的方法组织代码&#xff0c;以对象的组织&#xff08;封装&#xff09;数据。 8.1 方法的回顾 package com.oop.demo01;// Demo01 类 public class Demo01 {// main方法public static void main(String[] args) {int c 10…...

flink使用StatementSet降低资源浪费

背景 项目中有很多ods层&#xff08;mysql 通过cannal&#xff09;kafka&#xff0c;需要对这些ods kakfa做一些etl操作后写入下一层的kafka&#xff08;dwd层&#xff09;。 一开始采用的是executeSql方式来执行每个ods→dwd层操作&#xff0c;即类似&#xff1a; def main(…...

FineDataLink4.1.9支持Kettle调用

FDL更新至4.1.9后&#xff0c;新增kettle调用功能&#xff0c;支持不增加额外负担的情况下&#xff0c;将现有的Kettle任务平滑迁移到FineDataLink。 一、更新版本前存在的问题与痛点 在此次功能更新前&#xff0c;用户可能会遇到以下问题&#xff1a; 1.对于仅使用kettle的…...

SwanLinkOS首批实现与HarmonyOS NEXT互联互通,软通动力子公司鸿湖万联助力鸿蒙生态统一互联

在刚刚落下帷幕的华为开发者大会2024上&#xff0c;伴随全场景智能操作系统HarmonyOS Next的盛大发布&#xff0c;作为基于OpenHarmony的同根同源系统生态&#xff0c;软通动力子公司鸿湖万联全域智能操作系统SwanLinkOS首批实现与HarmonyOS NEXT互联互通&#xff0c;率先攻克基…...

Win11禁止右键菜单折叠的方法

背景 在使用windows11的时候&#xff0c;会发现默认情况下&#xff0c;右键菜单折叠了。以至于在使用一些软件的右键菜单时总是要点击“显示更多选项”菜单展开所有菜单&#xff0c;然后再点击。而且每次在显示菜单时先是全部展示&#xff0c;再隐藏一下&#xff0c;看着着实难…...

Maven列出所有的依赖树

在 IntelliJ IDEA 中&#xff0c;你可以使用 Maven 插件来列出项目的依赖树。Maven 插件提供了一个名为dependency:tree的目标&#xff0c;可以帮助你获取项目的依赖树详细信息。 要列出项目的依赖树&#xff0c;可以执行以下步骤&#xff1a; 打开 IntelliJ IDEA&#xff0c;…...

测试开发面试题和答案

Python 请解释Python中的列表推导式&#xff08;List Comprehension&#xff09;是什么&#xff0c;并给出一个示例。 答案&#xff1a; 列表推导式是Python中一种简洁的构建列表的方法。它允许从一个已存在的列表创建新列表&#xff0c;同时应用一个表达式来修改或选择元素。…...

llm学习-3(向量数据库的使用)

1&#xff1a;数据读取和加载 接着上面的常规操作 加载环境变量---》获取所有路径---》加载文档---》切分文档 代码如下&#xff1a; import os from dotenv import load_dotenv, find_dotenvload_dotenv(find_dotenv()) # 获取folder_path下所有文件路径&#xff0c;储存在…...

【01-02】Mybatis的配置文件与基于XML的使用

1、引入日志 在这里我们引入SLF4J的日志门面&#xff0c;使用logback的具体日志实现&#xff1b;引入相关依赖&#xff1a; <!--日志的依赖--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version&g…...

Linux-进程间通信(IPC)

进程间通信&#xff08;IPC&#xff09;介绍 进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09;是指在不同的进程之间传播或交换信息。IPC 的方式包括管道&#xff08;无名管道和命名管道&#xff09;、消息队列、信号量、共享内存、Socket、Stre…...

C++ STL: std::vector与std::array的深入对比

什么是 std::vector 和 std::array 首先&#xff0c;让我们简要介绍一下这两种容器&#xff1a; • std::vector&#xff1a;一个动态数组&#xff0c;可以根据需要动态调整其大小。 • std::array&#xff1a;一个固定大小的数组&#xff0c;其大小在编译时确定。 虽然…...

哈哈看到这条消息感觉就像是打开了窗户

在这个信息爆炸的时代&#xff0c;每一条动态可能成为我们情绪的小小触发器。今天&#xff0c;当我无意间滑过那条由杜海涛亲自发布的“自曝式”消息时&#xff0c;不禁心头一颤——如果这是我的另一半&#xff0c;哎呀&#xff0c;那画面&#xff0c;简直比烧烤摊还要“热辣”…...

10、matlab中字符、数字、矩阵、字符串和元胞合并为字符串并将字符串以不同格式写入读出excel

1、前言 在 MATLAB 中&#xff0c;可以使用不同的数据类型&#xff08;字符、数字、矩阵、字符串和元胞&#xff09;合并为字符串&#xff0c;然后将字符串以不同格式写入 Excel 文件。 以下是一个示例代码&#xff0c;展示如何将不同数据类型合并为字符串&#xff0c;并以不…...

如何正确面对GPT-5技术突破

随着人工智能技术的快速发展&#xff0c;预训练语言模型在自然语言处理领域取得了显著的成果。其中&#xff0c;GPT系列模型作为代表之一&#xff0c;受到了广泛关注。2023年&#xff0c;GPT-5模型的发布引起了业界的热烈讨论。本文将从以下几个方面分析GPT-5的发布及其对人工智…...

HarmonyOS ArkUi 官网踩坑:单独隐藏导航条无效

环境&#xff1a; 手机&#xff1a;Mate 60 Next版本&#xff1a; NEXT.0.0.26 导航条介绍 导航条官网设计指南 setSpecificSystemBarEnabled 设置实际效果&#xff1a; navigationIndicator&#xff1a;隐藏导航条无效status&#xff1a;会把导航条和状态栏都隐藏 官方…...

解决跨域问题(vite、axios/koa)

两种方法选其一即可 一、后端koa设置中间件 app.use(async (ctx, next)> {ctx.set(Access-Control-Allow-Origin, *);ctx.set(Access-Control-Allow-Headers, Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild);ctx.set(Access-C…...

echarts实现3D柱状图(视觉层面)

一、第一种效果 效果图 使用步骤 完整实例&#xff0c;copy就可直接使用 <template><div :class"className" :style"{height:height,width:width}" /> </template><script>import echarts from echartsrequire(echarts/theme/…...

K8S集群进行分布式负载测试

使用K8S集群执行分布式负载测试 本教程介绍如何使用Kubernetes部署分布式负载测试框架&#xff0c;该框架使用分布式部署的locust 产生压测流量&#xff0c;对一个部署到 K8S集群的 Web 应用执行负载测试&#xff0c;该 Web 应用公开了 REST 格式的端点&#xff0c;以响应传入…...

20.《C语言》——【移位操作符】

&#x1f339;开场语 亲爱的读者&#xff0c;大家好&#xff01;我是一名正在学习编程的高校生。在这个博客里&#xff0c;我将和大家一起探讨编程技巧、分享实用工具&#xff0c;并交流学习心得。希望通过我的博客&#xff0c;你能学到有用的知识&#xff0c;提高自己的技能&a…...

你想活出怎样的人生?

hi~好久不见&#xff0c;距离上次发文隔了有段时间了&#xff0c;这段时间&#xff0c;我是裸辞去感受了一下前端市场的水深火热&#xff0c;那么这次咱们不聊技术&#xff0c;就说一说最近这段时间的经历和一些感触吧。 先说一下自己的个人情况&#xff0c;目前做前端四年&am…...

py黑帽子学习笔记_burp

配置burp kali虚机默认装好了社区版burp和java&#xff0c;其他os需要手动装 burp是用java&#xff0c;还得下载一个jython包&#xff0c;供burp用 配apt国内源&#xff0c;然后apt install jython --download-only&#xff0c;会只下载包而不安装&#xff0c;下载的目录搜一…...

selenium,在元素块下查找条件元素

def get_norms_ele_text(self):elementsself.get_norms_elements()locBy.CSS_SELECTOR,"div.sku-select-row-label"by loc[0] # 获取By类型&#xff0c;例如By.CSS_SELECTORvalue loc[1] # 获取具体的CSS选择器字符串&#xff0c;例如"div.sku-select-row-l…...

认识String类

文章目录 String类字符串的遍历字符串的比较字符串的替换字符串的转换字符串的切割字符串的切片字符串的查找 总结 String类 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&#xff0c;可以使用标准库提 供的字符串系列函数完…...

计算机图形学入门23:蒙特卡洛路径追踪

1.前言 前面几篇文章介绍了Whitted-style光线追踪&#xff0c;还介绍了基于物理渲染的基础知识&#xff0c;包括辐射度量学、BRDF以及渲染方程&#xff0c;但并没有给出解渲染方程的方法&#xff0c;或者说如何通过该渲染方程计算出屏幕上每一个坐标的像素值。 Whitted-style光…...

探索 TensorFlow 模型的秘密:TensorBoard 详解与实战

简介 TensorBoard 是 TensorFlow 提供的可视化工具&#xff0c;帮助开发者监控和调试机器学习模型。它提供了多种功能&#xff0c;包括查看损失和精度曲线、可视化计算图、检查数据分布等。下面将介绍如何使用 TensorBoard。 1. 安装 TensorBoard 如果尚未安装 TensorBoard&…...

yolov8obb角度预测原理解析

预测头 ultralytics/nn/modules/head.py class OBB(Detect):"""YOLOv8 OBB detection head for detection with rotation models."""def __init__(self, nc80, ne1, ch()):"""Initialize OBB with number of classes nc and la…...

CICD之Git版本管理及基本应用

CICD:持续集成,持续交付--让对应的资料,对应的项目流程更加规范--提高效率 CICD 有很多的工具 GIT就是其中之一 1.版本控制概念与环境搭建 GIT的概念: Git是一款分布式源代码管理工具(版本控制工具) ,一个协同的工具。 Git得其数据更像是一系列微型文件系统的快照。使用Git&am…...

Python作用域及其应用

Python的作用域规则决定了变量在代码中的可见性和访问性。全局作用域中定义的变量可以在整个程序中访问&#xff0c;而局部作用域中定义的变量则只能在其被创建的函数或代码块中访问。 全局作用域与局部作用域 全局作用域中的变量通常在程序的顶层定义&#xff0c;可以被整个…...

谷歌上架,应用被Google play下架之后,活跃用户会暴跌?这是为什么?

在Google play上架应用&#xff0c;开发者们最不想到看到就是应用被下架了。这意味着所有的努力都将付诸东流&#xff0c;因为有的应用一但被下架&#xff0c;活跃用户也随之嗖嗖地往下掉&#xff0c;这事儿可真不是闹着玩的&#xff0c;严重影响了收益&#xff01; 为什么你的…...

web安全渗透测试十大常规项(一):web渗透测试之Fastjson反序列化

渗透测试之Java反序列化 1. Fastjson反序列化1.1 FastJson反序列化链知识点1.2 FastJson反序列化链分析1.3.1 FastJson 1.2.24 利用链分析1.3.2 FastJson 1.2.25-1.2.47 CC链分析1.3.2.1、开启autoTypeSupport:1.2.25-1.2.411.3.2.2 fastjson-1.2.42 版本绕过1.3.2.3 fastjson…...

Unity 3D软件下载安装;Unity 3D游戏制作软件资源包获取!

Unity3D&#xff0c;它凭借强大的功能和灵活的特性&#xff0c;在游戏开发和互动内容创作领域发挥着举足轻重的作用。 作为一款顶尖的游戏引擎&#xff0c;Unity3D内置了先进的物理引擎——PhysX。这一物理引擎堪称业界翘楚&#xff0c;能够为开发者提供全方位、高精度的物理模…...

PyTorch之nn.Module与nn.functional用法区别

文章目录 1. nn.Module2. nn.functional2.1 基本用法2.2 常用函数 3. nn.Module 与 nn.functional3.1 主要区别3.2 具体样例&#xff1a;nn.ReLU() 与 F.relu() 参考资料 1. nn.Module 在PyTorch中&#xff0c;nn.Module 类扮演着核心角色&#xff0c;它是构建任何自定义神经网…...

2024.06.24 校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、校招 | 昂瑞微2025届校园招聘正式启动 校招 | 昂瑞微2025届校园招聘正式启动 2、实习 | 东风公司研发总院暑期实习生火爆招募中 实习 | 东风公司研发总院暑期实习生火爆招募中 3、实习…...

【C++】using namespace std 到底什么意思

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 的学习笔记&#xff0c;引用了部分大佬的案例 &#x1f4e2;未来很长&a…...

基于ESP32 IDF的WebServer实现以及OTA固件升级实现记录(三)

经过前面两篇的前序铺垫&#xff0c;对webserver以及restful api架构有了大体了解后本篇描述下最终的ota实现的代码以及调试中遇到的诡异bug。 eps32的实际ota实现过程其实esp32官方都已经基本实现好了&#xff0c;我们要做到无非就是把要升级的固件搬运到对应ota flash分区里面…...

116-基于5VLX110T FPGA FMC接口功能验证6U CPCI平台

一、板卡概述 本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡。由一片Xilinx公司的XC5VLX110T-1FF1136 / XC5VSX95T-1FF1136 / XC5VFX70T-1FF1136芯片组成。FPGA接1片DDR2内存条 2GB&#xff0c;32MB Nor flash存储器&#xff0c;用于存储程序。外扩 SATA、PCI、PCI expres…...

Android - Json/Gson

Json数据解析 json对象&#xff1a;花括号开头和结尾&#xff0c;中间是键值对形式————”属性”:属性值”” json数组&#xff1a;中括号里放置 json 数组&#xff0c;里面是多个json对象或者数字等 JSONObject 利用 JSONObject 解析 1.创建 JSONObject 对象&#xff0c;传…...

盲信号处理的发展现状

盲源分离技术最早在上个世纪中期提出&#xff0c;在1991年Herault和Jutten提出基于反馈神经网络的盲源分离方法&#xff0c;但该方法缺乏理论基础&#xff0c;后来Tong和Liu分析了盲源分离问题的可辨识性和不确定性&#xff0c;Cardoso于1993年提出了基于高阶统计的联合对角化盲…...

Linux环境下快速部署Spring Boot应用:高效命令组合实践

概要&#xff1a; 本文旨在介绍一种高效的Linux命令组合&#xff0c;用于简化Spring Boot项目的部署与管理流程。通过结合使用nohup、java -jar、输出重定向以及进程管理命令&#xff0c;我们能够实现Spring Boot应用的快速后台启动及便捷的进程控制&#xff0c;尤其适合于自动…...

Linux shell编程学习笔记62: top命令 linux下的任务管理器

0 前言 top命令是Unix 和 Linux下常用的性能分析工具&#xff0c;提供了一个动态的、交互式的实时视图&#xff0c;显示系统的整体性能信息&#xff0c;以及正在运行的进程的相关信息&#xff0c;包括各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。 1 top命令…...

昇思25天学习打卡营第17天|ChatGLM-6B聊天demo

一、简介&#xff1a; 本次实验&#xff0c;基于MindNLP和ChatGLM6B模型搭建一个小的聊天应用&#xff0c;ChatGLM6B 是基于 GLM-4 模型开发的开源对话机器人&#xff0c;拥有 62 亿个参数&#xff0c;能够进行自然流畅的语言交流。在对话中&#xff0c;ChatGLM6B 可以胜任文案…...

Android - 模拟器

Android SDK 包括一个在您的计算机上运行的虚拟移动设备模拟器。 该模拟器可让您在不使用物理设备的情况下对 Android 应用程序进行原型设计、开发和测试。 在本章中&#xff0c;我们将探索真实安卓设备中存在的模拟器中的不同功能。 创建 AVD 如果您想模拟真实设备&#xff0c…...

java spring boot 单/多文件上传/下载

文章目录 使用版本文件上传服务端客户端&#xff08;前端&#xff09;方式一方式二 文件下载服务端客户端&#xff08;前端&#xff09; 代码仓库地址 使用版本 后端 spring-boot 3.3.0jdk17 前端 vue “^3.3.11”vite “^5.0.8”axios “^1.7.2” 文件上传 上传文件比较…...

Django + Vue 实现图片上传功能的全流程配置与详细操作指南

文章目录 前言图片上传步骤1. urls 配置2. settings 配置3. models 配置4. 安装Pillow 前言 在现代Web应用中&#xff0c;图片上传是一个常见且重要的功能。Django作为强大的Python Web框架&#xff0c;结合Vue.js这样的现代前端框架&#xff0c;能够高效地实现这一功能。本文将…...

秦L/海豹06的到来,扯下了合资燃油车最后一块遮羞布

从事汽车行业的小伙伴们肯定都知道,现在的新车价格很不稳定。即便是强如奔驰、宝马、奥迪这样的实力派传统豪华品牌,面对着市场环境的变化,中国品牌的崛起,在价格上一步一步下探,跌到了谷底中的谷底。可以预见的是,过去合资燃油车,靠品牌影响力,靠品质取胜的年代已经一…...

Java基础学习:深入解析Java中的位运算符

在Java中&#xff0c;位运算符用于对整数类型的值进行位运算。以下是Java中的位运算符&#xff1a; 位与(&)&#xff1a;两位都为1时&#xff0c;结果为1&#xff0c;否则为0。 位或(|)&#xff1a;两位中有1个为1&#xff0c;结果为1。 位非(~)&#xff1a;位的反&#…...

狠刹蜻蜓点水式调研虚风

新华社北京5月28日电 《关于在全党大兴调查研究的工作方案》印发以来,不少地方和部门在深化调查研究上务实功、出实招、求实效,用调研推动问题解决。然而在实际中,对调研安排很“上心”、对调研过程极“专注”、把“身入”当“深入”、把“过程”当“解决”的蜻蜓点水式调研…...

transformer的特点

Transformers是一种用于处理序列数据的神经网络架构&#xff0c;最初由Vaswani等人在2017年提出&#xff0c;主要用于自然语言处理任务。与传统的循环神经网络&#xff08;RNN&#xff09;和卷积神经网络&#xff08;CNN&#xff09;不同&#xff0c;Transformers采用了一种全新…...

JAVA系列:NIO

NIO学习 一、前言 先来看一下NIO的工作流程图&#xff1a; NIO三大核心组件&#xff0c;channel&#xff08;通道&#xff09;、Buffer&#xff08;缓冲区&#xff09;、selector&#xff08;选择器&#xff09;。NIO利用的是多路复用模型&#xff0c;一个线程处理多个IO的读…...

在Spring中自定义事件及发布与监听

在Spring框架中&#xff0c;自定义事件及其发布与监听是一个涉及Spring事件机制的过程。Spring提供了一个基于观察者模式的事件发布和监听机制&#xff0c;允许在Spring容器中的组件之间进行松耦合的通信。以下是如何自定义事件以及如何发布和监听这些事件的步骤&#xff1a; …...