# OH feeder **Repository Path**: blackvirus/oh-feeder ## Basic Information - **Project Name**: OH feeder - **Description**: 基于openharmony开发的智能喂食器,实现了: ①、可视化显示喂食数据 ②、自动喂食功能 ③、定时喂食 ④、手动喂食 ⑤、余粮显示功能 ⑥、远程观看实时视频 ⑦、自定义喂食的量 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2024-12-05 - **Last Updated**: 2024-12-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于openharmony开发的智能喂食器 ## 一、介绍 ​ 随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在宠物身上的基础花销是用来购买宠物粮,为了“小主”们的饮食可以说操碎了心,比起让它们吃好吃饱他们更关心狗粮猫粮的食品安全、生活舒适度、身心健康等。这也让宠物智能设备成为刚需产品,随着 5G、大数据、AI、云计算等技术的快速发展,各类智能养宠硬件如雨后春笋般涌现,在人的吃喝拉撒还需要“手动”的时候,宠物已经进入智能生活了。 ​ 而我们的作品就是用于更好的对宠物的投喂进行智能化管理,首先它基于OpenHarmony开发板开发,可以通过使用APP进行定时喂食,同时可以远程观看实时视频,同时也可以识别到小猫进行喂食,更好的知道家里宠物的状态;可以通过喂食器APP查看内余粮,方便用户更好的管理宠物的饮食健康和规律。 ### 解决多项联合国17项可持续发展目标中的问题 - #### 目标 2:零饥饿 - #### 目标 3:良好健康与福祉 - #### 目标 9:产业、创新和基础设施 ### [演示视频](https://www.bilibili.com/video/BV1Ug411k7VG):https://www.bilibili.com/video/BV1Ug411k7VG ### 整体视图 ![6ea5bee79865ac38dd9945902fc3901](media/6ea5bee79865ac38dd9945902fc3901.jpg) ### 主要功能介绍 ①、可视化显示喂食数据 ②、自动喂食功能 ③、定时喂食 ④、手动喂食 ⑤、余粮显示功能 ⑥、远程观看实时视频 ⑦、自定义喂食的量 ## 二、目录介绍 ``` ├─OHfeeder //智能喂食器应用代码 │ ├─Device_hi3861 //智能喂食器设备代码 │ ├─flaskProject //后端代码 │ ├─CameraServer //摄像头代码 │ └─3DModel //智能喂食器3D建模 ``` ## 三、系统架构 #### 系统结构图 ![1660463875261](media/1660463875261.png) ## 四、开发指导 ### ①、openharmony应用开发 #### 1.导航栏开发 ![1660499669373](media/1660499669373.png) 通过使用js 自带的tabs组件开发导航栏,是非常简单的
主页
控制
设置
#### 2.自定义组件喂食量可视化环状图 ![1660499698008](media/1660499698008.png) 对于自定义组件的开发,可以使用canvas去开发,在这里我就使用canvas去开发。 新建common/component文件夹,并新建doughnubt pages ![1660500275710](media/1660500275710.png) ```html
``` 这里要注意组件的生命周期和Pages的是不同的,所以onShow()方法在这里是无效的,这里使用onLayoutReady()代替onShow()方法 初始化画板数据: ```js onLayoutReady(){ ​ var el = this.$refs.canvasb; ​ this.ctx = el.getContext("2d",{antialias: true}); ​ // 清除画布上的内容 ​ this.gradient = this.ctx.createLinearGradient(0,0, 0,300); ​ this.gradient.addColorStop(0, '#ff149ee9'); ​ this.gradient.addColorStop(1, '#ff9ae2ee'); ​ this.ondoughnutline(); ​ this.setLength(this.max,this.min); ​ this.charss(this.min); ​ } ``` 画小圆环: ```js ondoughnutline() { ​ this.ctx.clearRect(0, 0, this.xlen, this.ylen); ​ this.ctx.lineCap = 'round'; ​ // 描边的宽度 ​ this.ctx.lineWidth = 2; ​ // 创建一个新的绘制路径 ​ this.ctx.beginPath(); ​ // 路径从当前点移动到指定点 ​ this.ctx.strokeStyle = this.gradient; ​ this.ctx.arc(this.xlen/2, this.ylen/2, 200, 0, Math.PI * 2); ​ this.ctx.stroke(); ​ }, ``` 组件的传参: 通过我们引用自定义组件时,通过props传参,同时我们可以使用$watch对数据进行监听 ```html //父
``` ```js //子 props: ['max']['min'], ​ onInit(){ ​ this.$watch('min', 'onPropertyChange'); ​ }, ​ onPropertyChange(newW,oldW){ ​ console.info('alls 属性变化 ' + newW + ' ' + oldW); ​ if(this.max >= newW) ​ { ​ this.onChange(this.max, this.min) ​ } ​ }, ``` 其余的自定义组件喂食量可视化环状图代码我就不在细讲,你们可以就看我的开源代码 #### 3.自定义图传视频组件 ![1660501665258](media/1660501665258.png) 这里采用的自定义的图传视频组件,是采用http获取图片然后通过canvas进行绘画
``` props: ['httpsrc'], ​ onInit() { ​ this.$watch('httpsrc', 'onPropertyChange'); ​ this.httpClientImpl = new httpclient.HttpClient.Builder().setConnectTimeout(15, httpclient.TimeUnit.SECONDS).setReadTimeout(15, httpclient.TimeUnit.SECONDS).build(); ​ this.request=new httpclient.Request.Builder() ​ .url(this.httpsrc) ​ .method('GET') ​ .addHeader("Content-Type", "application/json") ​ .params("token", "yukoyu") ​ .build(); ​ }, ​ onPropertyChange(newW,oldW){ ​ this.request=new httpclient.Request.Builder() ​ .url(newW) ​ .method('GET') ​ .addHeader("Content-Type", "application/json") ​ .params("token", "yukoyu") ​ .build(); ​ }, ​ videorun(){ ​ this.runflag=!this.runflag; ​ if(this.runflag) ​ { ​ this.intervalID = setTimeout(this.onLiveVideo , 100); ​ }else{ ​ clearTimeout(this.intervalID); ​ } ​ }, ​ onLiveVideo(){ ​ this.httpClientImpl.newCall(this.request).enqueue((result) => { ​ //console.log("success: " + JSON.stringify(result)) ​ this.img.src = (JSON.parse(result.data)).data; ​ var pat = this.ctx.createPattern(this.img, 'no-repeat'); ​ this.ctx.fillStyle = pat; ​ this.ctx.fillRect(0, 0, 720, 420); ​ if(this.runflag) ​ { ​ clearTimeout(this.intervalID); ​ this.intervalID = setTimeout(this.onLiveVideo , 100); ​ }else{ ​ this.img.src = '/common/images/live.png'; ​ var pat = this.ctx.createPattern(this.img, 'no-repeat'); ​ this.ctx.fillStyle = pat; ​ this.ctx.fillRect(0, 0, 720, 420); ​ } ​ }, (error) => { ​ console.log("error: " + JSON.stringify(error)) ​ }) ​ }, ​ onLayoutReady(){ ​ var el = this.$refs.canvasss; ​ this.ctx = el.getContext('2d'); ​ this.img = new Image(); ​ this.img.src = '/common/images/live.png'; ​ var pat = this.ctx.createPattern(this.img, 'no-repeat'); ​ this.ctx.fillStyle = pat; ​ this.ctx.fillRect(0, 0, 720, 420); ​ } ``` #### 4.环形图 ![1660502055490](media/1660502055490.png) 根据openharmony应用开发文档直接用progress,就可以简单的绘画一个环形图 ``` ``` #### 5.控制喂食器出粮 ``` handgo(){ ​ this.hmweight=this.hmweight+this.weight; ​ this.$emit('sethmweight',this.hmweight); //传递参数给父类 ​ sockettool.socketSend(this.$app.$def.datas.tcp,this.weight) //控制喂食器出粮 ​ } ``` 对socketTCPsend封装 ``` export default { ​ socketSend:async function (tcp,gg){ ​ var cc=new Uint8Array([170,2,gg]); ​ tcp.send({ ​ data:cc.buffer ​ },err => { ​ if (err) { ​ console.log('send fail'); ​ return; ​ } ​ console.log('send success'); ​ }); ​ } } ``` #### 6.数据上传到服务器 这里使用第三方库去实现http网络请求 ##### 移植HttpClient ##### 1).打开第三方组件库 ```html https://repo.harmonyos.com/#/cn/application/atomService?q=http%20keyword%3AOpenHarmony ``` ##### 2).找到我们需要的httpclient ![ #夏日挑战赛# openharmony eTS之http post请求-开源基础软件社区](https://dl-harmonyos.51cto.com/images/202207/33e01b10564c438f8db655573baa4474c88aaa.png?x-oss-process=image/resize,w_820,h_201) ##### 3).安装 代码: ```html npm install @ohos/httpclient --save ``` ![ #夏日挑战赛# openharmony eTS之http post请求-开源基础软件社区](https://dl-harmonyos.51cto.com/images/202207/97d70e415017482f12a11911256371e7f5d9e8.png?x-oss-process=image/resize,w_778,h_184) 在第三方的基础上,对post进行封装 ``` http_post:async function (url,data){ ​ let httpClientImpl = new httpclient.HttpClient.Builder().setConnectTimeout(15, httpclient.TimeUnit.SECONDS).setReadTimeout(15, httpclient.TimeUnit.SECONDS).build(); ​ let requestBody = httpclient.RequestBody.create(JSON.stringify(data)); ​ let request = new httpclient.Request.Builder() ​ .url(url) ​ .method('POST') ​ .body(requestBody) ​ .addHeader("Content-Type", "application/json") ​ .build(); ​ httpClientImpl.newCall(request).enqueue((result) => { ​ console.log("success: " + JSON.stringify(result)) ​ }, (error) => { ​ console.log("error: " + JSON.stringify(error)) ​ }) ​ } ``` 通过这样调用,非常方便的使用POST请求功能 ![1660503370557](media/1660503370557.png) #### 7.定时功能实现 在使用定时功能的时候,最主要是使用setInterval()去实现定时,使用setInterval()时,应该注意传递方法时,传递方法名就可以 ``` setScheduledTask(hour, minute,weight,callTask) { ​ let taskTime = new Date(); ​ taskTime.setHours(hour); ​ taskTime.setMinutes(minute); ​ let timeDiff = taskTime.getTime() - (new Date()).getTime(); // 获取时间差 ​ timeDiff = timeDiff > 0 ? timeDiff : (timeDiff + 24 * 60 * 60 * 1000); ​ setTimeout(()=> { ​ callTask(weight) // 首次执行 ​ setInterval(callTask, 24 * 60 * 60 * 1000); // 24小时为循环周期 ​ }, timeDiff); ​ },doTask(weight) { ​ this.$emit('setautoweight',weight); ​ sockettool.socketSend(this.$app.$def.datas.tcp,weight) ​ } ``` ### ②、openharmony设备开发 #### 1.创建一个任务 ``` void WifiSockets(void) { ​ osThreadAttr_t wifisocket; ​ wifisocket.name = "TcpServerTask"; ​ wifisocket.attr_bits = 0U; ​ wifisocket.cb_mem = NULL; ​ wifisocket.cb_size = 0U; ​ wifisocket.stack_mem = NULL; ​ wifisocket.stack_size = 10240; ​ wifisocket.priority = 25; ​ if (osThreadNew(TcpServerTask, NULL, &wifisocket) == NULL) { ​ printf("[Ssd1306TestDemo] Falied to create TcpServerTask!\n"); ​ } } APP_FEATURE_INIT(WifiSockets); ``` #### 2.对wifi信息配置 ``` static char ssid[] ="hh"; //wifi名称 static char password[] = "13267897941"; //wifi密码 static unsigned short port = 20222; //socket端口设置 ``` 这个TcpServer方法是可以让客户端断开重连的 ``` static void TcpServerHandler(void) { ​ ssize_t retval = 0; ​ // 创建一个通信的Socket,并返回一个Socket文件描述符。第一个参数IpV4,第二个参数SOCK_STREAM类型,第三个指用到的协议 ​ int sockfd = socket(AF_INET, SOCK_STREAM, 0); ​ // 客户端地址和地址长度 ​ struct sockaddr_in clientAddr = {0}; ​ socklen_t clientAddrLen = sizeof(clientAddr); ​ // 服务端地址 ​ struct sockaddr_in serverAddr = {0}; ​ serverAddr.sin_family = AF_INET; ​ // htons是将整型变量从主机字节顺序转变成网络字节顺序,就是整数在地址空间存储方式变为高位字节存放在内存的低地址处 ​ serverAddr.sin_port = htons(port); ​ // 监听本机的所有IP地址,INADDR_ANY=0x0 ​ // 将主机数转换成无符号长整型的网络字节顺序 ​ serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); ​ // 服务端绑定端口 ​ retval = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); ​ if (retval < 0) { ​ printf("bind failed, %ld!rn", retval); ​ CloseTcpSocket(sockfd); ​ return; ​ } ​ printf("bind to port %d success!rn", port); ​ // 开始监听,backlog指Pending连接队列增长到的最大长度。队列满了,再有新连接请求到达,则客户端ECONNREFUSED错误。如果支持重传,则请求忽略。 ​ int backlog = 1; ​ retval = listen(sockfd, backlog); ​ if (retval < 0) { ​ printf("listen failed!rn"); ​ CloseTcpSocket(sockfd); ​ return; ​ } ​ printf("listen with %d backlog success!rn", backlog); ​ int outerFlag = 1; ​ while (outerFlag) { ​ // 接受客户端连接,成功会返回一个表示连接的 socket。clientAddr参数将会携带客户端主机和端口信息;失败返回 -1 ​ // 从Pending连接队列中获取第一个连接,根据sockfd的socket协议、地址族等内容创建一个新的socket文件描述,并返回。 ​ // 此后的 收、发 都在 表示连接的 socket 上进行;之后 sockfd 依然可以继续接受其他客户端的连接, ​ // UNIX系统上经典的并发模型是“每个连接一个进程”——创建子进程处理连接,父进程继续接受其他客户端的连接 ​ // 鸿蒙liteos-a内核之上,可以使用UNIX的“每个连接一个进程”的并发模型liteos-m内核之上,可以使用“每个连接一个线程”的并发模型 ​ connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientAddrLen); ​ if (connfd < 0) { ​ printf("accept failed, %d, %d\r\n", connfd, errno); ​ continue; ​ } ​ printf("accept success, connfd = %d !\r\n", connfd); ​ // inet_ntoa:网络地址转换成“.”点隔的字符串格式。ntohs:16位数由网络字节顺序转换为主机字节顺序。 ​ printf("client addr info: host = %s, port = %d\r\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); ​ int innerFlag = 1; ​ // 接收消息,然后发送回去 ​ while (innerFlag) { ​ // 后续 收、发 都在 表示连接的 socket 上进行; ​ // 在新的Socket文件描述上接收信息. ​ retval = recv(connfd, request, sizeof(request), 0); ​ if (retval < 0) { ​ printf("recv request failed, %ld!\r\n", retval); ​ innerFlag = 0; ​ } else if (retval == 0) { ​ // 对方主动断开连接 ​ printf("client disconnected!\r\n"); ​ innerFlag = 0; ​ } else { ​ if (retval <= 0) { ​ printf("send response failed, %ld!\r\n", retval); ​ innerFlag = 0; ​ } ​ if(retval == 3 && request[0] == -86) ​ { ​ printf("recv = %d %d %d \r\n",request[0],request[1],request[2]); ​ if(request[1] == 0x00){ ​ retval = send(connfd, "aaok", strlen("aaok"), 0); //答应码 ​ } ​ if(request[1] == 0x01) ​ { ​ }else if(request[1] == 0x02) //喂食量处理函数 ​ { ​ int i = ((unsigned char)request[2]); ​ for(;i>0;i--) ​ { ​ custom(500); ​ osDelay(10); ​ } ​ } ​ } ​ // 清空缓冲区 ​ memset(&request, 0, sizeof(request)); ​ } ​ if(innerFlag == 0) ​ { ​ DisconnectTcpSocket(connfd); //清除连接 ​ break; //重连 ​ } ​ } ​ } ​ CloseTcpSocket(sockfd); } ``` #### 3.接收的部分代码 当客户端传来的命令,符合长度为3,并且头帧为0xAA才会进行处理 if(retval == 3 && request[0] == -86) { printf("recv = %d %d %d \r\n",request[0],request[1],request[2]); if(request[1] == 0x00){ retval = send(connfd, "aaok", strlen("aaok"), 0); //答应码 } if(request[1] == 0x01) { }else if(request[1] == 0x02) //喂食量处理函数 { int i = ((unsigned char)request[2]); for(;i>0;i--) { custom(500); //控制舵机旋转 osDelay(10); } } } #### 4.舵机部分 ![1660505441879](media/1660505441879.png) 本项目使用的是连续旋转舵机,我们可以调节脉冲的时长驱动连续旋转舵机 ``` #define GPIO2 2 void set_angle( unsigned int duty) { ​ IoTGpioInit(GPIO2); ​ IoTGpioSetDir(GPIO2, IOT_GPIO_DIR_OUT); ​ IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE1); ​ hi_udelay(duty); ​ IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE0); ​ hi_udelay(20000 - duty); } void custom(int delay) { ​ for (int i = 0; i <10; i++) { ​ set_angle(delay); ​ } } ``` #### 5.超声波部分 ![1660505663917](media/1660505663917.png) 超声波模块算出底部的距离,再算出余粮的百分比。 根据上面的时序图,进行编写超声波驱动 ``` void GetDistance (float *distance) { ​ static unsigned long start_time = 0, time = 0; ​ IotGpioValue value = IOT_GPIO_VALUE0; ​ unsigned int flag = 0; ​ IoTWatchDogDisable(); ​ hi_io_set_func(GPIO_8, GPIO_FUNC); ​ IoTGpioSetDir(GPIO_8, IOT_GPIO_DIR_IN); ​ IoTGpioSetDir(GPIO_7, IOT_GPIO_DIR_OUT); ​ IoTGpioSetOutputVal(GPIO_7, IOT_GPIO_VALUE1); ​ hi_udelay(20); ​ IoTGpioSetOutputVal(GPIO_7, IOT_GPIO_VALUE0); ​ while (1) { ​ IoTGpioGetInputVal(GPIO_8, &value); ​ if ( value == IOT_GPIO_VALUE1 && flag == 0) { ​ start_time = hi_get_us(); ​ flag = 1; ​ } ​ if (value == IOT_GPIO_VALUE0 && flag == 1) { ​ time = hi_get_us() - start_time; ​ start_time = 0; ​ break; ​ } ​ } ​ *distance = time * 0.034 / 2; ​ printf("distance is %f\r\n",*distance); ​ return ; } ``` ### ③、Flask服务端开发 #### 1、创建数据库对象 创建的这些对象,是我们可以通过SQLAlchemy的方法去操控数据库 ``` ''' 识别记录表 ''' class identificationRecord(db.Model): ​ id = db.Column(db.Integer, primary_key=True) ​ name = db.Column(db.String(128)) ​ state = db.Column(db.Integer) ​ date_time = db.Column(db.DateTime, default=datetime.datetime.now) ​ def return_json(self): ​ return { ​ 'id':self.id, ​ 'name': self.name, ​ 'state':self.state, ​ 'date_time':self.date_time.strftime('%Y-%m-%d %H:%M:%S') ​ } ''' 状态标记表 ''' class tateTag(db.Model): ​ id = db.Column(db.Integer, primary_key=True) ​ cv2state = db.Column(db.Integer) ​ magstate= db.Column(db.Integer) ​ date_time = db.Column(db.DateTime, default=datetime.datetime.now) ​ def return_json(self): ​ return { ​ 'id':self.id, ​ 'cv2state': self.cv2state, ​ 'magstate':self.magstate, ​ 'date_time':self.date_time.strftime('%Y-%m-%d %H:%M:%S') ​ } ​ def clear(self): ​ self.cv2state = 0 ​ self.magstate = 0 ​ self.date_time = datetime.datetime.now() ''' 定时记录表 ''' class timerOfFeeding(db.Model): ​ id = db.Column(db.Integer, primary_key=True) ​ name = db.Column(db.String(128)) ​ hour = db.Column(db.Integer) ​ minute = db.Column(db.Integer) ​ weight = db.Column(db.Integer) ​ date_time = db.Column(db.DateTime, default=datetime.datetime.now) ​ def return_json(self): ​ return { ​ 'id': self.id, ​ 'name':self.name, ​ 'hour':self.hour, ​ 'minute':self.minute, ​ 'weight':self.weight, ​ 'date_time':self.date_time.strftime('%Y-%m-%d %H:%M:%S') ​ } ''' 喂食记录表 ''' class feedingRecords(db.Model): ​ id = db.Column(db.Integer, primary_key=True) ​ name = db.Column(db.String(128)) ​ manualfeeding = db.Column(db.Integer) ​ automaticfeeding = db.Column(db.Integer) ​ date_time = db.Column(db.DateTime, default=datetime.datetime.now) ​ def return_json(self): ​ return { ​ 'id': self.id, ​ 'name':self.name, ​ 'manualfeeding':self.manualfeeding, ​ 'automaticfeeding':self.automaticfeeding, ​ 'date_time':self.date_time.strftime('%Y-%m-%d %H:%M:%S') ​ } 余粮记录表 ''' class surplusGrainRecord(db.Model): ​ id = db.Column(db.Integer, primary_key=True) ​ name = db.Column(db.String(128)) ​ weight = db.Column(db.Integer) ​ date_time = db.Column(db.DateTime, default=datetime.datetime.now) ​ def return_json(self): ​ return { ​ 'id':self.id, ​ 'name': self.name, ​ 'weight':(self.weight/10), ​ 'date_time':self.date_time.strftime('%Y-%m-%d %H:%M:%S') ​ } ``` #### 2、识别猫脸接口 通过add_resource方法使getImage方法和/服务器地址进行绑定 ``` class getImage(Resource): ​ def get(self): ​ image_url = "http://192.168.0.150:8080/stream.mjpg" ​ classfier= cv2.CascadeClassifier("haarcascade_frontalcatface.xml") ​ imgResp=urllib.request.urlopen(image_url) ​ imgNp=np.array(bytearray(imgResp.read()),dtype=np.uint8) ​ img=cv2.imdecode(imgNp,-1) ​ print(type(img)) ​ grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ​ faceRects = classfier.detectMultiScale(grey, scaleFactor = 1.2, minNeighbors = 3, minSize = (32, 32)) ​ if len(faceRects) > 0: #大于0则检测到猫脸 ​ for faceRect in faceRects: #单独框出每一张猫脸 ​ x, y, w, h = faceRect ​ cv2.rectangle(img, (x - 10, y - 10), (x + w + 10, y + h + 10), (0, 255, 0), 2) ​ duf = identificationRecord( name= 'cat' , state = len(faceRects)) ​ db.session.add(duf) ​ buff = tateTag.query.first() ​ buff.cv2state = 1 ​ buff.date_time = datetime.datetime.now() ​ db.session.commit() ​ image = cv2.imencode('.jpg',img)[1] ​ ss = base64.b64encode(image) ​ return { ​ "state":"true", ​ "data":"data:image/jpg;base64,"+ss.decode('ascii') ​ } ``` #### 3、接口讲解 该项目我们封装了很多的接口给应用端使用 ![1660506572112](media/1660506572112.png) 封装好的接口,我们可以使用Postman对接口进行测试 ![1660506727121](media/1660506727121.png) ### ④、摄像头开发 摄像头开发主要时实现,将图片发送到服务器 使用StreamingServer创建server服务,对请求返回实时的图片 ``` class StreamingOutput(object): ​ def __init__(self): ​ self.frame = None ​ self.buffer = io.BytesIO() ​ self.condition = Condition() ​ def write(self, buf): ​ if buf.startswith(b'\xff\xd8'): ​ \# New frame, copy the existing buffer's content and notify all ​ \# clients it's available ​ self.buffer.truncate() ​ with self.condition: ​ self.frame = self.buffer.getvalue() ​ self.condition.notify_all() ​ self.buffer.seek(0) ​ return self.buffer.write(buf) class StreamingHandler(server.BaseHTTPRequestHandler): ​ def do_GET(self): ​ elif self.path == '/stream.mjpg': ​ self.send_response(200) ​ self.send_header('Age', 0) ​ self.send_header('Cache-Control', 'no-cache, private') ​ self.send_header('Pragma', 'no-cache') ​ self.send_header('Content-Type', 'image/jpeg') ​ self.end_headers() ​ ​ with output.condition: ​ output.condition.wait() ​ frame = output.frame ​ self.wfile.write(frame) ​ self.wfile.write(b'\r\n') ​ else: ​ self.send_error(404) ​ self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): ​ allow_reuse_address = True ​ daemon_threads = True with picamera.PiCamera(resolution='720x420', framerate=24) as camera: ​ output = StreamingOutput() ​ camera.rotation = 270 ​ camera.start_recording(output, format='mjpeg') ​ try: ​ address = ('', 8080) ​ server = StreamingServer(address, StreamingHandler) ​ server.serve_forever() ​ finally: ​ camera.stop_recording() ``` ### ⑤、3D模型开发 ![1659021241571](media/1659021241571.png) ## 五、快速上手 ### ①、openharmony应用开发快速上手 #### 1.标准设备环境准备 润和DAYU200开发板套件: - [润和DAYU200开发板套件标准设备HelloWorld](https://gitee.com/openharmony),参考环境准备、编译和烧录章节; #### 2.应用编译环境准备 - 下载DevEco Studio [下载地址](https://gitee.com/link?target=https%3A%2F%2Fdeveloper.harmonyos.com%2Fcn%2Fdevelop%2Fdeveco-studio%23download_beta); - 配置SDK,参考 [配置OpenHarmony-SDK](https://gitee.com/openharmony/docs/blob/OpenHarmony-3.1-Beta/zh-cn/application-dev/quick-start/configuring-openharmony-sdk.md); - DevEco Studio 点击File -> Open 导入本下面的代码工程electricity-cake-clang/openharmony/electricitycakeclangdemo; #### 3.项目下载和导入 1)git下载 ``` git clone https://gitee.com/yukoyu/electricity-cake-clang.git ``` 2)项目导入 打开DevEco Studio,点击File->Open->下载路径/FA/Entertainment/electricity-cake-clang/openharmony/electricitycakeclangdemo #### 4.安装应用 - [配置应用签名信息](https://gitee.com/openharmony/docs/blob/OpenHarmony-3.1-Beta/zh-cn/application-dev/quick-start/configuring-openharmony-app-signature.md) - 安装应用 ``` 点击运行按钮 ``` ### ②、openharmony设备开发快速上手 #### 1.准备开发环境 开发环境安装配置参照文档:[DevEco Device Tool 环境搭建](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/docs/南向IDE环境搭建/README.md) #### 2.准备工程 本用例采DevEco Device Tool工具进行开发,当配置完开发环境后,我们可以在IDE上进行工程的配置下载。 - 打开DevEco Device Tool,连接远程linux服务器:[DevEco Device Tool 环境搭建](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/blob/master/docs/南向IDE环境搭建/README.md) - 点击左下角DevEco插件图标,然后点击左边框出现的主页,弹出主页界面,主页中选择新建项目,如下图: ![main_config](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/1%E6%96%B0%E5%BB%BA%E5%B7%A5%E7%A8%8B.PNG) - 配置下载工程 ![config download](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/2%E9%85%8D%E7%BD%AE%E5%B7%A5%E7%A8%8B.PNG) 如上图所示,填写对应样例工程的名称,选择对应的样例组件和样例工程存放路径后,点击创建即可进行样例工程的下载。下载界面如下: ![download](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/3%E8%8E%B7%E5%8F%96%E6%BA%90%E7%A0%81.PNG) 当右下角显示正在下载OpenHarmony镜像时,耐心等待下载完成即可。 ![download](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/4%E4%B8%8B%E8%BD%BD%E6%BA%90%E7%A0%81.PNG) #### 3.准备工具链 - 在Projects中,点击Settings按钮,进入配置工程界面。 - 在toolchain页签中,DevEco Device Tool会自动检测依赖的编译工具链是否完备,如果提示部分工具缺失,可点击SetUp按钮,自动安装所需工具链。 - 如果出现安装pip组件失败,可参考[修改Python源的方法](https://gitee.com/link?target=http%3A%2F%2Fdevice.harmonyos.com%2Fcn%2Fdocs%2Fdocumentation%2Fguide%2Fide-set-python-source-0000001227639986)进行修改,完成尝试重新安装。 - 工具链自动安装完成后如下图所示。 ![工具链配置成功](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/5%E9%85%8D%E7%BD%AE%E5%B7%A5%E5%85%B7%E9%93%BE.PNG) #### 4.编译 样例代码下载完成后,DevEco Device Tool会重新要求连接远程服务器,输入密码连接后会进入对应的代码编辑界面,此时点击左下角DevEco插件图标,选择PROJECT TASKS可以查看到对应的样例工程,点击build选项进行编译,并可在终端查看对应的编译结果。 ![build](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/6%E7%BC%96%E8%AF%91%E6%88%90%E5%8A%9F.PNG) 固件生成在对应工程目录的out/bearpi_hm_nano/smart_safe/目录下。 ![firm](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/7%E5%9B%BA%E4%BB%B6%E8%B7%AF%E5%BE%84.PNG) #### 5.烧录/安装 编译完成后可以通过DevEco Device Tool进行烧录,在烧录前需要做一些烧录的配置: ##### 配置准备 在配置烧录前需要先查看DevEco Device Tool是否可以正常识别串口。 - 点击左边栏"REMOTE DEVELOPMENT",找到 并点击” Local PC “ 选项。 - 查看 Local PC右边图标 如若图标为![方型图标](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/zhengfang_icon.png),则代表DevEco Device Tool已连接本地,可以正常识别串口。 如若图标为![箭头图标](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/sanjiao_icon.png),则代表DevEco Device Tool未连接本地,不能识别串口,此时需要点击该绿色图标进行连接,连接成功后图标会变为![方型图标](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/zhengfang_icon.png)。 ![check local pc](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/check_local_pc.png) - 点击主页,在主页选择对应工程,点击配置工程进入到配置页面 ![start config project](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/8%E6%89%93%E5%BC%80%E5%B7%A5%E7%A8%8B.PNG) ##### 配置串口 配置页面选择的板级配置页面,在该页面查找到烧录选项,配置烧录选项中的upload_port和upload_protocol,upload_port选择开发板对应的串口号,upload_protocol默认选择hiburn-serial,最后点击右上角的保存按钮进行保存。 ![config searail](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/9%E9%85%8D%E7%BD%AE%E7%83%A7%E5%BD%95%E4%B8%B2%E5%8F%A3.PNG) ##### 烧录 当配置完串口以及固件后,直接点击左边栏工程管理中的upload即可,此时下方终端会出现对应烧录的信息,当终端出现BootromDownloadBoot字样,复位开发板即可。 ![burn ok](https://gitee.com/openharmony-sig/knowledge_demo_smart_home/raw/master/docs/smart_safe/media/burn_ok.png) ### ③、3D模型快速上手 参考:https://www.solidworks.com/zh-hans ### ④、摄像头快速上手 在终端执行命令 ``` python app.py ``` ### ⑤、Flask服务端快速上手 #### 1.下载PyCharm PyCharm 的下载地址:[http://www.jetbrains.com/pycharm/download/#section=windows](https://gitee.com/link?target=http%3A%2F%2Fwww.jetbrains.com%2Fpycharm%2Fdownload%2F%23section%3Dwindows) #### 2.安装PyCharm 安装PyCharm,参考 [PyCharm 安装教程(Windows)](https://gitee.com/link?target=https%3A%2F%2Fwww.runoob.com%2Fw3cnote%2Fpycharm-windows-install.html); #### 3.项目下载和导入 1)git下载 ``` git clone https://gitee.com/yukoyu/electricity-cake-clang.git ``` 2)项目导入 ``` 打开PyCharm,点击File->Open->下载路径/FA/Entertainment/electricity-cake-clang/flaskProject ``` #### 4.运行flask后端 ``` 点击run按钮 ``` ## 六、常见问题解决 ### ①、hi3861 经常网络断开 可能是因为WiFi测试打开了,导致WiFi断开,同时也可以在这里关闭所有的测试,这样串口打印log的数据就非常干净,不会再收到Test打印log的影响 ![1660508060886](media/1660508060886.png) ### 七、 参考资料 - https://ost.51cto.com/posts/14533 - https://ost.51cto.com/posts/13159 - https://ost.51cto.com/posts/13138