东京奥运会可以说是奥运会历史上最特殊的一届,推迟一年、没有观众、环保奥运等。8 O* S, g( x* G! B ]+ g: j
& b; M& }0 j9 G
. X- f* u. A: }/ D5 U, n+ r为了能实时关注奖牌榜,有开发者周末在家使用STM32+ESP8266做了一个东京奥运会奖牌榜桌面小摆件,最终效果如下:
# L. u, w1 @2 s& A▲ 桌面效果1 ▲ 桌面效果2 ▲ 桌面效果3
+ o" V% W- ?, u5 i* M! _) @+ S' H1 M( ?" @) U0 Y0 O. V. v
原理是通过STM32驱动ESP8266连接网络,然后GET接口,读取到接口返回的JSON数据,然后进行JSON解析,LCD显示,使用不同的接口就是不同的功能。$ Q* l9 P4 [& j: I( u
) x' Q# W( ^- P* S b' ]$ z, z8 C6 U' u) X* J* i
以上两个API接口返回的数据都非常简短,只有几百字节,而奖牌榜接口返回的数据有20KB之多,使用STM32不能一次处理这么多的数据量,本文采用了一种简单的方法来进行数据截取,从而减少数据量。
+ T* s, Y, f: o: X' g
3 _6 A. t. {; E
8 j( e$ {5 M% l1 a4 p& y+ d6 J都有哪些内容?
+ P! d I$ P: f( r( ?! `7 b* \' m, q9 T& a& e i$ h* w8 u+ C" s
% G8 ?9 g1 `$ o# G7 S; KAPI接口获取
6 g* Q3 @+ ~& N9 EJSON数据预处理
" s8 m* o1 G* ]0 r, J3 HJSON数据解析
! b! D- j3 ~$ q* w显示效果% ?( E" _: N, M" J s
开源地址
8 J1 I# H5 l) G1 ~! z) b2 @( `; G
* g3 \- k! \' S7 h9 d* p, i
API接口获取 1 C# S0 U0 ~6 B( V
在进行开发之前,首先要找到一个API接口,请求方式最好是GET,返回数据格式JSON格式的。先上网搜一下,发现已经有很多网友实现奖牌榜数据获取的功能,使用的大多是:Python、Java、PHP,使用的语言无所谓,重要的是API接口是否是我们想要的,最终找到了两个API接口。
8 h/ ^; Q" M7 b第一个接口是央视网官方的东京奥运专题页面:1 ~& U0 ^ }- N8 Z
▲ 图:2020.cctv.com 网址如下:# B" n. }/ b* _
http://2020.cctv.com/medal_list/index.shtml
7 M8 v* o6 z' _! i9 ZF12打开开发者模式,可以找到页面请求的API地址:
, z5 B9 Z+ m. H" Z! J7 a% u6 f▲ cntv_api 这个接口返回的数据量有11KB,格式化后有24KB,JSON数据格式(部分):
% i3 T2 V/ I \- E, e( E+ g; I▲ cntv_json 可以看到数据比较齐全,包括排名,金、银、铜、合计奖牌总数,国家ID,国家名称为UTF-16BE编码方式,而开发板上的字库为GBK编码,这一点不好处理。看看还有没有其他的接口。
( g4 s2 c. {* Q, b3 g. @第二个接口是我的小米手机负一屏界面呈现的奥运奖牌榜,类似于这种:
6 ^; E) g1 f; o, o. E5 B6 S _▲ 手机界面 可以跳转到浏览器打开:
8 v5 C; Y( W( a- }http://act.e.mi.com/olympic/index.html
. l- H* S6 b8 n4 X5 Y3 H5 E( ?F12打开开发者模式,可以找到请求的API地址:
( {, `* ~: A5 [) O. e▲ mi_api 这个接口返回的数据有21KB,格式化后有33KB,JSON数据格式(部分):
& ~; m7 V, s, R/ x▲ mi_json 这个接口返回数据比较丰富,同样数据量相比于央视网的要大一些,除了奖牌数量排名等信息,还包括每个国家的国旗图片地址,更新时间等。国家名称为UTF-8编码,要在我们的开发板上进行使用需要进行UTF8-GBK的转换。
$ O- w+ \1 ^9 a: @7 C综上,我们就获取到了两个API接口的信息:
2 ?' j4 d6 v# n9 h$ z8 o) Y奥运奖牌榜央视API接口:
1 j& a; M1 j0 m ]' ]: w( ~: r; e- TYPE: "TCP"
% V, @- B `1 h# r# e. O - PORT: "80"" _9 S2 s& [# b U; Q4 |; t
- IP : "111.206.176.78"% G/ s% G2 y5 {3 y
- API : "http://api.cntv.cn/olympic/getOlyMedals?serviceId=pcocean&itemcode=GEN-------------------------------"
复制代码 奥运奖牌榜小米手机API接口# I/ i1 d) x# z& Y
- TYPE: "TCP"" Z) l. A3 E) \) A$ ]8 @! t" i
- PORT: "80"4 y6 F+ g* A6 u7 \' ~7 u O6 d
- IP : "111.206.101.253"
$ K9 Q9 f- c! ] - API : "http://act.e.mi.com/olympic/medal_rank"
复制代码 为了方便在开发板上直接显示国家名称,我们采用小米手机的API接口。
, _5 e9 U- |, h3 c. N& D2 P! }5 w2 _1 l2 k# c% @
, v" ], ~$ }6 ^- J u2 M
JSON 数据预处理
& k |9 x1 [( N H- _4 b& q1 L通过分析API接口返回的数据,共包括前90名的奖牌数据,数据长度为21KB:
. I; v! d7 \9 I0 n% b, w/ W▲ mi_json 但是我们只需要前10名的数据即可。串口接口缓存长度设置为2500个字节,即返回的21KB字节数据,只接收前2500个字节,然后处理成JSON标准格式就可以了。
! a1 F! j; L1 I6 N6 E- p处理方法为,倒序查找最后一个}号的位置,将{之后的数据丢弃掉,然后添加上]},这样就将截取的2500字节数据修改为了标准JSON格式数据:
* y" `5 a' ]4 X& p▲ mi_json_deal 1 B7 `4 T d; }7 R P
JSON数据解析 0 g1 f0 ?/ h+ w _
从上图也可以看出,JSON格式比较简单,使用cJSON可以很方便的进行解析。
/ [9 s+ T& s; O& f% n: D
+ E& J8 w7 l, w: b3 \+ Y* a
8 k: ?6 r; R+ S, u) H$ _1 L定义一个结构体:9 s* ]1 g8 e% D: ~ C% t
- typedef struct medal{
9 c& q; Y B: A+ Y. _9 q - char rank[5];. W, z4 z# ~0 M0 H1 ?1 }! ^8 R
- char countryname[50];
8 N% H6 R( O$ ~) ^8 I& {+ D - char count[5];) Y& A# F/ N" t; K2 r0 d& C
- char gold[5];
9 r% h4 j% b6 ~( h8 w6 v: A - char silver[5];
0 J9 U) p' c$ M0 b$ i9 E - char bronze[5];
# |: M; h3 z1 w& r; N9 `8 ]1 I - char update_time[50];
% `. n, J' X9 i4 R- J, G - char countryid[10];$ `% K4 H$ e& O' m5 w. p x
- }medalObj;
复制代码 解析函数,只读取前7名的奖牌数据:8 e% ~: {, ~6 [1 g2 X) y$ ^
- uint8_t parse_mi_data(void)
0 _5 `6 T; g# B4 E; H' y( ` - {
6 t i4 r* O" R% l, d* R9 C - cJSON *root, *data_obj, *list_obj;) o9 D3 D- g! f$ L
- char *str;
0 O* T& k/ I& S/ v0 F, b, _ - char dest[USART2_MAX_RECV_LEN];, e- M$ L5 D& i
- char *loc;
3 j K. G# t4 m - char gbk[50];
+ U* _- P* K t) [2 U - char utf8[50];
, V1 E& d" k! n( |5 |8 b2 I
8 F% q' S4 ? h! |6 d* _- medalObj *pobj;
0 n! i" L! }8 y$ k9 S - medalObj obj;
; U* @( e% w% l3 z b9 `" t - int idx;/ i9 L; f2 X; D- W1 t: k' u
- - C+ o6 K- x( M0 g2 m
- pobj = &obj;$ _* L" g4 O; B2 s* R5 M8 I: v9 ?
+ D* D5 r+ _% N- str = (char *)USART2_RX_BUF;
/ Y+ ?4 G; W- [8 P - memset(dest, '\0', USART2_MAX_RECV_LEN);9 k' x! l' {9 E% q0 X; E
- loc = strrchr(str, '}');
% l; T- R+ j/ a$ t/ B# d+ @ - strncpy(dest, str, loc-str+1);
- ?/ i5 _/ ^( Q X( \; m2 P6 G - strcat(dest, "]}");
. J4 y A# A* b$ } - printf("json data size: %d bytes\r\n", strlen(dest));
% I2 R0 j; @/ Q/ V - : I. ^/ Q. Z8 M- _8 U
- root = cJSON_Parse((const char*)dest);+ T g* [1 `3 z2 B% V* d# ]+ v! q
- & f& a5 ~1 l0 K1 N* R
- if(root != 0)$ v9 H6 b$ m- o& { u+ S4 _6 V
- {) k4 C7 z- r: E8 w
- printf("JSON format ok, start parse!!!\r\n");: J+ t! X3 s$ y2 l9 P! y
- data_obj = cJSON_GetObjectItem(root, "data");0 O% j' A/ B! q
- if(data_obj->type == cJSON_Array)
* i, ?( g9 {: ^/ F - {
* B7 n% K: L' C, G4 b/ H - int size = cJSON_GetArraySize(data_obj);
) \+ T$ X& x9 W: A1 o1 Z8 ]8 P, ^ - for(idx = 0; idx < size; idx++)
0 C, i& |( K. d4 k - {
: a7 g8 i, a, X, U6 w+ {1 t - if(size >= 7 && idx <= 7)! @: a7 P7 }7 Z- i
- {4 E! D$ j: ^: A- X
- list_obj = cJSON_GetArrayItem(data_obj, idx);
* m! N+ Y" Q) m) C
9 [; m2 C, F" ?8 y: d- strcpy(obj.bronze, cJSON_GetObjectItem(list_obj, "medal_bronze_count")->valuestring);% H" {4 W4 {+ q6 |4 m9 `
- strcpy(obj.rank, cJSON_GetObjectItem(list_obj, "rank")->valuestring);
" {7 v' O/ P7 b1 b" \# I9 } - strcpy(obj.count, cJSON_GetObjectItem(list_obj, "medal_sum_count")->valuestring);" x' O5 {7 w2 i+ _" q7 c
- strcpy(obj.silver, cJSON_GetObjectItem(list_obj, "medal_silver_count")->valuestring);
! x5 a6 k1 w4 J( ^# @ - //utf8->gbk
" W) H/ O+ W% X$ n - memset(utf8, '\0', sizeof(utf8));
7 I0 \! W0 [9 U- P$ C+ x( I* j - memset(gbk, '\0', sizeof(gbk));
( H. j( a2 P3 o( @* { - strcpy(utf8, cJSON_GetObjectItem(list_obj, "country_name")->valuestring);, R- s. Y7 O$ a6 V* B' O% I" ~
- SwitchToGbk(utf8, gbk);
9 M1 F4 u! R5 E t( H - % I3 s( _. Y* E, s
- strcpy(obj.countryname, gbk);
+ L5 T9 Y' t8 e# t w6 q4 J
' R% [4 H( p. U6 V7 _' ^. W- strcpy(obj.gold, cJSON_GetObjectItem(list_obj, "medal_gold_count")->valuestring);
$ X: s( [3 [5 B3 Y5 X" a - strcpy(obj.update_time, cJSON_GetObjectItem(list_obj, "update_time")->valuestring);- M9 X+ p4 p) W# O* l/ k; B6 [
- printf("%s: %s %10s %s:%s-%s-%s\r\n",
C) H1 [3 J5 [9 M5 j7 Q3 K - pobj->rank, pobj->update_time, pobj->countryname,
/ i- x8 @; U/ w7 \ - pobj->count, pobj->gold, pobj->silver, pobj->bronze);
7 q7 R: n: P9 d" m - gui_show_data(48+idx*20, pobj);: Y# L1 M: A/ J9 w1 R
- }% W. ~9 E5 e6 q n# Q4 W
- }6 ~5 v: W7 h' [
- Show_Str_Mid(200, 225, (u8 *)pobj->update_time, 12, 120);$ |1 n# ?9 O! b5 ~* [
- } 9 l. Y# T" U+ {: H9 x" R; C0 a
- }
& z( A) N6 ^- O" p$ L - else& D: G1 f9 v& i0 y$ q
- {
* h, _+ z5 \% m- m9 f" e$ `% T - printf("JSON format error:%s\r\n", cJSON_GetErrorPtr()); //输出json格式错误信息
9 S: I4 D% v: F - }
) w9 t7 w7 q+ J$ d5 p8 h - # H U5 W/ x3 A* x+ q
- USART2_RX_STA = 0;0 u# F) b# K- b( C& ^, U3 d" ?0 H
- memset(USART2_RX_BUF, 0, sizeof(USART2_RX_BUF));& ?# r* I4 z" u
: |2 V+ ^# s& O- cJSON_Delete(root);& N" M4 N% ^) n \2 A
- # s4 q$ L5 _4 w$ [, X
- return 0;+ J3 F0 s4 g8 s
- }
复制代码 如果出现解析失败的情况,可能需要调整启动文件中的堆栈大小:
) D- W/ Z$ v Q g. Z4 P7 z- //startup_stm32f10x_hd.s
' v1 s1 K8 } I1 }+ Y R - ' r0 Y/ q) l7 N" ?( Z
- Stack_Size EQU 0x00000C00
q& G7 r! T( M# w- u2 m; R
I& q( i: V* C8 A. l2 I+ e- Heap_Size EQU 0x00000200, B9 H0 L+ L S
复制代码最终效果 ▲ BMP显示效果 ▲ 桌面效果4 ▲ 桌面效果5 和手机端数据完全一致:, F, ^ _- o; M, L7 x3 O
▲ 手机界面 ! x9 \( v* {+ |" x1 @/ _; O# i) j
) D" Q$ f& K1 |& C1 J
开源地址
' G( d& T; k( c# L7 R% @我的开发板已经预先写入了中文字库,没有字库的开发板可能不适用。+ @* h5 e o$ X3 T+ z3 C
全部代码已经开源在Gitee码云:
' t/ P2 p5 H+ @https://gitee.com/whik/stm32_olympic_medals
$ F' k/ t2 n2 U, S7 P; W5 C. W0 ]3 ], C" j- Y
总结 * o2 @8 @2 L2 g. F+ F9 h2 k. |. t
北京时间2021年8月8日晚,17天的2020东京奥运会落下帷幕。本届奥运会闭幕式理念为“Worlds we share”(我们共享的世界),蕴含“铭记与不同性格、不同文化背景的人们共享的感动,携手共创未来”之意,同时也包含了人类共同抵抗新冠疫情的团结之意,虽然东京奥运会已和观众说再见,但即将到来的2022北京冬奥会将持续点燃广大体育迷的热情。
( ` t, S" E( Q; P T( _$ Z) `" e8 Q% W/ w% j+ I# n0 k H
$ H& w: `# n3 I; G/ l! q文章出处:张巧龙
4 u2 L2 ]) U! v5 {$ ?. a2 l& @2 N% C q# k5 a3 U
|