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 分类。500–537 这一段全是 OWM 的编号, // 与 Garmin 原生天气(300–315)完全分开。 function owns(id as Number) as Boolean { return (id >= 500 && id <= 537) || id == 502 || id == 503; } }