|
|
@@ -0,0 +1,984 @@
|
|
|
+package com.mes.core;
|
|
|
+
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.yaml.snakeyaml.Yaml;
|
|
|
+
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 工位配置类 - 从YAML配置文件加载工位配置
|
|
|
+ * 解决工位号硬编码问题,支持单工位/双工位动态切换
|
|
|
+ */
|
|
|
+public class StationConfig {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(StationConfig.class);
|
|
|
+
|
|
|
+ // 单例实例
|
|
|
+ private static StationConfig instance;
|
|
|
+
|
|
|
+ // ========== 工位配置 ==========
|
|
|
+ private String mode; // single | dual
|
|
|
+ private String stationName; // 工位/工序名称(如"液冷板安装+激光点固")
|
|
|
+ private List<StationInfo> stations; // 工位列表
|
|
|
+ private String lineSn; // 线体号
|
|
|
+
|
|
|
+ // ========== 服务器配置 ==========
|
|
|
+ private String serverIp;
|
|
|
+ private int tcpPort;
|
|
|
+ private int httpPort; // HTTP服务端口
|
|
|
+ private int heartBeatCycle;
|
|
|
+
|
|
|
+ // ========== 流程配置 ==========
|
|
|
+ private List<StepConfig> workflowSteps;
|
|
|
+ private String submitMode; // manual | auto (手动点击OK/NG | 自动提交)
|
|
|
+ private Map<String, Boolean> yamlFeatures = new HashMap<>(); // yaml 中定义的功能默认开关
|
|
|
+
|
|
|
+ // ========== 设备配置 ==========
|
|
|
+ private boolean deviceEnabled;
|
|
|
+ private String deviceType; // modbus_tcp | modbus_rtu | s7 | none
|
|
|
+ private List<DeviceConnection> deviceConnections;
|
|
|
+ private boolean timeoutEnabled;
|
|
|
+ private int connectTimeout = 3000; // Modbus连接超时(ms)
|
|
|
+ private int heartbeatAddress;
|
|
|
+ private int readySignalAddress;
|
|
|
+
|
|
|
+ // 设备地址配置
|
|
|
+ private DeviceAddresses deviceAddresses;
|
|
|
+ // 完成条件配置
|
|
|
+ private CompleteCondition completeCondition;
|
|
|
+ // 心跳配置
|
|
|
+ private HeartbeatConfig heartbeatConfig;
|
|
|
+ // 继电器配置
|
|
|
+ private RelayConfig relayConfig;
|
|
|
+ // 过程参数采集配置
|
|
|
+ private ProdParamsConfig prodParamsConfig;
|
|
|
+
|
|
|
+ // ========== UI配置 ==========
|
|
|
+ private boolean showDeviceInfo;
|
|
|
+ private boolean showDeviceInfo2; // 是否显示第二行设备信息
|
|
|
+ private boolean showMaterialInput;
|
|
|
+ private String materialLabel;
|
|
|
+ private List<DeviceInfoRow> deviceInfoRows; // 设备信息行配置
|
|
|
+
|
|
|
+ // 原始配置Map,用于获取未解析的配置
|
|
|
+ private Map<String, Object> rawConfig;
|
|
|
+
|
|
|
+ private StationConfig() {
|
|
|
+ stations = new ArrayList<>();
|
|
|
+ workflowSteps = new ArrayList<>();
|
|
|
+ deviceConnections = new ArrayList<>();
|
|
|
+ deviceInfoRows = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单例实例
|
|
|
+ */
|
|
|
+ public static synchronized StationConfig getInstance() {
|
|
|
+ if (instance == null) {
|
|
|
+ instance = new StationConfig();
|
|
|
+ instance.loadFromResource("resources/config/station.yaml");
|
|
|
+ }
|
|
|
+ return instance;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重新加载配置(用于热更新)
|
|
|
+ */
|
|
|
+ public static synchronized void reload() {
|
|
|
+ instance = new StationConfig();
|
|
|
+ instance.loadFromResource("resources/config/station.yaml");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从指定路径加载配置
|
|
|
+ */
|
|
|
+ public static synchronized StationConfig loadFrom(String resourcePath) {
|
|
|
+ instance = new StationConfig();
|
|
|
+ instance.loadFromResource(resourcePath);
|
|
|
+ return instance;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从资源文件加载配置
|
|
|
+ */
|
|
|
+ private void loadFromResource(String resourcePath) {
|
|
|
+ try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath)) {
|
|
|
+ if (is == null) {
|
|
|
+ log.error("配置文件不存在: {}", resourcePath);
|
|
|
+ throw new RuntimeException("配置文件不存在: " + resourcePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ Yaml yaml = new Yaml();
|
|
|
+ Map<String, Object> config = yaml.load(new InputStreamReader(is, StandardCharsets.UTF_8));
|
|
|
+ parseConfig(config);
|
|
|
+ log.info("配置加载成功: mode={}, stations={}", mode, stations.size());
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("加载配置文件失败: {}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("加载配置文件失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 展开 steps 列表:遇到 feature 块时按功能开关(含 override)过滤
|
|
|
+ * feature 块形如:
|
|
|
+ * - feature: cold_plate
|
|
|
+ * steps: [...]
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void expandSteps(List<Map<String, Object>> stepsArr, List<StepConfig> out) {
|
|
|
+ for (Map<String, Object> node : stepsArr) {
|
|
|
+ Object featureKey = node.get("feature");
|
|
|
+ if (featureKey != null) {
|
|
|
+ String fname = featureKey.toString();
|
|
|
+ if (!isFeatureEnabled(fname)) {
|
|
|
+ log.debug("跳过禁用的feature: {}", fname);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> inner = (List<Map<String, Object>>) node.get("steps");
|
|
|
+ if (inner != null) {
|
|
|
+ expandSteps(inner, out);
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 普通步骤
|
|
|
+ if (node.get("id") == null) continue;
|
|
|
+ StepConfig stepConfig = new StepConfig();
|
|
|
+ stepConfig.setId((String) node.get("id"));
|
|
|
+ stepConfig.setName((String) node.get("name"));
|
|
|
+ stepConfig.setCraft((String) node.get("craft"));
|
|
|
+ Map<String, Object> stepConfigMap = (Map<String, Object>) node.get("config");
|
|
|
+ if (stepConfigMap != null) {
|
|
|
+ stepConfig.setConfig(stepConfigMap);
|
|
|
+ }
|
|
|
+ out.add(stepConfig);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询功能是否启用
|
|
|
+ * 优先级:运行时 override (ProjectConfigManager) > yaml 默认 > false
|
|
|
+ */
|
|
|
+ public boolean isFeatureEnabled(String feature) {
|
|
|
+ Boolean override = ProjectConfigManager.getInstance().getFeatureOverride(feature);
|
|
|
+ if (override != null) return override;
|
|
|
+ Boolean def = yamlFeatures.get(feature);
|
|
|
+ return def != null && def;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有 yaml 定义的功能开关(用于 UI 显示可用功能列表)
|
|
|
+ */
|
|
|
+ public Map<String, Boolean> getYamlFeatures() {
|
|
|
+ return new HashMap<>(yamlFeatures);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据当前功能开关推导物料码标签:
|
|
|
+ * 启用 cold_plate → "冷板码"
|
|
|
+ * 启用 bottom_plate → "底护板码"
|
|
|
+ * 都未启用 → yaml 配置的 materialLabel
|
|
|
+ */
|
|
|
+ public String getEffectiveMaterialLabel() {
|
|
|
+ if (isFeatureEnabled("cold_plate")) return "冷板码";
|
|
|
+ if (isFeatureEnabled("bottom_plate")) return "底护板码";
|
|
|
+ return materialLabel;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析YAML配置
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void parseConfig(Map<String, Object> config) {
|
|
|
+ rawConfig = config;
|
|
|
+
|
|
|
+ // 解析工位配置
|
|
|
+ Map<String, Object> stationObj = (Map<String, Object>) config.get("station");
|
|
|
+ if (stationObj != null) {
|
|
|
+ mode = (String) stationObj.get("mode");
|
|
|
+ stationName = (String) stationObj.get("name");
|
|
|
+ lineSn = (String) stationObj.get("line_sn");
|
|
|
+
|
|
|
+ List<Map<String, Object>> stationsArr = (List<Map<String, Object>>) stationObj.get("stations");
|
|
|
+ if (stationsArr != null) {
|
|
|
+ for (int i = 0; i < stationsArr.size(); i++) {
|
|
|
+ Map<String, Object> s = stationsArr.get(i);
|
|
|
+ stations.add(new StationInfo(
|
|
|
+ i,
|
|
|
+ (String) s.get("code")
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析服务器配置
|
|
|
+ Map<String, Object> serverObj = (Map<String, Object>) config.get("server");
|
|
|
+ if (serverObj != null) {
|
|
|
+ serverIp = (String) serverObj.get("ip");
|
|
|
+ tcpPort = getIntValue(serverObj, "tcp_port", 3000);
|
|
|
+ httpPort = getIntValue(serverObj, "http_port", 8980);
|
|
|
+ heartBeatCycle = getIntValue(serverObj, "heart_beat_cycle", 60);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析流程配置
|
|
|
+ Map<String, Object> workflowObj = (Map<String, Object>) config.get("workflow");
|
|
|
+ if (workflowObj != null) {
|
|
|
+ // 提交模式:manual(手动点击OK/NG) | auto(设备信号自动提交)
|
|
|
+ submitMode = (String) workflowObj.get("submit_mode");
|
|
|
+ if (submitMode == null || submitMode.isEmpty()) {
|
|
|
+ submitMode = "manual"; // 默认手动模式
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析功能开关(yaml 默认值)
|
|
|
+ yamlFeatures.clear();
|
|
|
+ Map<String, Object> featuresObj = (Map<String, Object>) workflowObj.get("features");
|
|
|
+ if (featuresObj != null) {
|
|
|
+ for (Map.Entry<String, Object> e : featuresObj.entrySet()) {
|
|
|
+ if (e.getValue() instanceof Boolean) {
|
|
|
+ yamlFeatures.put(e.getKey(), (Boolean) e.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> stepsArr = (List<Map<String, Object>>) workflowObj.get("steps");
|
|
|
+ if (stepsArr != null) {
|
|
|
+ expandSteps(stepsArr, workflowSteps);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析设备配置
|
|
|
+ Map<String, Object> deviceObj = (Map<String, Object>) config.get("device");
|
|
|
+ if (deviceObj != null) {
|
|
|
+ deviceEnabled = getBooleanValue(deviceObj, "enabled", false);
|
|
|
+ deviceType = (String) deviceObj.get("type");
|
|
|
+ timeoutEnabled = getBooleanValue(deviceObj, "timeout_enabled", false);
|
|
|
+ connectTimeout = getIntValue(deviceObj, "connect_timeout", 3000);
|
|
|
+ heartbeatAddress = getIntValue(deviceObj, "heartbeat_address", 4160);
|
|
|
+ readySignalAddress = getIntValue(deviceObj, "ready_signal_address", 8);
|
|
|
+
|
|
|
+ List<Map<String, Object>> connArr = (List<Map<String, Object>>) deviceObj.get("connections");
|
|
|
+ if (connArr != null) {
|
|
|
+ for (int i = 0; i < connArr.size(); i++) {
|
|
|
+ Map<String, Object> conn = connArr.get(i);
|
|
|
+ deviceConnections.add(new DeviceConnection(
|
|
|
+ getIntValue(conn, "station_index", i),
|
|
|
+ (String) conn.get("ip"),
|
|
|
+ getIntValue(conn, "port", 502)
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析设备地址配置
|
|
|
+ Map<String, Object> addressesObj = (Map<String, Object>) deviceObj.get("addresses");
|
|
|
+ if (addressesObj != null) {
|
|
|
+ deviceAddresses = new DeviceAddresses();
|
|
|
+ deviceAddresses.setEnableSignal((String) addressesObj.get("enable_signal"));
|
|
|
+ deviceAddresses.setStartSignal((String) addressesObj.get("start_signal"));
|
|
|
+ deviceAddresses.setCompleteSignal((String) addressesObj.get("complete_signal"));
|
|
|
+ deviceAddresses.setDeviceStatus((String) addressesObj.get("device_status"));
|
|
|
+ deviceAddresses.setFinishedCount(getIntValue(addressesObj, "finished_count", 0));
|
|
|
+ deviceAddresses.setPresetCount(getIntValue(addressesObj, "preset_count", 0));
|
|
|
+ deviceAddresses.setQualifiedCount(getIntValue(addressesObj, "qualified_count", 0));
|
|
|
+ deviceAddresses.setStartCommand(getIntValue(addressesObj, "start_command", 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析完成条件配置
|
|
|
+ Map<String, Object> conditionObj = (Map<String, Object>) deviceObj.get("complete_condition");
|
|
|
+ if (conditionObj != null) {
|
|
|
+ completeCondition = new CompleteCondition();
|
|
|
+ completeCondition.setType((String) conditionObj.get("type"));
|
|
|
+ completeCondition.setRunningValue(getIntValue(conditionObj, "running_value", 1));
|
|
|
+ completeCondition.setStoppedValue(getIntValue(conditionObj, "stopped_value", 0));
|
|
|
+ completeCondition.setMinRunningTime(getIntValue(conditionObj, "min_running_time", 0));
|
|
|
+ } else {
|
|
|
+ // 默认使用信号判断
|
|
|
+ completeCondition = new CompleteCondition();
|
|
|
+ completeCondition.setType("signal");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析心跳配置
|
|
|
+ Map<String, Object> heartbeatObj = (Map<String, Object>) deviceObj.get("heartbeat");
|
|
|
+ if (heartbeatObj != null) {
|
|
|
+ heartbeatConfig = new HeartbeatConfig();
|
|
|
+ heartbeatConfig.setEnabled(getBooleanValue(heartbeatObj, "enabled", true));
|
|
|
+ heartbeatConfig.setAddress(getIntValue(heartbeatObj, "address", 4160));
|
|
|
+ heartbeatConfig.setInterval(getIntValue(heartbeatObj, "interval", 500));
|
|
|
+ heartbeatConfig.setTimeoutAddress(getIntValue(heartbeatObj, "timeout_address", 4170));
|
|
|
+ heartbeatConfig.setTimeoutValue(getIntValue(heartbeatObj, "timeout_value", 10));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析继电器配置
|
|
|
+ Map<String, Object> relayObj = (Map<String, Object>) deviceObj.get("relay");
|
|
|
+ if (relayObj != null) {
|
|
|
+ relayConfig = new RelayConfig();
|
|
|
+ relayConfig.setEnabled(getBooleanValue(relayObj, "enabled", true));
|
|
|
+ relayConfig.setType((String) relayObj.get("type"));
|
|
|
+ relayConfig.setPort((String) relayObj.get("port"));
|
|
|
+ relayConfig.setBaudrate(getIntValue(relayObj, "baudrate", 38400));
|
|
|
+ relayConfig.setOpenCommand((String) relayObj.get("open_command"));
|
|
|
+ relayConfig.setCloseCommand((String) relayObj.get("close_command"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析过程参数采集配置
|
|
|
+ Map<String, Object> prodParamsObj = (Map<String, Object>) deviceObj.get("prod_params");
|
|
|
+ if (prodParamsObj != null) {
|
|
|
+ prodParamsConfig = new ProdParamsConfig();
|
|
|
+ prodParamsConfig.setEnabled(getBooleanValue(prodParamsObj, "enabled", false));
|
|
|
+ prodParamsConfig.setFoutAddress(getIntValue(prodParamsObj, "fout_address", 4112));
|
|
|
+ prodParamsConfig.setSoutAddress(getIntValue(prodParamsObj, "sout_address", 4120));
|
|
|
+ prodParamsConfig.setFminAddress(getIntValue(prodParamsObj, "fmin_address", 4116));
|
|
|
+ prodParamsConfig.setSminAddress(getIntValue(prodParamsObj, "smin_address", 4126));
|
|
|
+ prodParamsConfig.setFmaxAddress(getIntValue(prodParamsObj, "fmax_address", 4114));
|
|
|
+ prodParamsConfig.setSmaxAddress(getIntValue(prodParamsObj, "smax_address", 4124));
|
|
|
+ prodParamsConfig.setQtyAddress(getIntValue(prodParamsObj, "qty_address", 4));
|
|
|
+ prodParamsConfig.setUploadInterval(getIntValue(prodParamsObj, "upload_interval", 60));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析UI配置
|
|
|
+ Map<String, Object> uiObj = (Map<String, Object>) config.get("ui");
|
|
|
+ if (uiObj != null) {
|
|
|
+ showMaterialInput = getBooleanValue(uiObj, "show_material_input", false);
|
|
|
+ materialLabel = (String) uiObj.get("material_label");
|
|
|
+
|
|
|
+ // 解析设备信息行配置
|
|
|
+ List<Map<String, Object>> rowsArr = (List<Map<String, Object>>) uiObj.get("device_info_rows");
|
|
|
+ if (rowsArr != null && !rowsArr.isEmpty()) {
|
|
|
+ for (int i = 0; i < Math.min(rowsArr.size(), 2); i++) {
|
|
|
+ Map<String, Object> row = rowsArr.get(i);
|
|
|
+ DeviceInfoRow infoRow = new DeviceInfoRow();
|
|
|
+ infoRow.setLabel((String) row.get("label"));
|
|
|
+ infoRow.setConnectionIndex(getIntValue(row, "connection_index", 0));
|
|
|
+ infoRow.setPresetAddress(getIntValue(row, "preset_address", 0));
|
|
|
+ infoRow.setFinishedAddress(getIntValue(row, "finished_address", 0));
|
|
|
+ deviceInfoRows.add(infoRow);
|
|
|
+ }
|
|
|
+ showDeviceInfo = true;
|
|
|
+ showDeviceInfo2 = deviceInfoRows.size() > 1;
|
|
|
+ } else {
|
|
|
+ // 兼容旧配置
|
|
|
+ showDeviceInfo = getBooleanValue(uiObj, "show_device_info", false);
|
|
|
+ showDeviceInfo2 = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // features 覆盖:prod_params 功能开关生效到 prodParamsConfig
|
|
|
+ if (prodParamsConfig != null && yamlFeatures.containsKey("prod_params")) {
|
|
|
+ boolean effective = isFeatureEnabled("prod_params");
|
|
|
+ if (prodParamsConfig.isEnabled() != effective) {
|
|
|
+ log.info("prod_params 启用状态被 features 覆盖: {} -> {}", prodParamsConfig.isEnabled(), effective);
|
|
|
+ prodParamsConfig.setEnabled(effective);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 应用 UI 覆盖:设备信息行启用/label
|
|
|
+ ProjectConfigManager pc = ProjectConfigManager.getInstance();
|
|
|
+ applyDeviceRowOverride(0, pc.getDeviceRow1EnabledOverride(), pc.getDeviceRow1LabelOverride());
|
|
|
+ applyDeviceRowOverride(1, pc.getDeviceRow2EnabledOverride(), pc.getDeviceRow2LabelOverride());
|
|
|
+ // showDeviceInfo / showDeviceInfo2 按 enabled 合并重算
|
|
|
+ showDeviceInfo = !deviceInfoRows.isEmpty() && deviceInfoRows.get(0).isEnabled();
|
|
|
+ showDeviceInfo2 = deviceInfoRows.size() > 1 && deviceInfoRows.get(1).isEnabled();
|
|
|
+ // 物料码输入框显隐覆盖
|
|
|
+ Boolean smiOverride = pc.getShowMaterialInputOverride();
|
|
|
+ if (smiOverride != null) {
|
|
|
+ showMaterialInput = smiOverride;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void applyDeviceRowOverride(int idx, Boolean enabled, String label) {
|
|
|
+ if (idx < 0 || idx >= deviceInfoRows.size()) return;
|
|
|
+ DeviceInfoRow row = deviceInfoRows.get(idx);
|
|
|
+ if (enabled != null) row.setEnabled(enabled);
|
|
|
+ if (label != null) row.setLabel(label);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中获取整数值,支持默认值
|
|
|
+ */
|
|
|
+ private int getIntValue(Map<String, Object> map, String key, int defaultValue) {
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value instanceof Number) {
|
|
|
+ return ((Number) value).intValue();
|
|
|
+ }
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中获取布尔值,支持默认值
|
|
|
+ */
|
|
|
+ private boolean getBooleanValue(Map<String, Object> map, String key, boolean defaultValue) {
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value instanceof Boolean) {
|
|
|
+ return (Boolean) value;
|
|
|
+ }
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 便捷方法 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为单工位模式
|
|
|
+ */
|
|
|
+ public boolean isSingleMode() {
|
|
|
+ return "single".equalsIgnoreCase(mode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为双工位模式
|
|
|
+ */
|
|
|
+ public boolean isDualMode() {
|
|
|
+ return "dual".equalsIgnoreCase(mode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取工位数量
|
|
|
+ */
|
|
|
+ public int getStationCount() {
|
|
|
+ return stations.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据索引获取工位信息
|
|
|
+ */
|
|
|
+ public StationInfo getStation(int index) {
|
|
|
+ if (index >= 0 && index < stations.size()) {
|
|
|
+ return stations.get(index);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据工位号获取工位信息
|
|
|
+ */
|
|
|
+ public StationInfo getStationByCode(String code) {
|
|
|
+ for (StationInfo station : stations) {
|
|
|
+ if (station.getCode().equalsIgnoreCase(code)) {
|
|
|
+ return station;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取工位号(兼容旧代码)
|
|
|
+ */
|
|
|
+ public String getStationCode(int index) {
|
|
|
+ StationInfo station = getStation(index);
|
|
|
+ return station != null ? station.getCode() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否有指定功能
|
|
|
+ */
|
|
|
+ public boolean hasFeature(String featureName) {
|
|
|
+ for (StepConfig step : workflowSteps) {
|
|
|
+ if (step.getId().contains(featureName)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否有物料绑定步骤
|
|
|
+ */
|
|
|
+ public boolean hasMaterialBinding() {
|
|
|
+ return hasFeature("scan_material") || hasFeature("bind_material");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否有物料校验步骤
|
|
|
+ */
|
|
|
+ public boolean hasMaterialValidation() {
|
|
|
+ return hasFeature("validate_material");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为手动提交模式
|
|
|
+ */
|
|
|
+ public boolean isManualSubmitMode() {
|
|
|
+ return "manual".equalsIgnoreCase(submitMode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否为自动提交模式
|
|
|
+ */
|
|
|
+ public boolean isAutoSubmitMode() {
|
|
|
+ return "auto".equalsIgnoreCase(submitMode);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取提交模式
|
|
|
+ */
|
|
|
+ public String getSubmitMode() {
|
|
|
+ return submitMode;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取设备连接配置
|
|
|
+ */
|
|
|
+ public DeviceConnection getDeviceConnection(int stationIndex) {
|
|
|
+ for (DeviceConnection conn : deviceConnections) {
|
|
|
+ if (conn.getStationIndex() == stationIndex) {
|
|
|
+ return conn;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== Getters ==========
|
|
|
+
|
|
|
+ public String getMode() {
|
|
|
+ return mode;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<StationInfo> getStations() {
|
|
|
+ return stations;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getLineSn() {
|
|
|
+ return lineSn;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 运行时覆盖线体号 */
|
|
|
+ public void setLineSn(String lineSn) {
|
|
|
+ this.lineSn = lineSn;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getServerIp() {
|
|
|
+ return serverIp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 运行时覆盖服务器IP */
|
|
|
+ public void setServerIp(String serverIp) {
|
|
|
+ this.serverIp = serverIp;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getTcpPort() {
|
|
|
+ return tcpPort;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getHttpPort() {
|
|
|
+ return httpPort;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getHeartBeatCycle() {
|
|
|
+ return heartBeatCycle;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<StepConfig> getWorkflowSteps() {
|
|
|
+ return workflowSteps;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isDeviceEnabled() {
|
|
|
+ // 优先使用运行时覆盖
|
|
|
+ Boolean override = ProjectConfigManager.getInstance().getDeviceEnabledOverride();
|
|
|
+ if (override != null) return override;
|
|
|
+ return deviceEnabled;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getDeviceType() {
|
|
|
+ return deviceType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<DeviceConnection> getDeviceConnections() {
|
|
|
+ return deviceConnections;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isTimeoutEnabled() {
|
|
|
+ return timeoutEnabled;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getConnectTimeout() {
|
|
|
+ return connectTimeout;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getHeartbeatAddress() {
|
|
|
+ return heartbeatAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getReadySignalAddress() {
|
|
|
+ return readySignalAddress;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getStationName() {
|
|
|
+ return stationName;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isShowDeviceInfo() {
|
|
|
+ return showDeviceInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isShowDeviceInfo2() {
|
|
|
+ return showDeviceInfo2;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<DeviceInfoRow> getDeviceInfoRows() {
|
|
|
+ return deviceInfoRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ public DeviceInfoRow getDeviceInfoRow(int index) {
|
|
|
+ if (index >= 0 && index < deviceInfoRows.size()) {
|
|
|
+ return deviceInfoRows.get(index);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isShowMaterialInput() {
|
|
|
+ return showMaterialInput;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否显示物料码输入框
|
|
|
+ * 直接返回 yaml 的 showMaterialInput(不再被 feature 强制覆盖)
|
|
|
+ */
|
|
|
+ public boolean isEffectiveShowMaterialInput() {
|
|
|
+ return showMaterialInput;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getMaterialLabel() {
|
|
|
+ return materialLabel;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> getRawConfig() {
|
|
|
+ return rawConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ public DeviceAddresses getDeviceAddresses() {
|
|
|
+ return deviceAddresses;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CompleteCondition getCompleteCondition() {
|
|
|
+ return completeCondition;
|
|
|
+ }
|
|
|
+
|
|
|
+ public HeartbeatConfig getHeartbeatConfig() {
|
|
|
+ return heartbeatConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ public RelayConfig getRelayConfig() {
|
|
|
+ return relayConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否配置了心跳
|
|
|
+ */
|
|
|
+ public boolean hasHeartbeat() {
|
|
|
+ return heartbeatConfig != null && heartbeatConfig.isEnabled();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否配置了继电器
|
|
|
+ */
|
|
|
+ public boolean hasRelay() {
|
|
|
+ return relayConfig != null && relayConfig.isEnabled();
|
|
|
+ }
|
|
|
+
|
|
|
+ public ProdParamsConfig getProdParamsConfig() {
|
|
|
+ return prodParamsConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否启用过程参数采集
|
|
|
+ */
|
|
|
+ public boolean hasProdParams() {
|
|
|
+ return prodParamsConfig != null && prodParamsConfig.isEnabled();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 内部类 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 工位信息
|
|
|
+ */
|
|
|
+ public static class StationInfo {
|
|
|
+ private int index;
|
|
|
+ private String code;
|
|
|
+
|
|
|
+ public StationInfo(int index, String code) {
|
|
|
+ this.index = index;
|
|
|
+ this.code = code;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getIndex() {
|
|
|
+ return index;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getCode() {
|
|
|
+ return code;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 运行时覆盖工位号 */
|
|
|
+ public void setCode(String code) {
|
|
|
+ this.code = code;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return "StationInfo{code='" + code + "'}";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 步骤配置
|
|
|
+ */
|
|
|
+ public static class StepConfig {
|
|
|
+ private String id;
|
|
|
+ private String name;
|
|
|
+ private String craft;
|
|
|
+ private Map<String, Object> config;
|
|
|
+
|
|
|
+ public StepConfig() {
|
|
|
+ this.config = new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getId() {
|
|
|
+ return id;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setId(String id) {
|
|
|
+ this.id = id;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getName() {
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setName(String name) {
|
|
|
+ this.name = name;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getCraft() {
|
|
|
+ return craft;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCraft(String craft) {
|
|
|
+ this.craft = craft;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> getConfig() {
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setConfig(Map<String, Object> config) {
|
|
|
+ this.config = config != null ? config : new HashMap<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getConfigString(String key) {
|
|
|
+ Object value = config.get(key);
|
|
|
+ return value != null ? value.toString() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean getConfigBoolean(String key, boolean defaultValue) {
|
|
|
+ Object value = config.get(key);
|
|
|
+ if (value instanceof Boolean) {
|
|
|
+ return (Boolean) value;
|
|
|
+ }
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return "StepConfig{id='" + id + "', name='" + name + "'}";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设备连接配置
|
|
|
+ */
|
|
|
+ public static class DeviceConnection {
|
|
|
+ private int stationIndex;
|
|
|
+ private String ip;
|
|
|
+ private int port;
|
|
|
+
|
|
|
+ public DeviceConnection(int stationIndex, String ip, int port) {
|
|
|
+ this.stationIndex = stationIndex;
|
|
|
+ this.ip = ip;
|
|
|
+ this.port = port;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getStationIndex() {
|
|
|
+ return stationIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getIp() {
|
|
|
+ return ip;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 运行时修改IP(用于软件内切换设备IP) */
|
|
|
+ public void setIp(String ip) {
|
|
|
+ this.ip = ip;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getPort() {
|
|
|
+ return port;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return "DeviceConnection{stationIndex=" + stationIndex + ", ip='" + ip + "', port=" + port + "}";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设备地址配置
|
|
|
+ */
|
|
|
+ public static class DeviceAddresses {
|
|
|
+ private String enableSignal; // 允许信号地址(S7格式如 DB9.18.1)
|
|
|
+ private String startSignal; // 启动信号地址
|
|
|
+ private String completeSignal; // 完成信号地址
|
|
|
+ private String deviceStatus; // 设备状态地址(Modbus格式如 15001)
|
|
|
+ private int finishedCount; // 完成数量地址
|
|
|
+ private int presetCount; // 预设数量地址
|
|
|
+ private int qualifiedCount; // 合格数量地址
|
|
|
+ private int startCommand; // 启动命令地址
|
|
|
+
|
|
|
+ public String getEnableSignal() { return enableSignal; }
|
|
|
+ public void setEnableSignal(String enableSignal) { this.enableSignal = enableSignal; }
|
|
|
+ public String getStartSignal() { return startSignal; }
|
|
|
+ public void setStartSignal(String startSignal) { this.startSignal = startSignal; }
|
|
|
+ public String getCompleteSignal() { return completeSignal; }
|
|
|
+ public void setCompleteSignal(String completeSignal) { this.completeSignal = completeSignal; }
|
|
|
+ public String getDeviceStatus() { return deviceStatus; }
|
|
|
+ public void setDeviceStatus(String deviceStatus) { this.deviceStatus = deviceStatus; }
|
|
|
+ public int getFinishedCount() { return finishedCount; }
|
|
|
+ public void setFinishedCount(int finishedCount) { this.finishedCount = finishedCount; }
|
|
|
+ public int getPresetCount() { return presetCount; }
|
|
|
+ public void setPresetCount(int presetCount) { this.presetCount = presetCount; }
|
|
|
+ public int getQualifiedCount() { return qualifiedCount; }
|
|
|
+ public void setQualifiedCount(int qualifiedCount) { this.qualifiedCount = qualifiedCount; }
|
|
|
+ public int getStartCommand() { return startCommand; }
|
|
|
+ public void setStartCommand(int startCommand) { this.startCommand = startCommand; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完成条件配置
|
|
|
+ */
|
|
|
+ public static class CompleteCondition {
|
|
|
+ private String type = "signal"; // signal | count | signal_with_time
|
|
|
+ private int runningValue = 1; // 运行中的值
|
|
|
+ private int stoppedValue = 0; // 停止的值
|
|
|
+ private int minRunningTime = 0; // 最小运行时间(ms)
|
|
|
+
|
|
|
+ public String getType() { return type; }
|
|
|
+ public void setType(String type) { this.type = type != null ? type : "signal"; }
|
|
|
+ public int getRunningValue() { return runningValue; }
|
|
|
+ public void setRunningValue(int runningValue) { this.runningValue = runningValue; }
|
|
|
+ public int getStoppedValue() { return stoppedValue; }
|
|
|
+ public void setStoppedValue(int stoppedValue) { this.stoppedValue = stoppedValue; }
|
|
|
+ public int getMinRunningTime() { return minRunningTime; }
|
|
|
+ public void setMinRunningTime(int minRunningTime) { this.minRunningTime = minRunningTime; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 心跳配置
|
|
|
+ */
|
|
|
+ public static class HeartbeatConfig {
|
|
|
+ private boolean enabled = true;
|
|
|
+ private int address = 4160;
|
|
|
+ private int interval = 500; // 心跳间隔(ms)
|
|
|
+ private int timeoutAddress = 4170; // 心跳超时寄存器地址(PLC 读这个值判断超时阈值)
|
|
|
+ private int timeoutValue = 10; // 心跳超时阈值(单位100ms,10=1秒)
|
|
|
+
|
|
|
+ public boolean isEnabled() { return enabled; }
|
|
|
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
|
+ public int getAddress() { return address; }
|
|
|
+ public void setAddress(int address) { this.address = address; }
|
|
|
+ public int getInterval() { return interval; }
|
|
|
+ public void setInterval(int interval) { this.interval = interval; }
|
|
|
+ public int getTimeoutAddress() { return timeoutAddress; }
|
|
|
+ public void setTimeoutAddress(int timeoutAddress) { this.timeoutAddress = timeoutAddress; }
|
|
|
+ public int getTimeoutValue() { return timeoutValue; }
|
|
|
+ public void setTimeoutValue(int timeoutValue) { this.timeoutValue = timeoutValue; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 继电器配置
|
|
|
+ */
|
|
|
+ public static class RelayConfig {
|
|
|
+ private boolean enabled = true;
|
|
|
+ private String type; // modbus_rtu
|
|
|
+ private String port; // COM2
|
|
|
+ private int baudrate = 38400;
|
|
|
+ private String openCommand; // 开继电器命令(十六进制)
|
|
|
+ private String closeCommand; // 关继电器命令(十六进制)
|
|
|
+
|
|
|
+ public boolean isEnabled() { return enabled; }
|
|
|
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
|
+ public String getType() { return type; }
|
|
|
+ public void setType(String type) { this.type = type; }
|
|
|
+ public String getPort() { return port; }
|
|
|
+ public void setPort(String port) { this.port = port; }
|
|
|
+ public int getBaudrate() { return baudrate; }
|
|
|
+ public void setBaudrate(int baudrate) { this.baudrate = baudrate; }
|
|
|
+ public String getOpenCommand() { return openCommand; }
|
|
|
+ public void setOpenCommand(String openCommand) { this.openCommand = openCommand; }
|
|
|
+ public String getCloseCommand() { return closeCommand; }
|
|
|
+ public void setCloseCommand(String closeCommand) { this.closeCommand = closeCommand; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设备信息行配置
|
|
|
+ */
|
|
|
+ public static class DeviceInfoRow {
|
|
|
+ private String label; // 可选前置标签(如"M6"),为空或null则不显示
|
|
|
+ private int connectionIndex; // 对应 connections 中的索引,指定从哪个设备读数据
|
|
|
+ private int presetAddress; // 预设数量寄存器地址
|
|
|
+ private int finishedAddress; // 完成数量寄存器地址
|
|
|
+ private boolean enabled = true; // 是否启用(UI 覆盖用)
|
|
|
+
|
|
|
+ public String getLabel() { return label; }
|
|
|
+ public void setLabel(String label) { this.label = label; }
|
|
|
+ public int getConnectionIndex() { return connectionIndex; }
|
|
|
+ public void setConnectionIndex(int connectionIndex) { this.connectionIndex = connectionIndex; }
|
|
|
+ public int getPresetAddress() { return presetAddress; }
|
|
|
+ public void setPresetAddress(int presetAddress) { this.presetAddress = presetAddress; }
|
|
|
+ public int getFinishedAddress() { return finishedAddress; }
|
|
|
+ public void setFinishedAddress(int finishedAddress) { this.finishedAddress = finishedAddress; }
|
|
|
+ public boolean isEnabled() { return enabled; }
|
|
|
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
|
+
|
|
|
+ public boolean hasLabel() {
|
|
|
+ return label != null && !label.trim().isEmpty();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 过程参数采集配置
|
|
|
+ */
|
|
|
+ public static class ProdParamsConfig {
|
|
|
+ private boolean enabled;
|
|
|
+ private int foutAddress = 4112; // 力输出值 F-out
|
|
|
+ private int soutAddress = 4120; // 行程输出值 S-out
|
|
|
+ private int fminAddress = 4116; // 力最小值 F-min
|
|
|
+ private int sminAddress = 4126; // 行程最小值 S-min
|
|
|
+ private int fmaxAddress = 4114; // 力最大值 F-max
|
|
|
+ private int smaxAddress = 4124; // 行程最大值 S-max
|
|
|
+ private int qtyAddress = 4; // 数量寄存器地址
|
|
|
+ private int uploadInterval = 60; // 上传间隔(秒)
|
|
|
+
|
|
|
+ public boolean isEnabled() { return enabled; }
|
|
|
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
|
+ public int getFoutAddress() { return foutAddress; }
|
|
|
+ public void setFoutAddress(int foutAddress) { this.foutAddress = foutAddress; }
|
|
|
+ public int getSoutAddress() { return soutAddress; }
|
|
|
+ public void setSoutAddress(int soutAddress) { this.soutAddress = soutAddress; }
|
|
|
+ public int getFminAddress() { return fminAddress; }
|
|
|
+ public void setFminAddress(int fminAddress) { this.fminAddress = fminAddress; }
|
|
|
+ public int getSminAddress() { return sminAddress; }
|
|
|
+ public void setSminAddress(int sminAddress) { this.sminAddress = sminAddress; }
|
|
|
+ public int getFmaxAddress() { return fmaxAddress; }
|
|
|
+ public void setFmaxAddress(int fmaxAddress) { this.fmaxAddress = fmaxAddress; }
|
|
|
+ public int getSmaxAddress() { return smaxAddress; }
|
|
|
+ public void setSmaxAddress(int smaxAddress) { this.smaxAddress = smaxAddress; }
|
|
|
+ public int getQtyAddress() { return qtyAddress; }
|
|
|
+ public void setQtyAddress(int qtyAddress) { this.qtyAddress = qtyAddress; }
|
|
|
+ public int getUploadInterval() { return uploadInterval; }
|
|
|
+ public void setUploadInterval(int uploadInterval) { this.uploadInterval = uploadInterval; }
|
|
|
+ }
|
|
|
+}
|