mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
slop
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -78,7 +78,7 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DrGDUWQo.js"></script>
|
<script type="module" crossorigin src="/assets/index-0ANoPaoC.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D6ALZM8p.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D6ALZM8p.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ constexpr uint8_t kBluetoothClassicAddressType = 0;
|
|||||||
constexpr int kFrameIntervalMs = 50;
|
constexpr int kFrameIntervalMs = 50;
|
||||||
constexpr int kDeviceScanIntervalMs = 500;
|
constexpr int kDeviceScanIntervalMs = 500;
|
||||||
constexpr int kDiscoveryRestartDelayMs = 1000;
|
constexpr int kDiscoveryRestartDelayMs = 1000;
|
||||||
|
constexpr const char* kDiscoveryTimeoutSeconds = "86400";
|
||||||
constexpr int kBatteryRefreshMs = 5000;
|
constexpr int kBatteryRefreshMs = 5000;
|
||||||
|
|
||||||
std::atomic<bool> running{true};
|
std::atomic<bool> running{true};
|
||||||
@@ -334,13 +335,21 @@ void stop_command(RunningCommand* command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BluetoothAddress> take_discovered_board(RunningCommand* discovery) {
|
std::optional<BluetoothAddress> take_discovered_board(RunningCommand* discovery,
|
||||||
|
bool* discovery_started) {
|
||||||
if (!discovery) return std::nullopt;
|
if (!discovery) return std::nullopt;
|
||||||
std::size_t newline = discovery->pending_output.find('\n');
|
std::size_t newline = discovery->pending_output.find('\n');
|
||||||
while (newline != std::string::npos) {
|
while (newline != std::string::npos) {
|
||||||
const std::string line = discovery->pending_output.substr(0, newline);
|
const std::string line = discovery->pending_output.substr(0, newline);
|
||||||
discovery->pending_output.erase(0, newline + 1);
|
discovery->pending_output.erase(0, newline + 1);
|
||||||
|
|
||||||
|
// bluetoothctl reports filter setup before StartDiscovery completes. Treat
|
||||||
|
// only this explicit event as proof that button presses can now be seen;
|
||||||
|
// `SetDiscoveryFilter success` alone is not an active Bluetooth scan.
|
||||||
|
if (discovery_started && line.find("Discovery started") != std::string::npos) {
|
||||||
|
*discovery_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
// A Classic device is initially announced by address and receives its name
|
// A Classic device is initially announced by address and receives its name
|
||||||
// in a later change event. Parse every complete scan line so either BlueZ
|
// in a later change event. Parse every complete scan line so either BlueZ
|
||||||
// form works, but require the exact Nintendo board name before accepting an
|
// form works, but require the exact Nintendo board name before accepting an
|
||||||
@@ -427,7 +436,12 @@ void commissioning_loop(PairingSharedState* shared) {
|
|||||||
// own event stream. The previous bounded scan exited for twelve seconds at a
|
// own event stream. The previous bounded scan exited for twelve seconds at a
|
||||||
// time and then queried a second client, making successful discovery depend
|
// time and then queried a second client, making successful discovery depend
|
||||||
// on when the physical button happened to be pressed.
|
// on when the physical button happened to be pressed.
|
||||||
RunningCommand discovery = start_command({"bluetoothctl", "scan", "bredr"});
|
// BlueZ's command-line client exits after the SetDiscoveryFilter callback
|
||||||
|
// unless non-interactive mode has a timeout. A one-day timeout keeps the
|
||||||
|
// client alive for unattended commissioning; the worker normally stops it
|
||||||
|
// itself as soon as the board appears and restarts it if the day expires.
|
||||||
|
RunningCommand discovery = start_command({
|
||||||
|
"bluetoothctl", "--timeout", kDiscoveryTimeoutSeconds, "scan", "bredr"});
|
||||||
if (discovery.pid < 0) {
|
if (discovery.pid < 0) {
|
||||||
emit_status("error", "", "could not start Bluetooth discovery: " +
|
emit_status("error", "", "could not start Bluetooth discovery: " +
|
||||||
command_error_summary(discovery.transcript, "unknown process error"));
|
command_error_summary(discovery.transcript, "unknown process error"));
|
||||||
@@ -436,13 +450,23 @@ void commissioning_loop(PairingSharedState* shared) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BluetoothAddress> address;
|
std::optional<BluetoothAddress> address;
|
||||||
|
bool discovery_started = false;
|
||||||
while (running.load() && !address.has_value()) {
|
while (running.load() && !address.has_value()) {
|
||||||
const bool discovery_running = collect_command_output(&discovery);
|
const bool discovery_running = collect_command_output(&discovery);
|
||||||
address = take_discovered_board(&discovery);
|
const bool was_started = discovery_started;
|
||||||
|
address = take_discovered_board(&discovery, &discovery_started);
|
||||||
|
if (!was_started && discovery_started) {
|
||||||
|
// This status clears any prior scanner error and tells the browser that
|
||||||
|
// the server is genuinely listening for the board's red Sync button.
|
||||||
|
emit_status("discovering");
|
||||||
|
}
|
||||||
if (address.has_value()) break;
|
if (address.has_value()) break;
|
||||||
if (!discovery_running) {
|
if (!discovery_running) {
|
||||||
emit_status("error", "", "Bluetooth discovery stopped: " +
|
const std::string detail = command_error_summary(
|
||||||
command_error_summary(discovery.transcript, "bluetoothctl exited unexpectedly"));
|
discovery.transcript, "bluetoothctl exited unexpectedly");
|
||||||
|
emit_status("error", "", discovery_started
|
||||||
|
? "Bluetooth scanner stopped unexpectedly; retrying automatically: " + detail
|
||||||
|
: "Bluetooth scanner exited before discovery started; retrying automatically: " + detail);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,29 @@ function formatWeight(value) {
|
|||||||
|
|
||||||
function describePhase(status, frame) {
|
function describePhase(status, frame) {
|
||||||
const phase = frame?.phase || status?.phase || 'waiting';
|
const phase = frame?.phase || status?.phase || 'waiting';
|
||||||
if (phase === 'commissioning') {
|
if (phase === 'error' || status?.lastError) {
|
||||||
|
const message = status?.lastError || 'The Balance Board bridge reported an error.';
|
||||||
|
// Name the failed subsystem in the badge. Generic labels such as “Needs
|
||||||
|
// attention” force an operator to read implementation details to understand
|
||||||
|
// whether the problem is scanning, pairing, or the sensor bridge itself.
|
||||||
|
const label = /scanner|discovery/i.test(message)
|
||||||
|
? 'Bluetooth scanner stopped'
|
||||||
|
: (/pair/i.test(message) ? 'Board pairing failed' : 'Balance Board error');
|
||||||
return {
|
return {
|
||||||
label: 'Pairing setup',
|
label,
|
||||||
// Discovery retries automatically, so retain and show the last concrete
|
instruction: message,
|
||||||
// BlueZ failure while the worker keeps listening for another Sync press.
|
className: 'border-red-500/60 bg-red-950/60 text-red-200',
|
||||||
instruction: status?.lastError || 'Press the red Sync button underneath the board. The server will pair it automatically.',
|
};
|
||||||
|
}
|
||||||
|
if (phase === 'commissioning') {
|
||||||
|
const scannerReady = status?.hardwareState === 'discovering';
|
||||||
|
return {
|
||||||
|
label: scannerReady ? 'Waiting for red Sync' : 'Starting Bluetooth scan',
|
||||||
|
// Pairing is automatic once discovery is active. State that directly so
|
||||||
|
// the UI cannot imply that a second software pairing action is required.
|
||||||
|
instruction: status?.lastError || (scannerReady
|
||||||
|
? 'Bluetooth is listening. Press the red Sync button underneath the board once.'
|
||||||
|
: 'The server is starting Bluetooth discovery. Wait for “Waiting for red Sync” before pressing the board button.'),
|
||||||
className: 'border-amber-500/60 bg-amber-950/60 text-amber-200',
|
className: 'border-amber-500/60 bg-amber-950/60 text-amber-200',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -47,13 +64,6 @@ function describePhase(status, frame) {
|
|||||||
className: 'border-sky-500/60 bg-sky-950/60 text-sky-200',
|
className: 'border-sky-500/60 bg-sky-950/60 text-sky-200',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (phase === 'error' || status?.lastError) {
|
|
||||||
return {
|
|
||||||
label: 'Needs attention',
|
|
||||||
instruction: status?.lastError || 'The Balance Board bridge reported an error.',
|
|
||||||
className: 'border-red-500/60 bg-red-950/60 text-red-200',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!status?.connected) {
|
if (!status?.connected) {
|
||||||
return {
|
return {
|
||||||
label: 'Sleeping',
|
label: 'Sleeping',
|
||||||
@@ -236,11 +246,9 @@ function BalanceBoardPanelContent() {
|
|||||||
<div className="border-t border-slate-700 pt-1">
|
<div className="border-t border-slate-700 pt-1">
|
||||||
<div className="mb-0.5 text-[0.62rem] text-slate-500">Admin maintenance</div>
|
<div className="mb-0.5 text-[0.62rem] text-slate-500">Admin maintenance</div>
|
||||||
<div className="flex flex-wrap gap-0.5">
|
<div className="flex flex-wrap gap-0.5">
|
||||||
{!status?.paired ? (
|
{/* An unpaired server scans automatically. A separate “Pair
|
||||||
<button type="button" className="button-dark px-1 py-0.5 text-xs" disabled={Boolean(actionPending)} onClick={() => runAction('pair')}>
|
board” action was redundant and incorrectly suggested that
|
||||||
Pair board
|
commissioning required two software/physical pair steps. */}
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<button type="button" className="button-dark px-1 py-0.5 text-xs" disabled={Boolean(actionPending) || !status?.connected} onClick={() => runAction('tare')}>
|
<button type="button" className="button-dark px-1 py-0.5 text-xs" disabled={Boolean(actionPending) || !status?.connected} onClick={() => runAction('tare')}>
|
||||||
Tare
|
Tare
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user