文章目录
- 随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在宠物身上的基础花销是用来购买宠物粮,为了“小主”们的饮食可以说操碎了心,比起让它们吃好吃饱他们更关心狗粮猫粮的食品安全、生活舒适度、身心健康等。这也让宠物智能设备成为刚需产品,随着 5G、大数据、AI、云计算等技术的快速发展,各类智能养宠硬件如雨后春笋般涌现,在人的吃喝拉撒还需要“手动”的时候,宠物已经进入智能生活了。 而我们的作品就是用于更好的对宠物的投喂进行智能化管理,首先它基于OpenHarmony开发板开发,可以通过使用APP进行定时喂食,同时可以远程观看实时视频,同时也可以识别到小猫进行喂食,更好的知道家里宠物的状态;可以通过喂食器APP查看内余粮,方便用户更好的管理宠物的饮食健康和规律。
-
-
- 通过使用js 自带的tabs组件开发导航栏,是非常简单的。 <tabs @change="setVoids"> <tab-content> <div class="text"> <home ></home> </div> <div class="text"> <videopage ></videopage> </div> <div class="text"> <setting></setting> </div> </tab-content> <tab-bar class="navigationbar"> <div class="tab-icon"> <image class="tab-icon-image" src="/common/images/03.png"></image> <text> 主页 </text> </div> <div class="tab-icon"> <image class="tab-icon-image" src="/common/images/04.png"></image> <text> 控制 </text> </div> <div class="tab-icon"> <image class="tab-icon-image" src="/common/images/05.png"></image> <text> 设置 </text> </div> </tab-bar> </tabs>
- 对于自定义组件的开发,可以使用canvas去开发,在这里我就使用canvas去开发。 新建common/component文件夹,并新建doughnubt pages。 <div class="container"> <canvas ref="canvasb"></canvas></div> 这里要注意组件的生命周期和Pages的是不同的,所以onShow()方法在这里是无效的,这里使用onLayoutReady()代替onShow()方法。 初始化画板数据: 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); } 画小圆环: 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对数据进行监听。 //父<div class="two"> <doughnut max="{{maxweight}}" min="{{weights}}"></doughnut></div> //子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) } }, 其余的自定义组件喂食量可视化环状图代码我就不在细讲,你们可以就看我的开源代码。
- 这里采用的自定义的图传视频组件,是采用http获取图片然后通过canvas进行绘画。 <div class="videocom" onclick="videorun"> <canvas ref="canvasss"></canvas></div> 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); }
- 根据openharmony应用开发文档直接用progress,就可以简单的绘画一个环形图。 <progress class="min-progress" type="scale-ring" percent="10" secondarypercent="50"></progress>
- 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'); }); }}
- 这里使用第三方库去实现http网络请求。
-
- https://repo.harmonyos.com/#/cn/application/atomService?q=http%20keyword%3AOpenHarmony
- 代码: npm install @ohos/httpclient --save 在第三方的基础上,对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请求功能。
- 在使用定时功能的时候,最主要是使用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) }
-
-
- 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);
- static char ssid[] ="hh"; //wifi名称static char password[] = "13267897941"; //wifi passwordstatic 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,并且头帧为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); } } }
- 本项目使用的是连续旋转舵机,我们可以调节脉冲的时长驱动连续旋转舵机。 #define GPIO2 2void 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); }}
- 超声波模块算出底部的距离,再算出余粮的百分比。 根据上面的时序图,进行编写超声波驱动。 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 ;}
-
- 创建的这些对象,是我们可以通过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') }
- 通过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') }
- 该项目我们封装了很多的接口给应用端使用。 封装好的接口,我们可以使用Postman对接口进行测试。
- 摄像头开发主要时实现,将图片发送到服务器。 使用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 = Truewith 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()

随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在宠物身上的基础花销是用来购买宠物粮,为了“小主”们的饮食可以说操碎了心,比起让它们吃好吃饱他们更关心狗粮猫粮的食品安全、生活舒适度、身心健康等。这也让宠物智能设备成为刚需产品,随着 5G、大数据、AI、云计算等技术的快速发展,各类智能养宠硬件如雨后春笋般涌现,在人的吃喝拉撒还需要“手动”的时候,宠物已经进入智能生活了。
而我们的作品就是用于更好的对宠物的投喂进行智能化管理,首先它基于OpenHarmony开发板开发,可以通过使用APP进行定时喂食,同时可以远程观看实时视频,同时也可以识别到小猫进行喂食,更好的知道家里宠物的状态;可以通过喂食器APP查看内余粮,方便用户更好的管理宠物的饮食健康和规律。

- 可视化显示喂食数据
- 自动喂食功能
- 定时喂食
- 手动喂食
- 余粮显示功能
- 远程观看实时视频
- 自定义喂食的量


通过使用js 自带的tabs组件开发导航栏,是非常简单的。
<tabs @change="setVoids">
<tab-content>
<div class="text">
<home ></home>
</div>
<div class="text">
<videopage ></videopage>
</div>
<div class="text">
<setting></setting>
</div>
</tab-content>
<tab-bar class="navigationbar">
<div class="tab-icon">
<image class="tab-icon-image" src="/common/images/03.png"></image>
<text>
主页
</text>
</div>
<div class="tab-icon">
<image class="tab-icon-image" src="/common/images/04.png"></image>
<text>
控制
</text>
</div>
<div class="tab-icon">
<image class="tab-icon-image" src="/common/images/05.png"></image>
<text>
设置
</text>
</div>
</tab-bar>
</tabs>

对于自定义组件的开发,可以使用canvas去开发,在这里我就使用canvas去开发。
新建common/component文件夹,并新建doughnubt pages。
<div class="container">
<canvas ref="canvasb"></canvas>
</div>
这里要注意组件的生命周期和Pages的是不同的,所以onShow()方法在这里是无效的,这里使用onLayoutReady()代替onShow()方法。
初始化画板数据:
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);
}
画小圆环:
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对数据进行监听。
//父
<div class="two">
<doughnut max="{{maxweight}}" min="{{weights}}"></doughnut>
</div>
//子
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)
}
},
其余的自定义组件喂食量可视化环状图代码我就不在细讲,你们可以就看我的开源代码。

这里采用的自定义的图传视频组件,是采用http获取图片然后通过canvas进行绘画。
<div class="videocom" onclick="videorun">
<canvas ref="canvasss"></canvas>
</div>
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);
}

根据openharmony应用开发文档直接用progress,就可以简单的绘画一个环形图。
<progress class="min-progress" type="scale-ring" percent="10" secondarypercent="50"></progress>
handgo(){
this.hmweight=this.hmweight+this.weight;
this.$emit('sethmweight',this.hmweight); //传递参数给父类
sockettool.socketSend(this.$app.$def.datas.tcp,this.weight) //控制喂食器出粮
}
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');
});
}
}
这里使用第三方库去实现http网络请求。
https://repo.harmonyos.com/#/cn/application/atomService?q=http%20keyword%3AOpenHarmony

代码:
npm install @ohos/httpclient --save

在第三方的基础上,对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请求功能。

在使用定时功能的时候,最主要是使用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)
}
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);
{
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);
static char ssid[] ="hh"; //wifi名称
static char password[] = "13267897941"; //wifi password
static unsigned short port = 20222; //socket端口设置
static char password[] = "13267897941"; //wifi password
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,并且头帧为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);
}
}
}

本项目使用的是连续旋转舵机,我们可以调节脉冲的时长驱动连续旋转舵机。
#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);
}
}

超声波模块算出底部的距离,再算出余粮的百分比。
根据上面的时序图,进行编写超声波驱动。
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 ;
}
创建的这些对象,是我们可以通过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')
}
通过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')
}
该项目我们封装了很多的接口给应用端使用。

封装好的接口,我们可以使用Postman对接口进行测试。

摄像头开发主要时实现,将图片发送到服务器。
使用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()

可能是因为WiFi测试打开了,导致WiFi断开,同时也可以在这里关闭所有的测试,这样串口打印log的数据就非常干净,不会再收到Test打印log的影响。
