项目3 监 测 谎 言 项目3 本项目通过鸿蒙App控制Hi3861开发板,监测用户的心率,基于心跳变化,判断用户是否说谎。 3.1总体设计 本部分包括系统架构和系统流程。 3.1.1系统架构 系统架构如图31所示,Hi3861开发板与外设引脚连线如表31所示。 图31系统架构 表31Hi3861开发板与外设引脚连线 Hi3861开发板OLEDMAX30102血氧模块 GNDGNDGND 3.3VVINVIN GPIO13SDASDA GPIO14SCLSCL 3.1.2系统流程 系统流程如图32所示。 图32系统流程 3.2模块介绍 本部分包括OLED显示、WiFi模块、血氧模块、OneNET云平台和前端模块。下面分别给出各模块的功能介绍及相关代码。 3.2.1OLED显示 本模块将问题呈现到OLED显示屏上,重点在于实现I2C通信,调用其常用API接口,如表32所示。 表32API接口与说明 API接口说明 I2cInit(WifiIotI2cIdx id, unsigned int baudrate)用指定的波特速率初始化I2C设备 I2cDeinit (WifiIotI2cIdx id)取消初始化I2C设备 I2cWrite(WifiIotI2cIdx id, unsigned short deviceAddr, const WifiIotI2cData*i2cData)将数据写入I2C设备 I2cRead(WifiIotI2cIdx id, unsigned short deviceAddr, const WifiIotI2cData*i2cData)从I2C设备中读取数据 I2cWriteread(WifiIotI2cIdx id, unsigned short deviceAddr, const WifiIotI2cData*i2cData)向I2C设备发送数据并接收数据响应 I2cRegisterResetBusFunc(WifiIotI2cIdx id, WifiIotI2cFunc pfn)注册I2C设备回调 I2cSetBaudrate(WifiIotI2cIdx id, unsigned int baudrate)设置I2C设备的波特率 OLED显示的相关代码如下: //显示字符串 //x,y:起点坐标 //size1:字体大小 //chr:字符串起始地址 void OLED_ShowString(u8 x,u8 y,char *chr,u8 size1) { while((*chr>=' ')&&(*chr<='~'))//判断是否为非法字符! { OLED_ShowChar(x,y,*chr,size1); x+=size1/2; if(x>128-size1)//换行 { x=0; y+=2; } chr++; } } //在指定位置显示一个字符,包括部分字符 //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<size2;i++) { //temp=asc2_1206[chr1][i]; if(size1==12) {temp=asc2_1206_1[chr1][i];}//调用1206字体 else if(size1==16) {temp=asc2_1608_1[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; } } } } //清屏函数 void OLED_Clear(void) { u8 i,n; for(i=0;i<8;i++) { for(n=0;n<128;n++) { OLED_GRAM[n][i]=0; //清除所有数据 } } OLED_Refresh(); //更新显示 } //画点 //x:0~127 //y:0~63 void OLED_DrawPoint(u8 x,u8 y) { u8 i,m,n; i=y/8; m=y%8; n=1<<m; OLED_GRAM[x][i]|=n; } //清除一个点 //x:0~127 //y:0~63 void OLED_ClearPoint(u8 x,u8 y) { u8 i,m,n; i=y/8; m=y%8; n=1<<m; OLED_GRAM[x][i]=~OLED_GRAM[x][i]; OLED_GRAM[x][i]|=n; OLED_GRAM[x][i]=~OLED_GRAM[x][i]; } hi_u32 my_i2c_write(hi_i2c_idx id, hi_u16 device_addr, hi_u32 send_len) { hi_u32 status; hi_i2c_data es8311_i2c_data = { 0 }; es8311_i2c_data.send_buf = g_send_data; es8311_i2c_data.send_len = send_len; status = hi_i2c_write(id, device_addr, &es8311_i2c_data); if (status != HI_ERR_SUCCESS) { printf("===== Error: I2C write status = 0x%x! =====\r\n", status); return status; } return HI_ERR_SUCCESS; } //I2C Write Command void Write_IIC_Command(unsigned char IIC_Command) { g_send_data[0] = 0x00; g_send_data[1] = IIC_Command; my_i2c_write(HI_I2C_IDX_0, 0x78, 2); } //I2C Write Data void Write_IIC_Data(unsigned char IIC_Data) { g_send_data[0] = 0x40; g_send_data[1] = IIC_Data; my_i2c_write(HI_I2C_IDX_0, 0x78, 2); } 3.2.2WiFi模块 实现WiFi连接包括寻找可用热点和连接热点,相关代码请扫描二维码获取。 3.2.3血氧模块 通过MAX30102模块读取信息并计算得到心率,MAX30102 FIFO 的深度是32,每个buf是6字节(两通道数据,每通道3字节)。例如,提取的心率值,是第3~5个buf组合而成,如图33所示,相关代码如下: 图33MAX30102模块端口 /** * @brief Initialize MAX30102 * @param redAddr the register address to Read or Writen. * @return Returns{@link IOT_SUCCESS} if the operation is successful */ void max30102_init(void) { uint8_t max30102_info = 0; printf("\r\n come in MAX30102 init\r\n"); max30102_reset(); MAX_Read_Data(REG_PART_ID,&max30102_info,1); printf("REG_PART_ID = %d \n",max30102_info); //测试是否输出ID,I2C是否正常 //if(max30102_info == 1)//ID不确定是多少测试 //{ //printf("\r\n MAX30102 init Faild \r\n"); //return ; //} printf("\r\n MAX30102 init Successful \r\n"); MAX_Write_Data(REG_INTR_ENABLE_1, 0xc0,1); //INTR setting MAX_Write_Data(REG_INTR_ENABLE_2, 0x00,1); MAX_Write_Data(REG_FIFO_WR_PTR, 0x00,1); //FIFO_WR_PTR[4:0] MAX_Write_Data(REG_OVF_COUNTER, 0x00,1); //OVF_COUNTER[4:0] MAX_Write_Data(REG_FIFO_RD_PTR, 0x00,1); //FIFO_RD_PTR[4:0] MAX_Write_Data(REG_FIFO_CONFIG, 0x0f,1); //sample avg = 1, fifo rollover=false, fifo almost full = 17 MAX_Write_Data(REG_MODE_CONFIG, 0x03,1); //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED MAX_Write_Data(REG_SPO2_CONFIG, 0x27,1); //SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS) MAX_Write_Data(REG_LED1_PA, 0x24,1); //Choose value for ~ 7mA for LED1 MAX_Write_Data(REG_LED2_PA, 0x24,1); //Choose value for ~ 7mA for LED2 MAX_Write_Data(REG_PILOT_PA, 0x7f,1); //Choose value for ~ 25mA for Pilot LED } /** * @brief read FIFO data in max30102 FIFO register 0x07 * @param RED_channel_data * @param IR_channel_data */ void max30102_FIFO_Read_Data(uint8_t *RED_channel_data, uint8_t *IR_channel_data) { printf("begin to read\n"); uint8_t buff[6]; //LSB /*组合数据 uint8_t H,M,L; H=buff[0]&0x03; //bit17-bit16 M=buff[1]; //bit8-bit15 L=buff[2]; //bit0-bit7 *RED_channel_data = (H<<16)|(M<<8)|L;*/ int res; res=MAX_Read_Data(REG_FIFO_DATA, &buff ,6); if(res == IOT_SUCCESS) { printf("read max30102 success\n"); *RED_channel_data=((buff[0]<<16)|(buff[1]<<8)|(buff[2]) & 0x03ffff); //buff[0-2] 组合 *IR_channel_data=((buff[3]<<16)|(buff[4]<<8)|(buff[5]) & 0x03ffff); //buff[3-5] 组合 } else{ printf("read max30102 failed\n"); } } 3.2.4OneNET云平台 本部分包括创建账号、创建产品、添加设备和获取信息。 1. 创建账号 登录网页https://open.iot.10086.cn/passport/reg/,按要求填写注册信息后进行实名认证。 2. 创建产品 进入Studio平台后,在全部产品中选择多协议接入,单击“添加产品”按钮,在弹出页面中按照提示填写基本信息。本项目采用MQTT协议接入。 3. 添加设备 单击“创建产品”按钮,进入详情页面,单击菜单栏中的设备列表,按照提示添加设备。 4. 获取信息 在代码中,需要获取以下认证信息。 #define ONENET_INFO_DEVID "954036868" #define ONENET_INFO_AUTH "20220604" #define ONENET_INFO_APIKEY "Ee4chljcr7Cv1k2IOJ1ZZHZ36CY=" #define ONENET_INFO_PROID "524506" #define ONENET_MASTER_APIKEY "01tIq=T30=4SiZAdw1VsVXgp7Sg=" (1) ONENET_INFO_DEVID和ONENET_INFO_AUTH。通过查看设备详情获取ID和鉴权信息,如图34所示。 图34获取设备ID和鉴权信息 (2) INFO_APIKEY,添加APIKey,如图35所示。 图35获取APIKey (3) INFO_PROID和MASTER_APIKEY。获取产品ID和MasterAPI key,如图36所示。 图36获取产品ID和MasterAPI key 3.2.5前端模块 本部分包括界面设计和程序逻辑。 图37界面布局 1. 界面设计 在ability_main.xml文件中将心率图片、当前心率、心率值、问题输入文本框以及输入完毕按钮设计为垂直布局,如图37所示。 (1) 图片插入。将heartbeat.jpg图片保存在entry/src/main/java/resource/base/media文件夹下,通过ability_main.xml对其进行调用,并设置其ID、长、宽、上边距、缩放类型等。 (2) 文本显示。通过Text组件创建文本内容,并设置其ID、长、宽、上边距、对齐方式、文本大小等,相关代码如下: <Text ohos:id="$+id:text_tem"//文本ID为text_tem ohos:below="$id:text_title"//文本在text_title组件下方 ohos:height="match_content"//文本高度 ohos:width="match_parent"//文本宽度 ohos:text_alignment="center"//对齐方式为中心对齐 ohos:top_margin="30vp"//上边距 ohos:text="1"//文本内容 ohos:text_size="30vp"//文本大小 /> (3) 文本输入框。使用TextField组件实现文本输入框,TextField组件与Text组件属性类似,独有属性为basement(输入框基线)以及Bubble(文本气泡),TextField组件相关代码如下: <TextField ohos:padding="10vp"//设置内边距 ohos:text_font="#000099 " //设置文本字体颜色 ohos:hint="请输入问题..."//设置提示文字 ohos:element_cursor_bubble="$graphic:bubble"//设置气泡Bubble ohos:multiple_lines="true"//多行显示 ohos:enabled="true" //设置可用状态,true可用,false不可用 ohos:basement="#000099 " //设置基线颜色 ohos:input_enter_key_type="enter_key_type_go"//输入键类型 ohos:text_input_type="pattern_text"//输入内容类型(数字、密码、文本等) /> (4) 使用Button组件实现一个按键功能,表示文本已输入完毕。后续逻辑设计需要通过该按键的单击事件获取文本输入框中的内容,并设置其ID、长、宽、按键背景颜色、上边距、对齐方式、文本大小等信息。 2. 程序逻辑 鸿蒙App软件部分的核心是与OneNET云平台进行交互,分为查询数据流和获取数据流两部分。 (1) 查询数据流详情。 请求方式: GET。 URL构成: http://api.heclouds.com/devices/device_id/datastreams/datastream_id。 其中,device_id需要替换为设备ID,datastream_id需要替换为数据流ID,利用OneNET云平台提供的API调试工具执行请求,观察返回内容,发现其为一个Json对象,如表33和图38所示。 表33返回参数 参 数 名 称格式说明 errnoint调用错误码, 0表示调用成功 errorstring错误描述,succ表示调用成功 datajson接口调用成功后返回的设备信息 idstring数据流ID create_timestring数据流创建时间 update_atstring最新数据上传时间 current_valuestring/int/json最新数据点 图38获取数据的API调试及返回内容 由查询到的数据流可知,返回内容为Json对象,根据其结构,定义一个JsonBean类,类的成员为参数errno、data、error与Json结构对应。 public static class JsonBean{ public int errno; public Data data; public String error; ... 定义一个内部类Data进行解析。 static class Data{ public String update_at; public String id; public String create_time; public float current_value; ... 对每个对象定义GET方法,用于在外部获取JsonBean内部对象的值。 public int getErrno() { return errno;} public Data getData() { return data;} public String getError() { return error;} ... JsonBean类定义完成后,通过Json库对其进行解析,在鸿蒙操作系统中实现这个操作需要用到外部库,本项目使用Google开发的Gson库。使用前,需要在build.gradle文件中导入依赖,输入命令“implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0'”按钮后单击“立即同步”按钮。 在发起GET请求获取数据的函数中,利用JsonBean类和Gson获取键值对,从而取得心率等关键信息。 JsonBean jsonBean = new Gson().fromJson(result, JsonBean.class); 在App界面中,实时呈现检测者的心率数据。实现方法是定义线程类myEventHandler,并通过sendEvent方法将数据投递到新线程。 myEventHandler.sendEvent(InnerEvent.get(1002, jsonBean)); if(event.eventId==1002){ JsonBean jsonBean = (JsonBean) event.object; float tem = jsonBean.getData().getCurrent_value(); //获取最新数据点值 String temText = Float.toString(tem); //将其呈现在界面的temText位置 textTem.setText(temText);} 定义timer类,重复执行任务,使心率数据自动更新。 timer.schedule(new TimerTask() { @Override public void run() { doGet(getUrl);} },delay:0,period:500); //每0.5s执行一次任务后更新界面 (2) 获取数据流。 请求方式: POST。 URL: http://api.heclouds.com/cmds?device_id=/device_id。 利用OneNET云平台提供的API调试工具执行请求,由图39可以看出,返回内容为“"errno": 10, "error": "device not online: 954036868"”,表示设备不在线。在此不需要关注返回内容,重点在于下达命令的数据类型。OneNET云平台可接收POST请求的body内容为: Json、string或二进制数据(小于64KB)。 图39下达命令的API调试及返回内容 通过单击事件获取文本内容。在进行界面设计时,应实现按钮组件的实体,对按钮绑定单击事件,使用当前类作为接口的实现类: but.setClickedListener(this)。 事件绑定完毕后,通过onClick函数实现单击按钮并获取文本框中内容的操作: “String message=tf.getText();”。其中,message为获取的文本框内容,后续下达命令可以直接调用它。 为了验证文本框内容是否成功获取,使用Toast弹框将信息显示在按键下方,此部分起验证作用,成功后可以删除。 ToastDialog td=new ToastDialog(context:this); //补充上下文信息,为当前页面 td.setTransparent(true); //设置Toast的背景 td.setAlignment(LayoutAlignment.BOTTOM); //位置(默认居中) td.setOffset(0,200); //设置一个偏移 td.setText(message);//设置Toast的内容为获取的文本框信息 图310文本信息 弹框出现于按键下方,应正确显示输入的文本信息,如图310所示。 为防阻塞主线程启用新线程发起POST请求,实时呈现OLED界面。在dopost函数中,将string类型的命令放入POST请求的body中访问API。 postConnection.setRequestProperty("Content-Type","application/octet-stream"); postConnection.setRequestProperty("api-key", api_key); new Thread(new Runnable() { @Override public void run() { doPost(postUrl, message, 1102);} }).start(); 首先,单击“输入完毕”按钮后,下达发送message的命令,提问者所输入的问题将显示在OLED屏幕上; 然后,借助MyEventHandler类更新界面,将当前的屏幕显示同步到页面中。 3.3成果展示 计算机屏幕左侧为OneNET云平台的数据波动,右侧为App端的界面,下方为Hi3861开发板实时刷新的展示效果,如图311所示; 单击按键后的OLED显示相应数据,如图312所示。 图311开发板实时刷新结果 图312单击按键后的OLED 3.4元件清单 完成本项目所需的元件及数量如表34所示。 表34元件清单 元件/测试仪表数量元件/测试仪表数量 面包板1个OLED显示屏1个 Hi38611个MAX30102血氧模块1个 项目4 监测心率及血氧