背景
双十一期间,买了块佳明 Instinct 2X,不过应用商店内没有多少有趣的适配App,于是便想自己折腾一下。
因为我买的是国行手表,所以GPS输出的经纬度信号都是偏移后的GCJ02坐标,而不是国际惯用的WGS84;另一方面,手表带有磁传感器和加速度传感器,所以我便有了这样的想法:
- 坐标纠偏,并且可以选择坐标系和显示格式
- 增加一点天文功能,比如晚上出去看到一颗星星,用手表对准它,便能够知道是什么星星;显示太阳月亮的天文信息等
安装
佳明手表上的所有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上,但是验证的时候发现如下的问题:
- 内存限制
- 指令执行长度限制
- 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
对于这个问题,在论坛中已有讨论,基本只能通过修改算法规避;但是对于我这个场景基本是无解的,因为计算日月恒星升落必然涉及大量的计算,如牛顿迭代公式。
Double类型计算结果十分不精准
这个问题是压垮本地化算法这条路的最后一根稻草!
让我们做一个测试: 0.123456789*0.987654321
对于大多数编程语言,Double类型的数据能提供15位小数精度,上面算式的结果应当为:0.1219326311126353
而在monkeyc中,结果如下:
对比
0.1219326311126353
0.121932634037635879
可以看出,monkeyc中的Double只能提供8位小数精度,在天文计算中,这是远远不够的,因为在天文计算中,常常用儒略千年数作为最基本的单位参与计算,而8位小数,相当于千年数中的31秒,误差达到半分钟,可以说已经是非常大了。
更换思路
既然本地化算法行不通,那只能依靠上位机了,佳明的SDK中Communication类提供了makeWebRequest功能,那一切就好说了,可以用服务器搭一个网关,手表将数据传送到服务器上,服务器计算后返回结果,手表再进行展示。
这种方法的缺陷在于,手表必须一直链接手机,且手机处于强网环境才能得知结果,整体链路太长,稳定性不佳。稍微优化一下,可以准备两套尚未程序,一套放在公网上,在强网环境使用,另一套放在手机上,跑在termux中,这样手表只有连接到手机上便能够直接得到数据。
算法
GPS纠偏有现成的算法,且算法比较简单,直接本地使用即可。
太阳和月亮的天文坐标用算法便可以直接算出,稍微复杂一点的便是星星算法,很多手机APP也提供了类似的AR功能,但对于手表来说,思路也是相近的,整体思路如下
- 首先,将手表对准了星星,便能通过磁传感器获取到方位角,通过加速度传感器,获得手表的倾角,也就类似等于星星的高度角。
- 通过GPS,能够获取到观测者的经纬度
- 结合时间与经度,获取到当地恒星时
- 通过如上方位角,高度角,经纬度与恒星时,便可以得知这个方位角与高度角对应的天球座标。
- 在城市中,人眼看到的星星大多都是视星等3等以下的星星,遍历这100多颗星星,按照亮度与天球座标的拟合度排序,再展示给用户