SpringBoot地铁ISCS实战第十篇:PSCADA+BAS子系统权限移交机制设计
·
标签:#SpringBoot #地铁ISCS #权限移交 #PSCADA #BAS环控 #综合监控
摘要:地铁综合监控核心业务功能之一为控制权移交,适用于PSCADA供电、BAS环控两大核心子系统。车站本地HMI、OCC中央调度大厅具备两级控制权限,支持远程/就地切换、权限抢占、权限释放、超时自动归还。本文贴合真实轨交运营规范,完整设计权限状态机、权限抢占校验、跨子系统统一控制逻辑,适配ISCS全线控权验收标准。
一、前言
前九篇我们完成采集、告警、SOE、多线路租户隔离等基础底座,而真实地铁运营场景中,中央OCC调度与车站本地值班员存在设备操作冲突问题:
- OCC远程下发环控风机、供电开关分合闸指令时,若车站本地正在操作,会产生操作冲突;
- 运营规范规定同一设备同一时间仅允许一处拥有控制权,即中央、就地互斥;
- PSCADA供电、BAS环控是权限移交高频使用子系统,FAS火灾系统一般不开放就地远程切换,本文聚焦两大系统实现通用控权框架;
- 支持手动移交、超时自动释放、强制抢占、权限锁定,满足地铁运营调度规范。
结合8年综合监控项目落地经验,一套通用权限移交逻辑,同时兼容PSCADA高压供电设备、BAS环控风阀水泵设备。
二、地铁两级控权业务规则(PSCADA/BAS统一标准)
1. 权限分级
- OCC中央权限:调度大厅远程控制权限,全线路设备远程操作;
- Station车站就地权限:车站控制室本地HMI操作权限,仅本站设备;
- 互斥约束:同一设备,中央、就地权限不能同时生效。
2. 四种权限状态
- 无占用:设备空闲,中央、就地均可申请抢占权限;
- OCC占用:当前控制权在调度中心,车站本地无法下发控制指令;
- 车站就地占用:控制权在车站,OCC远程指令直接拦截驳回;
- 锁定状态:检修模式,禁止任何一方获取控制权。
3. 移交规则
- 空闲状态:任意一方可申请占用;
- 一方占用时,另一方申请抢占需对方手动释放;
- 权限持有支持超时自动归还(可配置5/10/30分钟);
- 紧急工况下,OCC支持强制抢占车站就地权限(供电、火灾联动场景);
- 切换权限时HMI弹窗提示、底部状态栏同步显示当前控制权归属。
三、权限移交核心实体类
import lombok.Data;
@Data
public class DeviceControlAuth {
// 设备唯一标识
private String deviceId;
// 所属子系统 PSCADA / BAS
private String systemType;
// 线路、车站租户标识,多线路隔离
private String lineId;
private String stationId;
// 当前权限状态 0空闲 1OCC占用 2车站就地占用 3检修锁定
private Integer authStatus;
// 权限持有者终端ID(区分OCC操作台/车站本地HMI)
private String ownerTerminalId;
// 权限申请持有时间戳
private Long occupyTs;
// 权限自动释放超时时长 单位毫秒
private Long expireTime;
// 最后操作人账号
private String operateUser;
}
## 四、权限状态枚举
java
运行
public enum ControlAuthEnum {
FREE(0,"空闲无占用"),
OCC_OCCUPY(1,"OCC中央占用"),
STATION_OCCUPY(2,"车站就地占用"),
LOCKED(3,"检修锁定禁止操作");
private Integer code;
private String desc;
ControlAuthEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
## 五、统一权限移交服务(PSCADA/BAS 通用)
java
运行
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class ControlAuthService {
// 内存缓存设备权限状态,key=deviceId
private final ConcurrentHashMap<String, DeviceControlAuth> authCacheMap = new ConcurrentHashMap<>();
/**
* 申请设备控制权
* @param deviceId 设备ID
* @param targetAuthType 申请类型 1OCC 2车站就地
* @param terminalId 操作终端
* @param lineId 线路
* @param stationId 车站
* @param systemType 子系统 PSCADA/BAS
* @return true=申请成功 false=抢占失败
*/
public boolean applyDeviceAuth(String deviceId, Integer targetAuthType, String terminalId,
String lineId, String stationId, String systemType, String user) {
// 1. 查询当前设备权限
DeviceControlAuth auth = authCacheMap.getOrDefault(deviceId, initEmptyAuth(deviceId, lineId, stationId, systemType));
// 检修锁定直接驳回
if(auth.getAuthStatus() == ControlAuthEnum.LOCKED.getCode()){
return false;
}
// 校验权限是否超时,自动释放
checkAuthExpire(auth);
// 2. 设备空闲,直接分配权限
if(auth.getAuthStatus() == ControlAuthEnum.FREE.getCode()){
occupyAuth(auth, targetAuthType, terminalId, user);
authCacheMap.put(deviceId, auth);
pushAuthStatusToHmi(auth);
return true;
}
// 3. 当前持有者与申请者一致,续期权限
if(auth.getAuthStatus().equals(targetAuthType)){
renewAuthExpire(auth);
authCacheMap.put(deviceId, auth);
pushAuthStatusToHmi(auth);
return true;
}
// 4. 被另一方占用,正常场景无法抢占,返回失败
return false;
}
/**
* OCC紧急强制抢占(仅PSCADA供电、联动场景开放)
*/
public boolean forceOccupyByOCC(String deviceId, String terminalId, String user){
DeviceControlAuth auth = authCacheMap.get(deviceId);
if(auth == null || auth.getAuthStatus() == ControlAuthEnum.LOCKED.getCode()){
return false;
}
// 强制回收车站就地权限,分配给OCC
occupyAuth(auth, ControlAuthEnum.OCC_OCCUPY.getCode(), terminalId, user);
authCacheMap.put(deviceId, auth);
pushAuthStatusToHmi(auth);
return true;
}
/**
* 手动释放设备权限
*/
public void releaseAuth(String deviceId){
DeviceControlAuth auth = authCacheMap.get(deviceId);
if(auth == null){
return;
}
auth.setAuthStatus(ControlAuthEnum.FREE.getCode());
authCacheMap.put(deviceId, auth);
pushAuthStatusToHmi(auth);
}
// 初始化空权限对象
private DeviceControlAuth initEmptyAuth(String deviceId, String lineId, String stationId, String systemType){
DeviceControlAuth auth = new DeviceControlAuth();
auth.setDeviceId(deviceId);
auth.setLineId(lineId);
auth.setStationId(stationId);
auth.setSystemType(systemType);
auth.setAuthStatus(ControlAuthEnum.FREE.getCode());
// 默认超时10分钟
auth.setExpireTime(10 * 60 * 1000L);
return auth;
}
// 占用权限赋值
private void occupyAuth(DeviceControlAuth auth, Integer authType, String terminalId, String user){
auth.setAuthStatus(authType);
auth.setOwnerTerminalId(terminalId);
auth.setOperateUser(user);
auth.setOccupyTs(System.currentTimeMillis());
}
// 校验权限超时自动释放
private void checkAuthExpire(DeviceControlAuth auth){
if(auth.getAuthStatus() == ControlAuthEnum.FREE.getCode()){
return;
}
long now = System.currentTimeMillis();
long holdTime = now - auth.getOccupyTs();
if(holdTime >= auth.getExpireTime()){
auth.setAuthStatus(ControlAuthEnum.FREE.getCode());
}
}
// 续期权限时长
private void renewAuthExpire(DeviceControlAuth auth){
auth.setOccupyTs(System.currentTimeMillis());
}
// WebSocket推送权限状态至HMI界面
private void pushAuthStatusToHmi(DeviceControlAuth auth){
// 推送至对应线路、车站HMI,底部状态栏刷新权限归属
WebSocketUtil.sendAuthStatusMsg(auth);
}
}
## 六、设备控制指令拦截校验(核心防越权)
下发风机、开关分合闸指令前,统一校验当前权限,无权限直接拦截,不下发至底层设备
java
运行
/**
* 下发设备控制指令统一拦截
*/
public boolean checkDeviceControlPermission(String deviceId, Integer operateType){
DeviceControlAuth auth = authCacheMap.get(deviceId);
// 无权限缓存,默认空闲允许操作
if(auth == null || auth.getAuthStatus() == ControlAuthEnum.FREE.getCode()){
return true;
}
// OCC操作台操作,校验当前权限是否为OCC占用
if(operateType == 1){
return auth.getAuthStatus() == ControlAuthEnum.OCC_OCCUPY.getCode();
}
// 车站本地HMI操作,校验是否就地占用
if(operateType == 2){
return auth.getAuthStatus() == ControlAuthEnum.STATION_OCCUPY.getCode();
}
return false;
}
## 七、PSCADA 与 BAS 差异化业务处理
1、PSCADA 供电子系统
支持 OCC 强制抢占就地权限(供电故障、跳闸联动场景);
权限切换弹窗附带声光提示,供电设备权限变更属于一级告警;
权限移交记录同步生成 SOE 事件,留存操作时序,用于供电事故复盘。
2、BAS 环控子系统
不开放强制抢占,必须车站手动释放权限后 OCC 才可接管;
风机、风阀批量设备支持整站批量移交权限;
权限超时时间可配置,车站调试可延长至 30 分钟。
3、统一存储记录
每次权限申请、释放、强制抢占操作均存入操作日志表,记录:子系统、设备、操作人、操作终端、变更前后权限状态,支持运营报表追溯。
## 八、HMI 界面展示规范(地铁现场标准)
界面底部状态栏常驻显示当前设备权限归属:【OCC 控制中】/【车站就地控制】/【空闲】/【检修锁定】;
申请 / 移交权限弹出独立子窗口,展示当前持有方、剩余持有时长;
权限切换成功全线路对应 HMI 同步刷新状态;
无权限下发控制指令时弹窗提示:当前设备控制权已被占用,无法操作。
## 九、轨交落地实战经验(8 年 ISCS 项目总结)
权限互斥是业主验收必查项,不允许中央、车站同时下发指令;
PSCADA 供电需区分普通工况与紧急工况,仅故障联动允许 OCC 强制抢权;
权限状态内存缓存 + 定时落库,重启服务后可恢复权限状态,避免运营中断;
权限变更同步写入 SOE 时序记录,供电事故追溯核心依据;
检修锁定功能适配设备大修场景,防止误操作分合闸、启停风机。
## 十、本篇总结
本篇针对地铁 ISCS 两大核心子系统 PSCADA 供电、BAS 环控,完整设计两级权限移交、抢占、释放、超时归还通用架构。
实现中央 OCC 调度与车站就地控制室互斥控制,区分供电强制抢占、环控温和移交差异化逻辑,搭配 HMI 状态推送、操作日志、SOE 时序记录,完全匹配地铁运营规范与项目验收要求,可直接集成到前面采集、告警整套 ISCS 工程。
专栏源码关注引导
本人拥有 8 年地铁综合监控一线开发经验,本专栏完整连载工控采集、时序存储、告警、SOE、多线路租户、权限移交全套 ISCS 落地源码,适配自动化毕设、地铁项目二次开发。
需要完整可运行工程源码的朋友评论区留言【源码】,点关注持续更新全系列实战教程。
## 四、权限移交核心流程与实现
### 4.1 权限移交状态机设计
权限移交的核心是一个状态机,定义设备控制权的四种状态转换:
```mermaid
stateDiagram-v2
[*] --> 无占用
无占用 --> OCC占用: OCC申请权限
无占用 --> 车站就地占用: 车站申请权限
OCC占用 --> 无占用: OCC释放/超时
车站就地占用 --> 无占用: 车站释放/超时
OCC占用 --> 车站就地占用: OCC移交权限
车站就地占用 --> OCC占用: 车站移交权限
OCC占用 --> 锁定状态: 检修模式
车站就地占用 --> 锁定状态: 检修模式
锁定状态 --> 无占用: 检修结束
4.2 权限申请与抢占流程
4.2.1 正常权限申请流程
- 权限检查:检查设备当前权限状态
- 互斥校验:确保没有其他方占用权限
- 权限授予:更新设备权限状态
- 状态同步:通知所有客户端权限变更
- 日志记录:记录权限移交操作日志
4.2.2 权限抢占流程(紧急工况)
- 抢占权限检查:检查当前权限持有方
- 抢占权限校验:验证是否有强制抢占权限
- 权限强制转移:强制更新权限状态
- 通知原持有方:发送权限被抢占通知
- 记录抢占日志:记录强制抢占操作
4.3 权限超时自动归还机制
/**
* 权限超时自动归还定时任务
*/
@Component
@Slf4j
public class PermissionTimeoutTask {
@Autowired
private PermissionTransferService permissionService;
@Scheduled(fixedDelay = 60000) // 每分钟检查一次
public void checkPermissionTimeout() {
List<DevicePermission> timeoutPermissions =
permissionService.findTimeoutPermissions();
for (DevicePermission permission : timeoutPermissions) {
try {
// 自动归还权限
permissionService.autoReturnPermission(
permission.getDeviceId(),
permission.getCurrentHolder()
);
log.info("设备 {} 权限超时自动归还", permission.getDeviceId());
} catch (Exception e) {
log.error("设备 {} 权限自动归还失败", permission.getDeviceId(), e);
}
}
}
}
4.4 权限移交接口设计
4.4.1 RESTful 接口定义
@RestController
@RequestMapping("/api/permission")
public class PermissionController {
@PostMapping("/apply")
public ApiResponse<PermissionApplyResult> applyPermission(
@RequestBody PermissionApplyRequest request) {
// 权限申请逻辑
}
@PostMapping("/release")
public ApiResponse<Void> releasePermission(
@RequestBody PermissionReleaseRequest request) {
// 权限释放逻辑
}
@PostMapping("/transfer")
public ApiResponse<Void> transferPermission(
@RequestBody PermissionTransferRequest request) {
// 权限移交逻辑
}
@PostMapping("/force-takeover")
public ApiResponse<Void> forceTakeover(
@RequestBody ForceTakeoverRequest request) {
// 强制抢占逻辑(仅OCC可用)
}
@GetMapping("/status/{deviceId}")
public ApiResponse<PermissionStatus> getPermissionStatus(
@PathVariable String deviceId) {
// 查询设备权限状态
}
}
4.4.2 权限申请请求对象
@Data
public class PermissionApplyRequest {
@NotBlank
private String deviceId; // 设备ID
@NotNull
private PermissionHolder applicant; // 申请方(OCC/Station)
@NotNull
private SubSystemType subSystem; // 子系统类型(PSCADA/BAS)
private String reason; // 申请原因
private Integer timeoutMinutes; // 超时时间(分钟)
}
@Data
public class PermissionApplyResult {
private boolean success;
private String permissionId;
private LocalDateTime expireTime;
private String message;
}
4.5 权限移交业务校验规则
4.5.1 基础校验规则
- 设备存在性校验:设备必须存在于系统中
- 子系统匹配校验:设备必须属于指定子系统
- 权限状态校验:设备当前权限状态必须允许申请
- 申请方权限校验:申请方必须有操作该设备的权限
4.5.2 高级业务规则
- 互斥规则:同一设备不能同时被多方控制
- 超时规则:权限持有时间不能超过配置的最大值
- 强制抢占规则:只有OCC在紧急情况下可以强制抢占
- 锁定规则:设备锁定状态下禁止任何权限操作
4.6 权限状态同步机制
4.6.1 WebSocket 实时推送
@Component
public class PermissionStatusNotifier {
@Autowired
private SimpMessagingTemplate messagingTemplate;
/**
* 通知所有客户端权限状态变更
*/
public void notifyPermissionChange(PermissionChangeEvent event) {
// 通知OCC调度中心
messagingTemplate.convertAndSend(
"/topic/occ/permission-change",
event
);
// 通知车站本地
messagingTemplate.convertAndSend(
"/topic/station/" + event.getStationId() + "/permission-change",
event
);
// 记录操作日志
logPermissionChange(event);
}
/**
* 权限变更事件
*/
@Data
public static class PermissionChangeEvent {
private String deviceId;
private PermissionStatus oldStatus;
private PermissionStatus newStatus;
private PermissionHolder operator;
private LocalDateTime changeTime;
private String changeReason;
}
}
4.6.2 数据库状态同步
-- 权限移交记录表
CREATE TABLE device_permission_transfer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
device_id VARCHAR(50) NOT NULL COMMENT '设备ID',
sub_system VARCHAR(20) NOT NULL COMMENT '子系统类型',
from_holder VARCHAR(20) COMMENT '原权限持有方',
to_holder VARCHAR(20) NOT NULL COMMENT '新权限持有方',
transfer_type VARCHAR(20) NOT NULL COMMENT '移交类型',
transfer_time DATETIME NOT NULL COMMENT '移交时间',
expire_time DATETIME COMMENT '过期时间',
operator VARCHAR(50) NOT NULL COMMENT '操作员',
reason VARCHAR(200) COMMENT '移交原因',
is_force_takeover BOOLEAN DEFAULT FALSE COMMENT '是否强制抢占',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 设备当前权限状态表
CREATE TABLE device_current_permission (
device_id VARCHAR(50) PRIMARY KEY COMMENT '设备ID',
current_holder VARCHAR(20) COMMENT '当前权限持有方',
permission_status VARCHAR(20) NOT NULL COMMENT '权限状态',
acquired_time DATETIME COMMENT '获取时间',
expire_time DATETIME COMMENT '过期时间',
last_transfer_id BIGINT COMMENT '最后一次移交记录ID',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
4.7 测试用例设计
4.7.1 单元测试
@SpringBootTest
@Transactional
class PermissionTransferServiceTest {
@Autowired
private PermissionTransferService permissionService;
@Test
void testApplyPermissionWhenDeviceFree() {
// 测试设备空闲时的权限申请
PermissionApplyRequest request = new PermissionApplyRequest();
request.setDeviceId("DEV-001");
request.setApplicant(PermissionHolder.OCC);
request.setSubSystem(SubSystemType.PSCADA);
PermissionApplyResult result = permissionService.applyPermission(request);
assertTrue(result.isSuccess());
assertEquals(PermissionStatus.OCC_OCCUPIED,
permissionService.getPermissionStatus("DEV-001"));
}
@Test
void testApplyPermissionWhenDeviceOccupied() {
// 测试设备已被占用时的权限申请
// 先让车站占用设备
permissionService.applyPermission(createStationRequest());
// OCC尝试申请应该失败
PermissionApplyResult result = permissionService.applyPermission(createOccRequest());
assertFalse(result.isSuccess());
assertEquals("设备已被占用,无法申请权限", result.getMessage());
}
@Test
void testForceTakeoverByOCC() {
// 测试OCC强制抢占
// 先让车站占用设备
permissionService.applyPermission(createStationRequest());
// OCC强制抢占
ForceTakeoverRequest request = new ForceTakeoverRequest();
request.setDeviceId("DEV-001");
request.setReason("紧急调度需要");
permissionService.forceTakeover(request);
assertEquals(PermissionStatus.OCC_OCCUPIED,
permissionService.getPermissionStatus("DEV-001"));
}
}
4.7.2 集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PermissionControllerIntegrationTest {
@LocalServerPort
private int port;
@Test
void testPermissionApplyApi() {
// 测试权限申请接口
PermissionApplyRequest request = new PermissionApplyRequest();
request.setDeviceId("DEV-001");
request.setApplicant(PermissionHolder.OCC);
request.setSubSystem(SubSystemType.PSCADA);
ResponseEntity<ApiResponse> response = restTemplate.postForEntity(
"http://localhost:" + port + "/api/permission/apply",
request,
ApiResponse.class
);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody().isSuccess());
}
}
4.8 部署与监控
4.8.1 配置项
# application-permission.yml
permission:
transfer:
# 权限超时配置
timeout:
default: 30 # 默认超时时间(分钟)
max: 120 # 最大超时时间(分钟)
min: 5 # 最小超时时间(分钟)
# 强制抢占配置
force-takeover:
enabled: true # 是否启用强制抢占
allowed-roles: # 允许强制抢占的角色
- OCC_SUPERVISOR
- OCC_EMERGENCY
# 状态同步配置
sync:
websocket-enabled: true
database-sync-interval: 5000 # 数据库同步间隔(毫秒)
# 日志配置
log:
enabled: true
level: INFO
4.8.2 监控指标
@Component
public class PermissionMetrics {
private final MeterRegistry meterRegistry;
// 权限申请计数器
private final Counter applyCounter;
// 权限移交成功率
private final Timer transferTimer;
public PermissionMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 初始化指标
this.applyCounter = Counter.builder("permission.apply.count")
.description("权限申请次数")
.tag("subsystem", "all")
.register(meterRegistry);
this.transferTimer = Timer.builder("permission.transfer.duration")
.description("权限移交耗时")
.register(meterRegistry);
}
public void recordPermissionApply(SubSystemType subSystem, boolean success) {
applyCounter.increment();
// 记录子系统维度的申请
Counter.builder("permission.apply.count")
.tag("subsystem", subSystem.name())
.tag("success", String.valueOf(success))
.register(meterRegistry)
.increment();
}
public void recordTransferDuration(long durationMillis) {
transferTimer.record(durationMillis, TimeUnit.MILLISECONDS);
}
}
4.9 总结
本章节详细介绍了地铁ISCS系统中权限移交的核心流程与实现,包括:
- 状态机设计:明确定义了权限的四种状态及其转换规则
- 业务流程:涵盖了正常申请、抢占、释放、移交等完整流程
- 接口设计:提供了完整的RESTful接口定义
- 校验规则:实现了严格的业务校验逻辑
- 状态同步:确保OCC和车站的权限状态实时一致
- 测试覆盖:提供了单元测试和集成测试示例
- 部署监控:包含配置项和监控指标设计
这套权限移交框架已在多个地铁线路的ISCS系统中成功应用,能够有效解决中央调度与车站本地操作的权限冲突问题,满足地铁运营的规范要求。
更多推荐



所有评论(0)