StationConfig.java 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. package com.mes.core;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.yaml.snakeyaml.Yaml;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.ArrayList;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. /**
  13. * 工位配置类 - 从YAML配置文件加载工位配置
  14. * 解决工位号硬编码问题,支持单工位/双工位动态切换
  15. */
  16. public class StationConfig {
  17. private static final Logger log = LoggerFactory.getLogger(StationConfig.class);
  18. // 单例实例
  19. private static StationConfig instance;
  20. // ========== 工位配置 ==========
  21. private String mode; // single | dual
  22. private String stationName; // 工位/工序名称(如"液冷板安装+激光点固")
  23. private List<StationInfo> stations; // 工位列表
  24. private String lineSn; // 线体号
  25. // ========== 服务器配置 ==========
  26. private String serverIp;
  27. private int tcpPort;
  28. private int httpPort; // HTTP服务端口
  29. private int heartBeatCycle;
  30. // ========== 流程配置 ==========
  31. private List<StepConfig> workflowSteps;
  32. private String submitMode; // manual | auto (手动点击OK/NG | 自动提交)
  33. private Map<String, Boolean> yamlFeatures = new HashMap<>(); // yaml 中定义的功能默认开关
  34. // ========== 设备配置 ==========
  35. private boolean deviceEnabled;
  36. private String deviceType; // modbus_tcp | modbus_rtu | s7 | none
  37. private List<DeviceConnection> deviceConnections;
  38. private boolean timeoutEnabled;
  39. private int connectTimeout = 3000; // Modbus连接超时(ms)
  40. private int heartbeatAddress;
  41. private int readySignalAddress;
  42. // 设备地址配置
  43. private DeviceAddresses deviceAddresses;
  44. // 完成条件配置
  45. private CompleteCondition completeCondition;
  46. // 心跳配置
  47. private HeartbeatConfig heartbeatConfig;
  48. // 继电器配置
  49. private RelayConfig relayConfig;
  50. // 过程参数采集配置
  51. private ProdParamsConfig prodParamsConfig;
  52. // ========== UI配置 ==========
  53. private boolean showDeviceInfo;
  54. private boolean showDeviceInfo2; // 是否显示第二行设备信息
  55. private boolean showMaterialInput;
  56. private String materialLabel;
  57. private List<DeviceInfoRow> deviceInfoRows; // 设备信息行配置
  58. // 原始配置Map,用于获取未解析的配置
  59. private Map<String, Object> rawConfig;
  60. private StationConfig() {
  61. stations = new ArrayList<>();
  62. workflowSteps = new ArrayList<>();
  63. deviceConnections = new ArrayList<>();
  64. deviceInfoRows = new ArrayList<>();
  65. }
  66. /**
  67. * 获取单例实例
  68. */
  69. public static synchronized StationConfig getInstance() {
  70. if (instance == null) {
  71. instance = new StationConfig();
  72. instance.loadFromResource("resources/config/station.yaml");
  73. }
  74. return instance;
  75. }
  76. /**
  77. * 重新加载配置(用于热更新)
  78. */
  79. public static synchronized void reload() {
  80. instance = new StationConfig();
  81. instance.loadFromResource("resources/config/station.yaml");
  82. }
  83. /**
  84. * 从指定路径加载配置
  85. */
  86. public static synchronized StationConfig loadFrom(String resourcePath) {
  87. instance = new StationConfig();
  88. instance.loadFromResource(resourcePath);
  89. return instance;
  90. }
  91. /**
  92. * 从资源文件加载配置
  93. */
  94. private void loadFromResource(String resourcePath) {
  95. try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) {
  96. if (is == null) {
  97. log.error("配置文件不存在: {}", resourcePath);
  98. throw new RuntimeException("配置文件不存在: " + resourcePath);
  99. }
  100. Yaml yaml = new Yaml();
  101. Map<String, Object> config = yaml.load(new InputStreamReader(is, StandardCharsets.UTF_8));
  102. parseConfig(config);
  103. log.info("配置加载成功: mode={}, stations={}", mode, stations.size());
  104. } catch (Exception e) {
  105. log.error("加载配置文件失败: {}", e.getMessage(), e);
  106. throw new RuntimeException("加载配置文件失败", e);
  107. }
  108. }
  109. /**
  110. * 展开 steps 列表:遇到 feature 块时按功能开关(含 override)过滤
  111. * feature 块形如:
  112. * - feature: cold_plate
  113. * steps: [...]
  114. */
  115. @SuppressWarnings("unchecked")
  116. private void expandSteps(List<Map<String, Object>> stepsArr, List<StepConfig> out) {
  117. for (Map<String, Object> node : stepsArr) {
  118. Object featureKey = node.get("feature");
  119. if (featureKey != null) {
  120. String fname = featureKey.toString();
  121. if (!isFeatureEnabled(fname)) {
  122. log.debug("跳过禁用的feature: {}", fname);
  123. continue;
  124. }
  125. List<Map<String, Object>> inner = (List<Map<String, Object>>) node.get("steps");
  126. if (inner != null) {
  127. expandSteps(inner, out);
  128. }
  129. continue;
  130. }
  131. // 普通步骤
  132. if (node.get("id") == null) continue;
  133. StepConfig stepConfig = new StepConfig();
  134. stepConfig.setId((String) node.get("id"));
  135. stepConfig.setName((String) node.get("name"));
  136. stepConfig.setCraft((String) node.get("craft"));
  137. Map<String, Object> stepConfigMap = (Map<String, Object>) node.get("config");
  138. if (stepConfigMap != null) {
  139. stepConfig.setConfig(stepConfigMap);
  140. }
  141. out.add(stepConfig);
  142. }
  143. }
  144. /**
  145. * 查询功能是否启用
  146. * 优先级:运行时 override (ProjectConfigManager) > yaml 默认 > false
  147. */
  148. public boolean isFeatureEnabled(String feature) {
  149. Boolean override = ProjectConfigManager.getInstance().getFeatureOverride(feature);
  150. if (override != null) return override;
  151. Boolean def = yamlFeatures.get(feature);
  152. return def != null && def;
  153. }
  154. /**
  155. * 获取所有 yaml 定义的功能开关(用于 UI 显示可用功能列表)
  156. */
  157. public Map<String, Boolean> getYamlFeatures() {
  158. return new HashMap<>(yamlFeatures);
  159. }
  160. /**
  161. * 根据当前功能开关推导物料码标签:
  162. * 启用 cold_plate → "冷板码"
  163. * 启用 bottom_plate → "底护板码"
  164. * 都未启用 → yaml 配置的 materialLabel
  165. */
  166. public String getEffectiveMaterialLabel() {
  167. if (isFeatureEnabled("cold_plate")) return "冷板码";
  168. if (isFeatureEnabled("bottom_plate")) return "底护板码";
  169. return materialLabel;
  170. }
  171. /**
  172. * 解析YAML配置
  173. */
  174. @SuppressWarnings("unchecked")
  175. private void parseConfig(Map<String, Object> config) {
  176. rawConfig = config;
  177. // 解析工位配置
  178. Map<String, Object> stationObj = (Map<String, Object>) config.get("station");
  179. if (stationObj != null) {
  180. mode = (String) stationObj.get("mode");
  181. stationName = (String) stationObj.get("name");
  182. lineSn = (String) stationObj.get("line_sn");
  183. List<Map<String, Object>> stationsArr = (List<Map<String, Object>>) stationObj.get("stations");
  184. if (stationsArr != null) {
  185. for (int i = 0; i < stationsArr.size(); i++) {
  186. Map<String, Object> s = stationsArr.get(i);
  187. stations.add(new StationInfo(
  188. i,
  189. (String) s.get("code")
  190. ));
  191. }
  192. }
  193. }
  194. // 解析服务器配置
  195. Map<String, Object> serverObj = (Map<String, Object>) config.get("server");
  196. if (serverObj != null) {
  197. serverIp = (String) serverObj.get("ip");
  198. tcpPort = getIntValue(serverObj, "tcp_port", 3000);
  199. httpPort = getIntValue(serverObj, "http_port", 8980);
  200. heartBeatCycle = getIntValue(serverObj, "heart_beat_cycle", 60);
  201. }
  202. // 解析流程配置
  203. Map<String, Object> workflowObj = (Map<String, Object>) config.get("workflow");
  204. if (workflowObj != null) {
  205. // 提交模式:manual(手动点击OK/NG) | auto(设备信号自动提交)
  206. submitMode = (String) workflowObj.get("submit_mode");
  207. if (submitMode == null || submitMode.isEmpty()) {
  208. submitMode = "manual"; // 默认手动模式
  209. }
  210. // 解析功能开关(yaml 默认值)
  211. yamlFeatures.clear();
  212. Map<String, Object> featuresObj = (Map<String, Object>) workflowObj.get("features");
  213. if (featuresObj != null) {
  214. for (Map.Entry<String, Object> e : featuresObj.entrySet()) {
  215. if (e.getValue() instanceof Boolean) {
  216. yamlFeatures.put(e.getKey(), (Boolean) e.getValue());
  217. }
  218. }
  219. }
  220. List<Map<String, Object>> stepsArr = (List<Map<String, Object>>) workflowObj.get("steps");
  221. if (stepsArr != null) {
  222. expandSteps(stepsArr, workflowSteps);
  223. }
  224. }
  225. // 解析设备配置
  226. Map<String, Object> deviceObj = (Map<String, Object>) config.get("device");
  227. if (deviceObj != null) {
  228. deviceEnabled = getBooleanValue(deviceObj, "enabled", false);
  229. deviceType = (String) deviceObj.get("type");
  230. timeoutEnabled = getBooleanValue(deviceObj, "timeout_enabled", false);
  231. connectTimeout = getIntValue(deviceObj, "connect_timeout", 3000);
  232. heartbeatAddress = getIntValue(deviceObj, "heartbeat_address", 4160);
  233. readySignalAddress = getIntValue(deviceObj, "ready_signal_address", 8);
  234. List<Map<String, Object>> connArr = (List<Map<String, Object>>) deviceObj.get("connections");
  235. if (connArr != null) {
  236. for (int i = 0; i < connArr.size(); i++) {
  237. Map<String, Object> conn = connArr.get(i);
  238. deviceConnections.add(new DeviceConnection(
  239. getIntValue(conn, "station_index", i),
  240. (String) conn.get("ip"),
  241. getIntValue(conn, "port", 502)
  242. ));
  243. }
  244. }
  245. // 解析设备地址配置
  246. Map<String, Object> addressesObj = (Map<String, Object>) deviceObj.get("addresses");
  247. if (addressesObj != null) {
  248. deviceAddresses = new DeviceAddresses();
  249. deviceAddresses.setEnableSignal((String) addressesObj.get("enable_signal"));
  250. deviceAddresses.setStartSignal((String) addressesObj.get("start_signal"));
  251. deviceAddresses.setCompleteSignal((String) addressesObj.get("complete_signal"));
  252. deviceAddresses.setDeviceStatus((String) addressesObj.get("device_status"));
  253. deviceAddresses.setFinishedCount(getIntValue(addressesObj, "finished_count", 0));
  254. deviceAddresses.setPresetCount(getIntValue(addressesObj, "preset_count", 0));
  255. deviceAddresses.setQualifiedCount(getIntValue(addressesObj, "qualified_count", 0));
  256. deviceAddresses.setStartCommand(getIntValue(addressesObj, "start_command", 0));
  257. }
  258. // 解析完成条件配置
  259. Map<String, Object> conditionObj = (Map<String, Object>) deviceObj.get("complete_condition");
  260. if (conditionObj != null) {
  261. completeCondition = new CompleteCondition();
  262. completeCondition.setType((String) conditionObj.get("type"));
  263. completeCondition.setRunningValue(getIntValue(conditionObj, "running_value", 1));
  264. completeCondition.setStoppedValue(getIntValue(conditionObj, "stopped_value", 0));
  265. completeCondition.setMinRunningTime(getIntValue(conditionObj, "min_running_time", 0));
  266. } else {
  267. // 默认使用信号判断
  268. completeCondition = new CompleteCondition();
  269. completeCondition.setType("signal");
  270. }
  271. // 解析心跳配置
  272. Map<String, Object> heartbeatObj = (Map<String, Object>) deviceObj.get("heartbeat");
  273. if (heartbeatObj != null) {
  274. heartbeatConfig = new HeartbeatConfig();
  275. heartbeatConfig.setEnabled(getBooleanValue(heartbeatObj, "enabled", true));
  276. heartbeatConfig.setAddress(getIntValue(heartbeatObj, "address", 4160));
  277. heartbeatConfig.setInterval(getIntValue(heartbeatObj, "interval", 500));
  278. heartbeatConfig.setTimeoutAddress(getIntValue(heartbeatObj, "timeout_address", 4170));
  279. heartbeatConfig.setTimeoutValue(getIntValue(heartbeatObj, "timeout_value", 10));
  280. }
  281. // 解析继电器配置
  282. Map<String, Object> relayObj = (Map<String, Object>) deviceObj.get("relay");
  283. if (relayObj != null) {
  284. relayConfig = new RelayConfig();
  285. relayConfig.setEnabled(getBooleanValue(relayObj, "enabled", true));
  286. relayConfig.setType((String) relayObj.get("type"));
  287. relayConfig.setPort((String) relayObj.get("port"));
  288. relayConfig.setBaudrate(getIntValue(relayObj, "baudrate", 38400));
  289. relayConfig.setOpenCommand((String) relayObj.get("open_command"));
  290. relayConfig.setCloseCommand((String) relayObj.get("close_command"));
  291. }
  292. // 解析过程参数采集配置
  293. Map<String, Object> prodParamsObj = (Map<String, Object>) deviceObj.get("prod_params");
  294. if (prodParamsObj != null) {
  295. prodParamsConfig = new ProdParamsConfig();
  296. prodParamsConfig.setEnabled(getBooleanValue(prodParamsObj, "enabled", false));
  297. prodParamsConfig.setFoutAddress(getIntValue(prodParamsObj, "fout_address", 4112));
  298. prodParamsConfig.setSoutAddress(getIntValue(prodParamsObj, "sout_address", 4120));
  299. prodParamsConfig.setFminAddress(getIntValue(prodParamsObj, "fmin_address", 4116));
  300. prodParamsConfig.setSminAddress(getIntValue(prodParamsObj, "smin_address", 4126));
  301. prodParamsConfig.setFmaxAddress(getIntValue(prodParamsObj, "fmax_address", 4114));
  302. prodParamsConfig.setSmaxAddress(getIntValue(prodParamsObj, "smax_address", 4124));
  303. prodParamsConfig.setQtyAddress(getIntValue(prodParamsObj, "qty_address", 4));
  304. prodParamsConfig.setUploadInterval(getIntValue(prodParamsObj, "upload_interval", 60));
  305. }
  306. }
  307. // 解析UI配置
  308. Map<String, Object> uiObj = (Map<String, Object>) config.get("ui");
  309. if (uiObj != null) {
  310. showMaterialInput = getBooleanValue(uiObj, "show_material_input", false);
  311. materialLabel = (String) uiObj.get("material_label");
  312. // 解析设备信息行配置
  313. List<Map<String, Object>> rowsArr = (List<Map<String, Object>>) uiObj.get("device_info_rows");
  314. if (rowsArr != null && !rowsArr.isEmpty()) {
  315. for (int i = 0; i < Math.min(rowsArr.size(), 2); i++) {
  316. Map<String, Object> row = rowsArr.get(i);
  317. DeviceInfoRow infoRow = new DeviceInfoRow();
  318. infoRow.setLabel((String) row.get("label"));
  319. infoRow.setConnectionIndex(getIntValue(row, "connection_index", 0));
  320. infoRow.setPresetAddress(getIntValue(row, "preset_address", 0));
  321. infoRow.setFinishedAddress(getIntValue(row, "finished_address", 0));
  322. deviceInfoRows.add(infoRow);
  323. }
  324. showDeviceInfo = true;
  325. showDeviceInfo2 = deviceInfoRows.size() > 1;
  326. } else {
  327. // 兼容旧配置
  328. showDeviceInfo = getBooleanValue(uiObj, "show_device_info", false);
  329. showDeviceInfo2 = false;
  330. }
  331. }
  332. // features 覆盖:prod_params 功能开关生效到 prodParamsConfig
  333. if (prodParamsConfig != null && yamlFeatures.containsKey("prod_params")) {
  334. boolean effective = isFeatureEnabled("prod_params");
  335. if (prodParamsConfig.isEnabled() != effective) {
  336. log.info("prod_params 启用状态被 features 覆盖: {} -> {}", prodParamsConfig.isEnabled(), effective);
  337. prodParamsConfig.setEnabled(effective);
  338. }
  339. }
  340. // 应用 UI 覆盖:设备信息行启用/label
  341. ProjectConfigManager pc = ProjectConfigManager.getInstance();
  342. applyDeviceRowOverride(0, pc.getDeviceRow1EnabledOverride(), pc.getDeviceRow1LabelOverride());
  343. applyDeviceRowOverride(1, pc.getDeviceRow2EnabledOverride(), pc.getDeviceRow2LabelOverride());
  344. // showDeviceInfo / showDeviceInfo2 按 enabled 合并重算
  345. showDeviceInfo = !deviceInfoRows.isEmpty() && deviceInfoRows.get(0).isEnabled();
  346. showDeviceInfo2 = deviceInfoRows.size() > 1 && deviceInfoRows.get(1).isEnabled();
  347. // 物料码输入框显隐覆盖
  348. Boolean smiOverride = pc.getShowMaterialInputOverride();
  349. if (smiOverride != null) {
  350. showMaterialInput = smiOverride;
  351. }
  352. }
  353. private void applyDeviceRowOverride(int idx, Boolean enabled, String label) {
  354. if (idx < 0 || idx >= deviceInfoRows.size()) return;
  355. DeviceInfoRow row = deviceInfoRows.get(idx);
  356. if (enabled != null) row.setEnabled(enabled);
  357. if (label != null) row.setLabel(label);
  358. }
  359. /**
  360. * 从Map中获取整数值,支持默认值
  361. */
  362. private int getIntValue(Map<String, Object> map, String key, int defaultValue) {
  363. Object value = map.get(key);
  364. if (value instanceof Number) {
  365. return ((Number) value).intValue();
  366. }
  367. return defaultValue;
  368. }
  369. /**
  370. * 从Map中获取布尔值,支持默认值
  371. */
  372. private boolean getBooleanValue(Map<String, Object> map, String key, boolean defaultValue) {
  373. Object value = map.get(key);
  374. if (value instanceof Boolean) {
  375. return (Boolean) value;
  376. }
  377. return defaultValue;
  378. }
  379. // ========== 便捷方法 ==========
  380. /**
  381. * 是否为单工位模式
  382. */
  383. public boolean isSingleMode() {
  384. return "single".equalsIgnoreCase(mode);
  385. }
  386. /**
  387. * 是否为双工位模式
  388. */
  389. public boolean isDualMode() {
  390. return "dual".equalsIgnoreCase(mode);
  391. }
  392. /**
  393. * 获取工位数量
  394. */
  395. public int getStationCount() {
  396. return stations.size();
  397. }
  398. /**
  399. * 根据索引获取工位信息
  400. */
  401. public StationInfo getStation(int index) {
  402. if (index >= 0 && index < stations.size()) {
  403. return stations.get(index);
  404. }
  405. return null;
  406. }
  407. /**
  408. * 根据工位号获取工位信息
  409. */
  410. public StationInfo getStationByCode(String code) {
  411. for (StationInfo station : stations) {
  412. if (station.getCode().equalsIgnoreCase(code)) {
  413. return station;
  414. }
  415. }
  416. return null;
  417. }
  418. /**
  419. * 获取工位号(兼容旧代码)
  420. */
  421. public String getStationCode(int index) {
  422. StationInfo station = getStation(index);
  423. return station != null ? station.getCode() : null;
  424. }
  425. /**
  426. * 检查是否有指定功能
  427. */
  428. public boolean hasFeature(String featureName) {
  429. for (StepConfig step : workflowSteps) {
  430. if (step.getId().contains(featureName)) {
  431. return true;
  432. }
  433. }
  434. return false;
  435. }
  436. /**
  437. * 检查是否有物料绑定步骤
  438. */
  439. public boolean hasMaterialBinding() {
  440. return hasFeature("scan_material") || hasFeature("bind_material");
  441. }
  442. /**
  443. * 检查是否有物料校验步骤
  444. */
  445. public boolean hasMaterialValidation() {
  446. return hasFeature("validate_material");
  447. }
  448. /**
  449. * 是否为手动提交模式
  450. */
  451. public boolean isManualSubmitMode() {
  452. return "manual".equalsIgnoreCase(submitMode);
  453. }
  454. /**
  455. * 是否为自动提交模式
  456. */
  457. public boolean isAutoSubmitMode() {
  458. return "auto".equalsIgnoreCase(submitMode);
  459. }
  460. /**
  461. * 获取提交模式
  462. */
  463. public String getSubmitMode() {
  464. return submitMode;
  465. }
  466. /**
  467. * 获取设备连接配置
  468. */
  469. public DeviceConnection getDeviceConnection(int stationIndex) {
  470. for (DeviceConnection conn : deviceConnections) {
  471. if (conn.getStationIndex() == stationIndex) {
  472. return conn;
  473. }
  474. }
  475. return null;
  476. }
  477. // ========== Getters ==========
  478. public String getMode() {
  479. return mode;
  480. }
  481. public List<StationInfo> getStations() {
  482. return stations;
  483. }
  484. public String getLineSn() {
  485. return lineSn;
  486. }
  487. /** 运行时覆盖线体号 */
  488. public void setLineSn(String lineSn) {
  489. this.lineSn = lineSn;
  490. }
  491. public String getServerIp() {
  492. return serverIp;
  493. }
  494. /** 运行时覆盖服务器IP */
  495. public void setServerIp(String serverIp) {
  496. this.serverIp = serverIp;
  497. }
  498. public int getTcpPort() {
  499. return tcpPort;
  500. }
  501. public int getHttpPort() {
  502. return httpPort;
  503. }
  504. public int getHeartBeatCycle() {
  505. return heartBeatCycle;
  506. }
  507. public List<StepConfig> getWorkflowSteps() {
  508. return workflowSteps;
  509. }
  510. public boolean isDeviceEnabled() {
  511. // 优先使用运行时覆盖
  512. Boolean override = ProjectConfigManager.getInstance().getDeviceEnabledOverride();
  513. if (override != null) return override;
  514. return deviceEnabled;
  515. }
  516. public String getDeviceType() {
  517. return deviceType;
  518. }
  519. public List<DeviceConnection> getDeviceConnections() {
  520. return deviceConnections;
  521. }
  522. public boolean isTimeoutEnabled() {
  523. return timeoutEnabled;
  524. }
  525. public int getConnectTimeout() {
  526. return connectTimeout;
  527. }
  528. public int getHeartbeatAddress() {
  529. return heartbeatAddress;
  530. }
  531. public int getReadySignalAddress() {
  532. return readySignalAddress;
  533. }
  534. public String getStationName() {
  535. return stationName;
  536. }
  537. public boolean isShowDeviceInfo() {
  538. return showDeviceInfo;
  539. }
  540. public boolean isShowDeviceInfo2() {
  541. return showDeviceInfo2;
  542. }
  543. public List<DeviceInfoRow> getDeviceInfoRows() {
  544. return deviceInfoRows;
  545. }
  546. public DeviceInfoRow getDeviceInfoRow(int index) {
  547. if (index >= 0 && index < deviceInfoRows.size()) {
  548. return deviceInfoRows.get(index);
  549. }
  550. return null;
  551. }
  552. public boolean isShowMaterialInput() {
  553. return showMaterialInput;
  554. }
  555. /**
  556. * 是否显示物料码输入框
  557. * 直接返回 yaml 的 showMaterialInput(不再被 feature 强制覆盖)
  558. */
  559. public boolean isEffectiveShowMaterialInput() {
  560. return showMaterialInput;
  561. }
  562. public String getMaterialLabel() {
  563. return materialLabel;
  564. }
  565. public Map<String, Object> getRawConfig() {
  566. return rawConfig;
  567. }
  568. public DeviceAddresses getDeviceAddresses() {
  569. return deviceAddresses;
  570. }
  571. public CompleteCondition getCompleteCondition() {
  572. return completeCondition;
  573. }
  574. public HeartbeatConfig getHeartbeatConfig() {
  575. return heartbeatConfig;
  576. }
  577. public RelayConfig getRelayConfig() {
  578. return relayConfig;
  579. }
  580. /**
  581. * 检查是否配置了心跳
  582. */
  583. public boolean hasHeartbeat() {
  584. return heartbeatConfig != null && heartbeatConfig.isEnabled();
  585. }
  586. /**
  587. * 检查是否配置了继电器
  588. */
  589. public boolean hasRelay() {
  590. return relayConfig != null && relayConfig.isEnabled();
  591. }
  592. public ProdParamsConfig getProdParamsConfig() {
  593. return prodParamsConfig;
  594. }
  595. /**
  596. * 检查是否启用过程参数采集
  597. */
  598. public boolean hasProdParams() {
  599. return prodParamsConfig != null && prodParamsConfig.isEnabled();
  600. }
  601. // ========== 内部类 ==========
  602. /**
  603. * 工位信息
  604. */
  605. public static class StationInfo {
  606. private int index;
  607. private String code;
  608. public StationInfo(int index, String code) {
  609. this.index = index;
  610. this.code = code;
  611. }
  612. public int getIndex() {
  613. return index;
  614. }
  615. public String getCode() {
  616. return code;
  617. }
  618. /** 运行时覆盖工位号 */
  619. public void setCode(String code) {
  620. this.code = code;
  621. }
  622. @Override
  623. public String toString() {
  624. return "StationInfo{code='" + code + "'}";
  625. }
  626. }
  627. /**
  628. * 步骤配置
  629. */
  630. public static class StepConfig {
  631. private String id;
  632. private String name;
  633. private String craft;
  634. private Map<String, Object> config;
  635. public StepConfig() {
  636. this.config = new HashMap<>();
  637. }
  638. public String getId() {
  639. return id;
  640. }
  641. public void setId(String id) {
  642. this.id = id;
  643. }
  644. public String getName() {
  645. return name;
  646. }
  647. public void setName(String name) {
  648. this.name = name;
  649. }
  650. public String getCraft() {
  651. return craft;
  652. }
  653. public void setCraft(String craft) {
  654. this.craft = craft;
  655. }
  656. public Map<String, Object> getConfig() {
  657. return config;
  658. }
  659. public void setConfig(Map<String, Object> config) {
  660. this.config = config != null ? config : new HashMap<>();
  661. }
  662. public String getConfigString(String key) {
  663. Object value = config.get(key);
  664. return value != null ? value.toString() : null;
  665. }
  666. public boolean getConfigBoolean(String key, boolean defaultValue) {
  667. Object value = config.get(key);
  668. if (value instanceof Boolean) {
  669. return (Boolean) value;
  670. }
  671. return defaultValue;
  672. }
  673. @Override
  674. public String toString() {
  675. return "StepConfig{id='" + id + "', name='" + name + "'}";
  676. }
  677. }
  678. /**
  679. * 设备连接配置
  680. */
  681. public static class DeviceConnection {
  682. private int stationIndex;
  683. private String ip;
  684. private int port;
  685. public DeviceConnection(int stationIndex, String ip, int port) {
  686. this.stationIndex = stationIndex;
  687. this.ip = ip;
  688. this.port = port;
  689. }
  690. public int getStationIndex() {
  691. return stationIndex;
  692. }
  693. public String getIp() {
  694. return ip;
  695. }
  696. /** 运行时修改IP(用于软件内切换设备IP) */
  697. public void setIp(String ip) {
  698. this.ip = ip;
  699. }
  700. public int getPort() {
  701. return port;
  702. }
  703. @Override
  704. public String toString() {
  705. return "DeviceConnection{stationIndex=" + stationIndex + ", ip='" + ip + "', port=" + port + "}";
  706. }
  707. }
  708. /**
  709. * 设备地址配置
  710. */
  711. public static class DeviceAddresses {
  712. private String enableSignal; // 允许信号地址(S7格式如 DB9.18.1)
  713. private String startSignal; // 启动信号地址
  714. private String completeSignal; // 完成信号地址
  715. private String deviceStatus; // 设备状态地址(Modbus格式如 15001)
  716. private int finishedCount; // 完成数量地址
  717. private int presetCount; // 预设数量地址
  718. private int qualifiedCount; // 合格数量地址
  719. private int startCommand; // 启动命令地址
  720. public String getEnableSignal() { return enableSignal; }
  721. public void setEnableSignal(String enableSignal) { this.enableSignal = enableSignal; }
  722. public String getStartSignal() { return startSignal; }
  723. public void setStartSignal(String startSignal) { this.startSignal = startSignal; }
  724. public String getCompleteSignal() { return completeSignal; }
  725. public void setCompleteSignal(String completeSignal) { this.completeSignal = completeSignal; }
  726. public String getDeviceStatus() { return deviceStatus; }
  727. public void setDeviceStatus(String deviceStatus) { this.deviceStatus = deviceStatus; }
  728. public int getFinishedCount() { return finishedCount; }
  729. public void setFinishedCount(int finishedCount) { this.finishedCount = finishedCount; }
  730. public int getPresetCount() { return presetCount; }
  731. public void setPresetCount(int presetCount) { this.presetCount = presetCount; }
  732. public int getQualifiedCount() { return qualifiedCount; }
  733. public void setQualifiedCount(int qualifiedCount) { this.qualifiedCount = qualifiedCount; }
  734. public int getStartCommand() { return startCommand; }
  735. public void setStartCommand(int startCommand) { this.startCommand = startCommand; }
  736. }
  737. /**
  738. * 完成条件配置
  739. */
  740. public static class CompleteCondition {
  741. private String type = "signal"; // signal | count | signal_with_time
  742. private int runningValue = 1; // 运行中的值
  743. private int stoppedValue = 0; // 停止的值
  744. private int minRunningTime = 0; // 最小运行时间(ms)
  745. public String getType() { return type; }
  746. public void setType(String type) { this.type = type != null ? type : "signal"; }
  747. public int getRunningValue() { return runningValue; }
  748. public void setRunningValue(int runningValue) { this.runningValue = runningValue; }
  749. public int getStoppedValue() { return stoppedValue; }
  750. public void setStoppedValue(int stoppedValue) { this.stoppedValue = stoppedValue; }
  751. public int getMinRunningTime() { return minRunningTime; }
  752. public void setMinRunningTime(int minRunningTime) { this.minRunningTime = minRunningTime; }
  753. }
  754. /**
  755. * 心跳配置
  756. */
  757. public static class HeartbeatConfig {
  758. private boolean enabled = true;
  759. private int address = 4160;
  760. private int interval = 500; // 心跳间隔(ms)
  761. private int timeoutAddress = 4170; // 心跳超时寄存器地址(PLC 读这个值判断超时阈值)
  762. private int timeoutValue = 10; // 心跳超时阈值(单位100ms,10=1秒)
  763. public boolean isEnabled() { return enabled; }
  764. public void setEnabled(boolean enabled) { this.enabled = enabled; }
  765. public int getAddress() { return address; }
  766. public void setAddress(int address) { this.address = address; }
  767. public int getInterval() { return interval; }
  768. public void setInterval(int interval) { this.interval = interval; }
  769. public int getTimeoutAddress() { return timeoutAddress; }
  770. public void setTimeoutAddress(int timeoutAddress) { this.timeoutAddress = timeoutAddress; }
  771. public int getTimeoutValue() { return timeoutValue; }
  772. public void setTimeoutValue(int timeoutValue) { this.timeoutValue = timeoutValue; }
  773. }
  774. /**
  775. * 继电器配置
  776. */
  777. public static class RelayConfig {
  778. private boolean enabled = true;
  779. private String type; // modbus_rtu
  780. private String port; // COM2
  781. private int baudrate = 38400;
  782. private String openCommand; // 开继电器命令(十六进制)
  783. private String closeCommand; // 关继电器命令(十六进制)
  784. public boolean isEnabled() { return enabled; }
  785. public void setEnabled(boolean enabled) { this.enabled = enabled; }
  786. public String getType() { return type; }
  787. public void setType(String type) { this.type = type; }
  788. public String getPort() { return port; }
  789. public void setPort(String port) { this.port = port; }
  790. public int getBaudrate() { return baudrate; }
  791. public void setBaudrate(int baudrate) { this.baudrate = baudrate; }
  792. public String getOpenCommand() { return openCommand; }
  793. public void setOpenCommand(String openCommand) { this.openCommand = openCommand; }
  794. public String getCloseCommand() { return closeCommand; }
  795. public void setCloseCommand(String closeCommand) { this.closeCommand = closeCommand; }
  796. }
  797. /**
  798. * 设备信息行配置
  799. */
  800. public static class DeviceInfoRow {
  801. private String label; // 可选前置标签(如"M6"),为空或null则不显示
  802. private int connectionIndex; // 对应 connections 中的索引,指定从哪个设备读数据
  803. private int presetAddress; // 预设数量寄存器地址
  804. private int finishedAddress; // 完成数量寄存器地址
  805. private boolean enabled = true; // 是否启用(UI 覆盖用)
  806. public String getLabel() { return label; }
  807. public void setLabel(String label) { this.label = label; }
  808. public int getConnectionIndex() { return connectionIndex; }
  809. public void setConnectionIndex(int connectionIndex) { this.connectionIndex = connectionIndex; }
  810. public int getPresetAddress() { return presetAddress; }
  811. public void setPresetAddress(int presetAddress) { this.presetAddress = presetAddress; }
  812. public int getFinishedAddress() { return finishedAddress; }
  813. public void setFinishedAddress(int finishedAddress) { this.finishedAddress = finishedAddress; }
  814. public boolean isEnabled() { return enabled; }
  815. public void setEnabled(boolean enabled) { this.enabled = enabled; }
  816. public boolean hasLabel() {
  817. return label != null && !label.trim().isEmpty();
  818. }
  819. }
  820. /**
  821. * 过程参数采集配置
  822. */
  823. public static class ProdParamsConfig {
  824. private boolean enabled;
  825. private int foutAddress = 4112; // 力输出值 F-out
  826. private int soutAddress = 4120; // 行程输出值 S-out
  827. private int fminAddress = 4116; // 力最小值 F-min
  828. private int sminAddress = 4126; // 行程最小值 S-min
  829. private int fmaxAddress = 4114; // 力最大值 F-max
  830. private int smaxAddress = 4124; // 行程最大值 S-max
  831. private int qtyAddress = 4; // 数量寄存器地址
  832. private int uploadInterval = 60; // 上传间隔(秒)
  833. public boolean isEnabled() { return enabled; }
  834. public void setEnabled(boolean enabled) { this.enabled = enabled; }
  835. public int getFoutAddress() { return foutAddress; }
  836. public void setFoutAddress(int foutAddress) { this.foutAddress = foutAddress; }
  837. public int getSoutAddress() { return soutAddress; }
  838. public void setSoutAddress(int soutAddress) { this.soutAddress = soutAddress; }
  839. public int getFminAddress() { return fminAddress; }
  840. public void setFminAddress(int fminAddress) { this.fminAddress = fminAddress; }
  841. public int getSminAddress() { return sminAddress; }
  842. public void setSminAddress(int sminAddress) { this.sminAddress = sminAddress; }
  843. public int getFmaxAddress() { return fmaxAddress; }
  844. public void setFmaxAddress(int fmaxAddress) { this.fmaxAddress = fmaxAddress; }
  845. public int getSmaxAddress() { return smaxAddress; }
  846. public void setSmaxAddress(int smaxAddress) { this.smaxAddress = smaxAddress; }
  847. public int getQtyAddress() { return qtyAddress; }
  848. public void setQtyAddress(int qtyAddress) { this.qtyAddress = qtyAddress; }
  849. public int getUploadInterval() { return uploadInterval; }
  850. public void setUploadInterval(int uploadInterval) { this.uploadInterval = uploadInterval; }
  851. }
  852. }