14 changed files with 490 additions and 228 deletions
@ -0,0 +1,233 @@ |
|||||
|
import type Viewport from '@/core/engine/Viewport.ts' |
||||
|
import { worldModel } from '@/core/manager/WorldModel.ts' |
||||
|
import mqtt, { type IConnackPacket, type IPublishPacket } from 'mqtt' |
||||
|
import type { Cl2Task } from '@/modules/cl2/Cl23dObject.ts' |
||||
|
import type { ErrorWithReasonCode } from 'mqtt/src/lib/shared.ts' |
||||
|
import type Cl23dObject from '@/modules/cl2/Cl23dObject.ts' |
||||
|
import { Request } from '@ease-forge/shared' |
||||
|
|
||||
|
export default class EnvManager { |
||||
|
private viewport: Viewport |
||||
|
private client: mqtt.MqttClient = null |
||||
|
|
||||
|
init(viewport: Viewport): void { |
||||
|
this.viewport = viewport |
||||
|
} |
||||
|
|
||||
|
// 从后台读取所有车
|
||||
|
async loadExecutors() { |
||||
|
const res = await Request.request.post('/api/workbench/EnvController@getAllExecutor', { |
||||
|
projectUuid: worldModel.state.project_uuid, |
||||
|
catalogCode: worldModel.state.catalogCode, |
||||
|
envId: worldModel.state.runState.currentEnvId |
||||
|
}) |
||||
|
for (const row of res.data) { |
||||
|
const executor_id = row.executor_id |
||||
|
const payload = JSON.parse(row.virtual_executor_payload) |
||||
|
const wayPointId = row.virtual_location_at |
||||
|
|
||||
|
const point = Model.find(wayPointId) |
||||
|
if (!point) { |
||||
|
console.error(`Waypoint with ID ${wayPointId} not found for executor ${executor_id}.`) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
const item = _.cloneDeep(payload) |
||||
|
item.id = executor_id |
||||
|
item.tf[0] = _.cloneDeep(point.tf[0]) |
||||
|
Model.createExecutor(item) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 从后台读取所有库存
|
||||
|
async loadInv() { |
||||
|
const res = await Request.request.post('/api/workbench/EnvController@getAllInv', { |
||||
|
projectUuid: worldModel.state.project_uuid, |
||||
|
catalogCode: worldModel.state.catalogCode, |
||||
|
envId: worldModel.state.runState.currentEnvId |
||||
|
}) |
||||
|
for (const row of res.data) { |
||||
|
const bay = row.bay |
||||
|
const cell = row.cell // : 0
|
||||
|
const level = row.level // : 0
|
||||
|
const loc_code = row.loc_code // : "rack1_0_0_0"
|
||||
|
const lpn = row.lpn // : "LPN1"
|
||||
|
const rack = row.rack // : "rack1"
|
||||
|
const container_type = row.container_type // : "pallet"
|
||||
|
Model.createInv(container_type, lpn, rack, bay, level, cell) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
appendExecutor(id) { |
||||
|
const obj = this.viewport.entityManager.findObjectById(id) |
||||
|
const item = this.viewport.entityManager.findItemById(id) |
||||
|
if (item.t == 'cl2' && obj.userData.t === 'cl2') { |
||||
|
debugger |
||||
|
|
||||
|
const cl2 = obj as Cl23dObject |
||||
|
cl2.onMqttConnect(item, this.client) |
||||
|
// this.client.subscribe(['/wcs_server/' + cl2.id], { qos: 0 })
|
||||
|
// this.client.publish('/agv_robot/status', JSON.stringify(m20020), { retain: true })
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMqttConnect = (packet: IConnackPacket) => { |
||||
|
console.log('Connected') |
||||
|
} |
||||
|
|
||||
|
onMqttMessage = (topic: string, payload: Buffer, packet: IPublishPacket) => { |
||||
|
console.log(`[${topic}] ${msg}`) |
||||
|
debugger |
||||
|
const a: Cl2Task = JSON.parse(msg.toString()) |
||||
|
this.handleMessage(a) |
||||
|
} |
||||
|
|
||||
|
onMqttError = (error: Error | ErrorWithReasonCode) => { |
||||
|
console.error('Error:', error) |
||||
|
} |
||||
|
|
||||
|
async start(env: EnvInfo) { |
||||
|
if (!env) { |
||||
|
system.showErrorDialog('Environment is not specified, cannot start EnvManager.') |
||||
|
return |
||||
|
} |
||||
|
if (!worldModel.state.isOpened) { |
||||
|
system.showErrorDialog('WorldModel is not opened, cannot start EnvManager.') |
||||
|
return |
||||
|
} |
||||
|
if (!this.viewport) { |
||||
|
system.showErrorDialog('Viewport is not initialized, cannot start EnvManager.') |
||||
|
return |
||||
|
} |
||||
|
if (!worldModel.state.runState.currentEnvId) { |
||||
|
system.showErrorDialog('Current environment ID is not set, cannot start EnvManager.') |
||||
|
return |
||||
|
} |
||||
|
if (worldModel.state.runState.isRunning) { |
||||
|
system.showErrorDialog('EnvManager is already running, cannot start again.') |
||||
|
return |
||||
|
} |
||||
|
const payload = env.env_payload |
||||
|
const brokerUrl = payload?.mqtt?.websocket |
||||
|
const username = payload?.mqtt?.username |
||||
|
const password = payload?.mqtt?.password |
||||
|
|
||||
|
if (!brokerUrl || !username || !password) { |
||||
|
system.showErrorDialog('MQTT broker URL, username, or password is not set in the environment payload.') |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.stop() |
||||
|
|
||||
|
system.showLoading() |
||||
|
worldModel.state.runState.isLoading = true |
||||
|
worldModel.state.runState.currentEnv = Object.freeze(env) |
||||
|
try { |
||||
|
await this.loadExecutors() |
||||
|
await this.loadInv() |
||||
|
|
||||
|
this.client = mqtt.connect(brokerUrl, { |
||||
|
path: '/mqtt', |
||||
|
clientId: system.createUUID(), |
||||
|
clean: true, |
||||
|
connectTimeout: 300, |
||||
|
username, |
||||
|
password, |
||||
|
unixSocket: true, |
||||
|
keepalive: 60 |
||||
|
}) |
||||
|
|
||||
|
this.client.on('connect', this.onMqttConnect) |
||||
|
this.client.on('message', this.onMqttMessage) |
||||
|
this.client.on('error', this.onMqttError) |
||||
|
worldModel.state.runState.isRunning = true |
||||
|
|
||||
|
} finally { |
||||
|
system.clearLoading() |
||||
|
worldModel.state.runState.isLoading = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async stop() { |
||||
|
system.showLoading() |
||||
|
try { |
||||
|
if (worldModel.state.runState.isRunning) { |
||||
|
worldModel.state.runState.isRunning = false |
||||
|
} |
||||
|
worldModel.state.runState.currentEnv = null |
||||
|
if (this.client) { |
||||
|
this.client.removeAllListeners() |
||||
|
this.client.end() |
||||
|
this.client = null |
||||
|
} |
||||
|
|
||||
|
this.clearExecutors() |
||||
|
this.clearInv() |
||||
|
this.viewport.runtimeManager.clear() |
||||
|
|
||||
|
} finally { |
||||
|
system.clearLoading() |
||||
|
worldModel.state.runState.isLoading = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
clearInv() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
clearExecutors() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
static CURRENT_ALL_ENV: EnvInfo[] |
||||
|
|
||||
|
/** |
||||
|
* 创建运行环境 |
||||
|
* @param worldId 世界ID |
||||
|
* @param envName 运行环境名称 |
||||
|
* @param isVirtual 是否是虚拟环境 |
||||
|
*/ |
||||
|
static async createEnv(worldId: string, envName: string, isVirtual: boolean) { |
||||
|
throw new Error('Method not implemented.') |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取所有运行环境 |
||||
|
*/ |
||||
|
static async getAllEnv(worldId: string): Promise<ServerResponse<EnvInfo[]>> { |
||||
|
// system.invokeServer('')
|
||||
|
if (!worldId) { |
||||
|
return Promise.resolve({ success: true, data: [], msg: '' }) |
||||
|
} |
||||
|
const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', { |
||||
|
worldId: worldId |
||||
|
}) |
||||
|
if (res.success) { |
||||
|
EnvManager.CURRENT_ALL_ENV = res.data |
||||
|
for (const env of res.data) { |
||||
|
// payload 转换为 json 数据
|
||||
|
if (env.env_payload) { |
||||
|
try { |
||||
|
env.env_payload = JSON.parse(env.env_payload) |
||||
|
} catch (e) { |
||||
|
console.error('解析环境负载失败:', e) |
||||
|
env.env_payload = {} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return res |
||||
|
} |
||||
|
|
||||
|
EnvManager.CURRENT_ALL_ENV = [] |
||||
|
return res |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 卸载资源 |
||||
|
*/ |
||||
|
dispose(): void { |
||||
|
this.viewport = null |
||||
|
this.stop() |
||||
|
} |
||||
|
} |
||||
@ -1,67 +0,0 @@ |
|||||
import { Request } from '@ease-forge/shared' |
|
||||
|
|
||||
/** |
|
||||
* 运行管理器 |
|
||||
*/ |
|
||||
class RunManager { |
|
||||
|
|
||||
// 是否正在运行
|
|
||||
isRunning: boolean = false |
|
||||
|
|
||||
/** |
|
||||
* 获取所有运行环境 |
|
||||
*/ |
|
||||
async getAllEnv(worldId: string): Promise<ServerResponse<EnvInfo[]>> { |
|
||||
// system.invokeServer('')
|
|
||||
if (!worldId) { |
|
||||
return Promise.resolve({ success: true, data: [], msg: '' }) |
|
||||
} |
|
||||
const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', { |
|
||||
worldId: worldId |
|
||||
}) |
|
||||
for (const env of res.data) { |
|
||||
// payload 转换为 json 数据
|
|
||||
if (env.env_payload) { |
|
||||
try { |
|
||||
env.env_payload = JSON.parse(env.env_payload) |
|
||||
} catch (e) { |
|
||||
console.error('解析环境负载失败:', e) |
|
||||
env.env_payload = {} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return res |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 创建运行环境 |
|
||||
* @param worldId 世界ID |
|
||||
* @param envName 运行环境名称 |
|
||||
* @param isVirtual 是否是虚拟环境 |
|
||||
*/ |
|
||||
async createEnv(worldId: string, envName: string, isVirtual: boolean) { |
|
||||
throw new Error('Method not implemented.') |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 开始运行 |
|
||||
*/ |
|
||||
async run(timeRate: number, envId: number) { |
|
||||
if (this.isRunning) { |
|
||||
await this.stop() |
|
||||
} |
|
||||
|
|
||||
// 启动 websocket 监听等等
|
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 停止运行 |
|
||||
*/ |
|
||||
async stop() { |
|
||||
// 停止 websocket 监听等等
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const runManager = new RunManager() |
|
||||
export default runManager |
|
||||
@ -1,128 +1,125 @@ |
|||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
import { onMounted, reactive, useTemplateRef } from "vue"; |
import { onMounted, reactive, useTemplateRef } from 'vue' |
||||
import { AgGridVue } from "ag-grid-vue3"; |
import { AgGridVue } from 'ag-grid-vue3' |
||||
import { ElButton } from "element-plus"; |
import { ElButton } from 'element-plus' |
||||
import { type GridOptions } from "ag-grid-enterprise"; |
import { type GridOptions } from 'ag-grid-enterprise' |
||||
import { type GridApi, type GridReadyEvent } from "ag-grid-community"; |
import { type GridApi, type GridReadyEvent } from 'ag-grid-community' |
||||
import { Request } from "@ease-forge/shared"; |
import { Request } from '@ease-forge/shared' |
||||
import { localeText as localeTextCn } from "../components/yvTable/yv-aggrid-cn.locale"; |
import { localeText as localeTextCn } from '../components/yvTable/yv-aggrid-cn.locale' |
||||
import "ag-grid-community/styles/ag-grid.css"; |
import 'ag-grid-community/styles/ag-grid.css' |
||||
import "ag-grid-community/styles/ag-theme-alpine.css"; |
import 'ag-grid-community/styles/ag-theme-alpine.css' |
||||
|
|
||||
defineOptions({ |
defineOptions({ |
||||
name: 'OpenProject', |
name: 'OpenProject' |
||||
}); |
}) |
||||
|
|
||||
// 组件事件定义 |
// 组件事件定义 |
||||
const emit = defineEmits<{ |
const emit = defineEmits<{ |
||||
"cancel": []; |
'cancel': []; |
||||
"open": [row: any]; |
'open': [row: any]; |
||||
"add": [expose: OpenProjectExpose]; |
'add': [expose: OpenProjectExpose]; |
||||
}>(); |
}>() |
||||
|
|
||||
// 定义 Props 类型 |
// 定义 Props 类型 |
||||
interface OpenProjectProps { |
interface OpenProjectProps { |
||||
} |
} |
||||
|
|
||||
// 读取组件 props 属性 |
// 读取组件 props 属性 |
||||
const props = withDefaults(defineProps<OpenProjectProps>(), {}); |
const props = withDefaults(defineProps<OpenProjectProps>(), {}) |
||||
|
|
||||
// 定义 State 类型 |
// 定义 State 类型 |
||||
interface OpenProjectState { |
interface OpenProjectState { |
||||
grid1Data?: Array<any>; |
grid1Data?: Array<any>; |
||||
} |
} |
||||
|
|
||||
// state 属性 |
// state 属性 |
||||
const state = reactive<OpenProjectState>({}); |
const state = reactive<OpenProjectState>({}) |
||||
|
|
||||
// 定义 Data 类型 |
// 定义 Data 类型 |
||||
interface OpenProjectData { |
interface OpenProjectData { |
||||
gridSetting: Partial<GridOptions>; |
gridSetting: Partial<GridOptions>; |
||||
api?: GridApi; |
api?: GridApi; |
||||
} |
} |
||||
|
|
||||
// 内部数据 |
// 内部数据 |
||||
const data: OpenProjectData = { |
const data: OpenProjectData = { |
||||
gridSetting: { |
gridSetting: { |
||||
localeText: localeTextCn, |
localeText: localeTextCn, |
||||
// suppressNoRowsOverlay: true, |
// suppressNoRowsOverlay: true, |
||||
// suppressLoadingOverlay: true, |
// suppressLoadingOverlay: true, |
||||
// 选择行配置 |
// 选择行配置 |
||||
rowSelection: "single", |
rowSelection: 'single', |
||||
columnDefs: [ |
columnDefs: [ |
||||
{ field: 'id', headerName: 'id', editable: false, hide: true }, |
{ field: 'id', headerName: 'id', editable: false }, |
||||
{ field: 'projectUuid', headerName: '项目编号', editable: false }, |
{ field: 'projectUuid', headerName: '项目编号', editable: false }, |
||||
{ field: 'projectLabel', headerName: '项目标题', editable: false }, |
{ field: 'projectLabel', headerName: '项目标题', editable: false }, |
||||
{ field: 'projectVersion', headerName: '项目版本', editable: false }, |
{ field: 'projectVersion', headerName: '项目版本', editable: false }, |
||||
{ field: 'server', headerName: '所在服务器', editable: false }, |
{ field: 'createAt', headerName: '创建时间', editable: false }, |
||||
{ field: 'directoryData', headerName: '目录数据', editable: false }, |
{ field: 'createBy', headerName: '创建人', editable: false }, |
||||
{ field: 'otherData', headerName: '其他数据', editable: false }, |
{ field: 'updateAt', headerName: '最后更新时间', editable: false }, |
||||
{ field: 'createAt', headerName: '创建时间', editable: false }, |
{ field: 'updateBy', headerName: '更新人', editable: false } |
||||
{ field: 'createBy', headerName: '创建人', editable: false }, |
], |
||||
{ field: 'updateAt', headerName: '最后更新时间', editable: false }, |
onGridReady(event: GridReadyEvent) { |
||||
{ field: 'updateBy', headerName: '更新人', editable: false }, |
data.api = event.api |
||||
], |
} |
||||
onGridReady(event: GridReadyEvent) { |
} |
||||
data.api = event.api |
} |
||||
}, |
const grid = useTemplateRef<InstanceType<typeof AgGridVue>>('gridRef') |
||||
}, |
onMounted(loadData) |
||||
}; |
|
||||
const grid = useTemplateRef<InstanceType<typeof AgGridVue>>("gridRef"); |
|
||||
onMounted(loadData); |
|
||||
|
|
||||
function loadData() { |
function loadData() { |
||||
Request.request.post("/api/workbench/LccModelManager@projectList").then(res => { |
Request.request.post('/api/workbench/LccModelManager@projectList').then(res => { |
||||
state.grid1Data = res.records; |
state.grid1Data = res.records |
||||
}); |
}) |
||||
} |
} |
||||
|
|
||||
function open() { |
function open() { |
||||
const row = data.api?.getSelectedRows()?.[0]; |
const row = data.api?.getSelectedRows()?.[0] |
||||
if (!row) return; |
if (!row) return |
||||
emit("open", row); |
emit('open', row) |
||||
} |
} |
||||
|
|
||||
function add() { |
function add() { |
||||
emit("add", expose); |
emit('add', expose) |
||||
} |
} |
||||
|
|
||||
interface OpenProjectExpose { |
interface OpenProjectExpose { |
||||
state: OpenProjectState; |
state: OpenProjectState; |
||||
data: OpenProjectData; |
data: OpenProjectData; |
||||
loadData: () => void; |
loadData: () => void; |
||||
} |
} |
||||
|
|
||||
const expose: OpenProjectExpose = { |
const expose: OpenProjectExpose = { |
||||
state, |
state, |
||||
data, |
data, |
||||
loadData, |
loadData |
||||
}; |
} |
||||
// 定义组件公开内容 |
// 定义组件公开内容 |
||||
defineExpose(expose); |
defineExpose(expose) |
||||
|
|
||||
export type { |
export type { |
||||
OpenProjectProps, |
OpenProjectProps, |
||||
OpenProjectState, |
OpenProjectState |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<template> |
<template> |
||||
<div class="flex-column-container" style="height: 100%;"> |
<div class="flex-column-container" style="height: 100%;"> |
||||
<AgGridVue |
<AgGridVue |
||||
ref="gridRef" |
ref="gridRef" |
||||
:class="['ag-theme-alpine', 'yv-table', 'hi-light-selected-row','allow-vertical-line', 'flex-item-fill']" |
:class="['ag-theme-alpine', 'yv-table', 'hi-light-selected-row','allow-vertical-line', 'flex-item-fill']" |
||||
v-bind="{...data.gridSetting}" |
v-bind="{...data.gridSetting}" |
||||
:modelValue="state.grid1Data" |
:modelValue="state.grid1Data" |
||||
> |
> |
||||
</AgGridVue> |
</AgGridVue> |
||||
<div class="flex-item-fixed" style="padding-top: 12px;padding-right: 12px;text-align: right;"> |
<div class="flex-item-fixed" style="padding-top: 12px;padding-right: 12px;text-align: right;"> |
||||
<ElButton type="primary" @click="open">打开</ElButton> |
<ElButton type="primary" @click="open">打开</ElButton> |
||||
<ElButton type="primary" @click="add">新建项目</ElButton> |
<ElButton type="primary" @click="add">新建项目</ElButton> |
||||
<ElButton @click="emit('cancel')">取消</ElButton> |
<ElButton @click="emit('cancel')">取消</ElButton> |
||||
</div> |
|
||||
</div> |
</div> |
||||
|
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<style scoped> |
<style scoped> |
||||
|
|
||||
</style> |
</style> |
||||
|
|||||
Loading…
Reference in new issue