Openharmony应用开发--智能喂食器

文章目录
  • Openharmony应用开发--智能喂食器

    ​想了解更多关于开源的内容,请访问:​

    ​51CTO 开源基础软件社区​

    ​https://ost.51cto.com​

    随着人们生活方式的不断改变,宠物猫在许多家庭中占有重要的地位,其凭借独立的个性和易于打理的饲养方式,成为当下上班族喜欢的宠物之一,人们更是把宠物猫和狗作为家庭的重要成员。有铲屎官表示,每月在宠物身上的基础花销是用来购买宠物粮,为了“小主”们的饮食可以说操碎了心,比起让它们吃好吃饱他们更关心狗粮猫粮的食品安全、生活舒适度、身心健康等。这也让宠物智能设备成为刚需产品,随着 5G、大数据、AI、云计算等技术的快速发展,各类智能养宠硬件如雨后春笋般涌现,在人的吃喝拉撒还需要“手动”的时候,宠物已经进入智能生活了。

    而我们的作品就是用于更好的对宠物的投喂进行智能化管理,首先它基于OpenHarmony开发板开发,可以通过使用APP进行定时喂食,同时可以远程观看实时视频,同时也可以识别到小猫进行喂食,更好的知道家里宠物的状态;可以通过喂食器APP查看内余粮,方便用户更好的管理宠物的饮食健康和规律。

    Openharmony应用开发--智能喂食器

    1. 可视化显示喂食数据
    2. 自动喂食功能
    3. 定时喂食
    4. 手动喂食
    5. 余粮显示功能
    6. 远程观看实时视频
    7. 自定义喂食的量

    Openharmony应用开发--智能喂食器

    Openharmony应用开发--智能喂食器

    通过使用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>

    Openharmony应用开发--智能喂食器

    对于自定义组件的开发,可以使用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)
    }
    },

    其余的自定义组件喂食量可视化环状图代码我就不在细讲,你们可以就看我的开源代码。

    Openharmony应用开发--智能喂食器

    这里采用的自定义的图传视频组件,是采用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应用开发--智能喂食器

    根据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

    Openharmony应用开发--智能喂食器

    代码:

    npm install @ohos/httpclient --save

    Openharmony应用开发--智能喂食器

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

    Openharmony应用开发--智能喂食器

    在使用定时功能的时候,最主要是使用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 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);
    }
    }
    }

    Openharmony应用开发--智能喂食器

    本项目使用的是连续旋转舵机,我们可以调节脉冲的时长驱动连续旋转舵机。

    #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);
    }
    }

    Openharmony应用开发--智能喂食器

    超声波模块算出底部的距离,再算出余粮的百分比。

    根据上面的时序图,进行编写超声波驱动。

    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')
    }

    该项目我们封装了很多的接口给应用端使用。

    Openharmony应用开发--智能喂食器

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

    Openharmony应用开发--智能喂食器

    摄像头开发主要时实现,将图片发送到服务器。

    使用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()

    Openharmony应用开发--智能喂食器

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

    Openharmony应用开发--智能喂食器

    ​想了解更多关于开源的内容,请访问:​

    ​51CTO 开源基础软件社区​

    ​https://ost.51cto.com​​。

    © 版权声明

    相关文章