shuichan_old/V1.4-Chinese/STM32F103_App1/HardWare/RELAY/relay.c

334 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "relay.h"
#include "main.h"
#include "network.h"
#include "24c02.h"
#include "feed_scale.h"
#include "proto.h"
#include <string.h>
/* 命令执行后状态上报标志 */
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);
if (relayIsManualMode()) {
log_info("> 继电器: SW%d 动作正常(手动模式)", ch);
NET_publish_response(relay_cmd_id, 1, "OK");
} else 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, "Load unpowered / relay failure !");
}
} 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, "Load unpowered / relay failure !");
}
}
} 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_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);
/* PA8 手动模式检测, 下拉, 默认低电平=正常模式 */
__HAL_RCC_GPIOA_CLK_ENABLE();
g.Pin = MANUAL_MODE_Pin;
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(MANUAL_MODE_Port, &g);
/* 控制 GPIO 默认全部 OFF */
Relay1_OFF; log_info("SW1:关");
Relay2_OFF; log_info("SW2:关");
Relay3_OFF; log_info("SW3:关");
Relay4_OFF; log_info("SW4:关");
}
/* 网络连接成功后, 从 EEPROM 恢复继电器上一次状态(断电记忆)。
* 无秤模式(基础四路继电器): 四路全部恢复;
* 称重投喂模式: 继电器 1 是投喂通道,不恢复且强制关闭,防止上电误投料浪费饲料 */
void relayRestoreState(void)
{
#if FEED_SCALE_DISABLED
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:关(不恢复,投喂安全保护)");
#endif
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;
}
}
/* 读取 PA8 手动模式输入: 1=手动模式, 0=正常模式 */
int relayIsManualMode(void)
{
return (HAL_GPIO_ReadPin(MANUAL_MODE_Port, MANUAL_MODE_Pin) == MANUAL_MODE_ACTIVE) ? 1 : 0;
}
/* 设置指定继电器开关状态: 只写 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)
{
/* 云平台字段映射:把剩余料重放到 zero 字段,空载重量放到 weight 字段 */
/* 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 > 4) { log_warn("> 继电器: sw 编号无效,仅支持 sw1~sw4"); 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;
}
/* 控制继电器前先读取反馈电平 */
fb_before = relayReadFb(ch);
#if !FEED_SCALE_DISABLED
/* 称重自动投喂模式下继电器 1 仅由 feeding 指令控制, sw1 直接忽略 */
if (ch == 1) {
log_warn("> 继电器: 称重投喂模式下忽略 sw1请用 feeding 指令");
NET_publish_response(CmdId, 0, "sw1 disabled, use feeding");
return;
}
#endif
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 FEED_SCALE_DISABLED
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();
}
#endif
} else if (proto_locate((char *)data, "onceWt")) {
#if FEED_SCALE_DISABLED
/* 无秤模式:无单次投喂量概念,拒绝设置 */
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;
#endif
} else if (proto_locate((char *)data, "zero")) {
#if FEED_SCALE_DISABLED
/* 无秤模式:无去皮/空载重量概念,拒绝设置 */
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;
#endif
} 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);
}