#include "ext_relay.h" #include "relay.h" #include "main.h" #include "network.h" #include "24c02.h" #include "feed_scale.h" #include "proto.h" #include /* 命令执行后状态上报标志 */ volatile uint8_t relay_report_pending = 0; volatile uint32_t relay_report_tick = 0; /* 继电器命令响应阶段控制: 0: 空闲 1: 已动作, 等待反馈稳定 2: 反馈已读, 已发送 response 和 status, 结束 */ volatile uint8_t relay_cmd_stage = 0; volatile uint32_t relay_cmd_tick = 0; volatile uint8_t relay_cmd_ch = 0; volatile GPIO_PinState relay_cmd_fb_before = GPIO_PIN_RESET; char relay_cmd_id[20] = {0}; /* 运行继电器命令响应状态机,由主循环每轮调用 */ void relayCmdProcess(void) { GPIO_PinState fb_after; uint8_t ch = relay_cmd_ch; if (relay_cmd_stage == 0) return; if (relay_cmd_stage == 1) { /* 等待继电器反馈电路稳定 */ if (HAL_GetTick() - relay_cmd_tick < 100) return; fb_after = relayReadFb(ch); /* 测试: 仅第 2 路反馈失败应答改中文(直接写中文=GBK 字节, 其余路保持英文; 平台乱码则回退 UTF-8 转义) */ const char *failmsg = (ch == 2) ? "负载断开或继电器损坏" : "Load unpowered / relay failure !"; if (RELAY_FB_ENABLE) { if (MqttInfoStr.Relay_State[ch]) { if (relay_cmd_fb_before == FB_INACTIVE && fb_after == FB_ACTIVE) { log_info("> 继电器: SW%d 动作正常", ch); NET_publish_response(relay_cmd_id, 1, "OK"); } else { log_warn("> 继电器: SW%d 未接负载或继电器断开", ch); NET_publish_response(relay_cmd_id, 0, failmsg); } } else { if (relay_cmd_fb_before == FB_ACTIVE && fb_after == FB_INACTIVE) { log_info("> 继电器: SW%d 动作正常", ch); NET_publish_response(relay_cmd_id, 1, "OK"); } else { log_warn("> 继电器: SW%d 未接负载或继电器断开", ch); NET_publish_response(relay_cmd_id, 0, failmsg); } } } else { log_info("> 继电器: SW%d 动作正常", ch); NET_publish_response(relay_cmd_id, 1, "OK"); } /* 参考 APP1: 命令响应后立即上报一次全状态, 让平台刷新开关状态 */ relayStatueUpdata(); relay_cmd_stage = 0; return; } } void relayInit(void) { GPIO_InitTypeDef g = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /* 4 路继电器控制引脚初始化为推挽输出, 默认低电平释放 */ g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_LOW; g.Pin = Relay1_Pin; HAL_GPIO_Init(Relay1_Port, &g); g.Pin = Relay2_Pin; HAL_GPIO_Init(Relay2_Port, &g); g.Pin = Relay3_Pin; HAL_GPIO_Init(Relay3_Port, &g); g.Pin = Relay4_Pin; HAL_GPIO_Init(Relay4_Port, &g); /* 4 路反馈输入引脚, 上拉 */ g.Mode = GPIO_MODE_INPUT; g.Pull = GPIO_PULLUP; g.Pin = Relay1_FB_Pin; HAL_GPIO_Init(Relay1_FB_Port, &g); g.Pin = Relay2_FB_Pin; HAL_GPIO_Init(Relay2_FB_Port, &g); g.Pin = Relay3_FB_Pin; HAL_GPIO_Init(Relay3_FB_Port, &g); g.Pin = Relay4_FB_Pin; HAL_GPIO_Init(Relay4_FB_Port, &g); /* 控制 GPIO 默认全部 OFF(不打印,联网恢复状态时会统一打印一次) */ Relay1_OFF; Relay2_OFF; Relay3_OFF; Relay4_OFF; } /* 网络连接成功后, 从 EEPROM 恢复继电器上一次状态(断电记忆)。 * 无秤模式(基础四路继电器): 四路全部恢复; * 称重投喂模式: 继电器 1 是投喂通道,不恢复且强制关闭,防止上电误投料浪费饲料 */ void relayRestoreState(void) { if (!g_feed_scale_on) { if (MqttInfoStr.Relay_State[1]) { Relay1_ON; log_info("SW1:开(恢复)"); } else { Relay1_OFF; log_info("SW1:关(恢复)"); } } else { MqttInfoStr.Relay_State[1] = 0; Relay1_OFF; log_info("SW1:关(不恢复,投喂安全保护)"); } if (MqttInfoStr.Relay_State[2]) { Relay2_ON; log_info("SW2:开(恢复)"); } else { Relay2_OFF; log_info("SW2:关(恢复)"); } if (MqttInfoStr.Relay_State[3]) { Relay3_ON; log_info("SW3:开(恢复)"); } else { Relay3_OFF; log_info("SW3:关(恢复)"); } if (MqttInfoStr.Relay_State[4]) { Relay4_ON; log_info("SW4:开(恢复)"); } else { Relay4_OFF; log_info("SW4:关(恢复)"); } } /* 读取指定继电器反馈引脚原始电平 */ GPIO_PinState relayReadFb(uint8_t ch) { switch (ch) { case 1: return HAL_GPIO_ReadPin(Relay1_FB_Port, Relay1_FB_Pin); case 2: return HAL_GPIO_ReadPin(Relay2_FB_Port, Relay2_FB_Pin); case 3: return HAL_GPIO_ReadPin(Relay3_FB_Port, Relay3_FB_Pin); case 4: return HAL_GPIO_ReadPin(Relay4_FB_Port, Relay4_FB_Pin); default: return GPIO_PIN_RESET; } } /* 设置指定继电器开关状态: 只写 GPIO 和 Relay_State, 不碰 EEPROM/网络 */ void relaySet(uint8_t ch, uint8_t on) { if (ch < 1 || ch > 4) return; MqttInfoStr.Relay_State[ch] = on ? 1 : 0; switch (ch) { case 1: if (on) Relay1_ON; else Relay1_OFF; break; case 2: if (on) Relay2_ON; else Relay2_OFF; break; case 3: if (on) Relay3_ON; else Relay3_OFF; break; case 4: if (on) Relay4_ON; else Relay4_OFF; break; } } /* 请求一次状态上报: 由主循环检测到 relay_report_pending 后延时统一上报 */ void relayRequestReport(void) { relay_report_tick = HAL_GetTick(); relay_report_pending = 1; } /* 上报继电器当前状态到MQTT平台 */ void relayStatueUpdata(void) { /* 云平台物模型字段:weight=剩余料重(净重),zero=空载重量(去皮),onceWt=单次投料量 */ /* sw1 与 feeding 均上报继电器 1 状态,平台两个开关都能同步刷新 */ NET_publish_status( MqttInfoStr.Relay_State[1] ? 1 : 0, MqttInfoStr.Relay_State[1] ? 1 : 0, MqttInfoStr.Relay_State[2] ? 1 : 0, MqttInfoStr.Relay_State[3] ? 1 : 0, MqttInfoStr.Relay_State[4] ? 1 : 0, (float)feedScaleGetZero() / 10.0f, (float)feedScaleGetOnceWeight() / 10.0f, (float)feedScaleGetWeight() / 10.0f); } /* 从 JSON 报文中解析控制指令并执行(取值统一走 proto 模块,按键名定位) */ void relayAction(uint8_t *data) { char *p; char CmdId[20] = {0}; uint8_t ch = 0; GPIO_PinState fb_before; log_info("> 继电器命令: %.80s", data); /* 支持两种 id 格式:"id":"xxx" 或 "id":xxx */ if (!proto_get_id((char *)data, CmdId, sizeof(CmdId))) { log_warn("> 继电器: id 解析失败"); return; } if (strstr((char *)data, "\"sw")) { char swkey[4]; int on = 0; p = strstr((char *)data, "\"sw"); ch = p[3] - '0'; /* sw1 与 feeding 指令等效, 均控制继电器 1 */ if (ch < 1 || ch > 8) { log_warn("> 继电器: sw 编号无效,仅支持 sw1~sw8"); return; } swkey[0] = 's'; swkey[1] = 'w'; swkey[2] = (char)('0' + ch); swkey[3] = '\0'; if (!proto_get_bool((char *)data, swkey, &on)) { log_warn("> 继电器: sw%d 值无效", ch); NET_publish_response(CmdId, 0, "bad sw value"); return; } /* 控制继电器前先读取反馈电平 */ /* sw5~sw8: 485 扩展继电器板(异步下发, 应答由 ext_relay_process 完成) */ if (ch >= 5) { ext_relay_cmd((uint8_t)(ch - 4), (uint8_t)on, CmdId); return; } fb_before = relayReadFb(ch); if (g_feed_scale_on) { /* 称重自动投喂模式下继电器 1 仅由 feeding 指令控制, sw1 直接忽略 */ if (ch == 1) { log_warn("> 继电器: 称重投喂模式下忽略 sw1,请用 feeding 指令"); NET_publish_response(CmdId, 0, "sw1 disabled, use feeding"); return; } } relaySet(ch, (uint8_t)on); log_info("> SW%d %s", ch, on ? "开" : "关"); } else if (strstr((char *)data, "\"feeding\"")) { /* 喂料/投喂指令: body.feeding.value=true 启动, value=false 停止 */ int value_true = 0; if (!proto_get_bool((char *)data, "feeding", &value_true)) { log_warn("> 继电器: feeding 值无效"); NET_publish_response(CmdId, 0, "bad feeding value"); return; } ch = 1; fb_before = relayReadFb(ch); if (!g_feed_scale_on) { if (value_true) { relaySet(1, 1); log_info("> 继电器: 手动投喂开(无秤)"); } else { relaySet(1, 0); log_info("> 继电器: 手动投喂关(无秤)"); } } else { if (value_true) { /* 首次上电查询到有效重量前不允许启动投喂 */ if (feedScaleGetWeight() == 0) { log_warn("> 继电器: 拒绝启动投喂,重量未就绪"); NET_publish_response(CmdId, 0, "weight not ready"); return; } /* 优先使用本次指令中携带的 onceWt, 否则使用 EEPROM 保存的值 */ uint16_t once_wt_100g = 0; proto_get_kg100((char *)data, "onceWt", &once_wt_100g); if (once_wt_100g == 0) { once_wt_100g = feedScaleGetOnceWeight(); } else { feedScaleSetOnceWeight(once_wt_100g); } if (once_wt_100g == 0) { log_warn("> 继电器: 无 onceWt 单次投喂量,已忽略"); NET_publish_response(CmdId, 0, "no onceWt"); return; } log_info("> 继电器: 开始投喂,onceWt=%.1fKG", (float)once_wt_100g / 10.0f); feedScaleStart(once_wt_100g); } else { log_info("> 继电器: 停止投喂"); feedScaleStop(); } } } else if (proto_locate((char *)data, "onceWt")) { if (!g_feed_scale_on) { /* 无秤模式:无单次投喂量概念,拒绝设置 */ log_warn("> 继电器: 拒绝设置 onceWt(无秤)"); NET_publish_response(CmdId, 0, "no scale on this device"); return; } else { /* 单独设置单次投喂量, 持久化到 EEPROM */ uint16_t once_wt_100g = 0; proto_get_kg100((char *)data, "onceWt", &once_wt_100g); if (once_wt_100g == 0 || once_wt_100g > 5000) { log_warn("> 继电器: onceWt 无效"); NET_publish_response(CmdId, 0, "invalid onceWt"); return; } feedScaleSetOnceWeight(once_wt_100g); NET_publish_response(CmdId, 1, "OK"); relayStatueUpdata(); return; } } else if (proto_locate((char *)data, "zero")) { if (!g_feed_scale_on) { /* 无秤模式:无去皮/空载重量概念,拒绝设置 */ log_warn("> 继电器: 拒绝设置 zero(无秤)"); NET_publish_response(CmdId, 0, "no scale on this device"); return; } else { /* 平台下发空载重量/去皮值,直接解析 value 并保存 */ uint16_t zero_100g = 0; proto_get_kg100((char *)data, "zero", &zero_100g); if (zero_100g == 0 || zero_100g > 5000) { log_warn("> 继电器: zero 无效"); NET_publish_response(CmdId, 0, "invalid zero"); return; } feedScaleSetZero(zero_100g); NET_publish_response(CmdId, 1, "OK"); relayStatueUpdata(); return; } } else { log_warn("> 继电器: 未知指令(无 sw/feeding/zero)"); return; } /* 保存继电器状态到EEPROM, 掉电后恢复 */ eepromWriteData(102, &MqttInfoStr.Relay_State[0], 7); /* 启动非阻塞反馈检测与响应流程 */ relay_cmd_stage = 1; relay_cmd_tick = HAL_GetTick(); relay_cmd_ch = ch; relay_cmd_fb_before = fb_before; memset(relay_cmd_id, 0, sizeof(relay_cmd_id)); memcpy(relay_cmd_id, CmdId, sizeof(relay_cmd_id) - 1); }