mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
slop
This commit is contained in:
@@ -182,7 +182,7 @@ if [[ -f "$BALANCE_BOARD_NATIVE_DIR/Makefile" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Only this small audited bridge needs the management socket used for the
|
# Only this small audited bridge needs the management socket used for the
|
||||||
# board's raw six-byte pairing PIN and the reserved HID interrupt PSM used by
|
# board's raw six-byte pairing PIN and the two reserved HID PSMs used by
|
||||||
# front-button reconnects. Never grant either capability to node or the full
|
# front-button reconnects. Never grant either capability to node or the full
|
||||||
# multirover service executable.
|
# multirover service executable.
|
||||||
setcap cap_net_admin,cap_net_bind_service+ep "$BALANCE_BOARD_WORKER"
|
setcap cap_net_admin,cap_net_bind_service+ep "$BALANCE_BOARD_WORKER"
|
||||||
@@ -195,11 +195,11 @@ if [[ ! -f "$CONFIG_PATH" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Bluetoothd remains responsible for discovery and the one-time bond, but its
|
# Bluetoothd remains responsible for discovery and the one-time bond, but its
|
||||||
# generic input plugin otherwise reserves PSM 0x13 before the Balance Board
|
# generic input plugin otherwise reserves control PSM 0x11 and interrupt PSM
|
||||||
# worker can listen for the board's front-button reconnect. This dedicated rover
|
# 0x13 before the Balance Board worker can listen for the board's front-button
|
||||||
# server gives that one HID listener to the worker; every other BlueZ profile is
|
# reconnect. This dedicated rover server gives those two HID listeners to the
|
||||||
# left enabled. Clearing ExecStart is required by systemd before replacing the
|
# worker; every other BlueZ profile is left enabled. Clearing ExecStart is
|
||||||
# vendor unit's command in a drop-in.
|
# required by systemd before replacing the vendor unit's command in a drop-in.
|
||||||
install -d -m 0755 "$BLUETOOTH_OVERRIDE_DIR"
|
install -d -m 0755 "$BLUETOOTH_OVERRIDE_DIR"
|
||||||
cat > "$BLUETOOTH_OVERRIDE" <<'EOF'
|
cat > "$BLUETOOTH_OVERRIDE" <<'EOF'
|
||||||
[Service]
|
[Service]
|
||||||
|
|||||||
@@ -140,9 +140,10 @@ function handleWorkerMessage(message = {}) {
|
|||||||
updateStatus('zeroing', 'Connected. Keep the board empty for one second while it zeros.');
|
updateStatus('zeroing', 'Connected. Keep the board empty for one second while it zeros.');
|
||||||
} else if (workerState === 'link-detected') {
|
} else if (workerState === 'link-detected') {
|
||||||
connected = false;
|
connected = false;
|
||||||
// A controller connection proves the physical board answered, but Bluetooth
|
// The native bridge can now distinguish which half of the board's HID
|
||||||
// does not identify which physical button woke it. Keep the message exact.
|
// connection reached the server. Preserve that diagnostic until both
|
||||||
updateStatus('connecting', 'Board responded. Reading its sensor calibration.');
|
// channels arrive; the generic text remains for the outbound Sync flow.
|
||||||
|
updateStatus('connecting', message.error || 'Board responded. Reading its sensor calibration.');
|
||||||
} else if (workerState === 'connection-failed') {
|
} else if (workerState === 'connection-failed') {
|
||||||
connected = false;
|
connected = false;
|
||||||
latestFrame = null;
|
latestFrame = null;
|
||||||
|
|||||||
@@ -14,15 +14,15 @@
|
|||||||
//
|
//
|
||||||
// After commissioning, this worker owns both directions of the HID transport.
|
// After commissioning, this worker owns both directions of the HID transport.
|
||||||
// Red Sync uses wiiuse's normal outbound connection; the front power button is
|
// Red Sync uses wiiuse's normal outbound connection; the front power button is
|
||||||
// accepted on an always-open interrupt listener before the worker opens the
|
// accepted through always-open control and interrupt listeners. This is
|
||||||
// matching control channel. This is important rather than stylistic: BlueZ's
|
// important rather than stylistic: BlueZ's
|
||||||
// input profile applies medium security to bonded HID devices, and an original
|
// input profile applies medium security to bonded HID devices, and an original
|
||||||
// Balance Board rejects that request with EACCES. Direct low-security sockets
|
// Balance Board rejects that request with EACCES. Direct low-security sockets
|
||||||
// match the board and avoid the failing profile entirely.
|
// match the board and avoid the failing profile entirely.
|
||||||
//
|
//
|
||||||
// Security boundary:
|
// Security boundary:
|
||||||
// The installed binary receives CAP_NET_ADMIN solely for the Bluetooth
|
// The installed binary receives CAP_NET_ADMIN solely for the Bluetooth
|
||||||
// management socket and CAP_NET_BIND_SERVICE solely for the reserved HID PSM.
|
// management socket and CAP_NET_BIND_SERVICE solely for the reserved HID PSMs.
|
||||||
// The much larger Node server remains unprivileged. Normal sensor access uses
|
// The much larger Node server remains unprivileged. Normal sensor access uses
|
||||||
// ordinary Bluetooth L2CAP sockets through wiiuse.
|
// ordinary Bluetooth L2CAP sockets through wiiuse.
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
// Wiiuse exports these two handshake functions from its shared library but
|
// Wiiuse exports these two handshake functions from its shared library but
|
||||||
// keeps them out of the public header because ordinary callers receive sockets
|
// keeps them out of the public header because ordinary callers receive sockets
|
||||||
// from wiiuse_connect(). The Balance Board's front button reverses the normal
|
// from wiiuse_connect(). The Balance Board's front button reverses the normal
|
||||||
// direction of the interrupt channel, so this bridge must accept that socket
|
// connection direction for both HID channels, so this bridge must accept those
|
||||||
// first and then start the exact same upstream handshake explicitly.
|
// sockets and then start the exact same upstream handshake explicitly.
|
||||||
extern "C" void wiiuse_handshake(struct wiimote_t* board, byte* data, uint16_t length);
|
extern "C" void wiiuse_handshake(struct wiimote_t* board, byte* data, uint16_t length);
|
||||||
extern "C" int wiiuse_set_report_type(struct wiimote_t* board);
|
extern "C" int wiiuse_set_report_type(struct wiimote_t* board);
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ constexpr const char* kDiscoveryTimeoutSeconds = "86400";
|
|||||||
constexpr uint16_t kHidControlPsm = 0x0011;
|
constexpr uint16_t kHidControlPsm = 0x0011;
|
||||||
constexpr uint16_t kHidInterruptPsm = 0x0013;
|
constexpr uint16_t kHidInterruptPsm = 0x0013;
|
||||||
constexpr int kCommissioningConnectWindowMs = 15000;
|
constexpr int kCommissioningConnectWindowMs = 15000;
|
||||||
|
constexpr int kIncomingChannelPairTimeoutMs = 5000;
|
||||||
constexpr int kHandshakeWarningMs = 10000;
|
constexpr int kHandshakeWarningMs = 10000;
|
||||||
constexpr int kEmptyWeightThresholdCentiKg = 200;
|
constexpr int kEmptyWeightThresholdCentiKg = 200;
|
||||||
constexpr int kEmptySleepDelayMs = 2 * 60 * 1000;
|
constexpr int kEmptySleepDelayMs = 2 * 60 * 1000;
|
||||||
@@ -542,7 +543,7 @@ void commissioning_loop(PairingSharedState* shared) {
|
|||||||
// Red Sync makes the board discoverable rather than initiating its normal
|
// Red Sync makes the board discoverable rather than initiating its normal
|
||||||
// host reconnect. Give wiiuse one bounded outbound window immediately
|
// host reconnect. Give wiiuse one bounded outbound window immediately
|
||||||
// after commissioning; every later front-button wake arrives through the
|
// after commissioning; every later front-button wake arrives through the
|
||||||
// interrupt listener instead.
|
// two HID listeners instead.
|
||||||
shared->outbound_connection_requested = true;
|
shared->outbound_connection_requested = true;
|
||||||
}
|
}
|
||||||
emit_json("\"type\":\"paired\",\"address\":\"" + json_escape(address->display) + "\"");
|
emit_json("\"type\":\"paired\",\"address\":\"" + json_escape(address->display) + "\"");
|
||||||
@@ -744,7 +745,7 @@ bool request_low_bluetooth_security(int fd, std::string* error) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int open_interrupt_listener(std::string* error) {
|
int open_hid_listener(uint16_t psm, std::string* error) {
|
||||||
const int fd = socket(AF_BLUETOOTH,
|
const int fd = socket(AF_BLUETOOTH,
|
||||||
SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK,
|
SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK,
|
||||||
BTPROTO_L2CAP);
|
BTPROTO_L2CAP);
|
||||||
@@ -764,7 +765,7 @@ int open_interrupt_listener(std::string* error) {
|
|||||||
|
|
||||||
sockaddr_l2 local{};
|
sockaddr_l2 local{};
|
||||||
local.l2_family = AF_BLUETOOTH;
|
local.l2_family = AF_BLUETOOTH;
|
||||||
local.l2_psm = htobs(kHidInterruptPsm);
|
local.l2_psm = htobs(psm);
|
||||||
// Value initialization leaves l2_bdaddr at the all-zero BDADDR_ANY value.
|
// Value initialization leaves l2_bdaddr at the all-zero BDADDR_ANY value.
|
||||||
// Avoid BlueZ's C-only compound-literal macro, which is not valid C++17.
|
// Avoid BlueZ's C-only compound-literal macro, which is not valid C++17.
|
||||||
if (bind(fd, reinterpret_cast<const sockaddr*>(&local), sizeof(local)) != 0 ||
|
if (bind(fd, reinterpret_cast<const sockaddr*>(&local), sizeof(local)) != 0 ||
|
||||||
@@ -776,9 +777,9 @@ int open_interrupt_listener(std::string* error) {
|
|||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> accept_board_interrupt(int listener,
|
std::optional<int> accept_board_channel(int listener,
|
||||||
const std::string& expected_address,
|
const std::string& expected_address,
|
||||||
std::string* error) {
|
std::string* error) {
|
||||||
sockaddr_l2 remote{};
|
sockaddr_l2 remote{};
|
||||||
socklen_t remote_size = sizeof(remote);
|
socklen_t remote_size = sizeof(remote);
|
||||||
const int fd = accept4(listener, reinterpret_cast<sockaddr*>(&remote),
|
const int fd = accept4(listener, reinterpret_cast<sockaddr*>(&remote),
|
||||||
@@ -794,45 +795,21 @@ std::optional<int> accept_board_interrupt(int listener,
|
|||||||
ba2str(&remote.l2_bdaddr, remote_text);
|
ba2str(&remote.l2_bdaddr, remote_text);
|
||||||
const auto normalized = parse_address(remote_text);
|
const auto normalized = parse_address(remote_text);
|
||||||
if (!normalized.has_value() || normalized->display != expected_address) {
|
if (!normalized.has_value() || normalized->display != expected_address) {
|
||||||
// PSM 0x13 is global to the adapter. The installer dedicates it to this
|
// Both HID PSMs are global to the adapter. The installer dedicates them to
|
||||||
// worker, but still reject any unrelated controller instead of attaching
|
// this worker, but still reject any unrelated controller instead of
|
||||||
// an arbitrary Bluetooth input device to the Balance Board parser.
|
// attaching an arbitrary input device to the Balance Board parser.
|
||||||
close(fd);
|
close(fd);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool attach_incoming_board(wiimote_t* board, int interrupt_fd,
|
void attach_incoming_board(wiimote_t* board, int control_fd, int interrupt_fd,
|
||||||
const std::string& address, std::string* error) {
|
const std::string& address) {
|
||||||
const int control_fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET | SOCK_CLOEXEC,
|
// A reconnecting Wii device opens both channels toward the remembered host:
|
||||||
BTPROTO_L2CAP);
|
// control on PSM 0x11 followed by interrupt on PSM 0x13. Once both accepted
|
||||||
if (control_fd < 0) {
|
// sockets exist, their direction and semantics are identical to the pair
|
||||||
if (error) *error = std::strerror(errno);
|
// created by wiiuse_connect(). Attach them and run the upstream handshake.
|
||||||
close(interrupt_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!request_low_bluetooth_security(control_fd, error)) {
|
|
||||||
close(control_fd);
|
|
||||||
close(interrupt_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sockaddr_l2 remote{};
|
|
||||||
remote.l2_family = AF_BLUETOOTH;
|
|
||||||
remote.l2_psm = htobs(kHidControlPsm);
|
|
||||||
str2ba(address.c_str(), &remote.l2_bdaddr);
|
|
||||||
if (connect(control_fd, reinterpret_cast<const sockaddr*>(&remote), sizeof(remote)) != 0) {
|
|
||||||
if (error) *error = std::strerror(errno);
|
|
||||||
close(control_fd);
|
|
||||||
close(interrupt_fd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From this point the socket arrangement is identical to wiiuse_connect():
|
|
||||||
// commands leave through PSM 0x11 and reports arrive through PSM 0x13. Set
|
|
||||||
// the public Linux fields, mark the transport connected, and invoke wiiuse's
|
|
||||||
// own exported handshake so calibration and report parsing stay upstream.
|
|
||||||
close_wiiuse_sockets(board);
|
close_wiiuse_sockets(board);
|
||||||
wiiuse_disconnected(board);
|
wiiuse_disconnected(board);
|
||||||
prepare_wiiuse_address(board, address);
|
prepare_wiiuse_address(board, address);
|
||||||
@@ -841,7 +818,23 @@ bool attach_incoming_board(wiimote_t* board, int interrupt_fd,
|
|||||||
board->state |= WIIMOTE_STATE_CONNECTED;
|
board->state |= WIIMOTE_STATE_CONNECTED;
|
||||||
wiiuse_handshake(board, nullptr, 0);
|
wiiuse_handshake(board, nullptr, 0);
|
||||||
wiiuse_set_report_type(board);
|
wiiuse_set_report_type(board);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
struct PendingIncomingChannels {
|
||||||
|
int control_fd = -1;
|
||||||
|
int interrupt_fd = -1;
|
||||||
|
uint64_t first_channel_at = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void close_pending_channels(PendingIncomingChannels* pending) {
|
||||||
|
if (!pending) return;
|
||||||
|
if (pending->control_fd >= 0) close(pending->control_fd);
|
||||||
|
if (pending->interrupt_fd >= 0 && pending->interrupt_fd != pending->control_fd) {
|
||||||
|
close(pending->interrupt_fd);
|
||||||
|
}
|
||||||
|
pending->control_fd = -1;
|
||||||
|
pending->interrupt_fd = -1;
|
||||||
|
pending->first_channel_at = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
||||||
@@ -851,17 +844,30 @@ void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string listener_error;
|
std::string control_listener_error;
|
||||||
const int interrupt_listener = open_interrupt_listener(&listener_error);
|
const int control_listener = open_hid_listener(kHidControlPsm, &control_listener_error);
|
||||||
if (interrupt_listener < 0) {
|
if (control_listener < 0) {
|
||||||
emit_status("error", "",
|
emit_status("error", "",
|
||||||
"Cannot listen for the Balance Board front button: " + listener_error +
|
"Cannot listen for the Balance Board control channel: " +
|
||||||
|
control_listener_error +
|
||||||
|
". Run the installer to configure the dedicated Bluetooth listener.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string interrupt_listener_error;
|
||||||
|
const int interrupt_listener = open_hid_listener(
|
||||||
|
kHidInterruptPsm, &interrupt_listener_error);
|
||||||
|
if (interrupt_listener < 0) {
|
||||||
|
close(control_listener);
|
||||||
|
emit_status("error", "",
|
||||||
|
"Cannot listen for the Balance Board interrupt channel: " +
|
||||||
|
interrupt_listener_error +
|
||||||
". Run the installer to configure the dedicated Bluetooth listener.");
|
". Run the installer to configure the dedicated Bluetooth listener.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string prepared_address;
|
std::string prepared_address;
|
||||||
uint64_t outbound_connect_until = 0;
|
uint64_t outbound_connect_until = 0;
|
||||||
|
PendingIncomingChannels pending;
|
||||||
while (running.load()) {
|
while (running.load()) {
|
||||||
std::optional<std::string> address;
|
std::optional<std::string> address;
|
||||||
bool outbound_requested = false;
|
bool outbound_requested = false;
|
||||||
@@ -877,6 +883,11 @@ void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prepared_address != *address) {
|
if (prepared_address != *address) {
|
||||||
|
// Never combine a channel from the previous configured board with a
|
||||||
|
// channel from the new one. This normally matters only after Forget and
|
||||||
|
// re-pair, but keeping the socket pair atomic prevents a misleading
|
||||||
|
// handshake failure during that transition.
|
||||||
|
close_pending_channels(&pending);
|
||||||
close_wiiuse_sockets(board);
|
close_wiiuse_sockets(board);
|
||||||
wiiuse_disconnected(board);
|
wiiuse_disconnected(board);
|
||||||
prepare_wiiuse_address(board, *address);
|
prepare_wiiuse_address(board, *address);
|
||||||
@@ -888,24 +899,57 @@ void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool transport_connected = false;
|
bool transport_connected = false;
|
||||||
std::string incoming_error;
|
std::string control_error;
|
||||||
if (auto interrupt_fd = accept_board_interrupt(
|
if (auto control_fd = accept_board_channel(
|
||||||
interrupt_listener, *address, &incoming_error)) {
|
control_listener, *address, &control_error)) {
|
||||||
emit_status("link-detected", *address);
|
if (pending.control_fd >= 0) close(pending.control_fd);
|
||||||
std::string attach_error;
|
pending.control_fd = *control_fd;
|
||||||
if (attach_incoming_board(board, *interrupt_fd, *address, &attach_error)) {
|
if (pending.first_channel_at == 0) {
|
||||||
transport_connected = true;
|
pending.first_channel_at = monotonic_ms();
|
||||||
} else {
|
emit_status("link-detected", *address,
|
||||||
emit_status("connection-failed", *address,
|
"Front button reached the Bluetooth control channel.");
|
||||||
"Front button reached the server, but the HID control channel failed: " +
|
|
||||||
attach_error);
|
|
||||||
}
|
}
|
||||||
} else if (!incoming_error.empty()) {
|
} else if (!control_error.empty()) {
|
||||||
emit_status("connection-failed", *address,
|
emit_status("connection-failed", *address,
|
||||||
"Balance Board listener failed: " + incoming_error);
|
"Balance Board control listener failed: " + control_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transport_connected && monotonic_ms() < outbound_connect_until) {
|
std::string interrupt_error;
|
||||||
|
if (auto interrupt_fd = accept_board_channel(
|
||||||
|
interrupt_listener, *address, &interrupt_error)) {
|
||||||
|
if (pending.interrupt_fd >= 0) close(pending.interrupt_fd);
|
||||||
|
pending.interrupt_fd = *interrupt_fd;
|
||||||
|
if (pending.first_channel_at == 0) {
|
||||||
|
pending.first_channel_at = monotonic_ms();
|
||||||
|
emit_status("link-detected", *address,
|
||||||
|
"Front button reached the Bluetooth interrupt channel.");
|
||||||
|
}
|
||||||
|
} else if (!interrupt_error.empty()) {
|
||||||
|
emit_status("connection-failed", *address,
|
||||||
|
"Balance Board interrupt listener failed: " + interrupt_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending.control_fd >= 0 && pending.interrupt_fd >= 0) {
|
||||||
|
attach_incoming_board(
|
||||||
|
board, pending.control_fd, pending.interrupt_fd, *address);
|
||||||
|
pending.control_fd = -1;
|
||||||
|
pending.interrupt_fd = -1;
|
||||||
|
pending.first_channel_at = 0;
|
||||||
|
transport_connected = true;
|
||||||
|
} else if (pending.first_channel_at != 0 &&
|
||||||
|
monotonic_ms() - pending.first_channel_at >=
|
||||||
|
kIncomingChannelPairTimeoutMs) {
|
||||||
|
const bool control_arrived = pending.control_fd >= 0;
|
||||||
|
close_pending_channels(&pending);
|
||||||
|
emit_status(
|
||||||
|
"connection-failed", *address,
|
||||||
|
control_arrived
|
||||||
|
? "Front button reached the control channel, but the interrupt channel did not arrive."
|
||||||
|
: "Front button reached the interrupt channel, but the control channel did not arrive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transport_connected && pending.first_channel_at == 0 &&
|
||||||
|
monotonic_ms() < outbound_connect_until) {
|
||||||
// Red Sync makes the board discoverable instead of reconnecting to the
|
// Red Sync makes the board discoverable instead of reconnecting to the
|
||||||
// remembered host. During the short post-commissioning window only,
|
// remembered host. During the short post-commissioning window only,
|
||||||
// retain wiiuse's normal outbound connector so the first session starts
|
// retain wiiuse's normal outbound connector so the first session starts
|
||||||
@@ -1007,13 +1051,15 @@ void direct_connection_loop(PairingSharedState* shared, wiimote_t** boards) {
|
|||||||
prepare_wiiuse_address(board, *address);
|
prepare_wiiuse_address(board, *address);
|
||||||
if (intentional_sleep) {
|
if (intentional_sleep) {
|
||||||
// Closing both HID channels makes the board abandon the host connection
|
// Closing both HID channels makes the board abandon the host connection
|
||||||
// and power itself down. The interrupt listener stays open without paging
|
// and power itself down. Both HID listeners stay open without paging it,
|
||||||
// it, so only a later front-button connection starts another session.
|
// so only a later front-button connection starts another session.
|
||||||
} else {
|
} else {
|
||||||
emit_status("waiting", *address, "Board disconnected. Press the front power button.");
|
emit_status("waiting", *address, "Board disconnected. Press the front power button.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close_pending_channels(&pending);
|
||||||
|
close(control_listener);
|
||||||
close(interrupt_listener);
|
close(interrupt_listener);
|
||||||
close_wiiuse_sockets(board);
|
close_wiiuse_sockets(board);
|
||||||
wiiuse_disconnected(board);
|
wiiuse_disconnected(board);
|
||||||
|
|||||||
Reference in New Issue
Block a user