找回密码
 立即注册

微信扫码登录

搜索
查看: 18|回复: 0

[教程系列] 基于Nodered的曲线监控集客AP网络速度

[复制链接]

67

主题

724

回帖

4502

积分

论坛元老

积分
4502
金钱
3706
HASS币
20
发表于 昨天 22:54 | 显示全部楼层 |阅读模式
家里埋线有点小问题,导致部分AP定期会跳到100Mbps的网速,时不时去查看又嫌麻烦,于是有了本教程。。。


直接用nodered的http request去调用一直不成功,于是尝试用ubuntu中去识别,直接成功,感谢chatgpt的帮助

一、ubuntu直接安装nodered (不要使用docker等)

二、创建 python脚本,存放到目录/home/xxx/jike/ap_status.py,修改对应的ap的ip即可,密码是默认的admin

2.1 适用于7系列的集客AP (小米AX5,1800,N3000等)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import hashlib
import requests


BASE_URL = "http://192.168.0.107"
USERNAME = "root"
PASSWORD = "admin"  # 网页登录明文密码


def md5(s: str) -> str:
    return hashlib.md5(s.encode("utf-8")).hexdigest()


def main():
    s = requests.Session()

    # 1️⃣ 先访问首页拿初始 sysauth(跟你浏览器访问 http://192.168.0.107/?t=... 一样)
    t = int(time.time() * 1000)
    resp = s.get(f"{BASE_URL}/", params={"t": t}, timeout=5)
    resp.raise_for_status()

    # 2️⃣ 调用 /cgi-bin/api/admin/getrandom 获取 random
    randtime = int(time.time() * 1000)
    resp = s.get(f"{BASE_URL}/cgi-bin/api/admin/getrandom", params={"randtime": randtime}, timeout=5)
    resp.raise_for_status()
    data = resp.json()

    if data.get("ret") != 1:
        raise RuntimeError(f"getrandom ret != 1: {data}")

    random = data["random"]

    # 3️⃣ 按正确算法生成加密密码:MD5(random + 明文密码)
    hashed_pwd = md5(random + PASSWORD)

    # 4️⃣ POST /cgi-bin/api/admin/sysauth 登录
    login_url = f"{BASE_URL}/cgi-bin/api/admin/sysauth"
    payload = {
        "username": USERNAME,
        "password": hashed_pwd,
    }

    resp = s.post(login_url, data=payload, timeout=5)
    resp.raise_for_status()
    login_result = resp.json()

    if login_result.get("ret") != 1:
        raise RuntimeError(f"登录失败: {login_result}")

    # 5️⃣ 登录成功后,用同一个 session 获取 overview 数据
    randtime2 = int(time.time() * 1000)
    status_url = f"{BASE_URL}/cgi-bin/api/admin/status/overview"
    resp = s.get(status_url, params={"status": 1, "randtime": randtime2}, timeout=5)
    resp.raise_for_status()

    try:
        status_data = resp.json()
        
        # 提取 ipaddr 和 speed
        ipaddr = status_data.get('lan', {}).get('ipaddr', 'Unknown')
        speed = status_data.get('lan', {}).get('speed', 'Unknown')
        
        # 打印只需要的两条信息
        print(f"[+] IP 地址: {ipaddr}")
        print(f"[+] 连接速度: {speed}")

    except ValueError:
        # 如果不是 JSON 格式的数据,输出原始响应
        print("[+] overview 原始响应文本:")
        print(resp.text)


if __name__ == "__main__":
    main()
2.2 适用于8系列版本N3000,主要是https的调用
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import hashlib
import requests
import urllib3

# 关掉因为 verify=False 产生的告警
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BASE_URL = "https://192.168.0.131"   # 🔴 这里改成 https
USERNAME = "root"
PASSWORD = "admin"


def md5(s: str) -> str:
    return hashlib.md5(s.encode("utf-8")).hexdigest()


def main():
    s = requests.Session()
    s.verify = False   # 🔴 所有请求都不校验证书

    # 1️⃣ 访问首页拿初始 cookie
    t = int(time.time() * 1000)
    resp = s.get(f"{BASE_URL}/", params={"t": t}, timeout=5)
    resp.raise_for_status()

    # 2️⃣ /getrandom
    randtime = int(time.time() * 1000)
    resp = s.get(f"{BASE_URL}/cgi-bin/api/admin/getrandom",
                 params={"randtime": randtime},
                 timeout=5)
    resp.raise_for_status()
    data = resp.json()
    if data.get("ret") != 1:
        raise RuntimeError(f"getrandom ret != 1: {data}")
    random = data["random"]

    # 3️⃣ 生成密码:MD5(random + 明文)
    hashed_pwd = md5(random + PASSWORD)

    # 4️⃣ 登录
    login_url = f"{BASE_URL}/cgi-bin/api/admin/sysauth"
    payload = {"username": USERNAME, "password": hashed_pwd}
    resp = s.post(login_url, data=payload, timeout=5)
    resp.raise_for_status()
    login_result = resp.json()
    if login_result.get("ret") != 1:
        raise RuntimeError(f"登录失败: {login_result}")

    # 5️⃣ 获取 overview
    randtime2 = int(time.time() * 1000)
    status_url = f"{BASE_URL}/cgi-bin/api/admin/status/overview"
    resp = s.get(status_url,
                 params={"status": 1, "randtime": randtime2},
                 timeout=5)
    resp.raise_for_status()
    status_data = resp.json()

    ipaddr = status_data.get("lan", {}).get("ipaddr", "Unknown")
    speed = status_data.get("lan", {}).get("speed", "Unknown")
    print(f"[+] ipaddr: {ipaddr} [+] speed: {speed}")


if __name__ == "__main__":
    main()
2.3 适用于7.2版本固定N3000 (当时就这个一直出错,又重新抓了包,改了脚本,适配了一下)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import hashlib
import requests

BASE_URL = "http://192.168.1.105"
USERNAME = "root"
PASSWORD = "admin"  # 网页登录明文密码


def md5(s: str) -> str:
    return hashlib.md5(s.encode("utf-8")).hexdigest()


def main():
    s = requests.Session()

    # 1️⃣ 访问首页,拿初始 cookie
    t = int(time.time() * 1000)
    print(f"[+] GET /?t={t}  获取初始 cookie")
    resp = s.get(f"{BASE_URL}/", params={"t": t}, timeout=5)
    resp.raise_for_status()
    print("[+] 初始 cookies:", s.cookies.get_dict())

    # 2️⃣ /getrandom 获取 random
    randtime = int(time.time() * 1000)
    print(f"[+] GET /cgi-bin/api/admin/getrandom?randtime={randtime}")
    resp = s.get(f"{BASE_URL}/cgi-bin/api/admin/getrandom",
                 params={"randtime": randtime},
                 timeout=5)
    resp.raise_for_status()
    data = resp.json()
    print("[+] getrandom 返回:", data)

    if data.get("ret") != 1:
        raise RuntimeError(f"getrandom ret != 1: {data}")

    random = data["random"]
    print("[+] 明文密码:", PASSWORD)
    print("[+] random   :", random)

    # 3️⃣ 生成密码:MD5(random + 明文密码)
    hashed_pwd = md5(random + PASSWORD)
    print("[+] 加密后密码(应和浏览器抓包的一样):", hashed_pwd)

    # 4️⃣ 登录 /sysauth
    login_url = f"{BASE_URL}/cgi-bin/api/admin/sysauth"
    payload = {
        "username": USERNAME,
        "password": hashed_pwd,
    }

    print(f"[+] POST {login_url} 尝试登录")
    resp = s.post(login_url, data=payload, timeout=5)
    resp.raise_for_status()
    login_result = resp.json()
    print("[+] 登录返回:", login_result)
    print("[+] 登录后 cookies:", s.cookies.get_dict())

    if login_result.get("ret") != 1:
        raise RuntimeError(f"登录失败: {login_result}")

    # 虽然返回了 token,但从抓包看 overview 根本没用它
    token = login_result.get("token")
    print("[+] 登录 token:", token)

    # 5️⃣ 模仿浏览器请求 /status/overview:
    #    - GET 方法
    #    - URL 参数只有 status / randtime
    #    - Cookie 里带 sysauth(Session 自动处理)
    #    - Header 额外再带一个 sysauth: <same value>
    randtime2 = int(time.time() * 1000)
    status_url = f"{BASE_URL}/cgi-bin/api/admin/status/overview"

    # 从当前 session 的 cookie 里拿 sysauth
    sysauth_val = s.cookies.get("sysauth", "")
    print("[+] 用于 overview 的 sysauth:", sysauth_val)

    params = {
        "status": 1,
        "randtime": randtime2,
    }

    headers = {
        # 浏览器请求里确实有这个自定义头
        "sysauth": sysauth_val,
        # 其它头一般不强制,但你想更像浏览器可以加:
        # "Accept": "application/json, text/plain, */*",
        # "User-Agent": "Mozilla/5.0 ...",
        # "Referer": f"{BASE_URL}/?t={t}",
    }

    print(f"[+] GET {status_url}  带参数: {params}  头: {headers}")
    resp = s.get(status_url, params=params, headers=headers, timeout=5)
    resp.raise_for_status()

    try:
        status_data = resp.json()
        print("[+] overview JSON 数据:")
        print(status_data)

        ipaddr = status_data.get("lan", {}).get("ipaddr", "Unknown")
        speed = status_data.get("lan", {}).get("speed", "Unknown")
        print(f"[+] ipaddr: {ipaddr} [+] speed: {speed}")

    except ValueError:
        print("[+] overview 原始响应文本:")
        print(resp.text)


if __name__ == "__main__":
    main()
三、nodered中用exec,创建工作流,命令是python3 /home/XXX/jike/ap_status.py,然后第一个输出节点,连function,提取当前速度
var output = msg.payload;

// 使用正则表达式提取 ipaddr 后面的 IP 地址
var ipaddrMatch = output.match(/speed:\s*([\d\.]+)/);

// 提取到的 ipaddr 值,如果没有匹配到则为 null
var ipaddr = ipaddrMatch ? ipaddrMatch[1] : null;

// 将提取到的 ipaddr 设置为 msg.payload,以便后续处理
msg.payload = ipaddr;

return msg;
四、继续跟上mqtt,主题内填写:homeassistant/sensor/jikeapspeed1
五、homeassistant中添加代码:
mqtt:
  sensor:

# 192.168.0.198
    - name: "AP-girl-3l"
      state_topic: "homeassistant/sensor/jikeapspeed1"
      unit_of_measurement: "M"
      value_template: "{{ value | int }}"



六、最终效果如下
ap.png

七、nodered中添加监控,回传企业微信通知,大功告成!
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-12-5 05:07 , Processed in 0.569068 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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