用了半个多月小米手环,感觉不错。睡眠探测和运动统计比我预想中的精确,一次充电用一个月没问题。虽然没有GPS定位或者心率记录功能,不过以这个价位也不能要求太多。

用小米手环的官方应用可以在连接小米手环后显示睡眠和运动情况的统计,并且以柱状图显示:

screenshot.png

但是小米手环没有公开的API,分享功能也只能分享某一天的数据,所以想把小米手环的数据导出来分(装)析(逼)。

刚开始准备用一个小安卓应用读取小米手环的sqlite数据库然后把所需的数据传到服务器上存起来,但是自己没搞过安卓开发,做安卓的同学也表示并不是那么简单,所以换了种很low的方法…… 手动把数据库从手机复制到电脑上,然后传到服务器 Orz

小米手环应用的主要数据在 /data/data/com.xiaomi.hm.health/databases/origin_dbdata_data 表中,其中 summary 字段就是一天的统计,其中的值类似这样:

  1. {
  2. "slp": {
  3. "lt": 331,
  4. "st": 1427737320,
  5. "wk": 9,
  6. "dp": 146,
  7. "ed": 1427766480
  8. },
  9. "v": 5,
  10. "goal": 8000,
  11. "stp": {
  12. "rn": 3,
  13. "cal": 215,
  14. "runDist": 426,
  15. "wk": 84,
  16. "ttl": 6241,
  17. "runCal": 19,
  18. "dis": 4474
  19. }
  20. }

因为 summary 字段的值已经是 json 格式的了,所以用PHP处理很方便:

  1. <?php
  2. date_default_timezone_set("Asia/Shanghai"); //设定时区
  3. $db = new PDO("sqlite:mi.sqlite") or die("connect error");
  4. $sql = "SELECT `date`, `summary` FROM `date_data`";
  5. $res = $db->query($sql);
  6. function trans_time($time)
  7. {
  8. $hours = round($time / 60, 2); //分钟转换成小时
  9. return $hours;
  10. }
  11. foreach ($res as $row) {
  12. // $data = json_decode($row[1]);
  13. // //睡眠统计
  14. // $sleep_start = $data->slp->st; //睡眠开始时间
  15. // $sleep_stop = $data->slp->ed; //睡眠结束时间
  16. // $sleep_light = $data->slp->lt; //浅度睡眠时间
  17. // $sleep_deep = $data->slp->dp; //深度睡眠时间
  18. // $sleep_total_time = $sleep_start - $sleep_stop; //睡眠时长
  19. // //运动统计
  20. // $sport_total_step = $data->stp->ttl; //全天步数
  21. // $sport_total_dist = $data->stp->dis; //全天里程
  22. // $sport_run_dist = $data->stp->runDist; //跑步里程
  23. // $sport_walk_time = $data->stp->wk; //行走时长
  24. // $sport_run_time = $data->stp->rn; //跑步时长
  25. // $total_cal = $data->stp->cal; //总卡路里消耗
  26. // $run_cal = $data->stp->runCal; //跑步卡路里消耗
  27. $detail = json_decode($row[1]);
  28. $detail->date = $row[0]; //加入统计日期
  29. // $detail->slp->st = date("h:i", $detail->slp->st);
  30. // $detail->slp->ed = date("h:i", $detail->slp->ed);
  31. $detail->slp->lt = trans_time($detail->slp->lt);
  32. $detail->slp->dp = trans_time($detail->slp->dp);
  33. $data[] = $detail;
  34. }
  35. $data = json_encode($data);
  36. echo $data;

然后想找个现成的js库画图。先找到 Chart.js ,用法很简单,功能也很简单,但是怎么在Y轴上表示 10:2308:02 这种时间格式让我纠结了很久。

最后又找来找去找到百度的 ECharts,看demo感觉很不错,能展现和自定义的信息也比 Chart.js 多多了。

用ECharts花了张图,顺便温习了下 Jquery(再不写写就要忘光了……):

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>My weekly life</title>
  6. <style type="text/css">
  7. p{font-family: "Consolas"; text-align: center;}
  8. </style>
  9. </head>
  10. <body>
  11. <!-- 为ECharts准备一个具备大小(宽高)的Dom -->
  12. <div id="mainSleep" style="height:400px"></div>
  13. <div id="mainSport" style="height:400px"></div>
  14. <!-- ECharts单文件引入 -->
  15. <script src="http://echarts.baidu.com/build/dist/echarts.js"></script>
  16. <script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
  17. <script type="text/javascript">
  18. // 路径配置
  19. require.config({
  20. paths: {
  21. echarts: 'http://echarts.baidu.com/build/dist'
  22. }
  23. });
  24. // 使用
  25. require(
  26. [
  27. 'echarts',
  28. 'echarts/chart/bar',
  29. 'echarts/chart/line' // 使用柱状图就加载bar模块,按需加载
  30. ],
  31. function (ec) {
  32. var date = new Array(); //日期
  33. var total = new Array(); //总睡眠时间
  34. var dp = new Array(); //深度睡眠时间
  35. var ttl = new Array(); //全天步数
  36. var dis = new Array(); //全天里程
  37. var wk = new Array(); //行走时长
  38. var rn = new Array(); //跑步时长
  39. // 基于准备好的dom,初始化echarts图表
  40. var myChart = ec.init(document.getElementById('mainSleep'));
  41. var myChart2 = ec.init(document.getElementById('mainSport'));
  42. myChart.showLoading({
  43. text: '少女祈祷中…',
  44. effect : 'bubble',
  45. textStyle : {
  46. fontSize : 20
  47. }
  48. });
  49. myChart2.showLoading({
  50. text: '少年装逼中…',
  51. effect : 'whirling',
  52. textStyle : {
  53. fontSize : 20
  54. }
  55. });
  56. var option = {
  57. title : {
  58. text: '最近一周睡眠情况',
  59. subtext: '我要睡懒觉……'
  60. },
  61. tooltip : {
  62. trigger: 'axis'
  63. },
  64. legend: {
  65. data:['总睡眠时间','深度睡眠时间']
  66. },
  67. toolbox: {
  68. show : true,
  69. feature : {
  70. mark : {show: true},
  71. dataView : {show: true, readOnly: false},
  72. magicType : {show: true, type: ['line', 'bar']},
  73. restore : {show: true},
  74. saveAsImage : {show: true}
  75. }
  76. },
  77. calculable : true,
  78. xAxis : [
  79. {
  80. type : 'category',
  81. boundaryGap : false,
  82. data : date
  83. }
  84. ],
  85. yAxis : [
  86. {
  87. type : 'value',
  88. axisLabel : {
  89. formatter: '{value} 小时'
  90. }
  91. }
  92. ],
  93. series : [
  94. {
  95. name:'总睡眠时间',
  96. type:'line',
  97. smooth: true,
  98. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  99. data : total,
  100. markPoint : {
  101. data : [
  102. {type : 'max', name: '最大值'},
  103. {type : 'min', name: '最小值'}
  104. ]
  105. },
  106. markLine : {
  107. data : [
  108. {type : 'average', name: '平均值'}
  109. ]
  110. }
  111. },
  112. {
  113. name:'深度睡眠时间',
  114. type:'line',
  115. smooth: true,
  116. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  117. markPoint : {
  118. data : [
  119. {type : 'max', name: '最大值'},
  120. {type : 'min', name: '最小值'}
  121. ]
  122. },
  123. data : dp,
  124. markLine : {
  125. data : [
  126. {type : 'average', name : '平均值'}
  127. ]
  128. }
  129. }
  130. ]
  131. };
  132. var option2 = {
  133. title : {
  134. text: '最近一周运动情况',
  135. subtext: '宅宅宅……'
  136. },
  137. tooltip : {
  138. trigger: 'axis'
  139. },
  140. legend: {
  141. data:['全天步数','全天里程','行走时长','跑步时长']
  142. },
  143. toolbox: {
  144. show : true,
  145. feature : {
  146. mark : {show: true},
  147. dataView : {show: true, readOnly: false},
  148. magicType : {show: true, type: ['line', 'bar', 'stack']},
  149. restore : {show: true},
  150. saveAsImage : {show: true}
  151. }
  152. },
  153. calculable : true,
  154. xAxis : [
  155. {
  156. type : 'category',
  157. boundaryGap : false,
  158. data : date
  159. }
  160. ],
  161. yAxis : [
  162. {
  163. type : 'value',
  164. axisLabel : {
  165. formatter: '{value}'
  166. }
  167. }
  168. ],
  169. series : [
  170. {
  171. name:'全天步数',
  172. type:'line',
  173. smooth: true,
  174. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  175. data : ttl,
  176. markPoint : {
  177. data : [
  178. {type : 'max', name: '最大值'},
  179. {type : 'min', name: '最小值'}
  180. ]
  181. },
  182. markLine : {
  183. data : [
  184. {type : 'average', name: '平均值'}
  185. ]
  186. }
  187. },
  188. {
  189. name:'全天里程',
  190. type:'line',
  191. smooth: true,
  192. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  193. markPoint : {
  194. data : [
  195. {type : 'max', name: '最大值'},
  196. {type : 'min', name: '最小值'}
  197. ]
  198. },
  199. data : dis,
  200. markLine : {
  201. data : [
  202. {type : 'average', name : '平均值'}
  203. ]
  204. }
  205. },
  206. {
  207. name:'行走时长',
  208. type:'line',
  209. smooth: true,
  210. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  211. markPoint : {
  212. data : [
  213. {type : 'max', name: '最大值'},
  214. {type : 'min', name: '最小值'}
  215. ]
  216. },
  217. data : wk,
  218. markLine : {
  219. data : [
  220. {type : 'average', name : '平均值'}
  221. ]
  222. }
  223. },
  224. {
  225. name:'跑步时长',
  226. type:'line',
  227. smooth: true,
  228. itemStyle: {normal: {areaStyle: {type: 'default'}}},
  229. markPoint : {
  230. data : [
  231. {type : 'max', name: '最大值'},
  232. {type : 'min', name: '最小值'}
  233. ]
  234. },
  235. data : rn,
  236. markLine : {
  237. data : [
  238. {type : 'average', name : '平均值'}
  239. ]
  240. }
  241. }
  242. ]
  243. };
  244. $.ajax({url: "mi.php", dataType: "json", success: function(data) {
  245. $.each(data, function(index, item) {
  246. var len = data.length;
  247. if (index >= len - 7) {
  248. date.push(item.date);
  249. total.push(Math.round((item.slp.lt + item.slp.dp)*100)/100);
  250. dp.push(item.slp.dp);
  251. ttl.push(item.stp.ttl);
  252. dis.push(item.stp.dis);
  253. wk.push(item.stp.wk);
  254. rn.push(item.stp.rn);
  255. };
  256. });
  257. myChart.hideLoading();
  258. myChart2.hideLoading();
  259. myChart.setOption(option);
  260. myChart2.setOption(option2);
  261. },
  262. error: function (errorMsg) {
  263. alert(errorMsg);
  264. }
  265. });
  266. }
  267. );
  268. </script>
  269. <hr>
  270. <p>Data supported by my XiaoMi Bracelet | Cool Statistical chart Generated by ECharts</p>
  271. </body>
  272. </html>

最终效果类似这样

mi.jpg

TODO :

  • 解决Y轴使用 11:30 这种时间的方法
  • 自动上传手环数据