基础设计
传感器:用于检测手势动作,如陀螺仪、加速度计等。
无线通信模块:与电脑进行无线连接,如蓝牙。
微控制器:处理传感器数据并发送控制指令。
电源:如小型电池,为戒指供电。
戒指外壳:舒适、贴合手指,可集成传感器和电路。
配套软件:安装在电脑上,解读手势指令并执行相应操作。
手势识别算法:识别不同的手势动作。
校准功能:根据用户的手势习惯进行校准。
反馈机制:例如震动或指示灯,反馈操作状态。
多手势支持:实现点击、滑动、缩放等多种操作。
个性化设置:允许用户自定义手势映射和功能。
兼容性测试:确保与多种电脑和操作系统兼容。
如何交互
将鼠标和键盘合并在一起
细节设计
鼠标的基础功能
触摸盘操作
功能使用范围
1、方案光学鼠标方式
2、陀螺仪方案
3、触摸盘解决方案
通过绑定不同手势 实现不同传输方式的交互
戒子使用可弹性的夹子 有力但不大
最简单的方式利用电竞曝光,
软件操作曝光
外观为一个半弧形 有一条绳子可以伸缩的 原理类似皮带, 和一个小卡扣
根据参数传递过来获取对应的数据
通过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 客户端 向系统上报数据
算法优化
传输数据大切块
灵敏度淘汰一部分数据
外观设计
程序源码
模型源码
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、省电模式开发