项目分享|基于ElfBoard的远程监测系统

ElfBoard的“自创一派”共创社由19名来自各大高校的共创官组成,在不到一个月的时间里已经建立起浓厚的学习氛围,在这里每位共创官跨越不同的学科背景,交融思想、共享资源,迅速提升自身在嵌入式技术领域的专业素养。

值得一提的是,社群内部已经涌现出许多富有创意的产品设计理念与技术解决方案,今天就跟大家分享一名共创官完成的项目报告“基于ElfBoard的远程监测系统”。

一、项目介绍

1.1 项目目标

基于i.MX6ULL构建一个功能强大的远程检测系统。系统能够自动采集各种传感器数据,包括温度、湿度、电压等,并实时上传至云端服务器,并且能够根据采集到的传感器数据对设备进行自动化控制,如设置电压阈值,当采集到的电压大于阈值时,开启LED1。

在用户端,实现对采集到的传感器数据进行处理、分析和可视化,便于用户远程监控和管理,还可以实现对设备的远程控制。集成高清摄像头,将采集到的视频数据传输至客户端,实现对设备的远程实时监控。

1.2 项目硬件

  1. ElfBoard ELF 1 开发板

  2. WiFi(RTL8723DU)

  3. USB免驱摄像头

  4. Linux服务器

1.3 软件环境

  1. 阿里云物联网平台

  2. Nginx

  3. Python

  4. Flask

二、项目方案

2.1 远程监控

采用RTMP协议,设备端使用FFmpeg采集摄像头数据并推流至云端,云端使用Nginx提供Web服务,并使用nginx-http-flv-module提供RTMP服务,用户端采用Web界面,并使用flv.js进行拉流播放。

2.2 数据检测与设备控制

传感器数据传输以及设备的远程控制通过阿里云物联网平台,采用MQTT协议。

三、数据检测与设备控制

MQTT云平台配置

参考 ElfBoard学习(九):MQTT

传感器数据采集与上传

基于Linux SDK中的data_model_basic_demo.c进行修改。

温湿度数据采集

#define AHT20_DEV "/dev/aht20"int get_aht20(float* ath20_data){        int fd;        unsigned int databuf[2];        int c1,t1;         float hum,temp;        int ret = 0;         fd = open(AHT20_DEV, O_RDWR);        if(fd < 0) {                printf("can't open file %s\r\n", AHT20_DEV);                return -1;        }         ret = read(fd, databuf, sizeof(databuf));        if(ret == 0) {                                    c1 = databuf[0]*1000/1024/1024;              t1 = databuf[1] *200*10/1024/1024-500;            hum = (float)c1/10.0;            temp = (float)t1/10.0;
            printf("hum = %0.2f temp = %0.2f \r\n",hum,temp);        *ath20_data = hum;        *(ath20_data+1) = temp;        }
        close(fd);    return 0;}

电压数据采集​​​​​​​

#define voltage5_raw "/sys/bus/iio/devices/iio:device0/in_voltage5_raw"#define voltage_scale "/sys/bus/iio/devices/iio:device0/in_voltage_scale"float get_adc(void){        int raw_fd, scale_fd;        char buff[20];        int raw;        double scale;
        /* 1.打开文件 */        raw_fd = open(voltage5_raw, O_RDONLY);        if(raw_fd < 0){                printf("open raw_fd failed!\n");                return -1;        }        scale_fd = open(voltage_scale, O_RDONLY);        if(scale_fd < 0){                printf("open scale_fd failed!\n");                return -1;        }
        /* 2.读取文件 */        // rewind(raw_fd);   // 将光标移回文件开头        read(raw_fd, buff, sizeof(buff));        raw = atoi(buff);        memset(buff, 0, sizeof(buff));        // rewind(scale_fd);   // 将光标移回文件开头        read(scale_fd, buff, sizeof(buff));        scale = atof(buff);        printf("ADC原始值:%d,电压值:%.3fV\r\n", raw, raw * scale / 1000.f);        close(raw_fd);        close(scale_fd);        return raw * scale / 1000.f;}

LED状态采集与控制​​​​​​​

#define LED1_BRIGHTNESS "/sys/class/leds/led1/brightness"#define LED2_BRIGHTNESS "/sys/class/leds/led2/brightness"int get_led(int led_sel){    int led;    char buff[20];    int state=0;    if(led_sel == 2)    {        led=open(LED2_BRIGHTNESS, O_RDWR);    }else{        led=open(LED1_BRIGHTNESS, O_RDWR);    }    if(led<0)    {        perror("open device led error");        exit(1);    }
        read(led, buff, sizeof(buff));        state = atoi(buff);
    close(led);    return state;}
void set_led(int led_sel, char state){    int led;    if(led_sel == 2)    {        led=open(LED2_BRIGHTNESS, O_RDWR);    }else{        led=open(LED1_BRIGHTNESS, O_RDWR);    }    if(led<0)    {        perror("open device led error");        exit(1);    }
    write(led, &state, 1);//0->48,1->49    close(led);}

自动化控制

当ADC采集的电压大于阈值2.5V时自动开启LED1,低于时自动关闭LED1。​​​​​​​

        if(adc>2.5){            set_led(1,'1');        }else{            set_led(1,'0');        }

数据上传

在main函数的while(1)中​​​​​​​

        adc=get_adc();        get_aht20(ath20_data);        led1_state = get_led(1);        led2_state = get_led(2)>0?1:0;
        demo_send_property_post(dm_handle, "{\"temperature\": 21.1}");        sprintf(data_str,"{\"Voltage\": %.3f}", adc);        demo_send_property_post(dm_handle, data_str);
        memset(data_str, 0, sizeof(data_str));        sprintf(data_str,"{\"Humidity\": %.3f}", ath20_data[0]);        demo_send_property_post(dm_handle, data_str);
        memset(data_str, 0, sizeof(data_str));        sprintf(data_str,"{\"temperature\": %.3f}", ath20_data[1]);        demo_send_property_post(dm_handle, data_str);
        memset(data_str, 0, sizeof(data_str));        sprintf(data_str,"{\"LEDSwitch\": %d}", led1_state);        demo_send_property_post(dm_handle, data_str);
        memset(data_str, 0, sizeof(data_str));        sprintf(data_str,"{\"LEDSwitch2\": %d}", led2_state);        demo_send_property_post(dm_handle, data_str);

云端指令响应

由于云端传输的数据为JSON格式,因此需要使用cJSON进行解析。

添加cJSON

在components文件夹下添加cJSON相关文件

图片

修改Makefile

图片

在74行和78行后面要添加-lm,否则在编译的时候会报错。

实现代码​​​​​​​
static void demo_dm_recv_property_set(void *dm_handle, const aiot_dm_recv_t *recv, void *userdata){    int led;    char state=0;    printf("demo_dm_recv_property_set msg_id = %ld, params = %.*s\r\n",           (unsigned long)recv->data.property_set.msg_id,           recv->data.property_set.params_len,           recv->data.property_set.params);
    /* TODO: 以下代码演示如何对来自云平台的属性设置指令进行应答, 用户可取消注释查看演示效果 */    cJSON* cjson_result = NULL;    cJSON* cjson_set1 = NULL;    cJSON* cjson_set2 = NULL;
    cjson_result = cJSON_Parse(recv->data.property_set.params);    if(cjson_result == NULL)    {        printf("parse fail.\n");        return;    }    //{"LEDSwitch":0}        cjson_set1 = cJSON_GetObjectItem(cjson_result,"LEDSwitch");    if(cjson_set1)    {        printf("LED1 set %d\n",cjson_set1->valueint);        state = cjson_set1->valueint+48;                led=open(LED1_BRIGHTNESS, O_WRONLY);        if(led<0)        {            perror("open device led1");            exit(1);        }        write(led, &state, 1);//0->48,1->49        close(led);    }        cjson_set2 = cJSON_GetObjectItem(cjson_result,"LEDSwitch2");    if(cjson_set2){        printf("LED2 set %d\n",cjson_set2->valueint);        state = cjson_set2->valueint+48;
        led=open(LED2_BRIGHTNESS, O_WRONLY);        if(led<0)        {            perror("open device led1");            exit(1);        }        write(led, &state, 1);//0->48,1->49        close(led);       }                //释放内存        cJSON_Delete(cjson_result);
    {        aiot_dm_msg_t msg;
        memset(&msg, 0, sizeof(aiot_dm_msg_t));        msg.type = AIOT_DMMSG_PROPERTY_SET_REPLY;        msg.data.property_set_reply.msg_id = recv->data.property_set.msg_id;        msg.data.property_set_reply.code = 200;        msg.data.property_set_reply.data = "{}";        int32_t res = aiot_dm_send(dm_handle, &msg);        if (res < 0) {            printf("aiot_dm_send failed\r\n");        }    }    }

四、视频监控

RTMP服务器搭建

云端服务器使用Nginx,但Nginx本身并不支持RTMP,需要使用相关的插件使其支持RTMP。此外由于网页端播放RTMP流需要Flash插件的支持,而目前Flash插件许多浏览器已不再支持,因此需要使用支持 HTTPS-FLV的nginx-http-flv-module,并通过flv.js实现RTMP流的播放。

这里首先需要下载Nginx和nginx-http-flv-module的源码,并采用编译的方式安装Nginx,具体步骤如下:​​​​​​​

./configure --add-module=/usr/local/nginx/nginx-http-flv-modulemake&&make install

安装完成后,需要进入Nginx安装目录(默认为/usr/local/nginx/),并在conf文件夹下对nginx.conf文件进行修改,增加rtmp功能(注意需要打开服务器的1935端口):​​​​​​​

worker_processes  1;#worker_processes  auto;
#worker_cpu_affinity  0001 0010 0100 1000;#worker_cpu_affinity  auto;
error_log logs/error.log error;
events {    worker_connections  4096;}
http {    include       mime.types;    default_type  application/octet-stream;
    keepalive_timeout  65;
    server {        listen       80;
        location / {            root   html;            index  index.html;        }
        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }
        location /live {            flv_live on; #打开 HTTP 播放 FLV 直播流功能            chunked_transfer_encoding on; #支持 'Transfer-Encoding: chunked' 方式回复
            add_header 'Access-Control-Allow-Origin' '*'; #添加额外的 HTTP 头            add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的 HTTP 头        }
        location /hls {            types {                application/vnd.apple.mpegurl m3u8;                video/mp2t ts;            }
            root /tmp;            add_header 'Cache-Control' 'no-cache';        }
        location /dash {            root /tmp;            add_header 'Cache-Control' 'no-cache';        }
        location /stat {            rtmp_stat all;            rtmp_stat_stylesheet stat.xsl;        }
        location /stat.xsl {            root /var/www/rtmp;        }
        location /control {            rtmp_control all;        }    }}
rtmp_auto_push on;rtmp_auto_push_reconnect 1s;rtmp_socket_dir /tmp;
rtmp {    out_queue           4096;    out_cork            8;    max_streams         128;    timeout             1s;    drop_idle_publisher 1s;
    log_interval 5s;    log_size     1m;
    server {        listen 1935;        server_name xxx.xxx.xx; #填入你自己的域名
        application myapp {            live on;            gop_cache on;        }
        application hls {            live on;            hls on;            hls_path /tmp/hls;        }
        application dash {            live on;            dash on;            dash_path /tmp/dash;        }    }}

最后启动Nginx服务,即可完成RTMP服务器的搭建:​​​​​​​

cd /usr/local/nginx/sbin./nginx

本地推流

FFmpeg的编译配置参考:

摄像头采用的是USB免驱摄像头,将摄像头插入ElfBoard的USB口即可正常识别及工作,设备节点为/dev/video2。

之后可以使用v4l2-ctl工具查看并配置摄像头信息

最后使用命令就能够实现推流:

ffmpeg -f video4linux2 -r 5 -s 320x240 -i /dev/video2 -c:v libx264 -preset ultrafast -tune zerolatency -r 5 -f flv rtmp://xxx.xxxxxx.xxx/live/test

五、用户端设计

框架

使用Python编程,采用Web界面,并通过Flask提供Web服务以及后端数据处理能力。可以部署在云端,也可以在本地运行。界面如下所示:

图片

视频拉流

Web用户端的视频拉流通过flv.js实现,首先需要在html文件中导入flv.js:

<script src="https://cdn.bootcss.com/flv.js/1.5.0/flv.js"></script>

之后设计Web页面播放器,具体代码如下:​​​​​​​

<div class="row mt-10">    <div class="col-lg-8 mx-auto">        <video id="videoElement" class="img-fluid" controls autoplay width="1024" height="576" muted>            Your browser is too old which doesn't support HTML5 video.        </video>    </div>    <!-- /column --></div><br><div class="d-flex justify-content-center">    <!--<button οnclick="flv_load()">加载</button>-->    <button onclick="flv_start()">开始</button>    <button onclick="flv_pause()">停止</button></div>​​​​​
<script type="text/javascript">    var player = document.getElementById('videoElement');    if (flvjs.isSupported()) {        var flvPlayer = flvjs.createPlayer({            type: 'flv',            url: 'http://xxx.xxxxx.xx/live?port=1935&app=myapp&stream=test',            "isLive": true,            hasAudio: false,            hasVideo: true,            //withCredentials: false,            //cors: true        }, {            enableWorker: true,            enableStashBuffer: false,            lazyLoad: false,            lazyLoadMaxDuration: 0,            lazyLoadRecoverDuration: 0,            deferLoadAfterSourceOpen: false,            fixAudioTimestampGap: true,            autoCleanupSourceBuffer: true,        });        flvPlayer.attachMediaElement(videoElement);        flvPlayer.load(); //加载        flv_start();    }    function flv_start() {        player.play();    }
    function flv_pause() {        player.pause();    }</script>

远程数据的读取与指令下发

这一部分通过后端Python编程实现,并提供相应的Web接口。前后端的交互通过ajax请求实现。​​​​​​​

class Sample:    def __init__(self):        pass
    @staticmethod    def create_client(            access_key_id: str,            access_key_secret: str,    ) -> OpenApiClient:        """        使用AK&SK初始化账号Client        @param access_key_id:        @param access_key_secret:        @return: Client        @throws Exception        """        config = open_api_models.Config(            # 必填,您的 AccessKey ID,            access_key_id=access_key_id,            # 必填,您的 AccessKey Secret,            access_key_secret=access_key_secret        )        # Endpoint 请参考 https://api.aliyun.com/product/Iot        config.endpoint = f'iot.cn-shanghai.aliyuncs.com'        return OpenApiClient(config)
    @staticmethod    def create_set_info() -> open_api_models.Params:        """        API 相关        @param path: params        @return: OpenApi.Params        """        params = open_api_models.Params(            # 接口名称,            action='SetDeviceProperty',            # 接口版本,            version='2018-01-20',            # 接口协议,            protocol='HTTPS',            # 接口 HTTP 方法,            method='POST',            auth_type='AK',            style='RPC',            # 接口 PATH,            pathname=f'/',            # 接口请求体内容格式,            req_body_type='formData',            # 接口响应体内容格式,            body_type='json'        )        return params
    @staticmethod    def create_get_info() -> open_api_models.Params:        """        API 相关        @param path: params        @return: OpenApi.Params        """        params = open_api_models.Params(            # 接口名称,            action='QueryDeviceOriginalPropertyStatus',            # 接口版本,            version='2018-01-20',            # 接口协议,            protocol='HTTPS',            # 接口 HTTP 方法,            method='POST',            auth_type='AK',            style='RPC',            # 接口 PATH,            pathname=f'/',            # 接口请求体内容格式,            req_body_type='formData',            # 接口响应体内容格式,            body_type='json'        )        return params
    @staticmethod    def main():        client = Sample.create_client(access_key_id, access_key_secret)        params = Sample.create_get_info()        # query params        queries = {}        queries['PageSize'] = 10        queries['ProductKey'] = 'xxxxxxxxxx'        queries['DeviceName'] = 'xxxx'        queries['Asc'] = 0        # body params        body = {}        body['ApiProduct'] = None        body['ApiRevision'] = None        # runtime options        runtime = util_models.RuntimeOptions()        request = open_api_models.OpenApiRequest(            query=OpenApiUtilClient.query(queries),            body=body        )        # 复制代码运行请自行打印 API 的返回值        # 返回值为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。        response = client.call_api(params, request, runtime)        body = response['body']        Data = body['Data']        List = Data['List']        Proper = List['PropertyStatusDataInfo']        Temp = json.loads(Proper[0]['Value'])        Volt = json.loads(Proper[1]['Value'])        Led2 = json.loads(Proper[2]['Value'])        Led1 = json.loads(Proper[3]['Value'])        Humi = json.loads(Proper[4]['Value'])        message = {            'humi': Humi['data'],            'temp': Temp['data'],            'volt': Volt['data'],            'led1': Led1['data'],            'led2': Led2['data'],        }        return jsonify(message)
    @staticmethod    def main_set(item: str):        client = Sample.create_client(access_key_id, access_key_secret)        params = Sample.create_set_info()        # query params        queries = {}        queries['ProductKey'] = 'xxxxxxxxxxxx'        queries['DeviceName'] = 'xxxx'        queries['Items'] = item  # '{"LEDSwitch":0}'        # body params        body = {}        body['ApiProduct'] = None        body['ApiRevision'] = None        # runtime options        runtime = util_models.RuntimeOptions()        request = open_api_models.OpenApiRequest(            query=OpenApiUtilClient.query(queries),            body=body        )        # 复制代码运行请自行打印 API 的返回值        # 返回值为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。        resp = client.call_api(params, request, runtime)        body = resp['body']        data = body['Success']        return str(data)
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值