#打卡不停更#家庭健康管理平台

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#打卡不停更#家庭健康管理平台相关的知识,希望对你有一定的参考价值。

0. 项目简介

身体健康是一切生产生活的硬性基础。健康是福,一切安好,未来才可期。为什么经常跑步体重缺还在往上飘?突发紧急情况怎么处理?在数字时代,如何更好的为人们提供健康福祉、普及健康知识?如何进一步驱动个人健康管理是的值得研究的方向。为此,我们团队打造了一个健康管理平台——家庭健康管理平台。概览如下图所示:

家庭健康助理是集健康数据测量与管理、急救设备及使用指导、疫情防控实况、日常生活建议为一体的健康管理平台。健康助理核心是以数据为驱动,软硬结合,通过若干生理数据采集设备获取家庭成员健康状态,并将数据同步至移动端(DAYU200),软件进行数据管理与可视化,为每个成员打造健康日历,为每一个家庭提供数字化健康服务。 为展示平台的核心能力,开发的具体内容包含有:基于OpenHarmony驱动的移动端软件、多功能生理数据测量笔、便携式止血设备、康复训练手套。 交互软件完成了设备管理、数据可视化(分布式数据存储)、急救知识展示功能;多功能生理数据测量笔展示平台数据测量能力,测量包括体温、身高、心率血氧;便携式止血设备是为展示平台急救设备及使用指导能力,提供止血教学、实时查看止血状态服务;康复训练手套是为患者提供手部康复训练服务。

对应的联合国17项可持续发展目标

3.良好健康与福祉、 4.优质教育

1.开发环境及技术特点

OH系统版本:OpenHarmony 3.1 Release 开发环境:DevEco studio 3.0.0.900 技术特点: ArkUI (ets) 、软硬结合、http数据请求、加载web组件、socket通信、健康数据对象、分布式数据管理(开发中) 基础开发指导:环境准备设备开发指导应用开发指导

2.开发流程

2.1 家庭健康管理平台框架

如下图所示,家庭健康管理平台由家庭助理APP、分布式设备组成。APP核心部分是数据采集、医疗相关知识模块、健康数据管理三部分。

  • 数据采集使用各种医疗设备进行采集,本项目中以多功能测量笔为例,展示数据采集、自动同步的功能,免去复杂的手动输入数据操作。
  • 针对医疗相关知识模块,开发了便携式止血设备,并对肢体止血进行指导。
  • 健康数据管理负责整合各项数据,如天气、疫情信息、成员体温等。在成员信息页展示成员档案。还可利用分布式特性将数据传输至另一台设备,实现健康数据共享,方便管理家庭成员数据,更好的服务家庭。(涉及轻量数据存储,在开发中)

注: 对应的联合国17项可持续发展目标 3.良好健康与福祉 4.优质教育 APP 主页:

下面以APP页面为主线,对项目开发技术细节进行展开说明。

2.2 家庭成员健康档案页

2.2.1 数据抽象

将成员健康档案抽象为一个类,包含年龄、身高、心率等数据。另外定义对象操作方法,方便访问数据,新建对象。实现代码如下:

export class People 
  name: string
  age: number
  height: number
  weight: number
  heart: number
  temperature: number

  constructor(name:string, age:number, height:number, weight:number, heart:number, temperature:number)
    this.name = name
    this.age  = age
    this.height = height
    this.weight = weight
    this.heart = heart
    this.temperature = temperature
  


export const PersonMsg: any[]=[
  name: 爷爷, age: 1,height: 190, weight: 10, heart: 1,temperature: 1,
  name: 奶奶, age: 2,height: 180, weight: 20, heart: 2,temperature: 2,
  name: 爸爸, age: 3,height: 170, weight: 30, heart: 3,temperature: 3,
  name: 妈妈, age: 4,height: 160, weight: 40, heart: 4,temperature: 4,
  name: 小可爱, age: 5,height: 150,weight: 60, heart: 5,temperature: 5,
];
export function PersonInit(): Array<People> 
  let PersonDataArray: Array<People> = []
  PersonMsg.forEach(item => 
    PersonDataArray.push(new People(item.name, item.age, item.height, item.weight, item.heart, item.temperature));
  )
  return PersonDataArray;


2.2.2 展示信息

使用SideBar组件,迭代读取数据,对每一位成员的健康档案进行展示,并自动计算BMI。

import People,PersonInit from ../common/mystorage
// 使用SideBar组件展示
SideBarContainer(SideBarContainerType.Embed)
    
      Column() 
        ForEach(this.arr, (item, index) => 
          Column( space: 5 ) 
            Image(this.current === item ? this.selectedIcon : this.normalIcon).width(64).height(64)
            Text(this.personList[item-1].name )
              .fontSize(25)
              .fontColor(this.current === item ? #0A59F7 : #999)
              .fontFamily(source-sans-pro,cursive,sans-serif)
          
          .onClick(() => 
            this.current = item
            putStorage()
            this.BMI = this.person_arr[this.current-1].weight/Math.pow(this.person_arr[this.current-1].height/100,2)
            if(this.BMI<18.5)
              this.bodyStatus = 偏瘦
            
            else if(this.BMI>24.9)
              this.bodyStatus = 月半
            
            else
              this.bodyStatus = 正常
            
          )
        , item => item)
      .width(100%)
      .justifyContent(FlexAlign.SpaceEvenly)
      .backgroundColor(#19000090)
      Column()
        Row(space:20) 
          Image($r(app.media.ic_contacts_business_cards)).width(50%).height(20%).objectFit(ImageFit.Contain)
            .onClick(() => 
              this.personList[this.current].name = A
              this.person_arr[this.current].name = B
              putStorage()
              prompt.showToast( message: this.personList[this.current].name )
            )
          Text(this.person_arr[this.current-1].name).fontSize(40)
        
          Row() 
            Image($r(app.media.ic_contacts_birthday_filled)).objectFit(ImageFit.Contain)
              .size( height: 80, width: 80 )
            Text(年龄:  + this.person_arr[this.current-1].age).fontSize(this.info_font_size).margin( top: 10 )
          .margin(top:10)
          Row()
            Image($r(app.media.ic_user_portrait)).objectFit(ImageFit.Contain)
              .size( height: 80, width: 80 )
            Text(身高:  + this.person_arr[this.current-1].height).fontSize(this.info_font_size)
          .margin(top:10)
          Row()
            Image($r(app.media.ic_public_privacy)).objectFit(ImageFit.Contain)
              .size( height: 80, width: 80 )
            Text(体重:  + this.person_arr[this.current-1].weight.toString()).fontSize(this.info_font_size)
          .margin(top:10)
          Row() 
            Image($r(app.media.ic_contacts_blood_type)).objectFit(ImageFit.Contain)
              .size( height: 80, width: 80 )
            Text(心率:  + this.person_arr[this.current-1].heart.toString())
              .fontSize(this.info_font_size)
              .margin( top: 10 )
          .margin(top:10)
          Row() 
            Image($r(app.media.ic_controlcenter_eyeconfort_filled)).objectFit(ImageFit.Contain)
              .size( height: 80, width: 80 )
            Text(体温:  + this.person_arr[this.current-1].temperature).fontSize(this.info_font_size).margin( top: 10 )
          .margin(top:10)
          TextInput( placeholder: BMI指数:   + this.BMI.toString().substring(0, 4) +  + this.bodyStatus )
            .width(90%).height(5%).fontSize(30)
            .placeholderFont( size: this.info_font_size, weight: 100, family: cursive )
      .width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Start).margin(left:30)
    
    .sideBarWidth(240)
    .minSideBarWidth(209)
    .maxSideBarWidth(260)
    .onChange((value: boolean) => 
      console.info(status: + value)
    )

展示的数据涉及分布式轻量化数据存储,在另外一台设备可查看成员信息。 完整的数据库开发流程封装如下:

// 导入模块
import dataStorage from @ohos.data.storage;
import prompt from @ohos.prompt;
import distributedData from @ohos.data.distributedData;
import deviceManager from @ohos.distributedHardware.deviceManager;

let kvManager;
let kvStore;
let devManager;
// 订阅分布式数据库管理对象
export function CreateKVManger()


  try 
    const kvManagerConfig = 
      bundleName : com.example.familyheath,
      userInfo : 
        userId : 0,
        userType : distributedData.UserType.SAME_USER_ID
      
    
    distributedData.createKVManager(kvManagerConfig, function (err, manager) 
      if (err) 
        console.log("createKVManager err: "  + JSON.stringify(err));
        return;
      
      console.log("cccc createKVManager success");
      kvManager = manager;
    );
   catch (e) 
    console.log("cccc An unexpected error occurred. Error:" + e);
  

// 创建分布式数据库
export function CreateKVStore()

  try 
    const options = 
      createIfMissing : true,
      encrypt : false,
      backup : false,
      autoSync : false,
      kvStoreType : distributedData.KVStoreType.SINGLE_VERSION,
      securityLevel : distributedData.SecurityLevel.S0,
    ;
    kvManager.getKVStore(storeId10087, options, function (err, store) 
      if (err) 
        console.log("xxxxx getKVStore err: "  + JSON.stringify(err));
        return;
      
      console.log("cccc getKVStore success");
      kvStore = store;
    );
   catch (e) 
    console.log("cccc An unexpected error occurred. Error:" + e);
  


// 订阅分布式数据库
export function OnKVStore()

  kvStore.on(dataChange, distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, function (data) 
    console.log("dataChange callback call data: " + JSON.stringify(data));
  );


// 插入kv数据库
export function InsertKVStore(key:string,value)

  const KEY_STRING_ELEMENT = key;
  const VALUE_STRING_ELEMENT = value;
  try 
    kvStore.put(KEY_STRING_ELEMENT, VALUE_STRING_ELEMENT, function (err,data) 
      if (err != undefined) 
        console.log("cccc put err: " + JSON.stringify(err));
        return;
      
      console.log("cccc put success");
    );
  catch (e) 
    console.log("cccc An unexpected error occurred. Error:" + e);
  


// 查找数据库
export function  SearchKVStore(key:string)

  let value = "cc"
  const KEY_STRING_ELEMENT = key;
  try 
    kvStore.get(KEY_STRING_ELEMENT, function (err,data) 
      console.log("cccc get success data: " + data);
      value = data
      return  value
    );
  catch (e) 
    console.log("cccc An unexpected error occurred. Error:" + e);
  
  return  value


// 同步分布式数据库到可信任设备
export function AsyncKVStore()

  // create deviceManager
  deviceManager.createDeviceManager("com.example.familyheath", (err, value) => 
    if (!err) 
      devManager = value;
      // get deviceIds
      let deviceIds = [];
      if (devManager != null) 
        // 获取分布式组网内可信设备
        var devices = devManager.getTrustedDeviceListSync();
        for (var i = 0; i < devices.length; i++) 
          deviceIds[i] = devices[i].deviceId;
        
      
      try
        kvStore.sync(deviceIds, distributedData.SyncMode.PUSH_PULL, 1000);
      catch (e) 
        console.log("cccc An unexpected error occurred. Error:" + e);
      
    
  );

2.3 数据采集模块

2.3.1 设备管理

数据采集模块负责管理家用医疗设备、健身器械,数据采集。理论上可接入如体脂秤、血压血糖仪、跑步机等常用设备。针对该模块,项目开发了多功能测试笔,2.3.2节进行介绍。设备管理页面框架如下:

单台设备的信息展示实现如下:

 // 测量笔
      Row(space:20) 
        Column()
        
          Text(this.pen).height(30%).fontSize(35)
          Image($r(app.media.multipen))
            .width(45%).height(50%).margin(top:20)
            .objectFit(ImageFit.Contain)
        .backgroundColor(white).borderRadius(20).opacity(this.pen_opacity)
        .onClick(() => 
          this.penShow = !this.penShow
          if(this.penShow)
            this.pen_opacity = 0.4
          else
            this.pen_opacity = 0.9
          
          router.push( url: pages/multiPen )
        )

2.3.2 多功能测量笔-软件开发

  • 涉及模块、权限:
import socket from @ohos.net.socket
import prompt from @ohos.prompt;
import wifi from @ohos.wifi;
"ohos.permission.INTERNET"
"ohos.permission.GET_WIFI_INFO"

当在设备列表页面点击对应设备后,将跳转到对应的设备控制页面。项目以多功能数据测量笔为例,进行开发细节介绍。首先每台设备均使用socket与DAYU200建立连接,为了提升代码重用性,新建common文件夹,编写socket功能模块,外部只需进行简单调用即可。socket功能封装具体的实现如下:

import socket from @ohos.net.socket
import prompt from @ohos.prompt;

//tcp对象
let tcp = socket.constructTCPSocketInstance();

let message_recv = 0
  // 解析本地ip
export function  resolveIP(ip) 
    if (ip < 0 || ip > 0xFFFFFFFF) 
      throw ("The number is not normal!");
    
    return (ip >>> 24) + "." + (ip >> 16 & 0xFF) + "." + (ip >> 8 & 0xFF) + "." + (ip & 0xFF);
  

export function  tcpInit(localAddr) 
    tcp.on(connect, () => 
      let tcp_status = 连接ok
      prompt.showToast(message:tcp_status)
    );
    tcp.on(message, value => 
      message_recv = resolveArrayBuffer(value.message)
      prompt.showToast(message:message_recv)
     return message_recv
    );
    tcp.on(close, () => 
      prompt.showToast(message:"tcp close")
    );
    tcp.bind( address: localAddr.address, port:localAddr.port, family: 1 )
      .then(() => 
        prompt.showToast(message:"bind tcp success",)
      ).catch(err => 
      prompt.showToast(message:"bind tcp failed")
      return
    );
  

export function tcpRecv() 
  tcp.on(message, value => 
    message_recv = resolveArrayBuffer(value.message)
    prompt.showToast(message:message_recv)
  );
  return message_recv


export function tcpConnect(localAddr,targetAddr) 
    tcp.getState()
      .then((data) => 
        if (data.isClose) 
          tcpInit(localAddr)
        
        // 连接
        tcp.connect(
          
            address:  address: targetAddr.address, port: targetAddr.port, family: 1 , timeout: 2000
          
        ).then(() => 
          prompt.showToast(message:" tcp connect successful")
        ).catch((error) => 
          prompt.showToast(message:"tcp connect failed")
        )
      )
  

export function tcpSend(msg:string) 
    tcp.getState().then((data) => 
      if (data.isConnected) 
        // 发送消息
        tcp.send(
           data: msg, 
        ).then(() => 
          prompt.showToast(message: msg+" send message successful")
        ).catch((error) => 
          prompt.showToast(message:"send failed")
        )
       else 
        prompt.showToast(message:"tcp not connect")
      
    )
  

 export function tcpClose() 
    tcp.close().then(() => 
      prompt.showToast(message:断开TCP)
    ).catch((err) => 
      prompt.showToast(message:"tcp 断开失败")
    )
    tcp.off(close);
    tcp.off(message);
    tcp.off(connect);
  

  // 解析ArrayBuffer
  export function resolveArrayBuffer(message: ArrayBuffer): string 
    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 !== "\\n") 
          str += c
        
      
      return str;
    
  

完成封装后即可在设备页面进行调用,设备页面使用统一模板,设计简约直观,多功能测量笔实现代码如下:

// 导入 socket封装模块
import resolveIP,tcpInit, tcpConnect, tcpSend, tcpRecv, tcpClose, resolveArrayBuffer from ../common/setting

// 向笔请求数据
enum pullRequest 
  HEIGHT = h,
  TEMPERATURE = w ,
  HEART = x,
  POWER = b,
;

@Entry
@Component
struct MultiPen 
  @State message: string = 多功能测量笔
  @State height: string = 160
  @State temperature: string = 36.0
  @State heartSop: string = 88
  @State heartCount: string = 102
  private select: number = 1
  private people: string[] = [爷爷, 奶奶, 爸爸, 妈妈,小可爱]
  @State battery: string = 89
  @State InputIP: string = 192.168.43.149
  @State recvMsg: string = 00000000

  @State aboutPen: boolean = false
  @State inputShow:boolean = false

  // 本地ip
  localAddr = 
    address: resolveIP(wifi.getIpInfo().ipAddress),
    port:  8888
  ;

  // 目标ip
   targetAddr = 
    address: this.InputIP,
    family: 1,
    port: 8888
  

  build() 
      Column() 
        // 标题与返回按钮
        Row()
          Image($r(app.media.ic_public_back))
            .width(15%).height(5%).margin(top:20)
            .objectFit(ImageFit.Contain)
            .onClick(()=>
             router.push(url:pages/healthDevices)
            )
          Text(this.message)
            .fontSize(60).fontWeight(FontWeight.Bold)
            .margin(top:10).padding(left:20)
        .borderRadius(20).width(90%).align(Alignment.Start)
        Divider().height(2%)

        // 产品展示- 多功能测量笔、使用说明
        Row()
        
          if(this.aboutPen)
            Image($r(app.media.coverpen)).objectFit(ImageFit.Contain)
          
          else
            Image($r(app.media.multipen)).objectFit(ImageFit.Contain)
          
        .height(30%)

        Text(HealthPen)
          .fontSize(41)
          .fontWeight(FontWeight.Bold)

        // 开关、测量对象、电量、IP设置
        Row(space:40)
        
          Image($r(app.media.ic_power_on)).width(10%).objectFit(ImageFit.Contain).margin(left:20)
          .onClick(()=>
            tcpConnect(this.localAddr,this.targetAddr)
            this.recvMsg = tcpRecv()
          )
          Text(选择成员).width(5%)
          TextPicker(range: this.people, selected: this.select).width(20%).height(100%)
            .onChange((value: string, index: number) => 
              console.info(Picker item changed, value:  + value + , index:  + index)
            )
          Image($r(app.media.ic_statusbar_battery_powersaving)).width(18%).objectFit(ImageFit.Contain)
            .onClick(()=>
              tcpConnect(this.localAddr,this.targetAddr)
              tcpSend(pullRequest.HEIGHT)
              this.recvMsg = tcpRecv()
              this.battery  = tcpRecv()
            )
            .overlay(this.battery,  align: Alignment.Center)
          Image($r(app.media.ic_public_settings_filled)).width(10%).objectFit(ImageFit.Contain).margin(left:10)
          .onClick(()=>
          
            this.inputShow = !this.inputShow
          )

        .height(15%).borderRadius(20).backgroundColor(#F5F5F5).width(90%).margin(top:10).justifyContent(FlexAlign.Center)

        // 身高、体温
        Row(space:20)
        
          Column()
          
            Row(space:20)
            
              Image($r(app.media.ic_user_portrait)).height(100%).width(20%).objectFit(ImageFit.Contain)
              Text(身高).fontSize(40)
            .height(40%).justifyContent(FlexAlign.Start).width(100%)
            Text(this.height+ cm).fontSize(50).fontWeight(FontWeight.Bold)
          .borderRadius(20).backgroundColor(#F5FFFA).width(48%)
          .onClick(()=>
            tcpConnect(this.localAddr,this.targetAddr)
            tcpSend(pullRequest.HEIGHT)
            this.recvMsg = tcpRecv()
            this.height  = tcpRecv().substring(0,3)
          )
          Column()
          
            Row(space:20)
            
              Image($r(app.media.ic_user_portrait_select)).height(100%).width(20%).objectFit(ImageFit.Contain)
              Text(体温).fontSize(40)
            .height(40%).justifyContent(FlexAlign.Start).width(100%)
            Text(this.temperature+ ℃).fontSize(50).fontWeight(FontWeight.Bold)
          .borderRadius(20).backgroundColor(#FFF8DC).width(48%)
          .onClick(()=>
            tcpConnect(this.localAddr,this.targetAddr)
            tcpSend(pullRequest.TEMPERATURE)
            this.recvMsg = tcpRecv()
            this.temperature = tcpRecv().substring(3,7)
          )

        .height(15%).borderRadius(20).width(90%).margin(top:10)
      // 心率血氧、使用指导
        Row(space:20)
        
          Column()
          
            Row(space:20)
            
              Image($r(app.media.ic_gallery_shortcut_favorite)).height(100%).width(20%).objectFit(ImageFit.Contain)
              Text(this.heartCount).fontSize(50).fontWeight(FontWeight.Bold)
              Text(this.heartSop+%).fontSize(50).fontWeight(FontWeight.Bold)
            .height(40%).justifyContent(FlexAlign.Start).width(100%)
            Text(心率 血氧).fontSize(40)

          .borderRadius(20).backgroundColor(#FFFAFA).width(48%)
          .onClick(()=>
            tcpConnect(this.localAddr,this.targetAddr)
            tcpSend(pullRequest.HEART)
            this.recvMsg = tcpRecv()
            this.heartCount = tcpRecv().substring(7,9)
            this.heartSop = tcpRecv().substring(9,11)
          )
          Column()
          
            Image($r(app.media.coverpen)).height(50%).width(20%).objectFit(ImageFit.Contain)
            Text(使用指导).fontSize(40)
          .borderRadius(20).backgroundColor(#F5F5F5).width(48%)
          .onClick(()=>
            this.aboutPen = !this.aboutPen
          )

        .height(15%).borderRadius(20).width(90%)

        // 设备连接Ip
        if(this.inputShow)
          Row()
          
            TextInput( placeholder: 请输入设备IP: +tcpRecv()).width(75%).height(10%).fontSize(30)
              .placeholderColor("rgb(0,0,225)")
              .placeholderFont( size: 30, weight: 100, family: cursive, style: FontStyle.Italic )
              .onChange((value: string) => 
                this.InputIP = value
                this.targetAddr.address = value
              )
            Button(确认).height(60).margin(left:30).width(10%)
              .onClick(()=>
              tcpConnect(this.localAddr,this.targetAddr)
            )
          
        
      .justifyContent(FlexAlign.Start)
      .width(100%)
  
  // 先断开已有连接
  onPageShow()
    tcpClose()
  

多功能测量笔功能页面展示如下图:

2.3.3 多功能测量笔-硬件开发

为实现测量笔移动端数据传输,需要对硬件开发。多功能测量笔为自主设计,实现超声测身高、测温度、心率血氧测量。设计图与实物如下: 该部分涉及三款传感器的驱动开发,温度传感器使用Uart传输数据,mx30102心率血样使用ic2传输,超声波传感器使gpio直接驱动,更具声速测距的原理获取高度。目前已开发完传感器驱动,数据不稳定,仍在调试中。下面给出主要代码(max30102、socket开发参考本人早期分享帖:max30102开发Hi3861与DAYU200socket):

//业务逻辑代码:
//数据帧: 身高(178) + 体温(36.5)+ 心率(62)+ 血氧(99)共11位字符
		unsigned char msg_cmd = 0;
		// 读取止血数据
		while (1)
		
			if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1)
			
				printf("recv error \\r\\n");
			
			printf("recv :%s\\r\\n", recvbuf);
			msg_cmd = recvbuf[0];
		    printf("msg_cmd: %c\\n",msg_cmd);
			
			// 获取温度
		hi_uart_read(HI_UART_IDX_1,temp_buff,TEMP_SIZE);
			//hi_uart_read_timeout(HI_UART_IDX_1,temp_buff,BLOOD_SIZE,READ_TIME);  
			// temp_buff[0] = 0;
			// temp_buff[1] = 1;
			// temp_buff[2] = 2;
			// temp_buff[3] = 3;
	     	// printf("temp_buff: %s\\n",temp_buff);
            strcat(buf,temp_buff);

			// // 获取高度
			// height = get_distance();
			// height = 456.7;
			// Float2String(height,height_buff,1);
			printf("height_buff: %s\\n",height_buff);
			strcat(buf,height_buff);
			
			// 获取心率
            max_Data(&RED_channel_data, &IR_channel_data);
			// RED_channel_data =  88;
			Float2String(RED_channel_data,RED_buff,1);
			// IR_channel_data = 99;
			Float2String(IR_channel_data,IR_buff,1);
			// printf("RED_buff: %s\\n",RED_buff);
			// printf("IR_buff: %s\\n",IR_buff);
			strcat(buf,RED_buff);
			strcat(buf,IR_buff);

			// printf("buf= %s \\n",buf);
           
			if ((ret = send(new_fd, buf, strlen(buf)+1, 0)) == -1)
			
				perror("send : ");
			
			buf[0] = 0;
		

目前基本功能已实现,传感器数据稳定性有待提升,视频测试了将数据流传入DAYU200,如有下一阶段,将展示完整的测量效果,并添加oled显示功能。

【数据帧: 身高(178) + 体温(36.5)+ 心率(62)+ 血氧(99)共11位字符】

2.4 急救箱模块

2.4.1 急救知识教学模块

普及急救知识意义重大,科学的急救技能在危急时刻可化险为夷,软件中添加该模块意义即在于此。模块数据包括常用药物介绍、烫伤、海姆立克急救法等。这些数据均来自权威机构,但迫于版权,相关数据库目前仍在请求中。另外该模块还添加一台便携式肢体止血设备,在肢体大出血时可提供帮助,在2.4.2节详细介绍。下面先说明https请求数据的实现,为手续数据库同步做准备

// 权限、导入模块
import http from @ohos.net.http;
  // 每一个httpRequest对应一个http请求任务,不可复用
  httpRequest:http.HttpRequest = http.createHttp();
  GetDate()
    // 用于订阅http响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
    // 从API 8开始,使用on(headersReceive, Callback)替代on(headerReceive, AsyncCallback)。 8+
    prompt.showToast(message:Request Begin)
    this.httpRequest.request(
      // 填写http请求的url地址,可以带参数也可以不带参数。URL地址需要开发者自定义。
      // 请求的参数可以在extraData中指定
      "http://apis.juhe.cn/simpleWeather/query?key=397c9db4cb0621ad0313123dab416668&city=大连",
      
        method: http.RequestMethod.GET, // 可选,默认为http.RequestMethod.GET
        // 开发者根据自身业务需要添加header字段
        header: 
          Content-Type: application/json
        ,
        // 当使用POST请求时此字段用于传递内容
        extraData: 
          "data": "data to send",
        ,
        connectTimeout: 100000, // 可选,默认为60s
        readTimeout: 100000, // 可选,默认为60s
      , (err, data) => 
      if (!err) 
        // data.result为http响应内容,可根据业务需要进行解析
        console.info(Result: + data.result);
        this.message = JSON.stringify(data.result)

        console.info(code: + data.responseCode);
        // data.header为http响应头,可根据业务需要进行解析
        console.info(header: + JSON.stringify(data.header));
        console.info(cookies: + data.cookies); // 8+
        prompt.showToast(message:JSON.stringify(data.header))
       else 
        prompt.showToast(message:Request Failed)
        console.info(error: + JSON.stringify(err));
        // 当该请求使用完毕时,调用destroy方法主动销毁。
        this.httpRequest.destroy();
      
    
    );
  

// 展示请求到的数据(一般是key、value):
           Text("http 请求")
             .fontSize(50)
             .fontWeight(FontWeight.Bold)
             .onClick(()=>
             
               this.GetDate();
              )

2.4.2 便携式肢体止血-软件开发

便携式肢体止血设备软件页面如下图所示,整体布局与多功能测量笔类似,这里不再重复说明页面框架。该页面展示当前止血压力、持续时间,用户可通过软件查看,也可设置时间,当然在紧急情况下设备单独使用最为可靠,设备自带调压按钮,可独立工作。

2.4.3 便携式肢体止血-硬件开发

止为什么要开发止血设备呢? 因为止血过程中存在止血压力过大(小)、时间过长,引起肢体坏死或失血过多。参考相关文献【[1]气压止血带在四肢手术中应用的专家共识协作组. 气压止血带在四肢手术中应用的专家共识[J]. 中华麻醉学杂志, 2020, 40(10):7.】可知,止血压力、时间需要进行一定控制。

  • 设计的止血设备即是解决上述需求,可独立工作、止血压力可控,最大止血压力达50kPa,针对上肢完全冗余。设计结构与原理图如下:

为提升可靠性,hi3861仅作为中间模块,作为交互数据硬件介质,底层硬件控制完全依靠另一款mcu实现,可根据外部请求返回数据或执行相应动作。流程如下:

socket、uart在前文中已提到如何开发,这里不再赘述。hi3861业务逻辑代码如下:

		while (1)
		
			if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1)
			
				printf("recv error \\r\\n");
			
			printf("recv :%s\\r\\n", recvbuf);
			msg_cmd = recvbuf[0];
			
		    printf("msg_cmd: %c\\n",msg_cmd);
			switch (msg_cmd)
			
			case p:  // 压力
				msg_cmd = BEGIN;
				hi_uart_write(HI_UART_IDX_1, &msg_cmd, CMD_LEN);
			break;
			
	 		case t: // 时间
				 msg_cmd = CONNECT ; 
				hi_uart_write(HI_UART_IDX_1, &msg_cmd, CMD_LEN);
			break;
						
			default:
				break;
			
			hi_uart_read(HI_UART_IDX_1,uart_buff,BLOOD_SIZE);
			//hi_uart_read_timeout(HI_UART_IDX_1,uart_buff,BLOOD_SIZE,READ_TIME);  
	     	printf("uart_buff: %s\\n",uart_buff);   

			if ((ret = send(new_fd, uart_buff, BLOOD_SIZE, 0)) == -1)
			
				perror("send : ");
			
			uart_buff[0] = 0;
		

2.5 康复训练手套开发

该设备软件层面与其他设备类似,这里不再赘述。训练手套的思路是UI端下发训练强度指令,分为三种不同训练强度,硬件端hi3861接收到指令执行训练动作。下图是实物,演示见视频demo。 、

2.6 其他功能

除上述核心功能外,家庭活动(公益献血、健康教育等)、疫情防控消息、天气获取等小功能也有准备,奈何目前数据仅拿到天气API,疫情消息目前加载网易公开信息,拿到可用API可做一个精简页面。

// 和风实况天气 接口免费
@Component
struct FullWeather
  controller: WebController = new WebController();
  build() 
    Column()
    
            Web( src: https://widget-page.qweather.net/h5/index.html?md=0123456&bg=1&lc=auto&key=a400290f961647a884e98675bf8954d8&v=_1660208701535,
              controller: this.controller )
    
    .width(25%)
    .height(100%)
  

3. 家庭健康助理demo

作品视频: 家庭健康助理

想了解更多关于开源的内容,请访问:

51CTO 开源基础软件社区

https://ost.51cto.com/#bkwz

以上是关于#打卡不停更#家庭健康管理平台的主要内容,如果未能解决你的问题,请参考以下文章

基于单片机的测量心率脉搏健康系统设计与

华为哪个手环可以测心电图?

#打卡不停更# HarmonyOS - 基于ArkUI(ETS) 实现心电图组件

#打卡不停更# OpenHarmony - 应用开发入门指南

基于STM32设计的健康检测设备(测温心率计步)

基于STM32设计的健康检测设备(测温心率计步)