跳转至

EVCC Lua 脚本手册

最后更新:

适用 EVCC:v2.9.61 按源码核对:2026-08-17 运行时:LuaJ;每个脚本拥有独立线程、全局环境、事件队列和日志缓冲

目录

1. 何时使用 Lua

Lua 适合:

  • 同时监听多个车辆属性或变量;
  • 需要累计、状态机、去抖、重试或持久化状态;
  • 需要 HTTP、JSON、文件存储和车辆动作组合;
  • 希望控制台按钮、悬浮按钮、自动化规则与脚本互相联动;
  • 可视化自动化难以表达的复杂判断。

如果只是“一个触发器 + 几个条件 + 几个动作”,优先使用可视化自动化,维护和排错更简单。Lua 不是绕过 EVCC 权限、车型限制或服务连接状态的通道;它调用的车辆和系统能力仍依赖 EVCC 对应后端。

2. 创建、目录与生命周期

2.1 入口

打开顶部“脚本”页,切到“Lua 脚本”子页,可以:

  • 新建或编辑 .lua 文件;
  • 从剪贴板导入;
  • 选择脚本根目录;
  • 运行、停止或删除脚本;
  • 开启/关闭自启动;
  • 点击“触发”执行已经注册的手动回调;
  • 打开 Web IDE;
  • 进入脚本商店。

2.2 默认目录

首选根目录:

/sdcard/Download/EVCC/scripts/
├── lua/       Lua 脚本
├── frida/     Frida JavaScript
└── _data/     每个 Lua 脚本的私有数据

如果公共目录不可写,会回退到应用自己的外部文件目录。用户在界面选择其他可写根目录后,选择结果会被保存。

脚本 ID 来自文件名,不含扩展名。例如:

lua/music_light.lua -> script.id == "music_light"

文件名写入时会清理成字母、数字、点、下划线和短横线,最长 80 字符。

2.3 一次性脚本与常驻脚本

一次性脚本执行完主代码且没有注册任何事件处理器时自动退出:

log.i("只执行一次")

注册 trigger.*、property.on_change()、var.on_change()、widget.on_press() 或 float_btn.on_press() 后,脚本会进入事件循环并保持运行:

trigger.on_manual(function()
  log.i("收到手动触发")
end)

每个脚本只会有一个运行实例。已停止或失败的实例可以重新运行。当前同时运行上限为 20 个。

2.4 启动和停止

  • --@autostart true 的 Lua 脚本随自动化服务启动;多个自启脚本之间会错开约 200 毫秒;
  • 页面点击停止、script.stop()、自动化服务退出或应用关闭都会请求停止;
  • script.on_stop(fn) 注册清理函数,停止时按注册的相反顺序执行;
  • 停止后最终日志保留约 60 秒,便于 Web IDE 拉取;
  • 单脚本语法错误或运行异常只停止该脚本,不会直接停止其他脚本。

3. 脚本头部元信息

Lua 使用 --@字段 值,只扫描文件前 40 行:

--@name 音乐节奏灯
--@version 1.0.0
--@author EVCC User
--@description 播放时按 BPM 切换变量
--@min_evcc 2.9.61
--@permissions property_write, shell, http
--@autostart false
--@tags music, automation
字段 默认值 用途
name 文件名 页面和日志中的名称
version 0.0.0 脚本版本
author unknown 作者
description 空 功能说明
min_evcc 空 声明最低 EVCC 版本
permissions 空 逗号分隔的能力声明
autostart false 是否自动启动
tags 空 逗号分隔标签

重要边界:

  • permissions 只是供脚本商店和用户审阅的声明,不是运行时权限系统;
  • min_evcc 是元信息,不应替代脚本自身的兼容判断;
  • 第三方脚本即使没有声明 shell 或 property_write,仍应人工检查源码;
  • 不要在元信息或源码中写入 Token、密码、家庭坐标等敏感信息。

4. 返回值与错误处理

多数需要访问后端的 API 使用统一约定:

local value, err = property.read("vhal:VehicleSpeed")
if not value then
  log.e("读取失败", err)
  return
end

成功:

(value, nil)

失败:

(nil, "错误说明")

写入和动作通常返回:

local ok, err = property.write("vhal:SomeProperty", "1")
if not ok then
  log.e(err)
end

property、var、shell、http、json、storage、time.parse 和动作模块的错误会自动进入当前脚本日志;脚本仍可读取 err 自定义处理。同一脚本 5 秒内完全相同的 API 错误会被日志节流。

少数 API 的返回方式不同:

  • log.*、sleep()、toast.show()、trigger.off() 没有结构化成功返回;
  • var.delete()、storage.exists() 返回布尔值;
  • var.list()、storage.list()、automation.list() 直接返回 Lua 表;
  • widget.on_press()、float_btn.on_press() 直接返回处理器 ID 或 nil。

5. 运行环境与安全边界

5.1 可用的 Lua 能力

标准 Lua 的基础语法、math、string、table 等常用库可用。print(...) 会按制表符拼接参数并重定向到 log.i(...)。

os 只保留:

os.time
os.date
os.clock
os.difftime

5.2 被移除的能力

EVCC 显式移除:

io
package
require
loadfile
dofile
debug
os.execute
os.exit
os.remove
os.rename
os.tmpname
os.getenv
os.setlocale

需要命令、HTTP 或文件存储时,使用文档中的 shell、http、storage 模块。

5.3 这不是不可信代码隔离容器

Lua 环境降低了常见误操作风险,但 EVCC 本身拥有 RootService、车辆属性写入和系统动作能力,不能把脚本沙箱视为可以安全运行任意陌生代码的安全边界。

安装第三方脚本前至少检查:

  • 是否调用 shell.exec();
  • 是否写车辆属性、系统设置或系统属性;
  • 是否向外部 URL 上传数据;
  • 是否包含死循环、频繁定时器或高频日志;
  • 是否包含密钥、Token、手机号、VIN 或坐标。

5.4 循环与停止响应

sleep(ms) 每 100 毫秒检查一次停止请求,阻塞等待可较快退出。但当前 LuaJ 没有真正的逐指令中断钩子,纯 Lua 忙循环可能无法及时停止:

-- 不要这样写
while true do
end

需要循环时必须让出执行:

while true do
  -- 少量工作
  sleep(1000)
end

更推荐使用 trigger.on_timer(),让脚本进入事件循环。

6. property:属性读写、枚举和监听

6.1 来源前缀

前缀 别名 数据源
vhal: 省略前缀时默认 VHAL
apvp: — APVP
aidl: — 车机 AIDL 属性源
capi: carapi: CAPI / CarApi
adaptapi: adapt: AdaptAPI
soa: — SOA
settings: setting: Android Settings
sysprop: — Android System Property

6.2 读取

local speed, err = property.read("vhal:VehicleSpeed")
local gear = property.read("apvp:561024570")
local aidl = property.read("aidl:557877498")
local capi = property.read("capi:557877498/0")
local adapt = property.read("adaptapi:772277248")
local brightness = property.read("settings:system/screen_brightness")
local sdk = property.read("sysprop:ro.build.version.sdk")

寻址规则:

  • VHAL 可使用已知名称、十进制 ID 或 十进制ID/区域ID;VHAL 读取当前不接受 0x ID;
  • APVP 可使用信号名称或十进制 ID;
  • AIDL、CAPI、AdaptAPI、SOA 可使用十进制 ID、0x ID、ID/区域ID、名称或当前缓存中的十六进制标识;
  • settings: 必须是 system/key、secure/key 或 global/key;
  • sysprop: 后面直接写属性键。

车辆属性的返回值会去掉显示后缀。例如底层显示:

1 (0x1)
2 (READY)

Lua property.read() 返回:

1
2

因此可以直接 tonumber() 或做等值判断。settings: 和 sysprop: 保持系统返回的原始字符串。

6.3 写入

local ok, err = property.write("vhal:SomeProperty", "1")
local ok2, err2 = property.write("aidl:557877498/0", "42")
local ok3, err3 = property.write("settings:system/screen_brightness", "180")
local ok4, err4 = property.write("sysprop:debug.evcc.demo", "1")

写入依赖对应后端已经连接、属性元数据可用且属性允许写入。请先在属性页确认含义、数据类型和可写性。错误的车辆属性值可能影响车辆功能;不要在不了解属性用途时试写。

6.4 枚举

local all = property.list()
local vhal = property.list("vhal")
local aidl = property.list("aidl")
local capi = property.list("capi")
local adapt = property.list("adaptapi")

返回从下标 1 开始的字符串数组。AIDL、CAPI、AdaptAPI 和 SOA 列表以十进制属性 ID 为主,例如:

aidl:557877498

列表来自当前已连接后端的缓存,不保证后端未连接时仍有内容。

6.5 元数据

local info, err = property.info("aidl:557877498")
if info then
  log.i(info.source, info.id, info.area_id, info.value)
end

车辆属性返回字段:

字段 说明
source vhal / apvp / aidl / capi / adaptapi
id 十进制属性 ID
area_id 区域 ID
name 属性名称
value 规整后的原始值
value_display 底层完整展示值
data_type 数据类型说明
status 当前状态
timestamp 属性时间戳

settings: 和 sysprop: 的 info 只返回 source 和 name。

6.6 监听变化

local handler, err = property.on_change(
  "aidl:557877498/0",
  function(p)
    log.i("新值", p.new, "旧值", p.old)
  end,
  {cooldown_ms = 500}
)

第三个参数可选:

参数 默认 说明
cooldown_ms 0 两次回调的最短间隔
negate false 反转匹配结果;属性事件通常不建议使用

回调 p 字段:

source
name
new
old
new_display
old_display
id          可选
area_id     可选
hex         可选

只写 aidl:<ID> 时匹配该 ID 的任意区域;确实需要区分区域时写 aidl:<ID>/<areaId>。APVP 监听会自动为该脚本添加订阅,脚本停止时自动注销。

相同值的底层重复上报、或界面/脚本主动刷新当前值,不会被当作真实变化重复回调。

7. var:EVCC 变量

7.1 读取与写入

local value, err = var.read("counter")
local ok, writeErr = var.write("counter", "42")

Lua 与自动化、公式、小程序 MQTT 和 AIDL 联动共用变量。值按字符串读写;需要计算时自行转换:

local count = tonumber(var.read("counter")) or 0
var.write("counter", tostring(count + 1))

7.2 内置变量

时间、GPS 和音乐预设变量可直接读取:

var.read("time")
var.read("timestamp")
var.read("gps_available")
var.read("gps_lat")
var.read("bpm")
var.read("music_state")

音乐变量:

名称 值
bpm BPM 字符串;无音乐、未匹配或匹配失败时为 "0",暂停时保留当前歌曲 BPM
music_state playing / paused_or_stopped / none

bpm 和 music_state 是只读变量。对它们调用 var.write() 会返回错误,var.delete() 返回 false。

7.3 列表和删除

for _, name in ipairs(var.list()) do
  log.i(name)
end

local deleted = var.delete("temp")

var.list() 只列出用户创建的自定义变量,不列出时间、GPS、BPM 等预设变量。

7.4 监听

local id, err = var.on_change("music_state", function(p)
  log.i("music_state", p.old, "->", p.new)
end, {cooldown_ms = 200})

回调字段:

name
new
old

第三个参数支持 cooldown_ms 和 negate。一般变量监听不要使用 negate,否则其他变量的变化也可能成为反向匹配结果。

8. trigger:事件触发器

所有注册成功的处理器都会返回 ID。可随时取消:

local id = trigger.on_timer(60000, function() end)
trigger.off(id)

8.1 触发器总表

API 匹配参数 回调 payload
trigger.on_boot(fn) 无 空表
trigger.on_screen(opts, fn) on、cooldown_ms、negate on
trigger.on_app_foreground(pkg, fn) 包名 pkg、foreground=true
trigger.on_app_background(pkg, fn) 包名 pkg、foreground=false
trigger.on_broadcast(action, fn) Intent action action
trigger.on_log(opts, fn) tag、keyword、regex、cooldown_ms、negate tag、keyword、line
trigger.on_ble(opts, fn) preset、button、mfg_id、mfg_data_prefix、冷却/反转 mac、mfg_id、mfg_data、preset、button
trigger.on_bluetooth(opts, fn) on、冷却/反转 on
trigger.on_bt_device(opts, fn) mac、event、冷却/反转 mac、name、connected
trigger.on_hard_key(opts, fn) code、action、冷却/反转 key_code、action
trigger.on_geofence(opts, fn) mode、name、冷却/反转 name、entered、lat、lng
trigger.on_automation(idOrName, fn) 规则 ID 或名称 rule_id、rule_name
trigger.on_timer(ms, fn) 周期间隔,最短 50 ms 无参数
trigger.on_cron("HH:mm", fn) 每天本地时间 无参数
trigger.on_sun(opts, fn[, extraOpts]) 日出/日落与偏移 event、offset、target_ms
trigger.on_manual(fn) 页面、控制台、悬浮窗或自动化手动触发 无参数

8.2 常用示例

trigger.on_screen({on = true, cooldown_ms = 2000}, function(p)
  log.i("亮屏", p.on)
end)

trigger.on_app_foreground("com.example.music", function(p)
  log.i("音乐应用进入前台", p.pkg)
end)

trigger.on_log({tag = "Media", keyword = "ERROR", cooldown_ms = 5000}, function(p)
  log.w(p.line)
end)

trigger.on_log({regex = "speed=([0-9]+)"}, function(p)
  local speed = string.match(p.line, "speed=([0-9]+)")
  log.i("speed", speed or "?")
end)

trigger.on_ble({preset = "rotary_1", button = 3, mfg_id = 10262}, function(p)
  log.i("BLE 按键", p.button, p.mfg_data)
end)

trigger.on_bt_device({mac = "AA:BB:CC:DD:EE:FF", event = "connect"}, function(p)
  log.i("蓝牙设备已连接", p.name)
end)

trigger.on_hard_key({code = 24, action = "pressed"}, function(p)
  log.i("音量加按下", p.key_code)
end)

trigger.on_geofence({name = "家", mode = "enter"}, function(p)
  log.i("进入", p.name, p.lat, p.lng)
end)

8.3 定时、日出日落和手动触发

trigger.on_timer(60000, function()
  log.i("每分钟")
end)

trigger.on_cron("08:30", function()
  tts.speak("早上好")
end)

trigger.on_sun({event = "sunset", offset = -15, cooldown_ms = 60000}, function(p)
  log.i("日落前 15 分钟", p.target_ms)
end)

trigger.on_manual(function()
  notification.show("EVCC", "脚本已手动触发")
end)

on_sun 的 offset 单位为分钟,负数表示提前,正数表示延后;每 30 秒检查一次。无法取得定位时当前实现使用北京坐标作为兜底,因此对地理位置敏感的脚本应先自行检查 gps_available。

也兼容把冷却放在第三个参数:

trigger.on_sun(
  {event = "sunrise", offset = 0},
  function(p) log.i(p.event) end,
  {cooldown_ms = 60000}
)

9. 动作模块

动作模块复用 EVCC 自动化动作执行器。除特别注明外,返回 (true, nil) 或 (nil, err)。

9.1 应用与系统导航

app.launch("com.example.app")
app.launch("com.example.app", 2)      -- 可选 display ID
app.kill("com.example.app")
app.background("com.example.app", true) -- true 时同时回桌面

home.press()
voice_assistant.dismiss()

9.2 媒体和音量

media.play_pause()
media.next()
media.previous()
media.volume(0, 80) -- 音量组 ID、目标值

第一个参数是车机音量组的数字 ID,不是 "media" 等名称。可先在服务页“音量组调整”查看和测试本车音量组。

9.3 播放声音与 TTS

audio.play("/sdcard/Music/notice.mp3", {
  usage = 11,
  output = "usage",
  bus_address = "",
  repeat = false,
  repeat_count = 0
})

tts.speak("注意减速", {
  engine = "",
  voice = "",
  usage = -1,
  output = "usage",
  bus_address = "",
  repeat = true,
  repeat_count = 2
})

tts.stop() 当前没有接通可靠的取消动作,只是兼容占位;脚本不要依赖它停止正在播放的 TTS。

9.4 广播与通知

broadcast.send("com.example.ACTION", {
  mode = "demo",
  count = 3
})

notification.show("EVCC", "任务完成")

广播 extras 会被转换为字符串键值。不要用它传复杂嵌套对象。

9.5 BLE、远程消息和 Webhook

ble.broadcast("AABBCCDD")
remote.send("车辆状态提醒")
webhook.post("https://example.com/hook", "{\"ok\":true}")

ble.broadcast() 只暴露基础十六进制载荷;需要广播间隔、协议或复杂参数时,使用可视化自动化或 BLE 广播台配置。

9.6 自动化互调

local rules = automation.list()
for _, rule in ipairs(rules) do
  log.i(rule.id, rule.name, rule.enabled)
end

automation.trigger("rule_id_or_name")

Lua 触发自动化、自动化再触发 Lua 可能形成递归。automation.trigger() 当前最多允许 5 层调用深度,超出返回错误。设计互调时仍应使用状态变量或冷却避免循环。

9.7 小窗和异屏同显

mini_window.launch("com.example.app")
system_mini_window.launch("com.example.app")
display_mirror.start(2)
display_mirror.stop()

这些动作是否成功取决于车型、目标显示屏和相应服务状态。

10. 通用工具模块

10.1 log 与 print

log.i("信息", 123)
log.w("警告")
log.e("错误")
print("等同 info", true)
log.clear()

日志同时进入当前脚本缓冲和 EVCC 全局日志。每个脚本最多保留 2000 行,超出后丢弃最早内容。高频回调中不要每次都打印。

10.2 sleep

sleep(1000)

单位为毫秒;负数不会等待。长等待按约 100 毫秒切片,以便响应停止请求。

10.3 shell

local out, err = shell.exec("getprop ro.product.model")
local longOut, longErr = shell.exec("some_long_command", 60000)

命令通过 RootService 执行并同步阻塞当前脚本线程。默认按普通命令路径执行;传入超过 30 秒的超时时间会使用长任务路径。不要在高频触发器里重复执行耗时命令,也不要拼接未经验证的外部输入。

10.4 toast

toast.show("短提示")
toast.show("长提示", true)

Toast 依赖 RootService。该 API 没有结构化错误返回,失败会写警告日志。

10.5 http

local resp, err = http.get("https://example.com/status", {
  headers = {Authorization = "Bearer token"},
  timeout_ms = 10000,
  follow_redirects = true
})

local resp2, err2 = http.post(
  "https://example.com/api",
  "a=1&b=2",
  {timeout_ms = 10000}
)

local resp3, err3 = http.request({
  method = "PUT",
  url = "https://example.com/api/item",
  body = "{\"enabled\":true}",
  headers = { ["Content-Type"] = "application/json" },
  timeout_ms = 15000,
  follow_redirects = false
})

响应:

if resp then
  log.i(resp.status, resp.body)
  log.i(resp.headers["Content-Type"] or "")
end

规则:

  • 请求在脚本线程同步执行;
  • 默认连接和读取超时 30 秒,最短 1 秒;
  • 默认跟随重定向;
  • POST/PUT/PATCH/DELETE 有 body 且未指定 Content-Type 时默认为 application/x-www-form-urlencoded;
  • 返回体最多保留前 8 MiB;
  • 非 2xx 状态仍会返回 status 和错误响应体,不会仅因 HTTP 状态码而返回 err。

10.6 json

local text, err = json.encode({name = "EVCC", enabled = true})
local obj, decodeErr = json.decode('{"value":42}')

连续的 1..n 整数键表编码为 JSON 数组,否则编码为对象:

json.encode({"a", "b"})          -- ["a","b"]
json.encode({a = 1, b = 2})       -- {"a":1,"b":2}

10.7 storage

每个脚本拥有独立目录:

<脚本根目录>/_data/<script_id>/
storage.write("state.json", "{\"count\":1}")
local text, err = storage.read("state.json")
local exists = storage.exists("state.json")
local files = storage.list()
local ok, deleteErr = storage.delete("state.json")

允许相对的子目录,例如 cache/state.json。绝对路径、反斜杠、任何包含 .. 的路径和越出脚本数据目录的路径会被拒绝。storage.list() 只列出脚本数据目录第一层的文件或目录名。

10.8 time

local now = time.now()
local text = time.format(now, "yyyy-MM-dd HH:mm:ss")
local ts, err = time.parse("2026-08-17 12:30:00", "yyyy-MM-dd HH:mm:ss")
local today = time.today()

time.today() 返回:

year, month, day, hour, minute, second, weekday

weekday 为周一 1 到周日 7。格式使用 Java SimpleDateFormat,不是 Lua os.date 格式。

10.9 script

log.i(script.id)
log.i(script.name)
log.i(script.version)
log.i(script.author)
log.i(script.path)
log.i(script.started_at)

script.on_stop(function()
  log.i("清理")
end)

-- script.stop()

started_at 是 EVCC 单调时钟毫秒值,适合计算本次运行时长,不是 Unix 时间戳。

11. widget 与 float_btn

监听控制台组件点击:

widget.on_press("widget_id", function(p)
  log.i("组件被点击", p.widget_id)
end)

监听悬浮按钮:

float_btn.on_press("button_id", function(p)
  log.i("悬浮按钮被点击", p.btn_id)
end)

当前只有“监听点击”方向已接通。以下接口仍是占位,不会执行组件原动作:

widget.press("widget_id")
float_btn.press("button_id")

需要从 Lua 主动执行动作时,请直接调用对应动作模块,或使用 automation.trigger()。

12. Web IDE

12.1 打开方式

脚本页点击“Web IDE”,EVCC 会显示二维码和局域网地址:

http://<车机IP>:29999/debug/

Web IDE 当前没有独立登录认证,只应在可信局域网临时开启。不要通过路由器端口映射、frp 或公网隧道暴露 29999 端口。

12.2 主要能力

  • Lua / Frida 编辑模式切换;
  • 选择、加载和保存本地脚本;
  • 运行、停止 Lua;
  • 拉取实时和最终日志;
  • 复制、清空、自动滚动和暂停日志;
  • 为调试运行注入属性和变量 mock。

属性 mock 示例:

vhal:VehicleSpeed = 80
aidl:557877498 = 1

变量 mock 示例:

bpm = 128
music_state = playing

mock 只影响 Web IDE 本次临时 Lua 实例,不写真实车辆属性或全局变量。当前临时运行只支持 Lua;Frida 可以编辑和保存,但不通过同一临时运行接口执行。

13. 完整示例

13.1 BPM 与音乐状态

--@name BPM 状态演示
--@version 1.0.0
--@min_evcc 2.9.61
--@autostart true
--@permissions variables, notification

local function update()
  local bpm = tonumber(var.read("bpm")) or 0
  local state = var.read("music_state") or "none"

  if state == "playing" and bpm >= 120 then
    var.write("music_energy", "high")
  elseif state == "playing" then
    var.write("music_energy", "normal")
  else
    var.write("music_energy", "idle")
  end

  log.i("state=" .. state .. " bpm=" .. bpm)
end

var.on_change("bpm", update, {cooldown_ms = 200})
var.on_change("music_state", update, {cooldown_ms = 200})
update()

13.2 属性阈值提醒

--@name 车速提醒
--@version 1.0.0
--@permissions property_read, tts

local warned = false

property.on_change("vhal:VehicleSpeed", function(p)
  local speed = tonumber(p.new)
  if not speed then return end

  if speed >= 120 and not warned then
    warned = true
    tts.speak("当前车速较高,请注意安全")
  elseif speed < 110 then
    warned = false
  end
end, {cooldown_ms = 300})

实际车速属性的单位和 ID因车型而异,先在属性页确认。

13.3 持久化计数

local raw = storage.read("count.txt")
local count = tonumber(raw) or 0

trigger.on_manual(function()
  count = count + 1
  storage.write("count.txt", tostring(count))
  toast.show("已触发 " .. count .. " 次")
end)

storage.read() 失败时第一个返回值为 nil,这里使用 tonumber(nil) or 0 安全回退。

13.4 HTTP JSON 获取与变量更新

trigger.on_timer(60000, function()
  local resp, err = http.get("https://example.com/evcc.json", {
    timeout_ms = 10000
  })
  if not resp then
    log.w("请求失败", err)
    return
  end
  if resp.status ~= 200 then
    log.w("HTTP", resp.status)
    return
  end

  local body, jsonErr = json.decode(resp.body)
  if not body then
    log.w("JSON 失败", jsonErr)
    return
  end

  if body.mode ~= nil then
    var.write("cloud_mode", tostring(body.mode))
  end
end)

不要在脚本中硬编码长期 Token。可由用户手动写入 EVCC 变量或脚本私有 storage,并避免上传到脚本商店。

13.5 清理处理器

local timerId = trigger.on_timer(10000, function()
  log.i("tick")
end)

trigger.on_manual(function()
  trigger.off(timerId)
  log.i("定时器已取消")
end)

script.on_stop(function()
  log.i("脚本结束")
end)

14. 性能、稳定性与排错

14.1 建议

  • 车辆属性变化可能非常频繁,优先设置 cooldown_ms;
  • 不要在每次属性回调中执行 Shell、HTTP 或大量 JSON;
  • 定时器通常不要低于 500~1000 毫秒,除非确有需要;
  • 需要保存高频状态时先留在 Lua 变量,低频批量写 storage;
  • 先在 Web IDE 使用 mock 验证判断,再连接真实属性;
  • 车辆写入先用只读日志验证触发条件,确认后再加入 property.write();
  • 为互相触发的自动化与脚本设置状态变量和冷却,避免循环。

14.2 脚本一运行就结束

主代码执行完后没有注册处理器。需要常驻时至少注册一个:

trigger.on_manual(function() end)

不要用空的 while true 维持常驻。

14.3 提示后端未连接

例如:

[property.read] aidl backend not connected

检查:

  1. 设置页对应属性源开关是否开启;
  2. 属性页是否显示连接成功;
  3. 脚本是否早于服务连接完成就执行;
  4. 是否应在定时器中稍后重试。

14.4 on_change 不触发

  • 确认来源前缀正确;
  • 确认属性 ID、名称和区域 ID 与事件一致;
  • APVP 会自动订阅,但上游仍需连接;
  • 相同值重复刷新按设计不触发;
  • 检查是否被 cooldown_ms 限制;
  • 不要误用 negate=true。

14.5 停止按钮没有立即生效

常见原因是纯 Lua 忙循环,或正在同步等待 Shell / HTTP。循环加入 sleep(),耗时工作降低频率并设置合理超时。

14.6 中文变成问号

当前引擎按 UTF-8 加载脚本。请确认文件保存为 UTF-8;从其他编辑器复制时避免使用异常编码。

14.7 Web IDE 无法打开

  • 手机/电脑和车机需要在可互访的局域网;
  • 使用页面显示的实际 IP 和端口 29999;
  • 检查热点/路由器是否开启客户端隔离;
  • 不要把服务暴露公网来绕过局域网问题。

15. API 速查

property.read(name)
property.write(name, value)
property.list([source])
property.info(name)
property.on_change(name, fn[, opts])

var.read(name)
var.write(name, value)
var.list()
var.delete(name)
var.on_change(name, fn[, opts])

trigger.on_boot(fn)
trigger.on_screen(opts, fn)
trigger.on_app_foreground(pkg, fn)
trigger.on_app_background(pkg, fn)
trigger.on_broadcast(action, fn)
trigger.on_log(opts, fn)
trigger.on_ble(opts, fn)
trigger.on_bluetooth(opts, fn)
trigger.on_bt_device(opts, fn)
trigger.on_hard_key(opts, fn)
trigger.on_geofence(opts, fn)
trigger.on_automation(idOrName, fn)
trigger.on_timer(ms, fn)
trigger.on_cron("HH:mm", fn)
trigger.on_sun(opts, fn[, extraOpts])
trigger.on_manual(fn)
trigger.off(handlerId)

app.launch(pkg[, displayId])
app.kill(pkg)
app.background(pkg[, toHome])
home.press()
voice_assistant.dismiss()
media.play_pause()
media.next()
media.previous()
media.volume(groupId, value)
audio.play(path[, opts])
tts.speak(text[, opts])
broadcast.send(action[, extras])
notification.show(title, text)
ble.broadcast(hex)
remote.send(text)
webhook.post(url, body)
automation.trigger(idOrName)
automation.list()
mini_window.launch(pkg)
system_mini_window.launch(pkg)
display_mirror.start(displayId)
display_mirror.stop()

log.i(...)
log.w(...)
log.e(...)
log.clear()
sleep(ms)
shell.exec(command[, timeoutMs])
toast.show(text[, long])
http.get(url[, opts])
http.post(url, body[, opts])
http.request(opts)
json.encode(value)
json.decode(text)
storage.read(path)
storage.write(path, content)
storage.delete(path)
storage.list()
storage.exists(path)
time.now()
time.format([timestamp[, pattern]])
time.parse(text[, pattern])
time.today()
script.stop()
script.on_stop(fn)
widget.on_press(id, fn)
float_btn.on_press(id, fn)

16. 源码索引

内容 文件
脚本生命周期 ScriptEngine.kt、ScriptInstance.kt
Lua 环境和模块注册 ScriptRuntime.kt
元信息和目录 ScriptMeta.kt、ScriptRepository.kt
事件 payload ScriptEngineEvent.kt、EventBridge.kt
各 API 实现 scriptengine/modules/
Web IDE scriptengine/debug/

相关文档:

Geely 属性引用

新增前缀 geely_aidl:、geely_capi:。使用完整引用,例如 geely_aidl:signal:0x4061/1、geely_capi:sensor:0x100100/0。AIDL 通道为 signal/pa,CAPI 为 function/sensor。目录名称是通道限定名称,区域跟在 / 后。只给数字 ID 时必须匹配唯一属性。

读取、目录、信息、监听和支持的写入沿用 property 模块现有方法。Sensor 原生只读;写入使用目录声明的类型,字节值显式使用 base64:。显式选源失败不会换来源或提升调用身份。手动读取和首次快照不触发变化事件。