carla与g29联合调试(二)

news2024/9/20 6:30:12

前言:

对于力反馈的源码解析。

一、工作空间分析

工作空间如下图所示:

config文件中是对相关参数的设定;

launch文件是将yaml文件和执行文件融合启动;

msg文件是自定义消息类型;

script文件是python版本的ros执行文件,主要是为了发送控制旋转和力度的控制指令与carla的联合调试就是在这里实现的;

src里为核心内容通过输入输出信号控制g29

二、src解析

其他文件基础内容,只对于核心内容解析分析。

2.1 头文件

#include <ros/ros.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>

#include "g29_force_feedback/ForceFeedback.h"

其中:

#include <linux/input.h>

用于Linux系统中读取输入设备的信息。它提供了访问Linux内核输入子系统的接口,使开发者能够获取和处理用户输入事件,如按键、鼠标、触摸屏等。该头文件定义了一系列结构体和常量,用于描述输入设备的特性和事件。开发者可以使用这些结构体和函数来监听和处理输入事件,实现各种应用程序,例如图形界面、游戏、交互式应用等。主要用来与g29接口之间的输入输出。

#include <sys/ioctl.h>

用于进行输入输出控制操作。它提供了对设备的ioctl函数的声明,可以用来发送控制命令给设备驱动程序,实现一些特殊的操作。ioctl函数是一种特殊的系统调用,用于对已打开的设备文件进行控制。通过使用不同的参数和命令,可以实现对设备的配置、查询设备状态、发送控制命令等功能。在这个头文件中,定义了一些常量和结构体,用于ioctl函数中命令和参数的传递。程序员可以根据需要使用这些常量和结构体,调用ioctl函数来进行相应的设备控制操作。

#include "g29_force_feedback/ForceFeedback.h"

自定义消息类型

2.2 main函数

int main(int argc, char **argv)
{
    ros::init(argc, argv, "g29_force_feedback_node");
    G29ForceFeedback g29_ff;
    ros::spin();
    return(0);
}

核心在类G29ForceFeedback的实现中。

2.3 G29ForceFeedback声明

class G29ForceFeedback
{
private:
    ros::Subscriber sub_target;
    ros::Timer timer;
    float m_pub_rate;

    // variables from fouce feedback API
    int m_device_handle;
    int m_axis_code = ABS_X;
    int m_axis_min;
    int m_axis_max;
    struct ff_effect m_effect;

    // device config
    std::string m_device_name;
    double m_max_force;
    double m_min_force;

    // motion config 0:PID force, 1:constant force
    double m_Kp;
    double m_Ki;
    double m_Kd;
    double m_offset;

    // target and current state of the wheel
    bool m_pid_mode;
    double m_target_angle;
    double m_target_force;
    double m_current_angle;

public:
    G29ForceFeedback();

private:
    void targetCallback(const g29_force_feedback::ForceFeedback &in_target);
    void timerCallback(const ros::TimerEvent&);
    int testBit(int bit, unsigned char *array);
    void initFfDevice();
    void updateFfDevice();
};

逐行分析:

    ros::Subscriber sub_target;
    ros::Timer timer;
    float m_pub_rate;

一个订阅,一个计时,然后一个发布的速率。

    // variables from fouce feedback API
    int m_device_handle;
    int m_axis_code = ABS_X;
    int m_axis_min;
    int m_axis_max;
    struct ff_effect m_effect;

力反馈变量的相关设置,其中

m_device_handle:一个整数变量,表示力反馈设备的句柄或标识符。

m_axis_code:一个整数变量,表示力反馈设备的轴码,指示要应用力反馈效果的轴。

m_axis_min 和 m_axis_max:两个整数变量,表示力反馈设备的轴的最小值和最大值,用于标定力反馈效果的范围。

m_effect:一个ff_effect结构体变量,用于描述和控制力反馈效果。

而ABS_X为Linux输入子系统中定义的一个常量,用于表示力反馈设备的 X 轴。

ff_effect是Linux中的一个结构体,定义在<linux/input.h>头文件中。它用于描述和控制力反馈(force feedback)效果。

    // device config
    std::string m_device_name;
    double m_max_force;
    double m_min_force;

设备相关的设施,设备名称,最大力反馈、最小力反馈。

    // motion config 0:PID force, 1:constant force
    double m_Kp;
    double m_Ki;
    double m_Kd;
    double m_offset;

对于PID的设定。

    // target and current state of the wheel
    bool m_pid_mode;
    double m_target_angle;
    double m_target_force;
    double m_current_angle;

判断是否使用pid控制和目标角度、力反馈和当前的角度。

    void targetCallback(const g29_force_feedback::ForceFeedback &in_target);
    void timerCallback(const ros::TimerEvent&);
    int testBit(int bit, unsigned char *array);
    void initFfDevice();
    void updateFfDevice();

一些成员函数。

2.4 G29ForceFeedback实现

2.4.1 构造函数

G29ForceFeedback::G29ForceFeedback():
    m_device_name("/dev/input/event19"),
    m_Kp(0.1),
    m_Ki(0.0),
    m_Kd(0.0),
    m_offset(0.01),
    m_max_force(1.0),
    m_min_force(0.2),
    m_pub_rate(0.1),
    m_pid_mode(0)
{
    ros::NodeHandle n;
    sub_target = n.subscribe("/ff_target", 1, &G29ForceFeedback::targetCallback, this);

    n.getParam("device_name", m_device_name);
    n.getParam("Kp", m_Kp);
    n.getParam("Ki", m_Ki);
    n.getParam("Kd", m_Kd);
    n.getParam("offset", m_offset);
    n.getParam("max_force", m_max_force);
    n.getParam("min_force", m_min_force);
    n.getParam("pub_rate", m_pub_rate);

    initFfDevice();

    ros::Duration(1).sleep();
    timer = n.createTimer(ros::Duration(m_pub_rate), &G29ForceFeedback::timerCallback, this);
}

逐句分析:

    m_device_name("/dev/input/event19"),

默认使用event19接口

    m_Kp(0.1),
    m_Ki(0.0),
    m_Kd(0.0),
    m_offset(0.01),
    m_max_force(1.0),
    m_min_force(0.2),
    m_pub_rate(0.1),
    m_pid_mode(0)

相关参数设置。

    ros::NodeHandle n;
    sub_target = n.subscribe("/ff_target", 1, &G29ForceFeedback::targetCallback, this);

ros节点初始化,及订阅初始化,订阅topic为“/ff_target”,这个topic就是carla端要发送的topic。

    n.getParam("device_name", m_device_name);
    n.getParam("Kp", m_Kp);
    n.getParam("Ki", m_Ki);
    n.getParam("Kd", m_Kd);
    n.getParam("offset", m_offset);
    n.getParam("max_force", m_max_force);
    n.getParam("min_force", m_min_force);
    n.getParam("pub_rate", m_pub_rate);

参数服务器的设定,直接使用config里面的yaml文件设施,主要是为了方便调参。

    ros::Duration(1).sleep();
    timer = n.createTimer(ros::Duration(m_pub_rate), &G29ForceFeedback::timerCallback, this);

使用createTimer可以创建一个计时器对象,并指定它的触发周期。每次都使用回调函数timerCallback。


2.4.2 timerCallback函数

// update input event with timer callback
void G29ForceFeedback::timerCallback(const ros::TimerEvent&)
{
    updateFfDevice();
}

注意参数是const ros::TimerEvent&,这里有所疑问,以前从未遇见过。大概是形参可以直接省略?

// update input event with writing information to the event file
void G29ForceFeedback::updateFfDevice()
{
    struct input_event event;
    static float diff_i = 0.0, diff = 0.0;
    double diff_d, force, buf;

    // if you wanna use I control, let's avoid integral value exploding
    // static int count = 0;
    // count ++;
    // if (force > 0.3 || count > 10)
    // {
        //     diff_i = 0.0;
        //     count = 0;
        // }

    // calcurate values for PID control
    buf = diff;
    diff = m_target_angle - m_current_angle;
    diff_i += diff;
    diff_d = diff - buf;

    if (m_pid_mode)
    {
        force = fabs(m_Kp * diff + m_Ki * diff_i + m_Kd * diff_d) * ((diff > 0.0) ? 1.0 : -1.0);

        // if wheel angle reached to the target
        if (fabs(diff) < m_offset)
        {
            force = 0.0;
        }
        else
        {
            // force less than 0.2 cannot turn the wheel
            force = (force > 0.0) ? std::max(force, m_min_force) : std::min(force, -m_min_force);
            // set max force for safety
            force = (force > 0.0) ? std::min(force, m_max_force) : std::max(force, -m_max_force);
        }
    }
    else
    {
        force = fabs(m_target_force) * ((diff > 0.0) ? 1.0 : -1.0);

        // if wheel angle reached to the target
        if (fabs(diff) < m_offset)
        {
            force = 0.0;
        }
    }

    // for safety
    force = (force > 0.0) ? std::min(force, m_max_force) : std::max(force, -m_max_force);

    // start effect
    m_effect.u.constant.level = (short)(force * 32767.0);
    m_effect.direction = 0xC000;
    m_effect.u.constant.envelope.attack_level = (short)(force * 32767.0);
    m_effect.u.constant.envelope.fade_level = (short)(force * 32767.0);

    if (ioctl(m_device_handle, EVIOCSFF, &m_effect) < 0)
    {
        std::cout << "failed to upload m_effect" << std::endl;
    }

    // get current state
    while (read(m_device_handle, &event, sizeof(event)) == sizeof(event))
    {
        if (event.type == EV_ABS && event.code == m_axis_code)
        {
            m_current_angle = (event.value - (m_axis_max + m_axis_min) * 0.5) * 2 / (m_axis_max - m_axis_min);
        }
    }
}

逐句分析:

    struct input_event event;
    static float diff_i = 0.0, diff = 0.0;
    double diff_d, force, buf;

input_event 是一个结构体类型,它是 Linux 内核中用于表示输入设备事件的结构体,

定义了两个静态变量diff_i和diff。

diff_d、force和buf是一些参数,是为了使用PID调参,主要是为了调节目标角度和当前角度之间的PID控制。

    // calcurate values for PID control
    buf = diff;
    diff = m_target_angle - m_current_angle;
    diff_i += diff;
    diff_d = diff - buf;

简单描述下pid的过程,

diff是全局变量,其中buf用于存储上一次获得的diff数据,

然后用目标角度减去当前角度来计算出新的diff,

diff_i是diff的积分值,用于累计差值以实现积分控制,

diff_d是微分值,用于计算差值的变化率。

下面是pid控制的过程:

    if (m_pid_mode)
    {
        force = fabs(m_Kp * diff + m_Ki * diff_i + m_Kd * diff_d) * ((diff > 0.0) ? 1.0 : -1.0);

        // if wheel angle reached to the target
        if (fabs(diff) < m_offset)
        {
            force = 0.0;
        }
        else
        {
            // force less than 0.2 cannot turn the wheel
            force = (force > 0.0) ? std::max(force, m_min_force) : std::min(force, -m_min_force);
            // set max force for safety
            force = (force > 0.0) ? std::min(force, m_max_force) : std::max(force, -m_max_force);
        }
    }
    else
    {
        force = fabs(m_target_force) * ((diff > 0.0) ? 1.0 : -1.0);

        // if wheel angle reached to the target
        if (fabs(diff) < m_offset)
        {
            force = 0.0;
        }
    }

其中fabs函数的作用是计算绝对值;

m_Kp在参数服务器下表示的是Kp值;

m_Ki在参数服务器下表示的是Ki值;

m_Kd在参数服务器下表示的是Kd值;

然后分别乘上相应的参数,这行代码的目的是计算出根据PID控制器输出的力或转矩的大小,并且根据diff的正负情况来确定方向。

后面的if和else结构里面是说如果diff小于m_offset的值(设定为0.01)说明到了指定target_angle就将force设置为0。

后面的else就是设定安全范围,在设置的最小和最大值之间。

最后的else是设定不适用Pid控制时,当达到设定的值0.01以下直接置零,其他不操作。

    // start effect
    m_effect.u.constant.level = (short)(force * 32767.0);
    m_effect.direction = 0xC000;
    m_effect.u.constant.envelope.attack_level = (short)(force * 32767.0);
    m_effect.u.constant.envelope.fade_level = (short)(force * 32767.0);

这里用到底了使用ff_effect生成的对象,m_effect。

ff_effect时Linux中使用的力反馈结构,这四行是使用的控制参数,具体含义不太理解。

在网上找到的关于ff_effect的定义:

  • type:表示力反馈效果的类型,例如振动、冲击等。
  • id:用于标识力反馈效果的唯一 ID。
  • direction:表示力的方向,通常使用角度或向量来表示。
  • trigger:定义触发力反馈效果的触发器。
  • replay:设置力反馈效果的重播次数和时间间隔。
  • u:联合体(union),用于存储具体力反馈效果的各种属性和参数。
    if (ioctl(m_device_handle, EVIOCSFF, &m_effect) < 0)
    {
        std::cout << "failed to upload m_effect" << std::endl;
    }

其中ioctl函数是头文件#include <sys/ioctl.h>带入的。

它的作用是将上一步获得的ff_effect对象的相关参数传递给指定的设备,

m_device_handle表示要控制的设备名字,就是可以在参数服务器中设置的,

EVIOCSFF是请求码,表示具体的操作,这里是将m_effect结构体上传到设备,

然后如果返回值小于0表示失败。就会输出"failed to upload m_effect"。

    // get current state
    while (read(m_device_handle, &event, sizeof(event)) == sizeof(event))
    {
        if (event.type == EV_ABS && event.code == m_axis_code)
        {
            m_current_angle = (event.value - (m_axis_max + m_axis_min) * 0.5) * 2 / (m_axis_max - m_axis_min);
        }
    }

使用read函数读取控制设备的状态,并存储在event变量中。

如果event.type == EV_ABS && event.code == m_axis_code符合条件,就将m_current_angle赋值。主要作用是是获取设备上特定轴的当前角度,并将其存储在 m_current_angle变量中。

这里给出在网上找到的关于Linux控制符的相关资料:

链接:Linux输入子系统:事件的编码 -- event-codes.txt_ev_key_DroidPhone的博客-CSDN博客

 Event types:types对应于一个相同逻辑输入结构的一组Codes。每个type都有一组可用的codes用于产生输入事件。每个type可用的codes的详细信息请参考Codes一节的内容。

* EV_SYN:

  - 用于事件间的分割标志。事件可能按时间或空间进行分割,就像在多点触摸协议中的例子。

 * EV_KEY:

  - 用来描述键盘,按键或者类似键盘设备的状态变化。

 * EV_REL:

  - 用来描述相对坐标轴上数值的变化,例如:鼠标向左方移动了5个单位。

 * EV_ABS:

  -用来描述相对坐标轴上数值的变化,例如:描述触摸屏上坐标的值。

 * EV_MSC:

  - 当不能匹配现有的类型时,使用该类型进行描述。

 * EV_SW:

  - 用来描述具备两种状态的输入开关。

 * EV_LED:

  - 用于控制设备上的LED灯的开和关。

 * EV_SND:

  - 用来给设备输出提示声音。

 * EV_REP:

  -用于可以自动重复的设备(autorepeating)。

 * EV_FF:

  - 用来给输入设备发送强制回馈命令。(震动?)

 * EV_PWR:

  - 特别用于电源开关的输入。.

 * EV_FF_STATUS:

  - 用于接收设备的强制反馈状态。

2.4.3 targetCallback函数

// get target information of wheel control from ros message
void G29ForceFeedback::targetCallback(const g29_force_feedback::ForceFeedback &in_target)
{
    m_pid_mode = in_target.pid_mode;
    m_target_angle = in_target.angle;
    m_target_force = in_target.force;
}

使用过ros来接收控制指令,包括是否开启pid、目标角度和控制的力度,其实逻辑是有冲突的。如果选择开启pid,那么force会强制设置为1,这是force就没有用了。

2.4.4 initFfDevice函数

// initialize force feedback device
void G29ForceFeedback::initFfDevice()
{
    // setup device
    unsigned char key_bits[1+KEY_MAX/8/sizeof(unsigned char)];
    unsigned char abs_bits[1+ABS_MAX/8/sizeof(unsigned char)];
    unsigned char ff_bits[1+FF_MAX/8/sizeof(unsigned char)];

    struct input_event event;
    struct input_absinfo abs_info;

    m_device_handle = open(m_device_name.c_str(), O_RDWR|O_NONBLOCK);
    if (m_device_handle < 0)
    {
        std::cout << "ERROR: cannot open device : "<< m_device_name << std::endl;
        exit(1);
    }else{std::cout << "device opened" << std::endl;}

    // which axes has the device?
    memset(abs_bits, 0, sizeof(abs_bits));
    if (ioctl(m_device_handle, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits) < 0)
    {
        std::cout << "ERROR: cannot get abs bits" << std::endl;
        exit(1);
    }

    // get some information about force feedback
    memset(ff_bits, 0, sizeof(ff_bits));
    if (ioctl(m_device_handle, EVIOCGBIT(EV_FF, sizeof(ff_bits)), ff_bits) < 0)
    {
        std::cout << "ERROR: cannot get ff bits" << std::endl;
        exit(1);
    }

    // get axis value range
    if (ioctl(m_device_handle, EVIOCGABS(m_axis_code), &abs_info) < 0)
    {
        std::cout << "ERROR: cannot get axis range" << std::endl;
        exit(1);
    }
    m_axis_max = abs_info.maximum;
    m_axis_min = abs_info.minimum;
    if (m_axis_min >= m_axis_max)
    {
        std::cout << "ERROR: axis range has bad value" << std::endl;
        exit(1);
    }

    // check force feedback is supported?
    if(!testBit(FF_CONSTANT, ff_bits))
    {
        std::cout << "ERROR: force feedback is not supported" << std::endl;
        exit(1);
    }else{std::cout << "force feedback supported" << std::endl;}

    // auto centering off
    memset(&event, 0, sizeof(event));
    event.type = EV_FF;
    event.code = FF_AUTOCENTER;
    event.value = 0;
    if (write(m_device_handle, &event, sizeof(event)) != sizeof(event))
    {
        std::cout << "failed to disable auto centering" << std::endl;
        exit(1);
    }

    // initialize constant foce m_effect
    memset(&m_effect, 0, sizeof(m_effect));
    m_effect.type = FF_CONSTANT;
    m_effect.id = -1;
    m_effect.trigger.button = 0;
    m_effect.trigger.interval = 0;
    m_effect.replay.length = 0xffff;
    m_effect.replay.delay = 0;
    m_effect.u.constant.level = 0;
    m_effect.direction = 0xC000;
    m_effect.u.constant.envelope.attack_length = 0;
    m_effect.u.constant.envelope.attack_level = 0;
    m_effect.u.constant.envelope.fade_length = 0;
    m_effect.u.constant.envelope.fade_level = 0;

    if (ioctl(m_device_handle, EVIOCSFF, &m_effect) < 0)
    {
        std::cout << "failed to upload m_effect" << std::endl;
        exit(1);
    }

    // start m_effect
    memset(&event, 0, sizeof(event));
    event.type = EV_FF;
    event.code = m_effect.id;
    event.value = 1;
    if (write(m_device_handle, &event, sizeof(event)) != sizeof(event))
    {
        std::cout << "failed to start event" << std::endl;
        exit(1);
    }
}

初始化函数。

    // setup device
    unsigned char key_bits[1+KEY_MAX/8/sizeof(unsigned char)];
    unsigned char abs_bits[1+ABS_MAX/8/sizeof(unsigned char)];
    unsigned char ff_bits[1+FF_MAX/8/sizeof(unsigned char)];

这是Iinux中相关参数的驱动设置,其中

key_bits数组表示按键的数量,

abs_bits数组表示绝对值事件的数量,

ff_bits数组是设备支持的最大力反馈效果数。

具体见这篇博文:

https://www.cnblogs.com/lifexy/p/7553861.html

    struct input_event event;
    struct input_absinfo abs_info;

两个输入的结构。

    m_device_handle = open(m_device_name.c_str(), O_RDWR|O_NONBLOCK);
    if (m_device_handle < 0)
    {
        std::cout << "ERROR: cannot open device : "<< m_device_name << std::endl;
        exit(1);
    }else{std::cout << "device opened" << std::endl;}

使用open打开函数,打开m_device_name的c语言str类型,并使用O_RDWR|O_NONBLOCK表示打开读写和非堵塞行驶,如果成功打开就m_device_handle大于0,否则小于0。

    // which axes has the device?
    memset(abs_bits, 0, sizeof(abs_bits));
    if (ioctl(m_device_handle, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits) < 0)
    {
        std::cout << "ERROR: cannot get abs bits" << std::endl;
        exit(1);
    }

使用memset函数将abs_bits数组全部置为0,

之后使用ioctl函数将获取EV_ABS表示绝对值事件,并使用某种宏将它读取到的东西存放在了abs_bits数组中。如果ioctl小于0就表示示未读取成功。

    // get some information about force feedback
    memset(ff_bits, 0, sizeof(ff_bits));
    if (ioctl(m_device_handle, EVIOCGBIT(EV_FF, sizeof(ff_bits)), ff_bits) < 0)
    {
        std::cout << "ERROR: cannot get ff bits" << std::endl;
        exit(1);
    }

和上面的同理。

    // get axis value range
    if (ioctl(m_device_handle, EVIOCGABS(m_axis_code), &abs_info) < 0)
    {
        std::cout << "ERROR: cannot get axis range" << std::endl;
        exit(1);
    }
    m_axis_max = abs_info.maximum;
    m_axis_min = abs_info.minimum;
    if (m_axis_min >= m_axis_max)
    {
        std::cout << "ERROR: axis range has bad value" << std::endl;
        exit(1);
    }

判断能否获得轴的信息。

    // check force feedback is supported?
    if(!testBit(FF_CONSTANT, ff_bits))
    {
        std::cout << "ERROR: force feedback is not supported" << std::endl;
        exit(1);
    }else{std::cout << "force feedback supported" << std::endl;}

测试是否支持力反馈。

    // auto centering off
    memset(&event, 0, sizeof(event));
    event.type = EV_FF;
    event.code = FF_AUTOCENTER;
    event.value = 0;
    if (write(m_device_handle, &event, sizeof(event)) != sizeof(event))
    {
        std::cout << "failed to disable auto centering" << std::endl;
        exit(1);
    }

将event初始化置为0,

并将相关参数初始化,

EV_FF表示是力反馈,

FF_AUTOCENTER表示是自动居中控制的代码,

value将它置零就是关闭这个功能。

    // initialize constant foce m_effect
    memset(&m_effect, 0, sizeof(m_effect));
    m_effect.type = FF_CONSTANT;
    m_effect.id = -1;
    m_effect.trigger.button = 0;
    m_effect.trigger.interval = 0;
    m_effect.replay.length = 0xffff;
    m_effect.replay.delay = 0;
    m_effect.u.constant.level = 0;
    m_effect.direction = 0xC000;
    m_effect.u.constant.envelope.attack_length = 0;
    m_effect.u.constant.envelope.attack_level = 0;
    m_effect.u.constant.envelope.fade_length = 0;
    m_effect.u.constant.envelope.fade_level = 0;

一些参数设置,应该是linux中设定力反馈的参数。

    if (ioctl(m_device_handle, EVIOCSFF, &m_effect) < 0)
    {
        std::cout << "failed to upload m_effect" << std::endl;
        exit(1);
    }

    // start m_effect
    memset(&event, 0, sizeof(event));
    event.type = EV_FF;
    event.code = m_effect.id;
    event.value = 1;
    if (write(m_device_handle, &event, sizeof(event)) != sizeof(event))
    {
        std::cout << "failed to start event" << std::endl;
        exit(1);
    }

开始力反馈。

2.4.5 testBit函数

int G29ForceFeedback::testBit(int bit, unsigned char *array)
{
    return ((array[bit / (sizeof(unsigned char) * 8)] >> (bit % (sizeof(unsigned char) * 8))) & 1);
}

三、总结

对于Linux中相关指令的调用需要很熟悉,特别是输入输出相关的内容。

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

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

相关文章

子网划分详解+实例精析

IP编址系列文章&#xff08;下&#xff09; 前言 一&#xff0c;为什么要划分子网 举例&#xff1a; 二&#xff0c;划分子网数量公式 举例1&#xff1a; 举例2: 小结&#xff1a; 总结 前言 随着华为公司的不断发展&#xff0c;数据通信这门技术也越来越重要&#xff…

北大核心中文周刊复现-基于逻辑回归的金融风投评分卡模型实现

最近有些学员有论文需求&#xff0c;让我提供一下逻辑回归&#xff0c;金融风控&#xff0c;评分卡相关参考论文&#xff0c;以供参考。我找了一篇描述评分卡模型原理的论文&#xff0c;题目是《基于逻辑回归的金融风投评分卡模型实现》&#xff0c;第一作者边玉宁。这篇论文发…

vue3搭建(vite+create-vue)

目录 前提条件 输入命令 对于Add an End-to-End Testing Solution nightwatch和Cypress 和 Playwright 运行 前提条件 熟悉命令行已安装 16.0 或更高版本的 Node.js &#xff08;node -v查看版本&#xff09; 输入命令 npm init vuelatest 这一指令将会安装并执行 create-…

整数规划——第一章 引言

整数规划——第一章 引言 整数规划是带整数变量的最优化问题&#xff0c;即最大化或最小化一个全部或部分变量为整数的多元函数受约束于一组等式和不等式条件的最优化问题。许多经济、管理、交通、通信和工程中的最优化问题都可以用整数规划来建模。 考虑一个电视机工厂的生产…

软件测试面试总结——http协议相关面试题

前言 在PC浏览器的地址栏输入一串URL&#xff0c;然后按Enter键这个页面渲染出来&#xff0c;这个过程中都发生了什么事?这个是很多面试官喜欢问的一个问题 如果测试只是停留在表面上点点点&#xff0c;不知道背后的逻辑&#xff0c;是无法发现隐藏的bug&#xff0c;只能找一…

【JAVASE】类与对象

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈Java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 类与对象 1. 面向对象1.1 什么是面向对象…

Spark-对RDD的理解

RDD是分布式弹性数据集。 RDD有五大特性&#xff1a; 一组分区&#xff0c;每个RDD都会被分为多个分区&#xff0c;这些分区运行在集群的不同节点上&#xff0c;分区数决定并行计算的数量。一个计算每个分区的函数&#xff0c;就是计算逻辑。RDD之间的依赖关系&#xff0c;就…

PostgreSQL Patroni_exporter 监控 patroni高可用工具

Patroni是Cybertec公司基于python语言开发的&#xff0c;可用于使用流复制来创建&#xff0c;管理&#xff0c;维护和监视高可用性PostgreSQL集群设置的工具。 目前&#xff0c;PatroniEtcd 是最为推荐的PostgreSQL数据库高可用方案之一。 PostgreSQL有postgres_exporter监控采…

微信小程序使用 canvas 2d 实现签字板组件

本文是在微信小程序中使用 canvas 2d 来实现签字板功能&#xff1b; 效果图&#xff1a; 代码&#xff1a; 1、wxml <view><canvas id"canvas"type"2d"bindtouchstart"start"bindtouchmove"move"bindtouchend"end&qu…

iphone备份用什么软件?好用的苹果数据备份工具推荐!

众所周知&#xff0c;如果要将iPhone的数据跟电脑进行传输备份的话&#xff0c;我们需要用到iTunes这个pc工具。但是对于iTunes&#xff0c;不少人都反映这个软件比较难用&#xff0c;用不习惯。于是&#xff0c;顺应时代命运的iPhone备份同步工具就出现了。那iphone备份用什么…

VR元宇宙模拟法庭开审体验系统增加学生的参与度和学习效果

元宇宙技术是VR虚拟现实和AR增强现实技术的进一步发展和迭代&#xff0c;它在法治教育中的应用可以突破传统教育的许多限制&#xff0c;让体验者获得更加互动、沉浸和个性化的学习体验。 虚拟法庭&#xff1a; VR元宇宙开发可以模拟虚拟法庭环境&#xff0c;让学生在其中扮演不…

设计师必学!Figma怎么在线保存各种格式图片的操作方法

如今的市场上有许多协作 UI 设计软件可供选择&#xff0c;然而大部分都是国外版本的&#xff0c;对于国内设计师而言&#xff0c;由于语言上的障碍&#xff0c;使用这些软件可能会存在一定的困难。尤其是 Figma 虽然发展多年&#xff0c;但始终没有中文版&#xff0c;让众多设计…

手把手一起使用WPF开源UI框架MahApps.Metro

1、创建新项目 使用Visual Studio 2022创建新项目&#xff0c;如图点击创建新项目&#xff1a; 选择WPF应用(.NET Framework)&#xff1a; 配置新项目&#xff0c;如图&#xff1a; 随便写几个WPF默认样式控件&#xff1a; 2、安装 MahApps.Metro 点击工具->NuGet包管理器…

Iterator迭代器和Generator生成器

Iterator迭代器和for/of循环原理 Iterator迭代器规范 自己创建一个Iterator类,来实现ES6中的迭代器规范: class Iterator {constructor(assemble) {// assemble:需要迭代的数据结构this.assemble = assemble// index:记录迭代的次数(或者索引)this.index = -1}// 必须具…

excle中的条件求和SUMIF

问题&#xff1a;将每一行中红色文字的前一个值累计求和到境外总数这一列 使用的公式 自制单元格的格式计算公式&#xff1a;ctrlf3打开格式管理&#xff0c;创建如下公式&#xff0c;其中24是表示获取文字颜色 由于sumif只能直接与第二参数条件比较&#xff0c;所以先使用IF(公…

LoVT:医学图像与报告的局部表征联合学习

论文&#xff1a;https://arxiv.org/abs/2112.02889 Github&#xff1a;GitHub - philip-mueller/lovt: Localized representation learning from Vision and Text (LoVT) 摘要 摘要对比学习已被证明对未标记数据的预训练图像模型是有效的&#xff0c;在医学图像分类等任务中…

算法专题:子序列系列1

文章目录 单个序列问题示例1&#xff1a;最长递增子序列&#xff08;不连续&#xff09;思路DP数组含义递推公式初始化遍历顺序 完整版注意返回值问题 区分示例1&#xff1a;最长递增子数组区别&#xff1a;递推公式 示例2&#xff1a;最长连续递增序列&#xff08;连续&#x…

【LeetCode】不同路径Ⅱ

不同路径Ⅱ 题目描述算法流程编程代码 链接: 不同路径Ⅱ 题目描述 算法流程 编程代码 class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& ob) {int m ob.size();int n ob[0].size();vector<vector<int>>dp(m1,vecto…

LabVIEW 开发在不确定路况下自动速度辅助系统

LabVIEW 开发在不确定路况下自动速度辅助系统 智能驾驶辅助系统是汽车行业最先进的升级和尖端技术&#xff0c;智能交通系统依靠智能驾驶辅助系统在公共交通部门工作。该智能驾驶辅助系统技术包括自适应巡航控制&#xff0c;防抱死制动系统&#xff0c;安全气囊展开&#xff0…

C# 中使用ValueTask优化异步方法

概要 我们在开发过程中&#xff0c;经常使用async的异步方法&#xff0c;但是有些时候&#xff0c;异步的方法中&#xff0c;可能包含一些同步的处理。本文主要介绍通过ValueTask这个struct&#xff0c;优化异步处理的方法性能。 代码及实现 有些时候我们会缓存一些数据在内…