mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
clean up whep delivery
This commit is contained in:
@@ -56,7 +56,7 @@ cd ~/MultiRoombaRover
|
|||||||
sudo ./pi/install_roverd.sh --mediamtx
|
sudo ./pi/install_roverd.sh --mediamtx
|
||||||
```
|
```
|
||||||
|
|
||||||
Then point each rover's `/etc/roverd.yaml` at `ws://<server>:8080/rover`, set (or leave blank to auto-derive) `media.whepUrl` for the Pi’s mediaMTX instance (defaults to `http://<pi-ip>:8889/whep/rovercam`), enable the sensor stream from the UI, and drive with WASD.
|
Then point each rover's `/etc/roverd.yaml` at `ws://<server>:8080/rover`, enable the sensor stream from the UI, and drive with WASD. `roverd` automatically advertises its local mediaMTX WHEP endpoint (derived from the Pi’s DHCP address), so no video URL configuration is required unless you want to override the defaults.
|
||||||
Use the “Restart Camera” button if you enable media management so roverd can bounce the mediamtx service remotely.
|
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.
|
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
-1
@@ -14,7 +14,6 @@ battery:
|
|||||||
urgent: 1650
|
urgent: 1650
|
||||||
maxWheelSpeed: 350
|
maxWheelSpeed: 350
|
||||||
media:
|
media:
|
||||||
whepUrl: http://dummy1.local:8889/whep/rovercam
|
|
||||||
manage: false
|
manage: false
|
||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
|
|||||||
Vendored
-1
@@ -14,7 +14,6 @@ battery:
|
|||||||
urgent: 1650
|
urgent: 1650
|
||||||
maxWheelSpeed: 350
|
maxWheelSpeed: 350
|
||||||
media:
|
media:
|
||||||
whepUrl: http://dummy2.local:8889/whep/rovercam
|
|
||||||
manage: false
|
manage: false
|
||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -5,7 +5,7 @@ Each rover runs mediaMTX locally to capture the Pi camera, and the control serve
|
|||||||
## Pi (publisher)
|
## Pi (publisher)
|
||||||
|
|
||||||
- mediaMTX samples the Pi camera (`paths.rovercam.source: rpiCamera`) and exposes the HTTP API on `http://127.0.0.1:9997`.
|
- mediaMTX samples the Pi camera (`paths.rovercam.source: rpiCamera`) and exposes the HTTP API on `http://127.0.0.1:9997`.
|
||||||
- `/etc/roverd.yaml` contains `media.whepUrl`, pointing at the Pi’s own WHEP endpoint (e.g. `http://roomba-alpha.local:8889/whep/rovercam`). This is what the central server will pull.
|
- `roverd` automatically detects the Pi’s own WHEP endpoint (e.g. `http://roomba-alpha.local:8889/whep/rovercam`) and reports a `bridgeWhepUrl` (converted to `whep://.../whep`) to the server. No manual per-rover video URL configuration is required.
|
||||||
- The `media.manage` flag keeps the local service alive via `systemctl` and hits the API for health checks (`media.healthUrl`, defaults to `http://127.0.0.1:9997/v3/paths/list`).
|
- The `media.manage` flag keeps the local service alive via `systemctl` and hits the API for health checks (`media.healthUrl`, defaults to `http://127.0.0.1:9997/v3/paths/list`).
|
||||||
|
|
||||||
## Control server (viewer)
|
## Control server (viewer)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -59,6 +60,7 @@ type MediaConfig struct {
|
|||||||
WhepPort int `yaml:"whepPort" json:"-"`
|
WhepPort int `yaml:"whepPort" json:"-"`
|
||||||
WhepPath string `yaml:"whepPath" json:"-"`
|
WhepPath string `yaml:"whepPath" json:"-"`
|
||||||
LegacyPublish string `yaml:"publishUrl,omitempty" json:"-"`
|
LegacyPublish string `yaml:"publishUrl,omitempty" json:"-"`
|
||||||
|
BridgeWhepURL string `yaml:"-" json:"bridgeWhepUrl,omitempty"`
|
||||||
Manage bool `yaml:"manage"`
|
Manage bool `yaml:"manage"`
|
||||||
Service string `yaml:"service"`
|
Service string `yaml:"service"`
|
||||||
HealthURL string `yaml:"healthUrl"`
|
HealthURL string `yaml:"healthUrl"`
|
||||||
@@ -141,9 +143,40 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
scheme := "http"
|
scheme := "http"
|
||||||
cfg.Media.WhepURL = fmt.Sprintf("%s://%s:%d%s", scheme, ip, effectivePort(cfg.Media.WhepPort), path)
|
cfg.Media.WhepURL = fmt.Sprintf("%s://%s:%d%s", scheme, ip, effectivePort(cfg.Media.WhepPort), path)
|
||||||
}
|
}
|
||||||
|
if bridge, err := buildBridgeURL(cfg.Media.WhepURL); err == nil {
|
||||||
|
cfg.Media.BridgeWhepURL = bridge
|
||||||
|
}
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildBridgeURL(src string) (string, error) {
|
||||||
|
if src == "" {
|
||||||
|
return "", errors.New("empty whep url")
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if parsed.Host == "" {
|
||||||
|
return "", errors.New("missing host")
|
||||||
|
}
|
||||||
|
proto := "whep"
|
||||||
|
if parsed.Scheme == "https" {
|
||||||
|
proto = "wheps"
|
||||||
|
}
|
||||||
|
path := parsed.Path
|
||||||
|
if path == "" || path == "/" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, "/whep") {
|
||||||
|
if !strings.HasSuffix(path, "/") {
|
||||||
|
path += "/"
|
||||||
|
}
|
||||||
|
path += "whep"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s://%s%s", proto, parsed.Host, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ensureLeadingSlash(path string) string {
|
func ensureLeadingSlash(path string) string {
|
||||||
if !strings.HasPrefix(path, "/") {
|
if !strings.HasPrefix(path, "/") {
|
||||||
return "/" + path
|
return "/" + path
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ battery:
|
|||||||
urgent: 1650
|
urgent: 1650
|
||||||
maxWheelSpeed: 350
|
maxWheelSpeed: 350
|
||||||
media:
|
media:
|
||||||
whepUrl: http://roomba-alpha.local:8889/whep/rovercam
|
|
||||||
manage: false
|
manage: false
|
||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ battery:
|
|||||||
urgent: 1650
|
urgent: 1650
|
||||||
maxWheelSpeed: 350
|
maxWheelSpeed: 350
|
||||||
media:
|
media:
|
||||||
whepUrl: http://roomba-alpha.local:8889/whep/rovercam
|
|
||||||
manage: false
|
manage: false
|
||||||
service: mediamtx.service
|
service: mediamtx.service
|
||||||
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
healthUrl: http://127.0.0.1:9997/v3/paths/list
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ async function syncRover(record) {
|
|||||||
await removePath(record?.id);
|
await removePath(record?.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const source = normalizeSource(record.meta.media.whepUrl);
|
const source = record.meta.media.bridgeWhepUrl;
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
logger.warn(
|
||||||
|
'rover %s missing bridgeWhepUrl; video bridge disabled for this rover',
|
||||||
|
record.id
|
||||||
|
);
|
||||||
await removePath(record.id);
|
await removePath(record.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -81,27 +85,6 @@ async function removePath(roverId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSource(raw) {
|
|
||||||
if (!raw) return null;
|
|
||||||
const trimmed = raw.trim();
|
|
||||||
if (!trimmed) return null;
|
|
||||||
try {
|
|
||||||
const parsed = new URL(/^[a-z]+:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`);
|
|
||||||
const protocol = parsed.protocol === 'https:' ? 'wheps' : 'whep';
|
|
||||||
let path = parsed.pathname || '/';
|
|
||||||
if (!path.endsWith('/whep')) {
|
|
||||||
if (!path.endsWith('/')) {
|
|
||||||
path += '/';
|
|
||||||
}
|
|
||||||
path += 'whep';
|
|
||||||
}
|
|
||||||
return `${protocol}://${parsed.host}${path}`;
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn('invalid WHEP URL: %s (%s)', raw, err.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function callApi(method, path, body) {
|
async function callApi(method, path, body) {
|
||||||
const url = `${apiBase}${path}`;
|
const url = `${apiBase}${path}`;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
|
|||||||
Reference in New Issue
Block a user