如何为你打造一个家庭健康管理平台
发布时间:2022-10-05 11:10 所属栏目:52 来源:互联网
导读:1、项目简介 身体健康是一切生产生活的硬性基
1、项目简介 身体健康是一切生产生活的硬性基础。健康是福,一切安好,未来才可期。为什么经常跑步体重缺还在往上飘?突发紧急情况怎么处理?在数字时代,如何更好的为人们提供健康福祉、普及健康知识?如何进一步驱动个人健康管理是的值得研究的方向。为此,我们团队打造了一个健康管理平台——家庭健康管理平台。概览如下图所示: #打卡不停更#家庭健康管理平台-开源基础软件社区#打卡不停更#家庭健康管理平台-开源基础软件社区 #打卡不停更#家庭健康管理平台-开源基础软件社区 家庭健康助理是集健康数据测量与管理、急救设备及使用指导、疫情防控实况、日常生活建议为一体的健康管理平台。健康助理核心是以数据为驱动,软硬结合,通过若干生理数据采集设备获取家庭成员健康状态,并将数据同步至移动端(DAYU200),软件进行数据管理与可视化,为每个成员打造健康日历,为每一个家庭提供数字化健康服务。 为展示平台的核心能力,开发的具体内容包含有:基于OpenHarmony驱动的移动端软件、多功能生理数据测量笔、便携式止血设备、康复训练手套。 交互软件完成了设备管理、数据可视化(分布式数据存储)、急救知识展示功能;多功能生理数据测量笔展示平台数据测量能力,测量包括体温、身高、心率血氧;便携式止血设备是为展示平台急救设备及使用指导能力,提供止血教学、实时查看止血状态服务;康复训练手套是为患者提供手部康复训练服务。 对应的联合国17项可持续发展目标 3.良好健康与福祉、 4.优质教育 2、开发环境及技术特点 OH系统版本:OpenHarmony 3.1 Release。 开发环境:DevEco studio 3.0.0.900。 技术特点: ArkUI (ets) 、软硬结合、http数据请求、加载web组件、socket通信、健康数据对象、分布式数据管理(开发中)。 基础开发指导:环境准备、设备开发指导 、 应用开发指导。 3、开发流程 (1)家庭健康管理平台框架 如下图所示,家庭健康管理平台由家庭助理APP、分布式设备组成。APP核心部分是数据采集、医疗相关知识模块、健康数据管理三部分。 数据采集使用各种医疗设备进行采集,本项目中以多功能测量笔为例,展示数据采集、自动同步的功能,免去复杂的手动输入数据操作。 针对医疗相关知识模块,开发了便携式止血设备,并对肢体止血进行指导。 健康数据管理负责整合各项数据,如天气、疫情信息、成员体温等。在成员信息页展示成员档案。还可利用分布式特性将数据传输至另一台设备,实现健康数据共享,方便管理家庭成员数据,更好的服务家庭。(涉及轻量数据存储,在开发中) #打卡不停更#家庭健康管理平台-开源基础软件社区 注: 对应的联合国17项可持续发展目标 3.良好健康与福祉 4.优质教育。 APP 主页: #打卡不停更#家庭健康管理平台-开源基础软件社区 下面以APP页面为主线,对项目开发技术细节进行展开说明。 (2)家庭成员健康档案页 数据抽象 将成员健康档案抽象为一个类,包含年龄、身高、心率等数据。另外定义对象操作方法,方便访问数据,新建对象。实现代码如下: 复制 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; } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 展示信息 使用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) }) 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. #打卡不停更#家庭健康管理平台-开源基础软件社区 展示的数据涉及分布式轻量化数据存储,在另外一台设备可查看成员信息。 完整的数据库开发流程封装如下: 复制 // 导入模块 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); } } }); } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. (3)数据采集模块 设备管理 数据采集模块负责管理家用医疗设备、健身器械,数据采集。理论上可接入如体脂秤、血压血糖仪、跑步机等常用设备。针对该模块,项目开发了多功能测试笔,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' }) }) 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 多功能测量笔-软件开发 涉及模块、权限: 复制 import socket from '@ohos.net.socket' import prompt from '@ohos.prompt'; import wifi from '@ohos.wifi'; "ohos.permission.INTERNET" "ohos.permission.GET_WIFI_INFO" 1. 2. 3. 4. 5. 当在设备列表页面点击对应设备后,将跳转到对应的设备控制页面。项目以多功能数据测量笔为例,进行开发细节介绍。首先每台设备均使用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; } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 完成封装后即可在设备页面进行调用,设备页面使用统一模板,设计简约直观,多功能测量笔实现代码如下: 复制 // 导入 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() } } (编辑:ASP站长网) |
相关内容
网友评论
推荐文章
热点阅读