Skip to content

Commit a17c934

Browse files
authoredJul 3, 2024··
Support for ota download progress (#156)
1 parent 5aebb3d commit a17c934

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed
 

‎cli/ota/status.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,29 @@ type statusFlags struct {
3838

3939
func initOtaStatusCommand() *cobra.Command {
4040
flags := &statusFlags{}
41-
uploadCommand := &cobra.Command{
41+
statusCommand := &cobra.Command{
4242
Use: "status",
4343
Short: "OTA status",
4444
Long: "Get OTA status by OTA or device ID",
4545
Run: func(cmd *cobra.Command, args []string) {
46-
if err := runPrintOtaStatusCommand(flags); err != nil {
47-
feedback.Errorf("Error during ota get status: %v", err)
46+
if err := runPrintOtaStatusCommand(flags, cmd); err != nil {
47+
feedback.Errorf("\nError during ota get status: %v", err)
4848
os.Exit(errorcodes.ErrGeneric)
4949
}
5050
},
5151
}
52-
uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID")
53-
uploadCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)")
54-
uploadCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
55-
uploadCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)")
56-
uploadCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)")
52+
statusCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID")
53+
statusCommand.Flags().StringVarP(&flags.otaIDs, "ota-ids", "", "", "OTA IDs (comma separated)")
54+
statusCommand.Flags().StringVarP(&flags.deviceId, "device-id", "d", "", "Device ID")
55+
statusCommand.Flags().Int16VarP(&flags.limit, "limit", "l", 10, "Output limit (default: 10)")
56+
statusCommand.Flags().StringVarP(&flags.sort, "sort", "s", "desc", "Sorting (default: desc)")
5757

58-
return uploadCommand
58+
return statusCommand
5959
}
6060

61-
func runPrintOtaStatusCommand(flags *statusFlags) error {
61+
func runPrintOtaStatusCommand(flags *statusFlags, command *cobra.Command) error {
6262
if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" {
63+
command.Help()
6364
return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set")
6465
}
6566

‎command/ota/status.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ func PrintOtaStatus(otaid, otaids, device string, cred *config.Credentials, limi
2727
res, err := otapi.GetOtaStatusByOtaID(otaid, limit, order)
2828
if err == nil && res != nil {
2929
feedback.PrintResult(otaapi.OtaStatusDetail{
30-
Ota: res.Ota,
31-
Details: res.States,
30+
FirmwareSize: res.FirmwareSize,
31+
Ota: res.Ota,
32+
Details: res.States,
3233
})
3334
} else if err != nil {
3435
return err

‎internal/ota-api/dto.go

+57-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package otaapi
1919

2020
import (
21+
"strconv"
2122
"strings"
2223
"time"
2324

@@ -26,10 +27,13 @@ import (
2627
"github.com/arduino/arduino-cli/table"
2728
)
2829

30+
const progressBarMultiplier = 2
31+
2932
type (
3033
OtaStatusResponse struct {
31-
Ota Ota `json:"ota"`
32-
States []State `json:"states,omitempty"`
34+
FirmwareSize *int64 `json:"firmware_size,omitempty"`
35+
Ota Ota `json:"ota"`
36+
States []State `json:"states,omitempty"`
3337
}
3438

3539
OtaStatusList struct {
@@ -53,8 +57,9 @@ type (
5357
}
5458

5559
OtaStatusDetail struct {
56-
Ota Ota `json:"ota"`
57-
Details []State `json:"details,omitempty"`
60+
FirmwareSize *int64 `json:"firmware_size,omitempty"`
61+
Ota Ota `json:"ota"`
62+
Details []State `json:"details,omitempty"`
5863
}
5964
)
6065

@@ -154,15 +159,62 @@ func (r OtaStatusDetail) String() string {
154159
if len(r.Details) > 0 {
155160
t = table.New()
156161
t.SetHeader("Time", "Status", "Detail")
162+
fwSize := int64(0)
163+
if r.FirmwareSize != nil {
164+
fwSize = *r.FirmwareSize
165+
}
157166
for _, s := range r.Details {
158-
t.AddRow(formatHumanReadableTs(s.Timestamp), upperCaseFirst(s.State), s.StateData)
167+
stateData := formatStateData(s.State, s.StateData, fwSize, hasReachedFlashState(r.Details))
168+
t.AddRow(formatHumanReadableTs(s.Timestamp), upperCaseFirst(s.State), stateData)
159169
}
160170
output += "\nDetails:\n" + t.Render()
161171
}
162172

163173
return output
164174
}
165175

176+
func hasReachedFlashState(states []State) bool {
177+
for _, s := range states {
178+
if s.State == "flash" || s.State == "reboot" {
179+
return true
180+
}
181+
}
182+
return false
183+
}
184+
185+
func formatStateData(state, data string, firmware_size int64, hasReceivedFlashState bool) string {
186+
if data == "" || data == "Unknown" {
187+
return ""
188+
}
189+
if state == "fetch" {
190+
// This is the state 'fetch' of OTA progress. This contains a number that represents the number of bytes fetched
191+
actualDownloadedData, err := strconv.Atoi(data)
192+
if err != nil || actualDownloadedData <= 0 || firmware_size <= 0 { // Sanitize and avoid division by zero
193+
return data
194+
}
195+
if hasReceivedFlashState {
196+
return buildSimpleProgressBar(float64(100))
197+
}
198+
percentage := (float64(actualDownloadedData) / float64(firmware_size)) * 100
199+
return buildSimpleProgressBar(percentage)
200+
}
201+
return data
202+
}
203+
204+
func buildSimpleProgressBar(progress float64) string {
205+
progressInt := int(progress) / 10
206+
progressInt = progressInt * progressBarMultiplier
207+
maxProgress := 10 * progressBarMultiplier
208+
var bar strings.Builder
209+
bar.WriteString("[")
210+
bar.WriteString(strings.Repeat("=", progressInt))
211+
bar.WriteString(strings.Repeat(" ", maxProgress-progressInt))
212+
bar.WriteString("] ")
213+
bar.WriteString(strconv.FormatFloat(progress, 'f', 2, 64))
214+
bar.WriteString("%")
215+
return bar.String()
216+
}
217+
166218
func upperCaseFirst(s string) string {
167219
if len(s) > 0 {
168220
s = strings.ReplaceAll(s, "_", " ")

‎internal/ota-api/dto_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package otaapi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestProgressBar_notCompletePct(t *testing.T) {
10+
firmwareSize := int64(25665 * 2)
11+
bar := formatStateData("fetch", "25665", firmwareSize, false)
12+
assert.Equal(t, "[========== ] 50.00%", bar)
13+
}
14+
15+
func TestProgressBar_ifFlashState_goTo100Pct(t *testing.T) {
16+
firmwareSize := int64(25665 * 2)
17+
bar := formatStateData("fetch", "25665", firmwareSize, true) // If in flash status, go to 100%
18+
assert.Equal(t, "[====================] 100.00%", bar)
19+
}

0 commit comments

Comments
 (0)
Please sign in to comment.