Add camera streaming guide with all access methods
Comprehensive documentation for accessing the DWARF camera streams:
- RTSP URLs and channels (ch0=tele, ch1=wide)
- The critical prerequisite: camera must be opened via WebSocket first
- ffmpeg commands for frame capture, timelapse, and video recording
- mpv and VLC usage with TCP transport
- Python/OpenCV integration example
- Troubleshooting common issues (black image, connection refused, VLC delay)
- Comparison of RTSP vs MJPEG modes across device models
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
112
dwarfctl/internal/daemon/client.go
Normal file
112
dwarfctl/internal/daemon/client.go
Normal file
@ -0,0 +1,112 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/antitbone/dwarfctl/internal/odometry"
|
||||
)
|
||||
|
||||
// Client is a thin HTTP client that talks to the daemon.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a daemon client for the given address (e.g. "127.0.0.1:7777").
|
||||
func NewClient(addr string) *Client {
|
||||
if addr == "" {
|
||||
addr = DefaultAddr
|
||||
}
|
||||
return &Client{
|
||||
baseURL: "http://" + addr,
|
||||
http: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
// IsAlive checks if the daemon is reachable.
|
||||
func (c *Client) IsAlive() bool {
|
||||
resp, err := c.http.Get(c.baseURL + "/health")
|
||||
return err == nil && resp.StatusCode == 200
|
||||
}
|
||||
|
||||
// Orientation returns the current cumulative orientation from the daemon.
|
||||
func (c *Client) Orientation() (odometry.Orientation, error) {
|
||||
resp, err := c.http.Get(c.baseURL + "/orientation")
|
||||
if err != nil {
|
||||
return odometry.Orientation{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var o odometry.Orientation
|
||||
return o, json.NewDecoder(resp.Body).Decode(&o)
|
||||
}
|
||||
|
||||
// Grab captures a frame. If path is empty, the daemon picks a temp path.
|
||||
// Returns the saved file path.
|
||||
func (c *Client) Grab(path string) (string, error) {
|
||||
url := c.baseURL + "/grab"
|
||||
if path != "" {
|
||||
url += "?path=" + path
|
||||
}
|
||||
resp, err := c.http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("daemon: %s", body)
|
||||
}
|
||||
var result map[string]string
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
return result["path"], nil
|
||||
}
|
||||
|
||||
// Reset zeros the odometry.
|
||||
func (c *Client) Reset() error {
|
||||
_, err := c.http.Post(c.baseURL+"/reset", "", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Slew sends a joystick vector.
|
||||
func (c *Client) Slew(angle, length float64) error {
|
||||
body, _ := json.Marshal(map[string]float64{"angle": angle, "length": length})
|
||||
resp, err := c.http.Post(c.baseURL+"/slew", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopMotors stops all motors.
|
||||
func (c *Client) StopMotors() error {
|
||||
_, err := c.http.Post(c.baseURL+"/stop", "", nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Camera opens or closes a camera.
|
||||
func (c *Client) Camera(action, cam string) error {
|
||||
url := fmt.Sprintf("%s/camera?action=%s&cam=%s", c.baseURL, action, cam)
|
||||
resp, err := c.http.Post(url, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFoV overrides the field of view.
|
||||
func (c *Client) SetFoV(h, v float64) error {
|
||||
body, _ := json.Marshal(map[string]float64{"h": h, "v": v})
|
||||
resp, err := c.http.Post(c.baseURL+"/fov", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user