一、項目介紹
基于OpenHarmony使用HI3861實現血壓、心率、血氧的檢測和上傳(具有獨立APP)
采集被測人體血壓(高血壓/低血壓參數)
采集被測人體心率參數
采集被測人體血氧參數
具有WEB配網功能
與服務器進行連接并實現數據交互
可使用清潔能源(太陽能板進行供電和充電)
開發基于OpenHarmony的控制APP
具有離線屏幕顯示功能(OLED-0.96寸)
二、WEB配網
(1)碰一碰配網介紹
通過一機一碼的形式,識別到NFC后云端驗證設備,進行彈窗拉起,再由NAN或AP的方式,實現發送配網的SSID和Password。
NAN配網
1. 操作設備上配網鍵讓設備進入配網模式
3. 選擇配網wifi
4. 調用 discoveryByNAN接口code為0
5. 調用connectDevice接口連接設備
6. 調用configDeviceNet接口開始配網
7. 調用disconnectDevice接口斷開網絡
8. 調用檢測設備是否上線接口
9. 檢測到設備上線,調用綁定設備接口
AP配網
1. 操作設備上配網鍵讓設備進入配網模式
2. 手機碰一碰設備上的NFC標簽,拉起輕應用
3. 選擇配網wifi
4. 調用discoveryByNAN接口code不為0
5. 調用discoveryBySoftAp接口搜索當前設備的ap,搜索不到的話嘗試直接去連接ap
6. 調用connectDevice接口連接設備
7. 調用configDeviceNet接口開始配網
8. 調用disconnectDevice接口斷開網絡
9. 調用檢測設備是否上線接口
10. 檢測到設備上線,調用綁定設備接口
(2)WEB配網
本章主要講述如何實現web配網,是在STA模式下,模擬為一個網站服務器,當手機或其它設備進行訪問時,檢測是否為瀏覽器的協議頭(HTTP),返回一個封裝好的網頁界面,通過網頁上輸入框的填寫實現配網。
HTTP協議介紹:
1. http協議->超文本傳輸協議
2. 應用:編寫基于http協議的數據傳輸程序(網站中瀏覽器端獲取網頁的過程)
3. http請求作用:將要獲取的內容以http協議的格式發送給服務端,服務端根據格式進行解析獲取到其真實內容,將結果以http協議的格式回復給客戶端。
(3)WEB配網界面
html源代碼如下
"UTF-8" />"viewport" content="width=device-width, initial-scale=1.0">"X-UA-Compatible" content="ie=edge">程皖配網"my">"center">"16">歡迎使用程皖配網
"center">WiFi名稱:"text" name="s" placeholder="請輸入您WiFi的名稱" id="aa" style="text-align:center">
"center">WiFi密碼:"text" name="p" placeholder="請輸入您WiFi的密碼" id="bb">
"center">服務器IP:"text" name="i" placeholder="請輸入您的服務器IP" id="cc">
"center">服務器端口:"text" name="t" placeholder="請輸入您的服務器端口" 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()}
實現的效果如下:
(4)soft模式下實現網頁服務器
該部分步驟分為四步:打開WIFI、進入softap模式,創建tcp服務器,解析HTTP指令。此處可參照
潤和開源項目:
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)進入softap模式
在softap.c文件下WifiAPTask函數,注冊回調
//注冊wifi事件的回調函數
g_wifiEventHandler.OnHotspotStaJoin = OnHotspotStaJoinHandler;
g_wifiEventHandler.OnHotspotStaLeave = OnHotspotStaLeaveHandler;
g_wifiEventHandler.OnHotspotStateChanged = OnHotspotStateChangedHandler;
error = RegisterWifiEvent(&g_wifiEventHandler);
3)創建socket通道后進入判斷接受內容循環
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);
}
在這個循環中實現了判斷當前是否為HTTP指令,如果接收到訪問信號就回發網頁具體內容,實現手機顯示網頁。
在填寫SSID和PWD后點擊提交,此時手機再向HI3861發出HTTP指令,中間攜帶填入的信息,該部分由以下程序讀取:
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);
}
此時得到帳號密碼后嘗試連接,即實現網頁配網
WifiConnect(get_ssid,get_pwd);
三、外設驅動
本系統使用到usart(PM2.5傳感器)、IIC(OLED顯示屏)、單總線(DHT11)三個部分和TCP(雙線程收發)幾個部分
Winodows下HI3861開發:
HI3861:鴻蒙網頁顯示傳感器數據:
(1)打開外設使能
在usr_config.mk文件中去掉注釋
CONFIG_I2C_SUPPORT=y
CONFIG_UART0_SUPPORT=y
(2)OLED顯示屏驅動
OLED,即有機發光二極管(Organic Light-Emitting Diode),又稱為有機電激光顯示(Organic Electroluminesence Display)。OLED由于同時具備自發光,不需背光源、對比度高、厚度薄、視角廣、反應速度快、可用于撓曲性面板、使用溫度范圍廣、構造及制程較簡單等優異之特性,被認為是下一代的平面顯示器新興應用技術。
該傳感器使用的IIC協議,經過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 屏幕翻轉顯示
其中主要用到的函數是void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1):
//在指定位置顯示一個字符,包括部分字符
//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); //得到字體一個字符對應點陣集所占的字節數
chr1=chr-' '; //計算偏移后的值
for(i=0;i
{
//temp=asc2_1206[chr1][i];
if(size1==12)
{temp=asc2_1206[chr1][i];} //調用1206字體
else if(size1==16)
{temp=asc2_1608[chr1][i];} //調用1608字體
else return;
for(m=0;m<8;m++) ? ? ? ? ? //寫入數據
{
if(temp&0x80)OLED_DrawPoint(x,y);
else OLED_ClearPoint(x,y);
temp<<=1;
y++;
if((y-y0)==size1)
{
y=y0;
x++;
break;
}
}
}
}
通過該函數,就能實現傳感器數值和字符的顯示。
(3)數據發送和接收
因為HI3861的線程限制,這邊使用雙線程,一個實現TCP數據的發送,另一個實現TCP數據的接收
發送線程:
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協議
serverAddr.sin_port = htons(port); // 端口號,從主機字節序轉為網絡字節序
if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0) { ?// 將主機IP地址從“點分十進制”字符串 轉化為 標準格式(32位整數)
printf("inet_pton failed!
");
goto do_cleanup;
}
// 嘗試和目標主機建立連接,連接成功會返回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);
/////////////////////////////////////////////////////////上傳函數
retval = send(sockfd, buff, 6,0);//其中buff為數據
}
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!
");//此處對接收到的數據進行處理,并執行對應內容
}
}
}
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)血壓測量驅動
血壓的測量選擇使用便攜式測量,在開發中已與電子血壓儀行業標桿歐姆龍和傳統水銀血壓儀進行比較,較為準確,可作為參考使用。
當前為使用第一階段,與廠商(批量)第二階段合作時可以得到更多的數據,可以當做一次小型的體檢,如下圖:
其驅動方式為USART驅動,協議如下:
通過對數據的截取和發送即可實現。
四、APP開發
(1)環境搭建
使用的是官方下載地址:
https://developer.harmonyos.com/cn/develop/deveco-studio#download_beta
我這邊用的是今年三月份的版本,不過不影響,界面沒什么變化
(2)TCP數據交互
該部分參考官方手冊:
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);
//此處對接受到的數據進行處理
});
//將接受到的數據轉化為文本型
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;
}
}
//數據的發送函數
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)界面設計
OpenHarmony界面設計(簡單)教程:
本APP共用到了按鈕、圖片、標簽三個部分,其對應的官網連接如下
按鈕(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/
標簽(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)參數動態更新
@State srtText: string = "測試變量";
Text(this.srtText) //動態
.fontSize(60)
.fontWeight(FontWeight.Bold)
.fontColor("#e94674")
Button() { //按鈕控件
Text('點擊')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}.type(ButtonType.Capsule)
.margin({
top: 200
})
.width('50%')
.height('10%')
.backgroundColor('#0D9FFB')
.onClick(() => { //點擊事件
this.srtText = "更改內容"http://更改數據
})
在使用 @State變量對組件進行刷新時,發現只能在build中實現動態刷新,在外部創建全局變量或者外部函數的方式都不能實現,查閱資料后得到如下部分:
官方文檔:
https://docs.openharmony.cn/pages/v3.1/zh-cn/application-dev/ui/ts-application-states-appstorage.md/
AppStorage與組件同步
在管理組件擁有的狀態中,已經定義了如何將組件的狀態變量與父組件或祖先組件中的@State裝飾的狀態變量同步,主要包括@Prop、@Link、@Consume。
本章節定義如何將組件變量與AppStorage同步,主要提供@StorageLink和@StorageProp裝飾器。
@StorageLink裝飾器
組件通過使用@StorageLink(key)裝飾的狀態變量,與AppStorage建立雙向數據綁定,key為AppStorage中的屬性鍵值。當創建包含@StorageLink的狀態變量的組件時,該狀態變量的值將使用AppStorage中的值進行初始化。在UI組件中對@StorageLink的狀態變量所做的更改將同步到AppStorage,并從AppStorage同步到任何其他綁定實例中,如PersistentStorage或其他綁定的UI組件。
@StorageProp裝飾器
組件通過使用@StorageProp(key)裝飾的狀態變量,將與AppStorage建立單向數據綁定,key標識AppStorage中的屬性鍵值。當創建包含@StoageProp的狀態變量的組件時,該狀態變量的值將使用AppStorage中的值進行初始化。AppStorage中的屬性值的更改會導致綁定的UI組件進行狀態更新。
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') ? '數' : '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') ? '數' : 'Count'
})
}
}
}
即通過AppStorage.Link和 @StorageLink的方式,可實現外部動態刷新Text組件和image組件(等等之類都可以),方便我們在全局調用時更新數據。
-
OpenHarmony
+關注
關注
25文章
3725瀏覽量
16375
發布評論請先 登錄
相關推薦
評論