reachymini

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 19 Imported by: 0

README

reachy-mini-sdk-go

A Go SDK for piloting a Reachy Mini robot without writing any Python.

Pollen Robotics' official SDK is Python-only, but the robot runs a background daemon (a FastAPI service, pre-installed and auto-started) that exposes the entire control surface over plain HTTP REST + WebSocket JSON, plus a separate embedded WebRTC signalling server for the camera feed. This package is a from-scratch Go client for that daemon API, reverse-engineered from the daemon's live OpenAPI schema and, for the camera, the upstream GStreamer webrtcsink signalling protocol.

Install

go get github.com/nlm/reachy-mini-sdk-go

Requirements

  • Go 1.25+
  • ffmpeg on PATH (only needed for StreamCameraFrames — it's used to decode the camera's H.264/VP8 video, since there's no production-quality pure-Go decoder for either)
  • Network access to the robot's daemon: port 8000 for the REST/WebSocket API, and 8443 for the WebRTC signalling server used only for the camera feed.

Usage

import reachymini "github.com/nlm/reachy-mini-sdk-go"

client := reachymini.New("http://localhost:8000")

ctx := context.Background()
if err := client.EnsureMotorMode(ctx, reachymini.MotorModeEnabled); err != nil {
	log.Fatal(err)
}
if err := client.WakeUp(ctx); err != nil {
	log.Fatal(err)
}

See github.com/nlm/reachy-mini-sdk-go on pkg.go.dev for the full API, and github.com/nlm/reachy-mini-demos for runnable example programs built on this SDK.

SDK coverage

File Covers
state.go Robot state: full state, present head pose / body yaw / antenna positions, direction-of-arrival, plus StreamFullState for the live WebSocket feed
move.go Movement: Goto (interpolated), SetTarget/StreamSetTarget (direct, low-latency), WakeUp/GotoSleep (canned animations), Stop, recorded move datasets
motors.go Motor control mode (enabled / disabled / gravity_compensation), plus EnsureMotorMode
media.go Sound playback: play/stop/list/upload/delete, clear incoming audio
audio.go Speaker/mic volume, low-level audio mixer parameters, test sound
daemon.go Daemon status and restart
apps.go The Hugging Face Spaces app store: list/install/remove/update/start/stop apps, check for updates
camera.go Camera specs (resolutions, intrinsics/distortion)
camera_stream.go + webrtc_signalling.go StreamCameraFrames — live decoded video frames over WebRTC
audio_stream.go StreamMicrophoneAudio — live decoded PCM audio from the robot's microphone, over the same WebRTC feed
kinematics.go Kinematics info, URDF, STL mesh downloads
ws.go Shared WebSocket helper for the streaming state/target endpoints

All request/response types were derived from the daemon's live /openapi.json and cross-checked against a real robot and Pollen's own desktop simulator — see "Verified vs. inferred" below for the exceptions.

Known issues / caveats

  • gravity_compensation motor mode returns HTTP 500 on daemon v1.8.1 (confirmed against real hardware). Prefer disabled, which works reliably.
  • Motor control mode does not survive a daemon restart. On real hardware it always came back disabled; the desktop simulator has come back either way. Nothing enforces this — any program that moves the robot should call EnsureMotorMode(ctx, MotorModeEnabled) first rather than assuming it's already enabled. It's also been observed to reset to disabled on its own between sessions, with no restart involved, so "check before you move" is the safe default, not just "check after a restart."
  • Movement commands don't validate hardware state. Goto, SetTarget, WakeUp, etc. all return 200 OK and a UUID even when motor control is disabled — they're silently no-ops. There's no error to catch; the only sign is that nothing physically moves.
  • A daemon restart puts motors in compliant/free-spinning mode for its duration. Anything resting on the antennas/head (or gravity) can leave them far from where they started. Re-enabling control doesn't re-home position.
  • Pose's AnyPose union (XYZRPYPose vs Matrix4x4Pose) is distinguished in UnmarshalJSON by the presence of an "m" field, since the daemon's Pydantic model doesn't use an explicit discriminator tag. Verified against live data using XYZRPYPose; Matrix4x4Pose round-tripping hasn't been separately exercised.
Camera / WebRTC
  • Verified live end-to-end against Pollen's desktop simulator: signalling handshake, WebRTC/ICE negotiation, VP8 depacketization, IVF muxing, and ffmpeg decode all confirmed producing real, correctly-decoded frames.
  • The simulator negotiates VP8. Real hardware has been observed in the daemon's own source to use hardware H.264 on Raspberry Pi instead. Both codecs are handled in camera_stream.go (decoder choice is deferred until the negotiated codec is known), but only the VP8 path has actually been exercised against a live producer — H.264 is implemented from the same protocol understanding but unverified against real hardware.
  • TestFFmpegH264DecodePipeline and TestFFmpegVP8DecodePipeline (camera_stream_test.go) test the ffmpeg decode pipeline in isolation with synthetic streams, independent of the robot — run with go test ./....
  • The same WebRTC feed also carries an Opus audio track from the robot's microphone, alongside the video track (confirmed by cross-referencing the official Python SDK's ReachyMini(media_backend="webrtc") + mini.media.start_recording() path, which relies on the same daemon capability). StreamMicrophoneAudio (audio_stream.go) consumes it, muxing RTP packets into an Ogg/Opus container (pion/webrtc's oggwriter, no manual depacketization needed since one RTP packet is one Opus frame) and decoding via ffmpeg to raw PCM, the same subprocess pattern as the video path. This is implemented from the same protocol understanding as the rest of this file but has not yet been exercised against a live robot or simulator — treat it as unverified until tested against real hardware.

Verified vs. inferred

Most of the SDK has been exercised against either real Reachy Mini hardware or Pollen's desktop simulator (state reads, movement, sounds, motors, daemon restart, volume, apps listing, kinematics, camera). A few corners haven't been:

  • InstallApp / RemoveApp / UpdateApp / StartApp / StopCurrentApp / ResetAppsCache — not run live, since they mutate persistent app installation state
  • GetKinematicsSTL — needs a real filename from GetURDF's content, not just guessed
  • ReadAudioParameter / ApplyAudioConfig — parameter names are undocumented and backend-specific
  • H.264 camera decoding (see above)
  • StreamMicrophoneAudio (see "Camera / WebRTC" above)

These are implemented against the daemon's documented schema and follow the same patterns as the verified parts of the SDK, but treat them as a starting point to debug against real traffic rather than a guaranteed-working path.

Documentation

Overview

Package reachymini is a Go client for the Reachy Mini daemon's REST/WebSocket API (the same interface used by Pollen Robotics' own TypeScript SDK and desktop app), so the robot can be piloted without writing any Python.

Index

Examples

Constants

View Source
const AudioChannels = 1

AudioChannels is the channel count of the PCM chunks StreamMicrophoneAudio delivers. The decoder always downmixes to mono so callers don't need to discover the negotiated channel count asynchronously from inside OnTrack.

View Source
const AudioSampleRate = 48000

AudioSampleRate is the sample rate (Hz) of the PCM chunks StreamMicrophoneAudio delivers. Opus's RTP clock rate is fixed at 48000 by RFC 7587 regardless of the codec's actual internal audio bandwidth, so this is a constant rather than something negotiated per-track.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status the daemon returned (e.g. 404, 500).
	StatusCode int
	// Body is the raw response body, usually a FastAPI JSON error detail.
	Body string
}

APIError is returned when the daemon responds with a non-2xx status.

func (*APIError) Error

func (e *APIError) Error() string

type AppInfo

type AppInfo struct {
	// Name is the app's unique identifier, used e.g. by StartApp/RemoveApp.
	Name string `json:"name"`
	// SourceKind is where this app comes from (see SourceKind).
	SourceKind SourceKind `json:"source_kind"`
	// Description is a human-readable summary, if the source provides one.
	Description string `json:"description,omitempty"`
	// URL points at the app's source (e.g. its Hugging Face Space page),
	// if applicable to SourceKind.
	URL *string `json:"url,omitempty"`
	// Extra carries source-specific metadata not otherwise modeled here.
	Extra map[string]any `json:"extra,omitempty"`
}

AppInfo describes an app available from the robot's app store or already installed.

type AppState

type AppState string

AppState is the runtime status of an app.

const (
	AppStateStarting AppState = "starting"
	AppStateRunning  AppState = "running"
	AppStateDone     AppState = "done"
	AppStateStopping AppState = "stopping"
	AppStateError    AppState = "error"
)

type AppStatus

type AppStatus struct {
	// Info identifies which app this status is for.
	Info AppInfo `json:"info"`
	// State is the app's current runtime status (see AppState).
	State AppState `json:"state"`
	// Error is set when State is AppStateError, describing what went wrong.
	Error *string `json:"error,omitempty"`
}

AppStatus is the status of a running (or just-started/stopped) app.

type AppUpdateStatus

type AppUpdateStatus struct {
	// AppName is the installed app's name, matching AppInfo.Name.
	AppName string `json:"app_name"`
	// SpaceID is the app's Hugging Face Space identifier.
	SpaceID string `json:"space_id"`
	// InstalledSHA is the commit SHA currently installed.
	InstalledSHA string `json:"installed_sha"`
	// LatestSHA is the commit SHA currently published upstream.
	LatestSHA string `json:"latest_sha"`
	// UpdateAvailable reports whether InstalledSHA differs from LatestSHA.
	UpdateAvailable bool `json:"update_available"`
	// LastModified is when the upstream Space was last modified, if known.
	LastModified *string `json:"last_modified,omitempty"`
}

AppUpdateStatus is the result of checking one installed app for updates.

type AppUpdatesResponse

type AppUpdatesResponse struct {
	// AppsWithUpdates lists only the installed apps that have an update
	// available (not every app that was checked).
	AppsWithUpdates []AppUpdateStatus `json:"apps_with_updates"`
	// AppsChecked is how many installed apps were checked for updates.
	AppsChecked int `json:"apps_checked"`
	// AppsSkipped is how many installed apps couldn't be checked (e.g. not
	// backed by a Hugging Face Space).
	AppsSkipped int `json:"apps_skipped"`
}

AppUpdatesResponse is the response of GET /api/apps/check-updates.

type AudioParamPair

type AudioParamPair struct {
	// Name is the mixer parameter's name (backend-specific; see
	// ReadAudioParameter's doc comment).
	Name string `json:"name"`
	// Values are the parameter's new value(s) -- some mixer parameters are
	// scalar (one value), others are per-channel (one value each).
	Values []float64 `json:"values"`
}

AudioParamPair is one (parameter name, values) pair in an audio config payload, as used by ApplyAudioConfig.

type AudioStreamOptions added in v1.1.0

type AudioStreamOptions struct {
	// SignallingURL is the WebRTC signalling server's WebSocket URL, e.g.
	// "ws://localhost:8443". Same signalling server as CameraStreamOptions
	// (see camera_stream.go) -- defaults to Client's BaseURL host on port
	// 8443 if empty. Opening this alongside a concurrent StreamCameraFrames
	// call is fine: the daemon's webrtcsink producer supports multiple
	// simultaneous consumer sessions.
	SignallingURL string

	// ProducerName optionally selects a specific producer by its
	// advertised meta name, if the signalling server lists more than one.
	// If empty, the first available producer is used.
	ProducerName string

	// FFmpegPath overrides the ffmpeg binary used to decode Opus to raw
	// PCM. Defaults to "ffmpeg" (resolved via PATH).
	FFmpegPath string
}

AudioStreamOptions configures StreamMicrophoneAudio.

type BackendStatus

type BackendStatus struct {
	// Ready is the backend's self-reported readiness (unreliable -- see
	// the type-level comment above).
	Ready bool `json:"ready"`
	// MotorControlMode is the current motor control mode (see
	// MotorControlMode), mirroring GetMotorStatus.
	MotorControlMode MotorControlMode `json:"motor_control_mode"`
	// LastAlive is a backend-specific liveness timestamp/marker with no
	// fixed schema in the daemon's OpenAPI spec.
	LastAlive any `json:"last_alive"`
	// ControlLoopStats is nil if the backend hasn't reported stats yet.
	ControlLoopStats *ControlLoopStats `json:"control_loop_stats"`
	// Error is a backend-specific error payload with no fixed schema,
	// non-nil if the backend is in an error state.
	Error any `json:"error"`
}

BackendStatus reports the motor/hardware backend's health. Ready has been observed false even during normal operation, so don't gate on it; State on the enclosing DaemonStatus is the more reliable liveness signal.

type CameraSpecs

type CameraSpecs struct {
	// Name identifies the camera module/model the daemon detected.
	Name string `json:"name"`
	// AvailableResolutions lists every resolution/FPS mode this camera
	// supports.
	AvailableResolutions []ResolutionInfo `json:"available_resolutions"`
	// DefaultResolution is the mode StreamCameraFrames requests frames at.
	DefaultResolution ResolutionInfo `json:"default_resolution"`
	// K is the 3x3 intrinsic camera matrix (focal lengths and principal
	// point), row-major, for DefaultResolution's dimensions.
	K [][]float64 `json:"K"`
	// D is the lens distortion coefficients, in OpenCV's order
	// (k1, k2, p1, p2, k3, ...).
	D []float64 `json:"D"`
}

CameraSpecs is the response of GET /api/camera/specs: full camera specifications as detected by the daemon, including the intrinsic matrix K and distortion coefficients D (standard OpenCV pinhole camera model).

type CameraStreamOptions

type CameraStreamOptions struct {
	// SignallingURL is the WebRTC signalling server's WebSocket URL, e.g.
	// "ws://localhost:8443". This is a *separate* connection from the
	// daemon's REST API -- see webrtc_signalling.go. Defaults to Client's
	// BaseURL host on port 8443 if empty.
	SignallingURL string

	// ProducerName optionally selects a specific producer by its
	// advertised meta name, if the signalling server lists more than one.
	// If empty, the first available producer is used.
	ProducerName string

	// FFmpegPath overrides the ffmpeg binary used to decode video to raw
	// RGB frames. Defaults to "ffmpeg" (resolved via PATH). This is a
	// runtime dependency of the calling machine, not a Go dependency --
	// there is no production-quality pure-Go decoder for either codec the
	// daemon may negotiate (see below).
	FFmpegPath string
}

CameraStreamOptions configures StreamCameraFrames.

type Client

type Client struct {
	// BaseURL is the daemon's REST API root, with no trailing slash (e.g.
	// "http://localhost:8000"). Also used to derive the WebSocket URLs for
	// StreamFullState/StreamSetTarget, and, unless overridden, the WebRTC
	// signalling server URL for StreamCameraFrames.
	BaseURL string
	// HTTPClient issues all REST requests. Defaults to a client with a 10s
	// timeout (see New); replace it to change timeouts, add TLS config, etc.
	HTTPClient *http.Client
}

Client talks to a Reachy Mini daemon reachable at BaseURL (e.g. "http://localhost:8000" or "http://<robot-host>:8000").

func New

func New(baseURL string) *Client

New returns a Client pointed at baseURL, e.g. "http://localhost:8000".

func (*Client) ApplyAudioConfig

func (c *Client) ApplyAudioConfig(ctx context.Context, config []AudioParamPair, verify bool) (bool, error)

ApplyAudioConfig applies low-level audio mixer parameters. If verify is true (the daemon's own default), it reads each parameter back after writing to confirm it took effect.

func (*Client) CheckAppUpdates

func (c *Client) CheckAppUpdates(ctx context.Context, force bool) (AppUpdatesResponse, error)

CheckAppUpdates checks installed apps for available updates. Set force to bypass any cached result.

func (*Client) ClearIncomingAudio

func (c *Client) ClearIncomingAudio(ctx context.Context) (map[string]string, error)

ClearIncomingAudio discards any buffered incoming audio (e.g. from the microphone/direction-of-arrival pipeline).

func (*Client) DeleteSound

func (c *Client) DeleteSound(ctx context.Context, filename string) error

DeleteSound removes a sound file from the robot.

func (*Client) EnsureMotorMode

func (c *Client) EnsureMotorMode(ctx context.Context, mode MotorControlMode) (changed bool, err error)

EnsureMotorMode sets the robot's motor control mode to mode if it isn't already there, and reports whether a change was made. Movement commands (Goto, SetTarget, WakeUp, GotoSleep, ...) are accepted by the daemon and silently no-op if the robot isn't actually in MotorModeEnabled, so this is meant to be called at the top of anything that's about to move the robot.

func (*Client) GetCameraSpecs

func (c *Client) GetCameraSpecs(ctx context.Context) (CameraSpecs, error)

GetCameraSpecs returns the robot's camera specifications.

func (*Client) GetCurrentAppStatus

func (c *Client) GetCurrentAppStatus(ctx context.Context) (*AppStatus, error)

GetCurrentAppStatus returns the currently running app's status, or nil if no app is running.

func (*Client) GetDaemonStatus

func (c *Client) GetDaemonStatus(ctx context.Context) (DaemonStatus, error)

GetDaemonStatus returns the daemon's current status, including hardware backend health and motor control mode.

func (*Client) GetDoA

func (c *Client) GetDoA(ctx context.Context) (DoAInfo, error)

GetDoA returns the current direction-of-arrival (sound source) estimate.

func (*Client) GetFullState

func (c *Client) GetFullState(ctx context.Context) (FullState, error)

GetFullState returns the robot's complete current state.

func (*Client) GetJobStatus

func (c *Client) GetJobStatus(ctx context.Context, jobID string) (JobInfo, error)

GetJobStatus returns the status of an install/update job started by InstallApp, InstallPrivateSpace, or UpdateApp.

func (*Client) GetKinematicsInfo

func (c *Client) GetKinematicsInfo(ctx context.Context) (map[string]any, error)

GetKinematicsInfo returns the daemon's kinematics configuration as a free-form map -- the daemon's OpenAPI spec declares this endpoint's response as an open object with no fixed schema.

func (*Client) GetKinematicsSTL

func (c *Client) GetKinematicsSTL(ctx context.Context, filename string) ([]byte, error)

GetKinematicsSTL downloads the raw STL mesh file named filename (as referenced by GetURDF/GetKinematicsInfo) and returns its bytes.

func (*Client) GetMicrophoneVolume

func (c *Client) GetMicrophoneVolume(ctx context.Context) (VolumeInfo, error)

GetMicrophoneVolume returns the current microphone volume.

func (*Client) GetMotorStatus

func (c *Client) GetMotorStatus(ctx context.Context) (MotorStatus, error)

GetMotorStatus returns the robot's current motor control mode.

func (*Client) GetPresentAntennaPositions

func (c *Client) GetPresentAntennaPositions(ctx context.Context) ([2]float64, error)

GetPresentAntennaPositions returns the [left, right] antenna joint positions.

func (*Client) GetPresentBodyYaw

func (c *Client) GetPresentBodyYaw(ctx context.Context) (float64, error)

GetPresentBodyYaw returns the body's current yaw.

func (*Client) GetPresentHeadPose

func (c *Client) GetPresentHeadPose(ctx context.Context) (Pose, error)

GetPresentHeadPose returns the head's current pose.

func (*Client) GetURDF

func (c *Client) GetURDF(ctx context.Context) (map[string]string, error)

GetURDF returns the robot's URDF description as a set of named file contents (e.g. a "urdf" key holding the XML).

func (*Client) GetVolume

func (c *Client) GetVolume(ctx context.Context) (VolumeInfo, error)

GetVolume returns the current speaker volume.

func (*Client) Goto

func (c *Client) Goto(ctx context.Context, req GotoRequest) (MoveUUID, error)

Goto smoothly interpolates to the given target pose(s) over req.Duration.

Example

ExampleClient_Goto smoothly moves the head to a target pose over one second, using min-jerk interpolation for a natural-looking motion.

package main

import (
	"context"
	"log"

	reachymini "github.com/nlm/reachy-mini-sdk-go"
)

func main() {
	client := reachymini.New("http://localhost:8000")
	ctx := context.Background()

	_, err := client.Goto(ctx, reachymini.GotoRequest{
		HeadPose:      reachymini.NewXYZRPYPose(0, 0, 0, 0, 0.2, 0),
		Duration:      1.0,
		Interpolation: reachymini.InterpolationMinJerk,
	})
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) GotoSleep

func (c *Client) GotoSleep(ctx context.Context) (MoveUUID, error)

GotoSleep plays the robot's built-in sleep move.

func (*Client) InstallApp

func (c *Client) InstallApp(ctx context.Context, app AppInfo) (map[string]string, error)

InstallApp installs app and returns the daemon's raw install job response (the daemon's OpenAPI spec declares no fixed schema for it beyond string-to-string). Poll GetJobStatus to track progress.

func (*Client) InstallPrivateSpace

func (c *Client) InstallPrivateSpace(ctx context.Context, spaceID string) (map[string]string, error)

InstallPrivateSpace installs a private Hugging Face Space by ID.

func (*Client) ListAvailableApps

func (c *Client) ListAvailableApps(ctx context.Context) ([]AppInfo, error)

ListAvailableApps lists all apps available to install, across all sources.

func (*Client) ListAvailableAppsBySource

func (c *Client) ListAvailableAppsBySource(ctx context.Context, source SourceKind) ([]AppInfo, error)

ListAvailableAppsBySource lists apps available from a single source.

func (*Client) ListRecordedMoveDatasets

func (c *Client) ListRecordedMoveDatasets(ctx context.Context, datasetName string) ([]string, error)

ListRecordedMoveDatasets lists the recorded moves available in datasetName.

func (*Client) ListRunningMoves

func (c *Client) ListRunningMoves(ctx context.Context) ([]MoveUUID, error)

ListRunningMoves returns the UUIDs of moves currently in progress.

func (*Client) ListSounds

func (c *Client) ListSounds(ctx context.Context) ([]string, error)

ListSounds lists the sound files available on the robot.

func (*Client) PlayRecordedMoveDataset

func (c *Client) PlayRecordedMoveDataset(ctx context.Context, datasetName, moveName string) (MoveUUID, error)

PlayRecordedMoveDataset plays moveName from datasetName.

func (*Client) PlaySound

func (c *Client) PlaySound(ctx context.Context, file string) error

PlaySound plays a sound file already stored on the robot (see ListSounds).

Example

ExampleClient_PlaySound plays a sound file already uploaded to the robot (see Client.UploadSound and Client.ListSounds).

package main

import (
	"context"
	"log"

	reachymini "github.com/nlm/reachy-mini-sdk-go"
)

func main() {
	client := reachymini.New("http://localhost:8000")
	ctx := context.Background()

	if err := client.PlaySound(ctx, "boot_chime.wav"); err != nil {
		log.Fatal(err)
	}
}

func (*Client) ReadAudioParameter

func (c *Client) ReadAudioParameter(ctx context.Context, name string) (ReadAudioParameterResponse, error)

ReadAudioParameter reads a single low-level audio mixer parameter by name. Valid names are specific to the daemon's audio backend and aren't enumerated anywhere in the API itself.

func (*Client) RemoveApp

func (c *Client) RemoveApp(ctx context.Context, appName string) (map[string]string, error)

RemoveApp uninstalls appName.

func (*Client) ResetAppsCache

func (c *Client) ResetAppsCache(ctx context.Context) (map[string]string, error)

ResetAppsCache clears the daemon's cached app store listing. Note this endpoint is mounted at /cache/reset-apps, not under /api like everything else -- that's how the daemon itself exposes it.

func (*Client) RestartCurrentApp

func (c *Client) RestartCurrentApp(ctx context.Context) (AppStatus, error)

RestartCurrentApp restarts whichever app is currently running.

func (*Client) RestartDaemon

func (c *Client) RestartDaemon(ctx context.Context) (string, error)

RestartDaemon restarts the daemon process, which re-initializes its connection to the motor/hardware backend. Useful when a joint has silently dropped off the control loop (no error reported, motor mode still "enabled", but it stops responding to position commands) -- cycling motor mode alone does not fix that, but a daemon restart does. Returns the daemon's internal job ID for the restart operation; the restart is asynchronous and briefly interrupts connectivity, so poll GetDaemonStatus afterward rather than assuming it's done when this returns.

func (*Client) SetMicrophoneVolume

func (c *Client) SetMicrophoneVolume(ctx context.Context, volume int) (VolumeInfo, error)

SetMicrophoneVolume sets the microphone volume (0-100).

func (*Client) SetMotorMode

func (c *Client) SetMotorMode(ctx context.Context, mode MotorControlMode) error

SetMotorMode changes the robot's motor control mode (e.g. enabling/disabling torque).

func (*Client) SetTarget

func (c *Client) SetTarget(ctx context.Context, target FullBodyTarget) error

SetTarget directly commands a target pose with no interpolation (lower latency than Goto, useful for teleop). For continuous streaming control, use StreamSetTarget instead.

func (*Client) SetVolume

func (c *Client) SetVolume(ctx context.Context, volume int) (VolumeInfo, error)

SetVolume sets the speaker volume (0-100).

func (*Client) StartApp

func (c *Client) StartApp(ctx context.Context, appName string) (AppStatus, error)

StartApp starts appName (stopping any currently running app first).

func (*Client) Stop

func (c *Client) Stop(ctx context.Context, uuid MoveUUID) error

Stop halts the move identified by uuid (as returned by Goto, WakeUp, GotoSleep, or PlayRecordedMoveDataset; see also ListRunningMoves).

func (*Client) StopCurrentApp

func (c *Client) StopCurrentApp(ctx context.Context) error

StopCurrentApp stops whichever app is currently running.

func (*Client) StopSound

func (c *Client) StopSound(ctx context.Context) error

StopSound stops whatever sound is currently playing.

func (*Client) StreamCameraFrames

func (c *Client) StreamCameraFrames(ctx context.Context, opts CameraStreamOptions) (<-chan Frame, <-chan error, error)

StreamCameraFrames connects to the daemon's WebRTC camera feed and decodes it to raw RGB frames, delivered on the returned channel until ctx is cancelled. Requires an `ffmpeg` binary on PATH (or set opts.FFmpegPath).

The negotiated video codec isn't known until after WebRTC negotiation completes, so the decoder is started lazily from the OnTrack callback. Verified live end-to-end against Pollen's desktop simulator (signalling, WebRTC negotiation, VP8 depacketization, IVF muxing, and ffmpeg decode all confirmed producing real, correctly-decoded frames). Real hardware has been observed (in the daemon's own source) to use hardware H.264 on Raspberry Pi instead -- both codecs are handled here, but only the VP8 path has actually been exercised against a live producer so far.

func (*Client) StreamFullState

func (c *Client) StreamFullState(ctx context.Context) (<-chan FullState, <-chan error, error)

StreamFullState streams FullState frames from WS /api/state/ws/full until ctx is cancelled. The returned channels are closed when the stream ends; drain errs (buffered, size 1) to see why.

Example

ExampleClient_StreamFullState prints the robot's head pose as it changes, until ctx is cancelled.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	reachymini "github.com/nlm/reachy-mini-sdk-go"
)

func main() {
	client := reachymini.New("http://localhost:8000")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	states, errs, err := client.StreamFullState(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for {
		select {
		case state, ok := <-states:
			if !ok {
				return
			}
			fmt.Println(state.HeadPose)
		case err := <-errs:
			log.Fatal(err)
		}
	}
}

func (*Client) StreamMicrophoneAudio added in v1.1.0

func (c *Client) StreamMicrophoneAudio(ctx context.Context, opts AudioStreamOptions) (<-chan []byte, <-chan error, error)

StreamMicrophoneAudio connects to the daemon's WebRTC feed and decodes the robot microphone's Opus audio track to raw signed 16-bit little-endian PCM, delivered on the returned channel until ctx is cancelled. Requires an `ffmpeg` binary on PATH (or set opts.FFmpegPath).

The daemon's webrtcsink producer carries both a video track (see StreamCameraFrames) and this audio track in the same negotiated session; this function only consumes the audio one. Unlike H.264/VP8 depacketizing, Opus needs no reassembly across RTP packets (one packet is one Opus frame), so the incoming RTP stream is simply muxed into an Ogg/Opus container (github.com/pion/webrtc/v4/pkg/media/oggwriter) and piped into ffmpeg for decoding.

This mirrors the official Python SDK's ReachyMini(media_backend="webrtc") + mini.media.start_recording()/get_audio_sample() path (see examples/sound_record.py in pollen-robotics/reachy_mini), which relies on the same underlying daemon capability. It has not yet been exercised against a live robot or simulator -- see the README's "Known issues".

func (*Client) StreamSetTarget

func (c *Client) StreamSetTarget(ctx context.Context) (chan<- FullBodyTarget, <-chan error, error)

StreamSetTarget opens WS /api/move/ws/set_target and forwards every FullBodyTarget sent on the returned channel to the robot, until ctx is cancelled or the channel is closed. Useful for continuous/teleop control (e.g. driving the head from a joystick loop) where the lower per-call latency of a persistent connection matters; for one-off commands use SetTarget instead.

func (*Client) TestSound

func (c *Client) TestSound(ctx context.Context) (TestSoundResult, error)

TestSound plays a short sound at the current volume -- useful for letting a user confirm audio is working after changing the volume.

func (*Client) UpdateApp

func (c *Client) UpdateApp(ctx context.Context, appName string) (map[string]string, error)

UpdateApp updates appName to its latest available version.

func (*Client) UploadSound

func (c *Client) UploadSound(ctx context.Context, localPath string) error

UploadSound uploads the local file at localPath to the robot's sound library.

func (*Client) WakeUp

func (c *Client) WakeUp(ctx context.Context) (MoveUUID, error)

WakeUp plays the robot's built-in wake-up move.

Example

ExampleClient_WakeUp shows the minimal sequence to move the robot at all: motor control must be explicitly enabled before any movement command has an effect (see EnsureMotorMode's doc comment).

package main

import (
	"context"
	"log"

	reachymini "github.com/nlm/reachy-mini-sdk-go"
)

func main() {
	client := reachymini.New("http://localhost:8000")
	ctx := context.Background()

	if _, err := client.EnsureMotorMode(ctx, reachymini.MotorModeEnabled); err != nil {
		log.Fatal(err)
	}
	if _, err := client.WakeUp(ctx); err != nil {
		log.Fatal(err)
	}
}

type ControlLoopStats

type ControlLoopStats struct {
	// MeanControlLoopFrequency is the average control loop rate, in Hz.
	MeanControlLoopFrequency float64 `json:"mean_control_loop_frequency"`
	// MaxControlLoopInterval is the longest gap observed between control
	// loop iterations, in seconds -- large values indicate the loop
	// stalled or was delayed.
	MaxControlLoopInterval float64 `json:"max_control_loop_interval"`
	// NbError is the cumulative count of control loop errors.
	NbError int `json:"nb_error"`
	// MotorController names the low-level motor driver/backend in use.
	MotorController string `json:"motor_controller"`
}

ControlLoopStats reports low-level motor control loop timing/health.

type DaemonStatus

type DaemonStatus struct {
	// Type identifies this status payload's shape/version.
	Type string `json:"type"`
	// RobotName is the robot's configured hostname/name.
	RobotName string `json:"robot_name"`
	// State is the daemon's own lifecycle state (e.g. "running") -- the
	// most reliable liveness signal, more so than BackendStatus.Ready.
	State string `json:"state"`
	// WirelessVersion reports whether this is the wireless hardware
	// variant of the robot.
	WirelessVersion bool `json:"wireless_version"`
	// DesktopAppDaemon reports whether this daemon is running embedded in
	// Pollen's desktop app rather than on real hardware.
	DesktopAppDaemon bool `json:"desktop_app_daemon"`
	// SimulationEnabled reports whether motor commands are being
	// simulated rather than sent to real hardware.
	SimulationEnabled bool `json:"simulation_enabled"`
	// MockupSimEnabled reports whether a lightweight mockup simulation
	// backend is in use (distinct from the full desktop simulator).
	MockupSimEnabled bool `json:"mockup_sim_enabled"`
	// NoMedia reports whether audio/video subsystems are disabled for
	// this daemon instance.
	NoMedia bool `json:"no_media"`
	// MediaReleased reports whether media devices have been released
	// (e.g. to let another process use the camera/mic).
	MediaReleased bool `json:"media_released"`
	// CameraSpecsName is the detected camera's name, matching
	// CameraSpecs.Name.
	CameraSpecsName string `json:"camera_specs_name"`
	// BackendStatus reports the motor/hardware backend's health.
	BackendStatus BackendStatus `json:"backend_status"`
	// Error is a daemon-level error payload with no fixed schema, non-nil
	// if the daemon itself is in an error state.
	Error any `json:"error"`
	// WlanIP is the robot's current WiFi IP address, if connected.
	WlanIP string `json:"wlan_ip"`
	// Version is the daemon software's version string.
	Version string `json:"version"`
	// HardwareID uniquely identifies this physical robot.
	HardwareID string `json:"hardware_id"`
}

DaemonStatus is the response of GET /api/daemon/status.

type DoAInfo

type DoAInfo struct {
	// Angle is the estimated direction to the sound source, in degrees.
	Angle float64 `json:"angle"`
	// SpeechDetected reports whether the sound was classified as speech.
	SpeechDetected bool `json:"speech_detected"`
}

DoAInfo is the robot's direction-of-arrival (sound source) estimate.

type Frame

type Frame struct {
	// Width, Height are the frame's dimensions in pixels.
	Width, Height int
	// RGB is the frame's pixel data: 8-bit RGB, row-major, Width*Height*3
	// bytes.
	RGB []byte
	// Timestamp is when this frame was decoded, client-side (not a
	// timestamp carried by the stream itself).
	Timestamp time.Time
}

Frame is one decoded video frame from StreamCameraFrames: 8-bit RGB, row-major, Width*Height*3 bytes.

type FullBodyTarget

type FullBodyTarget struct {
	// TargetHeadPose is the target head pose, if the head should move.
	TargetHeadPose *Pose `json:"target_head_pose,omitempty"`
	// TargetAntennas is the target [left, right] antenna joint positions,
	// if the antennas should move.
	TargetAntennas *[2]float64 `json:"target_antennas,omitempty"`
	// TargetBodyYaw is the target body yaw, if the body should rotate.
	TargetBodyYaw *float64 `json:"target_body_yaw,omitempty"`
	// Timestamp optionally tags when this target was computed
	// client-side, e.g. for latency diagnostics during teleop.
	Timestamp *time.Time `json:"timestamp,omitempty"`
}

FullBodyTarget is the body of POST /api/move/set_target and each frame of WS /api/move/ws/set_target. Unlike GotoRequest, there's no interpolation: whichever fields are set are commanded immediately as-is.

type FullState

type FullState struct {
	// ControlMode is the current motor control mode (see MotorControlMode).
	ControlMode *MotorControlMode `json:"control_mode,omitempty"`
	// HeadPose is the head's current pose, equivalent to GetPresentHeadPose.
	HeadPose *Pose `json:"head_pose,omitempty"`
	// HeadJoints are the head's raw actuator joint positions, in the
	// daemon/URDF's native joint order (not a semantic X/Y/Z/RPY breakdown
	// like HeadPose).
	HeadJoints []float64 `json:"head_joints,omitempty"`
	// BodyYaw is the body's current yaw, equivalent to GetPresentBodyYaw.
	BodyYaw *float64 `json:"body_yaw,omitempty"`
	// AntennasPosition is the current [left, right] antenna joint
	// positions, equivalent to GetPresentAntennaPositions.
	AntennasPosition []float64 `json:"antennas_position,omitempty"`
	// PassiveJoints are the positions of joints not under direct motor
	// control (e.g. any spring-loaded or unactuated linkages).
	PassiveJoints []float64 `json:"passive_joints,omitempty"`
	// DoA is the current direction-of-arrival estimate, equivalent to GetDoA.
	DoA *DoAInfo `json:"doa,omitempty"`
	// Timestamp is when the daemon captured this state snapshot.
	Timestamp *time.Time `json:"timestamp,omitempty"`
}

FullState is the response of GET /api/state/full and each frame of WS /api/state/ws/full. Fields are omitted by the daemon (left nil/empty) when that subsystem isn't available (e.g. NoMedia mode for DoA).

type GotoRequest

type GotoRequest struct {
	// HeadPose is the target head pose, if the head should move.
	HeadPose *Pose `json:"head_pose,omitempty"`
	// Antennas is the target [left, right] antenna joint positions, if the
	// antennas should move.
	Antennas *[2]float64 `json:"antennas,omitempty"`
	// BodyYaw is the target body yaw, if the body should rotate.
	BodyYaw *float64 `json:"body_yaw,omitempty"`
	// Duration is how long the interpolation should take, in seconds.
	Duration float64 `json:"duration"`
	// Interpolation selects the blending curve between the current and
	// target pose (see InterpolationTechnique).
	Interpolation InterpolationTechnique `json:"interpolation"`
}

GotoRequest is the body of POST /api/move/goto. Only the non-nil target fields are commanded; the others are left at their current value.

type InterpolationTechnique

type InterpolationTechnique string

InterpolationTechnique selects how `Goto` blends between the current and target pose. Verified against a live daemon's /openapi.json.

const (
	InterpolationLinear    InterpolationTechnique = "linear"
	InterpolationMinJerk   InterpolationTechnique = "minjerk"
	InterpolationEaseInOut InterpolationTechnique = "ease_in_out"
	InterpolationCartoon   InterpolationTechnique = "cartoon"
)

type JobInfo

type JobInfo struct {
	// Command is the shell command the daemon is running for this job.
	Command string `json:"command"`
	// Status is the job's current lifecycle state (see JobStatus).
	Status JobStatus `json:"status"`
	// Logs are the job's captured output lines so far.
	Logs []string `json:"logs"`
}

JobInfo is the status of an async app install/update job (see InstallApp, InstallPrivateSpace, UpdateApp).

type JobStatus

type JobStatus string

JobStatus is the status of an async install/update job.

const (
	JobStatusPending    JobStatus = "pending"
	JobStatusInProgress JobStatus = "in_progress"
	JobStatusDone       JobStatus = "done"
	JobStatusFailed     JobStatus = "failed"
)

type Matrix4x4Pose

type Matrix4x4Pose struct {
	// M holds the matrix in row-major order: M[4*row+col]. The last row is
	// implicitly [0 0 0 1] for a rigid transform, but the daemon still sends
	// and expects all 16 values.
	M [16]float64 `json:"m"`
}

Matrix4x4Pose is a raw row-major 4x4 homogeneous transform.

type MotorControlMode

type MotorControlMode string

MotorControlMode selects the robot's motor control mode. Verified against a live daemon's /openapi.json.

const (
	MotorModeEnabled             MotorControlMode = "enabled"
	MotorModeDisabled            MotorControlMode = "disabled"
	MotorModeGravityCompensation MotorControlMode = "gravity_compensation"
)

type MotorStatus

type MotorStatus struct {
	// Mode is the robot's current motor control mode.
	Mode MotorControlMode `json:"mode"`
}

MotorStatus is the response of GET /api/motors/status.

type MoveUUID

type MoveUUID struct {
	// UUID is the move's unique identifier, as assigned by the daemon.
	UUID string `json:"uuid"`
}

MoveUUID identifies a move started via Goto, WakeUp, GotoSleep, or PlayRecordedMoveDataset, for use with Stop/ws/updates.

type Pose

type Pose struct {
	XYZRPY *XYZRPYPose
	Matrix *Matrix4x4Pose
}

Pose mirrors the daemon's `AnyPose = XYZRPYPose | Matrix4x4Pose` union. Exactly one of XYZRPY or Matrix should be set.

func NewMatrixPose

func NewMatrixPose(m [16]float64) *Pose

NewMatrixPose builds a Pose from a raw 4x4 transform.

func NewXYZRPYPose

func NewXYZRPYPose(x, y, z, roll, pitch, yaw float64) *Pose

NewXYZRPYPose builds a Pose from Cartesian position and roll/pitch/yaw.

func (Pose) MarshalJSON

func (p Pose) MarshalJSON() ([]byte, error)

MarshalJSON emits whichever variant is set. The daemon's Pydantic model distinguishes the union by field shape rather than an explicit tag, so we match on the presence of the "m" field (verified against a live daemon's /openapi.json: XYZRPYPose has no "m" field, Matrix4x4Pose requires one).

func (*Pose) UnmarshalJSON

func (p *Pose) UnmarshalJSON(data []byte) error

type ReadAudioParameterResponse

type ReadAudioParameterResponse struct {
	// Name echoes back the parameter name that was requested.
	Name string `json:"name"`
	// Values are the parameter's current value(s).
	Values []float64 `json:"values"`
}

ReadAudioParameterResponse is the response of GET /api/audio/config/parameter/{name}.

type ResolutionInfo

type ResolutionInfo struct {
	// Name is a short label for this resolution mode (e.g. "1080p").
	Name string `json:"name"`
	// Width, Height are the frame dimensions in pixels.
	Width  int `json:"width"`
	Height int `json:"height"`
	// FPS is the frame rate this resolution is streamed at.
	FPS int `json:"fps"`
	// CropFactor is the sensor crop applied to reach this resolution
	// (1.0 = full sensor field of view; >1.0 = cropped/zoomed in).
	CropFactor float64 `json:"crop_factor"`
}

ResolutionInfo is a single camera resolution entry.

type SourceKind

type SourceKind string

SourceKind is where an app comes from.

const (
	SourceKindHFSpace            SourceKind = "hf_space"
	SourceKindDashboardSelection SourceKind = "dashboard_selection"
	SourceKindLocal              SourceKind = "local"
	SourceKindInstalled          SourceKind = "installed"
)

type TestSoundResult

type TestSoundResult struct {
	// Status is a short machine-readable outcome (e.g. "ok").
	Status string `json:"status"`
	// Message is a human-readable description of the outcome.
	Message string `json:"message"`
}

TestSoundResult is the response of POST /api/volume/test-sound.

type VolumeInfo

type VolumeInfo struct {
	// Volume is the current level, 0-100.
	Volume int `json:"volume"`
	// Platform identifies the underlying audio backend/OS (e.g. "linux").
	Platform string `json:"platform"`
	// Device is the backend-specific name of the audio device this
	// volume applies to.
	Device string `json:"device"`
}

VolumeInfo reports the current volume level for a device.

type XYZRPYPose

type XYZRPYPose struct {
	// X, Y, Z are the position, in the daemon's native units (meters,
	// matching the robot's URDF -- see GetURDF).
	X float64 `json:"x"`
	Y float64 `json:"y"`
	Z float64 `json:"z"`
	// Roll, Pitch, Yaw are the orientation, in radians.
	Roll  float64 `json:"roll"`
	Pitch float64 `json:"pitch"`
	Yaw   float64 `json:"yaw"`
}

XYZRPYPose is a Cartesian position plus roll/pitch/yaw orientation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL