Update watch face: modular refactor + OWM/Solar/background + codegen tools

- Split render into FieldTable/Fields/Icons/Layout/Settings modules
- Add Fenix8V3Background, Owm (weather), Solar modules
- Add tools/ generators (gen_fields/gen_icons/gen_themes/gen_preview_svg)
- Tune README/manifest/strings/settings
This commit is contained in:
2026-09-10 07:10:55 +08:00
parent 52346bdd09
commit 4d96ab0912
23 changed files with 1549 additions and 344 deletions

68
source/Owm.mc Normal file
View File

@@ -0,0 +1,68 @@
import Toybox.Lang;
import Toybox.Application;
import Toybox.Math;
import Toybox.Time;
import Toybox.Time.Gregorian;
import Settings;
// ============================================================================
// OpenWeatherMap —— 读取端
// ----------------------------------------------------------------------------
// 本模块**不联网**,只负责把后台服务上次存下来的数据读出来。
// 真正的网络请求在 Fenix8V3Background.mc 里,由系统按定时器唤醒执行。
//
// 为什么要拆成两半表盘进程绝大多数时间根本没在运行onUpdate 又必须够快,
// 不可能在绘制路径里发 HTTP 请求。Connect IQ 给的方案就是后台服务 —— 定时唤醒、
// 请求、把结果通过 Background.exit() 交回前台,前台再写进 Application.Storage。
//
// API key 由用户自己在设置里填(免费档就够)。没填 key 时:
// - enabled() 返回 false后台定时器根本不会注册一次网络都不会发
// - 25 个 OWM 字段全部显示 "--"
//
// 后台服务会把两个接口的响应**压平成一个扁平字典**再存,这样表盘侧永远不用在
// 内存里扛一棵解析好的 JSON 树(手表内存很紧张)。
// ============================================================================
module Owm {
const STORE = "owm"; // 在 Application.Storage 里的键名
const MAX_AGE = 7200; // 超过两小时的数据视为过期(秒)
// 用户填的 API key没填返回空串。
function key() as String {
return Settings.text("OwmKey");
}
// 只有填了 key 才算启用。这个判断同时决定了后台定时器要不要注册,
// 见 Fenix8V3App.scheduleWeather()。
function enabled() as Boolean {
return key().length() > 0;
}
// 取出后台存的整个扁平字典;从没成功拉取过则为 null。
function data() as Dictionary? {
var d = Application.Storage.getValue(STORE);
return (d == null) ? null : (d as Dictionary);
}
// 取单个字段。键名是后台服务定义的短名("temp"/"feels"/"hi1"…),
// 与 Fenix8V3Background.mc 里的 put() 调用一一对应。
function get(field as String) {
var d = data();
if (d == null) { return null; }
return d.get(field);
}
// 数据是否还新鲜。目前 Fields 里没有强制用它 —— 手表离线一段时间后
// 显示稍旧的天气,比直接显示 "--" 更有用;留着给需要的地方调用。
function fresh() as Boolean {
var t = get("dt");
if (t == null) { return false; }
return (Time.now().value() - (t as Number)) < MAX_AGE;
}
// 该字段编号是否属于 OWM 分类。500537 这一段全是 OWM 的编号,
// 与 Garmin 原生天气300315完全分开。
function owns(id as Number) as Boolean {
return (id >= 500 && id <= 537) || id == 502 || id == 503;
}
}