【路径规划】全局路径规划算法——A*算法(含python实现 | c++实现)

news2024/9/22 0:58:56

文章目录

  • 参考资料
  • 1. 算法简介
  • 2. 算法精讲
    • 2.1 预处理
    • 2.2 开始搜索
    • 2.3 继续搜索
    • 2.4 确定实际路径
  • 3. 算法总结
    • 3.1 算法步骤
    • 3.2 伪代码
  • 4. python实现
  • 5. c++实现

参考资料

  • Introduction to the A* Algorithm
  • 路径规划与轨迹跟踪系列算法
  • Robotic Motion Planning Lectures
  • A星算法详解
  • A星寻路算法
  • 路径规划之 A* 算法

1. 算法简介

  • A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法,广泛应用于室内机器人路径搜索、游戏动画路径搜索等。它是图搜索算法的一种。

  • A*算法是一种启发式的搜索算法,它是基于深度优先算法(Depth First Search, DFS)和广度优先算法(Breadth First Search, BFS)的一种融合算法,按照一定原则确定如何选取下一个结点。



启发式搜索算法指的是,从起点出发,先寻找起点相邻的栅格,判断它是否是最好的位置,基于这个最好的栅格再往外向其相邻的栅格扩展,找到一个此时最好的位置,通过这样一步一步逼近目标点,减少盲目的搜索,提高了可行性和搜索效率。

深度优先搜索算法的思想是,搜索算法从起点开始进行搜索(初始状态下待搜索区域内所有结点均未被访问),与周围所有邻点进行比较,选择其中距离终点最近的点进行存储,然后再以该邻点为基础对比其周围未被访问的所有邻点,仍然选择距离终点最近的邻点存储。若访问结点到达终点或访问完所有结点仍未到达终点,则视为搜索失败。成功搜索所存储的结点连接而成的路径即为起点到终点的路径。

广度优先搜索的原理是,从初始点出发依次访问与它相连接的邻点,访问完毕后再从这些邻点出发访问邻点的邻点,但是要保证先被访问的邻点的邻点要比后被访问的邻点的邻点先访问,直至图中所有已被访问的结点的邻点都被访问到。如果此时图中尚有未被访问的结点,则需要选取一个尚未被访问的结点作为个新的初始点,继续搜索访问,直到图中所有的结点都被访问一遍为止。

因此深度优先算法与广度优先搜索算法从过程上存在较大差异。深度优先算法优先选择离目标点最近的结点,而广度优先搜索算法优先选择离初始点最近的点。基于深度优先算法,能以最快的速度找到一条连接初始点到目标点的路径,但不能保证路径的最优性(例如以路径最短为标准);广度优先搜索算法则必然能找到最短的路径,但由于需要遍历所有的结点,其计算复杂程度更大。基于这两种算法的优缺点,A*算法基于启发函数构建了代价函数,既考虑了新结点距离初始点的代价,又考虑了新结点与目标点距离的代价。



  • A*算法使用一个路径优劣评价公式为:
    f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)

    • f(n) 是从初始状态经由状态n到目标状态的代价估计,
    • g(n) 是从初始状态状态n的实际代价,
    • h(n) 是从状态n目标状态的最佳路径的估计代价。
  • A*算法需要维护两个状态表,分别称为openList表和closeList表。openList表由待考察的节点组成, closeList表由已经考察过的节点组成。

2. 算法精讲

2.1 预处理

如图,假设我们需要从A点到目标点,这两点之间有一堵墙。

首先,我们把地图栅格化,把每一个方格的中心称为节点;这个特殊的方法把我们的搜索区域简化为了 2 维数组。数组的每一项代表一个格子,它的状态就是可走 (walkalbe) 和不可走 (unwalkable) 。通过计算出从 A 到 目标点需要走过哪些方格,就找到了路径。一旦路径找到了,便从一个方格的中心移动到另一个方格的中心,直至到达目的地。

2.2 开始搜索

一旦我们把搜寻区域简化为一组可以量化的节点后,我们下一步要做的便是查找最短路径。在 A* 中,我们从起点开始,检查其相邻的方格,然后向四周扩展,直至找到目标。

  • 从起点 A 开始,定义A为父节点,并把它加入到 openList中。现在 openList 里只有起点 A ,后面会慢慢加入更多的项。
  • 如上图所示,父节点A周围共有8个节点,定义为子节点。将子节点中可达的(reachable)或者可走的(walkable)放入openList中,成为待考察对象。
  • 若某个节点既未在openList,也没在closeList中,则表明还未搜索到该节点。
  • 初始时, 节点A离自身距离为0,路径完全确定,将其移入closeList中; closeList 中的每个方格都是现在不需要再关注的。
  • 路径优劣判断依据是移动代价,单步移动代价采取Manhattan 计算方式(即 d = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d=|x_1-x_2|+|y_1-y_2| d=x1x2+y1y2),即把横向和纵向移动一个节点的代价定义为10。斜向移动代价为14(即 2 \sqrt2 2 倍的横向或纵向移动代价)。
  • 现在openList = {B,C,D,E,F,G,H,I}, closeList = {A}

下面我们需要去选择节点A相邻的子节点中移动代价 f f f最小的节点,下面以节点I的计算为例。

  • 移动代价评价函数为: f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n) f ( n ) f(n) f(n) 是从初始状态经由状态n到目标状态的代价估计, g ( n ) g(n) g(n) 是在状态空间中从初始状态到状态n的实际代价, h ( n ) h(n) h(n) 是从状态n到目标状态的最佳路径的估计代价。
  • 首先考察 g g g,由于从A到该格子是斜向移动,单步移动距离为14,故 g g g = 14.
  • 再考察估计代价 h h h。估计的含义是指忽略剩下的路径是否包含有障碍物(不可走), 完全按照Manhattan计算方式,计算只做横向或纵向移动的累积代价:横向向右移动3步,纵向向上移动1步,总共4步,故为 h h h = 40.
  • 因此从A节点移动至I节点的总移动代价为: f = g + h = 54 f=g+h=54 f=g+h=54
  • 以此类推,分别计算当前openList中余下的7个子节点的移动代价 f f f,从中挑选最小代价节点F,移到closeList中。
  • 现在openList = {B,C,D,E,G,H,I}, closeList = {A,F}

2.3 继续搜索

  • 从openList中选择 f f f值最小的 ( 方格 ) 节点I(D节点的 f f f值跟I相同,任选其一即可,不过从速度上考虑,选择最后加入 openList 的方格更快。这导致了在寻路过程中,当靠近目标时,优先使用新找到的方格的偏好。 对相同数据的不同对待,只会导致两种版本的 A ∗ A^* A 找到等长的不同路径 ),从 openList里取出,放到 closeList 中。
  • 检查所有与它相邻的子节点,忽略不可走 (unwalkable) 的节点、以及忽略已经存在于closeList的节点;如果方格不在openList中,则把它们加入到 openList中,并把它们作为节点I子节点
  • 如果某个相邻的节点(假设为X)已经在 opeLlist 中,则检查这条路径是否更优,也就是说经由当前节点( 我们选中的节点)到达节点X是否具有更小的 g g g 值。如果没有,不做任何操作。否则,如果 g g g 值更小,则把X的父节点设为当前方格 ,然后重新计算X f f f 值和 g g g 值。
  • 判断完所有子节点后,现在openList = {B,C,D,E,G,H,J,K,L}, closeList = {A,F,I}
  • 依次类推,不断重复。一旦搜索到目标节点T,完成路径搜索,结束算法。

2.4 确定实际路径

完成路径搜索后,从终点开始,向父节点移动,这样就被带回到了起点,这就是搜索后的路径。如下图所示。从起点 A 移动到终点 T就是简单从路径上的一个方格的中心移动到另一个方格的中心,直至目标。

3. 算法总结

3.1 算法步骤

  1. 首先把起点加入 openList 。

  2. 重复如下过程:

    • 遍历 openList ,查找 F 值最小的节点,把它作为当前要处理的节点。

    • 把这个节点移到 closeList 。

    • 对当前方格的 8 个相邻方格:

      ◆ 如果它是不可抵达的或者它在 closeList 中,忽略它;

      ◆ 如果它不在 openList 中,则把它加入 openList ,并且把当前方格设置为它的父节点,记录该方格的 f , g , h f,g,h f,g,h值。

      ◆ 如果它已经在 openList 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 g g g 值作参考,更小的 g g g 值表示这是更好的路径。如果 g g g值更小,把该节点的父节点设置为当前方格,并重新计算它的 g g g h h h 值。

  3. 停止搜索的情况有两种:

    • 把终点加入到了openList 中,此时路径已经找到了

    • 查找终点失败,并且 openList 是空的,此时没有路径。

  4. 保存路径。使用回溯的方法,从终点开始,每个方格沿着父节点移动直至起点,这就是最终搜索到的路径。

3.2 伪代码

初始化open_list和close_list;
* 将起点加入open_list中,并设其移动代价为0* 如果open_list不为空,则从open_list中选取移动代价最小的节点n:
    * 如果节点n为终点,则:
        * 从终点开始逐步追踪parent节点,一直达到起点;
        * 返回找到的结果路径,算法结束;
    * 如果节点n不是终点,则:
        * 将节点n从open_list中移除,并加入close_list中;
        * 遍历节点n所有的邻近节点:
            * 如果邻近节点m在close_list中,则:
                * 跳过,选取下一个邻近节点
            * 如果邻近节点m在open_list中,则:
            		* 计算起点经n到m的g值
           		* 如果此时计算出来的g值小于原来起点经m的原父节点到m的g值:
           				* 更新m的父节点为n,重新计算m的移动代价f=g+h
          			* 否则,跳过
            * 如果邻近节点m不在open_list也不在close_list中,则:
                * 设置节点m的parent为节点n
                * 计算节点m的移动代价f=g+h
                * 将节点m加入open_list中

4. python实现

代码实现来自PythonRobotics。

"""

A* grid planning

author: Atsushi Sakai(@Atsushi_twi)
        Nikos Kanargias (nkana@tee.gr)

See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm)

"""

import math

import matplotlib.pyplot as plt

show_animation = True


class AStarPlanner:

    def __init__(self, ox, oy, resolution, rr):
        """
        Initialize grid map for a star planning

        ox: x position list of Obstacles [m]
        oy: y position list of Obstacles [m]
        resolution: grid resolution [m],地图的像素
        rr: robot radius[m]
        """

        self.resolution = resolution
        self.rr = rr
        self.min_x, self.min_y = 0, 0
        self.max_x, self.max_y = 0, 0
        self.obstacle_map = None
        self.x_width, self.y_width = 0, 0
        self.motion = self.get_motion_model()
        self.calc_obstacle_map(ox, oy)

    class Node:
        """定义搜索区域节点类,每个Node都包含坐标x和y, 移动代价cost和父节点索引。
        """
        def __init__(self, x, y, cost, parent_index):
            self.x = x  # index of grid
            self.y = y  # index of grid
            self.cost = cost
            self.parent_index = parent_index

        def __str__(self):
            return str(self.x) + "," + str(self.y) + "," + str(
                self.cost) + "," + str(self.parent_index)

    def planning(self, sx, sy, gx, gy):
        """
        A star path search
        输入起始点和目标点的坐标(sx,sy)和(gx,gy),
        最终输出的结果是路径包含的点的坐标集合rx和ry。
        input:
            s_x: start x position [m]
            s_y: start y position [m]
            gx: goal x position [m]
            gy: goal y position [m]

        output:
            rx: x position list of the final path
            ry: y position list of the final path
        """

        start_node = self.Node(self.calc_xy_index(sx, self.min_x),
                               self.calc_xy_index(sy, self.min_y), 0.0, -1)
        goal_node = self.Node(self.calc_xy_index(gx, self.min_x),
                              self.calc_xy_index(gy, self.min_y), 0.0, -1)

        open_set, closed_set = dict(), dict()
        open_set[self.calc_grid_index(start_node)] = start_node

        while 1:
            if len(open_set) == 0:
                print("Open set is empty..")
                break

            c_id = min(
                open_set,
                key=lambda o: open_set[o].cost + self.calc_heuristic(goal_node, open_set[o]))
            current = open_set[c_id]

            # show graph
            if show_animation:  # pragma: no cover
                plt.plot(self.calc_grid_position(current.x, self.min_x),
                         self.calc_grid_position(current.y, self.min_y), "xc")
                # for stopping simulation with the esc key.
                plt.gcf().canvas.mpl_connect('key_release_event', lambda event: [exit(0) if event.key == 'escape' else None])
                if len(closed_set.keys()) % 10 == 0:
                    plt.pause(0.001)

            # 通过追踪当前位置current.x和current.y来动态展示路径寻找
            if current.x == goal_node.x and current.y == goal_node.y:
                print("Find goal")
                goal_node.parent_index = current.parent_index
                goal_node.cost = current.cost
                break

            # Remove the item from the open set
            del open_set[c_id]

            # Add it to the closed set
            closed_set[c_id] = current

            # expand_grid search grid based on motion model
            for i, _ in enumerate(self.motion):
                node = self.Node(current.x + self.motion[i][0],
                                 current.y + self.motion[i][1],
                                 current.cost + self.motion[i][2], c_id)
                n_id = self.calc_grid_index(node)

                # If the node is not safe, do nothing
                if not self.verify_node(node):
                    continue

                if n_id in closed_set:
                    continue

                if n_id not in open_set:
                    open_set[n_id] = node  # discovered a new node
                else:
                    if open_set[n_id].cost > node.cost:
                        # This path is the best until now. record it
                        open_set[n_id] = node

        rx, ry = self.calc_final_path(goal_node, closed_set)

        return rx, ry

    def calc_final_path(self, goal_node, closed_set):
        # generate final course
        rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [
            self.calc_grid_position(goal_node.y, self.min_y)]
        parent_index = goal_node.parent_index
        while parent_index != -1:
            n = closed_set[parent_index]
            rx.append(self.calc_grid_position(n.x, self.min_x))
            ry.append(self.calc_grid_position(n.y, self.min_y))
            parent_index = n.parent_index

        return rx, ry

    @staticmethod
    def calc_heuristic(n1, n2):
        """计算启发函数

        Args:
            n1 (_type_): _description_
            n2 (_type_): _description_

        Returns:
            _type_: _description_
        """
        w = 1.0  # weight of heuristic
        d = w * math.hypot(n1.x - n2.x, n1.y - n2.y)
        return d

    def calc_grid_position(self, index, min_position):
        """
        calc grid position

        :param index:
        :param min_position:
        :return:
        """
        pos = index * self.resolution + min_position
        return pos

    def calc_xy_index(self, position, min_pos):
        return round((position - min_pos) / self.resolution)

    def calc_grid_index(self, node):
        return (node.y - self.min_y) * self.x_width + (node.x - self.min_x)

    def verify_node(self, node):
        px = self.calc_grid_position(node.x, self.min_x)
        py = self.calc_grid_position(node.y, self.min_y)

        if px < self.min_x:
            return False
        elif py < self.min_y:
            return False
        elif px >= self.max_x:
            return False
        elif py >= self.max_y:
            return False

        # collision check
        if self.obstacle_map[node.x][node.y]:
            return False

        return True

    def calc_obstacle_map(self, ox, oy):

        self.min_x = round(min(ox))
        self.min_y = round(min(oy))
        self.max_x = round(max(ox))
        self.max_y = round(max(oy))
        print("min_x:", self.min_x)
        print("min_y:", self.min_y)
        print("max_x:", self.max_x)
        print("max_y:", self.max_y)

        self.x_width = round((self.max_x - self.min_x) / self.resolution)
        self.y_width = round((self.max_y - self.min_y) / self.resolution)
        print("x_width:", self.x_width)
        print("y_width:", self.y_width)

        # obstacle map generation
        self.obstacle_map = [[False for _ in range(self.y_width)]
                             for _ in range(self.x_width)]
        for ix in range(self.x_width):
            x = self.calc_grid_position(ix, self.min_x)
            for iy in range(self.y_width):
                y = self.calc_grid_position(iy, self.min_y)
                for iox, ioy in zip(ox, oy):
                    d = math.hypot(iox - x, ioy - y)
                    if d <= self.rr:
                        self.obstacle_map[ix][iy] = True
                        break

    @staticmethod
    def get_motion_model():
        # dx, dy, cost
        motion = [[1, 0, 1],
                  [0, 1, 1],
                  [-1, 0, 1],
                  [0, -1, 1],
                  [-1, -1, math.sqrt(2)],
                  [-1, 1, math.sqrt(2)],
                  [1, -1, math.sqrt(2)],
                  [1, 1, math.sqrt(2)]]

        return motion


def main():
    print(__file__ + " start!!")

    # start and goal position
    sx = 10.0  # [m]
    sy = 10.0  # [m]
    gx = 50.0  # [m]
    gy = 50.0  # [m]
    grid_size = 2.0  # [m]
    robot_radius = 1.0  # [m]

    # set obstacle positions
    ox, oy = [], []
    for i in range(-10, 60):
        ox.append(i)
        oy.append(-10.0)
    for i in range(-10, 60):
        ox.append(60.0)
        oy.append(i)
    for i in range(-10, 61):
        ox.append(i)
        oy.append(60.0)
    for i in range(-10, 61):
        ox.append(-10.0)
        oy.append(i)
    for i in range(-10, 40):
        ox.append(20.0)
        oy.append(i)
    for i in range(0, 40):
        ox.append(40.0)
        oy.append(60.0 - i)

    if show_animation:  # pragma: no cover
        plt.plot(ox, oy, ".k")
        plt.plot(sx, sy, "og")
        plt.plot(gx, gy, "xb")
        plt.grid(True)
        plt.axis("equal")

    a_star = AStarPlanner(ox, oy, grid_size, robot_radius)
    rx, ry = a_star.planning(sx, sy, gx, gy)

    if show_animation:  # pragma: no cover
        plt.plot(rx, ry, "-r")
        plt.pause(0.001)
        plt.show()


if __name__ == '__main__':
    main()

运行结果如下:
在这里插入图片描述

5. c++实现

由于在自动驾驶中算法实现一般使用C++,所以我也使用C++实现了相关功能,代码结构与python代码实现类似,这边就不再做相关代码解释了。完整代码详见另一个github仓库。

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

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

相关文章

【附下载】3Ds Max从安装、配置到入门提高和高级用法

#3Ds Max 一、安装 1.1 安装说明 地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1lwKMbgbE32wCL6PpMv706A?pwddll8 提取码&#xff1a;dll8 –来自百度网盘超级会员V2的分享 安装说明&#xff1a;文件夹里有安装说明 安装解压即可 关键就是将crack文件放到自己…

高架学习笔记之信息安全基础

目录 一、信息安全基础 1.1. 概念 1.2. 信息存储安全 1.3. 网络安全 二、信息安全系统的组成框架 三、信息加解密技术和数字签名 四、访问控制技术 五、信息安全的保障体系与评估方法 5.1. 计算机信息系统安全保护等级 5.2. 安全风险管理 一、信息安全基础 1.1. 概念 …

DEYOv2: Rank Feature with Greedy Matchingfor End-to-End Object Detection

摘要 与前代类似&#xff0c; DEYOv2 采用渐进式推理方法 来加速模型训练并提高性能。该研究深入探讨了一对一匹配在优化器中的局限性&#xff0c;并提出了有效解决该问题的解决方案&#xff0c;如Rank 特征和贪婪匹配 。这种方法使DEYOv2的第三阶段能够最大限度地从第一和第二…

工控MCGS触摸屏Hacking勒索部署

https://github.com/MartinxMax/Mo0n_V1.2 !!不关注点赞收藏,以后没好东西了奥!! 端口扫描-获取信息 $python Mo0n.py -scan x.x.x.0/24 or $nmap -sS -Pn -T4 x.x.x.0/24 -p 127 MCGS编程软件 发现触摸屏受到密码保护 Oops&#xff01;&#xff01;&#xff01; echo /\_…

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Circle)

用于绘制圆形的组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Circle(options?: {width?: string | number, height?: string | number}) 从API version 9开始&…

Flutter-底部弹出框(Widget层级)

需求 支持底部弹出对话框。支持手势滑动关闭。支持在widget中嵌入引用。支持底部弹出框弹出后不影响其他操作。支持弹出框中内容固定头部和下面列表时&#xff0c;支持触摸头部并在列表不在头部的时候支持滑动关闭 简述 通过上面的需求可知&#xff0c;就是在界面中可以支持…

Smart Light Random Memory Sprays Retinex 传统图像增强 SLRMSR

文章目录 前言1、Smart Light Random Memory Sprays Retinex概况2、Smart Light Random Memory Sprays Retinex的实现2.1、SLRMSR算法的伪代码2.2、初始化记忆喷雾&#xff08;CreateInitialMemorySpray&#xff09;2.3、更新记忆喷雾 (UpdateMemorySpray)2.4、计算颜色校正因子…

到底什么时候该使用MongoDB

NoSQL是什么 NoSQL &#xff1a; Not Only SQL , 本质也是一种数据库的技术&#xff0c;相对于传统数据库技术&#xff0c;它不会遵循一些约束&#xff0c;比如 &#xff1a; sql 标准、 ACID 属性&#xff0c;表结构等。 NoSQL分类 类型应用场景典型产品Key-value存储缓存&…

【Excel自动化办公】使用openpyxl对Excel进行读写操作

目录 一、环境安装 1.1 创建python项目 1.2 安装openpyxl依赖 二、Excel数据读取操作 三、Excel数据写入操作 3.1 创建空白工作簿 3.2 写数据 四、设置单元格样式 4.1 字体样式 4.2 设置单元格背景填充色 4.3 设置单元格边框样式 4.4 单元格对齐方式 4.5 数据筛选…

体系化全面认识 Nginx !

高并发、高性能&#xff1b;模块化架构使得它的扩展性非常好&#xff1b;异步非阻塞的事件驱动模型这点和 Node.js 相似&#xff1b;相对于其它服务器来说它可以连续几个月甚至更长而不需要重启服务器使得它具有高可靠性&#xff1b;热部署、平滑升级&#xff1b;完全开源&…

代码随想录算法训练营第46天 | 完全背包,139.单词拆分

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 完全背包理论基础&#xff1a; https://programmercarl.com/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E7%90%86%E8%AE%BA%E5%9…

数字化社会的新纪元:揭秘 Web3 的社交网络

随着区块链技术的不断发展和普及&#xff0c;Web3作为其重要组成部分&#xff0c;正逐渐改变着社交网络的面貌。Web3的出现不仅为社交网络带来了新的技术和理念&#xff0c;还为用户提供了更加开放、透明和安全的社交体验。本文将深入探讨Web3的社交网络&#xff0c;揭示其在数…

计算机网络:性能指标

计算机网络&#xff1a;性能指标 速率带宽吞吐量时延时延带宽积往返时间利用率丢包率 本博客介绍计算机网络的性能指标&#xff0c;我们可以从不同的方面来度量计算机网络的性能。常用的计算机网络性能指标有以下 8 个&#xff0c;他们是&#xff1a;速率、带宽、吞吐量、时延、…

47.全排列II

// 定义一个Solution类&#xff0c;用于解决给定不重复整数数组的全排列问题 class Solution {// 初始化结果集&#xff0c;用于存放所有不重复的全排列组合List<List<Integer>> result new ArrayList<>();// 初始化路径变量&#xff0c;用于暂存当前递归生…

ESP32实现(MQTT Client)连接物联网平台(EMQX)

目录 概述 1 配置EMQX服务器 1.1 搭建EMQX服务器 1.2 配置服务器参数 2 ESP32实现MQTT Client 2.1 创建MQTT Client项目 2.2 实现MQTT Client 2.3 ESP32连接EMQX 3 ESP32Client实现广播和订阅消息 3.1 广播消息 3.1.1 编写广播消息函数 3.1.2 下载和验证 3.1.3 订阅…

Windows11安装Msql8.0版本详细安装步骤!

文章目录 前言一、下载Mysql二、安装Mysql三、登录验证三、环境变量配置总结 前言 每次搭建新环境的时候&#xff0c;都需要网上搜寻安装的步骤教程&#xff01;为了以后方便查阅&#xff01;那么本次就记录一下Windows11安装Msql8.0的详细步骤&#xff01;也希望能帮助到有需…

蓝桥杯物联网竞赛_STM32L071_12_按键中断与串口中断

按键中断&#xff1a; 将按键配置成GPIO_EXTI中断即外部中断 模式有三种上升沿&#xff0c;下降沿&#xff0c;上升沿和下降沿都会中断 external -> 外部的 interrupt -> 打断 trigger -> 触发 detection -> 探测 NVIC中将中断线ENABLE 找接口函数 在接口函数中写…

Apache Doris 2.1 核心特性 Variant 数据类型技术深度解析

在最新发布的 Apache Doris 2.1 新版本中&#xff0c;我们引入了全新的数据类型 Variant&#xff0c;对半结构化数据分析能力进行了全面增强。无需提前在表结构中定义具体的列&#xff0c;彻底改变了 Doris 过去基于 String、JSONB 等行存类型的存储和查询方式。为了让大家快速…

redis-黑马点评-商户查询缓存

缓存&#xff1a;cache public Result queryById(Long id) {//根据id在redis中查询数据String s redisTemplate.opsForValue().get(CACHE_SHOP_KEY id);//判断是否存在if (!StrUtil.isBlank(s)) {//将字符串转为bean//存在&#xff0c;直接返回Shop shop JSONUtil.toBean(s, …

Linux课程四课---Linux第一个小程序(进度条)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…