用模块化的方式做快捷指令——以彩云天气 API 为例

2022-10-19

A version of this article appears on Oct. 19, 2022 on SSPAI as a member-only post. Learn more or subscribe

The article is permitted to be self-archived in the version as originally submitted for publication on the author’s personal website under CC BY-NC 4.0 pursuant to § 5.2(b) of the SSPAI Fellowship Contributor Agreement.

尽管快捷指令近年的进化有目共睹,但它至今也没有完全解决制作步骤繁琐的问题。一旦需要编写比较复杂的逻辑,所需步骤就会陡然增多,编写操作也变得格外繁琐。

对此,尽管我们无法改变快捷指令的操作方法,但可以优化编写思路,尽量使快捷指令的结构简洁、清晰,节省制作时间。而一种可以从专业开发中借鉴的思路,就是「模块化」。

具体到快捷指令的语境,「模块化」就是将重要的步骤拆分成独立的快捷指令,然后通过嵌入的方式发挥作用。 看起来,这让制作的个数变多了,但实则可以避免单个快捷指令过于冗长,乱中出错;也可以起到一次制作、多次使用的效果。

那么,怎样制作模块化的快捷指令呢?主要有两个原则:第一,模块的功能应该尽量精炼,「只做一事,但求做好」(DOTADIW);第二,模块的输入输出应当尽量设计得规律化、格式化。只有这样,模块才能足够灵活。

而要在快捷指令中实现这两个目标,最实用的步骤就是「词典」。词典原本是编程语言中一种常见的数据类型,就像它的名字所表明的,适合用来结构化、批量化地传递数据;作为一种「图形化」编程的工具,快捷指令也借用了这种模型。

具体而言,我们可以将模块中的可定制选项「封装」为词典类型的输入,接受其他快捷指令的调用;经过模块内部步骤的处理后,最终再次以词典的形式输出。这样,多个快捷指令就可以用同样的格式调用模块,并获得同样格式的输出,达到一劳多得的目的。

这种思路的一般实现结构如下图所示:

为了说明这种模块化思路的应用,我们考虑如下两个与天气信息相关的需求:

  1. 在合适时机推送高精度的降水预测;
  2. 基于天气数据计算是否适宜特定活动,例如户外高强度运动。

注意到,这两个需求的适用场景、输出结果不同,但都涉及同一个核心步骤,即获取天气数据。因此,它们就很适合用模块化的思路来一并解决。

下面,我们就以这两个需求为例,介绍怎样将彩云天气 API 封装为一个可以复用的模块,并在此基础上制作快捷指令。

彩云天气是国内一家以雷达降水预报见长的天气服务提供商。可惜,彩云自己的 app 设计比较普通,近年力推的 VIP 订阅模式也因权益和定价不匹配而受到一些批评。

与之形成对比的是,彩云天气的 API 服务不仅具有 app 的全部功能,并且对于个人用户非常慷慨:普通预报数据有每天一千次的免费额度;而对于分钟级预报等高级数据,注册时会获得一万次的免费额度,用完后的定价是 8 元每万次。比起一年一百多的订阅,这个价格可以说是便宜大碗了。

彩云天气 API 可以在官方网站注册,登录后前往控制台创建一个 API token,保存备用。

从彩云天气 API 的文档可以看出 1,他们将天气实况、分钟/小时/逐日预报等功能通过不同路径提供,其格式均为:

https://api.caiyunapp.com/<VERSION>/<TOKEN>/<LON>,<LAT>/<ENDPOINT>

其中,VERSION 为 API 版本号(截至写作时为 v2.6),TOKEN 为之前创建的 API token,LONLAT 分别为经纬度,ENDPOINT 为要查询的信息类别。

据此,我们可以设计这样一个「彩云 API 模块」:它接受词典类型的输入,其中包含经纬度和需要获取的信息类型,进而调用 API,将返回的数据传递给调用它的快捷指令。

(你可以下载我做好的模块;注意其中填写的 API token 是官方提供的测试 token,会在很有限次的调用后过期,应尽快换成自己之前创建的 API token 才能正常使用。)

可以看出,这个模块的核心步骤是「获取 URL 内容」,它按照上面介绍的格式调用彩云 API,并将其中需要按需填写的部分留空,在运行时从输入的词典中读取具体数值。

彩云 API 的调用采用最简单的 GET 方法,鉴权方式也足够简单粗暴(把 token 明文塞进 URL),因此这里没有需要进一步设置的地方。


怎样在快捷指令文本框中调用词典数据

快捷指令中,凡是可以输入文本的地方,都可以调用词典数据。方法如下:

  • 如果在 iOS 上操作:首先进入输入状态,从键盘上方的变量栏中选中要调用的词典,将其添加到文本框中。然后再次点击文本框中的词典名称,翻动到最底部,在「获取键值」框中输入要查询的键名。
  • 如果在 macOS 上操作:在文本框中点击右键,选择「插入变量」> 要调用的词典,将其添加到文本框中。然后再次点击文本框中的词典名称,在「获取键值」框中输入要查询的键名。

最后,由于彩云 API 返回的是 JSON 格式数据,这可以直接被快捷指令识别并转化为词典,因此到此直接输出即可。如果日后编写其他不涉及 JSON 的模块,可以像开头提供的模板那样,在最后增加一个词典步骤作为输出。

制作好了彩云 API 的通用模块,就可以在此基础上制作完成具体需求的快捷指令了。

需求一:自动获取分钟级降水预报

下载本例对应的快捷指令

分钟级降水预报是彩云的主打功能,根据文档

  • 查询分钟级预报的路径以 minutely 结尾;
  • 响应中的 result.minutely.probability是一个数组,其中包含四个数值,依次是接下来两小时内每隔半个小时的降水概率;
  • 响应中的 result.forecast_keypoint 是降水预报提示语,也就是官方彩云 app 在当前气温下方显示的那行字。

因此,在彩云 API 模块的基础上制作一个查询分钟级降水预报的快捷指令,主要包含以下三个部分:

前置步骤:用快捷指令获取当前定位。

需要注意的是,快捷指令经常会耗费很长的时间(接近十秒)才能获取到定位;因此可以按照下图方式,直接获取指定地点(「位置」步骤的输入框和地图搜索框一样,支持智能补全)的经纬度,节省运行时间。

调用步骤:创建一个词典,其 lonlat 的值为从定位信息中提取的经纬度,将 type 键的值设为 minutely;然后将这个词典作为输入,调用准备好的彩云 API 模块。

注意这里的词典结构比较简单,所以我们直接用「词典」步骤来构建;如果需要制作层级很深或者内容很多的词典,再这么做就很麻烦了。那种情况下,可以按下图所示,直接用一个「文本」步骤写 JSON 格式的数据,快捷指令也能识别出这是一个词典。

后续步骤:从彩云 API 模块返回的响应中提取上述关键信息,然后拼接成提示语,作为通知内容推送。

这里总体比较简单,唯一需要额外处理的是降水概率:由于返回值是很长的小数,这里用快捷指令的内置计算功能,将其乘以 100 后保留到十分位,以百分比的方式呈现,更为直观。

最后,还需要考虑在什么场景下运行这个快捷指令。受限于快捷指令的功能,它不可能像原生 app 那样一直在后台运行,但其实也没有这个必要。

所谓来得早不如来得巧,至少对我而言,在以下两个场景自动显示预报就已经很实用了:

  • 在上下班之前预报,防止南方地区常见的「意外惊喜」;
  • 在打开自带天气 app 的同时预报,相当于给自带天气增加一个彩云的数据源。

这两个场景恰好都可以通过 iPhone 端快捷指令的「个人自动化」功能识别。方法是,切换到「自动化」标签页,按右上角的加号,然后选择「创建个人自动化」。在选取触发条件界面,对于上述两个场景,分别选择「通勤之前」和「应用」并做相应设置;最后在添加操作界面选择运行上面做好的快捷指令即可。

下图演示了这个快捷指令在打开自带天气 app 的同时运行的效果:

需求二:户外运动前计算暑热指数

下载本例对应的快捷指令

过去这个夏天,遍及全国的炎热应该让不少人记忆犹新。对于喜欢户外运动的朋友来说,怎样在确认安全的情况下开展运动就成了一个课题。

作为一个跑步比较多的人,我自己就从少数派首页文章《如何在夏季坚持跑步》学到了一招:看温度不如看 WBGT(湿黑球温度指数,俗称暑热指数)。相比于气温、湿度、紫外线强度等单一数值,WBGT 能更好地反映中暑风险。

不过,那篇文章引用的资料稍微有些老旧,特别是其中给出的基于温度和湿度估算 WBGT 的表格来自日本气象学会,后来已经更新了多次;新版明确指出只适用于室内环境(因为没有考虑太阳辐射和风速)。

此外遗憾的是,国内目前除了马拉松赛事会临时架设 WBGT 检测设备,气象部门还没有像美国和日本那样提供 WBGT 公共预报服务;能搜到的 WBGT 应用也是都是个人开发,大多奇形怪状,难以入目。

经过一番搜索,我找到了中央气象台《气象》期刊过去的一篇文章《室外热环境指标的简化计算和应用研究》。

这篇文章基于回归分析,给出了一种「基于常规气象要素的简化算法」2

WBGT (℃) = 1.0031 × 温度 (℃) + 0.021 × 相对湿度 (%) − 0.083 × 风速 (m/s) + 0.002 × 短波辐射 (W/m) − 2.822

巧的是,这个公式用到的几个变量,彩云 API 的「实况」接口都能直接提供。根据文档

  • 查询天气实况的接口以 realtime 结尾;
  • 返回值中,result.realtime 数组下,temperaturehumiditywind.speeddswrf 就是计算上述公式值需要的数据。其中,只有风速的单位(km/h)和公式不一致需要换算,其他都可以直接使用。

因此,在彩云 API 模块的基础上制作一个查询当前 WBGT 的快捷指令,主要包含以下三个部分:

前置步骤:用快捷指令获取当前(或指定地点)定位。这与上一个例子完全相同。

调用步骤:创建一个词典,其 lonlat 的值为从定位信息中提取的经纬度,将 type 键的值设为 realtime;然后将这个词典作为输入,调用准备好的彩云 API 模块。

后续步骤:首先,从彩云 API 模块返回的响应中提取上述气象数据,然后用「计算表达式」步骤计算公式值。

接着,根据得出的 WBGT 温度,通过推送通知显示相应的提示语。

做好以后,下次出门运动前运行一次这个快捷指令,就可以看到实时的 WBGT 估测值和据此做出的运动提示。

如果读者有兴趣,还可以考虑利用彩云 API 的小时级别预报接口(端点 URL 以 /hourly 结尾),计算接下来最多 48 小时内每个小时的 WBGT 值,从而回答「今明两天什么时间段最适宜运动」的问题。有了本文介绍的方法和实例作为基础,相信这不难实现。


  1. 准确说不是看出而是猜出,这个文档的完成度很难让人相信不是加班气头上写的。 ↩︎

  2. 当然这仍然是一个高度简化的模型,只能作为一般参考。 ↩︎