背景

双十一期间,买了块佳明 Instinct 2X,不过应用商店内没有多少有趣的适配App,于是便想自己折腾一下。
因为我买的是国行手表,所以GPS输出的经纬度信号都是偏移后的GCJ02坐标,而不是国际惯用的WGS84;另一方面,手表带有磁传感器和加速度传感器,所以我便有了这样的想法:

  1. 坐标纠偏,并且可以选择坐标系和显示格式
  2. 增加一点天文功能,比如晚上出去看到一颗星星,用手表对准它,便能够知道是什么星星;显示太阳月亮的天文信息等

安装

佳明手表上的所有App都使用MonkeyC这个弱类型的语言来编写,按照官方指引下载SDK,再在vs code上安装对应的Monkyec插件,简单配置下JDK即可,不过调试的时候与模拟器的通信老是失败,最后发现还是那个老生常谈的问题:开了HyperV导致的,重启下winnat服务就能解决这个问题。

Hello World

新建一个空Project,插件会在source文件夹中默认初始化四个文件:App.mc Delegate.mc MenuDelegate.mc与view.mc;这四个文件的作用分别如下

App.mc  #程序入口文件,在getInitialView函数中初始化view和对应的Delegate
Delegate.mc #用于响应用户按键或触屏指令,默认生成的文件只带onMenu()函数,代表用户长按菜单键的动作,也可以自行添加其他函数来响应其他按键的动作:如onSelect() onPreviousPage()等
MenuDelegate.mc #用户响应用户对菜单的指令,和Delegate.mc类似,只不过是菜单的专属响应class
View.mc #用于页面布局

踩坑

搞清楚基础的编写逻辑后,便可以着手设计应用逻辑,本想着完整重写github.com/Starainrt/astro我的这个package到monkeyc上,但是验证的时候发现如下的问题:

  1. 内存限制
  2. 指令执行长度限制
  3. Double类型计算结果十分不精准

内存限制

智能手表其实也算个嵌入式硬件,与PC不同,这些设备的内存都比较小,稍有不注意就stackOverflow,对于佳明手表,每个型号的手表配置在: {SDK Folder}/Devices/{型号}/compiler.json 中定义,对于我的本能2X,相应的限制如下:

 "appTypes": [
        {
            "memoryLimit": 32768,
            "type": "background"
        },
        {
            "memoryLimit": 32768,
            "type": "datafield"
        },
        {
            "memoryLimit": 32768,
            "type": "glance"
        },
        {
            "memoryLimit": 98304,
            "type": "watchApp"
        },
        {
            "memoryLimit": 65536,
            "type": "watchFace"
        },
        {
            "memoryLimit": 65536,
            "type": "widget"
        }

可以看到,即便是watchAPP,最大也只给到了90多Kb大小的内存,而对于一个完整的VSOP87算法,每个星体的迭代项都高达至少400多项,以double类型存储的话,每个星体至少也需要500K以上的内存。

针对这个限制,可行的解决思路是降低迭代精度,当我们把精度从完整的0.01角秒增大到1角秒时,所需的迭代系数只需要几百项就够了,这大大低于内存限制值。而且对于手表,1角秒的精度也是完全够用的。

指令长度限制

但即便减少了精度,每次根据时间算出黄经黄炜也是需要上百次迭代计算的。但是对于佳明手表,每个流程所包含的代码执行长度也是有限制的,如下图,我想在启动时执行10次计算太阳高度的函数,前8此都能正常返回,但是第9次便报错: Code Executed Too Long

屏幕截图 2023-12-31 150858.png

对于这个问题,在论坛中已有讨论,基本只能通过修改算法规避;但是对于我这个场景基本是无解的,因为计算日月恒星升落必然涉及大量的计算,如牛顿迭代公式。

Double类型计算结果十分不精准

这个问题是压垮本地化算法这条路的最后一根稻草!
让我们做一个测试: 0.123456789*0.987654321
对于大多数编程语言,Double类型的数据能提供15位小数精度,上面算式的结果应当为:0.1219326311126353
而在monkeyc中,结果如下:

屏幕截图 2023-12-31 152322.png

对比
0.1219326311126353
0.121932634037635879

可以看出,monkeyc中的Double只能提供8位小数精度,在天文计算中,这是远远不够的,因为在天文计算中,常常用儒略千年数作为最基本的单位参与计算,而8位小数,相当于千年数中的31秒,误差达到半分钟,可以说已经是非常大了。

更换思路

既然本地化算法行不通,那只能依靠上位机了,佳明的SDK中Communication类提供了makeWebRequest功能,那一切就好说了,可以用服务器搭一个网关,手表将数据传送到服务器上,服务器计算后返回结果,手表再进行展示。

这种方法的缺陷在于,手表必须一直链接手机,且手机处于强网环境才能得知结果,整体链路太长,稳定性不佳。稍微优化一下,可以准备两套尚未程序,一套放在公网上,在强网环境使用,另一套放在手机上,跑在termux中,这样手表只有连接到手机上便能够直接得到数据。

算法

GPS纠偏有现成的算法,且算法比较简单,直接本地使用即可。

太阳和月亮的天文坐标用算法便可以直接算出,稍微复杂一点的便是星星算法,很多手机APP也提供了类似的AR功能,但对于手表来说,思路也是相近的,整体思路如下

  1. 首先,将手表对准了星星,便能通过磁传感器获取到方位角,通过加速度传感器,获得手表的倾角,也就类似等于星星的高度角。
  2. 通过GPS,能够获取到观测者的经纬度
  3. 结合时间与经度,获取到当地恒星时
  4. 通过如上方位角,高度角,经纬度与恒星时,便可以得知这个方位角与高度角对应的天球座标。
  5. 在城市中,人眼看到的星星大多都是视星等3等以下的星星,遍历这100多颗星星,按照亮度与天球座标的拟合度排序,再展示给用户

效果:

屏幕截图 2023-12-31 154151.png

屏幕截图 2023-12-31 154230.png

屏幕截图 2023-12-31 154510.png