我正在设置我想将RaspberryPi-3连接到另一台机器的设置。大部分时间它都是Windows机器。在Pi上我想通过蓝牙将所有键盘笔划转发到另一台(Windows)机器。
因此我做了一些研究,几年前有人叫Liam写了一个Python脚本,显然在BlueZ 4上运行得很好。所以我决定试一试BlueZ 5.目前还没有运气。
好吧,我想,好吧,让我们降级版本。所以我从源代码4.10开始编译它。没运气。树莓甚至不知道它安装了蓝牙适配器,可能是一些驱动程序出了问题。
然后我开始进入BlueZ 5,起初它很容易。但实际上我现在正在努力,以获得sdp_Record.xml注册。以及稳定的连接。
首先是BlueZ 4实现的参考文件:
BlueZ4.py:
#!/usr/bin/python2.7
#
# PiTooth allows the Raspberry Pi to act as a Bluetooth keyboard, and relays
# keypresses from a USB keyboard to a Bluetooth client. Written by Liam Fraser
# for a Linux User & Developer tutorial.
#
import os # Used to call external commands
import sys # Used to exit the script
import bluetooth
from bluetooth import *
import dbus # Used to set up the SDP record
import time # Used for pausing the process
import evdev # Used to get input from the keyboard
from evdev import *
import keymap # Used to map evdev input to hid keycodes
class Bluetooth:
HOST = 0 # BT Mac address
PORT = 1 # Bluetooth Port Number
# Define the ports we'll use
P_CTRL = 17
P_INTR = 19
def __init__(self):
# Set the device class to a keyboard and set the name
os.system("hciconfig hci0 class 0x002540")
os.system("hciconfig hci0 name Raspberry\ Pi")
# Make device discoverable
os.system("hciconfig hci0 piscan")
# Define our two server sockets for communication
self.scontrol = BluetoothSocket(L2CAP)
self.sinterrupt = BluetoothSocket(L2CAP)
# Bind these sockets to a port
self.scontrol.bind(("", Bluetooth.P_CTRL))
self.sinterrupt.bind(("", Bluetooth.P_INTR))
# Set up dbus for advertising the service record
self.bus = dbus.SystemBus()
try:
self.manager = dbus.Interface(self.bus.get_object("org.bluez", "/"),
"org.bluez.Manager")
adapter_path = self.manager.DefaultAdapter()
self.service = dbus.Interface(self.bus.get_object("org.bluez", adapter_path),
"org.bluez.Service")
except:
sys.exit("[Bluetooth - L.50] Could not configure bluetooth. Is bluetoothd started?")
# Read the service record from file
try:
fh = open(sys.path[0] + "/sdp_record.xml", "r")
except:
sys.exit("[Bluetooth - L.56] Could not open the sdp record. Exiting...")
self.service_record = fh.read()
fh.close()
def listen(self):
# Advertise our service record
self.service_handle = self.service.AddRecord(self.service_record)
print "[Bluetooth - L.63] Service record added"
# Start listening on the server sockets
self.scontrol.listen(1) # Limit of 1 connection
self.sinterrupt.listen(1)
print "[Bluetooth - L.68] Waiting for a connection"
self.ccontrol, self.cinfo = self.scontrol.accept()
print "[Bluetooth - L.70] Got a connection on the control channel from " + self.cinfo[Bluetooth.HOST]
self.cinterrupt, self.cinfo = self.sinterrupt.accept()
print "[Bluetooth - L.72] Got a connection on the interrupt channel from " + self.cinfo[Bluetooth.HOST]
def send_input(self, ir):
# Convert the hex array to a string
hex_str = ""
for element in ir:
if type(element) is list:
# This is our bit array - convert it to a single byte represented
# as a char
bin_str = ""
for bit in element:
bin_str += str(bit)
hex_str += chr(int(bin_str, 2))
else:
# This is a hex value - we can convert it straight to a char
hex_str += chr(element)
# Send an input report
self.cinterrupt.send(hex_str)
class Keyboard():
def __init__(self):
# The structure for an bt keyboard input report (size is 10 bytes)
self.state = [
0xA1, # This is an input report
0x01, # Usage report = Keyboard
# Bit array for Modifier keys (D7 being the first element, D0 being last)
[0, # Right GUI - (usually the Windows key)
0, # Right ALT
0, # Right Shift
0, # Right Control
0, # Left GUI - (again, usually the Windows key)
0, # Left ALT
0, # Left Shift
0], # Left Control
0x00, # Vendor reserved
0x00, # Rest is space for 6 keys
0x00,
0x00,
0x00,
0x00,
0x00 ]
# Keep trying to get a keyboard
have_dev = False
while have_dev == False:
try:
# Try and get a keyboard - should always be event0 as we're only
# plugging one thing in
self.dev = InputDevice("/dev/input/event0")
have_dev = True
except OSError:
print "[Keyboard - L.124] - Keyboard not found, waiting 3 seconds and retrying"
time.sleep(3)
print "[Keyboard - L.127]Found a keyboard"
def change_state(self, event):
evdev_code = ecodes.KEY[event.code]
modkey_element = keymap.modkey(evdev_code)
if modkey_element > 0:
# Need to set one of the modifier bits
if self.state[2][modkey_element] == 0:
self.state[2][modkey_element] = 1
else:
self.state[2][modkey_element] = 0
else:
# Get the hex keycode of the key
hex_key = keymap.convert(ecodes.KEY[event.code])
# Loop through elements 4 to 9 of the input report structure
for i in range (4, 10):
if self.state[i] == hex_key and event.value == 0:
# Code is 0 so we need to depress it
self.state[i] = 0x00
elif self.state[i] == 0x00 and event.value == 1:
# If the current space is empty and the key is being pressed
self.state[i] = hex_key
break
def event_loop(self, bt):
for event in self.dev.read_loop():
# Only bother if we a key and it's an up or down event
if event.type == ecodes.EV_KEY and event.value < 2:
self.change_state(event)
bt.send_input(self.state)
if __name__ == "__main__":
# We can only run as root
if not os.geteuid() == 0:
sys.exit("[FATAL] - Only root can run this script (sudo?)")
bt = Bluetooth()
bt.listen()
kb = Keyboard()
kb.event_loop(bt)
到目前为止,我所做的是尝试将旧的BlueZ 4代码迁移到版本5。
到目前为止我的实施:
#!/usr/bin/python2.7
import os
import sys
import bluetooth
from bluetooth import *
import dbus
import time
import evdev
from evdev import *
import keymap
class Bluetooth:
HOST = "<REMOTEMACHINEMAC>" #<PIMAC>
#HOST = 0
PORT = 1
# Define the ports we'll use
P_CTRL = 17
P_INTR = 19
def __init__(self):
os.system("hciconfig hci0 class 0x002540")
os.system("hciconfig hci0 name Raspberry\ Pi")
# Define our two server sockets for communication
self.scontrol = BluetoothSocket(L2CAP)
self.sinterrupt = BluetoothSocket(L2CAP)
# Bind these sockets to a port
self.scontrol.bind(("", Bluetooth.P_CTRL))
self.sinterrupt.bind(("", Bluetooth.P_INTR))
# Set up dbus for advertising the service record
self.bus = dbus.SystemBus()
# Set up dbus for advertising the service record
try:
self.objManager = dbus.Interface(self.bus.get_object("org.bluez", "/"),
"org.freedesktop.DBus.ObjectManager")
#print self.manager.GetManagedObjects()["/org/bluez/hci0"]
self.manager = dbus.Interface(self.bus.get_object("org.bluez", "/org/bluez"),
"org.bluez.ProfileManager1")
self.hci_props = dbus.Interface(self.bus.get_object("org.bluez", "/org/bluez/hci0"),
"org.freedesktop.DBus.Properties")
except:
print sys.exc_info()
sys.exit("[FATAL] Could not set up Bluez5")
# Read the service record from file
try:
fh = open(sys.path[0] + "/sdp_record.xml", "r")
except:
sys.exit("[Bluetooth - L.56] Could not open the sdp record. Exiting...")
self.service_record = fh.read()
fh.close()
try:
opts = { "AutoConnect": 1, "ServiceRecord": self.service_record }
uuidarray = self.hci_props.Get("org.bluez.Adapter1", "UUIDs")
for uuids in uuidarray:
try:
self.manager.RegisterProfile("/org/bluez/hci0", uuids, opts)
except:
print uuids
print "Service Record saved!"
except:
print "Service Records saved. Probably already exists"
#print sys.exc_info()
#sys.exit("Error updating service record")
print "Update class again"
#os.system("hciconfig hci0 class 0x002540")
#os.system("hciconfig hci0 name Raspberry\ Pi")
def listen(self):
# Advertise our service record
#self.service_handle = self.service.AddRecord(self.service_record)
#print "[Bluetooth - L.63] Service record added"
# Start listening on the server sockets
self.scontrol.listen(1) # Limit of 1 connection
self.sinterrupt.listen(1)
print "[Bluetooth - L.68] Waiting for a connection"
self.ccontrol, self.cinfo = self.scontrol.accept()
print "[Bluetooth - L.70] Got a connection on the control channel from " + self.cinfo[Bluetooth.HOST]
self.cinterrupt, self.cinfo = self.sinterrupt.accept()
print "[Bluetooth - L.72] Got a connection on the interrupt channel from " + self.cinfo[Bluetooth.HOST]
def python_to_data(self, data):
if isinstance(data, str):
data = dbus.String(data)
elif isinstance(data, bool):
data = dbus.Boolean(data)
elif isinstance(data, int):
data = dbus.Int64(data)
elif isinstance(data, float):
data = dbus.Double(data)
elif isinstance(data, list):
data = dbus.Array([self.python_to_data(value) for value in data], signature='v')
elif isinstance(data, dict):
data = dbus.Dictionary(data, signature='sv')
for key in data.keys():
data[key] = self.python_to_data(data[key])
return data
class Keyboard():
def __init__(self):
# The structure for an bt keyboard input report (size is 10 bytes)
self.state = [
0xA1, # This is an input report
0x01, # Usage report = Keyboard
# Bit array for Modifier keys (D7 being the first element, D0 being last)
[0, # Right GUI - (usually the Windows key)
0, # Right ALT
0, # Right Shift
0, # Right Control
0, # Left GUI - (again, usually the Windows key)
0, # Left ALT
0, # Left Shift
0], # Left Control
0x00, # Vendor reserved
0x00, # Rest is space for 6 keys
0x00,
0x00,
0x00,
0x00,
0x00 ]
# Keep trying to get a keyboard
have_dev = False
while have_dev == False:
try:
# Try and get a keyboard - should always be event0 as we're only
# plugging one thing in
self.dev = InputDevice("/dev/input/event0")
have_dev = True
except OSError:
print "[Keyboard - L.124] - Keyboard not found, waiting 3 seconds and retrying"
time.sleep(3)
print "[Keyboard - L.127]Found a keyboard"
def change_state(self, event):
evdev_code = ecodes.KEY[event.code]
modkey_element = keymap.modkey(evdev_code)
if modkey_element > 0:
# Need to set one of the modifier bits
if self.state[2][modkey_element] == 0:
self.state[2][modkey_element] = 1
else:
self.state[2][modkey_element] = 0
else:
# Get the hex keycode of the key
hex_key = keymap.convert(ecodes.KEY[event.code])
# Loop through elements 4 to 9 of the input report structure
for i in range (4, 10):
if self.state[i] == hex_key and event.value == 0:
# Code is 0 so we need to depress it
self.state[i] = 0x00
elif self.state[i] == 0x00 and event.value == 1:
# If the current space is empty and the key is being pressed
self.state[i] = hex_key
break
def event_loop(self, bt):
for event in self.dev.read_loop():
# Only bother if we a key and it's an up or down event
if event.type == ecodes.EV_KEY and event.value < 2:
self.change_state(event)
bt.send_input(self.state)
if __name__ == "__main__":
# We can only run as root
if not os.geteuid() == 0:
sys.exit("[FATAL] - Only root can run this script (sudo?)")
bt = Bluetooth()
bt.listen()
kb = Keyboard()
kb.event_loop(bt)
问题是,我现在想知道的是:
HOST
变量是否正确?PORT
是否正确?因为,我打印出的Python脚本是:
[Bluetooth - L.68] Waiting for a connection
同样,蓝牙连接也随着它的连接而下降,并连接了#34;用远程机器。
我还注意到,我认为SPD_Record.xml未能正确设置。
SDP_Record.xml供参考:
<?xml version="1.0" encoding="UTF-8" ?>
<record>
<attribute id="0x0001">
<sequence>
<uuid value="0x1124" />
</sequence>
</attribute>
<attribute id="0x0004">
<sequence>
<sequence>
<uuid value="0x0100" />
<uint16 value="0x0011" />
</sequence>
<sequence>
<uuid value="0x0011" />
</sequence>
</sequence>
</attribute>
<attribute id="0x0005">
<sequence>
<uuid value="0x1002" />
</sequence>
</attribute>
<attribute id="0x0006">
<sequence>
<uint16 value="0x656e" />
<uint16 value="0x006a" />
<uint16 value="0x0100" />
</sequence>
</attribute>
<attribute id="0x0009">
<sequence>
<sequence>
<uuid value="0x1124" />
<uint16 value="0x0100" />
</sequence>
</sequence>
</attribute>
<attribute id="0x000d">
<sequence>
<sequence>
<sequence>
<uuid value="0x0100" />
<uint16 value="0x0013" />
</sequence>
<sequence>
<uuid value="0x0011" />
</sequence>
</sequence>
</sequence>
</attribute>
<attribute id="0x0100">
<text value="Raspberry Pi Virtual Keyboard" />
</attribute>
<attribute id="0x0101">
<text value="USB > BT Keyboard" />
</attribute>
<attribute id="0x0102">
<text value="Raspberry Pi" />
</attribute>
<attribute id="0x0200">
<uint16 value="0x0100" />
</attribute>
<attribute id="0x0201">
<uint16 value="0x0111" />
</attribute>
<attribute id="0x0202">
<uint8 value="0x40" />
</attribute>
<attribute id="0x0203">
<uint8 value="0x00" />
</attribute>
<attribute id="0x0204">
<boolean value="true" />
</attribute>
<attribute id="0x0205">
<boolean value="true" />
</attribute>
<attribute id="0x0206">
<sequence>
<sequence>
<uint8 value="0x22" />
<text encoding="hex" value="05010906a101850175019508050719e029e715002501810295017508810395057501050819012905910295017503910395067508150026ff000507190029ff8100c0050c0901a1018503150025017501950b0a23020a21020ab10109b809b609cd09b509e209ea09e9093081029501750d8103c0" />
</sequence>
</sequence>
</attribute>
<attribute id="0x0207">
<sequence>
<sequence>
<uint16 value="0x0409" />
<uint16 value="0x0100" />
</sequence>
</sequence>
</attribute>
<attribute id="0x020b">
<uint16 value="0x0100" />
</attribute>
<attribute id="0x020c">
<uint16 value="0x0c80" />
</attribute>
<attribute id="0x020d">
<boolean value="false" />
</attribute>
<attribute id="0x020e">
<boolean value="true" />
</attribute>
<attribute id="0x020f">
<uint16 value="0x0640" />
</attribute>
<attribute id="0x0210">
<uint16 value="0x0320" />
</attribute>
</record>
如果有人可以帮助或指出我正确的方向,我会非常高兴,让它重新开始工作。
提前感谢您的帮助!
答案 0 :(得分:1)
在进一步搜索全球网络后,我在GitHub上偶然发现了一个有趣的存储库:
https://github.com/quangthanh010290/BL_keyboard_RPI
以及非常有趣的网站:
http://www.mlabviet.com/2017/09/make-raspberry-pi3-as-emulator.html
在查看代码和一些调整后,我能够完全正常工作。我的问题也得到了解答。我从中学到了什么:
绑定空地址:self.scontrol.bind(("", Bluetooth.P_CTRL))
由于bluetoothd
服务无效,导致我无法绑定它。我注意到这一点,因为使用@ quangthanh010290脚本告诉我,我的蓝牙MAC已经在使用。
杀死它之后:sudo killall bluetoothd
我可以正确绑定到给定的MAC地址,一切正常。
答案 1 :(得分:0)
为我工作。我唯一需要做的事情是在整个操作之前先运行命令tmux start-server,否则找不到进程错误。