Robot Operating System——AsyncParametersClient监控Parameters的增删改行为

news2024/9/20 22:51:06

大纲

  • 同步
    • 创建SyncParametersClient
    • 设置监控回调
    • 回调函数主体
    • 测试
    • 完整代码
  • 异步
    • 创建AsyncParametersClient
    • 设置监控回调
    • 测试
    • 完整代码

在《Robot Operating System——Parameter设置的预处理、校验和成功回调》一文中,我们使用Node::add_post_set_parameters_callback设置一个回调函数,用于在Parameters设置成功后,执行相关后续动作,但是这种行为发生在Node内部。

如果我们希望在Node外部监控Parameters变动,则可以使用SyncParametersClient或者AsyncParametersClient类的on_parameter_event方法设置一个回调函数来监控。

我们分别以同步和异步的方式讲解这个功能。

同步

我们参考的例子是demo_nodes_cpp/src/parameters/parameter_events.cpp。
首先我们创建一个待监控的Node

int main(int argc, char ** argv)
{
  // Force flush of the stdout buffer.
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);

  rclcpp::init(argc, argv);

  auto node = rclcpp::Node::make_shared("parameter_events");

创建SyncParametersClient

然后创建一个该Node的连接客户端,并和服务端进行连接。

  auto parameters_client = std::make_shared<rclcpp::SyncParametersClient>(node);
  while (!parameters_client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    RCLCPP_INFO(node->get_logger(), "service not available, waiting again...");
  }

设置监控回调

我们通过SyncParametersClient::on_parameter_event设置监控回调函数。它的入参是是一个rcl_interfaces::msg::ParameterEvent指针,用于表达Parameter变动的事件。

events_received_promise 是一个std::promise,我们通过它让回调函数和主程序之间有通信。如下面代码,当回调执行10次后,promise会被设置以通知主程序退出执行。

  auto events_received_promise = std::make_shared<std::promise<void>>();
  auto events_received_future = events_received_promise->get_future();

  // Setup callback for changes to parameters.
  auto sub = parameters_client->on_parameter_event(
    [node, promise = std::move(events_received_promise)](
      rcl_interfaces::msg::ParameterEvent::UniquePtr event) -> void
    {
      static size_t n_times_called = 0u;
      if (on_parameter_event(std::move(event), node->get_logger())) {
        ++n_times_called;
      }
      if (10u == n_times_called) {
        // This callback will be called 10 times, set the promise when that happens.
        promise->set_value();
      }
    });

回调函数主体

event中包含三种数据:

  • new_parameters。记录新增的Parameters。
  • changed_parameters。记录修改的Parameters。
  • deleted_parameters。记录删除的Parameters。
// /opt/ros/jazzy/include/rcl_interfaces/rcl_interfaces/msg/detail/parameter_event__struct.hpp
  using _new_parameters_type =
    std::vector<rcl_interfaces::msg::Parameter_<ContainerAllocator>, typename std::allocator_traits<ContainerAllocator>::template rebind_alloc<rcl_interfaces::msg::Parameter_<ContainerAllocator>>>;
  _new_parameters_type new_parameters;
  using _changed_parameters_type =
    std::vector<rcl_interfaces::msg::Parameter_<ContainerAllocator>, typename std::allocator_traits<ContainerAllocator>::template rebind_alloc<rcl_interfaces::msg::Parameter_<ContainerAllocator>>>;
  _changed_parameters_type changed_parameters;
  using _deleted_parameters_type =
    std::vector<rcl_interfaces::msg::Parameter_<ContainerAllocator>, typename std::allocator_traits<ContainerAllocator>::template rebind_alloc<rcl_interfaces::msg::Parameter_<ContainerAllocator>>>;
  _deleted_parameters_type deleted_parameters;

在监控函数中,我们会过滤掉新增Parameters中的有关qos_overrides前缀的项目。然后分门别类的把上述三种Parameters打印出来。

bool on_parameter_event(
  rcl_interfaces::msg::ParameterEvent::UniquePtr event, rclcpp::Logger logger)
{
  // TODO(wjwwood): The message should have an operator<<, which would replace all of this.
  std::stringstream ss;
  // ignore qos overrides
  event->new_parameters.erase(
    std::remove_if(
      event->new_parameters.begin(),
      event->new_parameters.end(),
      [](const auto & item) {
        const char * param_override_prefix = "qos_overrides.";
        return std::strncmp(
          item.name.c_str(), param_override_prefix, sizeof(param_override_prefix) - 1) == 0u;
      }),
    event->new_parameters.end());
  if (
    !event->new_parameters.size() && !event->changed_parameters.size() &&
    !event->deleted_parameters.size())
  {
    return false;
  }
  ss << "\nParameter event:\n new parameters:";
  for (auto & new_parameter : event->new_parameters) {
    ss << "\n  " << new_parameter.name;
  }
  ss << "\n changed parameters:";
  for (auto & changed_parameter : event->changed_parameters) {
    ss << "\n  " << changed_parameter.name;
  }
  ss << "\n deleted parameters:";
  for (auto & deleted_parameter : event->deleted_parameters) {
    ss << "\n  " << deleted_parameter.name;
  }
  ss << "\n";
  RCLCPP_INFO(logger, "%s", ss.str().c_str());
  return true;
}

测试

  // Declare parameters that may be set on this node
  node->declare_parameter("foo", 0);
  node->declare_parameter("bar", "");
  node->declare_parameter("baz", 0.);
  node->declare_parameter("foobar", false);

  // Set several different types of parameters.
  auto set_parameters_results = parameters_client->set_parameters(
  {
    rclcpp::Parameter("foo", 2),
    rclcpp::Parameter("bar", "hello"),
    rclcpp::Parameter("baz", 1.45),
    rclcpp::Parameter("foobar", true),
  });

  // Change the value of some of them.
  set_parameters_results = parameters_client->set_parameters(
  {
    rclcpp::Parameter("foo", 3),
    rclcpp::Parameter("bar", "world"),
  });

  // TODO(wjwwood): Create and use delete_parameter

  rclcpp::spin_until_future_complete(node, events_received_future.share());
  rclcpp::shutdown();

  return 0;
}

上面的代码的declare_parameter和set_parameters都会导致监控被执行。这些操作一共10次,这样就会导致回调函数中Promise被设置,进而让rclcpp::spin_until_future_complete退出等待,从而关闭整个程序。
在这里插入图片描述

完整代码

// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <chrono>
#include <cstring>
#include <future>
#include <memory>
#include <sstream>
#include <utility>

#include "rcl_interfaces/msg/parameter_event.hpp"
#include "rclcpp/rclcpp.hpp"

using namespace std::chrono_literals;

bool on_parameter_event(
  rcl_interfaces::msg::ParameterEvent::UniquePtr event, rclcpp::Logger logger)
{
  // TODO(wjwwood): The message should have an operator<<, which would replace all of this.
  std::stringstream ss;
  // ignore qos overrides
  event->new_parameters.erase(
    std::remove_if(
      event->new_parameters.begin(),
      event->new_parameters.end(),
      [](const auto & item) {
        const char * param_override_prefix = "qos_overrides.";
        return std::strncmp(
          item.name.c_str(), param_override_prefix, sizeof(param_override_prefix) - 1) == 0u;
      }),
    event->new_parameters.end());
  if (
    !event->new_parameters.size() && !event->changed_parameters.size() &&
    !event->deleted_parameters.size())
  {
    return false;
  }
  ss << "\nParameter event:\n new parameters:";
  for (auto & new_parameter : event->new_parameters) {
    ss << "\n  " << new_parameter.name;
  }
  ss << "\n changed parameters:";
  for (auto & changed_parameter : event->changed_parameters) {
    ss << "\n  " << changed_parameter.name;
  }
  ss << "\n deleted parameters:";
  for (auto & deleted_parameter : event->deleted_parameters) {
    ss << "\n  " << deleted_parameter.name;
  }
  ss << "\n";
  RCLCPP_INFO(logger, "%s", ss.str().c_str());
  return true;
}

int main(int argc, char ** argv)
{
  // Force flush of the stdout buffer.
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);

  rclcpp::init(argc, argv);

  auto node = rclcpp::Node::make_shared("parameter_events");

  auto parameters_client = std::make_shared<rclcpp::SyncParametersClient>(node);
  while (!parameters_client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    RCLCPP_INFO(node->get_logger(), "service not available, waiting again...");
  }

  auto events_received_promise = std::make_shared<std::promise<void>>();
  auto events_received_future = events_received_promise->get_future();

  // Setup callback for changes to parameters.
  auto sub = parameters_client->on_parameter_event(
    [node, promise = std::move(events_received_promise)](
      rcl_interfaces::msg::ParameterEvent::UniquePtr event) -> void
    {
      static size_t n_times_called = 0u;
      if (on_parameter_event(std::move(event), node->get_logger())) {
        ++n_times_called;
      }
      if (10u == n_times_called) {
        // This callback will be called 10 times, set the promise when that happens.
        promise->set_value();
      }
    });

  // Declare parameters that may be set on this node
  node->declare_parameter("foo", 0);
  node->declare_parameter("bar", "");
  node->declare_parameter("baz", 0.);
  node->declare_parameter("foobar", false);

  // Set several different types of parameters.
  auto set_parameters_results = parameters_client->set_parameters(
  {
    rclcpp::Parameter("foo", 2),
    rclcpp::Parameter("bar", "hello"),
    rclcpp::Parameter("baz", 1.45),
    rclcpp::Parameter("foobar", true),
  });

  // Change the value of some of them.
  set_parameters_results = parameters_client->set_parameters(
  {
    rclcpp::Parameter("foo", 3),
    rclcpp::Parameter("bar", "world"),
  });

  // TODO(wjwwood): Create and use delete_parameter

  rclcpp::spin_until_future_complete(node, events_received_future.share());
  rclcpp::shutdown();

  return 0;
}

异步

我们参考demo_nodes_cpp/src/parameters/parameter_events_async.cpp的实现。

这次我们使用Node的方式来运行监控。

class ParameterEventsAsyncNode : public rclcpp::Node
{
public:
  DEMO_NODES_CPP_PUBLIC
  explicit ParameterEventsAsyncNode(const rclcpp::NodeOptions & options)
  : Node("parameter_events", options)
  {
    setvbuf(stdout, NULL, _IONBF, BUFSIZ);

创建AsyncParametersClient

我们创建一个和本Node连接的AsyncParametersClient对象,并测试其连接。

    // Typically a parameter client is created for a remote node by passing the name of the remote
    // node in the constructor; in this example we create a parameter client for this node itself.
    parameters_client_ = std::make_shared<rclcpp::AsyncParametersClient>(this);

    // Even though this is in the same node, we still have to wait for the
    // service to be available before declaring parameters (otherwise there is
    // a chance we'll miss some events in the callback).
    while (!parameters_client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(this->get_logger(), "interrupted while waiting for the service. exiting.");
        rclcpp::shutdown();
        return;
      }
      RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");
    }

设置监控回调

回调函数和之前介绍的同步模式中的逻辑是一样的,这儿就不赘述了。

    auto on_parameter_event_callback =
      [this](rcl_interfaces::msg::ParameterEvent::UniquePtr event) -> void
      {
        // ignore qos overrides
        event->new_parameters.erase(
          std::remove_if(
            event->new_parameters.begin(),
            event->new_parameters.end(),
            [](const auto & item) {
              const char * param_override_prefix = "qos_overrides.";
              return std::strncmp(
                item.name.c_str(), param_override_prefix, sizeof(param_override_prefix) - 1) == 0u;
            }),
          event->new_parameters.end());
        if (
          !event->new_parameters.size() && !event->changed_parameters.size() &&
          !event->deleted_parameters.size())
        {
          return;
        }
        // TODO(wjwwood): The message should have an operator<<, which would replace all of this.
        std::stringstream ss;
        ss << "\nParameter event:\n new parameters:";
        for (auto & new_parameter : event->new_parameters) {
          ss << "\n  " << new_parameter.name;
        }
        ss << "\n changed parameters:";
        for (auto & changed_parameter : event->changed_parameters) {
          ss << "\n  " << changed_parameter.name;
        }
        ss << "\n deleted parameters:";
        for (auto & deleted_parameter : event->deleted_parameters) {
          ss << "\n  " << deleted_parameter.name;
        }
        ss << "\n";
        RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str());
      };

    // Setup callback for changes to parameters.
    parameter_event_sub_ = parameters_client_->on_parameter_event(on_parameter_event_callback);

测试

先声明一些Parameters。这个行为也会触发监控函数执行。

    // Declare parameters that may be set on this node
    this->declare_parameter("foo", 0);
    this->declare_parameter("bar", "");
    this->declare_parameter("baz", 0.);
    this->declare_parameter("foobar", false);

后面的测试代码是一个套娃模式。它会先启动一个Timer,执行一次值的设置。由于AsyncParametersClient的set_parameters也是异步的,但是我们可以设置一个完成时执行的回调。在这个回调中,我们再设置一些值,并最终关闭整个Node。


    // Queue a `set_parameters` request as soon as `spin` is called on this node.
    // TODO(dhood): consider adding a "call soon" notion to Node to not require a timer for this.
    timer_ = create_wall_timer(
      200ms,
      [this]() {
        this->queue_first_set_parameter_request();
      });
  }

private:
  // Set several different types of parameters.
  DEMO_NODES_CPP_LOCAL
  void queue_first_set_parameter_request()
  {
    timer_->cancel();  // Prevent another request from being queued by the timer.
    while (!parameters_client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(this->get_logger(), "interrupted while waiting for the service. exiting.");
        rclcpp::shutdown();
        return;
      }
      RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");
    }
    auto response_received_callback = [this](SetParametersResult future) {
        // Check to see if they were set.
        for (auto & result : future.get()) {
          if (!result.successful) {
            RCLCPP_ERROR(this->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
          }
        }
        this->queue_second_set_parameter_request();
      };

    parameters_client_->set_parameters(
      {
        rclcpp::Parameter("foo", 2),
        rclcpp::Parameter("bar", "hello"),
        rclcpp::Parameter("baz", 1.45),
        rclcpp::Parameter("foobar", true),
      }, response_received_callback);
  }

  // Change the value of some of them.
  DEMO_NODES_CPP_LOCAL
  void queue_second_set_parameter_request()
  {
    auto response_received_callback = [this](SetParametersResult future) {
        // Check to see if they were set.
        for (auto & result : future.get()) {
          if (!result.successful) {
            RCLCPP_ERROR(this->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
          }
        }
        // TODO(wjwwood): Create and use delete_parameter

        // Give time for all of the ParameterEvent callbacks to be received.
        timer_ = create_wall_timer(
          100ms,
          []() {
            rclcpp::shutdown();
          });
      };
    parameters_client_->set_parameters(
      {
        rclcpp::Parameter("foo", 3),
        rclcpp::Parameter("bar", "world"),
      }, response_received_callback);
  }

在这里插入图片描述

完整代码

// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <chrono>
#include <cstring>
#include <memory>
#include <sstream>
#include <vector>

#include "rcl_interfaces/msg/parameter_event.hpp"
#include "rcl_interfaces/msg/set_parameters_result.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_components/register_node_macro.hpp"

#include "demo_nodes_cpp/visibility_control.h"

using namespace std::chrono_literals;
using SetParametersResult =
  std::shared_future<std::vector<rcl_interfaces::msg::SetParametersResult>>;
namespace demo_nodes_cpp
{
class ParameterEventsAsyncNode : public rclcpp::Node
{
public:
  DEMO_NODES_CPP_PUBLIC
  explicit ParameterEventsAsyncNode(const rclcpp::NodeOptions & options)
  : Node("parameter_events", options)
  {
    setvbuf(stdout, NULL, _IONBF, BUFSIZ);
    // Typically a parameter client is created for a remote node by passing the name of the remote
    // node in the constructor; in this example we create a parameter client for this node itself.
    parameters_client_ = std::make_shared<rclcpp::AsyncParametersClient>(this);

    auto on_parameter_event_callback =
      [this](rcl_interfaces::msg::ParameterEvent::UniquePtr event) -> void
      {
        // ignore qos overrides
        event->new_parameters.erase(
          std::remove_if(
            event->new_parameters.begin(),
            event->new_parameters.end(),
            [](const auto & item) {
              const char * param_override_prefix = "qos_overrides.";
              return std::strncmp(
                item.name.c_str(), param_override_prefix, sizeof(param_override_prefix) - 1) == 0u;
            }),
          event->new_parameters.end());
        if (
          !event->new_parameters.size() && !event->changed_parameters.size() &&
          !event->deleted_parameters.size())
        {
          return;
        }
        // TODO(wjwwood): The message should have an operator<<, which would replace all of this.
        std::stringstream ss;
        ss << "\nParameter event:\n new parameters:";
        for (auto & new_parameter : event->new_parameters) {
          ss << "\n  " << new_parameter.name;
        }
        ss << "\n changed parameters:";
        for (auto & changed_parameter : event->changed_parameters) {
          ss << "\n  " << changed_parameter.name;
        }
        ss << "\n deleted parameters:";
        for (auto & deleted_parameter : event->deleted_parameters) {
          ss << "\n  " << deleted_parameter.name;
        }
        ss << "\n";
        RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str());
      };

    // Setup callback for changes to parameters.
    parameter_event_sub_ = parameters_client_->on_parameter_event(on_parameter_event_callback);

    // Even though this is in the same node, we still have to wait for the
    // service to be available before declaring parameters (otherwise there is
    // a chance we'll miss some events in the callback).
    while (!parameters_client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(this->get_logger(), "interrupted while waiting for the service. exiting.");
        rclcpp::shutdown();
        return;
      }
      RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");
    }

    // Declare parameters that may be set on this node
    this->declare_parameter("foo", 0);
    this->declare_parameter("bar", "");
    this->declare_parameter("baz", 0.);
    this->declare_parameter("foobar", false);

    // Queue a `set_parameters` request as soon as `spin` is called on this node.
    // TODO(dhood): consider adding a "call soon" notion to Node to not require a timer for this.
    timer_ = create_wall_timer(
      200ms,
      [this]() {
        this->queue_first_set_parameter_request();
      });
  }

private:
  // Set several different types of parameters.
  DEMO_NODES_CPP_LOCAL
  void queue_first_set_parameter_request()
  {
    timer_->cancel();  // Prevent another request from being queued by the timer.
    while (!parameters_client_->wait_for_service(1s)) {
      if (!rclcpp::ok()) {
        RCLCPP_ERROR(this->get_logger(), "interrupted while waiting for the service. exiting.");
        rclcpp::shutdown();
        return;
      }
      RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");
    }
    auto response_received_callback = [this](SetParametersResult future) {
        // Check to see if they were set.
        for (auto & result : future.get()) {
          if (!result.successful) {
            RCLCPP_ERROR(this->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
          }
        }
        this->queue_second_set_parameter_request();
      };

    parameters_client_->set_parameters(
      {
        rclcpp::Parameter("foo", 2),
        rclcpp::Parameter("bar", "hello"),
        rclcpp::Parameter("baz", 1.45),
        rclcpp::Parameter("foobar", true),
      }, response_received_callback);
  }

  // Change the value of some of them.
  DEMO_NODES_CPP_LOCAL
  void queue_second_set_parameter_request()
  {
    auto response_received_callback = [this](SetParametersResult future) {
        // Check to see if they were set.
        for (auto & result : future.get()) {
          if (!result.successful) {
            RCLCPP_ERROR(this->get_logger(), "Failed to set parameter: %s", result.reason.c_str());
          }
        }
        // TODO(wjwwood): Create and use delete_parameter

        // Give time for all of the ParameterEvent callbacks to be received.
        timer_ = create_wall_timer(
          100ms,
          []() {
            rclcpp::shutdown();
          });
      };
    parameters_client_->set_parameters(
      {
        rclcpp::Parameter("foo", 3),
        rclcpp::Parameter("bar", "world"),
      }, response_received_callback);
  }

  rclcpp::AsyncParametersClient::SharedPtr parameters_client_;
  rclcpp::Subscription<rcl_interfaces::msg::ParameterEvent>::SharedPtr parameter_event_sub_;
  rclcpp::TimerBase::SharedPtr timer_;
};

}  // namespace demo_nodes_cpp

RCLCPP_COMPONENTS_REGISTER_NODE(demo_nodes_cpp::ParameterEventsAsyncNode)

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

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

相关文章

Django项目中报错:django.template.exceptions.TemplateDoesNotExist: index.html

访问127.0.0.1&#xff1a;8000访问出错 查看报错原因 到Django项目当中找到settings.py&#xff0c;找到TEMPLATES中的DIRS: 添加如下代码&#xff0c;并导入OS模块&#xff1a; "DIRS": [os.path.join(BASE_DIR,templates)] 再次访问IP地址&#xff1a;

【JVM基础07】——类加载器-什么是类加载器?类加载器有哪些?双亲委派了解吗?

目录 1- 引言&#xff1a;类加载器1-1 类加载器是什么&#xff1f;(What)1-2 为什么要用类加载器&#xff1f; 作用&#xff1a;类加载的过程&#xff1f;(Why) 2- ⭐核心&#xff1a;类加载器详解(How)2-1 类加载器分类2-2 什么是双亲委派模型&#xff1f;2-3 为什么采用双亲委…

开始尝试从0写一个项目--前端(三)

器材管理板块 添加器材管理导航 src\views\home\Home.vue src\router\index.js src\views\equipment\Equipment.vue <template><div>hello!</div></template> 测试 搜索导航分页查询 src\views\equipment\Equipment.vue <template><div&…

C#、Net6、WebApi报表方案

目录 1 Pdf表单方案 1.1出现如下错误提示: 1.2 字体路径使用 2 Docx报表模板方案 2.1 pdf方案缺陷 2.2 解决方案 3 Spire.Doc报表方案 3.1 Docx方案缺陷 3.2 解决方案 4 插入复选框 5 WebApi文件流下载接口 6 软件获取方式 1 Pdf表单方案 使用【Adobe Acrobat P…

0726,没什么用的SELECT和没用的我

目录 select 可恶&#xff01;&#xff01;&#xff01; 一对多聊天室 select&#xff1a;&#xff08;抄抄抄 最怕人类开始思考 补一对一的 select 喵&#xff1a;&#xff08;抄抄抄 &#xff1f;&#xff1f;今天就这么结束了&#xff1f;&#xff1f;&#xff1f; …

全能Ai助手:写作到设计,宝藏神器帮你事半功倍

今天&#xff0c;就让我们一起踏上这场寻找“隐藏”宝藏的旅程&#xff0c;探索这些AI工具如何改变我们的生活&#xff01; 一、高效生产力的提升之道 1. 文案创作助手 案例&#xff1a;某位自媒体博主使用了一款智能写作工具&#xff0c;不仅大大节省了写作时间&#xff0c;…

JMeter接口测试:测试中奖概率!

介绍 Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具&#xff0c;用于对软件做压力测试。JMeter 最初被设计用于 Web 应用测试&#xff0c;但后来扩展到了其他测试领域&#xff0c;可用于测试静态和动态资源&#xff0c;如静态文件、Java 小服务程序、CGI 脚本、J…

c语言第四天笔记

关于 混合操作&#xff0c;不同计算结果推理 第一种编译结果&#xff1a; int i 5; int sum (i) (i) 6 7 13 第二种编译结果&#xff1a; int i 5; int sum (i) (i) 6 7 7 7 前面的7是因为后面i的变化被影响后&#xff0c;重新赋值 14 第一种编译结果&#xff…

Llama + Dify,在你的电脑搭建一套AI工作流

theme: smartblue 点赞 关注 收藏 学会了 本文简介 最近字节在推Coze&#xff0c;你可以在这个平台制作知识库、制作工作流&#xff0c;生成一个具有特定领域知识的智能体。 那么&#xff0c;有没有可能在本地也部署一套这个东西呢&#xff1f;这样敏感数据就不会泄露了&…

Redis的两种持久化方式---RDB、AOF

rdb其实就是一种快照持久化的方式&#xff0c;它会将Redis在某个时间点的所有的数据状态以二进制的方式保存到硬盘上的文件当中&#xff0c;它相对于aof文件会小很多&#xff0c;因为知识某个时间点的数据&#xff0c;当然&#xff0c;这就会导致它的实时性不够高&#xff0c;如…

Scrapy 爬取旅游景点相关数据(三)

这一节我们将之前爬取到的景点数据进行解析&#xff0c;并且保存为excel&#xff0c;便于后续使用&#xff0c;本节包含 &#xff08;1&#xff09; 景点数据解析 &#xff08;2&#xff09;数据保存到excel 1 编写爬虫 这次继续改进第二节的爬虫&#xff0c;新建一个爬虫文…

C#如何引用dll动态链接库文件的注释

1、dll动态库文件项目生成属性中要勾选“XML文档文件” 注意&#xff1a;XML文件的名字切勿修改。 2、添加引用时XML文件要与DLL文件在同一个目录下。 3、如果要是添加引用的时候XML不在相同目录下&#xff0c;之后又将XML文件复制到相同的目录下&#xff0c;需要删除引用&am…

蓝桥强化宝典(3)BFS

一、定义 广度优先搜索&#xff08;Breadth-First Search, BFS&#xff09;是另一种用于遍历或搜索树或图的算法。与深度优先搜索&#xff08;DFS&#xff09;沿着树的深度遍历不同&#xff0c;广度优先搜索会逐层遍历图的顶点。它从一个指定的源顶点开始&#xff0c;首先访问这…

失业潮下,有人靠天工AI做副业年入10万?

前言 你好&#xff0c;我是咪咪酱 这篇文章总结2个AI副业项目&#xff0c;不用写代码&#xff0c;就能做的2个副业项目。 第一&#xff1a;AI生成微信表情包&#xff0c;上传到微信表情包平台等&#xff0c;坚持下去&#xff0c;会有可观的收入。 第二&#xff1a;AI生成连载…

Java 8 中 20 个高频面试题及答案

文章目录 前言20 道高频题问题 1&#xff1a;给定一个整数列表&#xff0c;使用 Stream 函数找出列表中所有的偶数&#xff1f;问题 2&#xff1a;给定一个整数列表&#xff0c;使用 Stream 函数找出所有以 1 开头的数字&#xff1f;问题 3&#xff1a;如何使用 Stream 函数在给…

1.ESP32-CAM 下使用 ESP-IDF 打开摄像头

主要资料&#xff1a; 乐鑫官方编程指南 ESP-IDF 编程指南安信可官方模块页 安信可-ESP32-CAM摄像头开发板官方使用教程 安信可ESP32-CAM摄像头开发demo–局域网拍照、实时视频、人脸识别 &#xff08;开发环境是Linux&#xff09; 本文目标是在 Windows 下跑通摄像头 hello …

国衍科技——RFID技术的应用

在文物馆藏信息的记录与管理过程中&#xff0c;准确性和详细性是至关重要的。无论是大型博物馆还是私人收藏馆&#xff0c;都需要有效的方法来确保馆藏文物信息的可追溯性和可访问性&#xff0c;才能提供更好的服务和保护馆藏资源。而结合射频识别&#xff08;RFID&#xff09;…

2-46 基于matlab的声音信号的短时能量、短时过零率、端点检测

基于matlab的声音信号的短时能量、短时过零率、端点检测。通过计算计算短时能量、调整能量门限&#xff0c;然后开始端点检测。输出可视化结果。程序已调通&#xff0c;可直接运行。 2-46 短时能量 短时过零率 端点检测 - 小红书 (xiaohongshu.com)

未来的智能农业:智能合约如何提升农业生产效率和可持续性

随着全球人口的增长和资源的有限性&#xff0c;农业生产面临着越来越大的挑战。如何在提高生产效率的同时保障可持续发展成为全球农业发展的关键问题。智能合约作为一种基于区块链技术的自动化执行合约&#xff0c;正在逐渐应用于农业领域&#xff0c;为农业生产带来了新的机遇…