乐于分享
好东西不私藏

Openclaw折腾实例(二)——优雅安全地远程和操控家庭设备(无需域名和固定公网IP)

Openclaw折腾实例(二)——优雅安全地远程和操控家庭设备(无需域名和固定公网IP)


(一)书接上回——一句话获取所有设备IPv6
书接上回,Openclaw应用实例(一)中,我们已经可以随时获取到家里任意一台设备的IPv6地址。也就实现了可以仅仅使用微软自带的远程桌面工具(mstsc)等功能,直接“零”安装软件来随时随地的访问家中各个设备的能力。
第一步:首先要如何能够随时控制家用PC的开机和关机。
第二步:电脑开机后要能够自动通过DHCP协议从路由器获取到IPv6地址
第三步:我需要Openclaw能够通过SKILL技能随时获取到家里电脑获取到的最新的IPv6地址,能够通过APP对接插件发送到我手机上对应的APP上。
第四步:每当我需要远程家里电脑时,我只需要问APP上对接的AI助理“给我家里电脑的IPv6地址”即可获取到IPv6然后我就可以在互联网使用该IPv6进行远程登录。
实际上随着我对Openclaw的理解,在上一篇文章里我已经埋下了书写这篇文章的引子——还有优化的空间吗?今天我来继续作答。经过我这两天的测试。我发现无论我怎么调优,当我对Openclaw机器人发出询问家庭电脑地址的指令时。最快的响应速度也要34秒我才能拿到结果。慢的时候甚至要超过两三分钟,这让我难以忍受。
于是我开启Openclaw的debug模式尝试分析我的一条指令发出以后,后端模型通信究竟做了哪些工作,逐帧分析究竟是哪个环节拖慢了速度。脚本本身执行非常快:0.3秒 左右。
 <debug>执行耗时 1m 33s · 查看 3 个步骤Skill Read (14 ms)ikuai ipv6Run command (129 ms)time python3 -c "import requests, json, hashlib, base64, os, sys, time, re; print('导入完成')" 2>&1Run command (235 ms)time python3 -c " import requests, json, hashlib, base64, time from datetime import datetime ROUTER_IP = 'xxx.xxx.xxx' USERNAME = 'xxx' PASSWORD = 'xxxxxx' BASE_URL = {ROUTER_IP}' start = time.time() passwd_md5 = hashlib.md5(PASSWORD.encode()).hexdigest() pass_base64 = base64.b64encode(f'salt_11{PASSWORD}'.encode()).decode() session = requests.Session() login_resp = session.post( login', json={'username': USERNAME, 'passwd': passwd_md5, 'pass': pass_base64, 'remember_password': None}, headers={'Content-Type': 'application/json;charset=UTF-8'}, timeout=10 ) print(f'登录响应: {time.time() - start:.2f}秒') result = login_resp.json() cookie = login_resp.cookies.get('sess_key') print(f'sess_key: {cookie[:10]}...') resp = session.post( call', json={'func_name': 'monitor_lanipv6', 'action': 'show', 'param': {'TYPE': 'data,total', 'limit': '0,50'}}, headers={'Content-Type': 'application/json;charset=UTF-8'}, cookies=[redacted]'username': USERNAME, 'sess_key': cookie, 'login': '1'}, timeout=10 ) print(f'获取设备: {time.time() - start:.2f}秒') result = resp.json() devices = result.get('Data', {}).get('data', []) print(f'设备数量: {len(devices)}') print(f'总耗时: {time.time() - start:.2f}秒') " 2>&1
但我做了一个简化测试,直接测试脚本核心逻辑(登录+获取设备),实际耗时:
  • 登录路由器:0.01秒

  • 获取设备列表:0.11秒

  • 总计:0.12秒

    所以34秒的延迟不是脚本本身的问题,而是整个对话流程中的其他环节。于是我开始探索,当我通过飞书发送给机器人指令,都经过哪些环节?

    我通过分析认为:

  1. 很小概率是飞书的问题,经验来看当下发出信息到对端群组接受到信息基本都是毫秒级别。

  2. 那么接下来排除是否是自己Openclaw长期使用上下文过长等问题。毕竟以前有过10秒的最佳返回记录。虽然经过我的调优,我尝试把Openclaw的skill上下文等压缩到极致。最终确定不是文件读取问题 — 我的Openclaw的AGENTS.md(258行) + SOUL.md(36行) + USER.md(29行) + MEMORY.md(82行) + HEARTBEAT.md(39行),总共才 444 行,读取很快。

  3. 经过我的测试及分析,每当一个指令发送给Openclaw,会有以下大致逻辑:

我(飞书等APP)发消息 →Openclaw Gateway 组装 Prompt(系统提示词+上下文文件+历史对话+您的消息)→ 发送给大模型 API → 大模型推理生成回复 → 返回给我

即使简单指令如”电脑地址”:

    第一步:首先大模型收到我从飞书发出的消息 + 所有上下文文件(AGENTS.md、SOUL.md、USER.md、MEMORY.md 等)。

第二步: 大模型分析:”这是’电脑地址’触发词,应该调用 exec 工具执行脚本”生成 tool_call(执行脚本)脚本返回结果。

第三步:大模型再次分析结果,生成格式化的回复发给我。

所以耗时主要来自大模型推理和API的响应速度:

📝 Prompt 组装(读取文件,几乎瞬间)

🧠 大模型推理(Kimi API 处理,通常 多少秒具体未知)

🌐 网络传输(往返 API,通常 <1 秒)

📝可能是 Kimi API 当时的响应慢,或者 Gateway 进程长时间运行后的累积问题。重启后恢复正常了。

众所周不知,因为我设计获取家用设备的IPv6地址,是通过上图中的Openclaw设备登录家里的路由器来唤醒和获取家里所有设备的IPv6地址。

这就很🥚疼了,因为Openclaw调用的云端大模型推理时间和API时间不受我来控制,似乎无法再压缩响应时间了。


(二)调整思路——进一步深入

重新回到正轨,我的终极诉求是只需要每次发送指令“电脑地址”能够在10秒以内获取到信息即可。自然而然,从上面的Openclaw的工作逻辑,我们能否避免每次发送指令时,不让Openclaw调用大模型,而是直接调用脚本?理论上,对于固定指令(如”电脑地址”),可以配置本地规则直接执行脚本而不经过大模型。

但遗憾的是经过探索发现OpenClaw 目前的设计就是所有交互都走 LLM,这样更灵活,能处理复杂指令和上下文理解。如果直接想去修改Opencalw的底层逻辑,以后更新啥的可能都要重新维护一下,这代价和投入就比较大了。

接着分析Openclaw和飞书通信途径的建立,我发现Openclaw是和飞书服务器有一个长连接的通信的,并且有官方SDK提供Websocket连接和webhook连接。

突然灵光一闪,如果自己写一个不用加载LLM的具备和”Openclaw”能够适配飞书长连接的脚本程序,不就可以实现每次通过飞书发指令给机器人,机器人直接调用加载脚本登录路由器,获取所有设备信息再通过飞书返回不就成功省略掉 大模型推理以及API调用等网络因素延长的时间了。具体实现步骤如下:

第一步在飞书开发者后台创建”快捷指令”机器人,订阅方式选择长连接,该给的权限必要的飞书信息接收和发送权限都添加上。

第二步:参照飞书官网SDK文档API说明编写本地代码程序。

(https://open.feishu.cn/document/server-docs/server-side-sdk

ikuai_ipv6.py(这个脚本在Openclaw应用系列一中有初版)

Websocket代码如下,实际上完全没必要自己写,直接丢给AI官方文档,它自己会帮你写好,你只需要调试验证就行了。并且为了安全,我通在脚本中添加认证功能:AUTHORIZED_USERS = [“我的飞书OPENID”],来控制只有我本人通过飞书聊天调用该机器人才会能有正常结果反馈。

#!/usr/bin/env python3"""飞书快速通道机器人 - 使用官方 lark-oapi SDK 正确用法官方文档: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/handle-events功能:1. 通过 lark-oapi SDK 的 WebSocket 长连接接收飞书消息2. 匹配关键词"电脑地址"/"所有地址"/"唤醒电脑"3. 直接执行本地脚本,秒级返回4. 通过飞书 API 回复消息运行:    python3 feishu_fast_mstsc.py依赖:    pip3 install lark-oapi"""import jsonimport timeimport subprocessimport osimport sysimport signalfrom datetime import datetimeimport lark_oapi as larkfrom lark_oapi.api.im.v1 import ReplyMessageRequest, ReplyMessageRequestBody# ========== 配置区域 ==========APP_ID = "你飞书机器人的APPID"APP_SECRET = "你飞书机器人的密钥"# 授权用户(只响应指定用户,留空表示响应所有人)# 多个用户用逗号分隔,例如: ["user1", "user2"]AUTHORIZED_USERS = ["你的飞书OPENID"]# 快捷指令映射QUICK_COMMANDS = {    "电脑地址": {        "script""python3 ~/.openclaw/workspace/skills/ikuai_ipv6.py DESKTOP",        "description""获取 Windows 电脑 IPv6 地址"    },    "所有地址": {        "script""python3 ~/.openclaw/workspace/skills/ikuai_ipv6.py",        "description""获取所有设备 IPv6 地址"    },    "唤醒电脑": {        "script""python3 ~/.openclaw/workspace/skills/ikuai_ipv6.py -w 你的电脑MAC地址",        "description""唤醒 Windows 电脑"    }}running = True# ========== 工具函数 ==========def log(msg):    """打印日志"""    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}")def execute_script(script_cmd):    """执行本地脚本,返回输出"""    try:        expanded_cmd = script_cmd.replace("~", os.path.expanduser("~"))        result = subprocess.run(            expanded_cmd,            shell=True,            capture_output=True,            text=True,            timeout=300        )        output = result.stdout if result.stdout else result.stderr        return output.strip() or "执行完成,无输出"    except subprocess.TimeoutExpired:        return "⏱ 执行超时(超过5分钟)"    except Exception as e:        return f"❌ 执行错误: {str(e)}"def format_reply(raw_output, elapsed, command_name):    """格式化脚本输出"""    import re    clean = re.sub(r'\x1b\[[0-9;]*m''', raw_output)    header = f"⚡ 快速通道 | {command_name}⏱ 耗时: {elapsed:.1f}s{'='*40}"    return header + cleandef reply_to_message(message_id, content):    """回复指定消息"""    try:        client = lark.Client.builder() \            .app_id(APP_ID) \            .app_secret(APP_SECRET) \            .log_level(lark.LogLevel.INFO) \            .build()        request = ReplyMessageRequest.builder() \            .message_id(message_id) \            .request_body(ReplyMessageRequestBody.builder()                .content(json.dumps({"text": content}))                .msg_type("text")                .build()) \            .build()        response = client.im.v1.message.reply(request)        if response.success():            log("回复成功")        else:            log(f"回复失败: {response.code} - {response.msg}")    except Exception as e:        log(f"回复异常: {e}")# ========== 事件处理(官方 SDK 正确用法)==========def do_p2_im_message_receive_v1(data: lark.im.v1.P2ImMessageReceiveV1) -> None:    """处理消息事件 (v2.0 事件)"""    try:        log(f"收到消息事件!")        # 获取消息信息        message = data.event.message        sender = data.event.sender.sender_id.open_id        # 授权检查        if AUTHORIZED_USERS and sender not in AUTHORIZED_USERS:            log(f"未授权用户: {sender}")            return        # 只处理文本消息        if message.message_type != "text":            log(f"非文本消息: {message.message_type}")            return        # 解析消息内容        try:            content = json.loads(message.content)            text = content.get("text""").strip()        except:            text = ""        message_id = message.message_id        log(f"收到消息: {text[:50]}")        # ========== 快速通道检查 ==========        for keyword, config in QUICK_COMMANDS.items():            if keyword in text:                log(f"触发快捷指令: {keyword}")                start_time = time.time()                # 执行脚本                output = execute_script(config["script"])                elapsed = time.time() - start_time                # 格式化回复                reply = format_reply(output, elapsed, config["description"])                log(f"执行完成: {elapsed:.1f}s")                # 回复消息                reply_to_message(message_id, reply)                return        # 未匹配指令        log(f"未匹配的指令: {text[:50]}")    except Exception as e:        log(f"处理消息异常: {e}")        import traceback        traceback.print_exc()# ========== 主入口 ==========def signal_handler(sig, frame):    """处理退出信号"""    global running    log("收到退出信号,正在关闭...")    running = False    sys.exit(0)if __name__ == "__main__":    # 注册信号处理    signal.signal(signal.SIGINT, signal_handler)    signal.signal(signal.SIGTERM, signal_handler)    print("=" * 60)    print("飞书快速通道机器人")    print("=" * 60)    print("模式: WebSocket 长连接 (官方 SDK)")    print("优势: 无需公网IP,无需端口暴露")    print("=" * 60)    print("支持的快捷指令:")    for k, v in QUICK_COMMANDS.items():        print(f"  • {k} → {v['description']}")    print("=" * 60)    # 检查配置    if not APP_ID or not APP_SECRET:        print("❌ 错误: APP_ID 和 APP_SECRET 未配置")        sys.exit(1)    # 创建事件处理器 - 官方 SDK 正确用法    log("正在创建事件处理器...")    event_handler = lark.EventDispatcherHandler.builder("""") \        .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \        .build()    # 创建 WebSocket 客户端 - 官方 SDK 正确用法    log("正在启动 WebSocket 客户端...")    cli = lark.ws.Client(        APP_ID,        APP_SECRET,        event_handler=event_handler,        log_level=lark.LogLevel.DEBUG    )    try:        # 启动连接(阻塞主线程,直到进程结束)        cli.start()    except KeyboardInterrupt:        log("用户中断")    except Exception as e:        log(f"启动异常: {e}")        import traceback        traceback.print_exc()

脚本写好了,我就在Openclaw那台机器上加载执行并设置守护进程,开机启动等,避免重启还要手动启动脚本,这样就可以实现家里Openclaw那台机器和飞书机器人的第二个长连接实现无需公网域名和固定公网IP的信息指令调度达到如下效果:

我(飞书等APP)发消息 →飞书快捷指令机器人 → 调用部署了Websocket脚本的内网设备(和我的Openclaw机器同一台设备) →通过Websocket脚本调用运行IPv6信息获取脚本登录路由器查询IPv6信息→通过Websocket将信息通过飞书机器人呈现在飞书聊天框

(三)show time

脚本加载用时0.3秒,加上飞书发送和接收时间,整个响应仅仅用时1.3秒左右,比原来的速度快了20多倍

(四)复盘与展望

1.Websocket是个好东西,可以直接形成长连接从云端接收指令,而且不需要公网IP和域名,消息秒级响应,很丝滑。没有端口和服务直接暴露在互联网上相对动态域名等实时把服务映射到互联网而言相对更安全。 那么有原来需要突破网络隔离的网络通信有了新思路……

2.上篇文章我们彻底告别了“某日葵、某desk等”远程控制软件。实际上我前两天也彻底告别了DDNS动态映射域名。我认为把自己的设备映射到第三方云服务器上做跳板远程控制,流量走在第三方是对个人隐私难以保证的。实际原来装远控原来有点类似在被远控机器和远控机器上都装了类似“远控horse”的东西。

3.所以新的问题又来了,本次使用的Websocket长连接实际上走的是飞书的APP和服务器。如果有兄弟对这个不放心,有没有解决方法兄弟?

展望一下:有的兄弟,不如我们自己弄台服务器,写点类似于长连接的东西,再把通信加密了,服务器至于放在哪个网里,再搞个APP……..好了点到为止……