_FYAW智能显示控制仪表的简单使用_串口通信

一、简介

        该仪表可以实时显示位移传感器的测量值,并可设定阈值等。先谈谈简单的使用方法,通过说明书,我们可以知道长按SET键可以进入参数选择状态,按“↑”“↓”可以选择该组参数的上一个或者下一个参数。

        从参数一览中可以看到有不同组的参数,当我们第一次进入参数选择状态时会进入第一组参数,可以设置不同的阈值。只不过由于是数码管,显示字母时会用一些比较奇怪的表达,比如“5”其实就是“S”,可以通过对照参数表,获取不同字母的显示。

        如果想进入其他组参数,可以在第一组参数中,通过“↑”或“↓”找到最后一个oA,然后按“←”开始设置参数,当把4位数码管都设为1,即输入密码1111后,再按下SET确定,就可以解锁密码了。此时可以通过长按SET键切换到其他组参数。

        更多功能可自行查看数据手册,不过要注意的是说明书中的参数并非全部与实际仪器一一对应,实际仪器有时会缺少一两个参数。

二、串口使用

        串口使用的是RS485电平或者RS232,千万要注意的就是A、B的接线,不要接错。这个一般需要按照说明书的来,说明书上说“7”对应的是“B”,“8”对应的是“A”,如果仪器上贴着的标签是相反的,那么可以先按照说明书上的接法,如果不行再按照仪器上的,不要忘记接地

        接下来说说具体的串口通信,仪器默认波特率是9600,通信协议是Modbus-RTU。这里推荐使用Modbus-RTU协议。

        这个通信过程并非是仪表主动不断地发送测量数据给上位机,而是需要你先发送相应命令给仪器,然后接收仪器数据。使用过程中倒是发现一些与说明书不同吻合的地方,比如读取测量值这一步,按理来说应答应如下,但实际过程中接收的是“01 04 04 42 47 3F 3F 3F 28”,即多了一个04

        不过当我们需要连续读取时就会发现单次发送实在是麻烦,现在可以有下面几种方法连续发下

1,使用llcom

        这个串口助手可以写lua脚本,以实现自动发送数据,并读取数据保存

chenxuuu/llcom: 🛠功能强大的串口工具。支持Lua自动化处理、串口调试、WinUSB、串口曲线、TCP测试、MQTT测试、编码转换、乱码恢复等功能 (github.com)

下面是可以发送命令并把读取数据保存起来的lua脚本。只不过lua脚本很难运行什么GUI,自然就无法显示图表

-- 发送数据中间间隔时间(单位ms)
local sendDelay = 100

-- 生成16进制数据“01 04 0000 0002 71CB”
local usartData = "01040000000271CB"
usartData=usartData:fromHex()

-- 获取当前日期和时间
local function get_current_datetime()
    local datetime = os.date("%Y%m%d_%H%M%S")
    return datetime
end

-- 生成带有日期和时间的文件名
local function generate_filename()
    local datetime = get_current_datetime()
    return "D:/Script/python/expr_com/data_log/log_" .. datetime .. ".txt"
end

-- 打开文件,如果文件不存在则创建,如果存在则覆盖
local filePath = generate_filename()
local file, err = io.open(filePath, "w")
if not file then
    -- 如果文件打开失败,输出错误信息
    print("无法打开文件: " .. err)
else
    print("文件成功打开: " .. filePath)
end

local value_str = ""

-- 发送数据的函数
apiSetCb("uart", function(data)
    -- 写入数据
    value_str = data:toHex():sub(7, 14)
    file:write(value_str .. "\n")  -- 添加换行符以便区分不同数据
    print(value_str)
end)

-- 循环发送任务
sys.taskInit(function()
    while true do
        -- 发送数据
        apiSendUartData(usartData)
        sys.wait(sendDelay)
    end
end)

-- 确保在脚本结束时关闭文件
--[[
atexit(function()
    if file then
        file:close()
        print("文件已关闭")
    end
end)
]]

2,使用Python脚本

        使用Python脚本直接打开串口,然后发送命令并读取数据,需要注意的是下面脚本里指定了一个串口,你需要打开设备管理器来找到实际串口并修改脚本里的串口为实际串口号。同时注意波特率设置。

        由于仪器发送的其实是浮点数据的实际表达,所以下面脚本就自动做了这个转换

        可惜的是,使用Python后,无法很好地实时更新数据到图表中,下面也就没有添加这个功能。

import serial
import struct
import time
import os
from datetime import datetime

# 配置串口
ser = serial.Serial(
    port='COM7',  # 根据实际情况修改端口号
    baudrate=115200,  # 波特率
    timeout=1  # 超时设置
)

# 要发送的16进制数据
send_data = bytes.fromhex('01040000000271CB')

# 确保 data_log 目录存在
log_dir = 'data_log'
os.makedirs(log_dir, exist_ok=True)

# 获取当前日期和时间
current_time = datetime.now().strftime('%Y%m%d_%H%M%S')
log_file_path = os.path.join(log_dir, f"log_{current_time}.csv")

# 打开日志文件
with open(log_file_path, 'w') as log_file:
    log_file.write("Timestamp,Value\n")  # 写入表头

    try:
        while True:
            # 发送16进制数据
            ser.write(send_data)
            # 从串口读取9字节的数据,并提取中间的5-8位16进制数据
            data = ser.read(9)[3:7]

            # 将16进制数据转换为32位浮点数
            try:
                float_data = struct.unpack('>f', data)[0]  # '>f' 表示大端模式
                print(f"data: {float_data}")  # 打印浮点数

                # 获取当前时间戳
                timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

                # 写入日志文件
                log_file.write(f"{timestamp},{float_data}\n")
                log_file.flush()  # 立即写入文件

            except struct.error as e:
                print(f"Error unpacking data: {e}")

            # 每100毫秒(即每秒10次)更新一次
            time.sleep(0.1)

    except KeyboardInterrupt:
        print("Program terminated by user")

    finally:
        # 关闭串口
        ser.close()

 CRC校验计算

        补充一点,为了可以使用其他命令,需要计算16位CRC校验值。比如下面可以数据手册上的读取测量值命令的CRC校验值“01 04 0000 0002 71CB”,其中71CB是16位校验值,在下面脚本中输入前面命令“01 04 0000 0002”即可

import crcmod


def calculate_crc16(data):
    # 创建一个CRC16校验对象
    crc16_func = crcmod.mkCrcFun(0x18005, initCrc=0xFFFF, rev=True, xorOut=0x0000)

    # 将16进制字符串转换为字节
    byte_data = bytes.fromhex(data)

    # 计算CRC16校验码
    crc_value = crc16_func(byte_data)

    # 交换高低字节
    crc_value = ((crc_value & 0xFF) << 8) | ((crc_value & 0xFF00) >> 8)

    return crc_value


# 输入的16进制内容
hex_data = "01 04 0000 0002"

# 去除空格并计算CRC16
hex_data_no_spaces = hex_data.replace(" ", "")
crc16_result = calculate_crc16(hex_data_no_spaces)

# 打印结果
print(f"CRC16:{crc16_result:04X}")

 8位16进制字符串转为32位浮点数据

def hex_to_float(hex_str):
    # 将 16 进制字符串转换为 32 位无符号整数
    uint_value = int(hex_str, 16)
    # 将 32 位无符号整数转换为浮点数
    float_value = struct.unpack('!f', struct.pack('!I', uint_value))[0]
    return float_value

 3,使用WPF

        就功能而言,这个方法我是最满意的,可以自己定制化写一个专用的串口助手。不过它们各有缺陷,llcom虽然可以使用lua脚本做很多自动化处理,但无法显示图表。python虽然可以显示图表,但实时更新的效果并不好。使用WPF,虽然可以实现很多功能,但需要搭建Visual Studio环境,并且需要写不少代码。

        由于时间限制,目前我只实现了简单的定时发送,转为浮点数据的功能。后续,我会添加显示图表的功能,记录日志的功能还没有做好,使用会出问题。并且我暂时并不打算在这上面花太多时间,所以没怎么考虑界面设计。

        因此,界面很简单,使用起来也很简单。

XAML文件

<Window x:Class="expr_com.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="expr_com" Height="700" Width="1000">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <!-- 可用串口 -->
            <RowDefinition Height="Auto" />
            <!-- 波特率 -->
            <RowDefinition Height="Auto" />
            <!-- 打开串口按钮 -->
            <RowDefinition Height="Auto" />
            <!-- 发送数据 -->
            <RowDefinition Height="Auto" />
            <!-- 16进制复选框 -->
            <RowDefinition Height="Auto" />
            <!-- 16进制显示复选框 -->
            <RowDefinition Height="Auto" />
            <!-- 单次发送和定时循环发送按钮 -->
            <RowDefinition Height="Auto" />
            <!-- 周期(秒) -->
            <RowDefinition Height="Auto" />
            <!-- 记录数据复选框 -->
            <RowDefinition Height="Auto" />
            <!-- 处理数据复选框及参数 -->
            <RowDefinition Height="*" />
            <!-- 接收数据框 -->
            <RowDefinition Height="*" />
            <!-- 处理数据框 -->
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="10,10,10,0">
            <Label Content="可用串口:" Width="80" />
            <ComboBox Name="cmbPorts" SelectionChanged="cmbPorts_SelectionChanged" Width="150" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="10,0,10,0">
            <Label Content="波特率:" Width="80" />
            <TextBox Name="txtBaudRate" Text="115200" Width="100" />
        </StackPanel>

        <Button Name="btnOpenPort" Content="打开串口" Click="btnOpenPort_Click" Width="120" Margin="10,0,10,10" Grid.Row="2" />

        <StackPanel Orientation="Horizontal" Grid.Row="3" Margin="10,0,10,0">
            <Label Content="发送数据:" Width="80" />
            <TextBox Name="txtSendData" Text="01040000000271CB" Width="200" />
        </StackPanel>

        <CheckBox Name="chkHex" Content="16进制" Margin="10,0,10,10" Grid.Row="4" />

        <CheckBox Name="chkHexDisplay" Content="16进制显示" Margin="10,0,10,10" Grid.Row="5" />

        <StackPanel Orientation="Horizontal" Grid.Row="6" Margin="10,0,10,0">
            <Button Name="btnSendOnce" Content="单次发送" Click="btnSendOnce_Click" Width="120" />
            <Button Name="btnStartLoopSend" Content="定时循环发送" Click="btnStartLoopSend_Click" Width="120" Margin="10,0,0,0" />
        </StackPanel>

        <StackPanel Orientation="Horizontal" Grid.Row="7" Margin="10,0,10,0">
            <Label Content="周期(秒):" Width="80" />
            <TextBox Name="txtPeriod" Text="0.1" Width="50" />
        </StackPanel>

        <CheckBox Name="chkLog" Content="记录数据" Margin="10,0,10,10" Grid.Row="8" />

        <StackPanel Orientation="Horizontal" Grid.Row="9" Margin="10,0,10,0">
            <CheckBox Name="chkProcessData" Content="处理数据" Margin="0,0,10,0" />
            <Label Content="起始位置:" Width="80" />
            <TextBox Name="txtStartIndex" Text="7" Width="50" Margin="0,0,10,0" />
            <Label Content="长度:" Width="50" />
            <TextBox Name="txtLength" Text="8" Width="50" />
        </StackPanel>

        <TextBox Name="txtReceivedData" IsReadOnly="True" Margin="10,0,10,10" Grid.Row="10" VerticalScrollBarVisibility="Auto" />

        <Grid Grid.Row="11" Margin="10,0,10,10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBox Name="txtExtractedData" IsReadOnly="True" VerticalScrollBarVisibility="Auto" Grid.Column="0" />
            <TextBox Name="txtFloatData" IsReadOnly="True" VerticalScrollBarVisibility="Auto" Grid.Column="1" />
        </Grid>
    </Grid>
</Window>

CS代码

using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Timers;

namespace expr_com
{
    public partial class MainWindow : Window
    {
        private SerialPort? _serialPort; // 声明为可为 null 的类型
        private System.Timers.Timer? _timer; // 声告为可为 null 的类型

        public MainWindow()
        {
            InitializeComponent();
            LoadAvailablePorts();
            _serialPort = null; // 初始化为 null
            _timer = null; // 初始化为 null
        }

        private void LoadAvailablePorts()
        {
            cmbPorts.ItemsSource = SerialPort.GetPortNames();
        }

        private void cmbPorts_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cmbPorts.SelectedItem != null)
            {
                txtBaudRate.Focus();
            }
        }

        private void btnOpenPort_Click(object sender, RoutedEventArgs e)
        {
            if (cmbPorts.SelectedItem == null)
            {
                MessageBox.Show("请选择一个串口。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (_serialPort == null || !_serialPort.IsOpen)
            {
                try
                {
                    _serialPort = new SerialPort(cmbPorts.SelectedItem.ToString(), int.Parse(txtBaudRate.Text))
                    {
                        ReadTimeout = 1000, // 增加读取超时时间
                        WriteTimeout = 500,
                        DataBits = 8,
                        StopBits = StopBits.One,
                        Parity = Parity.None
                    };

                    // 注册 DataReceived 事件
                    _serialPort.DataReceived += SerialPort_DataReceived;

                    _serialPort.Open();
                    btnOpenPort.Content = "关闭串口";

                    // 显示成功消息
                    txtReceivedData.AppendText("← 串口已打开。" + Environment.NewLine);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"打开串口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    txtReceivedData.AppendText($"← 打开串口失败: {ex.Message}" + Environment.NewLine);
                }
            }
            else
            {
                _serialPort.DataReceived -= SerialPort_DataReceived; // 取消注册 DataReceived 事件
                _serialPort.Close();
                btnOpenPort.Content = "打开串口";

                // 显示成功消息
                txtReceivedData.AppendText("← 串口已关闭。" + Environment.NewLine);
            }
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                string data = _serialPort.ReadExisting();
                if (!string.IsNullOrEmpty(data))
                {
                    Dispatcher.Invoke(() =>
                    {
                        if (chkHexDisplay.IsChecked == true)
                        {
                            // 将接收到的数据转换为16进制字符串
                            byte[] bytes = Encoding.ASCII.GetBytes(data);
                            string hexData = BitConverter.ToString(bytes).Replace("-", " ");
                            txtReceivedData.AppendText($"→ {hexData}" + Environment.NewLine);

                            if (chkProcessData.IsChecked == true)
                            {
                                ProcessData(hexData);
                            }
                        }
                        else
                        {
                            txtReceivedData.AppendText($"→ {data}" + Environment.NewLine);
                        }

                        // 立即滚动到底部
                        txtReceivedData.ScrollToEnd();
                    });
                }
            }
            catch (Exception ex)
            {
                // 处理其他异常
                Dispatcher.Invoke(() =>
                {
                    txtReceivedData.AppendText($"→ 读取错误: {ex.Message}" + Environment.NewLine);
                });
            }
        }

        private void LogData(string data)
        {
            string logFilePath = Path.Combine("data_log", $"log_{DateTime.Now:yyyyMMdd_HHmmss}.csv");
            File.AppendAllText(logFilePath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss},{data}{Environment.NewLine}");
        }

        private async void btnSendOnce_Click(object sender, RoutedEventArgs e)
        {
            await SendDataAsync(false);
        }

        private async void btnStartLoopSend_Click(object sender, RoutedEventArgs e)
        {
            if (_timer == null || !_timer.Enabled)
            {
                try
                {
                    double period = double.Parse(txtPeriod.Text);
                    _timer = new System.Timers.Timer(period * 1000); // 转换为毫秒
                    _timer.Elapsed += OnTimedEvent;
                    _timer.AutoReset = true;
                    _timer.Enabled = true;

                    btnStartLoopSend.Content = "停止循环发送";

                    // 显示启动消息
                    txtReceivedData.AppendText($"← 定时循环发送已启动,周期: {period} 秒。" + Environment.NewLine);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"设置定时发送失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    txtReceivedData.AppendText($"← 设置定时发送失败: {ex.Message}" + Environment.NewLine);
                }
            }
            else
            {
                _timer.Enabled = false;
                _timer.Dispose();
                _timer = null;
                btnStartLoopSend.Content = "定时循环发送";

                // 显示停止消息
                txtReceivedData.AppendText("← 定时循环发送已停止。" + Environment.NewLine);
            }
        }

        private async void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            try
            {
                await Dispatcher.InvokeAsync(async () =>
                {
                    await SendDataAsync(true);
                });
            }
            catch (Exception ex)
            {
                // 处理其他异常
                await Dispatcher.InvokeAsync(() =>
                {
                    txtReceivedData.AppendText($"← 发送错误: {ex.Message}" + Environment.NewLine);
                });
            }
        }

        private async Task SendDataAsync(bool isLoop)
        {
            try
            {
                if (_serialPort != null && _serialPort.IsOpen)
                {
                    string sendData = txtSendData.Text.Trim();
                    if (string.IsNullOrEmpty(sendData))
                    {
                        throw new InvalidOperationException("发送数据不能为空。");
                    }

                    if (chkHex.IsChecked == true)
                    {
                        byte[] hexData = Convert.FromHexString(sendData);
                        _serialPort.Write(hexData, 0, hexData.Length);
                    }
                    else
                    {
                        _serialPort.WriteLine(sendData);
                    }

                    // 显示发送数据
                    txtReceivedData.AppendText($"← {sendData}" + Environment.NewLine);

                    if (chkLog.IsChecked == true)
                    {
                        LogData(sendData);
                    }
                }
                else
                {
                    // 串口未打开,显示错误信息
                    txtReceivedData.AppendText("← 串口未打开,无法发送数据。" + Environment.NewLine);
                }
            }
            catch (Exception ex)
            {
                // 处理其他异常
                txtReceivedData.AppendText($"← 发送错误: {ex.Message}" + Environment.NewLine);
            }
        }

        private void ProcessData(string hexData)
        {
            try
            {
                // 从文本框中获取用户输入的起始位置和长度
                int startIndex = int.Parse(txtStartIndex.Text) - 1; // 减1是为了适应索引从0开始
                int length = int.Parse(txtLength.Text);

                // 去除空格
                string hexDataWithoutSpaces = hexData.Replace(" ", "");

                // 检查数据长度是否足够
                if (hexDataWithoutSpaces.Length >= startIndex + length)
                {
                    // 截取指定位置和长度的数据
                    string hexDataToProcess = hexDataWithoutSpaces.Substring(startIndex, length);

                    // 将16进制字符串转换为字节数组
                    byte[] bytes = Convert.FromHexString(hexDataToProcess);

                    // 检查字节顺序
                    if (BitConverter.IsLittleEndian)
                    {
                        // 如果系统是小端序,而数据是大端序,则需要反转字节顺序
                        Array.Reverse(bytes);
                    }

                    // 将字节数组转换为浮点数
                    float floatValue = BitConverter.ToSingle(bytes, 0);

                    // 追加结果
                    txtExtractedData.AppendText($"{hexDataToProcess}" + Environment.NewLine);
                    txtFloatData.AppendText($"{floatValue}" + Environment.NewLine);

                    // 立即滚动到底部
                    txtExtractedData.ScrollToEnd();
                    txtFloatData.ScrollToEnd();
                }
                else
                {
                    txtExtractedData.AppendText("数据长度不足,无法处理。" + Environment.NewLine);
                }
            }
            catch (FormatException ex)
            {
                txtExtractedData.AppendText($"格式错误: {ex.Message}" + Environment.NewLine);
            }
        }
    }
}

实际效果:

单次发送

循环发送

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytest 学习 @allure.severity 标记用例级别的使用

一、前言 使用allure.serverity注解&#xff0c;可以在allure报告中清晰的看到不同级别用例情况 使用等级介绍 allure提供的枚举类 二、等级介绍 二、等级介绍 blocker&#xff1a;阻塞缺陷&#xff08;功能未实现&#xff0c;无法下一步&#xff09; critical&#xff1a;…

Linux编辑器 - vim

目录 一、vim 的基本概念 1. 正常/普通/命令模式(Normal mode) 2. 插入模式(Insert mode) 3. 末行模式(last line mode) 二、vim 的基本操作 三、vim 正常模式命令集 1. 插入模式 2. 移动光标 3. 删除文字 4. 复制 5. 替换 6. 撤销上一次操作 7. 更改 8. 调至指定…

windows下编译ffmpeg4.4版本

最近在做一个利用ffmpeg库播放rtsp流的一个项目&#xff0c;需要自己编译ffmpeg源码&#xff1b;记录一下编译源码的过程&#xff0c;仅供参考&#xff1b; 目标&#xff1a; 开发环境&#xff1a;windows10系统&#xff1b; ffmpeg:ffmpeg4.4版本&#xff0c;https://downlo…

vulfocus在线靶场:骑士cms_cve_2020_35339:latest 速通手册

目录 一、启动环境&#xff0c;访问页面&#xff0c;ip:端口号/index.php?madmin,进入后台管理页面&#xff0c;账号密码都是adminadmin 二、进入之后&#xff0c;根据图片所示&#xff0c;地址后追加一下代码&#xff0c;保存修改 ​三、新开标签页访问&#xff1a;①ip:端…

鸿蒙开发:ForEach中为什么键值生成函数很重要

前言 在列表组件使用的时候&#xff0c;如List、Grid、WaterFlow等&#xff0c;循环渲染时都会使用到ForEach或者LazyForEach&#xff0c;当然了&#xff0c;也有单独使用的场景&#xff0c;如下&#xff0c;一个很简单的列表组件使用&#xff0c;这种使用方式&#xff0c;在官…

力扣 LeetCode 257. 二叉树的所有路径(Day8:二叉树)

解题思路&#xff1a; 第一次提到回溯 前序遍历 中左右 中是处理过程 左右是递归过程 注意递归三部曲的第二部&#xff0c;确定终止条件&#xff0c;这里就是遍历完叶子节点就停止&#xff0c;而不是遍历到空节点 class Solution {List<String> res new ArrayLis…

el-table实现最后一行合计功能并合并指定单元格

效果图如下&#xff1a; 表格代码如下&#xff1a; <el-table width"100%"ref"tableRef" style"margin-bottom: 15px;":data"jlData"class"tableHeader6"header-row-class-name"headerStyleTr6":row-class-n…

Java基础知识(六)

文章目录 StringString、StringBuffer、StringBuilder 的区别&#xff1f;String 为什么是不可变的?字符串拼接用“” 还是 StringBuilder?String#equals() 和 Object#equals() 有何区别&#xff1f;字符串常量池的作用了解吗&#xff1f;String s1 new String("abc&qu…

antd中使用Table手动进行分页

<Table<DataType>//获取勾选中的数据rowSelection{rowSelection}//当列过多时&#xff0c;固定某些列&#xff0c;实现左右滑动scroll{{ x: max-content }}//字段名columns{columns}// rowKey{(record) > record.login.uuid}//每一行唯一的标识&#xff0c;也是勾选…

nodejs21: 快速构建自定义设计样式Tailwind CSS

Tailwind CSS 是一个功能强大的低级 CSS 框架&#xff0c;只需书写 HTML 代码&#xff0c;无需书写 CSS&#xff0c;即可快速构建美观的网站。 1. 安装 Tailwind CSS React 项目中安装 Tailwind CSS&#xff1a; 1.1 安装 Tailwind CSS 和相关依赖 安装 Tailwind CSS: npm…

Java开发经验——JDK工具类的安全问题

摘要 本文探讨了Java开发中JDK工具类的安全问题&#xff0c;重点分析了不同工具类&#xff08;包括Java自带的Objects工具类、Apache Commons Lang、Guava和Spring Framework的ObjectUtils&#xff09;在比较对象相等性时的使用方法和优势。同时&#xff0c;文章还涉及了Integ…

web——sqliabs靶场——第十四关——布尔盲注的使用

第一步、判断注入条件 输入#看看闭合条件 是双引号闭合。 由此可以确定&#xff0c;页面存在注入&#xff0c;注入点为双引号字符型注入。 开脚本

Python Turtle绘图:重现汤姆劈树的经典瞬间

Python Turtle绘图&#xff1a;重现汤姆劈树的经典瞬间 &#x1f980; 前言 &#x1f980;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f980; 前言 &#x1f980; 《汤姆与杰瑞》&#xff08;Tom and Jerry&#xff09;是我们小时候经常看的一…

论文分享 | FuzzLLM:一种用于发现大语言模型中越狱漏洞的通用模糊测试框架

大语言模型是当前人工智能领域的前沿研究方向&#xff0c;在安全性方面大语言模型存在一些挑战和问题。分享一篇发表于2024年ICASSP会议的论文FuzzLLM&#xff0c;它设计了一种模糊测试框架&#xff0c;利用模型的能力去测试模型对越狱攻击的防护水平。 论文摘要 大语言模型中…

Spring工作流程

&#xff08;3&#xff09;案例工作流程 启动服务器初始化过程 1.服务器启动&#xff0c;执行ServletContainersInitConfig类&#xff0c;初始化web容器功能类似于以前的web.xml 2.执行createServletApplicationContext方法&#xff0c;创建了WebApplicationContext对象 该方法…

python蓝桥杯刷题2

1.最短路 题解&#xff1a;这个采用暴力枚举&#xff0c;自己数一下就好了 2.门牌制作 题解&#xff1a;门牌号从1到2020&#xff0c;使用for循环遍历一遍&#xff0c;因为range函数无法调用最后一个数字&#xff0c;所以设置成1到2021即可&#xff0c;然后每一次for循环&…

【设计模式】如何用C++实现适配器模式

【设计模式】如何用C实现适配器模式 一、问题背景 用到过很多次适配器模式&#xff0c;一直不理解为什么用这种模式&#xff0c;好像这个模式天生就该如此使用。 实际上&#xff0c;我们很多的理念都源于一些简朴的思想&#xff0c;这些思想不一定高深&#xff0c;但是在保证…

深入探讨 Puppeteer 如何使用 X 和 Y 坐标实现鼠标移动

背景介绍 现代爬虫技术中&#xff0c;模拟人类行为已成为绕过反爬虫系统的关键策略之一。无论是模拟用户点击、滚动&#xff0c;还是鼠标的轨迹移动&#xff0c;都可以为爬虫脚本带来更高的“伪装性”。在众多的自动化工具中&#xff0c;Puppeteer作为一个无头浏览器控制库&am…

ubuntu 16.04 中 VS2019 跨平台开发环境配置

su 是 “switch user” 的缩写&#xff0c;表示从当前用户切换到另一个用户。 sudo 是 “superuser do” 的缩写&#xff0c;意为“以超级用户身份执行”。 apt 是 “Advanced Package Tool” 的缩写&#xff0c;Ubuntu中用于软件包管理的命令行工具。 1、为 root 用户设置密码…

如何保证MySQL与Redis缓存的数据一致性?

文章目录 一、引言二、场景来源三、高并发解决方案1. 先更新缓存&#xff0c;再更新数据库2. 先更新数据库&#xff0c;再更新缓存3. 先删除缓存&#xff0c;再更新数据库4. 先更新数据库&#xff0c;再删除缓存小结 四、拓展方案1. 分布式锁与分布式事务2. 消息队列3. 监听bin…