326 lines
11 KiB
C
326 lines
11 KiB
C
#include "ext_relay.h"
|
||
#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 (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_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 > 8) 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 > 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;
|
||
}
|
||
|
||
/* 控制继电器前先读取反馈电平 */
|
||
/* 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);
|
||
}
|