添加其他逻辑
This commit is contained in:
parent
de82ddf7cd
commit
1185f25fd3
169
CLAUDE.md
Normal file
169
CLAUDE.md
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
VWED Task Module is a FastAPI-based Python system for managing and executing task workflows for AMR (Autonomous Mobile Robot) scheduling systems. The system provides a low-code configuration tool that allows users to design and configure complex robot task flows through a visual interface.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Application Startup
|
||||||
|
```bash
|
||||||
|
# Primary method - run the main application
|
||||||
|
python app.py
|
||||||
|
|
||||||
|
# Alternative method - using uvicorn directly
|
||||||
|
uvicorn app:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
```bash
|
||||||
|
# Run database migrations
|
||||||
|
python scripts/run_migration.py
|
||||||
|
|
||||||
|
# Generate new migrations
|
||||||
|
python scripts/generate_migration.py
|
||||||
|
|
||||||
|
# Initialize database
|
||||||
|
python scripts/init_db.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
```bash
|
||||||
|
# Build and run with Docker
|
||||||
|
docker build -t vwed-task:latest .
|
||||||
|
docker run -d -p 8000:8000 --name vwed-task-container vwed-task:latest
|
||||||
|
|
||||||
|
# Or use Docker Compose (recommended)
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
The system follows a layered architecture with clear separation of concerns:
|
||||||
|
|
||||||
|
### Core Layers
|
||||||
|
- **API Layer (`routes/`)**: FastAPI REST endpoints for client communication
|
||||||
|
- **Middleware Layer (`middlewares/`)**: Request logging, error handling, and cross-cutting concerns
|
||||||
|
- **Business Logic Layer (`services/`)**: Core business logic including task execution and scheduling
|
||||||
|
- **Data Layer (`data/`)**: ORM models, database session management, and data persistence
|
||||||
|
- **Component Layer (`components/`)**: Extensible component system for task workflow building
|
||||||
|
- **Configuration Layer (`config/`)**: System settings, database configuration, and error mappings
|
||||||
|
|
||||||
|
### Key Services
|
||||||
|
- **Task Execution Engine (`services/execution/`)**: Handles task lifecycle and execution
|
||||||
|
- `task_executor.py`: Main task execution controller
|
||||||
|
- `block_executor.py`: Individual task block execution
|
||||||
|
- `task_context.py`: Execution context and variable management
|
||||||
|
- `handlers/`: Specific handlers for different component types
|
||||||
|
- **Enhanced Scheduler (`services/enhanced_scheduler/`)**: High-performance async task scheduling
|
||||||
|
- **Intelligence Layer (`services/intelligence/`)**: AI-powered features (partial implementation)
|
||||||
|
|
||||||
|
### Data Models
|
||||||
|
Key models in `data/models/`:
|
||||||
|
- `taskdef.py`: Task definition and configuration
|
||||||
|
- `taskrecord.py`: Task execution records
|
||||||
|
- `blockrecord.py`: Individual block execution records
|
||||||
|
- `tasktemplate.py`: Reusable task templates
|
||||||
|
- `calldevice.py`: Device integration configurations
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Task Management
|
||||||
|
- Visual task flow designer with drag-and-drop interface
|
||||||
|
- Version control for task configurations
|
||||||
|
- Task templates for reusability
|
||||||
|
- Real-time execution monitoring
|
||||||
|
|
||||||
|
### Component System
|
||||||
|
Components are registered in `config/components/` and include:
|
||||||
|
- Foundation components (basic operations)
|
||||||
|
- Robot scheduling components
|
||||||
|
- HTTP request components
|
||||||
|
- Storage location management
|
||||||
|
- Progress tracking components
|
||||||
|
- Script execution components
|
||||||
|
|
||||||
|
### Device Integration
|
||||||
|
- Modbus protocol support (`modbusconfig.py`)
|
||||||
|
- Generic device calling interface (`calldevice.py`)
|
||||||
|
- Extensible communication protocols
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### Database Schema Changes
|
||||||
|
1. Modify models in `data/models/`
|
||||||
|
2. Generate migration: `python scripts/generate_migration.py`
|
||||||
|
3. Apply migration: `python scripts/run_migration.py`
|
||||||
|
|
||||||
|
### Adding New Components
|
||||||
|
1. Define component configuration in `config/components/`
|
||||||
|
2. Implement handler in `services/execution/handlers/`
|
||||||
|
3. Register component in the component system
|
||||||
|
|
||||||
|
### Task Execution Flow
|
||||||
|
1. Task definition stored in `taskdef` table
|
||||||
|
2. Execution creates `taskrecord` entry
|
||||||
|
3. Individual blocks create `blockrecord` entries
|
||||||
|
4. Context variables managed through `task_context.py`
|
||||||
|
5. Component handlers execute specific business logic
|
||||||
|
|
||||||
|
### Configuration Management
|
||||||
|
- Environment-specific settings in `config/settings.py`
|
||||||
|
- Database configuration in `config/database_config.py`
|
||||||
|
- Error messages centralized in `config/error_messages.py`
|
||||||
|
|
||||||
|
## Important Technical Details
|
||||||
|
|
||||||
|
### Async Task Scheduling
|
||||||
|
The system uses a custom enhanced scheduler (`services/enhanced_scheduler/`) that:
|
||||||
|
- Maintains worker pools for concurrent task execution
|
||||||
|
- Provides priority-based task queuing
|
||||||
|
- Handles task persistence and recovery
|
||||||
|
- Manages worker lifecycle
|
||||||
|
|
||||||
|
### Component Architecture
|
||||||
|
Components follow a registry pattern:
|
||||||
|
- Each component type has a handler class
|
||||||
|
- Handlers implement standard execution interface
|
||||||
|
- Components are configurable through JSON definitions
|
||||||
|
- Extensible for new component types
|
||||||
|
|
||||||
|
### Database Session Management
|
||||||
|
- Uses SQLAlchemy ORM with session management in `data/session.py`
|
||||||
|
- Supports both sync and async database operations
|
||||||
|
- Automatic connection pooling and cleanup
|
||||||
|
|
||||||
|
## Testing and Debugging
|
||||||
|
|
||||||
|
### Test Files
|
||||||
|
- Basic tests in `tests/` directory
|
||||||
|
- Test data and fixtures available
|
||||||
|
- Integration tests for API endpoints
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
- Centralized logging configuration in `utils/logger.py`
|
||||||
|
- Application logs stored in `logs/` directory
|
||||||
|
- Structured logging for debugging task execution
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
- Swagger UI available at `http://localhost:8000/docs`
|
||||||
|
- Comprehensive API documentation in `VWED任务模块接口文档/`
|
||||||
|
|
||||||
|
## Common Troubleshooting
|
||||||
|
|
||||||
|
### Database Issues
|
||||||
|
- Check database connection in `config/database_config.py`
|
||||||
|
- Verify database migrations are up to date
|
||||||
|
- Review logs in `logs/app.log`
|
||||||
|
|
||||||
|
### Task Execution Problems
|
||||||
|
- Monitor task status through API endpoints
|
||||||
|
- Check execution logs for specific error messages
|
||||||
|
- Verify component configurations are correct
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
- Adjust scheduler worker counts in settings
|
||||||
|
- Monitor database connection pool usage
|
||||||
|
- Review task complexity and component efficiency
|
@ -565,6 +565,9 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
|
|||||||
|
|
||||||
##### 心跳检测
|
##### 心跳检测
|
||||||
|
|
||||||
|
支持两种格式的心跳检测:
|
||||||
|
|
||||||
|
**JSON格式心跳:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "ping",
|
"type": "ping",
|
||||||
@ -572,10 +575,105 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**字符串格式心跳:**
|
||||||
|
```
|
||||||
|
ping
|
||||||
|
```
|
||||||
|
|
||||||
#### 服务器消息格式
|
#### 服务器消息格式
|
||||||
|
|
||||||
与库位状态实时推送接口相同,参见上述文档。
|
与库位状态实时推送接口相同,参见上述文档。
|
||||||
|
|
||||||
|
##### 心跳响应
|
||||||
|
|
||||||
|
对于JSON格式心跳请求,服务器返回JSON格式响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "pong",
|
||||||
|
"timestamp": "2025-06-11T12:00:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
对于字符串格式心跳请求,服务器返回字符串响应:
|
||||||
|
```
|
||||||
|
pong
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 连接示例
|
||||||
|
|
||||||
|
##### JavaScript客户端示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 建立WebSocket连接
|
||||||
|
const sceneId = "your-scene-id";
|
||||||
|
const wsUrl = `ws://localhost:8000/ws/storage-location-broadcast/${sceneId}`;
|
||||||
|
|
||||||
|
const websocket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
// 连接建立
|
||||||
|
websocket.onopen = function(event) {
|
||||||
|
console.log("库位状态广播WebSocket连接已建立");
|
||||||
|
|
||||||
|
// 发送JSON格式心跳包
|
||||||
|
websocket.send(JSON.stringify({
|
||||||
|
type: "ping",
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 或者发送字符串格式心跳包
|
||||||
|
// websocket.send("ping");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 接收消息
|
||||||
|
websocket.onmessage = function(event) {
|
||||||
|
try {
|
||||||
|
// 尝试解析JSON格式响应
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch(data.type) {
|
||||||
|
case "storage_location_update":
|
||||||
|
console.log("库位状态更新:", data.data);
|
||||||
|
break;
|
||||||
|
case "storage_location_status_change":
|
||||||
|
console.log("库位状态变化:", data.layer_name, data.action);
|
||||||
|
break;
|
||||||
|
case "pong":
|
||||||
|
console.log("JSON格式心跳响应:", data.timestamp);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
console.error("服务器错误:", data.message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 处理字符串格式响应
|
||||||
|
if (event.data === "pong") {
|
||||||
|
console.log("字符串格式心跳响应: pong");
|
||||||
|
} else {
|
||||||
|
console.log("收到未知消息:", event.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定期发送心跳
|
||||||
|
setInterval(() => {
|
||||||
|
if (websocket.readyState === WebSocket.OPEN) {
|
||||||
|
// 可以选择JSON格式或字符串格式
|
||||||
|
websocket.send("ping"); // 字符串格式
|
||||||
|
// websocket.send(JSON.stringify({type: "ping", timestamp: new Date().toISOString()})); // JSON格式
|
||||||
|
}
|
||||||
|
}, 30000); // 每30秒发送一次心跳
|
||||||
|
|
||||||
|
// 连接关闭
|
||||||
|
websocket.onclose = function(event) {
|
||||||
|
console.log("库位状态广播WebSocket连接已关闭");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 连接错误
|
||||||
|
websocket.onerror = function(error) {
|
||||||
|
console.error("库位状态广播WebSocket连接错误:", error);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
#### 使用场景
|
#### 使用场景
|
||||||
|
|
||||||
1. **监控面板**:多个监控客户端同时监听库位状态变化
|
1. **监控面板**:多个监控客户端同时监听库位状态变化
|
||||||
@ -636,4 +734,5 @@ ws://your-domain/ws/storage-location-broadcast/{scene_id}
|
|||||||
| 版本 | 日期 | 更新内容 |
|
| 版本 | 日期 | 更新内容 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 |
|
| 1.0.0 | 2025-06-11 | 初始版本,支持任务执行结果实时推送和广播功能 |
|
||||||
| 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |
|
| 1.1.0 | 2025-06-11 | 新增库位状态实时推送和广播功能,支持多种过滤条件和状态变化通知 |
|
||||||
|
| 1.2.0 | 2025-07-18 | 更新心跳检测机制,所有WebSocket接口现在支持JSON和字符串两种格式的心跳检测 |
|
@ -95,14 +95,12 @@
|
|||||||
{
|
{
|
||||||
"station_name": "STATION-A-001",
|
"station_name": "STATION-A-001",
|
||||||
"area_name": "一般存储区B",
|
"area_name": "一般存储区B",
|
||||||
"max_layers": 2,
|
"max_layers": 1,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"layer_name": "1-1" // 库位名称
|
"layer_name": "DSA_2_1_1" // 库位名称
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer_name": "1-2" // 库位名称
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -111,32 +109,28 @@
|
|||||||
"max_layers": 1,
|
"max_layers": 1,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"layer_name": "2-1" //库位名称
|
"layer_name": "DSA_2_1_2" //库位名称
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"station_name": "STATION-B-004",
|
"station_name": "STATION-B-004",
|
||||||
"area_name": "一般存储区C",
|
"area_name": "一般存储区B",
|
||||||
"max_layers": 3,
|
"max_layers": 1,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"layer_name": "4-1" //库位名称
|
"layer_name": "DSA_2_1_3" //库位名称
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer_name": "4-2" // 库位名称
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"layer_name": "4-3"// 库位名称
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"station_name": "STATION-B-003",
|
"station_name": "STATION-B-003",
|
||||||
|
"area_name": "一般存储区B",
|
||||||
"max_layers": 1,
|
"max_layers": 1,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"layer_name": "3-1" //库位名称
|
"layer_name": "DSA_2_1_4" //库位名称
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
242
VWED任务模块接口文档/外部任务接口文档.md
Normal file
242
VWED任务模块接口文档/外部任务接口文档.md
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
# 外部任务接口文档
|
||||||
|
|
||||||
|
## 外部任务模块接口
|
||||||
|
|
||||||
|
### 1. 创建新任务 (create_new_task)
|
||||||
|
|
||||||
|
#### 接口描述
|
||||||
|
根据任务类型自动选择对应的任务模板并执行任务,用于外部系统调用创建和启动任务。
|
||||||
|
|
||||||
|
#### 请求方式
|
||||||
|
- **HTTP方法**: POST
|
||||||
|
- **接口路径**: `/newTask`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 描述 |
|
||||||
|
|-------|------|-----|------|
|
||||||
|
| ReqCode | String | 是 | 请求唯一标识码 |
|
||||||
|
| SourceID | String | 否 | 来源ID |
|
||||||
|
| TargetID | String | 是 | 目标ID |
|
||||||
|
| TaskType | TaskTypeEnum | 是 | 任务类型 |
|
||||||
|
|
||||||
|
#### 任务类型枚举 (TaskTypeEnum)
|
||||||
|
|
||||||
|
| 枚举值 | 描述 | 模板ID |
|
||||||
|
|-------|------|-------|
|
||||||
|
| GG2MP | 高柜到MP | template_gg2mp_id |
|
||||||
|
| GGFK2MP | 高柜发库到MP | template_ggfk2mp_id |
|
||||||
|
| GT2MP | 高台到MP | 571985c1-cfa5-4186-8acd-6e3868a5e08c |
|
||||||
|
| GTFK2MP | 高台发库到MP | template_gtfk2mp_id |
|
||||||
|
| ZG2MP | 中柜到MP | template_zg2mp_id |
|
||||||
|
| QZ2MP | 清洗到MP | template_qz2mp_id |
|
||||||
|
| LG2MP | 料柜到MP | template_lg2mp_id |
|
||||||
|
| PHZ2MP | 配货站到MP | template_phz2mp_id |
|
||||||
|
| MP2GG | MP到高柜 | template_mp2gg_id |
|
||||||
|
| MP2GGFK | MP到高柜发库 | template_mp2ggfk_id |
|
||||||
|
| MP2GT | MP到高台 | template_mp2gt_id |
|
||||||
|
| MP2GTFK | MP到高台发库 | template_mp2gtfk_id |
|
||||||
|
| MP2ZG | MP到中柜 | template_mp2zg_id |
|
||||||
|
| MP2QZ | MP到清洗 | template_mp2qz_id |
|
||||||
|
| MP2LG | MP到料柜 | template_mp2lg_id |
|
||||||
|
| MP2PHZ | MP到配货站 | template_mp2phz_id |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ReqCode": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"SourceID": "WH-A-001",
|
||||||
|
"TargetID": "WH-B-002",
|
||||||
|
"TaskType": "GT2MP"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应参数
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"message": "成功",
|
||||||
|
"rowCount": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
1. 不支持的任务类型时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"message": "不支持的任务类型: UNKNOWN_TYPE",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 任务启动失败时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"message": "任务启动失败",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 系统异常时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"reqCode": "123e4567-e89b-12d3-a456-426614174000",
|
||||||
|
"message": "创建任务失败: [详细错误信息]",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. AGV调度任务 (GenAgvSchedulingTask)
|
||||||
|
|
||||||
|
#### 接口描述
|
||||||
|
用于生成AGV调度任务,支持更丰富的调度参数配置,包括位置路径、安全密钥验证等。
|
||||||
|
|
||||||
|
#### 请求方式
|
||||||
|
- **HTTP方法**: POST
|
||||||
|
- **接口路径**: `/GenAgvSchedulingTask`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 描述 |
|
||||||
|
|-------|------|-----|------|
|
||||||
|
| ReqCode | String | 是 | 请求唯一标识码 |
|
||||||
|
| TaskTyp | String | 是 | 任务类型(使用TaskTypeEnum中的值) |
|
||||||
|
| SecurityKey | String | 是 | 安全密钥 |
|
||||||
|
| Type | String | 是 | 类型标识 |
|
||||||
|
| TaskCode | String | 是 | 任务代码 |
|
||||||
|
| SubType | String | 是 | 子类型标识 |
|
||||||
|
| AreaPositonCode | String | 是 | 区域位置代码 |
|
||||||
|
| AreaPositonName | String | 是 | 区域位置名称 |
|
||||||
|
| PositionCodePath | Array<PositionCodePath> | 是 | 位置代码路径 |
|
||||||
|
| ClientCode | String | 否 | 客户端代码 |
|
||||||
|
| TokenCode | String | 否 | 令牌代码 |
|
||||||
|
|
||||||
|
#### PositionCodePath 参数结构
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 描述 |
|
||||||
|
|-------|------|-----|------|
|
||||||
|
| PositionCode | String | 是 | 位置代码 |
|
||||||
|
| Type | String | 是 | 类型 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ReqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"TaskTyp": "MP2GGFK",
|
||||||
|
"SecurityKey": "2JN4YW5IJSQRXDEPBT3YQKEGJMT2GS1X",
|
||||||
|
"Type": "1",
|
||||||
|
"TaskCode": "d074b178e9c54a72a484a782d8955aa3",
|
||||||
|
"SubType": "3",
|
||||||
|
"AreaPositonCode": "1-1",
|
||||||
|
"AreaPositonName": "1-1,ZK",
|
||||||
|
"PositionCodePath": [
|
||||||
|
{
|
||||||
|
"PositionCode": "AP190",
|
||||||
|
"Type": "00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PositionCode": "AP313",
|
||||||
|
"Type": "04"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ClientCode": "",
|
||||||
|
"TokenCode": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应参数
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"reqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"message": "成功",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
1. 安全密钥为空时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"reqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"message": "安全密钥不能为空",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 不支持的任务类型时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"reqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"message": "不支持的任务类型: UNKNOWN_TYPE",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 任务启动失败时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"reqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"message": "任务启动失败",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 系统异常时:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"reqCode": "a98334b940af48328290389a64b71bcc",
|
||||||
|
"message": "创建任务失败: [详细错误信息]",
|
||||||
|
"rowCount": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通用说明
|
||||||
|
|
||||||
|
### 响应码说明
|
||||||
|
|
||||||
|
| 响应码 | 描述 |
|
||||||
|
|-------|------|
|
||||||
|
| 0 | 成功 |
|
||||||
|
| 400 | 请求参数错误 |
|
||||||
|
| 404 | 资源不存在 |
|
||||||
|
| 409 | 冲突(如任务已在运行) |
|
||||||
|
| 500 | 服务器内部错误 |
|
||||||
|
|
||||||
|
### 安全说明
|
||||||
|
|
||||||
|
1. 所有接口都需要提供有效的请求标识码(ReqCode)
|
||||||
|
2. AGV调度任务接口需要提供有效的安全密钥(SecurityKey)
|
||||||
|
3. 系统会记录所有请求的客户端信息和来源
|
||||||
|
4. 建议在生产环境中启用HTTPS
|
||||||
|
|
||||||
|
### 调用注意事项
|
||||||
|
|
||||||
|
1. 请求标识码(ReqCode)应保证全局唯一性
|
||||||
|
2. 任务类型必须在支持的枚举范围内
|
||||||
|
3. 系统会根据任务类型自动选择对应的任务模板
|
||||||
|
4. 位置代码路径用于指定AGV的行驶路径
|
||||||
|
5. 系统支持并发调用,但相同设备的相同任务可能有限制
|
||||||
|
|
||||||
|
### 监控和日志
|
||||||
|
|
||||||
|
1. 所有接口调用都会记录详细日志
|
||||||
|
2. 支持通过ReqCode追踪任务执行状态
|
||||||
|
3. 错误信息会包含足够的上下文用于排查问题
|
||||||
|
4. 建议定期监控接口响应时间和成功率
|
@ -22,7 +22,13 @@
|
|||||||
|
|
||||||
### 1. 获取库位列表 (GET /api/vwed-operate-point/list)
|
### 1. 获取库位列表 (GET /api/vwed-operate-point/list)
|
||||||
|
|
||||||
获取库位列表,支持多种筛选条件和分页查询。
|
获取库位列表,支持多种筛选条件、排序功能和分页查询。
|
||||||
|
|
||||||
|
**排序功能说明:**
|
||||||
|
- `layer_name_sort`:支持按层名称排序,如 GSD_1_1_1 格式,先按数字部分排序,然后按字母部分排序
|
||||||
|
- `station_name_sort`:支持按站点名称排序,如 AP1 格式,先按数字部分排序,然后按字母部分排序
|
||||||
|
- 排序规则:首先按数字排序,如果数字都一样的话,按照字母排序
|
||||||
|
- 可以同时使用两种排序,优先按 layer_name_sort 排序,然后按 station_name_sort 排序
|
||||||
|
|
||||||
#### 请求参数
|
#### 请求参数
|
||||||
|
|
||||||
@ -38,6 +44,8 @@
|
|||||||
| is_empty_tray | boolean | 否 | 是否空托盘 |
|
| is_empty_tray | boolean | 否 | 是否空托盘 |
|
||||||
| include_operate_point_info | boolean | 否 | 是否包含动作点信息(默认true) |
|
| include_operate_point_info | boolean | 否 | 是否包含动作点信息(默认true) |
|
||||||
| include_extended_fields | boolean | 否 | 是否包含扩展字段(默认true) |
|
| include_extended_fields | boolean | 否 | 是否包含扩展字段(默认true) |
|
||||||
|
| layer_name_sort | boolean | 否 | 层名称排序:true(升序,默认)、false(降序) |
|
||||||
|
| station_name_sort | boolean | 否 | 站点名称排序:true(升序,默认)、false(降序) |
|
||||||
| page | integer | 否 | 页码(默认1) |
|
| page | integer | 否 | 页码(默认1) |
|
||||||
| page_size | integer | 否 | 每页数量(默认20,最大100) |
|
| page_size | integer | 否 | 每页数量(默认20,最大100) |
|
||||||
|
|
||||||
@ -152,10 +160,23 @@
|
|||||||
|
|
||||||
#### 调用示例
|
#### 调用示例
|
||||||
|
|
||||||
|
**基本查询:**
|
||||||
```bash
|
```bash
|
||||||
GET /api/vwed-operate-point/list?scene_id=scene-001&is_occupied=false&page=1&page_size=20
|
GET /api/vwed-operate-point/list?scene_id=scene-001&is_occupied=false&page=1&page_size=20
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**带排序的查询:**
|
||||||
|
```bash
|
||||||
|
# 按层名称升序排序
|
||||||
|
GET /api/vwed-operate-point/list?layer_name_sort=true&page=1&page_size=20
|
||||||
|
|
||||||
|
# 按站点名称降序排序
|
||||||
|
GET /api/vwed-operate-point/list?station_name_sort=false&page=1&page_size=20
|
||||||
|
|
||||||
|
# 组合排序:先按层名称升序,再按站点名称降序
|
||||||
|
GET /api/vwed-operate-point/list?layer_name_sort=true&station_name_sort=false&page=1&page_size=20
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. 更新库位状态 (PUT /api/vwed-operate-point/status)
|
### 2. 更新库位状态 (PUT /api/vwed-operate-point/status)
|
||||||
|
BIN
__pycache__/app.cpython-311.pyc
Normal file
BIN
__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
config/__pycache__/error_code_mapping.cpython-311.pyc
Normal file
BIN
config/__pycache__/error_code_mapping.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
config/__pycache__/tf_api_config.cpython-311.pyc
Normal file
BIN
config/__pycache__/tf_api_config.cpython-311.pyc
Normal file
Binary file not shown.
@ -53,25 +53,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "preBinTask",
|
|
||||||
"type": "String",
|
|
||||||
"label": "预置binTask",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "param",
|
|
||||||
"type": "String",
|
|
||||||
"label": "参数",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "pick",
|
"name": "pick",
|
||||||
"type": "String",
|
"type": "String",
|
||||||
@ -100,25 +81,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "JackHeight",
|
|
||||||
"type": "String",
|
|
||||||
"label": "设置顶升高度 JackHeight",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "targetHeight",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "目标高度",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "drop",
|
"name": "drop",
|
||||||
"type": "String",
|
"type": "String",
|
||||||
@ -137,258 +99,7 @@
|
|||||||
"options": []
|
"options": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"name": "Wait",
|
|
||||||
"type": "String",
|
|
||||||
"label": "等待 Wait",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "wait_time",
|
|
||||||
"type": "Integer",
|
|
||||||
"label": "等待时间(ms)",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ForkLoad",
|
|
||||||
"type": "String",
|
|
||||||
"label": "叉车取货 ForkLoad",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "start_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "起始高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "end_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "结束高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "inspired_unique",
|
|
||||||
"type": "Boolean",
|
|
||||||
"label": "启用识别",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unique_file",
|
|
||||||
"type": "String",
|
|
||||||
"label": "识别文件",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "special_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "识别后高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ForkUnload",
|
|
||||||
"type": "String",
|
|
||||||
"label": "降低叉齿 ForkUnload",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "start_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "起始高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "end_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "结束高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "inspired_unique",
|
|
||||||
"type": "Boolean",
|
|
||||||
"label": "启用识别",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "unique_file",
|
|
||||||
"type": "String",
|
|
||||||
"label": "识别文件",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "special_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "识别后高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ForkHeight",
|
|
||||||
"type": "String",
|
|
||||||
"label": "边走边升降叉齿 ForkHeight",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "start_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "起始高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fork_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "货叉行走过程中举升高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "end_height",
|
|
||||||
"type": "Double",
|
|
||||||
"label": "结束高度",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CustomCommand",
|
|
||||||
"type": "String",
|
|
||||||
"label": "自定义指令",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "command",
|
|
||||||
"type": "String",
|
|
||||||
"label": "指令方式",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "operation_name",
|
|
||||||
"type": "String",
|
|
||||||
"label": "操作名",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "param",
|
|
||||||
"type": "JSONArray",
|
|
||||||
"label": "属性",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "syspy/setDO.py",
|
|
||||||
"type": "String",
|
|
||||||
"label": "syspy/setDO.py",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "DO",
|
|
||||||
"type": "JSONArray",
|
|
||||||
"label": "设置DO",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "syspy/waitDO.py",
|
|
||||||
"type": "String",
|
|
||||||
"label": "syspy/waitDO.py",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "DO",
|
|
||||||
"type": "JSONArray",
|
|
||||||
"label": "设置DO",
|
|
||||||
"description": "",
|
|
||||||
"required": true,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "timeout",
|
|
||||||
"type": "Integer",
|
|
||||||
"label": "超时时间(秒)",
|
|
||||||
"description": "",
|
|
||||||
"required": false,
|
|
||||||
"defaultValue": null,
|
|
||||||
"options": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -564,7 +564,7 @@
|
|||||||
"label": "加锁者",
|
"label": "加锁者",
|
||||||
"description": "",
|
"description": "",
|
||||||
"required": false,
|
"required": false,
|
||||||
"defaultValue": true,
|
"defaultValue": null,
|
||||||
"options": []
|
"options": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -645,7 +645,7 @@
|
|||||||
"label": "解锁者",
|
"label": "解锁者",
|
||||||
"description": "",
|
"description": "",
|
||||||
"required": false,
|
"required": false,
|
||||||
"defaultValue": true,
|
"defaultValue": null,
|
||||||
"options": []
|
"options": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -326,8 +326,8 @@ class BaseConfig(BaseSettings):
|
|||||||
TASK_EXPORT_IV: str = Field(default="vwed1234task5678", env="TASK_EXPORT_IV") # 初始化向量
|
TASK_EXPORT_IV: str = Field(default="vwed1234task5678", env="TASK_EXPORT_IV") # 初始化向量
|
||||||
|
|
||||||
# 增强版任务调度器配置
|
# 增强版任务调度器配置
|
||||||
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 15 # 最小工作线程数
|
TASK_SCHEDULER_MIN_WORKER_COUNT: int = 100 # 最小工作线程数
|
||||||
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 30 # 最大工作线程数
|
TASK_SCHEDULER_MAX_WORKER_COUNT: int = 150 # 最大工作线程数
|
||||||
TASK_SCHEDULER_QUEUE_COUNT: int = 3 # 队列数量
|
TASK_SCHEDULER_QUEUE_COUNT: int = 3 # 队列数量
|
||||||
TASK_SCHEDULER_QUEUE_THRESHOLD_PERCENTILES: List[float] = [0.1, 0.3, 1.0] # 队列阈值百分比配置
|
TASK_SCHEDULER_QUEUE_THRESHOLD_PERCENTILES: List[float] = [0.1, 0.3, 1.0] # 队列阈值百分比配置
|
||||||
TASK_SCHEDULER_WORKER_RATIOS: List[float] = [0.6, 0.3, 0.1] # 工作线程分配比例
|
TASK_SCHEDULER_WORKER_RATIOS: List[float] = [0.6, 0.3, 0.1] # 工作线程分配比例
|
||||||
@ -341,13 +341,10 @@ class BaseConfig(BaseSettings):
|
|||||||
TASK_SCHEDULER_CPU_THRESHOLD: float = 80.0 # CPU使用率阈值(百分比)
|
TASK_SCHEDULER_CPU_THRESHOLD: float = 80.0 # CPU使用率阈值(百分比)
|
||||||
TASK_SCHEDULER_MEMORY_THRESHOLD: float = 80.0 # 内存使用率阈值(百分比)
|
TASK_SCHEDULER_MEMORY_THRESHOLD: float = 80.0 # 内存使用率阈值(百分比)
|
||||||
TASK_SCHEDULER_AUTO_SCALE_INTERVAL: int = 120 # 自动扩缩容间隔(秒)
|
TASK_SCHEDULER_AUTO_SCALE_INTERVAL: int = 120 # 自动扩缩容间隔(秒)
|
||||||
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 120 # 心跳间隔(秒)
|
TASK_SCHEDULER_WORKER_HEARTBEAT_INTERVAL: int = 1200 # 心跳间隔(秒)
|
||||||
|
|
||||||
# 告警同步配置
|
# 告警同步配置
|
||||||
ALERT_SYNC_ENABLED: bool = Field(default=True, env="ALERT_SYNC_ENABLED") # 是否启用告警同步
|
ALERT_SYNC_ENABLED: bool = Field(default=True, env="ALERT_SYNC_ENABLED") # 是否启用告警同步
|
||||||
ALERT_SYNC_HOST: str = Field(default="192.168.189.80", env="ALERT_SYNC_HOST") # 主系统IP
|
|
||||||
ALERT_SYNC_PORT: int = Field(default=8080, env="ALERT_SYNC_PORT") # 主系统端口
|
|
||||||
ALERT_SYNC_API_PATH: str = Field(default="/jeecg-boot/warning", env="ALERT_SYNC_API_PATH") # 告警API路径
|
|
||||||
ALERT_SYNC_TIMEOUT: int = Field(default=10, env="ALERT_SYNC_TIMEOUT") # 请求超时时间(秒)
|
ALERT_SYNC_TIMEOUT: int = Field(default=10, env="ALERT_SYNC_TIMEOUT") # 请求超时时间(秒)
|
||||||
ALERT_SYNC_RETRY_COUNT: int = Field(default=3, env="ALERT_SYNC_RETRY_COUNT") # 重试次数
|
ALERT_SYNC_RETRY_COUNT: int = Field(default=3, env="ALERT_SYNC_RETRY_COUNT") # 重试次数
|
||||||
ALERT_SYNC_RETRY_DELAY: int = Field(default=1, env="ALERT_SYNC_RETRY_DELAY") # 重试延迟(秒)
|
ALERT_SYNC_RETRY_DELAY: int = Field(default=1, env="ALERT_SYNC_RETRY_DELAY") # 重试延迟(秒)
|
||||||
@ -365,6 +362,14 @@ class BaseConfig(BaseSettings):
|
|||||||
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = Field(default=15, env="MAP_GENERAL_STORAGE_CAPACITY_PER_POINT") # 一般库区每个动作点增加的容量
|
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = Field(default=15, env="MAP_GENERAL_STORAGE_CAPACITY_PER_POINT") # 一般库区每个动作点增加的容量
|
||||||
MAP_GENERAL_STORAGE_LAYER_MULTIPLIER: float = Field(default=1.2, env="MAP_GENERAL_STORAGE_LAYER_MULTIPLIER") # 一般库区分层倍数
|
MAP_GENERAL_STORAGE_LAYER_MULTIPLIER: float = Field(default=1.2, env="MAP_GENERAL_STORAGE_LAYER_MULTIPLIER") # 一般库区分层倍数
|
||||||
|
|
||||||
|
# 库位获取队列配置
|
||||||
|
STORAGE_QUEUE_MAX_WORKERS: int = Field(default=10, env="STORAGE_QUEUE_MAX_WORKERS") # 队列工作者数量
|
||||||
|
STORAGE_QUEUE_MAX_SIZE: int = Field(default=2000, env="STORAGE_QUEUE_MAX_SIZE") # 队列最大数量
|
||||||
|
STORAGE_QUEUE_ENABLE_TIMEOUT: bool = Field(default=False, env="STORAGE_QUEUE_ENABLE_TIMEOUT") # 是否启用超时
|
||||||
|
STORAGE_QUEUE_DEFAULT_TIMEOUT: int = Field(default=3600, env="STORAGE_QUEUE_DEFAULT_TIMEOUT") # 默认超时时间(秒),仅在启用超时时生效
|
||||||
|
STORAGE_QUEUE_CLEANUP_INTERVAL: int = Field(default=300, env="STORAGE_QUEUE_CLEANUP_INTERVAL") # 清理已完成请求的间隔(秒)
|
||||||
|
STORAGE_QUEUE_COMPLETED_REQUEST_TTL: int = Field(default=3600, env="STORAGE_QUEUE_COMPLETED_REQUEST_TTL") # 已完成请求保留时间(秒)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DATABASE_URL(self) -> str:
|
def DATABASE_URL(self) -> str:
|
||||||
"""构建数据库连接URL"""
|
"""构建数据库连接URL"""
|
||||||
@ -396,10 +401,10 @@ class BaseConfig(BaseSettings):
|
|||||||
return f"redis://:{encoded_password}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
return f"redis://:{encoded_password}@{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
||||||
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"
|
||||||
|
|
||||||
@property
|
# @property
|
||||||
def ALERT_SYNC_URL(self) -> str:
|
# def ALERT_SYNC_URL(self) -> str:
|
||||||
"""构建告警同步URL"""
|
# """构建告警同步URL"""
|
||||||
return f"http://{self.ALERT_SYNC_HOST}:{self.ALERT_SYNC_PORT}{self.ALERT_SYNC_API_PATH}"
|
# return f"http://{self.ALERT_SYNC_HOST}:{self.ALERT_SYNC_PORT}{self.ALERT_SYNC_API_PATH}"
|
||||||
|
|
||||||
# 更新为Pydantic v2的配置方式
|
# 更新为Pydantic v2的配置方式
|
||||||
model_config = {
|
model_config = {
|
||||||
@ -423,6 +428,11 @@ class DevelopmentConfig(BaseConfig):
|
|||||||
MAP_DENSE_STORAGE_CAPACITY_PER_POINT: int = 5
|
MAP_DENSE_STORAGE_CAPACITY_PER_POINT: int = 5
|
||||||
MAP_GENERAL_STORAGE_BASE_CAPACITY: int = 15
|
MAP_GENERAL_STORAGE_BASE_CAPACITY: int = 15
|
||||||
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = 8
|
MAP_GENERAL_STORAGE_CAPACITY_PER_POINT: int = 8
|
||||||
|
|
||||||
|
# 开发环境队列配置
|
||||||
|
# STORAGE_QUEUE_MAX_WORKERS: int = 5
|
||||||
|
# STORAGE_QUEUE_MAX_SIZE: int = 500
|
||||||
|
# STORAGE_QUEUE_ENABLE_TIMEOUT: bool = False # 开发环境也不启用超时
|
||||||
|
|
||||||
|
|
||||||
# 根据环境变量选择配置
|
# 根据环境变量选择配置
|
||||||
|
Binary file not shown.
BIN
data/enum/__pycache__/call_device_enum.cpython-311.pyc
Normal file
BIN
data/enum/__pycache__/call_device_enum.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/enum/__pycache__/modbus_config_enum.cpython-311.pyc
Normal file
BIN
data/enum/__pycache__/modbus_config_enum.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/enum/__pycache__/task_block_record_enum.cpython-311.pyc
Normal file
BIN
data/enum/__pycache__/task_block_record_enum.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/enum/__pycache__/task_input_param_enum.cpython-311.pyc
Normal file
BIN
data/enum/__pycache__/task_input_param_enum.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/enum/__pycache__/task_record_enum.cpython-311.pyc
Normal file
BIN
data/enum/__pycache__/task_record_enum.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
data/models/__pycache__/calldevice.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/calldevice.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/extended_property.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/extended_property.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/interfacedef.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/interfacedef.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
data/models/__pycache__/operate_point.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/operate_point.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/operate_point_layer.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/operate_point_layer.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
data/models/__pycache__/storage_area.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/storage_area.cpython-311.pyc
Normal file
Binary file not shown.
BIN
data/models/__pycache__/storage_location_log.cpython-311.pyc
Normal file
BIN
data/models/__pycache__/storage_location_log.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
121225
logs/app.log
121225
logs/app.log
File diff suppressed because it is too large
Load Diff
96521
logs/app.log.1
Normal file
96521
logs/app.log.1
Normal file
File diff suppressed because it is too large
Load Diff
BIN
middlewares/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
middlewares/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
middlewares/__pycache__/error_handlers.cpython-311.pyc
Normal file
BIN
middlewares/__pycache__/error_handlers.cpython-311.pyc
Normal file
Binary file not shown.
BIN
middlewares/__pycache__/request_logger.cpython-311.pyc
Normal file
BIN
middlewares/__pycache__/request_logger.cpython-311.pyc
Normal file
Binary file not shown.
@ -19,6 +19,7 @@ from routes.modbus_config_api import router as modbus_config_router
|
|||||||
from routes.websocket_api import router as websocket_router
|
from routes.websocket_api import router as websocket_router
|
||||||
from routes.map_data_api import router as map_data_router
|
from routes.map_data_api import router as map_data_router
|
||||||
from routes.operate_point_api import router as operate_point_router
|
from routes.operate_point_api import router as operate_point_router
|
||||||
|
from routes.external_task_api import router as external_task_router
|
||||||
|
|
||||||
# 路由列表,按照注册顺序排列
|
# 路由列表,按照注册顺序排列
|
||||||
routers = [
|
routers = [
|
||||||
@ -33,7 +34,8 @@ routers = [
|
|||||||
modbus_config_router,
|
modbus_config_router,
|
||||||
websocket_router,
|
websocket_router,
|
||||||
map_data_router,
|
map_data_router,
|
||||||
operate_point_router
|
operate_point_router,
|
||||||
|
external_task_router
|
||||||
]
|
]
|
||||||
|
|
||||||
def register_routers(app):
|
def register_routers(app):
|
||||||
|
BIN
routes/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
routes/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
routes/__pycache__/calldevice_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/calldevice_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/common_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/common_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/database.cpython-311.pyc
Normal file
BIN
routes/__pycache__/database.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/external_task_api.cpython-312.pyc
Normal file
BIN
routes/__pycache__/external_task_api.cpython-312.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/map_data_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/map_data_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/modbus_config_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/modbus_config_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/operate_point_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/operate_point_api.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
routes/__pycache__/script_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/script_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/task_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/task_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/task_edit_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/task_edit_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/task_record_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/task_record_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/template_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/template_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/__pycache__/websocket_api.cpython-311.pyc
Normal file
BIN
routes/__pycache__/websocket_api.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
383
routes/external_task_api.py
Normal file
383
routes/external_task_api.py
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
外部任务接口API模块
|
||||||
|
提供外部系统调用的任务创建接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any
|
||||||
|
from fastapi import APIRouter, Body, Request
|
||||||
|
from routes.model.external_task_model import ExternalTaskRequest, ExternalTaskResponse, TaskTypeEnum, GenAgvSchedulingTaskRequest
|
||||||
|
from routes.model.task_edit_model import TaskEditRunRequest, TaskInputParamNew, InputParamType
|
||||||
|
from services.task_edit_service import TaskEditService
|
||||||
|
from routes.common_api import format_response, error_response
|
||||||
|
from utils.logger import get_logger
|
||||||
|
from data.enum.task_record_enum import SourceType
|
||||||
|
from config.tf_api_config import TF_API_TOKEN
|
||||||
|
|
||||||
|
# tf_api_config = get_tf_api_config()
|
||||||
|
|
||||||
|
# 创建路由
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="",
|
||||||
|
tags=["外部任务接口"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logger = get_logger("app.external_task_api")
|
||||||
|
|
||||||
|
# 任务类型到模板ID的映射
|
||||||
|
TASK_TYPE_TEMPLATE_MAPPING = {
|
||||||
|
TaskTypeEnum.GG2MP: "template_gg2mp_id", # 高柜到MP模板ID
|
||||||
|
TaskTypeEnum.GGFK2MP: "template_ggfk2mp_id", # 高柜发库到MP模板ID
|
||||||
|
TaskTypeEnum.GT2MP: "571985c1-cfa5-4186-8acd-6e3868a5e08c", # 高台到MP模板ID
|
||||||
|
TaskTypeEnum.GTFK2MP: "template_gtfk2mp_id", # 高台发库到MP模板ID
|
||||||
|
TaskTypeEnum.ZG2MP: "template_zg2mp_id", # 中柜到MP模板ID
|
||||||
|
TaskTypeEnum.QZ2MP: "template_qz2mp_id", # 清洗到MP模板ID
|
||||||
|
TaskTypeEnum.LG2MP: "template_lg2mp_id", # 料柜到MP模板ID
|
||||||
|
TaskTypeEnum.PHZ2MP: "template_phz2mp_id", # 配货站到MP模板ID
|
||||||
|
TaskTypeEnum.MP2GG: "template_mp2gg_id", # MP到高柜模板ID
|
||||||
|
TaskTypeEnum.MP2GGFK: "571985c1-cfa5-4186-8acd-6e3868a5e08c", # MP到高柜发库模板ID
|
||||||
|
TaskTypeEnum.MP2GT: "template_mp2gt_id", # MP到高台模板ID
|
||||||
|
TaskTypeEnum.MP2GTFK: "template_mp2gtfk_id", # MP到高台发库模板ID
|
||||||
|
TaskTypeEnum.MP2ZG: "template_mp2zg_id", # MP到中柜模板ID
|
||||||
|
TaskTypeEnum.MP2QZ: "template_mp2qz_id", # MP到清洗模板ID
|
||||||
|
TaskTypeEnum.MP2LG: "template_mp2lg_id", # MP到料柜模板ID
|
||||||
|
TaskTypeEnum.MP2PHZ: "template_mp2phz_id", # MP到配货站模板ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/newTask")
|
||||||
|
async def create_new_task(request: Request, task_request: ExternalTaskRequest = Body(...)):
|
||||||
|
"""
|
||||||
|
创建新任务接口
|
||||||
|
根据任务类型自动选择对应的任务模板并执行任务
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_request: 外部任务创建请求,包含ReqCode、SourceID、TargetID、TaskType
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalTaskResponse: 包含code、reqCode、message、rowCount的响应
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"收到外部任务创建请求: ReqCode={task_request.ReqCode}, TaskType={task_request.TaskType}")
|
||||||
|
|
||||||
|
# 根据任务类型获取对应的模板ID
|
||||||
|
template_id = TASK_TYPE_TEMPLATE_MAPPING.get(task_request.TaskType)
|
||||||
|
# print("template_id::::", template_id, "==========")
|
||||||
|
if not template_id:
|
||||||
|
logger.error(f"不支持的任务类型: {task_request.TaskType}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=400,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message=f"不支持的任务类型: {task_request.TaskType}",
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构造任务运行参数
|
||||||
|
task_params = []
|
||||||
|
|
||||||
|
# 添加SourceID参数(如果提供)
|
||||||
|
if task_request.SourceID:
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="sourceId",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="来源ID",
|
||||||
|
required=False,
|
||||||
|
defaultValue=task_request.SourceID,
|
||||||
|
remark="外部接口传入的来源ID"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加TargetID参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="targetId",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="目标ID",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.TargetID,
|
||||||
|
remark="外部接口传入的目标ID"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加ReqCode参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="reqCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="请求标识码",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.ReqCode,
|
||||||
|
remark="外部接口传入的请求唯一标识码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 构造任务执行请求
|
||||||
|
run_request = TaskEditRunRequest(
|
||||||
|
taskId=template_id,
|
||||||
|
params=task_params,
|
||||||
|
source_type=SourceType.SYSTEM_SCHEDULING, # 第三方系统
|
||||||
|
source_system="EXTERNAL_API", # 外部接口系统标识
|
||||||
|
source_device=request.client.host if request.client else "unknown", # 使用客户端IP作为设备标识
|
||||||
|
use_modbus=False,
|
||||||
|
modbus_timeout=5000
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取客户端信息
|
||||||
|
client_ip = request.client.host if request.client else None
|
||||||
|
user_agent = request.headers.get("user-agent", "")
|
||||||
|
|
||||||
|
tf_api_token = TF_API_TOKEN
|
||||||
|
|
||||||
|
client_info = {
|
||||||
|
"user_agent": user_agent,
|
||||||
|
"headers": dict(request.headers),
|
||||||
|
"method": request.method,
|
||||||
|
"url": str(request.url)
|
||||||
|
}
|
||||||
|
client_info_str = json.dumps(client_info, ensure_ascii=False)
|
||||||
|
# print("tf_api_config::::::::::::", tf_api_token)
|
||||||
|
# print("run_request:::::::::", run_request)
|
||||||
|
# print("client_ip:::::::::", client_ip)
|
||||||
|
# print("client_info:::::::::", client_info)
|
||||||
|
# print("client_info_str:::::::::", client_info_str)
|
||||||
|
# print("tf_api_token:::::::::", tf_api_token)
|
||||||
|
# 调用任务执行服务
|
||||||
|
result = await TaskEditService.run_task(
|
||||||
|
run_request,
|
||||||
|
client_ip=client_ip,
|
||||||
|
client_info=client_info_str,
|
||||||
|
tf_api_token=tf_api_token
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
logger.error(f"任务启动失败: ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message="任务启动失败",
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
if not result.get("success", False):
|
||||||
|
logger.error(f"任务启动失败: {result.get('message')}, ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message=result.get("message", "任务启动失败"),
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"任务启动成功: ReqCode={task_request.ReqCode}, TaskRecordId={result.get('taskRecordId')}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=0,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message="成功",
|
||||||
|
rowCount=1
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建外部任务异常: {str(e)}, ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message=f"创建任务失败: {str(e)}",
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.post("/GenAgvSchedulingTask")
|
||||||
|
async def gen_agv_scheduling_task(request: Request, task_request: GenAgvSchedulingTaskRequest = Body(...)):
|
||||||
|
"""
|
||||||
|
AGV调度任务接口
|
||||||
|
用于生成AGV调度任务
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_request: AGV调度任务请求,包含ReqCode、TaskTyp、SecurityKey等参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalTaskResponse: 包含code、reqCode、message、rowCount的响应
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"收到AGV调度任务请求: ReqCode={task_request.ReqCode}, TaskTyp={task_request.TaskTyp}")
|
||||||
|
|
||||||
|
# # 验证安全密钥(可根据实际需求实现验证逻辑)
|
||||||
|
# if not task_request.SecurityKey:
|
||||||
|
# logger.error(f"安全密钥不能为空: ReqCode={task_request.ReqCode}")
|
||||||
|
# return ExternalTaskResponse(
|
||||||
|
# code=400,
|
||||||
|
# reqCode=task_request.ReqCode,
|
||||||
|
# message="安全密钥不能为空",
|
||||||
|
# rowCount=0GenAgvSchedulingTaskRequest
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 根据任务类型获取对应的模板ID
|
||||||
|
template_id = TASK_TYPE_TEMPLATE_MAPPING.get(task_request.TaskTyp)
|
||||||
|
# if not template_id:
|
||||||
|
# logger.error(f"不支持的任务类型: {task_request.TaskTyp}, ReqCode={task_request.ReqCode}")
|
||||||
|
# return ExternalTaskResponse(
|
||||||
|
# code=400,
|
||||||
|
# reqCode=task_request.ReqCode,
|
||||||
|
# message=f"不支持的任务类型: {task_request.TaskTyp}",
|
||||||
|
# rowCount=0
|
||||||
|
# )
|
||||||
|
|
||||||
|
# 构造任务运行参数
|
||||||
|
task_params = []
|
||||||
|
|
||||||
|
# 添加任务代码参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="taskCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="任务代码",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.TaskCode,
|
||||||
|
remark="AGV调度任务代码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加类型参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="type",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="类型",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.Type,
|
||||||
|
remark="任务类型标识"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加子类型参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="subType",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="子类型",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.SubType,
|
||||||
|
remark="任务子类型标识"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加区域位置代码参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="areaPositonCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="区域位置代码",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.AreaPositonCode,
|
||||||
|
remark="区域位置代码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加区域位置名称参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="areaPositonName",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="区域位置名称",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.AreaPositonName,
|
||||||
|
remark="区域位置名称"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加位置代码路径参数(转为JSON字符串)
|
||||||
|
position_path_json = json.dumps([path.dict() for path in task_request.PositionCodePath], ensure_ascii=False)
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="positionCodePath",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="位置代码路径",
|
||||||
|
required=True,
|
||||||
|
defaultValue=position_path_json,
|
||||||
|
remark="位置代码路径JSON数组"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加客户端代码参数(如果提供)
|
||||||
|
if task_request.ClientCode:
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="clientCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="客户端代码",
|
||||||
|
required=False,
|
||||||
|
defaultValue=task_request.ClientCode,
|
||||||
|
remark="客户端代码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加令牌代码参数(如果提供)
|
||||||
|
if task_request.TokenCode:
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="tokenCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="令牌代码",
|
||||||
|
required=False,
|
||||||
|
defaultValue=task_request.TokenCode,
|
||||||
|
remark="令牌代码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 添加ReqCode参数
|
||||||
|
task_params.append(TaskInputParamNew(
|
||||||
|
name="reqCode",
|
||||||
|
type=InputParamType.STRING,
|
||||||
|
label="请求标识码",
|
||||||
|
required=True,
|
||||||
|
defaultValue=task_request.ReqCode,
|
||||||
|
remark="请求唯一标识码"
|
||||||
|
))
|
||||||
|
|
||||||
|
# 构造任务执行请求
|
||||||
|
run_request = TaskEditRunRequest(
|
||||||
|
taskId=template_id,
|
||||||
|
params=task_params,
|
||||||
|
source_type=SourceType.SYSTEM_SCHEDULING, # 第三方系统
|
||||||
|
source_system="AGV_SCHEDULING", # AGV调度系统标识
|
||||||
|
source_device=request.client.host if request.client else "unknown", # 使用客户端IP作为设备标识
|
||||||
|
use_modbus=False,
|
||||||
|
modbus_timeout=5000
|
||||||
|
)
|
||||||
|
|
||||||
|
# 获取客户端信息
|
||||||
|
client_ip = request.client.host if request.client else None
|
||||||
|
user_agent = request.headers.get("user-agent", "")
|
||||||
|
|
||||||
|
tf_api_token = TF_API_TOKEN
|
||||||
|
|
||||||
|
client_info = {
|
||||||
|
"user_agent": user_agent,
|
||||||
|
"headers": dict(request.headers),
|
||||||
|
"method": request.method,
|
||||||
|
"url": str(request.url)
|
||||||
|
}
|
||||||
|
client_info_str = json.dumps(client_info, ensure_ascii=False)
|
||||||
|
|
||||||
|
# 调用任务执行服务
|
||||||
|
result = await TaskEditService.run_task(
|
||||||
|
run_request,
|
||||||
|
client_ip=client_ip,
|
||||||
|
client_info=client_info_str,
|
||||||
|
tf_api_token=tf_api_token
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
logger.error(f"AGV调度任务启动失败: ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message="任务启动失败",
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
if not result.get("success", False):
|
||||||
|
logger.error(f"AGV调度任务启动失败: {result.get('message')}, ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message=result.get("message", "任务启动失败"),
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"AGV调度任务启动成功: ReqCode={task_request.ReqCode}, TaskRecordId={result.get('taskRecordId')}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=0,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message="成功",
|
||||||
|
rowCount=0
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建AGV调度任务异常: {str(e)}, ReqCode={task_request.ReqCode}")
|
||||||
|
return ExternalTaskResponse(
|
||||||
|
code=500,
|
||||||
|
reqCode=task_request.ReqCode,
|
||||||
|
message=f"创建任务失败: {str(e)}",
|
||||||
|
rowCount=0
|
||||||
|
)
|
BIN
routes/model/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/base.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/calldevice_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/calldevice_model.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/external_task_model.cpython-312.pyc
Normal file
BIN
routes/model/__pycache__/external_task_model.cpython-312.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/map_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/map_model.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/modbus_config_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/modbus_config_model.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/operate_point_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/operate_point_model.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
routes/model/__pycache__/script_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/script_model.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/task_edit_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/task_edit_model.cpython-311.pyc
Normal file
Binary file not shown.
BIN
routes/model/__pycache__/task_model.cpython-311.pyc
Normal file
BIN
routes/model/__pycache__/task_model.cpython-311.pyc
Normal file
Binary file not shown.
68
routes/model/external_task_model.py
Normal file
68
routes/model/external_task_model.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
外部任务接口模型模块
|
||||||
|
包含外部任务创建相关的请求和响应数据模型
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from enum import Enum
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
# 任务类型枚举
|
||||||
|
class TaskTypeEnum(str, Enum):
|
||||||
|
"""任务类型枚举"""
|
||||||
|
GG2MP = "GG2MP" # 高柜到MP
|
||||||
|
GGFK2MP = "GGFK2MP" # 高柜发库到MP
|
||||||
|
GT2MP = "GT2MP" # 高台到MP
|
||||||
|
GTFK2MP = "GTFK2MP" # 高台发库到MP
|
||||||
|
ZG2MP = "ZG2MP" # 中柜到MP
|
||||||
|
QZ2MP = "QZ2MP" # 清洗到MP
|
||||||
|
LG2MP = "LG2MP" # 料柜到MP
|
||||||
|
PHZ2MP = "PHZ2MP" # 配货站到MP
|
||||||
|
MP2GG = "MP2GG" # MP到高柜
|
||||||
|
MP2GGFK = "MP2GGFK" # MP到高柜发库
|
||||||
|
MP2GT = "MP2GT" # MP到高台
|
||||||
|
MP2GTFK = "MP2GTFK" # MP到高台发库
|
||||||
|
MP2ZG = "MP2ZG" # MP到中柜
|
||||||
|
MP2QZ = "MP2QZ" # MP到清洗
|
||||||
|
MP2LG = "MP2LG" # MP到料柜
|
||||||
|
MP2PHZ = "MP2PHZ" # MP到配货站
|
||||||
|
|
||||||
|
# 外部任务创建请求模型
|
||||||
|
class ExternalTaskRequest(BaseModel):
|
||||||
|
"""外部任务创建请求模型"""
|
||||||
|
ReqCode: str = Field(..., description="请求唯一标识码")
|
||||||
|
SourceID: str = Field("", description="来源ID")
|
||||||
|
TargetID: str = Field(..., description="目标ID")
|
||||||
|
TaskType: TaskTypeEnum = Field(..., description="任务类型")
|
||||||
|
|
||||||
|
# 外部任务创建响应模型
|
||||||
|
class ExternalTaskResponse(BaseModel):
|
||||||
|
"""外部任务创建响应模型"""
|
||||||
|
code: int = Field(..., description="响应码,0表示成功")
|
||||||
|
reqCode: str = Field(..., description="请求唯一标识码")
|
||||||
|
message: str = Field(..., description="响应消息")
|
||||||
|
rowCount: int = Field(0, description="影响行数")
|
||||||
|
|
||||||
|
# 位置路径项模型
|
||||||
|
class PositionCodePath1(BaseModel):
|
||||||
|
"""位置路径项模型"""
|
||||||
|
PositionCode: str = Field(..., description="位置代码")
|
||||||
|
Type: str = Field(..., description="类型")
|
||||||
|
|
||||||
|
# AGV调度任务请求模型
|
||||||
|
class GenAgvSchedulingTaskRequest(BaseModel):
|
||||||
|
"""AGV调度任务请求模型"""
|
||||||
|
ReqCode: str = Field("", description="请求唯一标识码")
|
||||||
|
TaskTyp: str = Field("", description="任务类型")
|
||||||
|
SecurityKey: str = Field("", description="安全密钥")
|
||||||
|
Type: str = Field("", description="类型")
|
||||||
|
TaskCode: str = Field("", description="任务代码")
|
||||||
|
SubType: str = Field("", description="子类型")
|
||||||
|
AreaPositonCode: str = Field("", description="区域位置代码")
|
||||||
|
AreaPositonName: str = Field("", description="区域位置名称")
|
||||||
|
PositionCodePath: List[PositionCodePath1] = Field(..., description="位置代码路径")
|
||||||
|
ClientCode: str = Field("", description="客户端代码")
|
||||||
|
TokenCode: str = Field("", description="令牌代码")
|
@ -118,6 +118,8 @@ class StorageLocationListRequest(BaseModel):
|
|||||||
is_empty_tray: Optional[bool] = Field(None, description="是否空托盘")
|
is_empty_tray: Optional[bool] = Field(None, description="是否空托盘")
|
||||||
include_operate_point_info: bool = Field(True, description="是否包含动作点信息")
|
include_operate_point_info: bool = Field(True, description="是否包含动作点信息")
|
||||||
include_extended_fields: bool = Field(True, description="是否包含扩展字段")
|
include_extended_fields: bool = Field(True, description="是否包含扩展字段")
|
||||||
|
layer_name_sort: Optional[bool] = Field(None, description="层名称排序:true(升序,默认)、false(降序)")
|
||||||
|
station_name_sort: Optional[bool] = Field(None, description="站点名称排序:true(升序,默认)、false(降序)")
|
||||||
page: int = Field(1, ge=1, description="页码")
|
page: int = Field(1, ge=1, description="页码")
|
||||||
page_size: int = Field(20, ge=1, le=100, description="每页数量")
|
page_size: int = Field(20, ge=1, le=100, description="每页数量")
|
||||||
|
|
||||||
|
@ -78,13 +78,15 @@ async def get_storage_location_list(
|
|||||||
scene_id: Optional[str] = Query(None, description="场景ID"),
|
scene_id: Optional[str] = Query(None, description="场景ID"),
|
||||||
storage_area_id: Optional[str] = Query(None, description="库区ID"),
|
storage_area_id: Optional[str] = Query(None, description="库区ID"),
|
||||||
station_name: Optional[str] = Query(None, description="站点名称(支持模糊搜索)"),
|
station_name: Optional[str] = Query(None, description="站点名称(支持模糊搜索)"),
|
||||||
layer_name: Optional[str] = Query(None, description="层名称(支持模糊搜索)"),
|
layer_name: Optional[str] = Query(None, description="库位名称(支持模糊搜索)"),
|
||||||
is_disabled: Optional[bool] = Query(None, description="是否禁用"),
|
is_disabled: Optional[bool] = Query(None, description="是否禁用"),
|
||||||
is_occupied: Optional[bool] = Query(None, description="是否占用"),
|
is_occupied: Optional[bool] = Query(None, description="是否占用"),
|
||||||
is_locked: Optional[bool] = Query(None, description="是否锁定"),
|
is_locked: Optional[bool] = Query(None, description="是否锁定"),
|
||||||
is_empty_tray: Optional[bool] = Query(None, description="是否空托盘"),
|
is_empty_tray: Optional[bool] = Query(None, description="是否空托盘"),
|
||||||
include_operate_point_info: bool = Query(True, description="是否包含动作点信息"),
|
include_operate_point_info: bool = Query(True, description="是否包含动作点信息"),
|
||||||
include_extended_fields: bool = Query(True, description="是否包含扩展字段"),
|
include_extended_fields: bool = Query(True, description="是否包含扩展字段"),
|
||||||
|
layer_name_sort: Optional[bool] = Query(None, description="层名称排序:true(升序,默认)、false(降序)"),
|
||||||
|
station_name_sort: Optional[bool] = Query(None, description="站点名称排序:true(升序,默认)、false(降序)"),
|
||||||
page: int = Query(1, ge=1, description="页码"),
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
@ -146,6 +148,8 @@ async def get_storage_location_list(
|
|||||||
is_empty_tray=is_empty_tray,
|
is_empty_tray=is_empty_tray,
|
||||||
include_operate_point_info=include_operate_point_info,
|
include_operate_point_info=include_operate_point_info,
|
||||||
include_extended_fields=include_extended_fields,
|
include_extended_fields=include_extended_fields,
|
||||||
|
layer_name_sort=layer_name_sort,
|
||||||
|
station_name_sort=station_name_sort,
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size
|
page_size=page_size
|
||||||
)
|
)
|
||||||
|
125
routes/storage_queue_api.py
Normal file
125
routes/storage_queue_api.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
库位队列管理API
|
||||||
|
提供队列状态监控和管理接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from services.execution.handlers.storage_queue_manager import storage_queue_manager
|
||||||
|
from utils.api_response import success_response, error_response
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("routes.storage_queue_api")
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/storage-queue", tags=["库位队列管理"])
|
||||||
|
|
||||||
|
@router.get("/stats", summary="获取队列统计信息")
|
||||||
|
async def get_queue_stats() -> Dict[str, Any]:
|
||||||
|
"""获取队列统计信息"""
|
||||||
|
try:
|
||||||
|
stats = storage_queue_manager.get_queue_stats()
|
||||||
|
return success_response(data=stats, message="获取队列统计信息成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取队列统计信息失败: {str(e)}")
|
||||||
|
return error_response(f"获取队列统计信息失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/status/{request_id}", summary="获取请求状态")
|
||||||
|
async def get_request_status(request_id: str) -> Dict[str, Any]:
|
||||||
|
"""获取特定请求的状态"""
|
||||||
|
try:
|
||||||
|
status = await storage_queue_manager.get_request_status(request_id)
|
||||||
|
if status:
|
||||||
|
return success_response(data=status, message="获取请求状态成功")
|
||||||
|
else:
|
||||||
|
return error_response("请求不存在")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取请求状态失败: {str(e)}")
|
||||||
|
return error_response(f"获取请求状态失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/cancel/{request_id}", summary="取消请求")
|
||||||
|
async def cancel_request(request_id: str) -> Dict[str, Any]:
|
||||||
|
"""取消指定的请求"""
|
||||||
|
try:
|
||||||
|
success = await storage_queue_manager.cancel_request(request_id)
|
||||||
|
if success:
|
||||||
|
return success_response(message=f"取消请求成功: {request_id}")
|
||||||
|
else:
|
||||||
|
return error_response("请求不存在或无法取消")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"取消请求失败: {str(e)}")
|
||||||
|
return error_response(f"取消请求失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/health", summary="队列健康检查")
|
||||||
|
async def queue_health_check() -> Dict[str, Any]:
|
||||||
|
"""检查队列管理器的健康状态"""
|
||||||
|
try:
|
||||||
|
stats = storage_queue_manager.get_queue_stats()
|
||||||
|
|
||||||
|
# 判断健康状态
|
||||||
|
health_status = "healthy"
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# 检查队列大小
|
||||||
|
if stats.get('queue_size', 0) > 800: # 队列接近满载
|
||||||
|
health_status = "warning"
|
||||||
|
issues.append("队列接近满载")
|
||||||
|
|
||||||
|
# 检查活跃工作者数量
|
||||||
|
if stats.get('active_workers', 0) == 0:
|
||||||
|
health_status = "critical"
|
||||||
|
issues.append("没有活跃的工作者")
|
||||||
|
|
||||||
|
# 检查失败率
|
||||||
|
total_requests = stats.get('requests_total', 0)
|
||||||
|
if total_requests > 0:
|
||||||
|
failure_rate = stats.get('requests_failed', 0) / total_requests
|
||||||
|
if failure_rate > 0.1: # 失败率超过10%
|
||||||
|
health_status = "warning"
|
||||||
|
issues.append(f"失败率过高: {failure_rate:.2%}")
|
||||||
|
|
||||||
|
return success_response(data={
|
||||||
|
"status": health_status,
|
||||||
|
"issues": issues,
|
||||||
|
"stats": stats
|
||||||
|
}, message="队列健康检查完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"队列健康检查失败: {str(e)}")
|
||||||
|
return error_response(f"队列健康检查失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/start", summary="启动队列管理器")
|
||||||
|
async def start_queue_manager() -> Dict[str, Any]:
|
||||||
|
"""启动队列管理器"""
|
||||||
|
try:
|
||||||
|
await storage_queue_manager.start()
|
||||||
|
return success_response(message="队列管理器启动成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"启动队列管理器失败: {str(e)}")
|
||||||
|
return error_response(f"启动队列管理器失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/stop", summary="停止队列管理器")
|
||||||
|
async def stop_queue_manager() -> Dict[str, Any]:
|
||||||
|
"""停止队列管理器"""
|
||||||
|
try:
|
||||||
|
await storage_queue_manager.stop()
|
||||||
|
return success_response(message="队列管理器停止成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"停止队列管理器失败: {str(e)}")
|
||||||
|
return error_response(f"停止队列管理器失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/config", summary="获取队列配置")
|
||||||
|
async def get_queue_config() -> Dict[str, Any]:
|
||||||
|
"""获取队列管理器配置"""
|
||||||
|
try:
|
||||||
|
config = {
|
||||||
|
"max_workers": storage_queue_manager.max_workers,
|
||||||
|
"max_queue_size": storage_queue_manager.max_queue_size,
|
||||||
|
"handlers_registered": list(storage_queue_manager.handlers.keys())
|
||||||
|
}
|
||||||
|
return success_response(data=config, message="获取队列配置成功")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取队列配置失败: {str(e)}")
|
||||||
|
return error_response(f"获取队列配置失败: {str(e)}")
|
@ -14,7 +14,7 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from services.task_record_service import TaskRecordService
|
from services.task_record_service import TaskRecordService
|
||||||
from services.operate_point_service import OperatePointService
|
from services.operate_point_service import OperatePointService
|
||||||
from data.session import get_db
|
from data.session import session_scope
|
||||||
from routes.model.operate_point_model import StorageLocationListRequest
|
from routes.model.operate_point_model import StorageLocationListRequest
|
||||||
from utils.logger import get_logger
|
from utils.logger import get_logger
|
||||||
|
|
||||||
@ -163,10 +163,15 @@ async def websocket_task_execution(
|
|||||||
message = json.loads(data)
|
message = json.loads(data)
|
||||||
await handle_websocket_message(websocket, task_record_id, message)
|
await handle_websocket_message(websocket, task_record_id, message)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await websocket.send_text(safe_json_dumps({
|
# 检查是否是字符串心跳
|
||||||
"type": "error",
|
if data.strip() == "ping":
|
||||||
"message": "无效的JSON格式"
|
await websocket.send_text("pong")
|
||||||
}, ensure_ascii=False))
|
logger.debug(f"收到字符串心跳ping,已回复pong,任务记录ID: {task_record_id}")
|
||||||
|
else:
|
||||||
|
await websocket.send_text(safe_json_dumps({
|
||||||
|
"type": "error",
|
||||||
|
"message": "无效的消息格式,请发送JSON格式或字符串'ping'"
|
||||||
|
}, ensure_ascii=False))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理WebSocket消息失败: {str(e)}")
|
logger.error(f"处理WebSocket消息失败: {str(e)}")
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text(safe_json_dumps({
|
||||||
@ -242,17 +247,24 @@ async def websocket_storage_location_status(
|
|||||||
# 接收客户端消息
|
# 接收客户端消息
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
try:
|
try:
|
||||||
|
# 首先尝试解析为JSON
|
||||||
message = json.loads(data)
|
message = json.loads(data)
|
||||||
await handle_storage_location_websocket_message(websocket, scene_id, message, filter_params)
|
await handle_storage_location_websocket_message(websocket, scene_id, message, filter_params)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await websocket.send_text(safe_json_dumps({
|
# 如果不是JSON格式,检查是否是字符串心跳
|
||||||
"type": "error",
|
if data.strip() == "ping":
|
||||||
"message": "无效的JSON格式"
|
await websocket.send_text("pong")
|
||||||
}, ensure_ascii=False))
|
logger.debug(f"收到字符串心跳ping,已回复pong,场景ID: {scene_id}")
|
||||||
|
else:
|
||||||
|
await websocket.send_text(safe_json_dumps({
|
||||||
|
"type": "error",
|
||||||
|
"message": "无效的消息格式,请发送JSON格式或字符串'ping'"
|
||||||
|
}, ensure_ascii=False))
|
||||||
|
logger.warning(f"收到无效消息格式: {data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理库位状态WebSocket消息失败: {str(e)}")
|
logger.error(f"处理库位状态WebSocket消息失败: {str(e)}")
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text(safe_json_dumps({
|
||||||
"type": "error",
|
"type": "error",
|
||||||
"message": f"处理消息失败: {str(e)}"
|
"message": f"处理消息失败: {str(e)}"
|
||||||
}, ensure_ascii=False))
|
}, ensure_ascii=False))
|
||||||
finally:
|
finally:
|
||||||
@ -296,10 +308,11 @@ async def websocket_storage_location_broadcast(
|
|||||||
try:
|
try:
|
||||||
message = json.loads(data)
|
message = json.loads(data)
|
||||||
if message.get("type") == "ping":
|
if message.get("type") == "ping":
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text("pong")
|
||||||
"type": "pong",
|
except json.JSONDecodeError:
|
||||||
"timestamp": datetime.now().isoformat()
|
# 支持字符串格式的心跳
|
||||||
}, ensure_ascii=False))
|
if data.strip() == "ping":
|
||||||
|
await websocket.send_text("pong")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
@ -325,10 +338,7 @@ async def handle_websocket_message(websocket: WebSocket, task_record_id: str, me
|
|||||||
await send_task_execution_status(task_record_id, websocket)
|
await send_task_execution_status(task_record_id, websocket)
|
||||||
elif message_type == "ping":
|
elif message_type == "ping":
|
||||||
# 心跳检测
|
# 心跳检测
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text("pong")
|
||||||
"type": "pong",
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
}, ensure_ascii=False))
|
|
||||||
else:
|
else:
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text(safe_json_dumps({
|
||||||
"type": "error",
|
"type": "error",
|
||||||
@ -442,12 +452,12 @@ async def send_storage_location_status(scene_id: str, websocket: WebSocket, filt
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 获取库位状态
|
# 获取库位状态
|
||||||
with get_db() as db:
|
with session_scope() as db:
|
||||||
# 构建请求参数,过滤掉None值
|
# 构建请求参数,过滤掉None值
|
||||||
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
||||||
# 设置默认分页参数
|
# 设置默认分页参数
|
||||||
request_params.setdefault("page", 1)
|
request_params.setdefault("page", 1)
|
||||||
request_params.setdefault("page_size", 1000) # 默认获取大量数据
|
request_params.setdefault("page_size", 100) # 默认获取数据,符合验证规则
|
||||||
|
|
||||||
request = StorageLocationListRequest(**request_params)
|
request = StorageLocationListRequest(**request_params)
|
||||||
result = OperatePointService.get_storage_location_list(db, request)
|
result = OperatePointService.get_storage_location_list(db, request)
|
||||||
@ -503,11 +513,11 @@ async def periodic_push_storage_location_status(websocket: WebSocket, scene_id:
|
|||||||
|
|
||||||
# 获取当前数据
|
# 获取当前数据
|
||||||
try:
|
try:
|
||||||
with get_db() as db:
|
with session_scope() as db:
|
||||||
# 构建请求参数,过滤掉None值
|
# 构建请求参数,过滤掉None值
|
||||||
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
request_params = {k: v for k, v in filter_params.items() if v is not None}
|
||||||
request_params.setdefault("page", 1)
|
request_params.setdefault("page", 1)
|
||||||
request_params.setdefault("page_size", 1000)
|
request_params.setdefault("page_size", 100)
|
||||||
|
|
||||||
request = StorageLocationListRequest(**request_params)
|
request = StorageLocationListRequest(**request_params)
|
||||||
result = OperatePointService.get_storage_location_list(db, request)
|
result = OperatePointService.get_storage_location_list(db, request)
|
||||||
@ -564,10 +574,7 @@ async def handle_storage_location_websocket_message(websocket: WebSocket, scene_
|
|||||||
await send_storage_location_status(scene_id, websocket, filter_params)
|
await send_storage_location_status(scene_id, websocket, filter_params)
|
||||||
elif message_type == "ping":
|
elif message_type == "ping":
|
||||||
# 心跳检测
|
# 心跳检测
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text("pong")
|
||||||
"type": "pong",
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
}, ensure_ascii=False))
|
|
||||||
else:
|
else:
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text(safe_json_dumps({
|
||||||
"type": "error",
|
"type": "error",
|
||||||
@ -600,10 +607,11 @@ async def websocket_task_execution_broadcast(
|
|||||||
try:
|
try:
|
||||||
message = json.loads(data)
|
message = json.loads(data)
|
||||||
if message.get("type") == "ping":
|
if message.get("type") == "ping":
|
||||||
await websocket.send_text(safe_json_dumps({
|
await websocket.send_text("pong")
|
||||||
"type": "pong",
|
except json.JSONDecodeError:
|
||||||
"timestamp": datetime.now().isoformat()
|
# 支持字符串格式的心跳
|
||||||
}, ensure_ascii=False))
|
if data.strip() == "ping":
|
||||||
|
await websocket.send_text("pong")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
|
BIN
services/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
services/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/calldevice_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/calldevice_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/map_data_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/map_data_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/modbus_config_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/modbus_config_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/operate_point_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/operate_point_service.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
services/__pycache__/script_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/script_service.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
services/__pycache__/task_edit_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/task_edit_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/task_record_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/task_record_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/task_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/task_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/template_service.cpython-311.pyc
Normal file
BIN
services/__pycache__/template_service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/enhanced_scheduler/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
services/enhanced_scheduler/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/execution/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
services/execution/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/execution/__pycache__/block_executor.cpython-311.pyc
Normal file
BIN
services/execution/__pycache__/block_executor.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/execution/__pycache__/task_context.cpython-311.pyc
Normal file
BIN
services/execution/__pycache__/task_context.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/execution/__pycache__/task_executor.cpython-311.pyc
Normal file
BIN
services/execution/__pycache__/task_executor.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
services/execution/handlers/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
services/execution/handlers/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/execution/handlers/__pycache__/base.cpython-311.pyc
Normal file
BIN
services/execution/handlers/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
services/execution/handlers/__pycache__/core.cpython-311.pyc
Normal file
BIN
services/execution/handlers/__pycache__/core.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user