接入系统 Complication,补齐倒计时/自定义格式/28天累计/空气质量
字段 125 → 156 项。
Toybox.Complications(source/Comp.mc)
这条路推翻了我之前几个「表盘做不到」的判断 —— 当时只查了 ActivityMonitor /
UserProfile / Weather,漏了系统把这些作为 complication 发布出来:
- 下一个日程(CIQ 确实没有日历 API,但有 CALENDAR_EVENTS complication)
- 训练状态、完赛预测 ×4、配速预测 ×4、最近高尔夫得分
- 本周跑步 / 骑行距离(按运动分类的,按天历史做不出来)
- 第三方 App 1–5(CGM、补水、行情这类 App 发布的)
需要 ComplicationSubscriber 权限。
其余补齐
- 近 28 天累计:步数 / 卡路里 / 距离 / 楼层
- 事件倒计时:设置里填 YYYY-MM-DD,显示还剩几天
- 自定义时间格式、日期格式 1/2:单字母占位符(h/H/m/s/a/D/M/N/Y/y/w)
- OWM 空气质量:AQI 等级 + 描述 + PM2.5 + PM10 + CO,走
/data/2.5/air_pollution —— 那个接口在免费档就开放,不需要 One Call 订阅
运行时踩的坑
- Complication.unit 不保证是 String(周跑步距离返回的就不是),直接调
.length() 抛 Symbol Not Found。unit 和 shortLabel 都改成先 instanceof 判型。
- 浮点 value 直接 toString 印成 "0.000000",统一格式化成一位小数。
- 第三方 App 槽位每取一次就要走完整个迭代器,四个格子都选 App 时会走四遍。
改成每帧只枚举一次,由 view 在绘制开始时调 Comp.beginFrame() 失效。
测试
全字段验证的 harness 本身会触发看门狗(一次 onUpdate 里算 156 个字段),
改成分批每帧 12 个。实际使用一帧只算 4 个,不受影响。
156 项全部可渲染,零运行时错误;17 款设备全部编译通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,10 @@ import Toybox.Position;
|
||||
// HTTP 请求。Connect IQ 的方案是**后台服务**:系统按定时器唤醒一个独立进程,
|
||||
// 在里面发请求,把结果通过 Background.exit() 交回前台,前台再写进 Storage。
|
||||
//
|
||||
// 每次唤醒发两个请求:
|
||||
// 每次唤醒发三个请求:
|
||||
// 1. 当前天气 /data/2.5/weather
|
||||
// 2. 短期预报 /data/2.5/forecast?cnt=16(约 48 小时,够算明日/后日高低温)
|
||||
// 3. 空气质量 /data/2.5/air_pollution(AQI / PM2.5 / PM10 / CO)
|
||||
//
|
||||
// ⚠️ 预报接口必须带 cnt 限制条数。后台进程的内存预算非常小(约 32KB),
|
||||
// 完整的 5 天 / 3 小时预报解析出来能有几十 KB,会直接爆掉。
|
||||
@@ -49,7 +50,7 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
||||
return;
|
||||
}
|
||||
mResult = {};
|
||||
mPending = 2;
|
||||
mPending = 3; // 当前天气 + 短预报 + 空气质量
|
||||
// 一律用 units=metric 请求,单位换算统一放到 Fields 里按用户设置做,
|
||||
// 这样切换单位不用重新联网。
|
||||
var common = {"lat" => lat, "lon" => lon,
|
||||
@@ -66,6 +67,12 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
||||
Communications.makeWebRequest(
|
||||
"https://api.openweathermap.org/data/2.5/forecast",
|
||||
fc, opts, method(:onForecast));
|
||||
// 空气质量走独立接口。它在免费档里是开放的(One Call 才要订阅),
|
||||
// 所以 AQI / PM2.5 / PM10 / CO 这几项不用付费也能有。
|
||||
Communications.makeWebRequest(
|
||||
"https://api.openweathermap.org/data/2.5/air_pollution",
|
||||
{"lat" => lat, "lon" => lon, "appid" => Owm.key()},
|
||||
opts, method(:onAir));
|
||||
}
|
||||
|
||||
// 当前天气的回调。OWM 的 JSON 是嵌套的,逐层取值时每层都要判空 ——
|
||||
@@ -143,6 +150,25 @@ class Fenix8V3Background extends System.ServiceDelegate {
|
||||
done();
|
||||
}
|
||||
|
||||
// 空气质量的回调。响应形如 { list: [ { main: {aqi}, components: {...} } ] }。
|
||||
function onAir(code as Number, body as Dictionary?) as Void {
|
||||
if (code == 200 && body != null) {
|
||||
var list = body.get("list") as Array?;
|
||||
if (list != null && list.size() > 0) {
|
||||
var e = list[0] as Dictionary;
|
||||
var main = e.get("main") as Dictionary?;
|
||||
if (main != null) { put("aqi", main.get("aqi")); }
|
||||
var comp = e.get("components") as Dictionary?;
|
||||
if (comp != null) {
|
||||
put("pm2_5", comp.get("pm2_5"));
|
||||
put("pm10", comp.get("pm10"));
|
||||
put("co", comp.get("co"));
|
||||
}
|
||||
}
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
// 距 Unix 纪元的整天数,用来给预报条目分桶。
|
||||
function dayNumber(unix as Number) as Number {
|
||||
return unix / 86400;
|
||||
|
||||
Reference in New Issue
Block a user