shuichan_old/V1.2/STM32F103_App1/HardWare/RELAY/relay.c

320 lines
12 KiB
C
Raw Normal View History

#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("> RELAY: SW%d OK (manual mode)", 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("> RELAY: SW%d OK", ch);
NET_publish_response(relay_cmd_id, 1, "OK");
} else {
log_warn("> RELAY: SW%d error", 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("> RELAY: SW%d OK", ch);
NET_publish_response(relay_cmd_id, 1, "OK");
} else {
log_warn("> RELAY: SW%d error", ch);
NET_publish_response(relay_cmd_id, 0, "Load unpowered / relay failure !");
}
}
} else {
log_info("> RELAY: SW%d OK", 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:OFF");
Relay2_OFF; log_info("SW2:OFF");
Relay3_OFF; log_info("SW3:OFF");
Relay4_OFF; log_info("SW4:OFF");
}
/* 网络连接成功后, 从 EEPROM 恢复继电器上一次状态(断电记忆)。
* ():
* : 1 */
void relayRestoreState(void)
{
#if FEED_SCALE_DISABLED
if (MqttInfoStr.Relay_State[1]) { Relay1_ON; log_info("SW1:ON (restore)"); }
else { Relay1_OFF; log_info("SW1:OFF (restore)"); }
#else
MqttInfoStr.Relay_State[1] = 0;
Relay1_OFF; log_info("SW1:OFF (no restore, feeding safety)");
#endif
if (MqttInfoStr.Relay_State[2]) { Relay2_ON; log_info("SW2:ON (restore)"); }
else { Relay2_OFF; log_info("SW2:OFF (restore)"); }
if (MqttInfoStr.Relay_State[3]) { Relay3_ON; log_info("SW3:ON (restore)"); }
else { Relay3_OFF; log_info("SW3:OFF (restore)"); }
if (MqttInfoStr.Relay_State[4]) { Relay4_ON; log_info("SW4:ON (restore)"); }
else { Relay4_OFF; log_info("SW4:OFF (restore)"); }
}
/* 读取指定继电器反馈引脚原始电平 */
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("> RELAY cmd: %.80s", data);
/* 支持两种 id 格式:"id":"xxx" 或 "id":xxx */
if (!proto_get_id((char *)data, CmdId, sizeof(CmdId))) {
log_warn("> RELAY: bad 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("> RELAY: bad sw, only sw1~sw4 valid"); 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("> RELAY: bad sw%d value", 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("> RELAY: sw1 ignored in scale feeding mode, use feeding");
NET_publish_response(CmdId, 0, "sw1 disabled, use feeding");
return;
}
#endif
relaySet(ch, (uint8_t)on);
log_info("> SW%d %s", ch, on ? "ON" : "OFF");
} 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("> RELAY: feeding bad value");
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("> RELAY: manual feeding ON (no scale)");
} else {
relaySet(1, 0);
log_info("> RELAY: manual feeding OFF (no scale)");
}
#else
if (value_true) {
/* 首次上电查询到有效重量前不允许启动投喂 */
if (feedScaleGetWeight() == 0) {
log_warn("> RELAY: feeding start rejected, weight not ready");
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("> RELAY: feeding no onceWt, ignore");
NET_publish_response(CmdId, 0, "no onceWt");
return;
}
log_info("> RELAY: feeding start, onceWt=%.1fKG", (float)once_wt_100g / 10.0f);
feedScaleStart(once_wt_100g);
} else {
log_info("> RELAY: feeding stop");
feedScaleStop();
}
#endif
} else if (proto_locate((char *)data, "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 > 5000) {
log_warn("> RELAY: onceWt invalid");
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")) {
/* 平台下发空载重量/去皮值,直接解析 value 并保存 */
uint16_t zero_100g = 0;
proto_get_kg100((char *)data, "zero", &zero_100g);
if (zero_100g == 0 || zero_100g > 5000) {
log_warn("> RELAY: zero invalid");
NET_publish_response(CmdId, 0, "invalid zero");
return;
}
feedScaleSetZero(zero_100g);
NET_publish_response(CmdId, 1, "OK");
relayStatueUpdata();
return;
} else {
log_warn("> RELAY: no 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);
}