我正在设置一个我想将 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
此外,蓝牙连接一旦与远程机器“连接”就会断开。
我还注意到,我认为 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>
如果有人可以帮助我或为我指明正确的方向,我会非常高兴,以使其再次正常工作。
在此先感谢您的帮助!