Created
May 10, 2026 16:07
-
-
Save sploders101/fef72bfa20e6ab176c390d6d67b942ea to your computer and use it in GitHub Desktop.
Roborock Gamepad Control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "math" | |
| "net/http" | |
| "os" | |
| "time" | |
| "github.com/veandco/go-sdl2/sdl" | |
| ) | |
| const ( | |
| deadzone = 4000 | |
| emitInterval = 250 * time.Millisecond | |
| maxAxis = 32767.0 | |
| endpoint = "http://192.168.1.26/api/v2/robot/capabilities/HighResolutionManualControlCapability" | |
| ) | |
| type Payload struct { | |
| Action string `json:"action"` | |
| Vector struct { | |
| Velocity float64 `json:"velocity"` | |
| Angle float64 `json:"angle"` | |
| } `json:"vector"` | |
| } | |
| func applyDeadzone(v int16) int16 { | |
| if math.Abs(float64(v)) < deadzone { | |
| return 0 | |
| } | |
| return v | |
| } | |
| func normalize(v int16) float64 { | |
| f := float64(v) | |
| if f < -maxAxis { | |
| f = -maxAxis | |
| } | |
| return f / maxAxis | |
| } | |
| func sendCommand(client *http.Client, velocity, angle float64) { | |
| payload := Payload{ | |
| Action: "move", | |
| } | |
| payload.Vector.Velocity = velocity | |
| payload.Vector.Angle = angle | |
| data, err := json.Marshal(payload) | |
| if err != nil { | |
| fmt.Println("JSON error:", err) | |
| return | |
| } | |
| req, err := http.NewRequest("PUT", endpoint, bytes.NewBuffer(data)) | |
| if err != nil { | |
| fmt.Println("Request error:", err) | |
| return | |
| } | |
| req.Header.Set("Content-Type", "application/json") | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| fmt.Println("HTTP error:", err) | |
| return | |
| } | |
| defer resp.Body.Close() | |
| } | |
| func main() { | |
| if err := sdl.Init(sdl.INIT_GAMECONTROLLER); err != nil { | |
| fmt.Fprintf(os.Stderr, "SDL init failed: %s\n", err) | |
| os.Exit(1) | |
| } | |
| defer sdl.Quit() | |
| var controller *sdl.GameController | |
| for i := 0; i < sdl.NumJoysticks(); i++ { | |
| if sdl.IsGameController(i) { | |
| c := sdl.GameControllerOpen(i) | |
| if c != nil { | |
| controller = c | |
| fmt.Printf("Connected: %s\n", controller.Name()) | |
| break | |
| } | |
| } | |
| } | |
| if controller == nil { | |
| fmt.Println("No controller found.") | |
| return | |
| } | |
| defer controller.Close() | |
| var currentX, currentY int16 | |
| client := &http.Client{ | |
| Timeout: 2 * time.Second, | |
| } | |
| ticker := time.NewTicker(emitInterval) | |
| defer ticker.Stop() | |
| fmt.Println("Sending continuous control commands...") | |
| for { | |
| select { | |
| // Update state continuously | |
| default: | |
| for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { | |
| switch e := event.(type) { | |
| case *sdl.ControllerAxisEvent: | |
| switch e.Axis { | |
| case 0: | |
| currentX = applyDeadzone(e.Value) | |
| case 1: | |
| currentY = applyDeadzone(e.Value) | |
| } | |
| case *sdl.QuitEvent: | |
| return | |
| } | |
| } | |
| // Always send at interval | |
| case <-ticker.C: | |
| normX := normalize(currentX) | |
| normY := normalize(currentY) | |
| velocity := -normY // up = positive | |
| angle := normX * 90.0 // -90 to 90 | |
| go sendCommand(client, velocity, angle) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment