icon

UNIHIKER: GR10-30 gesture sensor

1 669 Easy

In this tutorial, you'll learn how to set up a Python Tkinter application on the UNIHIKER device that interacts with GR10-30 gesture sensor. The sensor detects gestures and changes the displayed images accordingly. You'll walk through the necessary hardware, assembly, code creation, and deployment steps.

HARDWARE LIST
1 UNIHIKER
1 GR10-20 gesture sensor
STEP 1
Assembling the Hardware

UNIHIKER Device - A single board computer compatible with Python.

 

Gesture Sensor - An I2C-enabled gesture sensor, specifically the DFRobot GR10-30.

 

Connecting Wires - To connect the sensor to the UNIHIKER device.

 

Connect the Gesture Sensor to UNIHIKER. Make sure that you only turn on the power supply after the connection! Double-check the connections for secure and correct placement. 

 

STEP 2
Create Python Project Structure and Files

Create a directory for your project named “ImageViewer”. Inside this directory create two more directories named “lib” and “demo”. The directory “lib” will contain a file named "GR10_30.py". Inside the directory “demo” all pictures will be stored. In addition, a file called “main.py” is required in the project folder.

 

Here is an example of what everything will look like in the end:

 

You can use the following images (generated on perchance.org) for testing purposes:

 

 

 

 

 

 

STEP 3
Python Code

The file "GR10_30.py" serves as a driver for the sensor. I modified this for you from the original (Raspberry) for UNIHIKER. I was a bit lazy and the Python DocStrings are missing. But that shouldn't be a problem.

CODE
from pinpong.board import I2C
from time import sleep


GR10_30_I2C_ADDR = 0x73

GR30_10_INPUT_REG_ADDR = 0x02
GR30_10_INPUT_REG_DATA_READY = 0x06
GR30_10_INPUT_REG_INTERRUPT_STATE = 0x07
GR30_10_INPUT_REG_EXIST_STATE = 0x08

GR30_10_HOLDING_REG_INTERRUPT_MODE = 0x09
GR30_10_HOLDING_REG_RESET = 0x18

GESTURE_UP = (1 << 0)
GESTURE_DOWN = (1 << 1)
GESTURE_LEFT = (1 << 2)
GESTURE_RIGHT = (1 << 3)
GESTURE_FORWARD = (1 << 4)
GESTURE_BACKWARD = (1 << 5)
GESTURE_CLOCKWISE = (1 << 6)
GESTURE_COUNTERCLOCKWISE = (1 << 7)
GESTURE_WAVE = (1 << 8)
GESTURE_HOVER = (1 << 9)
GESTURE_UNKNOWN = (1 << 10)
GESTURE_CLOCKWISE_C = (1 << 14)
GESTURE_COUNTERCLOCKWISE_C = (1 << 15)


class DFRobot_GR10_30_I2C:

    def __init__(self, i2c_addr=GR10_30_I2C_ADDR, i2c_bus=0):
        self._addr = i2c_addr
        self._temp_buffer = [0] * 2

        try:
            self._i2c = I2C(i2c_bus)
        except Exception as err:
            print(f'Could not initialize i2c! bus: {i2c_bus}, error: {err}')

    def _write_reg(self, reg, data) -> None:
        if isinstance(data, int):
            data = [data]

        try:
            self._i2c.writeto_mem(self._addr, reg, bytearray(data))
        except Exception as err:
            print(f'Write issue: {err}')

    def _read_reg(self, reg, length) -> bytes:
        try:
            result = self._i2c.readfrom_mem(self._addr, reg, length)
        except Exception as err:
            print(f'Read issue: {err}')
            result = [0, 0]

        return result

    def _detect_device_address(self) -> int:
        r_buf = self._read_reg(GR30_10_INPUT_REG_ADDR, 2)
        data = r_buf[0] << 8 | r_buf[1]

        return data

    def _reset_sensor(self) -> None:
        self._temp_buffer[0] = 0x55
        self._temp_buffer[1] = 0x00
        self._write_reg(GR30_10_HOLDING_REG_RESET, self._temp_buffer)

        sleep(0.1)

    def begin(self) -> bool:
        if self._detect_device_address() != GR10_30_I2C_ADDR:
            return False

        self._reset_sensor()
        sleep(0.5)

        return True

    def en_gestures(self, gestures) -> None:
        gestures = gestures & 0xc7ff

        self._temp_buffer[0] = (gestures >> 8) & 0xC7
        self._temp_buffer[1] = gestures & 0x00ff
        self._write_reg(GR30_10_HOLDING_REG_INTERRUPT_MODE, self._temp_buffer)

        sleep(0.1)

    def get_exist(self) -> bool:
        r_buf = self._read_reg(GR30_10_INPUT_REG_EXIST_STATE, 2)
        data = r_buf[0] * 256 + r_buf[1]

        return bool(data)

    def get_data_ready(self) -> bool:
        r_buf = self._read_reg(GR30_10_INPUT_REG_DATA_READY, 2)
        data = r_buf[0] * 256 + r_buf[1]

        if data == 0x01:
            return True
        else:
            return False

    def get_gestures(self) -> int:
        r_buf = self._read_reg(GR30_10_INPUT_REG_INTERRUPT_STATE, 2)
        data = (r_buf[0] & 0xff) * 256 + r_buf[1]

        return int(data)

The application logic is located in the “main.py” file. All necessary packages/libraries are imported here, constants are defined, functions are created, etc. Here I was a little more diligent and created the Python DocStrings. These should help you understand the code better.

CODE
from os import listdir
from os.path import dirname, join
from tkinter import Tk, BOTH, Frame, Label, PhotoImage
from typing import List, Tuple
from PIL import Image, ImageTk
from time import sleep
from sys import exit
from threading import Thread
from pinpong.board import Board
from lib.GR10_30 import DFRobot_GR10_30_I2C, GESTURE_LEFT, GESTURE_RIGHT, GESTURE_UP, GESTURE_DOWN


SCREEN_WIDTH: int = 240
SCREEN_HEIGHT: int = 320
FONT: tuple = ("Arial", 14, "bold")
IMAGE_PATH: str = 'demo/'
IMAGE_EXTENSIONS: tuple = ('.png', '.jpg', '.jpeg')


def load_image(image_path: str, image_size: Tuple[int, int]) -> ImageTk.PhotoImage:
    """
    Load an image from the specified path and resize it to the given size.

    :param image_path: The path to the image file.
    :type image_path: str
    :param image_size: The desired size of the image as a tuple of width and height.
    :type image_size: Tuple[int, int]
    :return: The loaded and resized image as an `ImageTk.PhotoImage` object.
    :rtype: ImageTk.PhotoImage
    """
    image = Image.open(image_path)
    image = image.resize(image_size)

    return ImageTk.PhotoImage(image)


def load_all_images(image_dir: str, image_size: Tuple[int, int]) -> List[ImageTk.PhotoImage]:
    """
    Load all the images in the specified directory.

    :param image_dir: The directory where the images are located.
    :type image_dir: str
    :param image_size: The desired size of the images.
    :type image_size: Tuple[int, int]
    :return: A list of loaded images as `ImageTk.PhotoImage`.
    :rtype: List[ImageTk.PhotoImage]
    """
    filenames = [join(image_dir, f) for f in listdir(image_dir) if f.endswith(IMAGE_EXTENSIONS)]

    return [load_image(filename, image_size) for filename in filenames]


def update_label(label: Label, image_list: List[PhotoImage], index: int) -> None:
    """
    Updates the image and text displayed in a tkinter Label widget.

    :param label: The tkinter Label widget to update.
    :type label: Label
    :param image_list: A list of PhotoImage objects representing the images to cycle through.
    :type image_list: List[ImageTk.PhotoImage]
    :param index: The index of the image in the image_list to display in the label.
    :type index: int
    :return: None
    """
    total_images = len(image_list)

    if 0 <= index < total_images:
        message = f'Image: {index + 1} of {total_images}'
        print(f'[INFO] Display {message.lower()}')

        label.config(image=image_list[index], text=message, font=FONT, fg='white')
        label.image = image_list[index]


def handle_gestures(sensor: DFRobot_GR10_30_I2C, image_list: List[PhotoImage], label: Label) -> None:
    """
    Handles gestures detected by the given sensor.

    :param sensor: The instance of the DFRobot_GR10_30_I2C sensor.
    :type sensor: DFRobot_GR10_30_I2C
    :param image_list: A list of PhotoImage objects representing the images to cycle through.
    :type image_list: List[ImageTk.PhotoImage]
    :param label: The tkinter Label widget to update.
    :type label: Label
    :return: None
    """
    image_index = 0
    total_images = len(image_list)

    while True:
        if gesture_sensor.get_data_ready():
            gesture = sensor.get_gestures()

            if gesture & GESTURE_LEFT:
                print(f'[INFO] Gesture: {gesture} left detected')
                if image_index < total_images:
                    image_index += 1

            if gesture & GESTURE_RIGHT:
                print(f'[INFO] Gesture: {gesture} right detected')
                if image_index > 0:
                    image_index -= 1

            if gesture & GESTURE_UP:
                print(f'[INFO] Gesture: {gesture} up detected')
                image_index = 0

            if gesture & GESTURE_DOWN:
                print(f'[INFO] Gesture: {gesture} down detected')
                image_index = total_images - 1

            update_label(label, images, image_index)

        sleep(.05)


if __name__ == '__main__':
    Board("UNIHIKER").begin()

    gesture_sensor = DFRobot_GR10_30_I2C()
    while not gesture_sensor.begin():
        print('[INFO] Initializing gesture sensor...')
        sleep(1)

    gesture_sensor.en_gestures(GESTURE_LEFT | GESTURE_RIGHT | GESTURE_UP | GESTURE_DOWN)

    root = Tk()
    root.resizable(False, False)
    root.geometry(f'{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0')

    frame = Frame(root, height=SCREEN_HEIGHT, width=SCREEN_WIDTH, bg='black')
    frame.pack(fill=BOTH, expand=True)
    img_label = Label(frame, text='', font=FONT, fg='white', compound='center')
    img_label.pack(fill=BOTH, expand=True)

    current_dir = dirname(__file__)
    target_path = join(current_dir, IMAGE_PATH)
    images = load_all_images(join(dirname(__file__), IMAGE_PATH), (SCREEN_WIDTH, SCREEN_HEIGHT))

    if not images:
        print(f'[INFO] No images found: {target_path}')
        exit(1)
    else:
        print(f'[INFO] Found {len(images)} images')
        update_label(img_label, images, 0)

        gesture_thread = Thread(target=handle_gestures, args=(gesture_sensor, images, img_label))
        gesture_thread.daemon = True
        gesture_thread.start()

    root.mainloop()
STEP 4
Upload from Local to UNIHIKER (via SMB/SCP)

Ensure UNIHIKER is Connected: Connect the UNIHIKER device to your local environment  via USB. To send the project files to the UNIHIKER board, you can use tools like SCP or SMB. Here's an example using SCP:

 

$ scp -r ImageViewer [email protected]:/root/

STEP 5
Run Application

You can start the application via the touch display or via command line. Here the example via command-line.

 

$ python3 /root/ImageViewer/main.py

 

 

You can now change the images with hand gestures. Left, right, up and down...

STEP 6
Annotations (What More Can Be Done and Developed)

Gesture Customization: Enhance the application by adding more gestures or customizing actions for existing gestures.

 

User Interface: Improve the Tkinter UI with additional features.

 

Performance Optimization: Optimize image loading and memory usage for better performance.

 

Interactive Features: Integrate interactive features such as touch input or additional sensors for a more engaging experience.

 

Feel free to modify and expand upon this tutorial based on your project's needs and creativity!

License
All Rights
Reserved
licensBg
1