311 lines
11 KiB
C
311 lines
11 KiB
C
|
|
#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)
|
||
|
|
{
|
||
|
|
MqttInfoStr.Relay_State[1] = 0;
|
||
|
|
Relay1_OFF; log_info("SW1:OFF (no restore, feeding safety)");
|
||
|
|
|
||
|
|
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 字段 */
|
||
|
|
NET_publish_status(
|
||
|
|
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);
|
||
|
|
}
|