一、項(xiàng)目介紹
基于OpenHarmony使用HI3861實(shí)現(xiàn)血壓、心率、血氧的檢測和上傳(具有獨(dú)立APP)
采集被測人體血壓(高血壓/低血壓參數(shù))
采集被測人體心率參數(shù)
采集被測人體血氧參數(shù)
具有WEB配網(wǎng)功能
與服務(wù)器進(jìn)行連接并實(shí)現(xiàn)數(shù)據(jù)交互
可使用清潔能源(太陽能板進(jìn)行供電和充電)
開發(fā)基于OpenHarmony的控制APP
具有離線屏幕顯示功能(OLED-0.96寸)
二、WEB配網(wǎng)
(1)碰一碰配網(wǎng)介紹
通過一機(jī)一碼的形式,識別到NFC后云端驗(yàn)證設(shè)備,進(jìn)行彈窗拉起,再由NAN或AP的方式,實(shí)現(xiàn)發(fā)送配網(wǎng)的SSID和Password。
NAN配網(wǎng)
1. 操作設(shè)備上配網(wǎng)鍵讓設(shè)備進(jìn)入配網(wǎng)模式
2. 手機(jī)碰一碰設(shè)備上的NFC標(biāo)簽,拉起輕應(yīng)用
3. 選擇配網(wǎng)wifi
4. 調(diào)用 discoveryByNAN接口code為0
5. 調(diào)用connectDevice接口連接設(shè)備
6. 調(diào)用configDeviceNet接口開始配網(wǎng)
7. 調(diào)用disconnectDevice接口斷開網(wǎng)絡(luò)
8. 調(diào)用檢測設(shè)備是否上線接口
9. 檢測到設(shè)備上線,調(diào)用綁定設(shè)備接口
AP配網(wǎng)
1. 操作設(shè)備上配網(wǎng)鍵讓設(shè)備進(jìn)入配網(wǎng)模式
2. 手機(jī)碰一碰設(shè)備上的NFC標(biāo)簽,拉起輕應(yīng)用
3. 選擇配網(wǎng)wifi
4. 調(diào)用discoveryByNAN接口code不為0
5. 調(diào)用discoveryBySoftAp接口搜索當(dāng)前設(shè)備的ap,搜索不到的話嘗試直接去連接ap
6. 調(diào)用connectDevice接口連接設(shè)備
7. 調(diào)用configDeviceNet接口開始配網(wǎng)
8. 調(diào)用disconnectDevice接口斷開網(wǎng)絡(luò)
9. 調(diào)用檢測設(shè)備是否上線接口
10. 檢測到設(shè)備上線,調(diào)用綁定設(shè)備接口
(2)WEB配網(wǎng)
本章主要講述如何實(shí)現(xiàn)web配網(wǎng),是在STA模式下,模擬為一個(gè)網(wǎng)站服務(wù)器,當(dāng)手機(jī)或其它設(shè)備進(jìn)行訪問時(shí),檢測是否為瀏覽器的協(xié)議頭(HTTP),返回一個(gè)封裝好的網(wǎng)頁界面,通過網(wǎng)頁上輸入框的填寫實(shí)現(xiàn)配網(wǎng)。
HTTP協(xié)議介紹:
1. http協(xié)議->超文本傳輸協(xié)議
2. 應(yīng)用:編寫基于http協(xié)議的數(shù)據(jù)傳輸程序(網(wǎng)站中瀏覽器端獲取網(wǎng)頁的過程)
3. http請求作用:將要獲取的內(nèi)容以http協(xié)議的格式發(fā)送給服務(wù)端,服務(wù)端根據(jù)格式進(jìn)行解析獲取到其真實(shí)內(nèi)容,將結(jié)果以http協(xié)議的格式回復(fù)給客戶端。
(3)WEB配網(wǎng)界面
html源代碼如下
"UTF-8" />"viewport" content="width=device-width, initial-scale=1.0">"X-UA-Compatible" content="ie=edge">程皖配網(wǎng)"my">"center">"16">歡迎使用程皖配網(wǎng)
"center">WiFi名稱:"text" name="s" placeholder="請輸入您WiFi的名稱" id="aa" style="text-align:center">
"center">WiFi密碼:"text" name="p" placeholder="請輸入您WiFi的密碼" id="bb">
"center">服務(wù)器IP:"text" name="i" placeholder="請輸入您的服務(wù)器IP" id="cc">
"center">服務(wù)器端口:"text" name="t" placeholder="請輸入您的服務(wù)器端口" id="dd">
"center">"button" value="連接" onclick="wifi()" style="width:150px;height:40px" >"javascript">function wifi(){var ssid = my.s.value;var password =my.p.value;var tcp_ip = my.i.value;var tcp_port = my.t.value;var xmlhttp=new XMLHttpRequest();xmlhttp.open("GET","/HandleVal?ssid="+ssid+"&password="+password+"&tcp_ip="+tcp_ip+"&tcp_port="+tcp_port,true);xmlhttp.send()}
實(shí)現(xiàn)的效果如下:
(4)soft模式下實(shí)現(xiàn)網(wǎng)頁服務(wù)器
該部分步驟分為四步:打開WIFI、進(jìn)入softap模式,創(chuàng)建tcp服務(wù)器,解析HTTP指令。此處可參照
潤和開源項(xiàng)目:
https://gitee.com/hihopeorg/HarmonyOS-IoT-Application-Development/tree/master
1)打開WIFI
ret = hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);
if (ret != HISI_OK) {
printf("wifi init failed!
");
} else {
printf("wifi init success!
");
}
2)進(jìn)入softap模式
在softap.c文件下WifiAPTask函數(shù),注冊回調(diào)
//注冊wifi事件的回調(diào)函數(shù)
g_wifiEventHandler.OnHotspotStaJoin = OnHotspotStaJoinHandler;
g_wifiEventHandler.OnHotspotStaLeave = OnHotspotStaLeaveHandler;
g_wifiEventHandler.OnHotspotStateChanged = OnHotspotStateChangedHandler;
error = RegisterWifiEvent(&g_wifiEventHandler);
3)創(chuàng)建socket通道后進(jìn)入判斷接受內(nèi)容循環(huán)
while (1)
{
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1)
{
printf("recv error
");
}else
{
//printf("recv :%s
", recvbuf);
//返回s1中包含s2所有字符的最大起始段長度
//size_t strspn(const char *s1, const char *s2);
char* p= strstr(recvbuf,TEST);
uint16_t DIR_buff = p - recvbuf;
printf("
The GET HTTP num:%d
",DIR_buff);
if(DIR_buff<10)
{
Set_clint_flag = 1;
}else if(DIR_buff>40)
{
Set_clint_flag = 2;
char *p1, *p2;
p1 = strstr(recvbuf, "ssid=");
p2 = strstr(recvbuf, "&password");
if(p1!=0 && p2!=0 && p1
{
p1 += strlen("ssid=");
memcpy(get_ssid, p1, p2 - p1);
printf("
get the ssid = %s
", get_ssid);
}
p1 = strstr(recvbuf, "password=");
p2 = strstr(recvbuf, "&tcp_ip");
if(p1!=0 && p2!=0 && p1
{
p1 += strlen("password=");
memcpy(get_pwd, p1, p2 - p1);
printf("get the ssid = %s
", get_pwd);
}
WifiConnect(get_ssid,get_pwd);
}else
{
Set_clint_flag = 3;
}
bzero(recvbuf, sizeof(recvbuf));
//close(new_fd);
}
sleep(2);
if(Set_clint_flag==1)
{
if ((ret = send(new_fd, httphard1, strlen(httphard1), 0)) == -1)
{
perror("send : ");
}
if ((ret = send(new_fd, webtr, strlen(webtr), 0)) == -1)
{
perror("send : ");
}
Set_clint_flag = 0;
new_fd = -1;
break;
}else if(Set_clint_flag==2)
{
Set_clint_flag = 0;
new_fd = -1;
WifiConnect(get_ssid,get_pwd);
break;
}else if(Set_clint_flag==3)
{
Set_clint_flag = 0;
new_fd = -1;
break;
}
sleep(2);
}
在這個(gè)循環(huán)中實(shí)現(xiàn)了判斷當(dāng)前是否為HTTP指令,如果接收到訪問信號就回發(fā)網(wǎng)頁具體內(nèi)容,實(shí)現(xiàn)手機(jī)顯示網(wǎng)頁。
在填寫SSID和PWD后點(diǎn)擊提交,此時(shí)手機(jī)再向HI3861發(fā)出HTTP指令,中間攜帶填入的信息,該部分由以下程序讀?。?/span>
p1 = strstr(recvbuf, "ssid=");
p2 = strstr(recvbuf, "&password");
if(p1!=0 && p2!=0 && p1
{
p1 += strlen("ssid=");
memcpy(get_ssid, p1, p2 - p1);
printf("
get the ssid = %s
", get_ssid);
}
此時(shí)得到帳號密碼后嘗試連接,即實(shí)現(xiàn)網(wǎng)頁配網(wǎng)
WifiConnect(get_ssid,get_pwd);
三、外設(shè)驅(qū)動(dòng)
本系統(tǒng)使用到usart(PM2.5傳感器)、IIC(OLED顯示屏)、單總線(DHT11)三個(gè)部分和TCP(雙線程收發(fā))幾個(gè)部分
Winodows下HI3861開發(fā):
HI3861:鴻蒙網(wǎng)頁顯示傳感器數(shù)據(jù):
(1)打開外設(shè)使能
在usr_config.mk文件中去掉注釋
CONFIG_I2C_SUPPORT=y
CONFIG_UART0_SUPPORT=y
(2)OLED顯示屏驅(qū)動(dòng)
OLED,即有機(jī)發(fā)光二極管(Organic Light-Emitting Diode),又稱為有機(jī)電激光顯示(Organic Electroluminesence Display)。OLED由于同時(shí)具備自發(fā)光,不需背光源、對比度高、厚度薄、視角廣、反應(yīng)速度快、可用于撓曲性面板、使用溫度范圍廣、構(gòu)造及制程較簡單等優(yōu)異之特性,被認(rèn)為是下一代的平面顯示器新興應(yīng)用技術(shù)。
該傳感器使用的IIC協(xié)議,經(jīng)過IIC使能后初始化OLED就可以使用了:
hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA);
hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);
ret = hi_i2c_deinit(HI_I2C_IDX_0);
ret |= hi_i2c_init(HI_I2C_IDX_0, 100000);
if (ret != HI_ERR_SUCCESS) {
printf("IIC error
");
}else
{
printf("IIC sucesefful
");
}
OLED_ColorTurn(0);//0正常顯示,1 反色顯示
OLED_DisplayTurn(0);//0正常顯示 1 屏幕翻轉(zhuǎn)顯示
其中主要用到的函數(shù)是void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1):
//在指定位置顯示一個(gè)字符,包括部分字符
//x:0~127
//y:0~63
//size:選擇字體 12/16/24
//取模方式 逐列式
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{
u8 i,m,temp,size2,chr1;
u8 y0=y;
size2=(size1/8+((size1%8)?1:0))*(size1/2); //得到字體一個(gè)字符對應(yīng)點(diǎn)陣集所占的字節(jié)數(shù)
chr1=chr-' '; //計(jì)算偏移后的值
for(i=0;i
{
//temp=asc2_1206[chr1][i];
if(size1==12)
{temp=asc2_1206[chr1][i];} //調(diào)用1206字體
else if(size1==16)
{temp=asc2_1608[chr1][i];} //調(diào)用1608字體
else return;
for(m=0;m<8;m++) ? ? ? ? ? //寫入數(shù)據(jù)
{
if(temp&0x80)OLED_DrawPoint(x,y);
else OLED_ClearPoint(x,y);
temp<<=1;
y++;
if((y-y0)==size1)
{
y=y0;
x++;
break;
}
}
}
}
通過該函數(shù),就能實(shí)現(xiàn)傳感器數(shù)值和字符的顯示。
(3)數(shù)據(jù)發(fā)送和接收
因?yàn)镠I3861的線程限制,這邊使用雙線程,一個(gè)實(shí)現(xiàn)TCP數(shù)據(jù)的發(fā)送,另一個(gè)實(shí)現(xiàn)TCP數(shù)據(jù)的接收
發(fā)送線程:
void TcpClientTest(const char* host, unsigned short port)
{
ssize_t retval = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP socket
SET_SOCKET_ID = sockfd;
struct sockaddr_in serverAddr = {0};
serverAddr.sin_family = AF_INET; // AF_INET表示IPv4協(xié)議
serverAddr.sin_port = htons(port); // 端口號,從主機(jī)字節(jié)序轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0) { ?// 將主機(jī)IP地址從“點(diǎn)分十進(jìn)制”字符串 轉(zhuǎn)化為 標(biāo)準(zhǔn)格式(32位整數(shù))
printf("inet_pton failed!
");
goto do_cleanup;
}
// 嘗試和目標(biāo)主機(jī)建立連接,連接成功會(huì)返回0 ,失敗返回 -1
if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
printf("connect failed!
");
goto do_cleanup;
}
printf("connect to server %s success!
", host);
Wifi_SOCKET_GET();
while (1)
{
osDelay(500);
/////////////////////////////////////////////////////////上傳函數(shù)
retval = send(sockfd, buff, 6,0);//其中buff為數(shù)據(jù)
}
do_cleanup:
printf("do_cleanup...
");
closesocket(sockfd);
}
接收處理線程:
static BOOL Wifi_SOCKET_RUN(void)
{
ssize_t retval = 0;
while(1)
{
retval = recv(SET_SOCKET_ID, &response, sizeof(response), 0);
if(retval>0)
{
response[retval] = '';
if(response[0] == 'o')
{
printf("send open!
");//此處對接收到的數(shù)據(jù)進(jìn)行處理,并執(zhí)行對應(yīng)內(nèi)容
}
}
}
do_cleanup:
printf("do_cleanup...
");
closesocket(SET_SOCKET_ID);
}
void Wifi_SOCKET_GET(void)
{
osThreadAttr_t attr;
attr.name = "Wifi_SOCKET_RUN";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 2048;
attr.priority = 25;
if (osThreadNew((osThreadFunc_t)Wifi_SOCKET_RUN, NULL, &attr) == NULL)
{
printf("Falied to create WifiAPTask!
");
}
}
(4)血壓測量驅(qū)動(dòng)
血壓的測量選擇使用便攜式測量,在開發(fā)中已與電子血壓儀行業(yè)標(biāo)桿歐姆龍和傳統(tǒng)水銀血壓儀進(jìn)行比較,較為準(zhǔn)確,可作為參考使用。
當(dāng)前為使用第一階段,與廠商(批量)第二階段合作時(shí)可以得到更多的數(shù)據(jù),可以當(dāng)做一次小型的體檢,如下圖:
其驅(qū)動(dòng)方式為USART驅(qū)動(dòng),協(xié)議如下:
通過對數(shù)據(jù)的截取和發(fā)送即可實(shí)現(xiàn)。
四、APP開發(fā)
(1)環(huán)境搭建
使用的是官方下載地址:
https://developer.harmonyos.com/cn/develop/deveco-studio#download_beta
我這邊用的是今年三月份的版本,不過不影響,界面沒什么變化
(2)TCP數(shù)據(jù)交互
該部分參考官方手冊:
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/apis/js-apis-socket.md/
import socket from '@ohos.net.socket';
let tcp = socket.constructTCPSocketInstance();
tcp.bind({address: '0.0.0.0', port: 12121, family: 1}, err => {
if (err) {
console.log('bind fail');
return;
}
console.log('bind success');
})
tcp.on('message', value => {
console.log("on message, message:" + value.message + ", remoteInfo:" + value.remoteInfo)
let da = resolveArrayBuffer(value.message);
let dat_buff = String(da);
//此處對接受到的數(shù)據(jù)進(jìn)行處理
});
//將接受到的數(shù)據(jù)轉(zhuǎn)化為文本型
function resolveArrayBuffer(message){
if (message instanceof ArrayBuffer) {
let dataView = new DataView(message)
let str = ""
for (let i = 0;i < dataView.byteLength; ++i) {
let c = String.fromCharCode(dataView.getUint8(i))
if (c !== "
") {
str += c
}
}
return str;
}
}
//數(shù)據(jù)的發(fā)送函數(shù)
function send_once(Con_buff) {
if (flag == false) {
let promise = tcp.connect({ address: { address: 'xxx.xxx.xxx.xxx', port: xxxx, family: 1 }, timeout: 2000 });
promise.then(() => {
console.log('connect success');
flag = true;
tcp.send({
data: Con_buff
}, err => {
if (err) {
console.log('send fail');
return;
}
console.log('send success');
})
}).catch(err => {
console.log('connect fail');
});
} else if (flag == true) {
tcp.send({
data: Con_buff
}, err => {
if (err) {
console.log('send fail');
return;
}
console.log('send success');
})
}
}
(3)界面設(shè)計(jì)
OpenHarmony界面設(shè)計(jì)(簡單)教程:
本APP共用到了按鈕、圖片、標(biāo)簽三個(gè)部分,其對應(yīng)的官網(wǎng)連接如下
按鈕(Button):
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-button.md/
圖片(Image):
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-image.md/
標(biāo)簽(TEXT):
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-text.md/
豎向排列(Column):
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md/
橫向排列(Row):
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md/
(4)參數(shù)動(dòng)態(tài)更新
@State srtText: string = "測試變量";
Text(this.srtText) //動(dòng)態(tài)
.fontSize(60)
.fontWeight(FontWeight.Bold)
.fontColor("#e94674")
Button() { //按鈕控件
Text('點(diǎn)擊')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}.type(ButtonType.Capsule)
.margin({
top: 200
})
.width('50%')
.height('10%')
.backgroundColor('#0D9FFB')
.onClick(() => { //點(diǎn)擊事件
this.srtText = "更改內(nèi)容"http://更改數(shù)據(jù)
})
在使用 @State變量對組件進(jìn)行刷新時(shí),發(fā)現(xiàn)只能在build中實(shí)現(xiàn)動(dòng)態(tài)刷新,在外部創(chuàng)建全局變量或者外部函數(shù)的方式都不能實(shí)現(xiàn),查閱資料后得到如下部分:
官方文檔:
https://docs.openharmony.cn/pages/v3.1/zh-cn/application-dev/ui/ts-application-states-appstorage.md/
AppStorage與組件同步
在管理組件擁有的狀態(tài)中,已經(jīng)定義了如何將組件的狀態(tài)變量與父組件或祖先組件中的@State裝飾的狀態(tài)變量同步,主要包括@Prop、@Link、@Consume。
本章節(jié)定義如何將組件變量與AppStorage同步,主要提供@StorageLink和@StorageProp裝飾器。
@StorageLink裝飾器
組件通過使用@StorageLink(key)裝飾的狀態(tài)變量,與AppStorage建立雙向數(shù)據(jù)綁定,key為AppStorage中的屬性鍵值。當(dāng)創(chuàng)建包含@StorageLink的狀態(tài)變量的組件時(shí),該狀態(tài)變量的值將使用AppStorage中的值進(jìn)行初始化。在UI組件中對@StorageLink的狀態(tài)變量所做的更改將同步到AppStorage,并從AppStorage同步到任何其他綁定實(shí)例中,如PersistentStorage或其他綁定的UI組件。
@StorageProp裝飾器
組件通過使用@StorageProp(key)裝飾的狀態(tài)變量,將與AppStorage建立單向數(shù)據(jù)綁定,key標(biāo)識AppStorage中的屬性鍵值。當(dāng)創(chuàng)建包含@StoageProp的狀態(tài)變量的組件時(shí),該狀態(tài)變量的值將使用AppStorage中的值進(jìn)行初始化。AppStorage中的屬性值的更改會(huì)導(dǎo)致綁定的UI組件進(jìn)行狀態(tài)更新。
let varA = AppStorage.Link('varA')
let envLang = AppStorage.Prop('languageCode')
@Entry
@Component
struct ComponentA {
@StorageLink('varA') varA: number = 2
@StorageProp('languageCode') lang: string = 'en'
private label: string = 'count'
private aboutToAppear() {
this.label = (this.lang === 'zh') ? '數(shù)' : 'Count'
}
build() {
Row({ space: 20 }) {
Button(`${this.label}: ${this.varA}`)
.onClick(() => {
AppStorage.Set('varA', AppStorage.Get('varA') + 1)
})
Button(`lang: ${this.lang}`)
.onClick(() => {
if (this.lang === 'zh') {
AppStorage.Set('languageCode', 'en')
} else {
AppStorage.Set('languageCode', 'zh')
}
this.label = (this.lang === 'zh') ? '數(shù)' : 'Count'
})
}
}
}
即通過AppStorage.Link和 @StorageLink的方式,可實(shí)現(xiàn)外部動(dòng)態(tài)刷新Text組件和image組件(等等之類都可以),方便我們在全局調(diào)用時(shí)更新數(shù)據(jù)。
-
OpenHarmony
+關(guān)注
關(guān)注
25文章
3731瀏覽量
16431
發(fā)布評論請先 登錄
相關(guān)推薦
評論