Explorar el Código

楼控组态图-测试

huangyan hace 2 semanas
padre
commit
133b33d355

+ 152 - 0
internal/handler/intelligentbuildingcontrol.go

@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"go.uber.org/zap"
 	"math/rand"
+	"net/http"
 	"regexp"
 	"strconv"
 	"time"
@@ -218,6 +219,7 @@ func (h *IntelligentBuildingControlHandler) GetGetPoint(ctx *gin.Context) {
 	resp.HandleSuccess(ctx, m)
 
 }
+
 func (h *IntelligentBuildingControlHandler) GetGetPointSSE(ctx *gin.Context) {
 	// 设置响应头
 	ctx.Header("Content-Type", "text/event-stream")
@@ -320,6 +322,141 @@ func (h *IntelligentBuildingControlHandler) GetGetPointSSE(ctx *gin.Context) {
 	}
 }
 
+//func (h *IntelligentBuildingControlHandler) GetGetPointSSE(ctx *gin.Context) {
+//	// 设置响应头
+//	ctx.Header("Content-Type", "text/event-stream")
+//	ctx.Header("Cache-Control", "no-cache")
+//	ctx.Header("Connection", "keep-alive")
+//
+//	// 监听客户端断开连接
+//	conn := true
+//	notify := ctx.Writer.CloseNotify()
+//
+//	type Response struct {
+//		RequestId string `json:"requestId,omitempty"`
+//		Code      int32  `json:"code,omitempty"`
+//		Msg       string `json:"msg,omitempty"`
+//		Data      any    `json:"data"`
+//	}
+//
+//	pointName := ctx.Query("pointName")
+//	deviceType := ctx.Query("deviceType")
+//	building := ctx.Query("building")
+//	floor := ctx.Query("floor")
+//	section := ctx.Query("section")
+//	device_name := ctx.Query("deviceName")
+//
+//	conds := make(map[string]any)
+//	if pointName != "" {
+//		conds["point_name"] = pointName
+//	}
+//	if deviceType != "" {
+//		conds["device_type"] = deviceType
+//	}
+//	if building != "" {
+//		conds["building"] = building
+//	}
+//	if floor != "" {
+//		conds["floor"] = floor
+//	}
+//	if section != "" {
+//		conds["section"] = section
+//	}
+//	if device_name != "" {
+//		conds["device_name"] = device_name
+//	}
+//
+//	baseUrl := h.conf.GetString("obix.baseUrl")
+//
+//	var response Response
+//	for conn {
+//		select {
+//		case <-notify:
+//			conn = false
+//			fmt.Println("客户端断开连接")
+//			return
+//		default:
+//			m := make(map[string]any)
+//			points, _, err := h.intelligentBuildingControlService.GetPoint(conds, 1, 100)
+//			if err != nil {
+//				resp.HandleError(ctx, 1201, "查询点位失败", nil)
+//				conn = false
+//				continue
+//			}
+//
+//			var wg sync.WaitGroup
+//			var mutex sync.Mutex
+//			sem := make(chan struct{}, 10) // 控制最大并发数为10
+//
+//			for _, v := range *points {
+//				select {
+//				case sem <- struct{}{}:
+//				default:
+//					<-sem
+//					sem <- struct{}{}
+//				}
+//				wg.Add(1)
+//				go func(v model.Point) {
+//					defer func() {
+//						<-sem
+//						wg.Done()
+//					}()
+//
+//					url := baseUrl + v.FullPath
+//					request, err := obix.SendSecureRequest(url, h.conf.GetString("obix.username"), h.conf.GetString("obix.password"))
+//					if err != nil {
+//						h.logger.Error("发送请求失败", zap.String("url", url), zap.Error(err))
+//						return
+//					}
+//
+//					re := regexp.MustCompile(`val="([^"]+)"`)
+//					matches := re.FindStringSubmatch(request)
+//
+//					if len(matches) > 1 {
+//						s := model.PointName[v.PointName]
+//						if s != "" {
+//							value := matches[1]
+//							result := obix.DetectType(value)
+//
+//							mutex.Lock()
+//							defer mutex.Unlock()
+//
+//							switch val := result.(type) {
+//							case int:
+//								m[s] = val
+//							case float64:
+//								m[s] = fmt.Sprintf("%.2f", val)
+//							case bool:
+//								if val {
+//									m[s] = "是"
+//								} else {
+//									m[s] = "否"
+//								}
+//							default:
+//								m[s] = value
+//							}
+//						}
+//					} else {
+//						h.logger.Warn("未找到 val 值", zap.String("url", url))
+//					}
+//				}(v)
+//			}
+//
+//			wg.Wait() // 等待所有 goroutine 完成
+//
+//			response.Code = 200
+//			response.RequestId = ctx.ClientIP()
+//			response.Msg = "success"
+//			response.Data = m
+//
+//			res, _ := json.Marshal(&response)
+//			fmt.Fprintf(ctx.Writer, "data: %s\n\n", string(res))
+//			ctx.Writer.Flush()
+//			time.Sleep(10 * time.Second)
+//		}
+//	}
+//}
+
 // GetPointType 获取点位类型
 func (h *IntelligentBuildingControlHandler) GetPointType(ctx *gin.Context) {
 	points, err := h.intelligentBuildingControlService.GetPointType()
@@ -365,3 +502,18 @@ func (h *IntelligentBuildingControlHandler) GetDevices(ctx *gin.Context) {
 	}
 	resp.PageHandleSuccess(ctx, devices, total, pageNum, pageSize)
 }
+
+// ConfigurationDiagram 获取设备组态图
+func (h *IntelligentBuildingControlHandler) ConfigurationDiagram(c *gin.Context) {
+	// 获取查询参数
+	deviceName := c.Query("deviceName")
+	if deviceName == "" {
+		c.String(http.StatusBadRequest, "缺少 deviceName 参数")
+		return
+	}
+
+	// 传递变量给模板
+	c.HTML(http.StatusOK, "PAU.html", gin.H{
+		"deviceName": deviceName,
+	})
+}

+ 24 - 15
internal/model/intelligentbuildingcontrol.go

@@ -39,21 +39,30 @@ type PointType struct {
 
 // PointName 设备名称对应表
 var PointName = map[string]string{
-	"TempS1":   "送风温度",   //
-	"HumS1":    "送风湿度",   //
-	"TempH1":   "回风温度",   //
-	"TempX1":   "新风温度",   //
-	"CO2":      "回风二氧化碳", //
-	"AHUSts1":  "运行状态",   //
-	"AHUAlr1":  "故障报警",   //
-	"AHUAorm1": "自动状态",   //
-	"FJDPS1":   "风机压差",   //
-	"FDKG1":    "防冻开关",   //
-	"Sorw1":    "冬夏季模式",  //
-	"XFLev1":   "新风阀反馈",  //
-	"HFLev1":   "回风阀反馈",  //
-	"SFLev1":   "水阀反馈",
-	"GlwDPS1":  "过滤网压差", //
+	"TempS1":    "送风温度",
+	"HumS1":     "送风湿度",
+	"TempH1":    "回风温度",
+	"TempX1":    "新风温度",
+	"CO2":       "回风二氧化碳",
+	"AHUSts1":   "运行状态",
+	"AHUAlr1":   "故障报警",
+	"AHUAorm1":  "自动状态",
+	"FJDPS1":    "风机压差",
+	"FDKG1":     "防冻开关",
+	"Sorw1":     "冬夏季模式",
+	"XFLev1":    "新风阀反馈",
+	"HFLev1":    "回风阀反馈",
+	"SFLev1":    "水阀反馈",
+	"GlwDPS1":   "过滤网压差",
+	"PumbSts1":  "泵运行状态1",
+	"PumbAlr1":  "泵故障报警1",
+	"PumbSts2":  "泵运行状态2",
+	"PumbAlr2":  "泵故障报警2",
+	"PumbAorm1": "泵自动状态1",
+	"YWG1":      "超高液位",
+	"PFSts1":    "运行状态",
+	"PFAlr1":    "故障报警",
+	"PFAorm1":   "自动状态",
 }
 
 // RealPoint TempS1 点位表

+ 2 - 1
internal/server/http.go

@@ -30,7 +30,7 @@ func NewServerHTTP(
 	)
 	// 使用 HTTP/2 必须启用 HTTPS
 	//r.RunTLS(":443", "cert.pem", "key.pem")
-	r.LoadHTMLGlob("templates/h5player.html")
+	r.LoadHTMLGlob("templates/*.html")
 	r.Static("/static", "./templates/static")
 
 	//出入口控制系统
@@ -143,6 +143,7 @@ func NewServerHTTP(
 		inte.GET("/deviceType", intell.GetDeviceType)
 		inte.GET("/getPoint", intell.GetGetPoint)
 		inte.GET("/getDevices", intell.GetDevices)
+		inte.GET("/configurationDiagram", intell.ConfigurationDiagram)
 	}
 	//温控
 	temper := r.Group("/temperature")

+ 128 - 0
templates/PAU.html

@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+  <meta charset="UTF-8" />
+  <title>设备组态图 - {{ .deviceName }}</title>
+  <style>
+    body {
+      font-family: Arial, sans-serif;
+      background-color: #f4f6f8;
+      padding: 20px;
+    }
+    h1 {
+      text-align: center;
+      color: #333;
+    }
+    .dashboard {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+      gap: 20px;
+    }
+    .card {
+      background: white;
+      border-radius: 8px;
+      box-shadow: 0 2px 6px rgba(0,0,0,0.1);
+      padding: 20px;
+    }
+    .card h3 {
+      margin-top: 0;
+      font-size: 1.2em;
+      color: #555;
+    }
+    .value {
+      font-size: 1.5em;
+      font-weight: bold;
+      color: #007bff;
+      margin-top: 10px;
+    }
+    .status-on {
+      color: green;
+    }
+    .status-off {
+      color: red;
+    }
+  </style>
+</head>
+<body>
+
+<h1>{{ .deviceName }} 组态图</h1>
+
+<div class="dashboard" id="dashboard">
+  <!-- 动态生成卡片 -->
+</div>
+
+<script>
+const deviceName = "{{ .deviceName }}";
+const eventSource = new EventSource(`./pointSSE?deviceName=${deviceName}`);
+
+const dashboard = document.getElementById("dashboard");
+
+// 初始化卡片 DOM 结构
+const fields = {
+  "冬夏季模式": { type: "text", label: "冬夏模式" },
+  "回风二氧化碳": { type: "number", unit: "ppm", label: "回风 CO₂" },
+  "回风温度": { type: "number", unit: "℃", label: "回风温度" },
+  "回风阀反馈": { type: "number", unit: "%", label: "回风阀开度" },
+  "故障报警": { type: "boolean", label: "故障报警" },
+  "新风温度": { type: "number", unit: "℃", label: "新风温度" },
+  "新风阀反馈": { type: "number", unit: "%", label: "新风阀开度" },
+  "水阀反馈": { type: "number", unit: "%", label: "水阀开度" },
+  "自动状态": { type: "boolean", label: "自动状态" },
+  "过滤网压差": { type: "boolean", label: "过滤网压差" },
+  "运行状态": { type: "boolean", label: "运行状态" },
+  "送风温度": { type: "number", unit: "℃", label: "送风温度" },
+  "送风湿度": { type: "number", unit: "%", label: "送风湿度" },
+  "防冻开关": { type: "boolean", label: "防冻开关" },
+  "风机压差": { type: "boolean", label: "风机压差" }
+};
+
+// 创建初始卡片
+function createCards() {
+  Object.keys(fields).forEach(key => {
+    const card = document.createElement("div");
+    card.className = "card";
+    card.id = `card-${key}`;
+    card.innerHTML = `
+      <h3>${fields[key].label}</h3>
+      <div class="value" id="value-${key}">--</div>
+    `;
+    dashboard.appendChild(card);
+  });
+}
+
+createCards();
+
+// 处理 SSE 数据更新
+eventSource.onmessage = function(event) {
+  try {
+    const data = JSON.parse(event.data)?.data || {};
+    for (const key in data) {
+      const value = data[key];
+      const el = document.getElementById(`value-${key}`);
+      if (!el) continue;
+
+      // 类型判断并格式化显示
+      const field = fields[key];
+      if (field?.type === 'boolean') {
+        el.textContent = value === "是" ? "是" : "否";
+        el.className = `value ${value === "是" ? "status-on" : "status-off"}`;
+      } else if (field?.type === 'number') {
+        el.textContent = `${parseFloat(value).toFixed(2)}${field.unit || ''}`;
+        el.className = "value"; // 修复此处
+      } else {
+        el.textContent = value;
+        el.className = "value"; // 保持一致性
+      }
+    }
+  } catch (e) {
+    console.error("解析数据失败", e);
+  }
+};
+
+
+eventSource.onerror = function(err) {
+  console.error("SSE 错误:", err);
+};
+</script>
+</body>
+</html>