Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
Z
zhmes-agecal
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
耿迪迪
zhmes-agecal
Commits
260dd38b
Commit
260dd38b
authored
Aug 05, 2025
by
wanghao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1 机械臂功能开发
parent
cd00704b
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
428 additions
and
114 deletions
+428
-114
application-dev.yml
zhmes-agecal-admin/src/main/resources/application-dev.yml
+2
-1
pom.xml
zhmes-agecal-system/pom.xml
+5
-1
NettyConfig.java
...em/src/main/java/com/zehong/system/netty/NettyConfig.java
+15
-10
NettyUdpServer.java
...src/main/java/com/zehong/system/netty/NettyUdpServer.java
+5
-6
NettyUdpServerHandler.java
...om/zehong/system/netty/handler/NettyUdpServerHandler.java
+158
-65
NettyServerListener.java
...com/zehong/system/netty/listener/NettyServerListener.java
+2
-2
IRobotArmCommandService.java
...va/com/zehong/system/service/IRobotArmCommandService.java
+3
-0
RobotArmCommandServiceImpl.java
...ehong/system/service/impl/RobotArmCommandServiceImpl.java
+101
-24
RobotArmMessageParser.java
...ain/java/com/zehong/system/udp/RobotArmMessageParser.java
+84
-0
UdpCommandSender.java
...src/main/java/com/zehong/system/udp/UdpCommandSender.java
+35
-0
RoboticArm.vue
zhmes-agecal-web/src/views/screen/components/RoboticArm.vue
+18
-5
No files found.
zhmes-agecal-admin/src/main/resources/application-dev.yml
View file @
260dd38b
...
...
@@ -104,9 +104,10 @@ netty:
max-frame-length
:
65535
# 最大帧长度
heartbeat-timeout
:
10
# 心跳超时时间(秒)
# 机械臂UDP配置
robot
:
arm
:
udp
:
ip
:
192.168.2.1
6
ip
:
192.168.2.1
4
port
:
6000
zhmes-agecal-system/pom.xml
View file @
260dd38b
...
...
@@ -22,7 +22,11 @@
<groupId>
com.zehong
</groupId>
<artifactId>
zhmes-agecal-common
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-configuration-processor
</artifactId>
<optional>
true
</optional>
</dependency>
</dependencies>
</project>
\ No newline at end of file
zhmes-agecal-
framework/src/main/java/com/zehong/framework/config
/NettyConfig.java
→
zhmes-agecal-
system/src/main/java/com/zehong/system/netty
/NettyConfig.java
View file @
260dd38b
package
com
.
zehong
.
framework
.
config
;
package
com
.
zehong
.
system
.
netty
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
org.springframework.stereotype.Component
;
/**
* @author lenovo
* @date 2025/7/31
* @description Netty配置类
*/
@Component
@ConfigurationProperties
(
prefix
=
"netty"
)
@Configuration
public
class
NettyConfig
{
/**
* 服务端口
*/
private
int
port
=
6001
;
@Value
(
"${netty.port}"
)
private
int
port
;
/**
* 主线程数
*/
@Value
(
"${netty.boss-group-thread-count}"
)
// 注意配置文件中的横杠命名对应类中的驼峰命名
private
int
bossGroupThreadCount
=
1
;
/**
* 工作线程数
* 工作线程数
(从配置文件读取,默认值为CPU核心数*2)
*/
private
int
workerGroupThreadCount
=
Runtime
.
getRuntime
().
availableProcessors
()
*
2
;
@Value
(
"${netty.worker-group-thread-count}"
)
// 新增配置映射
private
int
workerGroupThreadCount
;
/**
* 最大帧长度
*/
private
int
maxFrameLength
=
65535
;
@Value
(
"${netty.max-frame-length}"
)
// 配置文件中的横杠对应类中的驼峰
private
int
maxFrameLength
;
/**
* 心跳检测超时时间(秒)
*/
private
int
heartbeatTimeout
=
10
;
@Value
(
"${netty.heartbeat-timeout}"
)
// 配置文件中的横杠对应类中的驼峰
private
int
heartbeatTimeout
;
// getter和setter方法
public
int
getPort
()
{
...
...
zhmes-agecal-
framework/src/main/java/com/zehong/framework
/netty/NettyUdpServer.java
→
zhmes-agecal-
system/src/main/java/com/zehong/system
/netty/NettyUdpServer.java
View file @
260dd38b
package
com
.
zehong
.
framework
.
netty
;
package
com
.
zehong
.
system
.
netty
;
import
com.zehong.framework.config.NettyConfig
;
import
com.zehong.framework.netty.handler.NettyUdpServerHandler
;
import
com.zehong.system.netty.handler.NettyUdpServerHandler
;
import
org.springframework.stereotype.Component
;
import
io.netty.bootstrap.Bootstrap
;
...
...
@@ -19,9 +18,9 @@ import io.netty.handler.timeout.IdleStateHandler;
import
io.netty.util.CharsetUtil
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
javax.annotation.PreDestroy
;
import
javax.annotation.Resource
;
import
java.util.concurrent.TimeUnit
;
/**
* @author lenovo
...
...
@@ -32,10 +31,10 @@ import java.util.concurrent.TimeUnit;
public
class
NettyUdpServer
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
NettyUdpServer
.
class
);
@
Autowired
@
Resource
private
NettyConfig
nettyConfig
;
@
Autowired
@
Resource
private
NettyUdpServerHandler
nettyUdpServerHandler
;
private
EventLoopGroup
group
;
...
...
zhmes-agecal-
framework/src/main/java/com/zehong/framework
/netty/handler/NettyUdpServerHandler.java
→
zhmes-agecal-
system/src/main/java/com/zehong/system
/netty/handler/NettyUdpServerHandler.java
View file @
260dd38b
This diff is collapsed.
Click to expand it.
zhmes-agecal-
framework/src/main/java/com/zehong/framework
/netty/listener/NettyServerListener.java
→
zhmes-agecal-
system/src/main/java/com/zehong/system
/netty/listener/NettyServerListener.java
View file @
260dd38b
package
com
.
zehong
.
framework
.
netty
.
listener
;
package
com
.
zehong
.
system
.
netty
.
listener
;
import
com.zehong.
framework
.netty.NettyUdpServer
;
import
com.zehong.
system
.netty.NettyUdpServer
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.stereotype.Component
;
...
...
zhmes-agecal-system/src/main/java/com/zehong/system/service/IRobotArmCommandService.java
View file @
260dd38b
...
...
@@ -64,6 +64,9 @@ public interface IRobotArmCommandService
public
void
processIdleState
();
void
completeCommand
(
Long
commandId
);
void
markCommandTimeout
(
Long
commandId
);
public
void
processPendingCommands
();
public
void
completeCommand
(
String
trayCode
,
String
storeyCode
);
...
...
zhmes-agecal-system/src/main/java/com/zehong/system/service/impl/RobotArmCommandServiceImpl.java
View file @
260dd38b
package
com
.
zehong
.
system
.
service
.
impl
;
import
java.net.InetAddress
;
import
java.net.InetSocketAddress
;
import
java.net.SocketAddress
;
import
java.net.UnknownHostException
;
import
java.util.Date
;
import
java.util.List
;
import
com.zehong.common.utils.DateUtils
;
import
com.zehong.system.domain.TStoreyInfo
;
import
com.zehong.system.mapper.TStoreyInfoMapper
;
import
com.zehong.system.netty.handler.NettyUdpServerHandler
;
import
com.zehong.system.service.websocket.RobotArmWebSocketHandler
;
import
com.zehong.system.udp.UdpCommandSender
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
com.zehong.system.mapper.RobotArmCommandMapper
;
import
com.zehong.system.domain.RobotArmCommand
;
...
...
@@ -28,6 +34,9 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
RobotArmCommandServiceImpl
.
class
);
@Resource
private
NettyUdpServerHandler
nettyUdpServerHandler
;
@Resource
private
RobotArmWebSocketHandler
robotArmWebSocketHandler
;
...
...
@@ -39,47 +48,93 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
@Resource
private
UdpCommandSender
udpCommandSender
;
@Value
(
"${robot.arm.udp.ip}"
)
private
String
robotArmAddress
;
@Value
(
"${robot.arm.udp.port}"
)
private
int
robotArmPort
;
private
SocketAddress
getRobotAddress
()
{
try
{
return
new
InetSocketAddress
(
InetAddress
.
getByName
(
robotArmAddress
),
robotArmPort
);
}
catch
(
UnknownHostException
e
)
{
throw
new
RuntimeException
(
"无效的机械臂地址: "
+
robotArmAddress
);
}
}
@Override
@Transactional
public
void
processIdleState
()
{
// 1. 更新执行中状态为完成状态
robotArmCommandMapper
.
updateExecutingToCompleted
();
// 2. 优先处理上料指令
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
public
void
processPendingCommands
()
{
// 1. 处理待执行的上料指令
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
if
(!
loadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
loadingCommands
.
get
(
0
);
sendLoadingCommand
(
command
);
sendCommand
(
loadingCommands
.
get
(
0
),
"LOAD"
);
return
;
}
// 3. 处理下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
// 2. 处理待执行的下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
if
(!
unloadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
unloadingCommands
.
get
(
0
);
sendUnloadingCommand
(
command
);
sendCommand
(
unloadingCommands
.
get
(
0
),
"UNLOAD"
);
}
}
private
void
sendCommand
(
RobotArmCommand
command
,
String
commandType
)
{
// 更新状态为执行中
command
.
setStatus
(
"1"
);
command
.
setStartExecutionTime
(
new
Date
());
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
// 通过WebSocket广播更新
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
// 发送UDP指令
SocketAddress
address
=
getRobotAddress
();
udpCommandSender
.
sendCommandWithId
(
address
,
command
.
getRobotArmCommandId
(),
commandType
,
command
.
getTrayCode
(),
command
.
getStoreyCode
()
);
// 注册指令跟踪
nettyUdpServerHandler
.
registerCommandExecution
(
address
,
command
.
getRobotArmCommandId
());
}
@Override
@Transactional
public
void
processPendingCommands
()
{
// 1. 只处理待执行指令(状态为0)
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
if
(!
loadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
loadingCommands
.
get
(
0
);
sendLoadingCommand
(
command
);
return
;
public
void
completeCommand
(
Long
commandId
)
{
RobotArmCommand
command
=
robotArmCommandMapper
.
selectRobotArmCommandById
(
commandId
);
if
(
command
!=
null
&&
"1"
.
equals
(
command
.
getStatus
()))
{
command
.
setStatus
(
"3"
);
command
.
setEndExecutionTime
(
new
Date
());
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
// 通过WebSocket广播更新
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
}
}
// 2. 处理待执行的下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
if
(!
unloadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
unloadingCommands
.
get
(
0
);
sendUnloadingCommand
(
command
);
@Override
@Transactional
public
void
markCommandTimeout
(
Long
commandId
)
{
RobotArmCommand
command
=
robotArmCommandMapper
.
selectRobotArmCommandById
(
commandId
);
if
(
command
!=
null
&&
"1"
.
equals
(
command
.
getStatus
()))
{
command
.
setStatus
(
"3"
);
// 标记为未上电
robotArmCommandMapper
.
updateRobotArmCommand
(
command
);
// 通过WebSocket广播更新
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
robotArmWebSocketHandler
.
broadcastStatus
(
"idle"
);
}
}
@Override
@Transactional
public
void
completeCommand
(
String
trayCode
,
String
storeyCode
)
{
...
...
@@ -245,6 +300,28 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
return
i
;
}
@Override
@Transactional
public
void
processIdleState
()
{
// 1. 更新执行中状态为完成状态
robotArmCommandMapper
.
updateExecutingToCompleted
();
// 2. 优先处理上料指令
List
<
RobotArmCommand
>
loadingCommands
=
robotArmCommandMapper
.
selectPendingLoadingCommands
();
if
(!
loadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
loadingCommands
.
get
(
0
);
sendLoadingCommand
(
command
);
return
;
}
// 3. 处理下料指令
List
<
RobotArmCommand
>
unloadingCommands
=
robotArmCommandMapper
.
selectPendingUnloadingCommands
();
if
(!
unloadingCommands
.
isEmpty
())
{
RobotArmCommand
command
=
unloadingCommands
.
get
(
0
);
sendUnloadingCommand
(
command
);
}
}
private
void
notifyCommandsUpdate
()
{
robotArmWebSocketHandler
.
broadcastCommandUpdate
();
}
...
...
zhmes-agecal-system/src/main/java/com/zehong/system/udp/RobotArmMessageParser.java
0 → 100644
View file @
260dd38b
package
com
.
zehong
.
system
.
udp
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* @author lenovo
* @date 2025/8/5
* @description TODO
*/
public
class
RobotArmMessageParser
{
private
static
final
Pattern
STATUS_PATTERN
=
Pattern
.
compile
(
"PPP (\\w+) (\\S+)(?: (\\d+),(\\d+),(\\d+),(\\d+),)?"
);
public
static
RobotArmStatus
parseMessage
(
String
message
)
{
Matcher
matcher
=
STATUS_PATTERN
.
matcher
(
message
);
if
(
matcher
.
find
())
{
String
statusCode
=
matcher
.
group
(
1
);
String
statusText
=
matcher
.
group
(
2
);
String
x
=
matcher
.
group
(
3
);
String
y
=
matcher
.
group
(
4
);
String
z
=
matcher
.
group
(
5
);
String
r
=
matcher
.
group
(
6
);
return
new
RobotArmStatus
(
statusCode
,
statusText
,
parseCoord
(
x
),
parseCoord
(
y
),
parseCoord
(
z
),
parseCoord
(
r
));
}
return
null
;
}
private
static
Integer
parseCoord
(
String
coord
)
{
if
(
coord
==
null
||
coord
.
isEmpty
())
return
null
;
try
{
return
Integer
.
parseInt
(
coord
);
}
catch
(
NumberFormatException
e
)
{
return
null
;
}
}
public
static
class
RobotArmStatus
{
private
final
String
code
;
private
final
String
text
;
private
final
Integer
x
;
private
final
Integer
y
;
private
final
Integer
z
;
private
final
Integer
r
;
public
RobotArmStatus
(
String
code
,
String
text
,
Integer
x
,
Integer
y
,
Integer
z
,
Integer
r
)
{
this
.
code
=
code
;
this
.
text
=
text
;
this
.
x
=
x
;
this
.
y
=
y
;
this
.
z
=
z
;
this
.
r
=
r
;
}
public
boolean
isBusy
()
{
return
"BUSY"
.
equals
(
code
);
}
public
boolean
isOk
()
{
return
"OK"
.
equals
(
code
);
}
public
boolean
isComplete
()
{
return
isOk
()
&&
"完成"
.
equals
(
text
);
}
public
boolean
isFullyIdle
()
{
return
isComplete
()
&&
x
==
0
&&
y
==
0
&&
z
==
0
&&
r
==
0
;
}
// Getters
public
String
getCode
()
{
return
code
;
}
public
String
getText
()
{
return
text
;
}
public
Integer
getX
()
{
return
x
;
}
public
Integer
getY
()
{
return
y
;
}
public
Integer
getZ
()
{
return
z
;
}
public
Integer
getR
()
{
return
r
;
}
}
}
zhmes-agecal-system/src/main/java/com/zehong/system/udp/UdpCommandSender.java
View file @
260dd38b
...
...
@@ -11,7 +11,9 @@ import java.io.IOException;
import
java.net.DatagramPacket
;
import
java.net.DatagramSocket
;
import
java.net.InetAddress
;
import
java.net.SocketAddress
;
import
java.nio.charset.StandardCharsets
;
import
java.util.concurrent.locks.ReentrantLock
;
/**
* @author lenovo
...
...
@@ -31,6 +33,7 @@ public class UdpCommandSender {
private
DatagramSocket
socket
;
private
InetAddress
address
;
private
final
ReentrantLock
lock
=
new
ReentrantLock
();
@PostConstruct
public
void
init
()
{
try
{
...
...
@@ -42,6 +45,38 @@ public class UdpCommandSender {
}
}
/**
* 发送指令到指定地址
*/
public
void
sendCommand
(
SocketAddress
address
,
String
message
)
{
if
(
socket
==
null
||
address
==
null
)
{
log
.
error
(
"UDP命令发送器未初始化或地址无效"
);
return
;
}
lock
.
lock
();
try
{
byte
[]
buffer
=
message
.
getBytes
(
StandardCharsets
.
UTF_8
);
DatagramPacket
packet
=
new
DatagramPacket
(
buffer
,
buffer
.
length
,
address
);
socket
.
send
(
packet
);
log
.
info
(
"已发送UDP指令到 {}: {}"
,
address
,
message
);
}
catch
(
IOException
e
)
{
log
.
error
(
"发送UDP指令失败: {}"
,
message
,
e
);
}
finally
{
lock
.
unlock
();
}
}
/**
* 发送带指令ID的命令
*/
public
void
sendCommandWithId
(
SocketAddress
address
,
Long
commandId
,
String
commandType
,
String
trayCode
,
String
storeyCode
)
{
String
message
=
String
.
format
(
"CMD_ID:%d#%s#%s#%s"
,
commandId
,
commandType
,
trayCode
,
storeyCode
);
sendCommand
(
address
,
message
);
}
public
void
sendCommand
(
String
message
)
{
if
(
socket
==
null
||
address
==
null
)
{
log
.
error
(
"UDP命令发送器未初始化,无法发送消息"
);
...
...
zhmes-agecal-web/src/views/screen/components/RoboticArm.vue
View file @
260dd38b
...
...
@@ -152,7 +152,7 @@ export default {
name
:
'RoboticArm'
,
data
()
{
return
{
status
:
'
idle
'
,
// idle, running, error
status
:
'
unknown
'
,
// idle, running, error
showAddDialog
:
false
,
trayCode
:
''
,
loadingCommands
:
[
...
...
@@ -195,14 +195,16 @@ export default {
return
{
'idle'
:
this
.
status
===
'idle'
,
'running'
:
this
.
status
===
'running'
,
'error'
:
this
.
status
===
'error'
'error'
:
this
.
status
===
'error'
,
'unknown'
:
this
.
status
===
'unknown'
};
},
statusText
()
{
return
{
'idle'
:
'空闲中'
,
'running'
:
'运行中'
,
'error'
:
'故障'
'error'
:
'故障'
,
'unknown'
:
'未知'
}[
this
.
status
];
}
},
...
...
@@ -233,13 +235,14 @@ export default {
this
.
websocket
.
onopen
=
()
=>
{
console
.
log
(
'机械臂指令WebSocket连接成功'
);
this
.
status
=
'
running
'
;
this
.
status
=
'
unknown
'
;
this
.
sendWebSocketMessage
({
type
:
'request'
,
commands
:
[
'loading'
,
'unloading'
]
});
};
this
.
websocket
.
onmessage
=
(
event
)
=>
{
try
{
const
message
=
JSON
.
parse
(
event
.
data
);
console
.
log
(
'收到WebSocket消息:'
,
message
);
if
(
message
.
type
===
'loading'
)
{
this
.
loadingCommands
=
message
.
data
.
map
(
cmd
=>
({
robotArmCommandId
:
cmd
.
robotArmCommandId
,
...
...
@@ -488,6 +491,17 @@ export default {
animation
:
blink
1s
infinite
;
}
/* 添加未知状态样式 */
.status-light.unknown
{
background-color
:
#a0a0a0
;
color
:
#a0a0a0
;
animation
:
blink-gray
1s
infinite
;
}
@keyframes
blink-gray
{
0
%,
100
%
{
opacity
:
0.6
;
}
50
%
{
opacity
:
1
;
}
}
.status-text
{
font-size
:
14px
;
font-weight
:
bold
;
...
...
@@ -992,7 +1006,6 @@ export default {
min-width
:
auto
;
}
}
/* 指令状态样式 */
.command-item.status-pending
{
background
:
rgba
(
0
,
40
,
80
,
0.3
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment