Skip to main content

moregeek program

#打卡不停更#家庭健康管理平台_鸿蒙社区的博客-多极客编程

0. 项目简介


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


image.pngimage.png
image.png


家庭健康助理是集健康数据测量与管理、急救设备及使用指导、疫情防控实况、日常生活建议为一体的健康管理平台。健康助理核心是以数据为驱动,软硬结合,通过若干生理数据采集设备获取家庭成员健康状态,并将数据同步至移动端(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核心部分是数据采集、医疗相关知识模块、健康数据管理三部分。



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

image.png


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


image.png


下面以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)
})

image.png


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


// 导入模块
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节进行介绍。设备管理页面框架如下:


image.png


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


 // 测量笔
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()
}
}

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


image.png


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


为实现测量笔移动端数据传输,需要对硬件开发。多功能测量笔为自主设计,实现超声测身高、测温度、心率血氧测量。设计图与实物如下:
image.png
该部分涉及三款传感器的驱动开发,温度传感器使用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 便携式肢体止血-软件开发


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


image.png


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


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



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

image.pngimage.png


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


image.png


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。

6c45fb81374b8135218b979e7491ca3.png
image.png


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


©著作权归作者所有:来自51CTO博客作者开源基础软件社区官方的原创作品,请联系作者获取转载授权,否则将追究法律责任

基于springboot的服务端开发脚手架-自动生成工具_生而为人的博客-多极客编程

继之前的专题系列课程: ​​从零开始搭建grpc分布式应用​​完整DEMO:​​基于Springboot的Rpc服务端开发脚手架(base-grpc-framework)​​后带来一款项目自动手成工具(由于包路径等原因,完整demo想应用在实际开发中需要改很多代码),可以设置成自己公司的一些规定包路径等。自动生成工具源码地址:​​源码下载​​此为一工具,默认生成java服务工程,但可经过配置生成其

【c语言练习_1】用c语言实现仿射变换加/解密_秃头程序媛的博客-多极客编程

1.什么是仿射密码?由加法密码和乘法密码结合而成加密算法:y≡a*x+b(mod26) //y为密文解密算法:x≡a-1 (y-b)(mod26) //x为明文a,b为密钥,a,b均与p互素; gcd(a,p)=1;gcd(b,p)=1a-1为a在Zm群的乘法逆元2.代码#include <stdio.h>#include <string.h>//加密int encrypt

1行python代码,用腾讯云 ai 录音文件识别,实现短视频字幕批量处理_程序员晚枫的博客-多极客编程

大家好,我是在重庆的Python程序员晚枫,全网同名。 经常遇到身边的朋友,想从视频中提取出文字,尤其是自媒体博主,如果能直接把视频转换成文章,那可太省时间了。 通过一阵检索,发现网上有很多付费软件可以提供视频提取语音的功能,但是价格都不低。 作为程序员,肯定不满足于付费工具的东西,正好看到腾讯云AI平台正在搞活动,1元即可购买60个小时的录音文件识别时长,另外还有多种福利的赠送,于是果断购买。

源码学习之mybatis的底层查询原理_京东云官方的博客-多极客编程

导读本文通过MyBatis一个低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查询流程,从配置文件的解析到一个查询的完整执行过程详细解读MyBatis的一次查询流程,通过本文可以详细了解MyBatis的一次查询过程。在平时的代码编写中,发现了MyBatis一个低版本的bug(3.4.5之前的版本),由于现在很多工程中的版本都是低于3.4.5的,因此在这里用一个简单的例子复

【c语言练习_2】用c语言实现凯撒密码加密解密_秃头程序媛的博客-多极客编程

1.凯撒密码简介又叫循环移位密码.它的加密方法是将明文中的每个字母用此字符在字母表中后面第k个字母替代.它的加密过程可以表示为下面的函数:E(m)=m+k(mod n)其中:m为明文字母在字母表中的位置数;n为字母表中的字母个数;k为密钥;E(m)为密文字母在字母表中对应的位置数.2.代码#include <stdio.h>#include <string.h>//加密in

css:利用伪类处理图片加载失败的样式问题_彭世瑜的博客-多极客编程

实现效果 实现代码 index.html <h2>未做错误处理</h2> <div style="font-size: 0"> <img src="./img/image.jpg" alt="" /> <img src="./img/image-1.jpg" alt="" /> <

#冲刺创作新星#一起学做鸿蒙“羊了个羊”_鸿蒙社区的博客-多极客编程

简介 最近大火了一个小游戏火遍朋友圈,我们就一起看看如何能用OpenHarmony学习做个”羊了个羊“。本文中引用的图片资源均来自:https://github.com/Jetereting/ylgy。 开发 1. HAP应用建立 《#跟着小白一起学鸿蒙#[六]如何编写一个hap应用》里我们介绍了简单的Hap应用的开发以及基础控件的介绍,这里我们就不赘述Hap项目的建立过程,以下就是基础的Ha

#打卡不停更# openharmony-arkui(ts)声明式开发之列表拖动排列_鸿蒙社区的博客-多极客编程

作者:梁青松 项目介绍 本项目基于OpenHarmony的ArkUI框架:TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址:基于TS扩展的声明式开发范式,因为OpenHarmony的API相对于HarmonyOS的API,功能上比较完善和成熟的,有些新的技术也早早接触到,所以本项目直接使用OpenHarmony SDK开发。 工具版本: DevEco Studio 3.0 Relea

#打卡不停更#编译效率快三倍,使用wsl2编译openharmony嘎嘎得劲!!_鸿蒙社区的博客-多极客编程

笔者发现使用wsl(Windows Subsystem for Linux,适用于 Linux 的 Windows 子系统)编译openharmony镜像比传统虚拟机快3倍以上。编译环境换成wsl后,编译ohos只需要传统虚拟机上三分之一的时间。下面分享使用经验如下: (目录) 为什么使用WSL2比传统虚拟机编译OpenHarmony快? 以下是微软官方给的解释:https://learn.mi

#打卡不停更#智能喂食器_鸿蒙社区的博客-多极客编程

一、介绍 ​ 随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在宠物身上的基础花销是用来购买宠物粮,为了“小主”们的饮食可以说操碎了心,比起让它们吃好吃饱他们更关心狗粮猫粮的食品安全、生活舒适度、身心健康等。这也让宠物智能设备成为刚需产品,随着

#冲刺创作新星# #跟着小白一起学鸿蒙# [七] 写个napi子系统_鸿蒙社区的博客-多极客编程

作者:王石 在《#跟着小白一起学鸿蒙#[六]第一个hap应用》我们熟悉了如何在开源鸿蒙开发hap应用,后期的文章我们会写在hap应用里调用系统库甚至是动态库。此篇文章,我们主要是熟悉下NAPI框架,并一起写一个支持NAPI的子系统,这样以后当我们想在hap应用里加自己功能的时候就可以方便的添加。 NAPI框架简介 NAPI(Native API)组件是一套对外接口基于Node.js N-API规范

#冲刺创作新星# #跟着小白一起学鸿蒙# [八] 蓝牙应用_鸿蒙社区的博客-多极客编程

蓝牙简介 蓝牙(Bluetooth)是一个短距离无线通信标准,用于在手机、计算机和其他电子设备之间通信。在 Linux 中权威的蓝牙协议栈实现是 BlueZ。其本身自带了很多有用的工具,如bluetoothctl,hcidump和monitor。本章我们主要介绍开源鸿蒙中的蓝牙应用和接口以及和Linux上工具软件bluetoothctl的对比。 经典蓝牙: 在2010年以前,我们谈论的蓝牙就是经典