去年买了两台Panther X2,今年又买了一台OES Plus正好把几个2.5硬盘给整上,折腾了一段时间终于理顺,记录下。
分工
一台Panther X2当外部流量入口:
- frpc将nginx端口映射到VPS,外网ipv4可通过VPS访问内部网部;
- cron定时脚本更新cloudflare dns解析域名到ipv6地址,dns解析到ipv4至VPS,同一个域名都可以访问对外暴露的服务;
- cupsd服务,wireguard/tailscale连接后,在外可以连接另一个VLAN的打印机给小朋友打印作业;
- adguardhome服务,在路由器RouterOS中设置
NetWatch,服务正常时使用此服务,服务不正常时使用外部DNS服务; - zigbee2mqtt/mqtt/homeassistant服务,HA主要控制中心;
- openthread/border-router服务,用一个ESP32-C6当RCP,可做为一个Thread路由节点。
另一台Panther X2当安装了带NPU的Armbian:
- openthread/border-router服务,另一个ZBT-1当RCP,做一个Thread路由节点;
- frigate服务,连接家里两个摄像头,录制视频通过NFS写入到OES Plus的硬盘中。其中莹石的一个摄像头需要把音频给禁掉(在RSTP流地址后面加上
#audio=0),否则APP对讲机无法使用。
OESPlus性能和存储都较Panther X2强,有一些大的服务在上面跑:
- hermes-agent/docker,通过飞书或电报可以远程安装其干活;
- 两个navidrome容器,一个听歌,一个听书;
- qbittorrent/qbittorrent-ee,下载一些视频什么的;
- esphome,用业OTA更新固件,固件编译使用NAS或笔记本当后台build server;
- hass-otbr-docker/python-matter-server,用来当Thread主边界路由;
- 一个11年的老Intel 80G SSD当系统存储,另外两个机械盘做为下载与共享,通过NFS提供文件共享。
Panther X2有32G的eMMC,OESPlus只有8G,AI找了一些如何减少emmc写入的注意事项,尽量减少日志的写入,能不写日志的就不写日志,HomeAssistant只记录一些需要的数据(recorder只记录一些温度等数据),docker容器产生的一些临时文件存tmpfs,journal也写tmpfs等等。
ESP-IDF环境安装
之前安装ESP-IDF老是走GH,速度慢得很,后来发现官方有个EIM,可以走国内镜像,速度快不少,下载地址。
也可以自己设置变量,也可以给ESPHOME编译提高速度。其他pypi镜像可以用aliyun或高校的。
1 2 3 4 5 6 7
| export ESPHOME_IDF_FRAMEWORK_MIRRORS="\ https://dl.espressif.cn/github_assets/espressif/esp-idf/releases/download/v{VERSION}/esp-idf-v{VERSION}.zip;\ https://github.com/espressif/esp-idf/releases/download/v{VERSION}/esp-idf-v{VERSION}.zip" export IDF_GITHUB_ASSETS="dl.espressif.cn/github_assets" export ESP_IDF_CONSTRAINTS_MIRRORS="\ https://my.local.mirror/espidf.constraints.v{VERSION}.txt;\ https://dl.espressif.com/dl/esp-idf/espidf.constraints.v{VERSION}.txt"
|
ESP32-C6当OpenThread RCP
A Low-Cost Thread Border Router: ESP32-C6-WROOM as an OpenThread RCP for Home Assistant in Docker
Make your own Thread Border Router for just $5
就是ESP32-C6当RCP,通过USB与Docker的OTBR程序通信,可以做为一个Thread边界路由使用。${IDF_PATH}/examples/openthread/ot_rcp进行配置编译上传。
docker-compose.yaml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| services: otbr: image: openthread/border-router restart: always volumes: - ./data:/data - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro environment: - TZ=Asia/Shanghai env_file: otbr-env.list cap_add: - SYS_ADMIN - NET_ADMIN network_mode: host devices: - /dev/net/tun - /dev/ttyACM0:/dev/ttyACM0
|
otbr-env.list1 2 3 4 5 6 7 8 9 10 11 12
| OT_RCP_DEVICE=spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=460800 OT_INFRA_IF=end1 OT_THREAD_IF=wpan0 OT_LOG_LEVEL=1
OT_REST_LISTEN_ADDR=0.0.0.0 OT_REST_LISTEN_PORT=8081 OT_WEB_LISTEN_ADDR=0.0.0.0 OT_WEB_LISTEN_PORT=7586
NDP_PROXY=1 RA=1
|
开启转发,或者用脚本curl -sSL https://raw.githubusercontent.com/openthread/ot-br-posix/refs/heads/main/etc/docker/border-router/setup-host | bash
1 2 3 4 5
| net.ipv6.conf.all.disable_ipv6 = 0 net.ipv4.conf.all.forwarding = 1 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.all.accept_ra_rt_info_max_plen = 64 net.ipv6.conf.all.accept_ra = 2
|
另外有一个
docker-compose.yaml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| services: otbr: container_name: otbr image: ownbee/hass-otbr-docker:latest restart: unless-stopped privileged: true network_mode: host cap_add: - SYS_ADMIN - NET_ADMIN environment: DEVICE: "/dev/ttyUSB0" BACKBONE_IF: eth0 FLOW_CONTROL: 1 FIREWALL: 1 NAT64: 1 BAUDRATE: 460800 OTBR_REST_PORT: 8081 OTBR_WEB_PORT: 7586 OTBR_WEB: 1 AUTOFLASH_FIRMWARE: 0 OTBR_LOG_LEVEL: error TZ: Asia/Shanghai devices: - /dev/ttyACM0:/dev/ttyUSB0 - /dev/net/tun:/dev/net/tun volumes: - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime - ./otbr_data:/data/thread logging: driver: "journald"
matter-server: image: ghcr.nju.edu.cn/home-assistant-libs/python-matter-server:stable container_name: matter-server restart: unless-stopped network_mode: host privileged: true security_opt: - apparmor:unconfined volumes: - ./matter_data:/data/ - /run/dbus:/run/dbus:ro - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro logging: driver: "journald" command: > --storage-path /data --paa-root-cert-dir /data/credentials --bluetooth-adapter 0 --log-level-sdk progress --primary-interface eth0
|
双芯片方案
官方推荐的方案是ESP32 S3 + ESP32 H2,之前H2已经被我用到其他地方去了,S3在角落里面懒得找了。ESP32 C6手边上倒是还有4个,就想用两个ESP32 C6来试试。
嘉立创开源广场有位网友做了一个MINI边界路由器。
现在只想快速验证可行性,没有画PCB板,直接用面包板接线,最后发现是可行的,只是也走了一些弯路。想法是一个C6当Host,UART连接另一个C6 RCP,然后WIFI连接主网络,与Home Assistant通信;另一个C6只当RCP,UART与Host通信。
最早是用IDF自带例子ot_br与ot_rcp,两个都刷成功,通过idf.py monitor开启WIFI,设置OpenThread网络信息,可以成功连接到网络,但是在固件里面开启自动启动WIFI与OpenThread时会报错,而且还没有HTTP服务,不能在HomeAssistant中增加Open Thread Border Router。
后来换了官方推荐里面的仓库,填入WIFI信息,OpenThread信息,。。。报错。。。其中有两个花了不少时间来处理,一个是分区大小,仓库示例里面需要的存储是8M,但我的C6 SuperMini只有4M,这个搜索了不少时间,把分区大小改掉,然后不用OTA与RCP_UPDATE,最终解决。
partitions.csv1 2 3 4 5 6
| # Name, Type, SubType, Offset, Size, Flags # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap nvs, data, nvs, , 0x6000, phy_init, data, phy, , 0x1000, factory, app, factory, , 2M, web_storage,data, spiffs, , 512K,
|
另外一个是固件刷成功后,启动后无限重启,好像是Host与RCP通信错误,把错误信息丢给AI,没有解决。。。过了好长时间,试着把TX/RX引脚号对掉,问题解决。。。后来查了下代码,发现是我理解错了。配的TX是PIN_TO_RCP_RX,RX是PIN_TO_RCP_TX。。。。。
esp_ot_config.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ { \ .radio_mode = RADIO_MODE_UART_RCP, \ .radio_uart_config = { \ .port = 0, \ .uart_config = \ { \ .baud_rate = 460800, \ .data_bits = UART_DATA_8_BITS, \ .parity = UART_PARITY_DISABLE, \ .stop_bits = UART_STOP_BITS_1, \ .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ .rx_flow_ctrl_thresh = 0, \ .source_clk = UART_SCLK_DEFAULT, \ }, \ .rx_pin = CONFIG_PIN_TO_RCP_TX, \ .tx_pin = CONFIG_PIN_TO_RCP_RX, \ }, \ } #else
|
当然我只是验证方案是否可行,这个方案还不稳定,毕竟WIFI网络是有延迟或干扰问题。后续如果再自己做的话,可能打板走RJ45网串口吧。另外C6当RCP时,有一点发热,主频降到80MHZ,稍微有一点效果,但不是特别明显。