avatar


TeslaMate 国内部署:Android + Termux

这是什么

TeslaMate 是一个开源的特斯拉数据记录器:通过特斯拉官方 API 拉车辆数据(位置、电量、充电、行程、温度等),存进自己掌控的 PostgreSQL,再用 Grafana 出图表。它是车主完全自己拥有数据的一种选择。

但 TeslaMate 原项目为海外用户写,国内跑会撞上几堵墙:

  • 默认反向地理编码用 OpenStreetMap Nominatim,国内不通
  • 地图坐标用 WGS-84(GPS 原始),中国法律要求公开地图用 GCJ-02 偏移坐标系,直接叠加偏移 50-500 米

此外,原项目推荐 Docker Compose 部署,但我希望跑在手机上,Termux 跑不了 Docker。

本文记录把整套 TeslaMate 部署到一台 Android 手机(小米 Redmi)+ Termux 的实战,连带几个值得留意的细节。最终成果是手机里持续跑着 8 个服务,开车一公里数据立即可视化。

整体方案

硬件

  • 设备:Redmi(Android 15)+ Termux app
  • 架构:aarch64(64-bit ARM 原生,不经 PRoot/QEMU 翻译)
  • 内存:7.3 GB,编译期峰值 4-5 GB
  • 磁盘:30 GB+
  • 网络:家用 Wi-Fi 即可,无需公网 IP

为什么选手机:本身就插着电、有屏幕、24 小时在线、aarch64 性能足够跑 Elixir/PostgreSQL,且数据完全留在内网。还以为,便宜不花钱。(用的是我废弃的手机)

软件栈

五个开源项目共享一个 PostgreSQL:

1
2
3
4
5
6
7
8
9
Tesla API

TeslaMate(采集 / 写库)

PostgreSQL 18(drives / positions / charging_processes / addresses)
├──→ Grafana + chinese-dashboards(深度数据分析,49 个中文仪表盘)
├──→ TeslamateCyberUI(Go 后端 + React PWA,手机赛博朋克 UI)
├──↔ fix_addrs(用腾讯地图 API 补全地址)
└──↔ merge_daemon(合并临停被切两段的相邻行程,给 CyberUI 看)

读写边界是安全约定:

  • TeslaMate:写 drives / positions / charging_processes
  • Grafana:只读
  • CyberUI:只读(除了 ui_settings 表它自己写)
  • fix_addrs:只写 addresses 表,不动 GPS 原始数据
  • merge_daemon:只写 drive_merge_groups 侧表(自己建的,不动 TeslaMate 原表)

坐标系统:分两层处理,不可合并

数据库永远存 WGS-84。两个 UI 在不同层做 GCJ-02 纠偏:

组件 纠偏位置
Grafana PostgreSQL 里的 wgs84_to_gcj02() 存储过程,Grafana 查询时调用
CyberUI Go 后端代码里调用工具函数

不要把两者合并到任一层——数据库必须保持 WGS-84,因为 fix_addrs 调用腾讯 API 时用 coord_type=1 直接吃 WGS-84,跳过坐标转换。

网络:用蒲公英解决"不在家就访问不到"

手机蹲在家里 Wi-Fi 上,外出时访问不通。

我用蒲公英解决这个问题。蒲公英是 Oray 贝锐 出的 mesh VPN:所有装了客户端并登录同一账号的设备会自动组成一个虚拟局域网(默认 172.16.x.x 段),互相通过虚拟 IP 直连,不需要公网 IP、不需要路由器端口转发、自动 NAT 穿透。免费档 3 台设备,限速 1 Mbps,看 TeslaMate / Grafana / CyberUI 这种轻量应用完全够。

部署

  • 手机端:安卓应用商店搜"蒲公英"装上(不是在 Termux 里跑——Termux 没权限创建 TUN 设备,但 Android app 通过系统 VpnService API 可以)。登录 → 加入网络 → 授权 VPN,手机就拿到一个 172.16.x.x 固定虚拟 IP
  • 笔记本 / iPhone 客户端官网下载 或对应应用商店,登同一账号、加入同一网络
  • 使用:浏览器打 http://172.16.x.x:4000(TeslaMate)、:3000(Grafana)、:8080(CyberUI),SSH 用 ssh -p 8022 root@172.16.x.x

为什么不用 Tailscale

我最初装的是 Tailscale,跑了几天就换掉了。Tailscale 设计上没毛病(基于 WireGuard,开源,全球节点,免费档 100 设备额度),但在大陆有结构性的水土不服

打洞失败时回退海外 DERP 中继(东京 / 旧金山 / 法兰克福)。三大运营商家宽十之八九带 CGNAT,蜂窝数据网必然 CGNAT,双方都在 CGNAT 后面 P2P 极难打通,几乎必然回退中继。中继单程 80–200ms 起,叠加 WireGuard 封装后,TCP RTT 起步 150ms+,看 Grafana dashboard 加载要好几秒,看 CyberUI 实时地图直接劝退。蒲公英同样是 P2P + 中继架构,但中继节点在大陆,单程 30–50ms。

开工前的硬约束

部署前要先解决两件事:禁掉 Android 的 Phantom Process Killer,以及了解 Termux 跟标准 Linux 的几个不同。这两组都不是技术上的难点,是平台特性——绕不开,开工前先认清楚后面会顺很多。

Android Phantom Process Killer

PPK 是什么

Android 12+ 对每个 app 的"幻影子进程"数有上限(默认 32 个)。超出后系统会随机 SIGKILL 最老的子进程,无警告、无日志。"幻影"指原生 fork 出来、不在 Android lifecycle 里的 child—— sshd 子进程、bashbeam.smpgccnode 全是。

触发症状:长编译跑到一半,sshd / postgres / mosquitto 突然全死,但几个进程(启动较晚的 beam.smp、grafana-server)还在。不是内存,不是电池,纯粹是进程数。

原生 Android 的禁用方式

ADB 两条命令搞定:

1
2
adb shell "settings put global settings_enable_monitor_phantom_procs false"
adb shell "device_config put activity_manager max_phantom_processes 2147483647"

验证:

1
2
3
4
5
adb shell "settings get global settings_enable_monitor_phantom_procs"
# 期望: false

adb shell "device_config get activity_manager max_phantom_processes"
# 期望: 2147483647

重启手机后保留。

小米/澎湃 OS 的两道暗门

我用的是 Redmi 手机,跑澎湃 OS。直接跑上面 ADB 会报:

1
2
java.lang.SecurityException: Permission denial: writing to settings requires:
android.permission.WRITE_SECURE_SETTINGS

第一道暗门:「USB 调试(安全设置)」开关

原生 Android 的 ADB 默认就有 WRITE_SECURE_SETTINGS小米/澎湃 OS 要单独打开一个隐藏开关——「USB 调试(安全设置)」,位置在设置 → 开发者选项 → 往下滑找。

第二道暗门:必须插实体 SIM 卡

点了「USB 调试(安全设置)」会弹窗:

需要登录小米账号且手机已插入 SIM 卡才能开启

这是小米为了防止二手机倒卖后被远程刷机做的硬性反措施。

Termux 平台特性

路径完全不一样

标准 Linux Termux
/opt/... 不存在,用 ~/teslamate/$PREFIX/var/...
/etc/... $PREFIX/etc/...
/usr/... $PREFIX/...$PREFIX=/data/data/com.termux/files/usr
/tmp $PREFIX/tmp 真实存在;但绝对路径 /tmp 在 Android 里不存在(TeslaMate 写死 TZDATA_DIR=/tmp,必须改)
多用户、postgres user 全是 Termux app 的单 user

文件系统不支持硬链接

Termux 的 /data/data/com.termux/files/... 是 Android 内置存储,不允许 hardlinkmix deps.get 默认要 hardlink 做 compilation lock,会挂掉报:

1
2
3
could not create hard link from ... permission denied.
Hard link support is required for Mix compilation locking.
If your system does not support hard links, set MIX_OS_CONCURRENCY_LOCK=0

修法:所有 mix 调用前 export MIX_OS_CONCURRENCY_LOCK=0

没有 systemd

Termux 没有 systemd,但 termux-services 包提供 runit,行为类似。也可以用 setsid + nohup 简单做。我最后选 setsid + nohup + Termux:Boot 应用,对单用户场景已经够用。

安装路线

pkg install 装齐全套

Termux 是 Android app 沙盒,没有 cgroup 写权限,Docker daemon 跑不起来。所以本方案走原生 pkg install + 源码编译路线:

1
2
3
pkg install postgresql postgis erlang elixir golang nodejs nginx mosquitto \
grafana git python python-pip build-essential termux-services \
tmux cronie openssl-tool curl wget

Termux 仓库走 Cloudflare CN CDN,国内直连 1-2 秒一个包,不需要代理。

实测装到的版本(与 TeslaMate 官方 Dockerfile 对齐):

组件 版本
PostgreSQL 18.2
PostGIS 3.6.3
Erlang/OTP 28(带 JIT)
Elixir 1.19.5
Go 1.26.3
Node.js 26.1.0
Grafana 12.3.3
Python 3.13.13

PostgreSQL:cube 和 earthdistance 必须从源码编

TeslaMate 重度依赖 ll_to_earth / earth_distance / earth_box 做地理围栏计算。这些函数由 earthdistance 扩展提供,它又依赖 cube

Termux 默认 contrib 不带 cube 和 earthdistance——但好消息是 Termux 的 postgresql带完整 pgxs + dev headers + clang + flex + bison,可以 5 分钟从源码编出来:

1
2
3
4
5
cd $HOME/teslamate/build
wget --no-check-certificate https://ftp.postgresql.org/pub/source/v18.2/postgresql-18.2.tar.gz
tar xzf postgresql-18.2.tar.gz
cd postgresql-18.2/contrib/cube && make USE_PGXS=1 install
cd ../earthdistance && make USE_PGXS=1 install

产物落到 $PREFIX/share/postgresql/extension/$PREFIX/lib/postgresql/。然后:

1
2
3
4
5
psql -h $PREFIX/var/run -p 5433 -d teslamate <<SQL
CREATE EXTENSION cube;
CREATE EXTENSION earthdistance;
CREATE EXTENSION postgis;
SQL

PostgreSQL:单用户初始化

Termux 装出来的 PG 以 Termux app 用户身份跑,不像 Debian 有 postgres 用户。直接:

1
2
3
4
5
6
7
8
9
10
11
12
13
initdb -D $PREFIX/var/lib/postgresql \
--encoding UTF8 --locale C.UTF-8 \
--auth-local=trust --auth-host=md5

pg_ctl -D $PREFIX/var/lib/postgresql \
-l $PREFIX/var/log/postgresql.log \
-o "-p 5433 -k $PREFIX/var/run" start

psql -h $PREFIX/var/run -p 5433 -d postgres <<SQL
CREATE ROLE teslamate WITH LOGIN PASSWORD '<生成的强密码>';
CREATE DATABASE teslamate OWNER teslamate ENCODING 'UTF8';
ALTER ROLE teslamate SUPERUSER;
SQL

teslamate 角色必须有 SUPERUSER。TeslaMate 的 20190925152807_create_geo_extensions migration 要 ALTER FUNCTION ll_to_earth SET search_path = public,普通用户没权限。Termux 单用户安全边界本就不严,给 SUPERUSER 没问题。

TeslaMate:编译 Elixir release

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd $HOME/teslamate/src/teslamate
export MIX_OS_CONCURRENCY_LOCK=0 # Termux 不支持 hardlink
export MIX_ENV=prod
export SKIP_LOCALE_DOWNLOAD=true

mix local.hex --force
mix local.rebar --force # pkg 没有 rebar3, 在线装
mix deps.get --only prod
mix deps.compile

(cd assets && npm config set registry https://registry.npmmirror.com && npm ci)
mix assets.deploy
mix compile
mix release --path $HOME/teslamate/bin/teslamate-app

整个过程在手机上 6 分钟左右(编译 cldr_utils 那段最慢,单核 100%)。

CyberUI

DeaglePC/TeslamateCyberUI 是一个 Go 后端 + React PWA 前端的 TeslaMate 手机端,赛博朋克风格,做得很漂亮。在国内场景下我做了几处适配(高德 Web JS API 安全密钥的完整接入、新部署 drives 表为空时的边界处理、补一些国内 Tesla API 上报的颜色字符串到配色表等),整理成 fork:KakaWanYifan/TeslamateCyberUI。具体改动见 fork main 分支的 commit log。

部署时直接用 fork:

1
2
3
git clone https://github.com/KakaWanYifan/TeslamateCyberUI.git
cd TeslamateCyberUI/backend && go build -o cyberui-backend ./cmd/server
cd ../frontend && npm install && npm run build

fix_addrs

TeslaMate 默认用 OSM Nominatim 做反向地理编码,国内访问不稳,导致 addresses 表多数字段为空。fix_addrs 是个补救脚本——读出 drives / charging_processesaddress_id IS NULL 的记录,再调一次反向地理 API 把地址写回。

国内的反向地理 API,候选只有腾讯和高德。fix_addrs 选腾讯不选高德,唯一理由:腾讯 Web Service API 支持 coord_type=1 直接吃 WGS-84 坐标,跟数据库里 TeslaMate 写入的原始 GPS 坐标格式一致,跳过 WGS-84 → GCJ-02 的转换步骤。高德 Web Service 不支持 WGS-84 输入,必须先转 GCJ-02,多一步、多一个出错点。

顺便区分一下:CyberUI 前端用的高德 JS API 是地图瓦片服务,跟反向地理编码是两个完全不同的接口、不同的 Key 类型。别混淆。

部署时直接用 fork(fork 加了腾讯优先分支):

1
2
3
4
5
# 不要拉原仓库
git clone https://github.com/KakaWanYifan/teslamate_fix_addrs.git
cd teslamate_fix_addrs
python -m venv venv
venv/bin/pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

补丁已 commit 在 fork 的 main 分支:

  • fix(geocoder): use Tencent for mode-0 fix-empty path when key is set

启动 fix_addrs 前还要去腾讯位置服务控制台做一步配额分配,否则会撞到 status=121,具体见后面「fix_addrs 三层独立的细节」。

merge_daemon:临停误切的合并

TeslaMate 用 IGN OFF(点火关闭)作为一段 drive 结束的信号。停车买杯咖啡 5 分钟再继续开,TeslaMate 会判定成两段独立 drive——明明一次连贯出行,行程列表却多出一行几百米的"碎片",体验很差。

merge_daemon 是后台 Python 守护进程,每分钟扫一遍 drives 表,把符合规则的相邻两段标记成"同一组":

  • 同一辆车按 start_date, id 排序的相邻两段
  • 上一段终点到下一段起点 ≤ 50 米(earthdistance 算地表距离)
  • gap 内非充电闲置 ≤ 30 分钟(= 墙钟间隔 − 累计充电时长)且累计充电 ≤ 2 小时——单一墙钟阈值兼容不了"服务区充 30 分钟"(想合)和"公司停车 1.5 小时不充电"(不想合)这两个对立场景,拆两半各管一边
  • 支持链式合并:A→B 合并后,C 与 B 满足规则就也归入 A 组

中途充电会让 start_ideal_range_km - end_ideal_range_km 变负数。视图 drives_merged 加 3 列 net_range_consumed_km / gap_charge_added_soc / gap_charge_added_kwh 把充电增量扣回去,UI 显示"中途充电 +X%"提示;电量 / 续航的绝对值保留车机真实读数。

daemon 不动 TeslaMate 原表,只 INSERT 自建的 drive_merge_groups 侧表 + drives_merged / positions_merged 视图,CyberUI 改读视图。误合就 DELETE 一行还原,调阈值就 TRUNCATE 重扫历史,TeslaMate 全程无感。Grafana 不切——49 个面板的 SQL 都 FROM drives,改不动;好在 SUM 类统计前后同值,只有 COUNT 行程数会少几行(反而提示"原始 drive 数 vs. 合并后行程数",有信息量)。

源码在 CyberUI fork 的 merge-daemon/ 目录,跟 CyberUI 一起 git pull 更新。部署:

1
2
3
4
5
6
7
8
9
cd ~/teslamate/src/TeslamateCyberUI/merge-daemon
psql -h ... -d teslamate -f install-schema.sql # 侧表 + 视图 + charging_processes(car_id, start_date) 索引(幂等)

# 复用 fix-addrs 的 venv,psycopg2 已经在了
~/teslamate/bin/fix-addrs-venv/bin/python merge_daemon.py \
-H 127.0.0.1 -P 5433 -d teslamate -u teslamate -p $DBPASS \
--distance-threshold-m 50 \
--idle-gap-min 30 --charge-gap-min 120 \
--scan-interval-sec 60

跟 fix_addrs 完全解耦:merge_daemon 只看 GPS 和充电时长,fix_addrs 只写地址,互不依赖,起停顺序无所谓。

其他几个细节

TeslaMate:TZDATA_DIR=/tmp 写死

TeslaMate config/runtime.exs:189 写死:

1
config :tzdata, :data_dir, System.get_env("TZDATA_DIR", "/tmp")

Termux 下绝对路径 /tmp 不存在 → tzdata 启动找 /tmp/release_ets 失败 → BEAM 整个崩溃。

修法:提前 seed:

1
2
3
4
5
6
7
8
mkdir -p $HOME/teslamate/run/tzdata/release_ets
cp $HOME/teslamate/bin/teslamate-app/lib/tzdata-*/priv/release_ets/*.ets \
$HOME/teslamate/run/tzdata/release_ets/
cp $HOME/teslamate/bin/teslamate-app/lib/tzdata-*/priv/latest_remote_poll.txt \
$HOME/teslamate/run/tzdata/

# 启动时
export TZDATA_DIR=$HOME/teslamate/run/tzdata

TeslaMate:mix release 不会自动跑 migration

Elixir release 完之后必须手工触发

1
$HOME/teslamate/bin/teslamate-app/bin/teslamate eval 'TeslaMate.Release.migrate'

99 个 migration 应该一次跑过。没跑 migration 直接 start 会因为 private.tokens 表不存在崩溃

Grafana:datasource 的两个隐性细节

这是整套部署里最让人默默崩溃的环节——49 个仪表盘全空,但 Grafana 不报错。

细节 1:datasource UID 必须显式设成 TeslaMate

provisioning yaml 不写 uid: 字段时,Grafana 会自动生成一个随机 UID。但 chinese-dashboards 的 49 个仪表盘里每个 panel 都硬编码引用 {"uid": "TeslaMate"}——UID 不匹配则所有查询静默失败,页面所有 panel 都显示「无数据」。

细节 2:database 字段必须放 jsonData

症状:仪表盘所有 panel 红三角告警,hover 弹出:

1
2
You do not currently have a default database configured for this data source.
Postgres requires a default database...

但 yaml 里明明写了 database: teslamate/api/datasources/uid/<x> 返回顶层 database: "teslamate" 看着正确,Grafana 12 的 postgres datasource plugin 不再读这个字段

根因:Grafana 11+ 的 postgres datasource 把 database 配置从顶层移到了 jsonData.database。老 yaml 格式 Grafana 接受但不生效(不报错,silent 失败)。

正确的 datasource yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: 1
deleteDatasources:
- name: TeslaMate
orgId: 1
datasources:
- name: TeslaMate
uid: TeslaMate # ← 必须显式!!!
type: postgres
url: 127.0.0.1:5433
user: teslamate
secureJsonData:
password: "$__env{DATABASE_PASSWORD}"
isDefault: true
jsonData:
database: teslamate # ← 必须在这里!!!
sslmode: disable
postgresVersion: 1800
timescaledb: false

验证:

1
2
3
curl -u "admin:$GRAFANA_PASSWORD" \
"http://127.0.0.1:3000/api/datasources/uid/TeslaMate" \
| jq '{uid, db: .jsonData.database}'

期望 {"uid": "TeslaMate", "db": "teslamate"}。两者必须都不是 null

CyberUI:后端必须装 PostGIS

CyberUI 后端的 GetStatus 用了 ST_Contains / ST_Buffer / ST_SetSRID / ST_MakePoint 算地理围栏匹配。不装 PostGIS 时整个 query 报 type "geometry" does not exist → silent 失败 → 返回的 status JSON 缺 latitude/longitude/insideTemp/outsideTemp/geofence 字段 → 前端 MapCard 显示「无位置信息」。

TeslaMate 本体不依赖 PostGIS,但 CyberUI 后端必须装。

CyberUI:PG 默认时区 PRC,60 分钟窗口失效

部署完一切看着都对,但 CyberUI 主页温度卡显示 --(不是数字)。

挖到的根因:TeslaMate 用 Ecto :utc_datetime_usec 写入 positions.date 列。这个列类型是 timestamp without time zone存的是 UTC 数值但没带 TZ 元信息

CyberUI 后端 stats_repository.go:181 查"最近 60 分钟温度":

1
WHERE date >= (NOW() - INTERVAL '60 minutes')

PG 比较 timestamp without tztimestamptz 时,会用 session TZ 来解释 timestamp。initdb 默认拿系统时区,是 PRC (+08):

  • 数据库存的:04:59:38(UTC 数值)
  • PG 用 PRC 解释成:04:59:38+08,相当于 UTC 20:59:38 昨天
  • NOW()13:11:08+08 = 05:11:08 UTC 今天
  • 比较:04:59:38 < 12:11:08(也就是 60 分钟前)→ 不在窗口,过滤掉

数据真实只滞后 11 分钟,但 PG 当成 8 小时前的。

修法

1
2
ALTER SYSTEM SET timezone = 'UTC';
SELECT pg_reload_conf();

效果持久化在 $PREFIX/var/lib/postgresql/postgresql.auto.conf,重启 PG 也保留。

对 Grafana 的影响:无。Grafana 的 $__timeFilter() 宏用绝对 UTC 时间戳,chinese-dashboards 用 3-arg date_trunc(field, source, timezone) 显式带时区。两者都跟 PG session TZ 无关。

fix_addrs:三层独立的细节

部署完发现 CyberUI 行程列表里所有起点终点都是 Unknown

1
2
3
4
5
Unknown
2026-05-20 12:41
Unknown
12:59
3.9 km / 18m

照理 fix_addrs 应该用腾讯地图把地址补全。挖下去发现卡在三层,少一层都跑不通。

第一层:fix_addrs 默认走 OSM,国内不通

原项目 hipudding/teslamate_fix_addrs 的"补空地址"路径默认走 OpenStreetMap Nominatim——国内不通。我 fork 一份加了腾讯优先分支:KakaWanYifan/teslamate_fix_addrs,配了 -k <腾讯 key> 参数时优先用腾讯,国内能跑通。补丁细节见 fork 的 commit log。

第二层:腾讯位置服务的"总额度 ≠ KEY 额度"

补丁打完,fix_addrs 终于调腾讯了。第一次调用成功返回了一个地址"上海外滩茂悦大酒店",紧接着第二次:

1
{"status": 121, "message": "此key每日调用量已达到上限"}

控制台「我的额度」里这个 KEY 显示今日调用量 0。怀疑过 QPS 限流、KEY 失效、试用 grace 之类。但 3 次直接 curl 间隔 ≥2 秒(远低于 5 RPS),次次 121。

根因:腾讯位置服务额度是两层结构:

  • 账户总额度:个人开发者 = 逆地址解析 6000 次/日 + 5 并发
  • 每个 KEY 单独的额度:默认 = 0,必须手动从总额度里分配

我创建 KEY 后只点了「申请」,没去「账户额度 → 配额分配」分配过,KEY 的可用额度就是 0。状态码 121 在这种情况是真实的——它的描述是「KEY 调用量已达到上限」,但 0 也算"达到上限"。

修法:

  1. 浏览器打开 腾讯位置服务 → 账户额度
  2. 找「逆地址解析/ws/geocoder/v1?location=* 这一行
  3. 右侧点「配额分配」→ 给你的 KEY 填调用量 6000 + 并发 5(或一键分配)
  4. 保存。30 秒内生效

第三层:首次试用 grace 让人误判

腾讯对新申请未分配额度的 KEY 似乎有 1-2 次「试用 grace」额度,用掉就归 0。日志会显示「先成功后超额」的诡异序列,很容易让人以为是 QPS 限流。

验证不是限流:3 次 curl 间隔 ≥2 秒(远低于 5 RPS),看是不是次次都 121。如果是,就不是 QPS 而是上面的配额没分配。

1
2
3
4
5
6
7
8
for i in 1 2 3; do
curl -s "https://apis.map.qq.com/ws/geocoder/v1/?location=31.23,121.47&key=$TENCENT_KEY&coord_type=1" \
| jq .status
sleep 2
done
# 全部 121 = 配额问题
# 第一次 0, 后面 121 = 同样是配额问题(被 grace 蒙了)
# 121 间杂偶尔 120 = QPS 真的限流,再看并发量额度

修完三层后

腾讯配额分配完 30 秒内,fix_addrs 自动捡到那趟 drive:

1
2
3
4
5
6
7
8
INFO - checking empty records...
INFO - processing drive address 1/1 (total remaining: 1, id=1)
INFO - Tencent raw: country=中国, province=上海市, city=上海市,
district=虹口区, street=大名路, recommend=上海外滩茂悦大酒店
INFO - Tencent address added: 上海外滩茂悦大酒店
INFO - Changing drives(id = 1) start address to 上海外滩茂悦大酒店
INFO - Changing drives(id = 1) end address to 上海外滩茂悦大酒店
INFO - saving...

CyberUI 行程页刷新,Unknown 变成「上海外滩茂悦大酒店 → 上海外滩茂悦大酒店」(起点和终点距离 2 米,看着像没动)。

验收

部署完成后应满足:

端口 期望
:4000 TeslaMate 控制台 → 302 → /sign_in(粘 Tesla token 开始记录)
:3000 Grafana → 302 → /login,49 个中文仪表盘可见,数据有
:8080 CyberUI 主页,赛博朋克风格,地图可见,行程地址非 Unknown
:8899/ CyberUI 后端,404(根路径不定义;活着的标志)
:5433 PostgreSQL,loopback only
:1884 Mosquitto MQTT,loopback only

一行命令验证:

1
2
3
4
5
for p in 4000 3000 8080 8899; do
c=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:$p")
echo ":$p -> $c"
done
# 期望: 302 / 302 / 200 / 404

致谢

部署中遇到的所有问题能挖出根因,靠的是 Claude Code(Anthropic 的官方 CLI Agent)一路 SSH 上手机、读日志、读源码、改代码、跑实验。整个部署从干净 Termux 到跑通,耗时约 2 小时,期间产出了三份文档和两个 fork 仓库的补丁。

关于 Claude Code 的安装和使用,可以参考《安装 Claude Code》

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/19913
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板