Eclipse Ditto 物模型搭建
需要知道Eclipse Ditto是什么 、怎么安装就点击下面的链接查阅。Eclipse Ditto 、 Mosquitto MQTT 、 OpenModelica 开发工具-CSDN博客Eclipse Ditto 开发环境 搭建-CSDN博客Eclipse Ditto开源物联网数字孪生平台技术深度解析 - 东峰叵,com - 博客园一、主要内容本文主要讲解Eclipse Ditto物模型的搭建及数据传输步骤供大家参考。二、界面预览下发的数据对应的控制面板三、Eclipse Ditto 物模型1创建物模型curl -X PUT http://localhost:8080/api/2/things/my-demo:device001 \ -u ditto:ditto \ -H Content-Type: application/json \ -d { attributes: { deviceName: 粉体料仓设备, model: TSK-001, location: 1号车间 }, features: { sensor: { properties: { pressure: 0, temperature: 25, level: 0, coConcentration: 0 } }, actuator: { properties: { valveOpen: false, fanRun: false } } } } 如果需要可以增加 { thingId: the.namespace:the-thing-id, policyId: the.namespace:the-policy-id }2测试反馈ianhang:~$ curl http://localhost:8080/api/2/things -u ditto:ditto [{thingId:my-demo:device001,policyId:my-demo:device001,attributes:{deviceName:粉体料仓设备,model:TSK-001,location:1号车间},features:{sensor:{properties:{pressure:-60,temperature:35,level:85,coConcentration:22}},actuator:{properties:{valveOpen:false,fanRun:false}}}}]四、代码文件展示src/utils/dittoWs.ts type DittoMsgCallback (msg: Recordstring, any) void export class DittoWebSocket { private ws: WebSocket | null null private url /ditto-ws private msgCallback: DittoMsgCallback | null null private reconnectTimer: number | null null private readonly RECONNECT_DELAY 3000 connect() { if (this.ws this.ws.readyState WebSocket.OPEN) return this.ws new WebSocket(this.url) this.bindEvent() } private bindEvent() { if (!this.ws) return this.ws.onopen () { console.log(✅ Ditto WebSocket 代理连接成功) this.subscribeAllThings() } this.ws.onmessage (ev) { try { const data JSON.parse(ev.data) console.log(收到Ditto原始消息, data) // 过滤错误消息不抛给页面 if(data.topic data.topic.includes(errors)) return this.msgCallback?.(data) } catch (err) { console.error(消息解析失败, ev.data, err) } } this.ws.onclose () { console.warn(WS断开3s后重连) this.reconnect() } this.ws.onerror (e) { console.error(WS异常, e) this.ws?.close() } } private reconnect() { if (this.reconnectTimer) clearTimeout(this.reconnectTimer) this.reconnectTimer window.setTimeout(() this.connect(), this.RECONNECT_DELAY) } // ✅ 修正订阅格式符合 Ditto WebSocket API subscribeAllThings() { const subCmd { topic: _/_/things/twin/commands/subscribe, headers: { response-required: false }, path: / } this.send(subCmd) console.log(✅ 标准订阅报文已发送) } // ✅ 新增检索指定 thing 的完整状态 retrieveThing(thingId: string) { const [ns, id] thingId.split(:) const cmd { topic: ${ns}/${id}/things/twin/commands/retrieve, headers: { response-required: true }, path: / } this.send(cmd) console.log(✅ 检索报文已发送: ${thingId}) } send(data: Recordstring, any) { if (!this.ws || this.ws.readyState ! WebSocket.OPEN) { console.warn(WS未就绪发送失败) return } this.ws.send(JSON.stringify(data)) } setMessageCallback(cb: DittoMsgCallback) { this.msgCallback cb } close() { if (this.reconnectTimer) clearTimeout(this.reconnectTimer) this.ws?.close() this.ws null } // 控制执行器格式正确未改动 controlActuator(thingId: string, valveOpen: boolean, fanRun: boolean) { const [ns, id] thingId.split(:) const cmd { topic: ${ns}/${id}/things/twin/commands/modify, path: features/actuator/properties, value: { valveOpen, fanRun } } this.send(cmd) } } export const dittoWs new DittoWebSocket() DigitalTwin.vue文件内容 template div classtwin-container h2 classtitle数字孪生设备实时面板/h2 div v-ifdeviceData classdevice-card p classrow span classlabel设备ID/span span classval{{ deviceId }}/span /p p classrow span classlabel仓内负压/span span classval{{ deviceData.features?.sensor?.properties?.pressure }} kPa/span /p p classrow span classlabel温度/span span classval{{ deviceData.features?.sensor?.properties?.temperature }} ℃/span /p p classrow span classlabel料位/span span classval{{ deviceData.features?.sensor?.properties?.level }} %/span /p p classrow span classlabelCO浓度/span span classval{{ deviceData.features?.sensor?.properties?.coConcentration }}/span /p div classdivider/div div classbtn-group button clicksetValve(true)打开卸料阀/button button clicksetValve(false)关闭卸料阀/button button clicksetFan(true)启动风机/button button clicksetFan(false)停止风机/button /div /div div v-else classloading 正在加载设备数据... /div /div /template script setup langts import { ref, onMounted, onUnmounted } from vue const deviceId my-demo:device001 const deviceData refany(null) let pollTimer: number | null null // 拉取设备全量数据 async function fetchThing() { try { const res await fetch(/api/2/things/${deviceId}) if (res.ok) { deviceData.value await res.json() } } catch (err) { console.error(拉取设备数据失败, err) } } // 修改执行器状态 async function updateActuator(data: Recordstring, boolean) { try { await fetch(/api/2/things/${deviceId}/features/actuator/properties, { method: PUT, headers: { Content-Type: application/json }, body: JSON.stringify(data) }) fetchThing() } catch (err) { console.error(修改执行器失败, err) } } // 按钮操作 const setValve (open: boolean) { if (!deviceData.value) return const fanRun deviceData.value.features?.actuator?.properties?.fanRun ?? false updateActuator({ valveOpen: open, fanRun }) } const setFan (run: boolean) { if (!deviceData.value) return const valveOpen deviceData.value.features?.actuator?.properties?.valveOpen ?? false updateActuator({ valveOpen, fanRun: run }) } onMounted(() { fetchThing() // 2秒轮询一次模拟实时更新 pollTimer window.setInterval(fetchThing, 2000) }) onUnmounted(() { if (pollTimer) clearInterval(pollTimer) }) /script style scoped .twin-container { padding: 16px; color: #e8f0ff; } .title { margin: 0 0 16px; font-size: 18px; color: #ffffff; } .device-card { font-size: 14px; } .row { display: flex; margin: 8px 0; } .label { width: 100px; color: #a0c4e8; } .val { color: #fff; } .divider { height: 1px; background: rgba(255,255,255,0.2); margin: 16px 0; } .btn-group { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } button { border: none; border-radius: 4px; padding: 8px 0; background: #1f589c; color: white; cursor: pointer; } button:hover { background: #2b72c2; } .loading { color: #b4cce6; text-align: center; margin-top: 40px; } /style vite.config.js文件内容 import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : resolve(__dirname, src) } }, server: { proxy: { // 直接对齐 Ditto 原生 /api 前缀自动携带鉴权无需路径重写 /api: { target: http://localhost:8080, changeOrigin: true, headers: { Authorization: Basic ZGl0dG86ZGl0dG8 } }, // WebSocket 通道代理后续升级实时推送备用 /ws/2: { target: ws://localhost:8080, ws: true, changeOrigin: true, headers: { Authorization: Basic ZGl0dG86ZGl0dG8 } } } } })五、数据联动1点击开卸料阀按钮和启动风机按钮后界面2点击关闭卸料阀按钮和关闭风机按钮后界面3检查数据流正常