This commit is contained in:
legop3
2025-11-14 04:47:24 -05:00
parent ce99309b5d
commit d47ff3f60a
5 changed files with 41 additions and 14 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ cd ~/MultiRoombaRover
sudo ./pi/install_roverd.sh --mediamtx
```
Then point each rover's `/etc/roverd.yaml` at `ws://<server>:8080/rover`, set `name` to the rovers ID, and make sure the Pis mediaMTX is reachable at `http://<pi-ip>:8889/whep/rovercam` (the default). Each rover advertises that WHEP URL in its `hello` frame, and the servers media bridge automatically creates a matching pull path so mediaMTX on 192.168.0.86 fans the stream out to drivers/spectators—browsers never talk to the Pi directly.
Then point each rover's `/etc/roverd.yaml` at `ws://<server>:8080/rover`, set `name` to the rovers ID, and make sure the Pis mediaMTX is reachable at `http://<pi-ip>:8889/rovercam/whep` (the default). Each rover advertises that WHEP URL in its `hello` frame, and the servers media bridge automatically creates a matching pull path so mediaMTX on 192.168.0.86 fans the stream out to drivers/spectators—browsers never talk to the Pi directly.
Use the “Restart Camera” button if you enable media management so roverd can bounce the mediamtx service remotely.
Heads-up: the BRC pulser now uses libgpiod; make sure the `roverd` service account is in the `gpio` group (or otherwise allowed to access `/dev/gpiochip*`) and set `brc.gpioChip` if your hardware exposes a different chip name.
Vendored
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -55,7 +55,7 @@ Flags:
| `--mediamtx` | download/install mediaMTX plus the provided config + unit |
| `--mediamtx-version X.Y.Z` | override the mediaMTX release tag (default `1.15.3`) |
If the script installs the sample config, it will remind you to edit `/etc/roverd.yaml` before manually restarting the service: set `name`, `serverUrl`, serial device, BRC pin, battery thresholds, and optionally override `media.whepUrl`. When left blank, roverd automatically uses the Pis primary IPv4 plus `:8889/whep/rovercam`.
If the script installs the sample config, it will remind you to edit `/etc/roverd.yaml` before manually restarting the service: set `name`, `serverUrl`, serial device, BRC pin, battery thresholds, and optionally override `media.whepUrl`. When left blank, roverd automatically uses the Pis primary IPv4 plus `:8889/rovercam/whep`.
## Manual installation
@@ -65,7 +65,7 @@ If the script installs the sample config, it will remind you to edit `/etc/rover
sudo install -o roverd -g roverd -m 0755 dist/roverd /usr/local/bin/roverd
sudo install -o roverd -g roverd -m 0640 pi/roverd/roverd.sample.yaml /etc/roverd.yaml
```
Adjust `/etc/roverd.yaml` for each rover: `name`, `serverUrl` (e.g. `ws://control-server:8080/rover`), serial port path, battery thresholds, GPIO pin for BRC, and (if needed) the media `whepUrl` override. Otherwise, roverd fills in `http://<pi-ip>:8889/whep/rovercam` based on the DHCP-assigned address.
Adjust `/etc/roverd.yaml` for each rover: `name`, `serverUrl` (e.g. `ws://control-server:8080/rover`), serial port path, battery thresholds, GPIO pin for BRC, and (if needed) the media `whepUrl` override. Otherwise, roverd fills in `http://<pi-ip>:8889/rovercam/whep` based on the DHCP-assigned address.
2. Install the systemd unit:
```bash
@@ -92,7 +92,7 @@ If you set `media.manage: true` in `/etc/roverd.yaml`, make sure the `roverd` se
sudo systemctl enable --now mediamtx.service
```
The sample config uses the Raspberry Pi camera module as the source, enables the local mediaMTX HTTP API so `roverd` can health-check the service, and serves WebRTC playback at `http://<pi-ip>:8889/whep/rovercam`. Keep that URL reachable from the control server—`roverd` advertises it automatically and the central media bridge pulls each rovers feed over WHEP.
The sample config uses the Raspberry Pi camera module as the source, enables the local mediaMTX HTTP API so `roverd` can health-check the service, and serves WebRTC playback at `http://<pi-ip>:8889/rovercam/whep`. Keep that URL reachable from the control server—`roverd` advertises it automatically and the central media bridge pulls each rovers feed over WHEP.
Expose the mediaMTX HTTP API locally (default `http://127.0.0.1:9997`) and set `media.healthUrl` so `roverd` can monitor the pipeline; `media.service` should match the systemd unit name (default `mediamtx.service`).
With that in place, the Pi only talks to the server over the trusted LAN while the server-side mediaMTX distributes video to every driver and spectator.
+2 -2
View File
@@ -94,7 +94,7 @@ func LoadConfig(path string) (*Config, error) {
},
Media: MediaConfig{
WhepPort: 8889,
WhepPath: "/whep/rovercam",
WhepPath: "/rovercam/whep",
HealthInterval: Duration{Duration: 30 * time.Second},
},
}
@@ -135,7 +135,7 @@ func LoadConfig(path string) (*Config, error) {
}
path := cfg.Media.WhepPath
if path == "" {
path = "/whep/rovercam"
path = "/rovercam/whep"
}
path = ensureLeadingSlash(path)
scheme := "http"
+35 -8
View File
@@ -93,23 +93,50 @@ function normalizeSource(raw) {
if (!raw) return null;
const trimmed = String(raw).trim();
if (!trimmed) return null;
if (/^wheps?:\/\//i.test(trimmed)) {
return trimmed;
}
try {
const parsed = new URL(trimmed);
const protocol = parsed.protocol === 'https:' ? 'wheps' : 'whep';
let pathname = parsed.pathname || '/';
if (!pathname.startsWith('/')) {
pathname = `/${pathname}`;
let protocol = parsed.protocol;
if (!protocol || protocol === 'http:') {
protocol = 'whep:';
} else if (protocol === 'https:') {
protocol = 'wheps:';
} else if (protocol !== 'whep:' && protocol !== 'wheps:') {
throw new Error('unsupported protocol');
}
return `${protocol}://${parsed.host}${pathname}${parsed.search || ''}`;
const host = parsed.host;
if (!host) {
throw new Error('missing host');
}
let pathname = parsed.pathname || '/';
pathname = normalizeWhepPath(pathname);
return `${protocol.slice(0, -1)}://${host}${pathname}${parsed.search || ''}`;
} catch (err) {
logger.warn('invalid WHEP URL provided by rover (%s): %s', raw, err.message);
return null;
}
}
function normalizeWhepPath(pathname) {
let result = pathname || '/';
if (!result.startsWith('/')) {
result = `/${result}`;
}
result = result.replace(/\/+/g, '/');
result = result.replace(/\/+$/, '');
if (result.startsWith('/whep/')) {
result = `/${result.slice(6)}`;
} else if (result === '/whep') {
result = '/rovercam';
}
if (!result || result === '/') {
result = '/rovercam';
}
if (!result.endsWith('/whep')) {
result = `${result}/whep`;
}
return result;
}
async function callApi(method, path, body) {
const url = `${apiBase}${path}`;
const res = await fetch(url, {