diff --git a/cli/ota/status.go b/cli/ota/status.go index 89d67081..4c3ce90e 100644 --- a/cli/ota/status.go +++ b/cli/ota/status.go @@ -38,28 +38,29 @@ type statusFlags struct { func initOtaStatusCommand() *cobra.Command { flags := &statusFlags{} - uploadCommand := &cobra.Command{ + statusCommand := &cobra.Command{ Use: "status", Short: "OTA status", Long: "Get OTA status by OTA or device ID", Run: func(cmd *cobra.Command, args []string) { - if err := runPrintOtaStatusCommand(flags); err != nil { - feedback.Errorf("Error during ota get status: %v", err) + if err := runPrintOtaStatusCommand(flags, cmd); err != nil { + feedback.Errorf("\nError during ota get status: %v", err) os.Exit(errorcodes.ErrGeneric) } }, } - uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID") - uploadCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)") - uploadCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID") - uploadCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)") - uploadCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)") + statusCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID") + statusCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)") + statusCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID") + statusCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)") + statusCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)") - return uploadCommand + return statusCommand } -func runPrintOtaStatusCommand(flags *statusFlags) error { +func runPrintOtaStatusCommand(flags *statusFlags, command *cobra.Command) error { if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" { + command.Help() return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set") } diff --git a/command/ota/status.go b/command/ota/status.go index 6913209d..f13ec5dd 100644 --- a/command/ota/status.go +++ b/command/ota/status.go @@ -27,8 +27,9 @@ func PrintOtaStatus(otaid, otaids, device string, cred *config.Credentials, limi res, err := otapi.GetOtaStatusByOtaID(otaid, limit, order) if err == nil && res != nil { feedback.PrintResult(otaapi.OtaStatusDetail{ - Ota: res.Ota, - Details: res.States, + FirmwareSize: res.FirmwareSize, + Ota: res.Ota, + Details: res.States, }) } else if err != nil { return err diff --git a/internal/ota-api/dto.go b/internal/ota-api/dto.go index eaf09424..04184ed8 100644 --- a/internal/ota-api/dto.go +++ b/internal/ota-api/dto.go @@ -18,6 +18,7 @@ package otaapi import ( + "strconv" "strings" "time" @@ -26,10 +27,13 @@ import ( "github.com/arduino/arduino-cli/table" ) +const progressBarMultiplier = 2 + type ( OtaStatusResponse struct { - Ota Ota `json:"ota"` - States []State `json:"states,omitempty"` + FirmwareSize *int64 `json:"firmware_size,omitempty"` + Ota Ota `json:"ota"` + States []State `json:"states,omitempty"` } OtaStatusList struct { @@ -53,8 +57,9 @@ type ( } OtaStatusDetail struct { - Ota Ota `json:"ota"` - Details []State `json:"details,omitempty"` + FirmwareSize *int64 `json:"firmware_size,omitempty"` + Ota Ota `json:"ota"` + Details []State `json:"details,omitempty"` } ) @@ -154,8 +159,13 @@ func (r OtaStatusDetail) String() string { if len(r.Details) > 0 { t = table.New() t.SetHeader("Time", "Status", "Detail") + fwSize := int64(0) + if r.FirmwareSize != nil { + fwSize = *r.FirmwareSize + } for _, s := range r.Details { - t.AddRow(formatHumanReadableTs(s.Timestamp), upperCaseFirst(s.State), s.StateData) + stateData := formatStateData(s.State, s.StateData, fwSize, hasReachedFlashState(r.Details)) + t.AddRow(formatHumanReadableTs(s.Timestamp), upperCaseFirst(s.State), stateData) } output += "\nDetails:\n" + t.Render() } @@ -163,6 +173,48 @@ func (r OtaStatusDetail) String() string { return output } +func hasReachedFlashState(states []State) bool { + for _, s := range states { + if s.State == "flash" || s.State == "reboot" { + return true + } + } + return false +} + +func formatStateData(state, data string, firmware_size int64, hasReceivedFlashState bool) string { + if data == "" || data == "Unknown" { + return "" + } + if state == "fetch" { + // This is the state 'fetch' of OTA progress. This contains a number that represents the number of bytes fetched + actualDownloadedData, err := strconv.Atoi(data) + if err != nil || actualDownloadedData <= 0 || firmware_size <= 0 { // Sanitize and avoid division by zero + return data + } + if hasReceivedFlashState { + return buildSimpleProgressBar(float64(100)) + } + percentage := (float64(actualDownloadedData) / float64(firmware_size)) * 100 + return buildSimpleProgressBar(percentage) + } + return data +} + +func buildSimpleProgressBar(progress float64) string { + progressInt := int(progress) / 10 + progressInt = progressInt * progressBarMultiplier + maxProgress := 10 * progressBarMultiplier + var bar strings.Builder + bar.WriteString("[") + bar.WriteString(strings.Repeat("=", progressInt)) + bar.WriteString(strings.Repeat(" ", maxProgress-progressInt)) + bar.WriteString("] ") + bar.WriteString(strconv.FormatFloat(progress, 'f', 2, 64)) + bar.WriteString("%") + return bar.String() +} + func upperCaseFirst(s string) string { if len(s) > 0 { s = strings.ReplaceAll(s, "_", " ") diff --git a/internal/ota-api/dto_test.go b/internal/ota-api/dto_test.go new file mode 100644 index 00000000..bb91225f --- /dev/null +++ b/internal/ota-api/dto_test.go @@ -0,0 +1,19 @@ +package otaapi + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestProgressBar_notCompletePct(t *testing.T) { + firmwareSize := int64(25665 * 2) + bar := formatStateData("fetch", "25665", firmwareSize, false) + assert.Equal(t, "[========== ] 50.00%", bar) +} + +func TestProgressBar_ifFlashState_goTo100Pct(t *testing.T) { + firmwareSize := int64(25665 * 2) + bar := formatStateData("fetch", "25665", firmwareSize, true) // If in flash status, go to 100% + assert.Equal(t, "[====================] 100.00%", bar) +}