|
|
@@ -0,0 +1,189 @@
|
|
|
+package com.guanlan.backend.web;
|
|
|
+
|
|
|
+import com.guanlan.backend.redact.Redact;
|
|
|
+import io.swagger.annotations.Api;
|
|
|
+import io.swagger.annotations.ApiOperation;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.web.bind.annotation.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+import org.yaml.snakeyaml.Yaml;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * SCADA 通道清单(Java 实现)——契约 {@code /api/channels}。
|
|
|
+ *
|
|
|
+ * <p>与 Python `channel_list()`(serve.py:1974-1980)**逐字对齐**:
|
|
|
+ * 读契约 YAML → 取每组里 {@code liveness == '活'} 的列 → {@code {code, cn: ch_cn(col, with_tag=False) + " · " + col}}。
|
|
|
+ * 这里**不代理算法服务**:列名中文化是后端展示口径,且 Python 后端最终要删除,故用 Java 实现。
|
|
|
+ */
|
|
|
+@Api(tags = "channels")
|
|
|
+@RestController
|
|
|
+public class ChannelController {
|
|
|
+
|
|
|
+ private static final Map<String, String> STEM = new LinkedHashMap<>();
|
|
|
+ static {
|
|
|
+ STEM.put("MBearHTm", "前主轴承温度(轮毂侧)");
|
|
|
+ STEM.put("MBearGTm", "后主轴承温度(齿轮箱侧)");
|
|
|
+ STEM.put("GeOilTmp", "齿轮箱油温");
|
|
|
+ STEM.put("IMSGenTm", "齿轮箱中速轴温度(发电机侧)");
|
|
|
+ STEM.put("IMSRotTm", "齿轮箱中速轴温度(转子侧)");
|
|
|
+ STEM.put("HSGenTmp", "齿轮箱高速轴温度(发电机侧)");
|
|
|
+ STEM.put("HSRotTmp", "齿轮箱高速轴温度(转子侧)");
|
|
|
+ STEM.put("GFilB1Tm", "齿轮箱滤芯加热带B1温度");
|
|
|
+ STEM.put("GFilB2Tm", "齿轮箱滤芯加热带B2温度");
|
|
|
+ STEM.put("GFilB3Tm", "齿轮箱滤芯加热带B3温度");
|
|
|
+ STEM.put("BrkTmpGn", "机械刹车温度(发电机侧)");
|
|
|
+ STEM.put("BrkTmpGr", "机械刹车温度(齿轮箱侧)");
|
|
|
+ STEM.put("GenBeGTm", "发电机非驱动端(NDE)轴承温度");
|
|
|
+ STEM.put("GenBeRTm", "发电机驱动端(DE)轴承温度");
|
|
|
+ STEM.put("Gen1U1Tm", "发电机定子绕组温度U相");
|
|
|
+ STEM.put("Gen1V1Tm", "发电机定子绕组温度V相");
|
|
|
+ STEM.put("Gen1W1Tm", "发电机定子绕组温度W相");
|
|
|
+ STEM.put("ConvWTmp", "变流器冷却水温度");
|
|
|
+ STEM.put("TrafoTMa", "变压器绕组最高温度");
|
|
|
+ STEM.put("TraOilTF", "变压器油温");
|
|
|
+ STEM.put("TraRooTF", "变压器室温度");
|
|
|
+ STEM.put("A1ExtTmp", "A1柜排风温度");
|
|
|
+ STEM.put("A21IntTm", "A21柜内温度");
|
|
|
+ STEM.put("A3LefTmp", "A3柜左侧温度");
|
|
|
+ STEM.put("A3RigTmp", "A3柜右侧温度");
|
|
|
+ STEM.put("NacelTmp", "机舱温度");
|
|
|
+ STEM.put("HubTemp", "轮毂温度");
|
|
|
+ STEM.put("AmbieTmp", "环境温度");
|
|
|
+ STEM.put("HydOilTm", "液压油温");
|
|
|
+ STEM.put("HydPress_band", "变桨液压压力摆幅");
|
|
|
+ STEM.put("HydPress", "变桨液压系统压力");
|
|
|
+ STEM.put("YawPress", "偏航制动压力");
|
|
|
+ STEM.put("YawLubPu", "偏航润滑泵");
|
|
|
+ STEM.put("Y1LubPis", "偏航润滑柱塞");
|
|
|
+ STEM.put("MBLubPis", "主轴承润滑柱塞");
|
|
|
+ STEM.put("GBLubPis", "齿轮箱润滑柱塞");
|
|
|
+ STEM.put("GenLubPu", "发电机润滑泵");
|
|
|
+ STEM.put("HubLubPu", "轮毂润滑泵(变桨轴承)");
|
|
|
+ STEM.put("PitchPum", "变桨液压泵使能");
|
|
|
+ STEM.put("LubPistA", "变桨润滑柱塞A");
|
|
|
+ STEM.put("LubPistB", "变桨润滑柱塞B");
|
|
|
+ STEM.put("LubPistC", "变桨润滑柱塞C");
|
|
|
+ STEM.put("HydLevel", "液压油位开关");
|
|
|
+ STEM.put("HydrFilt", "液压滤芯开关");
|
|
|
+ STEM.put("ScYawOpe", "偏航动作(运行标志)");
|
|
|
+ STEM.put("TORYaw", "扭缆正常位");
|
|
|
+ STEM.put("Windvane", "风向标信号");
|
|
|
+ STEM.put("WindFauS", "风传感故障计数S路");
|
|
|
+ STEM.put("WindFauT", "风传感故障计数T路");
|
|
|
+ STEM.put("PriAnemo", "一次风速计");
|
|
|
+ STEM.put("SecAnemo", "二次风速计(判别用)");
|
|
|
+ STEM.put("ActualWindDirection", "风向(合成口径)");
|
|
|
+ STEM.put("MainSRpm", "主轴转速");
|
|
|
+ STEM.put("GenRpm", "发电机转速");
|
|
|
+ STEM.put("ActPower", "有功功率");
|
|
|
+ STEM.put("PowerRef", "功率给定");
|
|
|
+ STEM.put("PowerRed", "功率削减位");
|
|
|
+ STEM.put("PitcPosA", "桨叶A桨距角");
|
|
|
+ STEM.put("PitcPosB", "桨叶B桨距角");
|
|
|
+ STEM.put("PitcPosC", "桨叶C桨距角");
|
|
|
+ STEM.put("PitchRef_BladeA", "桨叶A桨距角给定");
|
|
|
+ STEM.put("PitchRef_BladeB", "桨叶B桨距角给定");
|
|
|
+ STEM.put("PitchRef_BladeC", "桨叶C桨距角给定");
|
|
|
+ STEM.put("kWG1TotE", "发电量计数(出口)");
|
|
|
+ STEM.put("kWG1TotI", "受电量计数");
|
|
|
+ STEM.put("kWG1Tot", "发电量计数");
|
|
|
+ STEM.put("ScComErr", "主控通讯错误计数");
|
|
|
+ STEM.put("ScEnvErr", "主控环境错误计数");
|
|
|
+ STEM.put("ScGrdErr", "主控电网错误计数");
|
|
|
+ STEM.put("ScTurErr", "主控机组错误计数");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final Map<String, String> SUFFIX = new LinkedHashMap<>();
|
|
|
+ static {
|
|
|
+ SUFFIX.put("_mean", "均值");
|
|
|
+ SUFFIX.put("_max", "最大");
|
|
|
+ SUFFIX.put("_min", "最小");
|
|
|
+ SUFFIX.put("_stddev", "标准差");
|
|
|
+ SUFFIX.put("_endvalue", "末值");
|
|
|
+ SUFFIX.put("_accum", "累计");
|
|
|
+ SUFFIX.put("_counts", "次数");
|
|
|
+ SUFFIX.put("_timeon", "接通时长");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Value("${guanlan.contract-yaml:reference/rudong/windscada_contract.yaml}")
|
|
|
+ private String contractYaml;
|
|
|
+
|
|
|
+ private Path contractFile() {
|
|
|
+ Path p = Paths.get(contractYaml);
|
|
|
+ if (p.isAbsolute() && Files.exists(p)) {
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+ Path cwd = Paths.get("").toAbsolutePath();
|
|
|
+ for (Path base : new Path[]{cwd, cwd.resolve("..").resolve("..")}) {
|
|
|
+ Path f = base.resolve(contractYaml);
|
|
|
+ if (Files.exists(f)) {
|
|
|
+ return f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cwd.resolve(contractYaml);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 旧 `i18n.ch_cn(col, with_tag=False)` */
|
|
|
+ static String chCn(String col) {
|
|
|
+ String base = col;
|
|
|
+ for (String pre : new String[]{"tmp_wtc_", "prs_wtc_", "dot_wtc_", "din_wtc_", "cnt_wtc_",
|
|
|
+ "flg_wtc_", "tur_wtc_", "grd_wtc_"}) {
|
|
|
+ base = base.replace(pre, "");
|
|
|
+ }
|
|
|
+ String suf = "";
|
|
|
+ for (Map.Entry<String, String> e : SUFFIX.entrySet()) {
|
|
|
+ if (base.endsWith(e.getKey())) {
|
|
|
+ base = base.substring(0, base.length() - e.getKey().length());
|
|
|
+ suf = e.getValue();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String cn = STEM.get(base);
|
|
|
+ if (cn == null) {
|
|
|
+ return col; // 查不到就回原列名(与旧实现一致)
|
|
|
+ }
|
|
|
+ return cn + (suf.isEmpty() ? "" : "·" + suf);
|
|
|
+ }
|
|
|
+
|
|
|
+ @ApiOperation("SCADA 通道清单(按契约分组,只列活列)")
|
|
|
+ @GetMapping(value = "/api/channels", produces = "application/json; charset=utf-8")
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public ResponseEntity<Map<String, Object>> channels() {
|
|
|
+ Map<String, Object> out = new LinkedHashMap<>();
|
|
|
+ try {
|
|
|
+ String text = new String(Files.readAllBytes(contractFile()), StandardCharsets.UTF_8);
|
|
|
+ Map<String, Object> c = new Yaml().load(text);
|
|
|
+ Map<String, Object> groups = (Map<String, Object>) c.get("groups");
|
|
|
+ for (Map.Entry<String, Object> g : groups.entrySet()) {
|
|
|
+ List<Map<String, Object>> cols = new ArrayList<>();
|
|
|
+ Map<String, Object> chan = (Map<String, Object>) g.getValue();
|
|
|
+ for (Map.Entry<String, Object> col : chan.entrySet()) {
|
|
|
+ Map<String, Object> meta = (Map<String, Object>) col.getValue();
|
|
|
+ if (!"活".equals(String.valueOf(meta.get("liveness")))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map<String, Object> item = new LinkedHashMap<>();
|
|
|
+ item.put("code", col.getKey());
|
|
|
+ item.put("cn", chCn(col.getKey()) + " · " + col.getKey());
|
|
|
+ cols.add(item);
|
|
|
+ }
|
|
|
+ out.put(g.getKey(), cols);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ out.clear();
|
|
|
+ out.put("err", ("契约读取失败: " + e.getMessage()).substring(0, Math.min(200, 20 + String.valueOf(e.getMessage()).length())));
|
|
|
+ }
|
|
|
+ return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body((Map<String, Object>) Redact.apply(out));
|
|
|
+ }
|
|
|
+}
|