添加gpio控制
This commit is contained in:
parent
18205b02df
commit
d91e6e41e1
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/rk1808-yolov5-inference" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"task_id": "6589",
|
"task_id": "6589",
|
||||||
"address": "房间南侧",
|
"address": "房间南侧",
|
||||||
"type": "1",
|
"type": "1",
|
||||||
"facility": "02"
|
"facility": "02"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"host_uuid": "qbxmjyzrkpntfgswaevodhluicqzxplkm",
|
"host_uuid": "qbxmjyzrkpntfgswaevodhluicqzxplkm",
|
||||||
|
|||||||
47
business/gpio_ctrl.go
Normal file
47
business/gpio_ctrl.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// gpio_controller.go
|
||||||
|
package business
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BuzzerGPIO = 109
|
||||||
|
|
||||||
|
// TriggerBuzzer 触发蜂鸣器响指定秒数
|
||||||
|
func TriggerBuzzer(seconds int) {
|
||||||
|
if seconds <= 0 {
|
||||||
|
seconds = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// 必须用同步!异步 goroutine 在某些系统里会被静默丢弃
|
||||||
|
fmt.Printf("蜂鸣器开始响铃 %d 秒...\n", seconds)
|
||||||
|
|
||||||
|
gpioPath := fmt.Sprintf("/sys/class/gpio/gpio%d", BuzzerGPIO)
|
||||||
|
|
||||||
|
// 1. 强制导出(即使已经导出也没事)
|
||||||
|
os.WriteFile("/sys/class/gpio/export", []byte(strconv.Itoa(BuzzerGPIO)), 0666)
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// 2. 强制设为输出
|
||||||
|
os.WriteFile(fmt.Sprintf("%s/direction", gpioPath), []byte("out"), 0666)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// 3. 拉高 → 响
|
||||||
|
if err := os.WriteFile(fmt.Sprintf("%s/value", gpioPath), []byte("1"), 0666); err != nil {
|
||||||
|
fmt.Printf("GPIO拉高失败: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("GPIO109 已拉高 → 蜂鸣器响!")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(seconds) * time.Second)
|
||||||
|
|
||||||
|
// 4. 拉低 → 停
|
||||||
|
if err := os.WriteFile(fmt.Sprintf("%s/value", gpioPath), []byte("0"), 0666); err != nil {
|
||||||
|
fmt.Printf("GPIO拉低失败: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("GPIO109 已拉低 → 蜂鸣器停止")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,17 @@ var (
|
|||||||
wsURL = "ws://172.17.0.1:18080/ws"
|
wsURL = "ws://172.17.0.1:18080/ws"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AlertMessage struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Topic string `json:"topic"`
|
||||||
|
QoS int `json:"qos"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Timeout int `json:"timeout"`
|
||||||
|
}
|
||||||
|
type AlertContent struct {
|
||||||
|
Type int `json:"type"` // 1=蜂鸣器告警
|
||||||
|
BuzzerDuration int `json:"buzzer_duration"` // 响多久(秒)
|
||||||
|
}
|
||||||
type WSMessage struct {
|
type WSMessage struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
@ -46,8 +57,9 @@ type MetricDataParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PublishParams struct {
|
type PublishParams struct {
|
||||||
Topic string `json:"topic"`
|
Topic string `json:"topic"`
|
||||||
Data json.RawMessage `json:"data,omitempty"`
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
Content json.RawMessage `json:"content,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WSClient struct {
|
type WSClient struct {
|
||||||
@ -65,15 +77,6 @@ type WSClient struct {
|
|||||||
reconnectFlag bool
|
reconnectFlag bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Public API ====================
|
|
||||||
|
|
||||||
//// GetWSClient returns the global WebSocket client instance
|
|
||||||
//func GetWSClient() *WSClient {
|
|
||||||
// wsMutex.RLock()
|
|
||||||
// defer wsMutex.RUnlock()
|
|
||||||
// return wsClient
|
|
||||||
//}
|
|
||||||
|
|
||||||
// InitWSChannel initializes WebSocket connection
|
// InitWSChannel initializes WebSocket connection
|
||||||
func InitWSChannel() error {
|
func InitWSChannel() error {
|
||||||
wsMutex.Lock()
|
wsMutex.Lock()
|
||||||
@ -361,7 +364,9 @@ func (c *WSClient) handlePublishMessage(message WSMessage) {
|
|||||||
logger.Logger.Printf("Failed to parse publish message parameters: %v", err)
|
logger.Logger.Printf("Failed to parse publish message parameters: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 加这一行!万能抓包,永不漏!
|
||||||
|
logger.Logger.Printf("WebSocket 收到 publish 消息 → Topic: %s | 原始数据: %s",
|
||||||
|
publishParams.Topic, string(publishParams.Data))
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
callback, exists := c.subscribers[publishParams.Topic]
|
callback, exists := c.subscribers[publishParams.Topic]
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
@ -458,11 +463,11 @@ func (c *WSClient) autoReRegister() {
|
|||||||
}
|
}
|
||||||
c.mutex.Unlock()
|
c.mutex.Unlock()
|
||||||
|
|
||||||
for topic, callback := range subscribers {
|
//for topic, callback := range subscribers {
|
||||||
if err := SubscribeTopic(topic, callback); err != nil {
|
// if err := SubscribeTopic(topic, callback); err != nil {
|
||||||
logger.Logger.Printf("Failed to re-subscribe topic %s: %v", topic, err)
|
// logger.Logger.Printf("Failed to re-subscribe topic %s: %v", topic, err)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
68
main.go
68
main.go
@ -9,6 +9,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -126,6 +127,13 @@ func runNormalMode() {
|
|||||||
if err := connect.SubscribeTopic("device_updates", handleDeviceUpdates); err != nil {
|
if err := connect.SubscribeTopic("device_updates", handleDeviceUpdates); err != nil {
|
||||||
log.Fatal("Topic subscription failed:", err)
|
log.Fatal("Topic subscription failed:", err)
|
||||||
}
|
}
|
||||||
|
logger.Logger.Printf("Subscribing to alert topic (/dthjjc/alert) for buzzer...")
|
||||||
|
|
||||||
|
if err := connect.SubscribeTopic("/dthjjc/alert", handleAlertMessage); err != nil {
|
||||||
|
logger.Logger.Printf("Failed to subscribe alert topic: %v (continue anyway)", err)
|
||||||
|
} else {
|
||||||
|
logger.Logger.Printf("Successfully subscribed to /dthjjc/alert → buzzer ready!")
|
||||||
|
}
|
||||||
|
|
||||||
// 8. 启动数据采集循环
|
// 8. 启动数据采集循环
|
||||||
logger.Logger.Printf("Starting data collection loop...")
|
logger.Logger.Printf("Starting data collection loop...")
|
||||||
@ -171,3 +179,63 @@ func startDataCollectionLoop(businessManager *business.BusinessManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAlertMessage(params *connect.PublishParams) {
|
||||||
|
payload := params.Content
|
||||||
|
if len(payload) == 0 {
|
||||||
|
payload = params.Data
|
||||||
|
}
|
||||||
|
if len(payload) == 0 {
|
||||||
|
logger.Logger.Printf("平台告警空包 → 强制响 3 秒")
|
||||||
|
business.TriggerBuzzer(3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := string(payload)
|
||||||
|
logger.Logger.Printf("收到告警原始内容 → %s", raw)
|
||||||
|
|
||||||
|
type Cmd struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
BuzzerDuration json.RawMessage `json:"buzzer_duration"` // 万能类型!
|
||||||
|
}
|
||||||
|
var cmd Cmd
|
||||||
|
|
||||||
|
var outer string
|
||||||
|
if json.Unmarshal(payload, &outer) == nil {
|
||||||
|
logger.Logger.Printf("检测到外层是字符串 → %s", outer)
|
||||||
|
raw = outer
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(raw), &cmd); err != nil {
|
||||||
|
logger.Logger.Printf("最终解析失败: %v → 暴力识别", err)
|
||||||
|
} else if cmd.Type == 1 {
|
||||||
|
|
||||||
|
dur := 3
|
||||||
|
var temp interface{}
|
||||||
|
if json.Unmarshal(cmd.BuzzerDuration, &temp) == nil {
|
||||||
|
switch v := temp.(type) {
|
||||||
|
case string:
|
||||||
|
if n, err := strconv.Atoi(v); err == nil {
|
||||||
|
dur = n
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
dur = int(v)
|
||||||
|
case int:
|
||||||
|
dur = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dur <= 0 {
|
||||||
|
dur = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Logger.Printf("平台告警解析成功 → 蜂鸣器响 %d 秒!!!", dur)
|
||||||
|
business.TriggerBuzzer(dur)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 终极兜底:只要包含 type 和 1 就响
|
||||||
|
//if strings.Contains(raw, "type") && strings.Contains(raw, "1") {
|
||||||
|
// logger.Logger.Printf("暴力识别告警指令 → 强制响 3 秒")
|
||||||
|
// business.TriggerBuzzer(3)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|||||||
84
连接线路.drawio
Normal file
84
连接线路.drawio
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<mxfile host="Electron" modified="2025-11-26T09:09:40.277Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.0.4 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="c7_EBDN9ol-w24zU6wdL" version="24.0.4" type="device">
|
||||||
|
<diagram name="第 1 页" id="tfFKj4gfuwo1-KzXBk_g">
|
||||||
|
<mxGraphModel dx="1309" dy="999" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="CCi69vAXkhbXQBJSxABA-1" target="CCi69vAXkhbXQBJSxABA-8" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="CCi69vAXkhbXQBJSxABA-1" target="CCi69vAXkhbXQBJSxABA-9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-1" target="CCi69vAXkhbXQBJSxABA-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="CCi69vAXkhbXQBJSxABA-1" target="CCi69vAXkhbXQBJSxABA-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="CCi69vAXkhbXQBJSxABA-1" target="CCi69vAXkhbXQBJSxABA-14" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-1" value="N2000" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="700" y="360" width="120" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-4" target="CCi69vAXkhbXQBJSxABA-14" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-4" target="CCi69vAXkhbXQBJSxABA-7" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-4" value="220V供电" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="730" y="280" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-5" target="CCi69vAXkhbXQBJSxABA-10" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-5" target="CCi69vAXkhbXQBJSxABA-9" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-5" target="CCi69vAXkhbXQBJSxABA-8" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;dashed=1;" parent="1" source="CCi69vAXkhbXQBJSxABA-5" target="CCi69vAXkhbXQBJSxABA-1" edge="1">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="980" y="430" />
|
||||||
|
<mxPoint x="820" y="430" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-5" value="12v" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="950" y="465" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-7" value="安卓<div>显示屏</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="890" y="370" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-8" value="温湿度<br>传感器" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="660" y="470" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-9" value="水浸<div>传感器</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="730" y="470" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-10" value="<div>烟雾</div><div>传感器</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="800" y="470" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-14" value="摄像头" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="580" y="370" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-15" value="485" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="760" y="418" width="40" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-17" value="HTTP" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="825" y="368" width="50" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-19" value="RTSP" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="640" y="368" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="CCi69vAXkhbXQBJSxABA-27" value="电表" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="730" y="310" width="60" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
Loading…
x
Reference in New Issue
Block a user