【XR806开发板试用】+移植rosserial到XR806
1 XR806简介
板子来源于极术社区的试用,XR806的在线网址
其主要参数:
主控 | XR806AF2L |
---|---|
DDR | SIP 288KB SRAM |
存储 | SIP 160KB Code ROM. SIP 16Mbit Flash. |
天线 | 板载WiFi/BT双天线,可共存 |
按键 | reboot按键 1,功能按键 1 |
灯 | 红色电源指示灯 1,蓝色可调节LED 1 |
供电 | Type-C 5V |
引脚 | 插针引脚 *9 |
调试方式 | Type-C(已板载串口转USB芯片) |
晶振 | 外接40MHz晶振 |
2 rosserial简介
官网
rosserial是用于非ROS设备与ROS设备进行通信的一种协议。它为非ROS设备的应用程序提供了ROS节点和服务的发布/订阅功能,使在非ROS环境中运行的应用能够通过串口或网络能够轻松地与ROS应用进行数据交互。
rosserial分为客户端和服务器两部分。rosserial客户端运行在运行在没有安装ROS的环境的应用中,通过串口或网络与运行在ROS环境中的rosserial服务器连接,并通过服务器节点在ROS中发布/订阅话题。
3 移植目标
通过rosserial使XR806能通过串口和TCP两种方式和ROS进行通信。
4 移植前准备
4.1 源码获取
[官方源码]( https://github.com/ros-drivers/rosserial.git
该仓库中的代码需要编译才能获取源码,为了直接获取源码,使用以下仓库的源码做为基础。
使用源码
该代码时属于RT-thread软件包,有较高的可信度。
clone下来的代码放在/ohosdemo/rosserial
中,文件结构:
tree -L 1
tree -L 1.
├── BUILD.gn
├── inc
├── port
└── src
- BUILD.gn :配置文件
- inc :rosserial源文件
- port:移植文件(为了和XR806适配的代码放在该文件下)
4.2 XR806 C++支持
rosserial为C++代码,需要XR806支持C++编译。
- 修改
/device/xradio/xr806/liteos_m/config.gni
文件,添加以下内容:
board_cxx_flags = []board_cxx_flags += SDK_cflags
board_cxx_flags += ["-includelog/log.h","-DVIRTUAL_HCI","-DCONFIG_ARM",#"-DNULL=((void*)0)",#"-std=c++17","-lstdc++","-fno-rtti","-fno-exceptions"]
大部分和board_cflags
的配置一样,添加的编译项 "-lstdc++",-fno-rtti,-fno-exception
是为了解决以下错误:
undefined reference to `vtable for __cxxabiv1::__si_class_type_info'
该错误的原因是C++在链接时会有相关库链接不上
2. 在/ohosdemo/rosserial/BUILD.gn
中添加以下代码:
cflags_cc = board_cxx_flags
表示支持c++编译
5 ROSserial移植核心
根据官网的移植介绍,只需要填写完以下模板即可:
class Hardware
{Hardware();// any initialization code necessary to use the serial portvoid init(); // read a byte from the serial port. -1 = failureint read()// write data to the connection to ROSvoid write(uint8_t* data, int length);// returns milliseconds since start of programunsigned long time();};
init()
:提供初始化函数,初始化串口或者TCP网络read()
:读取一个字节write(uint8_t* data, int length)
:写字符time()
:提供时间基准
6 串口通信
和串口相关的代码放在rosserial/port/UartHaedware.h
关键代码:
6.1 串口初始化:init()
函数
void init(){// HAL_Status status = HAL_ERROR;UART_InitParam param;param.baudRate = this->baudRate;param.dataBits = UART_DATA_BITS_8;param.stopBits = UART_STOP_BITS_1;param.parity = UART_PARITY_NONE;param.isAutoHwFlowCtrl = 0;HAL_UART_Init(UARTID, ¶m);}
6.2 串口读取一个字节:read()
函数
int read(){uint8_t rx_data;int32_t len=0;len = HAL_UART_Receive_IT(UARTID,&rx_data,1,1000);if(len>0){return rx_data;}else return -1;}
6.3 串口写字节:write(uint8_t* data, int length)
// write data to the connection to ROSvoid write(uint8_t* data, int length){HAL_UART_Transmit_IT(UARTID, data, length);}
6.4 时间基准:time()
函数
// returns milliseconds since start of programunsigned long time(){unsigned long temp = (unsigned long)OS_GetTime() * 1000;return temp;}
6.5 代码风格统一
为了保持ROS代码的编写风格一致,添加``rosserial/port/ros.h`
关键代码:
#define ROS_USE_TCP 0
#define ROS_USE_UART 1namespace ros
{#if ROS_USE_TCP == 1typedef NodeHandle_<TCPHardware> NodeHandle;#endif#if ROS_USE_UART == 1typedef NodeHandle_<Hardware> NodeHandle;#endif
}
#endif
可以通过宏定义选择使用串口还是TCP
7 TCP通信
7.1 wifi连接
wifi连接使用官方在ohosdemo/wlan_demo
中的代码。具体使用void wifi_device_event_test()
函数和wifi连接成功后的回调函数:void Connected_deal(int state, WifiLinkedInfo *info)
。
大致思路是在wifi连接线程中增加一个信号量,初始化信号量为0,rosserial线程会一直等待信号量有效后才会触发rosserial的相关函数,利用信号量作为两个线程间的同步,保证wifi顺利连接成功后才会进行rosserial的TCP通信。另外每次wifi重新连接后都会保证XR806会自动连接ROS。
信号量的三个关键函数:
-
OS_Status OS_SemaphoreCreate(OS_Semaphore_t *sem, uint32_t initCount, uint32_t maxCount)
初始化信号量,定义计数的初始值和最大值 -
OS_Status OS_SemaphoreWait(OS_Semaphore_t *sem, OS_Time_t waitMS)
等待信号量有效,及信号量的计数不为0;等待时间可以使无限长,该参数为OS_WAIT_FOREVER
-
OS_Status OS_SemaphoreRelease(OS_Semaphore_t *sem)
释放信号量,信号量计数加1;主要代码:
#define WIFI_DEVICE_CONNECT_AP_SSID "myyy" #define WIFI_DEVICE_CONNECT_AP_PSK "123456789"WifiEvent sta_event;void Connected_deal(int state, WifiLinkedInfo *info) {if (state == WIFI_STATE_AVALIABLE) {//释放信号量,计数加1OS_SemaphoreRelease(&ros_sem);// OS_Sleep(5);printf("\r\n======== Callback: connected========\r\n");} else if (state == WIFI_STATE_NOT_AVALIABLE) {printf("======== Callback: disconnected\n");} }void wifi_device_event_test() {const char ssid_want_connect[] = WIFI_DEVICE_CONNECT_AP_SSID;const char psk[] = WIFI_DEVICE_CONNECT_AP_PSK;//创建信号量:初始值为0,最大值为2if(OS_SemaphoreCreate(&ros_sem, 0, 2) != OS_OK){printf("\r\n sem creat fail!\r\n");return ;}sta_event.OnWifiConnectionChanged = Connected_deal;if (WIFI_SUCCESS != RegisterWifiEvent(&sta_event)) {printf("Error: RegisterWifiEvent fail\n");return;}printf("\n=========== Connect Test Start ===========\n");if (WIFI_SUCCESS != EnableWifi()) {printf("Error: EnableWifi fail.\n");return;}printf("EnableWifi Success.\n");if (WIFI_STA_ACTIVE == IsWifiActive())printf("Wifi is active.\n");OS_Sleep(1);/*.........................................................省略的代码 参见官方demo*/if (WIFI_SUCCESS != GetDeviceMacAddress(get_mac_res)) {printf("Error: GetDeviceMacAddress Fail\n");return;}printf("GetDeviceMacAddress Success.\n");for (int j = 0; j < WIFI_MAC_LEN - 1; j++) {printf("%02X:", get_mac_res[j]);}printf("%02X\n", get_mac_res[WIFI_MAC_LEN - 1]);}
7.2 TCP客户端初始化
为了不和串口的Hardware类重名,类名为TCPHardware
void init(){sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// address info!struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(serverPort_);inet_pton(AF_INET, server_, &server_addr.sin_addr);// connect!if (connect(sock_fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0){printf("connect tcp_server failed! \r\n");return;}printf("connect tcp_server successfuly! \r\n");}
7.3 TCP读取字节
int read(){char ch[2];int bytes_received = recv(sock_fd, ch, 1, 0);if (bytes_received > 0){return ch[0];}else{return -1;}}
7.4 TCP写字节
void write(const uint8_t *data, int length){send(sock_fd, data, length, 0);}
时间基准和6.4一样
8 测试
8.1 XR806 端
文件目录:
.
├── BUILD.gn
├── led_demo
│ ├── BUILD.gn
│ └── src
├── rosserial
│ ├── BUILD.gn
│ ├── inc
│ ├── port
│ └── src
└── wlan_demo├── BUILD.gn├── main.c├── test_case.c└── test_case.h
其中:
led_demo
:led灯,指示作用rosserial
:rosserialb包wlan_demo
:负载wifi连接
8.1.1 配置文件BUILD.gn
# 必须,config中定义了头文件路径和关键宏定义
import("//device/xradio/xr806/liteos_m/config.gni")# 必须,所有应用工程必须是app_打头
static_library("app_rosserial")
{configs = []sources = ["src/ros_helloworld.cpp","inc/duration.cpp","inc/time.cpp",]#必须,board_cflags是在config.gni中定义的关键宏定义cflags = board_cflags# c++cflags_cc = board_cxx_flags#必须,board_include_dirs是在config.gni中定义的文件路径include_dirs = board_include_dirs# 根据实际情况添加头文件路径include_dirs += ["//kernel/liteos_m/kernel/arch/include","./../wlan_demo/","inc","port" ,"//base/iot_hardware/peripheral/interfaces/kits","//foundation/communication/wifi_lite/interfaces/wifiservice",]
}
8.1.2 测试程序
- 编写一个发布话题:XR806_to_ROS
发布的内容为“hello world!”,时间间隔为1s
- 编写一个接收话题:ROS_to_XR860
接收的内容通过串口显示出来
#include <ros.h>
#include <std_msgs/String.h>#include <stdio.h>
#include "ohos_init.h"
#include <stdlib.h>//信号量的声明
extern OS_Semaphore_t ros_sem;static OS_Thread_t g_main_thread;static ros::NodeHandle nh;
static std_msgs::String str_msg;
static ros::Publisher chatter("XR806_to_ROS", &str_msg);
static char hello_msg[25] = "hello world!";// 回调函数
static void message_callback(const std_msgs::String& msgs)
{printf("\r\nresive:%s\r\n", msgs.data);}static ros::Subscriber<std_msgs::String> sub("ROS_to_XR860", &message_callback);static void ROSThread(void *arg)
{ //等待信号量有效if (OS_SemaphoreWait(&ros_sem, OS_WAIT_FOREVER) == OS_OK){printf("\r\n--------- star ROS----------------\r\n");nh.initNode();nh.advertise(chatter);nh.subscribe(sub);while (1){if (nh.connected()){str_msg.data = hello_msg;chatter.publish(&str_msg); }nh.spinOnce();OS_MSleep(1000);} }
}
void ROSMain(void)
{printf("\r\nROSserial Start\r\n");if (OS_ThreadCreate(&g_main_thread, "ROSThread", ROSThread, NULL,OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {printf("[ERR] Create MainThread Failed\n");}
}SYS_RUN(ROSMain);
8.1.3 编译程序
hb build -f
可能会遇到下面问题:
这个flash分配的分配有问题:
cd 到 device/xradio/xr806/xr_skylark/project/demo/audio_demo/image/xr806
用文件image_auto_cal.cfg
中的内容覆盖image_wlan_ble.cfg
中的内容。
8.2 PC ROS端
Ubuntu版本:20版(18版也可以使用)
8.2.1 创建ros空间
mkdir -p rosworkspace/src
cd rosworkspace
catkin_make
8.2.2 编写发布节点
import rospy
from std_msgs.msg import Stringif __name__ == "__main__":#2.初始化 ROS 节点:命名(唯一)rospy.init_node("talker_p")#3.实例化 发布者 对象pub = rospy.Publisher("ROS_to_XR860",String,queue_size=10)#4.组织被发布的数据,并编写逻辑发布数据msg = String() #创建 msg 对象msg_front = "hello XR806 "count = 0 #计数器 # 设置循环频率rate = rospy.Rate(1)while not rospy.is_shutdown():#拼接字符串msg.data = msg_front + str(count)pub.publish(msg)rate.sleep()#rospy.loginfo("写出的数据:%s",msg.data)count += 1
修改CMakeLists.txt
catkin_install_python(PROGRAMSscripts/test_pub.pyDESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
8.2.3 启动节点
- 启动主节点:
roscore
- 起动test_pub节点
source ./devel/setup.bashrosrun ros_test test_pub.py
- 启用serial_node节点:
串口:
rosrun rosserial_python serial_node.py _port:=/dev/ttyUSB0 _baud:=115200
如果串口连接成功,终端显示:
INFO] [1640087588.757505]: ROS Serial Python Node
[INFO] [1640087588.762633]: Connecting to /dev/ttyUSB0 at 115200 baud
[INFO] [1640087591.079099]: Requesting topics...
[INFO] [1640087591.131236]: Note: publish buffer size is 512 bytes
[INFO] [1640087591.134777]: Setup publisher on XR806_to_ROS [std_msgs/String]
[INFO] [1640087591.142147]: Note: subscribe buffer size is 512 bytes
[INFO] [1640087591.144610]: Setup subscriber on ROS_to_XR860 [std_msgs/String]
注意查看串口的权限,如果权限不足,先开启权限:
# 查看权限
ll /dev/ttyUSB*
#开启权限
sudo chmod 777 /dev/ttyUSB*
TCP
默认节点11411
rosrun rosserial_python serial_node.py tcp
如果TCP连接成功,终端显示:
[INFO] [1640232707.758952]: ROS Serial Python Node
[INFO] [1640232707.763597]: Fork_server is: False
[INFO] [1640232707.764688]: Waiting for socket connections on port 11411
[INFO] [1640232707.765650]: Waiting for socket connection
[INFO] [1640232724.857086]: Established a socket connection from 192.168.43.49 on port 60872
[INFO] [1640232724.859268]: calling startSerialClient
[INFO] [1640232726.966374]: Requesting topics...
[INFO] [1640232727.289246]: Note: publish buffer size is 512 bytes
[INFO] [1640232727.290573]: Setup publisher on XR806_to_ROS [std_msgs/String]
[INFO] [1640232727.295084]: Note: subscribe buffer size is 512 bytes
[INFO] [1640232727.296294]: Setup subscriber on ROS_to_XR860 [std_msgs/String]
8.3 结果查询
- ROS上查看话题:
rostopic list /ROS_to_XR860
/XR806_to_ROS
/diagnostics
/rosout
/rosout_agg
可见 /ROS_to_XR860和/XR806_to_ROS两个话题
- 查看/XR806_to_ROS
rostopic echo /ROS_to_XR860data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
- 查看XR806接收的 /ROS_to_XR860话题消息
串口连接
-------- star ROS----------------
resive:hello XR806 1
resive:hello XR806 2
resive:hello XR806 3
resive:hello XR806 4
resive:hello XR806 5
resive:hello XR806 6
resive:hello XR806 7
resive:hello XR806 8
resive:hello XR806 9
resive:hello XR806 10
resive:hello XR806 11
resive:hello XR806 12
TCP连接
--------- star ROS----------------
connect tcp_server successfuly!
resive:hello XR806 1
resive:hello XR806 2
resive:hello XR806 3
resive:hello XR806 4
resive:hello XR806 5
resive:hello XR806 6
resive:hello XR806 7
resive:hello XR806 8
相关文章:

【XR806开发板试用】+移植rosserial到XR806
1 XR806简介 板子来源于极术社区的试用,XR806的在线网址 其主要参数: 主控XR806AF2LDDRSIP 288KB SRAM存储SIP 160KB Code ROM. SIP 16Mbit Flash.天线板载WiFi/BT双天线,可共存按键reboot按键 1,功能按键 1灯红色电源指示灯 1…...
JSON协议详解、语法及应用
文章目录 一、什么是JSON二、JSON协议结构协议结构包括要素JSON语法规则JSON的协议结构示例 三、JSON的特点四、JSON常见应用场景 一、什么是JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以易于阅读和编写的文本格式…...
kubeasz部署k8s:v1.27.5集群
安装k8s集群相关系统及组件的详细版本号 Ubuntu 22.04.3 LTS k8s: v1.27.5 containerd: 1.6.23 etcd: v3.5.9 coredns: 1.11.1 calico: v3.24.6 安装步骤清单: 1.deploy机器做好对所有k8s node节点的免密登陆操作 2.deploy机器安装好python2版本以及pip,…...

RSA加密,解密,加签及验签
目录 1.说明 2.加密和加签的区别 3.后端加密,解密,加签及验签示例 4.前端加密,解密,加签及验签示例 5.前端加密,后端解密,前端加签,后端验签 6.注意事项 1.说明 RSA算法是一种非对称加密…...
【C++搜索】BFS:走迷宫
题目描述 一个迷宫由R行C列格子组成,有的格子里有障碍物,不能走;有的格子是空地,可以走。 给定一个迷宫,求从左上角走到右下角最少需要走多少步(数据保证一定能走到)。只能在水平方向或垂直方向走,不能斜着…...

SpringMVC 的参数绑定之list集合、Map
标签中name属性的值就是pojo类的属性名 参数绑定4 list [对象] <form action"teaupd.do" method"post"> <c:forEach items"${list}" var"tea" varStatus "status"> 教师编号:<input…...

Code Composer Studio (CCS) - Current and Local Revision
Code Composer Studio [CCS] - Current and Local Revision References 鼠标放在文件内的任意位置,鼠标右键 -> Compare With -> Local History -> Revision Time. References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/...

Vue实现多个input输入,光标自动聚焦到下一个input
遇到一个需求,需要实现和移动端短信输入一样,输入内容后,光标会进入下一个输入框 需要用到2个事件 keydown事件发生在键盘的键被按下的时候 keyup 事件在按键被释放的时候触发 <template><div class"box"><el-fo…...

人工智能技术应用笔记(二):OpenAI SORA文生视频模型技术报告全文中英对照 (GPT4翻译+人工润色)
目录 Video generation models as world simulators(视频生成模型作为世界模拟器) Turning visual data into patches (将视觉数据转换为图像块) Video compression network (视频压缩网络) Spacetim…...

Linux-系统资源管理的命令
目录 查看CPU:more /proc/meminfo 查看内存数据:free -m / free -h 查看系统版本:more /etc/issue 查看操作系统的类型:uname -a 查看主机名称:hostname 查看磁盘空间:df -h 查看某个目录空间…...

Html的<figure><figcaption>标签
Html的<figure><figcaption>标签 示例一: <figure><figcaption>figcaption001, fig标题1 </figcaption><figcaption>figcaption002, fig标题2 </figcaption><div style"width:calc(100px*2); height:calc(100px*2); back…...

Selenium实现多页面切换
当使用 Selenium 进行自动化测试或爬取数据时,有时需要处理多个页面之间的切换。以下是一些可能需要多页面切换的情况: 1、打开新窗口/页面: 在当前页面上点击链接、按钮或执行某些操作时,可能会打开一个新的窗口或页面。此时&a…...

Electron实战之菜单与托盘
菜单、托盘是桌面端应用必备的功能之一,我们通常会在菜单上配置应用常用的:偏好设置、显示隐藏、打开文件等功能,在托盘内设置:退出、重启、帮助等辅助性功能,帮助用户方便快捷地控制应用的一些系统功能。系统托盘实际…...

【Java EE初阶十六】网络原理(一)
在网络原理中主要学习TCP/IP四层模型中的重点网络协议 1. 应用层 1.1 应用程序与协议 应用层是和程序员接触最密切的; 应用程序:在应用层这里,很多时候都是程序员自定义应用层协议(步骤:1、根据需求,明确…...

51_蓝桥杯_led流水灯
一 原理图分析 二 三八译码器工作原理 三八译码器:3个输入控制8路互斥的低电平有效输出。 C B A 输出 0 0 0 Y0 0 0 1 Y1 0 1 0 Y2 0 1 1 Y3 1 0 0 Y4 1 0 1 Y5 1 1 0 Y6 1 1 1 Y7 三 锁存器工作原理 锁存器:当使…...

⭐北邮复试刷题589. N 叉树的前序遍历__DFS (力扣每日一题)
589. N 叉树的前序遍历 给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。 示例 1: 输入:root [1,null,…...

php伪协议之phar
一.phar协议 用于将多个 PHP 文件、类、库、资源(如图像、样式表)等打包成一个单独的文件。这个归档文件可以像其他 PHP 文件一样被包含(include)或执行。PHAR 归档提供了一种方便的方式来分发和安装 PHP 应用程序和库,…...

蓝桥杯电子类单片机提升三——NE555
目录 单片机资源数据包_2023 一、NE555和定时器工作模式 1.NE555的介绍 2.定时器的计数模式 二、NE555频率读取代码的实现 1.定时器0初始化 2.通过读取TH0和TL0来读取频率 3.通过中断读取频率 三、完整代码演示 通过读取TH0和TL0来读取频率 main.c 通过中断读取频…...

发掘GPT-4商业创新的潜力
GPT-4在商业创新方面的应用潜力巨大,它能够基于庞大的训练数据集和强大的语言生成能力,协助企业或个人用户在多个商业场景中推动创新: 市场分析与战略规划:GPT-4可以对历史数据、行业趋势、竞争对手信息进行深度分析,并…...

LeetCode42.接雨水(单调栈)
题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 : 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...