RYMCU

一个漂亮的数据采集+渲染解决方案

ychost 2 年前
# 采集 # MCU # InfluxDB # Grafana

目的

MCU 采集到的数据能够直接渲染出来,不需要与前端、后端进行对接,而是直接渲染,效果图如下

效果

image.png

准备

  1. 单片机一块,AT89C52RC 即可
  2. WIFi 通讯模块,Esp8266
  3. 数据存储,InfluxDB(建议 Docker 版本)
  4. 数据渲染,Grafana(建议 Docker 版本)

链路

链路如下,其中整个服务端仅需要部署 InfluxDB + Grafana 即可,无需任何代码,相关参考文档

注:InfluxDB 默认提供了 HTTP RESTful API,HTTP 协议 就是 TCP 的文字,所以直接通过 TCP 就能上报 HTTP 数据

image.png

相关细节

Esp8266 操作比较简单,连接上单片机后基本就是对串口的操作,网上也有很多资料这里仅列举几项关键说明

  1. 需要修改的宏

    // 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"
    
  2. 默认波特率为 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;
    }
    
  3. 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
    
  4. 代码里面是通过按键模拟事件上传的随机数

    /**
     * 模拟采集,并上传数据
     */
    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);
    }
    
  5. 经测试,并不是每次都能读到 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;
    }
    
  6. 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;
    }
    
  7. 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;
    
  8. 构建 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;
    }
    

源码

仓库

核心代码

  1. https://github.com/ychost/HybSCH/blob/master/Sources/Esp8266Wifi.c
  2. https://github.com/ychost/HybSCH/blob/master/Sources/Main.c
后发布评论

能直接采集数据就很棒了!

wow....👍