值得一提的是,社群內(nèi)部已經(jīng)涌現(xiàn)出許多富有創(chuàng)意的產(chǎn)品設(shè)計(jì)理念與技術(shù)解決方案,今天就跟大家分享一名共創(chuàng)官完成的項(xiàng)目報(bào)告“基于ElfBoard的遠(yuǎn)程監(jiān)測(cè)系統(tǒng)”。
一、項(xiàng)目介紹
1.2 項(xiàng)目硬件
-
ElfBoard ELF 1 開(kāi)發(fā)板 -
WiFi(RTL8723DU) -
USB免驅(qū)攝像頭 -
Linux服務(wù)器
1.3 軟件環(huán)境
-
阿里云物聯(lián)網(wǎng)平臺(tái) -
Nginx -
Python -
Flask
二、項(xiàng)目方案
采用RTMP協(xié)議,設(shè)備端使用FFmpeg采集攝像頭數(shù)據(jù)并推流至云端,云端使用Nginx提供Web服務(wù),并使用nginx-http-flv-module提供RTMP服務(wù),用戶端采用Web界面,并使用flv.js進(jìn)行拉流播放。
2.2 數(shù)據(jù)檢測(cè)與設(shè)備控制
三、數(shù)據(jù)檢測(cè)與設(shè)備控制
參考 ElfBoard學(xué)習(xí)(九):MQTT
傳感器數(shù)據(jù)采集與上傳
溫濕度數(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.打開(kāi)文件 */
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); // 將光標(biāo)移回文件開(kāi)頭
read(raw_fd, buff, sizeof(buff));
raw = atoi(buff);
memset(buff, 0, sizeof(buff));
// rewind(scale_fd); // 將光標(biāo)移回文件開(kāi)頭
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);
}
自動(dòng)化控制
當(dāng)ADC采集的電壓大于閾值2.5V時(shí)自動(dòng)開(kāi)啟LED1,低于時(shí)自動(dòng)關(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)
添加cJSON
修改Makefile
實(shí)現(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: 以下代碼演示如何對(duì)來(lái)自云平臺(tái)的屬性設(shè)置指令進(jìn)行應(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");
}
}
}
RTMP服務(wù)器搭建
./configure --add-module=/usr/local/nginx/nginx-http-flv-module
make&&make install
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; #打開(kāi) 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;
}
}
}
cd /usr/local/nginx/sbin
./nginx
本地推流
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
框架
視頻拉流
<script src="https://cdn.bootcss.com/flv.js/1.5.0/flv.js"></script>
之后設(shè)計(jì)Web頁(yè)面播放器,具體代碼如下:
<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()">開(kāi)始</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>
遠(yuǎn)程數(shù)據(jù)的讀取與指令下發(fā)
class Sample:
def __init__(self):
pass
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> OpenApiClient:
"""
使用AK&SK初始化賬號(hào)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 請(qǐng)參考 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'/',
# 接口請(qǐng)求體內(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'/',
# 接口請(qǐng)求體內(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ù)制代碼運(yùn)行請(qǐng)自行打印 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ù)制代碼運(yùn)行請(qǐng)自行打印 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)