目的
MCU 采集到的数据能够直接渲染出来,不需要与前端、后端进行对接,而是直接渲染,效果图如下
效果
准备
- 单片机一块,AT89C52RC 即可
- WIFi 通讯模块,Esp8266
- 数据存储,InfluxDB(建议 Docker 版本)
- 数据渲染,Grafana(建议 Docker 版本)
链路
链路如下,其中整个服务端仅需要部署 InfluxDB + Grafana 即可,无需任何代码,相关参考文档
注:InfluxDB 默认提供了 HTTP RESTful API,HTTP 协议 就是 TCP 的文字,所以直接通过 TCP 就能上报 HTTP 数据
相关细节
Esp8266 操作比较简单,连接上单片机后基本就是对串口的操作,网上也有很多资料这里仅列举几项关键说明
-
需要修改的宏
// WiFi ssid 和 password #define WIFI_CMD_CONNECT_ROUTER "AT+CWJAP_DEF="SSID","PASSWORD"rn" // InfluxDB 所在的 TCP 服务,默认是 8086端口 #define WIFI_CMD_TCP_SERVER "AT+CIPSTART="TCP","192.168.50.11",8086rn" // InfluxDB HTTP 请求里面的 Host 字段 #define WIFI_HTTP_INFLUXDB_HOST "192.168.50.11:8086"
-
默认波特率为 115200,仅能使用定时器 2 才支持,初始化如下:
/** * 通过定时器 2 来初始化 esp8266,波特率 115200 */ void wifi_init_timer2() { SCON = 0x50; TH2 = 0xFF; TL2 = 0xFD; RCAP2H = 0xFF; RCAP2L = 0xFD; TCLK = 1; RCLK = 1; C_T2 = 0; EXEN2 = 0; TR2 = 1; ES = 1; }
-
InfluxDB 的写入格式如下:
可以先用串口往 PC 上面调试,PC 复制转发 TCP 给 InfluxDB 查看效果
POST /write?db=telegraf HTTP/1.1 Host: 192.168.50.11:8086 Content-Length: 42 embedded,mcu=c51,name=temperature value=10
-
代码里面是通过按键模拟事件上传的随机数
/** * 模拟采集,并上传数据 */ void mock_data_post(u8 event_type, u8 key_code) { WifiPost xdata mock_post; char xdata mock_value_str[4]; char xdata mock_name_str[5] = "btn_"; int xdata mock_value = 0; mock_name_str[4] = '0' + key_code; // 0-key_code*100 的随机数 // random_seeds 调度器每扫描一次就 +1 mock_value = random_seeds % ((key_code + 1) * 100); sprintf(mock_value_str, "%d", mock_value); mock_post.host = WIFI_HTTP_INFLUXDB_HOST; mock_post.path = WIFI_HTTP_INFLUXDB_WRITE_PATH; mock_post.content_type = NULL_STR; mock_post.body = NULL_STR; mock_post.influx_db_measure_name = mock_name_str; mock_post.influx_db_measure_value = mock_value_str; wifi_exec_post(&mock_post); }
-
经测试,并不是每次都能读到 Esp8266 响应的 OK,所以用亮灯警告,比如连接 TCP 服务器的时候,用 0 号 Led 告警,告警并不代表没连上
/** * 连接指定的服务器,会自动进入透传模式 * @param server 服务器地址 */ bool wifi_connect_tcp_server(char code *server) { bool is_ok = false; is_ok = wifi_exec_cmd(server, WIFI_RESP_OK, 11000); if (!is_ok) { // wifi 连接 tcp 服务器失败 led_light_one(0); } // 进入透传模式 is_ok &= wifi_enter_auto_trans(); return is_ok; } /** * wifi 执行命令 * @param cmd AT 命令 * @param resp_ok AT 命令响应 ok 的字符串 * @param delay_ms 执行命令等待多久延迟才获取响应数据 */ bool wifi_exec_cmd(char *cmd, char *resp_ok, u16 delay_ms) { bool is_ok = false; wifi_response_clear(); wifi_send_cmd(cmd); soft_delay_ms(delay_ms); is_ok = string_contains(wifi_response.bytes, resp_ok); wifi_response_clear(); return is_ok; }
-
Esp8266 模块的初始化,调用一次即可,通过 led 来标识步骤异常
/** * wifi 首次初始化配置,仅需调用一次 */ void wifi_first_init() { // 1. 测试响应 if (!wifi_exec_cmd(WIFI_CMD_TEST, WIFI_RESP_OK, 2000)) { led_light_one(0); return; } // 2. 设置工作模式 STA+AP if (!wifi_exec_cmd(WIFI_CMD_MODE_STA, WIFI_RESP_OK, 2000)) { led_light_one(3); return; } // 3. 连接路由器 if (!wifi_exec_cmd(WIFI_CMD_CONNECT_ROUTER, WIFI_RESP_OK, 10000)) { led_light_one(5); return; } // 4. 设置自动连接路由器 if (!wifi_exec_cmd(WIFI_CMD_AUTO_CONN, WIFI_RESP_OK, 2000)) { led_light_one(7); return; } // 5. 配置成功点亮所有 LED P1 = 0x00; }
-
HTTP POST 结构体
/** * Post 请求结构体 */ typedef xdata struct { /** * 请求的服务器地址,比如 192.168.50.11:8086 */ char *host; /** * 请求路由 */ char *path; /** * body 内容,body 和 influx_xxx 二选一,优先 body */ char *body; /** * 往 influxdb 写入的 name */ char *influx_db_measure_name; /** * 往 influxDB 写入的 value */ char *influx_db_measure_value; /** * text/plain application/json 等等 */ char *content_type; } WifiPost;
-
构建 HTTP POST 请求 的相关逻辑如下
/** * 透传模式下发送数据 */ void wifi_auto_trans(char *package) { wifi_send_cmd(package); } /** * 操作 influx post api * * POST /write?db=mcu HTTP/1.1 * Host: 192.168.50.11:8086 * Content-Type:text/plain * Content-Length:59 * * weather,mcu=c51,measure_name=temperature,region=hz value=10 * * @param post 请求结构体 */ void wifi_exec_post(void xdata *param) { // 1. 优先取 post->body WifiPost xdata *post = (WifiPost *)param; bool body_clear = false; char *body = post->body; // sprintf 仅支持 int int content_len = 0; char content_len_str[3]; //2. 其次取 influxdb 写入 if (string_len(body) == 0) { body = wifi_influx_write_body(post->influx_db_measure_name, post->influx_db_measure_value); body_clear = true; } //3. 得出最终的 content_len content_len = string_len(body); sprintf(content_len_str, "%d", content_len); //拼接写入字符串 wifi_auto_trans("POST "); wifi_auto_trans(post->path); wifi_auto_trans(" HTTP/1.1rn"); wifi_auto_trans("Host: "); wifi_auto_trans(post->host); wifi_auto_trans("rn"); // 可以忽略 content_type if (string_len(post->content_type) != 0) { wifi_auto_trans("Content-Type: "); wifi_auto_trans(post->content_type); wifi_auto_trans("rn"); } wifi_auto_trans("Content-Length: "); wifi_auto_trans(content_len_str); wifi_auto_trans("rnrn"); wifi_auto_trans(body); if (body_clear) { free(body); } } /** * 获取 influx 写入的 body */ char *wifi_influx_write_body(char *measure_name, char *value) { char xdata body[WIFI_HTTP_BODY_MAX_LENGTH] = "embedded,mcu=c51,name="; string_append(body, measure_name); string_append(body, " value="); string_append(body, value); return body; }
能直接采集数据就很棒了!