找回密码
 立即注册

微信扫码登录

搜索
查看: 1118|回复: 9

[技术探讨] 填坑 - 平台消息处理(基于node-red)

[复制链接]

22

主题

554

回帖

4814

积分

论坛UI达人

积分
4814
金钱
4228
HASS币
60
发表于 2026-5-20 21:52:15 | 显示全部楼层 |阅读模式
本帖最后由 hhh. 于 2026-5-20 21:55 编辑

前言:
实现UI界面,需要使用和了解大量的卡片信息,构建符合自己审美和肩负易操作,好实现的UI界面,是很不容易的一件事。
而“智能家居”的最终目标,我觉得不是UI如何好看,设备如何丰富,语音如何智能,操作如何简单。而是完全基于“自动化而”实现的可持续,可扩展,易维护的“个人管家”。

那么我们先迈出第一步,搭建“个人管家”的基础框架:

思路:数据获取→数据处理→回复结果。

  • 数据获取,顾名思义,就是数据来源,无平台限制,任何通信平台数据来源,支持接入HA的都可以。(只支持字符串)
  • 数据处理,根据指令,执行指令中需要执行的自动化,无任何限制,目的就是数据处理。细分一下:{指令判断、分类},执行,输出
  • 回复结果,根据不同平台的回复规则,执行不同平台的回复代码。



数据获取:

按照微信接入HA的一种方法帖子中的方法,我们通过”cn_im_hub“ + ”Node-RED Conversation Agent”集成实现了微信接入HA,那么下面我们探讨,如何处理获取后的数据。

流程构造:events:all节点(监听nodered_request_event) function节点(处理) link out节点(输出)。
                                                                                          → function节点(存储)
function节点(处理)示例:   将来源数据打上特定标签,用户后续流程判断。
// 提取事件数据
let eventData = msg.payload.event;
if (!eventData) return null;
let requestId = eventData.request_id || "";

let Label = "cn_im_hub";

// 整理输出
msg = {
    payload: eventData.text,      // 消息
    request_id: requestId,         // 通信ID
    Label: Label                       // 判定标签
};

return msg;

function节点(存储)示例:  将,用户ID账户ID,持久化存储。
let req = msg.request_id || "";
let parts = req.split(":");

// 来源
let source = parts[0] || "";

// 仅处理 wechat 来源
if (source === "wechat") {

    // wechat:<prefix>@im.bot:<userId>@im.wechat
    let fullBotId = parts[1] || "";               // 账号
    let prefix    = fullBotId.split("@")[0] || ""; // 账号识别码
    let userId    = parts[2] || "";                // 用户ID

    // 生成 key 名称(固定,不编号)
    let botKey  = `${prefix}_Bot_id`;
    let userKey = `${prefix}_User_id`;

    // 读取当前存储
    let existingBot  = flow.get(botKey, "file");
    let existingUser = flow.get(userKey, "file");

    // --- Bot_id:只要 prefix 不变,就永远覆盖,不新增 ---
    if (existingBot !== fullBotId) {
        flow.set(botKey, fullBotId, "file");
    }

    // --- User_id:同理,只要 prefix 不变,就永远覆盖,不新增 ---
    if (existingUser !== userId) {
        flow.set(userKey, userId, "file");
    }

    // 状态显示
    node.status({
        fill: "green",
        shape: "dot",
        text: `用户识别码: ${prefix}`
    });
}

return msg;

企业微信接入同理。



数据处理:

指令判断link in节点(上联数据获取) → function节点(判断) → function节点(数据输出)

不等于任何规则的代码示例:
function节点(判断):     判断:如果其他模式(vx_mode)未激活,来源消息与规则数组不匹配,就输出,否则阻止。
// 获取输入内容并去除空格
let payload = String(msg.payload || "").trim();

// 读取持久化状态,如果已激活则阻断
let modeActive = flow.get("wx_mode", "file");
if (modeActive === true) {
    // 已激活模式,不输出
    return null;
}

// 安全获取持久化全局数组
const andOrArray = flow.get("AND/ORarray", "file") || {};
const excludeKeywords = Object.values(andOrArray).flat();

// 保留全局关键字阻断
if (excludeKeywords.some(k => payload.includes(k))) {
    return null;
}

// 输出 payload
msg.payload = payload;
return msg;

大家应该注意到其中,有两段基于持久化的判定代码,目的就是判断流程走向。

  • let modeActive = flow.get("wx_mode", "file");  判断是否激活其他模式,如:大模型。(已激活 小爱同学 大模型,基于此)
  • const andOrArray = flow.get("AND/ORarray", "file") || {};  根据数组中的默认规则匹配流程走向。

而此阶段就是流程的精髓,也就是判断流程走向的重中之重,其中是否激活其他模式的来源就是,大模型流程中的 function节点(判断)。
激活其他模式 function节点(判断)示例:     判断:来源消息包含:小爱同学等,打开其他模式。
let payload = String(msg.payload || "").trim();

// 激活关键字及对应 flow key
const keywords = {
    "通义千问": "wx_ali_mode",
    "小爱同学": "wx_xiaoai_mode",
    "aihub": "wx_aihub_mode"
};

for (let key in keywords) {
    if (payload.includes(key)) {
        // 持久化保存对应的激活状态
        flow.set(keywords[key], true, "file");
        flow.set("wx_mode", true, "file");
        msg.payload = `已激活 ${key} 模式`;
        return msg;
    }
}

// 其他情况阻断,不输出
return null;

规则数组的目的就是减少代码量,重复使用。
代码示例:   规则持久化存储
flow.set("AND/ORarray", {
    Ai: [
        "小爱同学",
        "aihub",
        "r1",
        "通义千问"
    ],
    Specific: [
        "你能做什么",
        "测试",
        "帮助"
    ],
    Device: [
        "阳台窗帘纱",
        "阳台窗帘布",
        "主卧窗帘纱",
        "主卧窗帘布",
        "次卧窗帘"
    ],
    Custom: [
        "今日天气",
        "现在天气",
        "日程",
        "农历",
        "真太阳时",
        "室内环境监控",
        "门窗状态",
        "室内光照情况",
        "电池电量预警",
        "电池电量总览",
        "Debian13备份状态"
    ],
    Plan: [
        "休息日",
        "工作日",
        "外出"
    ],
    Special: [
        "重置阿里对话",
        "数组初始化",
        "退下吧"
    ]
}, "file");  // 指定持久化存储类型

node.status({fill:"green", shape:"dot", text:"AND/ORarray 持久化完成"});

let message = "数组初始化完成";

// 输出
msg.message = message;

return msg;

而借此规则,我们可以衍生出基于此规则的,流程走向,这就是将指令“分类”。

我们将流程再延申:

link in节点(上联数据获取) → function节点(判断) → link call(格式化日期) → function节点(分类) → 其他节点(数据获取) →  function节点(数据输出)

function节点(判断)示例:     判断:如果其他模式(vx_mode)未激活,来源消息与规则数组匹配,就输出,否则阻止。
let payload = String(msg.payload || "").trim();

// 读取持久化状态,如果已激活则阻断
let modeActive = flow.get("wx_mode", "file");
if (modeActive === true) {
    // 已激活模式,不输出
    return null;
}

// 安全获取 Custom 数组(持久化存储)
const andOrArray = flow.get("AND/ORarray", "file") || {};
const match = andOrArray.Custom || [];

// 完全匹配时输出时间戳
if (match.includes(payload)) {
    msg.payload = Date.now();    // 毫秒级时间戳
    msg.tag = payload;           // 标签直接使用汉字
    return msg;
}

// 不匹配时阻断消息
return null;

function节点(分类)
例如农历获取流程示例:   判断:来源消息与特定标签匹配,就输出,否则阻止。
let tag = String(msg.tag).trim(); 

// 指定要匹配的精确值
const match = "农历";

if (match.includes(tag)) {
    msg.payload = Date.now();   // 输出时间戳
    return msg;
}
// 不匹配时阻断消息
return null;

其他节点(数据获取)农历数据示例:     数据获取后格式化输出。
//  基础数据
const Now = new Date(msg.payload);
const Year = Now.getFullYear();
const Month = Now.getMonth() + 1;
const Day = Now.getDate();

// 农历
const LunarDate = Lunar.Lunar.fromDate(Now);


// 节气(含倒计时)
const CurrentJieQi = LunarDate.getCurrentJieQi();
const NextJieQi = LunarDate.getNextJieQi();

let JieQiInfo = "节气数据不可用";

if (CurrentJieQi) {
    // 今天正好是节气
    JieQiInfo = `今天是${CurrentJieQi._p.name}`;
} 
else if (NextJieQi) {
    const nextSolar = NextJieQi.getSolar();

    // 用年月日构造 Date,避免 toYmd() 解析失败
    const nextDate = new Date(
        nextSolar.getYear(),
        nextSolar.getMonth() - 1,
        nextSolar.getDay()
    );

    const currentDate = new Date(
        Now.getFullYear(),
        Now.getMonth(),
        Now.getDate()
    );

    const diffMs = nextDate.getTime() - currentDate.getTime();
    const diffDays = Math.ceil(diffMs / 86400000);

    JieQiInfo = `${diffDays}天后是${NextJieQi.getName()}`;
}


// 输出
const lunar = {
    "农历": LunarDate.getMonthInChinese() + "月" + LunarDate.getDayInChinese(),
    "干支": {
        "月": LunarDate.getMonthInGanZhiExact() + "月",
        "日": LunarDate.getDayInGanZhiExact() + "日"
    },
    "生肖": LunarDate.getDayShengXiao(),
    "日禄": LunarDate.getDayLu(),
    "节气差": JieQiInfo,
    "物候": LunarDate.getHou() + " " + LunarDate.getWuHou(),
    "东方星宿": LunarDate.getGong() + "方" + LunarDate.getXiu() + LunarDate.getZheng() + LunarDate.getAnimal() + "-" + LunarDate.getXiuLuck(),
    "十二天神": LunarDate.getDayTianShen() + "(" + LunarDate.getDayTianShenType() + ") " + LunarDate.getDayTianShenLuck(),
    "建除日": LunarDate.getZhiXing() + "执位",
    "冲煞": LunarDate.getDayShengXiao() + "日 冲" + LunarDate.getDayChongDesc() + " 煞" + LunarDate.getDaySha(),
    "彭祖干": LunarDate.getPengZuGan(),
    "彭祖支": LunarDate.getPengZuZhi(),
    "宜": LunarDate.getDayYi().join(". "),
    "忌": LunarDate.getDayJi().join(". "),
    "胎神": LunarDate.getDayPositionTai(),
    "吉神": LunarDate.getDayJiShen().join(". "),
    "凶煞": LunarDate.getDayXiongSha().join(". ")
};

// 推送文本
const title = msg.titles || "";
const dateStr = msg.today.dateweekStr;

const message =
`${title}
📅 ${dateStr}
🌙 农历:${lunar.农历}
🪐 干支:${lunar.干支.月}${lunar.干支.日}
🐍 生肖:${lunar.生肖}
📌 日禄:${lunar.日禄}
🌞 节气:${lunar.节气差}
🍃 物候:${lunar.物候}
🌌 星宿:${lunar.东方星宿}
⚡ 吉日:${lunar.十二天神}

🟢 五行:${lunar.建除日}
💥 冲煞:${lunar.冲煞}
🔢 彭祖干:${lunar.彭祖干}
🔢 彭祖支:${lunar.彭祖支}

✅ 宜:${lunar.宜}
❌ 忌:${lunar.忌}

👶 胎神:${lunar.胎神}
🎯 吉神:${lunar.吉神}
☠️ 凶煞:${lunar.凶煞}`;

// 输出
msg.message = message;

return msg;

这个延申的流程,已经具备,“执行”和“输出”的能力,当然有人会说,这个流程完全可以在一个function节点中实现,但是别忘了:我们实现分类判断的目的,而且基于分类判断,我们的流程将不在只为一个流程服务,而是基于标签规则中的所有需要此类流程信息的高级流程而服务。(后续会探讨)



回复结果:

流程构造:function节点(前置判断,数据干净) → function节点(构造消息) → action节点(消息推送)
                                                                      → function节点(构造消息) → fire evevt节点(消息回复)

function节点(前置判断,数据干净)示例:  前置判断和数据干净
// 判断 cn_im_hub 消息
if (msg.Label !== "cn_im_hub" && msg.Label !== "all") {                 // 前置判断,获取数据流程中的 判定标签
    return null;
}

// 获取字段
let message = msg.message || "";
let request_id = msg.request_id || "";
let channel = msg.channel || "";
let user_id = msg.user_id || "";
let bot_id = msg.bot_id || "";

// 1️⃣ 如果 channel 有数据 → 输出第 1 口
if (channel) {
    return [
        {
            message: message,      
            channel: channel,           
            user_id: user_id,
            bot_id: bot_id
        },
        null
    ];
}

// 2️⃣ 如果 request_id 有数据 → 输出第 2 口
if (request_id) {
    return [
        null,
        {
            message: message,
            request_id: request_id
        }
    ];
}

// 3️⃣ 两者都没有 → 不输出
return null;

主动推送 function节点(构造消息) 示例:
let channel = msg.channel;
let message = msg.message;
let userId = msg.user_id;
let botId = msg.bot_id;


msg.payload = {
    "channel": channel,                        // 平台
    "message": message,                     // 消息
    "target": userId,                            // 用户ID
    "wechat_account_id": botId           // 账户ID
};

return msg;

应答消息回复 function节点(构造消息) 示例:
// 数据获取
let replyText = msg.message;
let requestId = msg.request_id;

msg.payload = {
    "request_id": requestId,              // 通信ID 获取数据流程中的
    "response": replyText                 // 消息
};

return msg;

到此,我们已经完整的实现了一个关于”数据获取、数据处理、回复结果“的流程构造,而基于流程判断规则,分类规则,我们可以无限延申代码。
基于”数据处理“这个系统的大框架下,我们又可以实现,手动回复,自动推送,还有主动询问等等的高级流程。

聊聊:
就个人而言,搭建此系统较比搭建UI系统,不相上下,而此系统的易用性,功能性,相对而言也值得付出。
个人能力有限,文中内容起步较高,初学者建议先了解node-red的基础知识,请善用AI。

想要图中个别流程代码的友友,请留言,整体代码还在完善中,喜欢此类系统的友友可以留言探讨其他需要实现的功能。
码字不易,还望大家支持,谢谢!

对话代理总页.jpeg

对话代理流程.jpeg

游客,如果您要查看本帖隐藏内容请回复



评分

参与人数 1金钱 +20 收起 理由
sorrypqa + 20 高手,这是高手!

查看全部评分

回复

使用道具 举报

0

主题

103

回帖

2778

积分

金牌会员

积分
2778
金钱
2675
HASS币
0
发表于 2026-5-21 09:39:37 | 显示全部楼层
感谢分享,学习了。
回复

使用道具 举报

0

主题

86

回帖

571

积分

高级会员

积分
571
金钱
485
HASS币
0
发表于 2026-5-22 13:03:06 | 显示全部楼层
感谢大佬分享
回复

使用道具 举报

6

主题

53

回帖

342

积分

中级会员

积分
342
金钱
283
HASS币
0
发表于 2026-6-21 16:33:42 | 显示全部楼层
感谢分享
回复

使用道具 举报

6

主题

53

回帖

342

积分

中级会员

积分
342
金钱
283
HASS币
0
发表于 2026-6-21 16:52:55 | 显示全部楼层
大佬能分享一下整体代码吗
回复

使用道具 举报

6

主题

53

回帖

342

积分

中级会员

积分
342
金钱
283
HASS币
0
发表于 2026-6-21 22:21:25 | 显示全部楼层
大佬,未闻讯的状态下,能使用个人微信自动化推送吗
回复

使用道具 举报

22

主题

554

回帖

4814

积分

论坛UI达人

积分
4814
金钱
4228
HASS币
60
 楼主| 发表于 2026-6-21 23:06:23 | 显示全部楼层
keveon 发表于 2026-6-21 22:21
大佬,未闻讯的状态下,能使用个人微信自动化推送吗

这个帖子的目的是建立消息回复和主动推送的后端。

如我上一贴中论述的个人微信接入方式,使用openclaw打通HA和个人微信,但是这种方式基于腾讯的长轮询协议,也就是要想保证消息推送,那么首先保证24小时中,必须有一次通信。
所以未闻讯的状态,不支持。

目前想实现随时随地的推送,只有如企业微信这种异步消息机制,才行。
或者只推送无需回复的,纯推送API,也行。

整体代码信息量较大,如无基本的框架了解,无法使用,这也不是单纯的复杂就能用。等我后面优化看,能不能拿来就用。
回复

使用道具 举报

6

主题

53

回帖

342

积分

中级会员

积分
342
金钱
283
HASS币
0
发表于 2026-6-22 08:18:35 | 显示全部楼层
hhh. 发表于 2026-6-21 23:06
这个帖子的目的是建立消息回复和主动推送的后端。

如我上一贴中论述的个人微信接入方式,使用openclaw打 ...

我昨天测试没有24小时,对话超时时间(秒)30s,最大120
回复

使用道具 举报

22

主题

554

回帖

4814

积分

论坛UI达人

积分
4814
金钱
4228
HASS币
60
 楼主| 发表于 2026-6-22 10:24:54 | 显示全部楼层
keveon 发表于 2026-6-22 08:18
我昨天测试没有24小时,对话超时时间(秒)30s,最大120

对话超时时间是为,长文本,或者AI接入预留的应答时间,并非长轮询时间。

这个长轮询时间,我的记忆中好像是24小时,可能有错,我今天测试一下,看看在多长时间后会丢失
回复

使用道具 举报

0

主题

21

回帖

94

积分

注册会员

积分
94
金钱
73
HASS币
0
发表于 2026-9-3 09:31:14 | 显示全部楼层
学习一下,感谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian ( 晋ICP备17001384号-1 )|网站地图

GMT+8, 2026-9-12 06:57 , Processed in 0.016713 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表