Detailed Development Case of OriginBot Smart Robot

This article mainly discusses how to use OriginBot to take care of babies. When the baby’s face is not within the camera’s range, a message is sent to the DingTalk group to notify family members to check promptly.

Introduction

Last month, I had a baby, and to make it easier to look after the baby, I bought a camera with baby monitoring features. However, the product was not satisfactory, and most importantly, the face obscuring feature did not work, so I returned it. After returning it, I had the idea to create a similar function using OriginBot, which led to this blog~

Function Flowchart (Architecture Diagram)

The specific process, or architecture, is as follows:

Detailed Development Case of OriginBot Smart Robot

It is not complicated overall. On OriginBot, there is a MIPI camera, and it utilizes the human detection and tracking feature of Horizon TogetheROS.Bot (https://developer.horizon.cc/documents_tros/boxs/function/mono2d_body_detection#%E5%9C%B0%E5%B9%B3%E7%BA%BFrdk%E5%B9%B3%E5%8F%B0) to detect if there is a face in the camera in real-time. If there is no face, it sends data to the backend, which then sends a message to the DingTalk group to notify family members. A webhook needs to be created in the DingTalk group in advance.

Next, we will document the three parts: human detection, face detection, and backend operation.

Human Detection

This part uses the ready-made functionality of Horizon TogetheROS.Bot (https://developer.horizon.cc/documents_tros/boxs/function/mono2d_body_detection#%E5%9C%B0%E5%B9%B3%E7%BA%BFrdk%E5%B9%B3%E5%8F%B0). After starting OriginBot, run the following command in the command line:

# Configure tros.b environment
source /opt/tros/setup.bash
# Copy the configuration files needed for running examples from the installation path of tros.b.
cp -r /opt/tros/lib/mono2d_body_detection/config/ .
# Configure MIPI camera
export CAM_TYPE=mipi
# Start launch file
ros2 launch mono2d_body_detection mono2d_body_detection.launch.py

You can check the detection effect through http://IP:8000. This module detects human bodies, heads, faces, hand detection boxes, detection box types, target tracking IDs, and human key points, but I only take the face part. Of course, in the future, we can add human detection and more.

After running the above command, execute ros2 topic list on OriginBot; there should be a topic called hobot_mono2d_body_detection, which is what we need. We will subscribe to this topic and analyze the data sent to determine if there is a face.

Determining Whether There Is a Face in the Camera

According to the TogetheROS.Bot documentation, the message type of hobot_mono2d_body_detection is ai_msgs.msg.PerceptionTargets, as follows:

# Perception results
# Message header
std_msgs/Header header
# The processing frame rate of the perception results
# fps val is invalid if fps is less than 0
int16 fps
# Performance statistics, such as recording the inference time of each model
Perf[] perfs
# Perception target collection
Target[] targets
# Disappeared target collection
Target[] disappeared_targets

The disappeared_targets is our focus. If a face appears in disappeared_targets, it means there was a face before, but now it is gone. At this time, we need to send data to the backend for further processing.

To determine whether there is a face, I wrote a Node, and the code is as follows:

import rclpy
from rclpy.node import Node
from ai_msgs.msg import PerceptionTargets
from cv_bridge import CvBridge
import time
from api_connection import APIConnection
BabyMonitorMapping = {    # The k-v here needs to be determined according to the values in the backend Django    "face": "Face not visible",    "body": "Not in camera range",}

class FaceDetectionListener(Node):    """    Detect whether the baby's face is in the camera    """    def __init__(self):        super().__init__("face_detection")        self.bridge = CvBridge()        self.subscription = self.create_subscription(            PerceptionTargets, "hobot_mono2d_body_detection", self.listener_callback, 10        )        self.conn = APIConnection()        self.timer = time.time()        self.counter = 0
    def listener_callback(self, msg):        targets = msg.targets        disappeared_targets = msg.disappeared_targets        targets_list = []        disappeared_targets_list = []        if disappeared_targets:            for item in disappeared_targets:                disappeared_targets_list.append(item.rois[0].type)        if targets:            for item in targets:                targets_list.append(item.rois[0].type)        print(f"Detected objects: {targets_list}")        print(f"Disappeared objects: {disappeared_targets_list}")        if disappeared_targets_list:            self.sending_notification(disappeared_targets_list)
    def sending_notification(self, disappeared_targets_list):        for item in disappeared_targets_list:            if BabyMonitorMapping.get(item):                event = BabyMonitorMapping.get(item)                if self.counter == 0:                    # The baby's ID here is simulated, it should be checked in the database                    data = {"event": event, "baby": "6b56979a-b2b9-11ee-920d-f12e14f97477"}                     self.conn.post_data(item=data, api="api/monitor/face-detection/")                    self.counter += 1                    self.timer = time.time()                else:                    if time.time() - self.timer >= 60.0:                        # Do not resend DingTalk message for 60 seconds                        data = {"event": event, "baby": "6b56979a-b2b9-11ee-920d-f12e14f97477"}                        self.conn.post_data(item=data, api="api/monitor/face-detection/")                        self.timer = time.time()                        self.counter += 1

def main(args=None):    rclpy.init(args=args)    try:        face_detection_listener = FaceDetectionListener()        rclpy.spin(face_detection_listener)    except KeyboardInterrupt:        print("Terminating")    finally:        face_detection_listener.destroy_node()        rclpy.shutdown()

if __name__ == "__main__":    main()

The code overall is not difficult, but a few necessary explanations are still needed:

1. BabyMonitorMapping

This dictionary is used to map the fields in TogetheROS.Bot to the fields in the backend for ease of use later.

2. API Calls

In the code, there are two lines as follows:

data = {"event": event, "baby": "6b56979a-b2b9-11ee-920d-f12e14f97477"}
self.conn.post_data(item=data, api="api/monitor/face-detection/")

The uri and data format here are determined by the backend, so there is no need to go into details. For those who want to know the details, you can check the backend code.

3. APIConnection

APIConnection is a wrapper class for making API requests, and the code is as follows:

"""API CONNECTION FOR IMPORTING WRAPPER"""
import json
import logging
import requests
import envs

logging.basicConfig(    format="%(asctime)s %(levelname)-8s %(message)s",    level=logging.INFO,    datefmt="%Y-%m-%d %H:%M:%S",)

class APIConnection:    """    Api Connection    """    def __init__(self):        self.api_url = envs.API_URL        self.token = None        self.headers = {            "Content-Type": "application/json",            "Cache-Control": "no-cache",        }        self.request_jwt()
    def request_jwt(self):        """        Request JWT token.        """        logging.info("Requesting JWT..")        api_url = f"{self.api_url}api/token/"        data = {            "username": envs.SCRIPT_USER,            "password": envs.SCRIPT_PASSWORD,        }        res = requests.post(api_url, data=json.dumps(data), headers=self.headers)        if res.status_code == 200:            data = res.json()            self.token = data["access"]            self.headers["Authorization"] = f"Bearer {self.token}"        else:            logging.error(                f"Failed to obtain JWT. Status code: {res.status_code}, Message: {res.text}"            )    def upload_video(self, api, file):        """        post data        :param item: items to be posted in json format        :param api: path of endpoint        """        api_url = f"{self.api_url}{api}"        try:            res = requests.post(api_url, files=file, headers=self.headers, timeout=1)            if res.status_code == 401:                self.request_jwt()                self.post_data(api, file)            elif res.status_code in [200, 201]:                logging.info(f"{res.status_code} - video uploaded successfully")                return res.status_code            else:                logging.error(                    f"{res.status_code} - {res.json()}- unable to upload video"                )                return res.status_code        except Exception as err:            logging.error(err)            return 500    def post_data(self, item, api):        """        Create a new data entry        """        api_url = f"{self.api_url}{api}"        try:            response = requests.post(                api_url, data=json.dumps(item), headers=self.headers, timeout=1            )            if response.status_code == 403:                self.request_jwt()                self.post_data(item, api)            elif response.status_code not in [200, 201]:                logging.error(                    f"post data to backend failed, 
                     status code is {response.status_code}, 
                     error message is:
 			{response.text}"                )        except Exception as err:            logging.error(                f"post data to backend failed, 
                  error message is:
 			{err}"            )

Backend Operation

After the previous two parts, if face data is found to have disappeared from the OriginBot’s camera, it will have sent the record to the backend. Now let’s talk about the backend part.

The backend operation is actually quite simple. It receives the data, stores a copy in the database, and then sends a message to DingTalk to remind family members.

In the OriginBot family assistant plan (https://www.guyuehome.com/44812), it is mentioned that the backend is developed based on Django + Django Rest Framework, and the following content requires some basic understanding of Django to comprehend.

First, two Django models are created to store the data.

class Baby(models.Model):    """    Record of the baby's data    """    id = models.UUIDField(primary_key=True, default=uuid.uuid1, editable=False)    name = models.CharField(max_length=256)    birth_date = models.DateField()    gender = models.CharField(max_length=1, choices=(("男", "男"), ("女", "女")))    def __str__(self):        return self.name

class BabyMonitorData(models.Model):    """    Record of data related to baby monitoring    """    event_choices = (        ("看不到脸", "Face not visible"),        ("哭", "Crying"),        ("翻身", "Rolling"),        ("不在摄像头范围内", "Not in camera range"),    )    baby = models.ForeignKey(Baby, on_delete=models.PROTECT)    event = models.CharField(max_length=128, choices=event_choices)    timestamp = models.DateTimeField(auto_now_add=True)    def __str__(self):        return f"{self.baby.name} {self.event} {self.timestamp}"    class Meta:        ordering = ["-timestamp"]

It is important to note that there is a foreign key relationship between the Baby and BabyMonitorData classes.

The uri requested in FaceDetectionListener api/monitor/face-detection/ ultimately executes the following code in the backend:

class BabyMonitorView(viewsets.ModelViewSet):    queryset = BabyMonitorData.objects.all().order_by("-timestamp")    serializer_class = BabyMonitorSerializer
    def create(self, request, *args, **kwargs):        response = super().create(request, *args, **kwargs)        message = request.data        try:            event = message.get("event")            baby = Baby.objects.filter(id=message.get("baby"))[0].name        except IndexError:            print("Cannot find corresponding baby data")            return response        send_msg_to_dingtalk(f"{baby} {event} has occurred!")        # Return the original response        return response

What this does is store a record in BabyMonitorData and send a message to the DingTalk group through send_msg_to_dingtalk.

The code for send_msg_to_dingtalk is as follows:

import json
import requests
from utils import envs
from datetime import datetime

def send_msg_to_dingtalk(msg):    webhook = envs.DING_TALK_URL    headers = {"Content-Type": "application/json;charset=utf-8"}    data = {        "msgtype": "text",        "text": {            "content": msg            + f"[From OriginBot, {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]"        },    }    response = requests.post(webhook, headers=headers, data=json.dumps(data))    return response.text

if __name__ == "__main__":    message = "Hello from my Python script!"    send_msg_to_dingtalk(message)

The code webhook = envs.DING_TALK_URL actually retrieves the DingTalk group robot link from the environment variable. There are many tutorials online on how to create a DingTalk group robot, so I won’t elaborate on that.

At this point, if everything goes smoothly, you should be able to see messages in DingTalk, as shown below:

Detailed Development Case of OriginBot Smart Robot

Source Code

Source code address: https://github.com/yexia553/originbot_home_assistant

Areas for Optimization

Although a basic demo has been completed, there are still many areas that need optimization:

The car can only run on the ground, but the baby is generally on the bed. The car must be placed in a specific position to achieve the above functions, which limits practical applications and needs to be resolved.

Battery life issue, currently the car’s battery can only last about 2 hours.

Currently, it only detects whether there is a face in the camera, rather than detecting the baby’s face. Optimization can be considered.

Similar features like crying detection, rolling, and other action recognition can be added, along with data analysis.

Detailed Development Case of OriginBot Smart Robot

Detailed Development Case of OriginBot Smart Robot

Detailed Development Case of OriginBot Smart Robot

Detailed Development Case of OriginBot Smart Robot

Detailed Development Case of OriginBot Smart Robot

Leave a Comment