加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 一、項目介紹
    • 二、項目方案
    • 三、數(shù)據(jù)檢測與設(shè)備控制
    • 四、視頻監(jiān)控
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

基于ElfBoard的遠程監(jiān)測系統(tǒng)

01/22 14:10
1994
閱讀需 37 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論
ElfBoard的“自創(chuàng)一派”共創(chuàng)社由19名來自各大高校的共創(chuàng)官組成,在不到一個月的時間里已經(jīng)建立起濃厚的學(xué)習(xí)氛圍,在這里每位共創(chuàng)官跨越不同的學(xué)科背景,交融思想、共享資源,迅速提升自身在嵌入式技術(shù)領(lǐng)域的專業(yè)素養(yǎng)。值得一提的是,社群內(nèi)部已經(jīng)涌現(xiàn)出許多富有創(chuàng)意的產(chǎn)品設(shè)計理念與技術(shù)解決方案,今天就跟大家分享一名共創(chuàng)官完成的項目報告“基于ElfBoard的遠程監(jiān)測系統(tǒng)”。

一、項目介紹

1.1 項目目標

基于i.MX6ULL構(gòu)建一個功能強大的遠程檢測系統(tǒng)。系統(tǒng)能夠自動采集各種傳感器數(shù)據(jù),包括溫度、濕度、電壓等,并實時上傳至云端服務(wù)器,并且能夠根據(jù)采集到的傳感器數(shù)據(jù)對設(shè)備進行自動化控制,如設(shè)置電壓閾值,當采集到的電壓大于閾值時,開啟LED1。
在用戶端,實現(xiàn)對采集到的傳感器數(shù)據(jù)進行處理、分析和可視化,便于用戶遠程監(jiān)控和管理,還可以實現(xiàn)對設(shè)備的遠程控制。集成高清攝像頭,將采集到的視頻數(shù)據(jù)傳輸至客戶端,實現(xiàn)對設(shè)備的遠程實時監(jiān)控。

1.2 項目硬件

  1. ElfBoard ELF 1 開發(fā)板
  2. WiFi(RTL8723DU)
  3. USB免驅(qū)攝像頭
  4. Linux服務(wù)器

1.3 軟件環(huán)境

  1. 阿里云物聯(lián)網(wǎng)平臺
  2. Nginx
  3. Python
  4. Flask

二、項目方案

2.1 遠程監(jiān)控采用RTMP協(xié)議,設(shè)備端使用FFmpeg采集攝像頭數(shù)據(jù)并推流至云端,云端使用Nginx提供Web服務(wù),并使用nginx-http-flv-module提供RTMP服務(wù),用戶端采用Web界面,并使用flv.js進行拉流播放。

2.2 數(shù)據(jù)檢測與設(shè)備控制

傳感器數(shù)據(jù)傳輸以及設(shè)備的遠程控制通過阿里云物聯(lián)網(wǎng)平臺,采用MQTT協(xié)議。

三、數(shù)據(jù)檢測與設(shè)備控制

MQTT云平臺配置參考 ElfBoard學(xué)習(xí)(九):MQTT

傳感器數(shù)據(jù)采集與上傳

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

溫濕度數(shù)據(jù)采集

#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 %srn", 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 rn",hum,temp);
        *ath20_data = hum;
        *(ath20_data+1) = temp;
        }

        close(fd);
    return 0;
}

電壓數(shù)據(jù)采集

#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,電壓值:%.3fVrn", raw, raw * scale / 1000.f);
        close(raw_fd);
        close(scale_fd);
        return raw * scale / 1000.f;
}

LED狀態(tài)采集與控制

#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,低于時自動關(guān)閉LED1。

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

數(shù)據(jù)上傳

在main函數(shù)的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);

云端指令響應(yīng)

由于云端傳輸?shù)臄?shù)據(jù)為JSON格式,因此需要使用cJSON進行解析。

添加cJSON

在components文件夾下添加cJSON相關(guān)文件

修改Makefile

在74行和78行后面要添加-lm,否則在編譯的時候會報錯。

實現(xiàn)代碼

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 = %.*srn",
           (unsigned long)recv->data.property_set.msg_id,
           recv->data.property_set.params_len,
           recv->data.property_set.params);

    /* TODO: 以下代碼演示如何對來自云平臺的屬性設(shè)置指令進行應(yīng)答, 用戶可取消注釋查看演示效果 */
    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 %dn",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 %dn",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);   
    }
        
        //釋放內(nèi)存
        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 failedrn");
        }
    }
    
}

四、視頻監(jiān)控

RTMP服務(wù)器搭建

云端服務(wù)器使用Nginx,但Nginx本身并不支持RTMP,需要使用相關(guān)的插件使其支持RTMP。此外由于網(wǎng)頁端播放RTMP流需要Flash插件的支持,而目前Flash插件許多瀏覽器已不再支持,因此需要使用支持 HTTPS-FLV的nginx-http-flv-module,并通過flv.js實現(xiàn)RTMP流的播放。這里首先需要下載Nginx和nginx-http-flv-module的源碼,并采用編譯的方式安裝Nginx,具體步驟如下:

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

安裝完成后,需要進入Nginx安裝目錄(默認為/usr/local/nginx/),并在conf文件夾下對nginx.conf文件進行修改,增加rtmp功能(注意需要打開服務(wù)器的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' 方式回復(fù)

            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服務(wù),即可完成RTMP服務(wù)器的搭建:

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

本地推流

FFmpeg的編譯配置參考:
攝像頭采用的是USB免驅(qū)攝像頭,將攝像頭插入ElfBoard的USB口即可正常識別及工作,設(shè)備節(jié)點為/dev/video2。
之后可以使用v4l2-ctl工具查看并配置攝像頭信息
最后使用命令就能夠?qū)崿F(xiàn)推流:
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
五、用戶端設(shè)計

框架

使用Python編程,采用Web界面,并通過Flask提供Web服務(wù)以及后端數(shù)據(jù)處理能力??梢圆渴鹪谠贫?,也可以在本地運行。界面如下所示:

視頻拉流

Web用戶端的視頻拉流通過flv.js實現(xiàn),首先需要在html文件中導(dǎo)入flv.js:
<script src="https://cdn.bootcss.com/flv.js/1.5.0/flv.js"></script>

之后設(shè)計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>

遠程數(shù)據(jù)的讀取與指令下發(fā)

這一部分通過后端Python編程實現(xiàn),并提供相應(yīng)的Web接口。前后端的交互通過ajax請求實現(xiàn)。
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 相關(guān)
        @param path: params
        @return: OpenApi.Params
        """
        params = open_api_models.Params(
            # 接口名稱,
            action='SetDeviceProperty',
            # 接口版本,
            version='2018-01-20',
            # 接口協(xié)議,
            protocol='HTTPS',
            # 接口 HTTP 方法,
            method='POST',
            auth_type='AK',
            style='RPC',
            # 接口 PATH,
            pathname=f'/',
            # 接口請求體內(nèi)容格式,
            req_body_type='formData',
            # 接口響應(yīng)體內(nèi)容格式,
            body_type='json'
        )
        return params

    @staticmethod
    def create_get_info() -> open_api_models.Params:
        """
        API 相關(guān)
        @param path: params
        @return: OpenApi.Params
        """
        params = open_api_models.Params(
            # 接口名稱,
            action='QueryDeviceOriginalPropertyStatus',
            # 接口版本,
            version='2018-01-20',
            # 接口協(xié)議,
            protocol='HTTPS',
            # 接口 HTTP 方法,
            method='POST',
            auth_type='AK',
            style='RPC',
            # 接口 PATH,
            pathname=f'/',
            # 接口請求體內(nèi)容格式,
            req_body_type='formData',
            # 接口響應(yīng)體內(nèi)容格式,
            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
        )
        # 復(fù)制代碼運行請自行打印 API 的返回值
        # 返回值為 Map 類型,可從 Map 中獲得三類數(shù)據(jù):響應(yīng)體 body、響應(yīng)頭 headers、HTTP 返回的狀態(tài)碼 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
        )
        # 復(fù)制代碼運行請自行打印 API 的返回值
        # 返回值為 Map 類型,可從 Map 中獲得三類數(shù)據(jù):響應(yīng)體 body、響應(yīng)頭 headers、HTTP 返回的狀態(tài)碼 statusCode。
        resp = client.call_api(params, request, runtime)
        body = resp['body']
        data = body['Success']
        return str(data)

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險等級 參考價格 更多信息
AT25128B-XHL-B 1 Microchip Technology Inc IC EEPROM 128KBIT 20MHZ 8TSSOP
$0.75 查看
LTC6994HDCB-1#TRMPBF 1 Linear Technology LTC6994 - TimerBlox: Delay Block/ Debouncer; Package: DFN; Pins: 6; Temperature Range: -40&deg;C to 125&deg;C
$2.63 查看
TJA1051T/CM,118 1 NXP Semiconductors TJA1051 - High-speed CAN transceiver SOIC 8-Pin

ECAD模型

下載ECAD模型
$1.02 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜