diff --git a/dwarfctl/cmd/dwarfctl/main.go b/dwarfctl/cmd/dwarfctl/main.go index 511c120..06e56a4 100644 --- a/dwarfctl/cmd/dwarfctl/main.go +++ b/dwarfctl/cmd/dwarfctl/main.go @@ -29,6 +29,7 @@ func main() { rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "d", false, "verbose WebSocket traffic log") rootCmd.AddCommand( + cmdHealth(), cmdState(), cmdCamera(), cmdMotor(), @@ -71,6 +72,106 @@ func mustCam(s string) api.Camera { } } +// --- health --- + +func cmdHealth() *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Full telescope health check (connectivity + device state)", + Run: func(_ *cobra.Command, _ []string) { + checks := 0 + passed := 0 + + check := func(name string, ok bool, detail string) { + checks++ + if ok { + passed++ + fmt.Printf(" ✅ %-20s %s\n", name, detail) + } else { + fmt.Printf(" ❌ %-20s %s\n", name, detail) + } + } + + fmt.Println("DWARF Telescope Health Check") + fmt.Println(strings.Repeat("=", 50)) + + // 1. Connect + scope := api.New(flagClientID) + scope.SetDebug(flagDebug) + connectStart := time.Now() + err := scope.Connect(flagIP) + elapsed := time.Since(connectStart) + check("WebSocket :9900", err == nil, fmt.Sprintf("%s (%dms)", flagIP+":9900", elapsed.Milliseconds())) + if err != nil { + fmt.Println("\nCannot connect. Aborting.") + os.Exit(1) + } + defer scope.Close() + + // 2. Device state + state, err := scope.GetDeviceState() + check("Device state query", err == nil, fmt.Sprintf("%d bytes", len(state.String()))) + + if err == nil { + di := state.GetDeviceStateInfo() + + // 3. Battery + batt := di.GetBatteryInfo().GetPercentage() + check("Battery", batt > 0, fmt.Sprintf("%d%%", batt)) + + // 4. Storage + stor := di.GetStorageInfo() + avail := stor.GetAvailableSize() + total := stor.GetTotalSize() + storOK := total > 0 && avail > 0 && stor.GetIsValid() + check("Storage", storOK, fmt.Sprintf("%d/%d GB (%s)", avail, total, func() string { + if storOK { return "valid" } + return "INVALID" + }())) + + // 5. RGB state + rgb := di.GetRgbState().GetState() + check("RGB ring", true, fmt.Sprintf("state=%d", rgb)) + + // 6. Focus position + focus := state.GetFocusMotorStateInfo().GetFocusPosition().GetPos() + check("Focus motor", focus > 0, fmt.Sprintf("pos=%d", focus)) + + // 7. Tele camera + tele := state.GetTeleCameraStateInfo() + teleRes := fmt.Sprintf("%dx%d", tele.GetResolutionWidth(), tele.GetResolutionHeight()) + check("Tele camera", tele.GetResolutionWidth() > 0, teleRes) + + // 8. Wide camera + wide := state.GetWideCameraStateInfo() + wideRes := fmt.Sprintf("%dx%d", wide.GetResolutionWidth(), wide.GetResolutionHeight()) + check("Wide camera", wide.GetResolutionWidth() > 0, wideRes) + + // 9. Motor position (may be NEED_RESET) + pos, posErr := scope.MotorGetPosition(0) + if posErr != nil { + check("Motor position", false, posErr.Error()) + } else if pos.GetCode() != 0 { + check("Motor position", false, fmt.Sprintf("code=%d (NEED_RESET)", pos.GetCode())) + } else { + check("Motor position", true, fmt.Sprintf("%.2f", pos.GetPosition())) + } + + // 10. Temperature + cmosTemp := state.GetWideCameraStateInfo().GetCmosTemperature().GetTemperature() + check("CMOS temp", cmosTemp > -270, fmt.Sprintf("%d°C", cmosTemp)) + } + + fmt.Println(strings.Repeat("=", 50)) + status := "HEALTHY" + if passed < checks { + status = "ISSUES DETECTED" + } + fmt.Printf("Result: %d/%d checks passed — %s\n", passed, checks, status) + }, + } +} + // --- state --- func cmdState() *cobra.Command {