基础设计

  1. 传感器:用于检测手势动作,如陀螺仪、加速度计等。

  2. 无线通信模块:与电脑进行无线连接,如蓝牙。

  3. 微控制器:处理传感器数据并发送控制指令。

  4. 电源:如小型电池,为戒指供电。

  5. 戒指外壳:舒适、贴合手指,可集成传感器和电路。

  6. 配套软件:安装在电脑上,解读手势指令并执行相应操作。

  7. 手势识别算法:识别不同的手势动作。

  8. 校准功能:根据用户的手势习惯进行校准。

  9. 反馈机制:例如震动或指示灯,反馈操作状态。

  10. 多手势支持:实现点击、滑动、缩放等多种操作。

  11. 个性化设置:允许用户自定义手势映射和功能。

  12. 兼容性测试:确保与多种电脑和操作系统兼容。

如何交互

将鼠标和键盘合并在一起

细节设计

鼠标的基础功能

触摸盘操作

功能使用范围

1、方案光学鼠标方式

2、陀螺仪方案

3、触摸盘解决方案

通过绑定不同手势 实现不同传输方式的交互

戒子使用可弹性的夹子 有力但不大

05-ring_mouse_yankodesign.jpg03-ring_mouse_yankodesign.jpg02-ring_mouse_yankodesign.jpg

最简单的方式利用电竞曝光,

软件操作曝光

previewImag

外观为一个半弧形 有一条绳子可以伸缩的 原理类似皮带, 和一个小卡扣

根据参数传递过来获取对应的数据

通过esp32传递数据到蓝牙里面 传递到本地服务中

1、开发阶段接入数据 一共6组数据,通过数据的连续性训练AI模型,然后通过传递的数据获取具体的操作内容,

上下左右滑动等操作

技术实现方案

1、 开发硬件相关传输方法

陀螺仪可以获取六组数据通过六组数据

xyz 3轴的加速度 以及 偏转位置

通过连续的多个点获得当前设备运动轨迹

通过运动轨迹投射到一个平面上面利用AI计算处当前的输入内容

2、蓝牙传输

将多组数据传输给后台后台采集数据

esp32 陀螺仪驱动

esp32 蓝牙传输

后台活去蓝牙传输数据 通过数据转换成对应 的文字

现有问题

如何标识输入的间隔

如何关闭输入 关闭输出

如何确定是信息输入间隔

蓝牙相关源码

import bluetooth

# 初始化蓝牙串口服务

bt = bluetooth.Bluetooth()

bt.start()

# 等待连接

print("等待蓝牙设备连接...")

client_socket, client_info = bt.accept()

while True:

try:

# 接收数据

data = client_socket.recv(1024)

if data:

print("接收到数据:", data.decode())

# 处理数据

# ...

# 发送响应

response = "已收到数据"

client_socket.send(response.encode())

except OSError:

break

# 关闭连接

client_socket.close()

bt.stop()

硬件完成百分之20

通过蓝牙或者wifi 客户端 向系统上报数据

算法优化

传输数据大切块

灵敏度淘汰一部分数据

外观设计

未命名.png程序源码

模型源码

戒指6.stl

esp32 源码

from machine import Pin,Timer, I2C
import time
import bluetooth


keys = [Pin(1, Pin.IN, Pin.PULL_UP), Pin(2, Pin.IN, Pin.PULL_UP), Pin(3, Pin.IN, Pin.PULL_UP), Pin(4, Pin.IN, Pin.PULL_UP), Pin(5, Pin.IN, Pin.PULL_UP)]
last_send_times = [0] * len(keys)  # 记录每个按钮上次发送的时间

# 定义全局变量
BLE_MESSAGE = ""
Vbat_Pin = 29
I2C_SDA = 11
I2C_SDL = 12
SEND_M = False


class ESP32S3_BLE():
     def __init__(self, name):
         self.timer1 =Timer(0)
         self.name =name
         # 初始化蓝牙
         self.ble =bluetooth.BLE()
         # 开启蓝牙
         self.ble.active(True)
         # 设置蓝牙名称
         self.ble.config(gap_name=name)
         # 配置蓝牙回调函数
         self.ble.irq(self.ble_irq)
         # 注册蓝牙
         self.register()
         self.advertiser()

     def ble_irq(self, event, data):
         global BLE_MESSAGE
         # _IRQ_CENTRAL_CONNECT 蓝牙终端链接了此设备
         print(event)
         if event == 1:
             self.connected()
         # _IRQ_CENTRAL_DISCONNECT 蓝牙终端断开此设备
         elif event == 2:
             self.advertiser()
             self.disconnected()
         # _IRQ_GATTS_WRITE 蓝牙终端向 ESP32-S3 发送数据,接收数据处理
         elif event == 3: 
             buffer = self.ble.gatts_read(self.rx)
             BLE_MESSAGE = buffer.decode('UTF-8').strip()
             
     def connected(self):
         global SEND_M
         print("connnect")
         SEND_M = True
         
     def disconnected(self): 
         global SEND_M
         print("disconnected")
         SEND_M = False
         
     def send(self, data):
         self.ble.gatts_notify(1, self.tx, data + '\n')     
            
     def register(self):
         service_uuid = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E'
         reader_uuid = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E'
         sender_uuid = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'
         services = (
             (
                 bluetooth.UUID(service_uuid),
                 (
                     (bluetooth.UUID(sender_uuid), bluetooth.FLAG_NOTIFY),
                     (bluetooth.UUID(reader_uuid), bluetooth.FLAG_WRITE),
                 )
             ),
         )

         ((self.tx, self.rx,), ) = self.ble.gatts_register_services(services)
     
     def advertiser(self):
         name = bytes(self.name, 'UTF-8')
         adv_data = bytearray('\x02\x01\x02', 'UTF-8') + bytearray((len(name) + 1, 0x09), 'UTF-8') + name
         self.ble.gap_advertise(100, adv_data)
         
class QMI8658(object):
    def __init__(self,address=0X6B):
        self._address = address
        self._bus = I2C(scl=Pin(I2C_SDL), sda=Pin(I2C_SDA), freq=20_000)
        
        bRet=self.WhoAmI()
        if bRet :
            self.Read_Revision()
        else    :
            return NULL
        self.Config_apply()

    def _read_byte(self,cmd):
        rec=self._bus.readfrom_mem(int(self._address),int(cmd),1)
        return rec[0]
    def _read_block(self, reg, length=1):
        rec=self._bus.readfrom_mem(int(self._address),int(reg),length)
        return rec
    def _read_u16(self,cmd):
        LSB = self._bus.readfrom_mem(int(self._address),int(cmd),1)
        MSB = self._bus.readfrom_mem(int(self._address),int(cmd)+1,1)
        return (MSB[0] << 8) + LSB[0]
    def _write_byte(self,cmd,val):
        self._bus.writeto_mem(int(self._address),int(cmd),bytes([int(val)]))
        
    def WhoAmI(self):
        bRet=False
        if (0x05) == self._read_byte(0x00):
            bRet = True
        return bRet
    def Read_Revision(self):
        return self._read_byte(0x01)
    def Config_apply(self):
        # REG CTRL1
        self._write_byte(0x02,0x60)
        # REG CTRL2 : QMI8658AccRange_8g  and QMI8658AccOdr_1000Hz
        self._write_byte(0x03,0x23)
        # REG CTRL3 : QMI8658GyrRange_512dps and QMI8658GyrOdr_1000Hz
        self._write_byte(0x04,0x53)
        # REG CTRL4 : No
        self._write_byte(0x05,0x00)
        # REG CTRL5 : Enable Gyroscope And Accelerometer Low-Pass Filter 
        self._write_byte(0x06,0x11)
        # REG CTRL6 : Disables Motion on Demand.
        self._write_byte(0x07,0x00)
        # REG CTRL7 : Enable Gyroscope And Accelerometer
        self._write_byte(0x08,0x03)

    def Read_Raw_XYZ(self):
        xyz=[0,0,0,0,0,0]
        raw_timestamp = self._read_block(0x30,3)
        raw_acc_xyz=self._read_block(0x35,6)
        raw_gyro_xyz=self._read_block(0x3b,6)
        raw_xyz=self._read_block(0x35,12)
        timestamp = (raw_timestamp[2]<<16)|(raw_timestamp[1]<<8)|(raw_timestamp[0])
        for i in range(6):
            xyz[i] = (raw_xyz[(i*2)+1]<<8)|(raw_xyz[i*2])
            if xyz[i] >= 32767:
                xyz[i] = xyz[i]-65535
        return xyz
    def Read_XYZ(self):
        xyz=[0,0,0,0,0,0]
        raw_xyz=self.Read_Raw_XYZ()
        acc_lsb_div=(1<<12)
        gyro_lsb_div = 64
        for i in range(3):
            xyz[i]=raw_xyz[i]/acc_lsb_div
            xyz[i+3]=raw_xyz[i+3]*1.0/gyro_lsb_div
        return xyz

def CLICK_KEY(ble):
    for i, key in enumerate(keys):
        if key.value() == 0:  # 判断按钮是否按下
            time.sleep_ms(10)  # 消抖延时
            if key.value() == 0:  # 再次确认按下
                current_time = time.time() * 1000  # 获取当前时间(毫秒)
                if (current_time - last_send_times[i]) >= 40:  # 如果距离上次发送超过 40 毫秒
                    # 发送按钮信号
                    SEND_CMD = f"btn | {i + 1}"
                    print(SEND_CMD)
                    if SEND_M == True:
                        ble.send(SEND_CMD)
                    last_send_times[i] = current_time  # 更新上次发送时间
                        
def LOAD_MSG():
    qmi8658 = QMI8658()
    ble = ESP32S3_BLE("FINGERTIPS BLE")
    i = 0
    while(True):
        #read QMI8658
        CLICK_KEY(ble)
        xyz = qmi8658.Read_XYZ()
        x = "move | {:+.2f} {:+.2f} {:+.2f} {:+.2f} {:+.2f} {:+.2f}".format(xyz[0], xyz[1], xyz[2], xyz[3], xyz[4], xyz[5])
        print(x)
        if SEND_M == True :
            ble.send(x)
            
if __name__ == "__main__":

    LOAD_MSG()
   
  

后端源码

import asyncio

from bleak import BleakClient, BleakScanner
from bleak.backends.characteristic import BleakGATTCharacteristic
from pynput.mouse import Controller, Button

# 设备名称
device_name: str = "FINGERTIPS BLE"
msg_count = 0
# 最大连接超时时间
timeout = 10

# 时间间隔(模拟采样间隔)
delta_t = 0.1
# 防抖阈值
debounce_threshold = 0.1

old_accelerations = []
move_x = 0
move_y = 0
move_speed = 3
flexibility = 1
mouse = Controller()

def deal_acc(acc):
    x = abs(acc)
    if x < 10:
        x = 1
    if 10 < x < 100:
        x = 2
    if x > 100:
        x = 3
    return round(x)
def deal_acc_x(acc):
    x = abs(acc)
    if 10 < x < 50:
        x = 1
    if 50 < x < 100:
        x = 5
    if 100< x < 200:
        x = 10
    if 200< x < 300:
        x = 20
    if 300 < x:
        x = 50
    return round(x)

def deal_acc_y(acc):
    x = abs(acc)
    if 10 < x < 40:
        x = 1
    if 40 < x < 100:
        x = 5
    if 100< x < 200:
        x = 10
    if 200< x < 300:
        x = 20
    if 300 < x:
        x = 50
    return round(x)

def determine_sign(num):
    if num > 0:
        return 1
    elif num < 0:
        return -1
    else:
        return 0

def calculate_mouse_movement_acc(accelerations):
    global old_accelerations, move_x, move_y, i
    mouse = Controller()
    if len(old_accelerations) > 0:
        #print(old_accelerations)
        if (abs(accelerations[4]) > 10 and accelerations[1] > -0.3 and accelerations[1] < 0.3):
            move_x = deal_acc_x(accelerations[4]) * determine_sign(old_accelerations[4]*10)
        if (abs(accelerations[3]) > 10 and accelerations[0] > - 0.65 and accelerations[0] < 0.9):
            move_y = deal_acc_y(accelerations[3]) * determine_sign(old_accelerations[3])
        # 左右移动上下防抖
    move_x = round(move_x * move_speed)
    move_y = round(move_y * move_speed)
    mouse.move(move_y, -move_x)
    old_accelerations = accelerations
    move_x = 0
    move_y = 0

def calculate_mouse_movement(accelerations):
    global old_accelerations, move_x, move_y, mouse

    if len(old_accelerations) > 0:
        print(old_accelerations)
        if (abs(accelerations[0] - old_accelerations[0]) > debounce_threshold):
            move_x = (accelerations[0] - old_accelerations[0]) * deal_acc(accelerations[3])
        elif accelerations[0] > 0.6:
            move_x = 1
        elif accelerations[0] < 0.3:
            move_x = -1

        if (abs(accelerations[1] - old_accelerations[1]) > debounce_threshold):
            move_y = (accelerations[1] - old_accelerations[1]) * deal_acc(accelerations[4])
        elif accelerations[1] > 0.3:
            move_y = 1
        elif accelerations[1] < -0.3:
            move_y = -1
    move_x = - round(move_x * move_speed)
    move_y =  round(move_y * move_speed)
    #print(f"移动位置: x={move_x}, y={move_y}")
    mouse.move(move_y, move_x)
    old_accelerations = accelerations
    move_x = 0
    move_y = 0

# 通知处理器函数,当收到通知时调用
move_list = []

def notification_handler(characteristic: BleakGATTCharacteristic, data: bytearray):
    global old_finger_data, move_list, flexibility
    data_str = data.decode('utf-8')
    # 按空格分割字符串,并转换为浮点数存储在列表中
    opt_type = data_str.split("|")[0].split()
    data = data_str.split("|")[1].split()
    if opt_type == ["move"]:
        deal_move(data)
    if opt_type == ["btn"]:
        deal_btn(data)
def deal_btn(data):
    global mouse
    print(data)
    # 1 F: 左键 4 R:  3 L:  2 B: 右键  5 Z:
    if data == ["1"]:
        #左键
        mouse.press(Button.left)
    if data == ["2"]:
        # 左键
        mouse.press(Button.right)
    if data == ["5"]:
        # 左键
        mouse.click(Button.left, 2)
    if data == ["3"]:
        # 滚动向上
        mouse.scroll(0, 2)  # 垂直向上滚动 2 格
    if data == ["4"]:
        # 滚动向下
        mouse.scroll(0, -2)   # 垂直向上滚动 2 格


def deal_move(data_list):
    global msg_count
    data_list = [float(item) for item in data_list]
    # 增加淘汰率减少参数 降低灵敏度
    msg_count = msg_count + 1
    if msg_count % flexibility == 0:
        calculate_mouse_movement_acc(data_list)

# print(i)
# 异步函数:扫描设备并根据本地名称获取地址
async def scan_for_device(device_name: str):
    devices = await BleakScanner.discover()
    for device in devices:
        if device.name == device_name:
            return device
    return None


# 异步函数:查找描述为“Heart Rate Measurement”的特征UUID
async def find_heart_rate_measurement_uuid(client: BleakClient):
    for service in client.services:
        for characteristic in service.characteristics:
            print(characteristic.uuid)
            return characteristic.uuid
    return None


# 异步主函数
async def main():
    print("Starting scan...")
    # 根据设备名称扫描设备,也可以直接填入设备的物理地址
    device = await scan_for_device(device_name)

    if device is None:
        print(f"Device with name {device_name} not found.")
        return

    print(f"Found device: {device.name} ({device.address})")

    # 创建一个事件对象,用于处理断开连接的情况
    disconnected_event = asyncio.Event()

    # 断开连接时的回调函数
    def disconnected_callback(client):
        print("Disconnected callback called!")
        disconnected_event.set()

    print("Connecting to device...")
    async with BleakClient(device, disconnected_callback=disconnected_callback, timeout=timeout) as client:
        print("Connected")
        # 查找“Heart Rate Measurement”特征UUID(心率广播)
        hr_measurement_uuid = await find_heart_rate_measurement_uuid(client)
        if hr_measurement_uuid:
            # 开始通知,并设置通知处理器
            await client.start_notify(hr_measurement_uuid, notification_handler)
            # 等待断开连接事件
            await disconnected_event.wait()
        else:
            print("Heart Rate Measurement characteristic not found.")


# 运行异步主函数
asyncio.run(main())

现有问题

1、鼠标的准星问题

2、操作按钮时候 的鼠标产生的抖动如何消除

3、后端程序安装鼠标驱动

4、搜索蓝牙不是使用的蓝牙名称 而是使用默认名字

5、走线问题

6、后端代码默认启动

7、省电模式开发