二自由度机械臂软件系统(三)ros2_control硬件底层插件
- ros2_control实现了两个功能,一个是控制算法插件即控制的实现,另一个是底层插件即跟硬件通信的功能。
参考资料:https://zhuanlan.zhihu.com/p/682574842
1、创建功能包
ros2 pkg create --build-type ament_cmake robot_control_test
- 在src中创建功能包并编译
2、创建launch文件
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.conditions import IfCondition
from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitutionfrom launch_ros.actions import Node
from launch_ros.substitutions import FindPackageSharedef generate_launch_description():# Declare arguments 1、声明参数declared_arguments = []declared_arguments.append(DeclareLaunchArgument("description_file",default_value="robottest.urdf.xacro", #这个名字是跟description中的xacro文件有关的description="URDF/XACRO description file with the robot.带有机器人的URDF/XACRO描述文件。",))declared_arguments.append(DeclareLaunchArgument("gui",default_value="true",description="Start Rviz2 and Joint State Publisher gui automatically \with this launch file.使用此启动文件自动启动Rviz2和Joint State Publisher gui。",))declared_arguments.append(DeclareLaunchArgument("prefix",default_value='""',description="Prefix of the joint names, useful for \multi-robot setup. If changed than also joint names in the controllers' configuration \have to be updated.\关节名称的前缀,用于多机器人设置。如果改变了,那么控制器配置中的联合名称也必须更新",))# Initialize Arguments 2、初始化参数description_file = LaunchConfiguration("description_file")gui = LaunchConfiguration("gui")prefix = LaunchConfiguration("prefix")# Get URDF via xacro 3、通过xacro给出urdfrobot_description_content = Command([PathJoinSubstitution([FindExecutable(name="xacro")])," ",PathJoinSubstitution([FindPackageShare("robot_test_control"), "urdf", description_file]),])robot_description = {"robot_description": robot_description_content}# rviz_config_file = PathJoinSubstitution(# [FindPackageShare("motor_test_control"), "rviz", "motorbot_view.rviz"]# )########################################################################################joint_state_publisher_node = Node(package="joint_state_publisher_gui",executable="joint_state_publisher_gui",condition=IfCondition(gui),)#发布关节数据信息robot_state_publisher_node = Node(package="robot_state_publisher",executable="robot_state_publisher",output="both",parameters=[robot_description],)#发布模型信息rviz_node = Node(package="rviz2",executable="rviz2",name="rviz2",output="log",# arguments=["-d", rviz_config_file],condition=IfCondition(gui),)#rviz节点nodes = [joint_state_publisher_node,robot_state_publisher_node,rviz_node,]return LaunchDescription(declared_arguments + nodes)
- 启动文件分成了两部分,参数的存储和节点生成。前面都是在准备所需的参数,后面设置节点,最终利用LaunchDescription生成了节点。
节点名 | 作用 |
---|---|
rviz_node | 运行rviz |
joint_state_publisher_node | 用一个ui获得控制命令,并发布到状态节点 |
robot_state_publisher_node | 主要用于发布机器人的urdf模型数据 |
3、机器人的xacro文件
(1)robottest.urdf.xacro
<?xml version="1.0"?>
<!-- Basic differential drive mobile base -->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="robotdrive_robot"><!-- 导入基本的几何形状 --><xacro:include filename="$(find robot_control_test)/urdf/robottest_description.urdf.xacro" /><!-- 导入 ros2_control 描述部分 --><xacro:include filename="$(find robot_control_test)/urdf/robottest.ros2_control.xacro" /></robot>
- 分别导入模型和控制器
(2)robottest.ros2_control.xacro
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro"><ros2_control name="test_control123" type="system"><hardware><plugin>robot_control_test/RobotTestSystemHardware</plugin></hardware><joint name="link1_joint"><command_interface name="position"/><state_interface name="position"/><state_interface name="velocity"/></joint><joint name="link2_joint"><command_interface name="position"/><state_interface name="position"/><state_interface name="velocity"/></joint></ros2_control></robot>
- 这是导入控制器的代码,注意在这里引入了底层控制器的插件。这个插件就是之后要写代码编译生成的动态库
4、底层硬件控制器代码
#include "motor_test_system.hpp"#include <chrono>
#include <cmath>
#include <cstddef>
#include <limits>
#include <memory>
#include <vector>#include "hardware_interface/types/hardware_interface_type_values.hpp"
#include "rclcpp/rclcpp.hpp"
#include "utils_can.h"utils_can motor1(1);
utils_can motor2(2);namespace
{
constexpr auto DEFAULT_COMMAND_TOPIC = "motor_test_cmd";
constexpr auto DEFAULT_STATE_TOPIC = "motor_test_state";} // namespacenamespace motor_test_control
{
hardware_interface::CallbackReturn MotorTestSystemHardware::on_init(const hardware_interface::HardwareInfo & info)
{ std::cout << "初始化" << std::endl;//1、打开can盒int state = 0;state = VCI_OpenDevice(VCI_USBCAN2A,0,0);if(state==1)//打开设备{std::cout << "open deivce success!\n" <<std::endl;//打开设备成功}else if(state==-1){std::cout << "no can device!\n" <<std::endl;}else{printf(">>open deivce fail!\n");//这个就算成功打开,还是为0}//2、初始化两路can口//2.1 can1初始化VCI_INIT_CONFIG config_can;config_can.AccCode=0;config_can.AccMask=0xFFFFFFFF;config_can.Filter=1;//接收所有帧config_can.Mode=0;//正常模式config_can.Timing0=0x00;/*波特率1000 Kbps*/config_can.Timing1=0x14;std::cout <<"1000" <<std::endl;if(VCI_InitCAN(VCI_USBCAN2,0,0,&config_can)!=1){std::cout << ">>Init CAN fail" <<std::endl;VCI_CloseDevice(VCI_USBCAN2,0);}else{std::cout << ">>Init CAN success" <<std::endl;}if(VCI_StartCAN(VCI_USBCAN2,0,0)!=1){VCI_CloseDevice(VCI_USBCAN2,0);}else{std::cout << ">>start CAN success" <<std::endl;}motor1.CanInit();motor2.CanInit();if (hardware_interface::SystemInterface::on_init(info) !=hardware_interface::CallbackReturn::SUCCESS){return hardware_interface::CallbackReturn::ERROR;}if (info_.joints.size() != 2){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"The number of crawlers is set not correactly,please check your urdf. 2 is expected,but now it is %zu.", info_.joints.size());return hardware_interface::CallbackReturn::ERROR;}else{RCLCPP_INFO(rclcpp::get_logger("MotorTestSystemHardware"), "Found 2 crawlers sucessfully!");}hw_positions_.resize(info_.joints.size(), std::numeric_limits<double>::quiet_NaN());hw_velocities_.resize(info_.joints.size(), std::numeric_limits<double>::quiet_NaN());hw_commands_.resize(info_.joints.size(), std::numeric_limits<double>::quiet_NaN());for (const hardware_interface::ComponentInfo & joint : info_.joints){// DiffBotSystem has exactly two states and one command interface on each jointif (joint.command_interfaces.size() != 1){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"Joint '%s' has %zu command interfaces found. 1 expected.", joint.name.c_str(),joint.command_interfaces.size());return hardware_interface::CallbackReturn::ERROR;}if (joint.command_interfaces[0].name != hardware_interface::HW_IF_POSITION){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"Joint '%s' have %s command interfaces found. '%s' expected.", joint.name.c_str(),joint.command_interfaces[0].name.c_str(), hardware_interface::HW_IF_POSITION);return hardware_interface::CallbackReturn::ERROR;}if (joint.state_interfaces.size() != 2){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"Joint '%s' has %zu state interface. 2 expected.", joint.name.c_str(),joint.state_interfaces.size());return hardware_interface::CallbackReturn::ERROR;}if (joint.state_interfaces[0].name != hardware_interface::HW_IF_POSITION){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"Joint '%s' have '%s' as first state interface. '%s' expected.", joint.name.c_str(),joint.state_interfaces[0].name.c_str(), hardware_interface::HW_IF_POSITION);return hardware_interface::CallbackReturn::ERROR;}if (joint.state_interfaces[1].name != hardware_interface::HW_IF_VELOCITY){RCLCPP_FATAL(rclcpp::get_logger("MotorTestSystemHardware"),"Joint '%s' have '%s' as second state interface. '%s' expected.", joint.name.c_str(),joint.state_interfaces[1].name.c_str(), hardware_interface::HW_IF_VELOCITY);return hardware_interface::CallbackReturn::ERROR;}}return hardware_interface::CallbackReturn::SUCCESS;
}std::vector<hardware_interface::StateInterface> MotorTestSystemHardware::export_state_interfaces()
{std::vector<hardware_interface::StateInterface> state_interfaces;for (auto i = 0u; i < info_.joints.size(); i++){state_interfaces.emplace_back(hardware_interface::StateInterface(info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_positions_[i]));state_interfaces.emplace_back(hardware_interface::StateInterface(info_.joints[i].name, hardware_interface::HW_IF_VELOCITY, &hw_velocities_[i]));}return state_interfaces;
}std::vector<hardware_interface::CommandInterface> MotorTestSystemHardware::export_command_interfaces()
{std::vector<hardware_interface::CommandInterface> command_interfaces;for (auto i = 0u; i < info_.joints.size(); i++){command_interfaces.emplace_back(hardware_interface::CommandInterface(info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_commands_[i]));}return command_interfaces;
}hardware_interface::CallbackReturn MotorTestSystemHardware::on_activate(const rclcpp_lifecycle::State & /*previous_state*/)
{std::cout << "激活" << std::endl;// BEGIN: This part here is for exemplary purposes - Please do not copy to your production codeRCLCPP_INFO(rclcpp::get_logger("MotorTestSystemHardware"), "Activating ...please wait...");// END: This part here is for exemplary purposes - Please do not copy to your production code// set some default valuesfor (auto i = 0u; i < hw_positions_.size(); i++){if (std::isnan(hw_positions_[i])){hw_positions_[i] = 0;hw_velocities_[i] = 0;hw_commands_[i] = 0;}}subscriber_is_active_ = true;this->node_ = std::make_shared<rclcpp::Node>("hardware_node");std_msgs::msg::Float64MultiArray empty_int16array;for(std::size_t i = 0; i < hw_positions_.size(); i++){empty_int16array.data.push_back(0.0);}received_fb_msg_ptr_.set(std::make_shared<std_msgs::msg::Float64MultiArray>(empty_int16array));fb_subscriber_ =this->node_->create_subscription<std_msgs::msg::Float64MultiArray>(DEFAULT_STATE_TOPIC, rclcpp::SystemDefaultsQoS(),[this](const std::shared_ptr<std_msgs::msg::Float64MultiArray> msg) -> void{if (!subscriber_is_active_){RCLCPP_WARN(this->node_->get_logger(), "Can't accept new commands. subscriber is inactive");return;}received_fb_msg_ptr_.set(std::move(msg));});//创建实时Publishercmd_publisher = this->node_->create_publisher<std_msgs::msg::Float64MultiArray>(DEFAULT_COMMAND_TOPIC, rclcpp::SystemDefaultsQoS());realtime_cmd_publisher_ = std::make_shared<realtime_tools::RealtimePublisher<std_msgs::msg::Float64MultiArray>>(cmd_publisher);RCLCPP_INFO(rclcpp::get_logger("MotorTestSystemHardware"), "Successfully activated!");return hardware_interface::CallbackReturn::SUCCESS;
}hardware_interface::CallbackReturn MotorTestSystemHardware::on_deactivate(const rclcpp_lifecycle::State & /*previous_state*/)
{std::cout << "反激活" << std::endl;// BEGIN: This part here is for exemplary purposes - Please do not copy to your production codeRCLCPP_INFO(rclcpp::get_logger("MotorTestSystemHardware"), "Deactivating ...please wait...");subscriber_is_active_ = false;fb_subscriber_.reset();received_fb_msg_ptr_.set(nullptr);// END: This part here is for exemplary purposes - Please do not copy to your production codeRCLCPP_INFO(rclcpp::get_logger("MotorTestSystemHardware"), "Successfully deactivated!");return hardware_interface::CallbackReturn::SUCCESS;
}hardware_interface::return_type motor_test_control::MotorTestSystemHardware::read(const rclcpp::Time & /*time*/, const rclcpp::Duration & period)
{std::cout << "读" << std::endl;std::shared_ptr<std_msgs::msg::Float64MultiArray> fb_msg;received_fb_msg_ptr_.get(fb_msg);rclcpp::spin_some(this->node_);for (std::size_t i = 0; i < hw_positions_.size(); i++){// Update the joint status: this is a revolute joint without any limit.double pos_ = 0;int id_ = 0;motor1.receive_callback(pos_,id_);if(id_==1){std::cout << "id==1" << std::endl;
hw_positions_[0] = pos_;}else if(id_==2){std::cout << "id==2" << std::endl;
hw_positions_[1] = pos_;}else{std::cout << "else:" << id_<<std::endl;}if(i < hw_velocities_.size()){hw_velocities_[i] = fb_msg->data[i];// hw_positions_[i] = fb_msg->data[i];//+= period.seconds() * hw_velocities_[i];std::cout << "读:" << hw_positions_[i]<<std::endl;}}return hardware_interface::return_type::OK;
}hardware_interface::return_type motor_test_control::MotorTestSystemHardware::write(const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/)
{std::cout << "写" << std::endl;if(realtime_cmd_publisher_->trylock()){auto & cmd_msg = realtime_cmd_publisher_->msg_;cmd_msg.data.resize(hw_commands_.size());for (auto i = 0u; i < hw_commands_.size(); i++){cmd_msg.data[i] = hw_commands_[i];std::cout << "写:" <<cmd_msg.data[i] <<std::endl;}motor1.send_callback(cmd_msg.data[0]/3.14*180.0);motor2.send_callback(cmd_msg.data[1]/3.14*180.0);realtime_cmd_publisher_->unlockAndPublish();}return hardware_interface::return_type::OK;
}} // namespace diff_test_control#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(motor_test_control::MotorTestSystemHardware, hardware_interface::SystemInterface)
相关文章:

二自由度机械臂软件系统(三)ros2_control硬件底层插件
ros2_control实现了两个功能,一个是控制算法插件即控制的实现,另一个是底层插件即跟硬件通信的功能。 参考资料:https://zhuanlan.zhihu.com/p/682574842 1、创建功能包 ros2 pkg create --build-type ament_cmake robot_control_test在sr…...

24.8.9.11数据结构|链栈和队列
链栈 1、理解 实际上是一个仅在表头进行操作的单链表,头指针指向栈顶结点或头结点,以下恋栈均指带头结点的链栈. 2、 基本操作 1、定义结构:节点含有数据域和指针域 2、初始化操作:建立一个带头结点的空栈 3、取栈顶元素操作:取出栈的栈顶元…...
StarSpider:一款高效的网络爬虫框架解析与实战
文章目录 引言官网链接StarSpider 原理简介基础使用1. 添加依赖2. 编写PageProcessor3. 启动爬虫 高级使用1. 分布式抓取2. 自定义下载器3. 深度定制 优点结语 引言 在大数据时代,数据成为了推动业务增长和创新的关键。网络爬虫作为数据获取的重要手段之一…...

LVS详细解析及其NAT模式与DR模式部署(理论+实验全方位指导)
目录 1. 集群 2. 分布式系统 3. 集群与分布式的比较 4.通俗的解释 集群 分布式系统 总结 LVS(Linux Virtual Server)简介 LVS专业术语 工作原理 LVS调度算法 静态调度算法 动态调度算法 ipvsadm脑图 NAT模式集群 LVS的配置 在LVS主机内打开…...
负载均衡相关概念介绍(一)
负载均衡(Load Balance)是集群技术的一种重要应用,旨在将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,从而提高系统的并发处理能力、增加吞吐量、加强网络处理能力,并提供故障转移以…...

二叉树详解(1)
文章目录 目录1. 树的概念及结构1.1 树的相关概念1.2 树的表示1.3 树在实际中的运用(表示文件系统的目录树结构) 2. 二叉树的概念及结构2.1 概念2.2 特殊的二叉树2.3 二叉树的存储结构 3. 二叉树的顺序结构及实现3.1 二叉树的顺序结构3.2 堆的概念及结构…...
Spring定时任务注解
Service EnableScheduling public class xxxServiceImpl implement xxxService{Scheduled(cron "0 15 11 * * ?") // 每天的11:15执行public void reportCurrentTime() {aaa();}Scheduled(cron "0 15 17 * * ?") // 每天的17:15执行public void report…...

数据结构-绪论
学习目标: 认识数据结构的基本内容 学习内容: 了解:数据结构的研究内容掌握:数据结构的基本概念和术语了解:数据元素间的结构关系掌握:算法及算法的描述 数据结构的发展: 数据结构的发展简史 …...

Web开发:web服务器-Nginx的基础介绍(含AI文稿)
目录 一、Nginx的功能: 二、正向代理和反向代理的区别 三、Nginx负载均衡的主要功能 四、nginx安装目录下的各个文件(夹)的作用: 五、常用命令 一、Nginx的功能: 1.反向代理:例如我有三台服务器&#x…...

共享经济背景下校园、办公闲置物品交易平台-计算机毕设Java|springboot实战项目
🍊作者:计算机毕设残哥 🍊简介:毕业后就一直专业从事计算机软件程序开发,至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长:按照需求定制化开发项目、 源…...
Linux 服务器上简单配置 minio
Linux 服务器上简单配置 minio 初始化结构目录 mkdir -p /data/minio/bin mkdir -p /data/minio/conf mkdir -p /data/minio/data 下载 minio cd /data/minio/bin curl -O https://dl.min.io/server/minio/release/linux-amd64/minio 添加执行权限 chmod x minio 创建配置文件…...
TypeScript 面试题汇总
引言 TypeScript 是一种由微软开发的开源、跨平台的编程语言,它是 JavaScript 的超集,为 JavaScript 添加了静态类型系统和其他高级功能。随着 TypeScript 在前端开发领域的广泛应用,掌握 TypeScript 已经成为很多开发者必备的技能之一。本文…...
杰卡德系数
杰卡德系数(Jaccard Index 或 Jaccard Similarity Coefficient) 杰卡德系数是一种用于衡量两个集合相似度的重要指标。 从数学定义上来看,如前面所述,杰卡德系数计算公式为: J ( A , B ) ∣ A ∩ B ∣ ∣ A ∪ B ∣…...

微服务实现-sleuth+zipkin分布式链路追踪和nacos配置中心
1. sleuthzipkin分布式链路追踪 在大型系统的微服务化构建中,一个系统被拆分成了许多微服务。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。 这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软…...
数学中常用的解题方法
文章目录 待定系数法应用示例1. 多项式除法2. 分式化简3. 数列通项公式 总结 递归数列特征方程特征根的求解通项公式的求解示例 错位相减,差分错位相减法差分的应用结合理解 韦达定理二项式定理二项式定理的通项公式二项式系数的性质应用示例 一元二次求解1. 因式分…...
pytorch 1 张量
张量 文章目录 张量torch.Tensor 的 主要属性torch.Tensor 的 其他常用属性和方法叶子张量(Leaf Tensors)定义叶子张量的约定深入理解示例代码总结 中间计算结果与 detach() 方法定义中间计算结果不是叶子节点使用 detach() 方法使中间结果成为叶子张量示…...

音视频开发继续学习
RGA模块 RGA模块定义 RGA模块是RV1126用于2D图像的裁剪、缩放、旋转、镜像、图片叠加等格式转换的模块。比方说:要把一个原分辨率1920 * 1080的视频压缩成1280 * 720的视频,此时就要用到RGA模块了。 RGA模块结构体定义 RGA区域属性结构体 imgType&am…...

【Datawhale X 魔搭 】AI夏令营第四期大模型方向,Task1:智能编程助手(持续更新)
在一个数据驱动的世界里,人工智能的未来应由每一个愿意学习和探索的人共同塑造和掌握。希望这里是你实现AI梦想的起点。 大模型小白入门:https://linklearner.com/activity/14/11/25 大模型开发工程师能力测试:https://linklearner.com/activ…...

如何判断监控设备是否支持语音对讲
目录 一、大华摄像机 二、海康摄像机 三、宇视摄像机 一、大华摄像机 注意:大华摄像机支持跨网语音对讲,即设备和服务器可以不在同一网络内,大华设备的语音通道填写:34020000001370000001 配置接入示例: 音频输入…...

Grafana+Influxdb(Prometheus)+Apache Jmeter搭建可视化性能测试监控平台
此性能测试监控平台,架构可以是: GrafanaInfluxdbJmeterGrafanaPrometheusJmeter Influxdb和Prometheus在这里都是时序性数据库 在测试环境中,压测数据对存储和持久化的要求不高,所以这里的组件可以都通过docker-compose.yml文件…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...

运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
报错 找到package.json文件 找到这个修改成 "lint": "eslint --fix --ext .js,.vue src" 为elsint有配置结尾换行符,最后运行:npm run lint --fix...