18
18
package ota
19
19
20
20
import (
21
+ "errors"
21
22
"fmt"
22
23
"io/ioutil"
23
24
"os"
24
25
"path/filepath"
26
+ "strings"
25
27
26
28
"github.com/arduino/arduino-cloud-cli/internal/config"
27
29
"github.com/arduino/arduino-cloud-cli/internal/iot"
@@ -32,19 +34,29 @@ const (
32
34
otaExpirationMins = 10
33
35
// deferred ota can take up to 1 week (equal to 10080 minutes)
34
36
otaDeferredExpirationMins = 10080
37
+
38
+ numConcurrentUploads = 10
35
39
)
36
40
37
41
// UploadParams contains the parameters needed to
38
42
// perform an OTA upload.
39
43
type UploadParams struct {
40
- DeviceID string
41
- File string
42
- Deferred bool
44
+ DeviceIDs []string
45
+ Tags map [string ]string
46
+ File string
47
+ Deferred bool
48
+ FQBN string
43
49
}
44
50
45
51
// Upload command is used to upload a firmware OTA,
46
52
// on a device of Arduino IoT Cloud.
47
53
func Upload (params * UploadParams ) error {
54
+ if params .DeviceIDs == nil && params .Tags == nil {
55
+ return errors .New ("provide either DeviceID or Tags" )
56
+ } else if params .DeviceIDs != nil && params .Tags != nil {
57
+ return errors .New ("cannot use both DeviceID and Tags. only one of them should be not nil" )
58
+ }
59
+
48
60
conf , err := config .Retrieve ()
49
61
if err != nil {
50
62
return err
@@ -54,10 +66,14 @@ func Upload(params *UploadParams) error {
54
66
return err
55
67
}
56
68
57
- dev , err := iotClient . DeviceShow ( params .DeviceID )
69
+ d , err := idsGivenTags ( iotClient , params .Tags )
58
70
if err != nil {
59
71
return err
60
72
}
73
+ devs := append (params .DeviceIDs , d ... )
74
+ if len (devs ) == 0 {
75
+ return errors .New ("no device found" )
76
+ }
61
77
62
78
otaDir , err := ioutil .TempDir ("" , "" )
63
79
if err != nil {
@@ -66,7 +82,7 @@ func Upload(params *UploadParams) error {
66
82
otaFile := filepath .Join (otaDir , "temp.ota" )
67
83
defer os .RemoveAll (otaDir )
68
84
69
- err = Generate (params .File , otaFile , dev . Fqbn )
85
+ err = Generate (params .File , otaFile , params . FQBN )
70
86
if err != nil {
71
87
return fmt .Errorf ("%s: %w" , "cannot generate .ota file" , err )
72
88
}
@@ -81,10 +97,56 @@ func Upload(params *UploadParams) error {
81
97
expiration = otaDeferredExpirationMins
82
98
}
83
99
84
- err = iotClient .DeviceOTA (params .DeviceID , file , expiration )
100
+ return run (iotClient , devs , file , expiration )
101
+ }
102
+
103
+ func idsGivenTags (iotClient iot.Client , tags map [string ]string ) ([]string , error ) {
104
+ if tags == nil {
105
+ return nil , nil
106
+ }
107
+ devs , err := iotClient .DeviceList (tags )
85
108
if err != nil {
86
- return err
109
+ return nil , fmt .Errorf ("%s: %w" , "cannot retrieve devices from cloud" , err )
110
+ }
111
+ devices := make ([]string , 0 , len (devs ))
112
+ for _ , d := range devs {
113
+ devices = append (devices , d .Id )
114
+ }
115
+ return devices , nil
116
+ }
117
+
118
+ func run (iotClient iot.Client , ids []string , file * os.File , expiration int ) error {
119
+ idsToProcess := make (chan string , 2000 )
120
+ idsFailed := make (chan string , 2000 )
121
+ for _ , id := range ids {
122
+ idsToProcess <- id
123
+ }
124
+ close (idsToProcess )
125
+
126
+ for i := 0 ; i < numConcurrentUploads ; i ++ {
127
+ go func () {
128
+ for id := range idsToProcess {
129
+ err := iotClient .DeviceOTA (id , file , expiration )
130
+ fail := ""
131
+ if err != nil {
132
+ fail = id
133
+ }
134
+ idsFailed <- fail
135
+ }
136
+ }()
137
+ }
138
+
139
+ failMsg := ""
140
+ for range ids {
141
+ i := <- idsFailed
142
+ if i != "" {
143
+ failMsg = strings .Join ([]string {i , failMsg }, "," )
144
+ }
87
145
}
88
146
147
+ if failMsg != "" {
148
+ failMsg = strings .TrimRight (failMsg , "," )
149
+ return fmt .Errorf ("failed to update these devices: %s" , failMsg )
150
+ }
89
151
return nil
90
152
}
0 commit comments