java通过org.eclipse.milo实现OPCUA客户端进行连接和订阅

前言

之前写过一篇关于MQTT的方式进行物理访问的文章:SpringBoot集成MQTT,WebSocket返回前端信息_springboot mqtt websocket-CSDN博客

最近又接触到OPCUA协议,想通过java试试看能不能实现。

软件

在使用java实现之前,想着有没有什么模拟器作为服务器端能够进行发送opcua数据,网上搜到好多都是使用KEPServerEX6,下载了之后,发现学习成本好大,这个软件都不会玩,最后终于找到了Prosys OPC UA Simulation Server,相对来说,这个软件的学习成本很低。但是也有一个弊端,只能进行本地模拟。

下载地址:Prosys OPC - OPC UA Simulation Server Downloads

下载安装完成之后,打开页面就可以看到,软件生成的opcua测试地址

为了方便操作,把所有的菜单全部暴露出来,点击Options下的Switch to Basic Mode

如果需要修改这个默认的连接地址,可通过 Endpoints 菜单进行设置(我这里用的是默认的地址)。也可以在这个菜单下修改连接方式和加密方式。

也可以在Users下添加用户名和密码

Objects上自带了一些函数能够帮助我们快速进行测试,也可以自己创建(我使用的是自带的)

接下来就是代码

代码

引入依赖

        <dependency><groupId>org.eclipse.milo</groupId><artifactId>sdk-client</artifactId><version>0.6.9</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.70</version></dependency><dependency><groupId>org.eclipse.milo</groupId><artifactId>sdk-server</artifactId><version>0.6.9</version></dependency>

目前实现了两种方式:匿名方式、用户名加证书方式,还有仅用户名方式后续继续研究

匿名方式:

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.sdk.server.Session;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.UserNameIdentityToken;import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;/*** 无密码无证书无安全认证模式* @Author: majinzhong* @Data:2024/8/30*/
public class OpcUaTest {//opc ua服务端地址private final static String endPointUrl = "opc.tcp://Administrator:53530/OPCUA/SimulationServer";
//    private final static String endPointUrl = "opc.tcp://192.168.24.13:4840";public static void main(String[] args) {try {//创建OPC UA客户端OpcUaClient opcUaClient = createClient();//开启连接opcUaClient.connect().get();// 订阅消息subscribe(opcUaClient);// 写入
//            writeValue(opcUaClient);// 读取
//            readValue(opcUaClient);// 关闭连接opcUaClient.disconnect().get();} catch (Exception e) {throw new RuntimeException(e);}}/*** 创建OPC UA客户端** @return* @throws Exception*/private static OpcUaClient createClient() throws Exception {Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");Files.createDirectories(securityTempDir);if (!Files.exists(securityTempDir)) {throw new Exception("unable to create security dir: " + securityTempDir);}return OpcUaClient.create(endPointUrl,endpoints ->endpoints.stream().filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())).findFirst(),configBuilder ->configBuilder.setApplicationName(LocalizedText.english("OPC UA test")) // huazh-01.setApplicationUri("urn:eclipse:milo:client") // ns=2:s=huazh-01.device1.data-huazh//访问方式 new AnonymousProvider().setIdentityProvider(new AnonymousProvider()).setRequestTimeout(UInteger.valueOf(5000)).build());}private static void subscribe(OpcUaClient client) throws Exception {//创建发布间隔1000ms的订阅对象client.getSubscriptionManager().createSubscription(1000.0).thenAccept(t -> {//节点ns=2;s=test.device2.test2
//                    NodeId nodeId = new NodeId(4, 322);NodeId nodeId = new NodeId(3, 1003);ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);//创建监控的参数MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(1), 1000.0, null, UInteger.valueOf(10), true);//创建监控项请求//该请求最后用于创建订阅。MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);List<MonitoredItemCreateRequest> requests = new ArrayList<>();requests.add(request);//创建监控项,并且注册变量值改变时候的回调函数。t.createMonitoredItems(TimestampsToReturn.Both,requests,(item, id) -> item.setValueConsumer((it, val) -> {System.out.println("=====订阅nodeid====== :" + it.getReadValueId().getNodeId());System.out.println("=====订阅value===== :" + val.getValue().getValue());}));}).get();//持续订阅Thread.sleep(Long.MAX_VALUE);}public static void readValue(OpcUaClient client) {try {NodeId nodeId = new NodeId(3, 1002);DataValue value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();System.out.println("=====读取ua1====:" + value.getValue().getValue());} catch (Exception e) {e.printStackTrace();}}public static void writeValue(OpcUaClient client) {try {//创建变量节点 test.device2.test2NodeId nodeId = new NodeId(2, "test.device2.test2");//uda3 booleanShort value = 11;//创建Variant对象和DataValue对象Variant v = new Variant(value);DataValue dataValue = new DataValue(v, null, null);StatusCode statusCode = client.writeValue(nodeId, dataValue).get();System.out.println(statusCode);System.out.println("=====写入ua1====:" + statusCode.isGood());} catch (Exception e) {e.printStackTrace();}}
}

用户名加正式认证方式:

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;/*** 有密码有证书有安全认证模式* @Author: majinzhong* @Data:2024/8/30*/
public class OpcUaTest2 {//opc ua服务端地址private final static String endPointUrl = "opc.tcp://Administrator:53530/OPCUA/SimulationServer";
//    private final static String endPointUrl = "opc.tcp://192.168.24.13:4840";public static void main(String[] args) {try {//创建OPC UA客户端OpcUaClient opcUaClient = createClient();//开启连接opcUaClient.connect().get();// 订阅消息subscribe(opcUaClient);// 写入
//            writeValue(opcUaClient);// 读取
//            readValue(opcUaClient);// 关闭连接opcUaClient.disconnect().get();} catch (Exception e) {throw new RuntimeException(e);}}/*** 创建OPC UA客户端** @return* @throws Exception*/private static OpcUaClient createClient() throws Exception {Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");Files.createDirectories(securityTempDir);if (!Files.exists(securityTempDir)) {throw new Exception("unable to create security dir: " + securityTempDir);}KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);return OpcUaClient.create(endPointUrl,endpoints ->endpoints.stream().filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.Basic256Sha256.getUri()))
//                                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())).findFirst(),configBuilder ->configBuilder.setApplicationName(LocalizedText.english("OPC UA test")) // huazh-01.setApplicationUri("urn:eclipse:milo:client") // ns=2:s=huazh-01.device1.data-huazh//访问方式 new AnonymousProvider().setCertificate(loader.getClientCertificate()).setKeyPair(loader.getClientKeyPair()).setIdentityProvider(new UsernameProvider("TOPNC", "TOPNC123")).setRequestTimeout(UInteger.valueOf(5000)).build());}private static void subscribe(OpcUaClient client) throws Exception {//创建发布间隔1000ms的订阅对象client.getSubscriptionManager().createSubscription(1000.0).thenAccept(t -> {//节点ns=2;s=test.device2.test2
//                    NodeId nodeId = new NodeId(3, "unit/Peri_I_O.gs_ComToRM.r32_A1_Axis_ActValue");NodeId nodeId = new NodeId(3, 1003);ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);//创建监控的参数MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(1), 1000.0, null, UInteger.valueOf(10), true);//创建监控项请求//该请求最后用于创建订阅。MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);List<MonitoredItemCreateRequest> requests = new ArrayList<>();requests.add(request);//创建监控项,并且注册变量值改变时候的回调函数。t.createMonitoredItems(TimestampsToReturn.Both,requests,(item, id) -> item.setValueConsumer((it, val) -> {System.out.println("=====订阅nodeid====== :" + it.getReadValueId().getNodeId());System.out.println("=====订阅value===== :" + val.getValue().getValue());}));}).get();//持续订阅Thread.sleep(Long.MAX_VALUE);}public static void readValue(OpcUaClient client) {try {NodeId nodeId = new NodeId(3, 1002);DataValue value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();System.out.println("=====读取ua1====:" + value.getValue().getValue());} catch (Exception e) {e.printStackTrace();}}public static void writeValue(OpcUaClient client) {try {//创建变量节点 test.device2.test2NodeId nodeId = new NodeId(2, "test.device2.test2");//uda3 booleanShort value = 11;//创建Variant对象和DataValue对象Variant v = new Variant(value);DataValue dataValue = new DataValue(v, null, null);StatusCode statusCode = client.writeValue(nodeId, dataValue).get();System.out.println(statusCode);System.out.println("=====写入ua1====:" + statusCode.isGood());} catch (Exception e) {e.printStackTrace();}}
}

证书加密类

import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;/*** Created by Cryan on 2021/8/4.* TODO.OPCUA  证书生成*/class KeyStoreLoader {private final Logger logger = LoggerFactory.getLogger(getClass());private static final Pattern IP_ADDR_PATTERN = Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");// 证书别名private static final String CLIENT_ALIAS = "client-ai";// 获取私钥的密码private static final char[] PASSWORD = "password".toCharArray();// 证书对象private X509Certificate clientCertificate;// 密钥对对象private KeyPair clientKeyPair;KeyStoreLoader load(Path baseDir) throws Exception {// 创建一个使用`PKCS12`加密标准的KeyStore。KeyStore在后面将作为读取和生成证书的对象。KeyStore keyStore = KeyStore.getInstance("PKCS12");// PKCS12的加密标准的文件后缀是.pfx,其中包含了公钥和私钥。// 而其他如.der等的格式只包含公钥,私钥在另外的文件中。Path serverKeyStore = baseDir.resolve("example-client.pfx");logger.info("Loading KeyStore at {}", serverKeyStore);// 如果文件不存在则创建.pfx证书文件。if (!Files.exists(serverKeyStore)) {keyStore.load(null, PASSWORD);// 用2048位的RAS算法。`SelfSignedCertificateGenerator`为Milo库的对象。KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);// `SelfSignedCertificateBuilder`也是Milo库的对象,用来生成证书。// 中间所设置的证书属性可以自行修改。SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair).setCommonName("Eclipse Milo Example Client test").setOrganization("mjz").setOrganizationalUnit("dev").setLocalityName("mjz").setStateName("CA").setCountryCode("US").setApplicationUri("urn:eclipse:milo:client").addDnsName("localhost").addIpAddress("127.0.0.1");// Get as many hostnames and IP addresses as we can listed in the certificate.for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {if (IP_ADDR_PATTERN.matcher(hostname).matches()) {builder.addIpAddress(hostname);} else {builder.addDnsName(hostname);}}// 创建证书X509Certificate certificate = builder.build();// 设置对应私钥的别名,密码,证书链keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});try (OutputStream out = Files.newOutputStream(serverKeyStore)) {// 保存证书到输出流keyStore.store(out, PASSWORD);}} else {try (InputStream in = Files.newInputStream(serverKeyStore)) {// 如果文件存在则读取keyStore.load(in, PASSWORD);}}// 用密码获取对应别名的私钥。Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);if (serverPrivateKey instanceof PrivateKey) {// 获取对应别名的证书对象。clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);// 获取公钥PublicKey serverPublicKey = clientCertificate.getPublicKey();// 创建Keypair对象。clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);}return this;}// 返回证书X509Certificate getClientCertificate() {return clientCertificate;}// 返回密钥对KeyPair getClientKeyPair() {return clientKeyPair;}
}

代码讲解

仔细阅读代码不难发现,匿名方式和用户名加正式方式仅仅只有这一块不太一样

配置完成之后,需要修改想要订阅的节点,进行读取数据,匿名方式和用户名加证书方式一致,都是在代码的NodeId nodeId = new NodeId(3, 1003);进行修改,其中的3和1003对应软件上Objects上的

运行

一切配置好并且修改好之后,先运行匿名方式!匿名方式!匿名方式!!!(用户名加证书方式还有一个点,下面再说)

可以看到已经能够读取到节点的数据了

第一次运行用户名加证书方式的时候,会报java.lang.RuntimeException: java.util.concurrent.ExecutionException: UaException: status=Bad_SecurityChecksFailed, message=Bad_SecurityChecksFailed (code=0x80130000, description="An error occurred verifying security.")的错误,这是因为证书没有被添加信任

在Certificates下找到自己的证书,将Reject改成Trust即可。

因为代码中setApplicationUri时写的是urn:eclipse:milo:client,所以这个就是刚刚代码创建的证书。

运行用户名加证书方式

已经可以正常读取到节点数据了

补充

问题一:运行代码时,可能会遇见java.lang.RuntimeException: UaException: status=Bad_ConfigurationError, message=no endpoint selected的错误,这是因为,OPCUA服务器端没有允许这种方式(OPCUA目前我看到的有三种方式:匿名、用户名、用户名加证书),所以需要修改OPCUA服务器端添加这种方式,添加在 Endpoints菜单下,或者查看服务器端支持哪种方式,修改代码。

问题二:org.eclipse.milo.opcua.stack.core.UaException: no KeyPair configured

这种是因为没有配置密钥,代码方面出现了问题,需要在创建客户端的时候setKeyPair()

问题三:org.eclipse.milo.opcua.stack.core.UaException: no certificate configured

这种时因为没有配置证书,代码方面出现了问题,需要在创建客户端的时候setCertificate()

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

品牌力是什么?如何评估企业品牌影响力?

品牌影响力&#xff0c;其实就是指品牌在消费者心智中所占据的位置&#xff0c;以及它对消费者购买决策和行为的影响力。如果一个企业的品牌影响力越强&#xff0c;它在消费者心中的印象就越深刻&#xff0c;能够更有效地驱动消费者的购买行为&#xff0c;形成品牌忠诚度&#…

2024.9.20营养小题【2】(动态分配二维数组)

这道题里边涉及到了动态分配二维数组的知识点&#xff0c;不刷这道题我也不知道这个知识点&#xff0c;算是一个比较进阶一点的知识点了。 参考&#xff1a;C语言程序设计_动态分配二维数组_哔哩哔哩_bilibili【C/C 数据结构 】二维数组结构解析 - 知乎 (zhihu.com)

JSONC:为JSON注入注释的力量

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;广泛应用于Web开发、配置文件和数据存储等领域。 其简洁的语法和易于解析的特点&#xff0c;使得JSON成为了现代编程中不可或缺的一部分。然而&#xff0c;JSON的一个显著缺点是…

迁移学习+多模态融合,小白轻松发一区!创新性拉满!

多模态研究如今愈发火热&#xff0c;已成为各大顶级会议的投稿热门。今天&#xff0c;我为大家提供一个多模态的创新思路&#xff1a;迁移学习与多模态融合。 迁移学习多模态融合方向的优势 1.提升模型性能&#xff1a;综合更多维度优势&#xff0c;跨模态互补 2.快速适应新…

深入理解ConcurrentHashMap

HashMap为什么线程不安全 put的不安全 由于多线程对HashMap进行put操作&#xff0c;调用了HashMap的putVal()&#xff0c;具体原因&#xff1a; 1、假设两个线程A、B都在进行put操作&#xff0c;并且hash函数计算出的插入下标是相同的&#xff1b; 当线程A执行完第六行由于时间…

VuePress搭建文档网站/个人博客(详细配置)主题配置-侧边栏配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【C++ 学习】多态的基础和原理(10)

目录 前言1. 概念2. 多态的定义及实现2.1 多态的构成条件2.2 虚函数2.3 虚函数重写2.4 虚函数重写的例外2.4.1 协变2.4.1 析构函数的重写 2.5 多态调用和普通调用2.6 函数重写/函数隐藏/函数重载 的对比2.6.1 函数重写2.6.2 函数隐藏2.6.3 函数重载 2.7 C11 final 和override 3…

我的数据库第一课:从懵懂到启迪

我的数据库第一课&#xff1a;从懵懂到启迪 前言 在数字化浪潮席卷全球的今天&#xff0c;数据库作为IT技术的“活化石”&#xff0c;已经成为不可或缺的基础设施。特别是在国内&#xff0c;随着经济的飞速发展和信息化建设的推进&#xff0c;数据库技术也经历了从无到有、从…

3GPP协议入门——物理层基础(一)

1. 频段/带宽 NR指定了两个频率范围&#xff0c;FR1&#xff1a;通常称Sub 6GHz&#xff0c;也称低频5G&#xff1b;FR2&#xff1a;通常称毫米波&#xff08;Millimeter Wave&#xff09;&#xff0c;也称高频5G。 2. 子载波间隔 NR中有15kHz&#xff0c;30kHz&#xff0c;6…

【图像检索】基于颜色模型的图像内容检索,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于颜色模型的图像内容检索&#xff0c;用matlab实现。 一、案例背景和算法介绍 这…

Qt 模型视图(一):概述

文章目录 Qt 模型视图(一):概述1、模型/视图结构基本原理2、模型3、视图4、代理5、简单实例 Qt 模型视图(一):概述 ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据&#xff0c;视图组件显示模型中的数据&#xff0c;在视图组件里修改的数据会被自动…

c++ day01

格式化输入 #include <iostream> #include<iomanip> using namespace std;int main() {double num1090.123456;cout<<"num"<<num<<endl;cout<<oct<<"num"<<num<<endl;cout<<hex<<&qu…

web前端-HTML常用标签-综合案例

如图&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document&…

算法.图论-建图/拓扑排序及其拓展

文章目录 建图的三种方式邻接矩阵邻接表链式前向星 拓扑排序拓扑排序基础原理介绍拓扑排序步骤解析拓扑排序模板leetcode-课程表 拓扑排序拓展食物链计数喧闹与富有并行课程 建图的三种方式 我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星 邻接矩阵 假设我们的点的…

IMS 在线计费 IMS 离线计费

目录 1. IMS 在线计费 1.1 主要内容 1.2 IMS 在线计费架构 ​编辑1.3 IMS 在线计费方案 1.4 IMS 在线计费的关键步骤 1.5 在线计费的基本流程 1.6 IMS Information AVP 2. IMS 离线计费 2.1 IMS 离线计费架构 2.2 IMS 离线计费概述 2.3 什么时候 AS 给 CG 发送 ACR?…

深度学习:基础知识

深度学习是机器学习的一个领域 神经网络构造 一个神经元有n个输入&#xff0c;每一个输入对应一个权值w&#xff0c;神经元内会对输入与权重做乘法后求和。 感知器 由两层神经元组成的神经网络--“感知器”&#xff08;Perceptron&#xff09;,感知器只能线性划分数据。 公式…

生成式人工智能在无人机群中的应用、挑战和机遇

人工智能咨询培训老师叶梓 转载标明出处 无人机群在执行人类难以或危险任务方面有巨大潜力&#xff0c;但在复杂动态环境中学习和协调大量无人机的移动和行动&#xff0c;对传统AI方法来说是重大挑战。生成式人工智能&#xff08;Generative AI, GAI&#xff09;&#xff0c;凭…

实例讲解电动汽车钥匙Start上下电控制策略及Simulink建模方法

在电动汽车VCU开发中&#xff0c;上下电控制是其中一个核心控制内容&#xff0c;也是其他控制功能的基础&#xff0c;在钥匙ON挡上电后&#xff0c;整车电池主回路高压供电接通&#xff0c;但此时车辆电驱动回路尚未接通高压&#xff0c;如果要达到车辆具备行车准备就绪状态&am…

Qt_按钮类控件

目录 1、QAbstractButton 2、设置带图标的按钮 3、设置带有快捷键的按钮 4、QRadioButtion&#xff08;单选按钮&#xff09; 4.1 QButtonGroup 5、QCheckBox 结语 前言&#xff1a; 按钮类控件是Qt中最重要的控件类型之一&#xff0c;该类型的控件可以通过鼠标的点击…

pdf文件怎么直接翻译?使用这些工具让翻译变得简单

在全球化日益加深的职场环境中&#xff0c;处理外语PDF文件成为了许多职场人士面临的共同挑战。 面对这些“加密”的信息宝库&#xff0c;如何高效、准确地将英文pdf翻译成对应语言&#xff0c;成为了提升工作效率的关键。 以下是几款在PDF翻译领域表现出色的软件&#xff0c…