Skip to content

Commit a92e17c

Browse files
committed
[WIP] Full device provision
1 parent fc8fa40 commit a92e17c

File tree

6 files changed

+311
-5
lines changed

6 files changed

+311
-5
lines changed

arduino/cli/commander.go

+38
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/arduino/arduino-cli/cli/instance"
2727
"github.com/arduino/arduino-cli/commands/board"
28+
"github.com/arduino/arduino-cli/commands/compile"
2829
"github.com/arduino/arduino-cli/commands/upload"
2930
"github.com/arduino/arduino-cli/configuration"
3031
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -87,3 +88,40 @@ func (c *commander) UploadBin(fqbn, bin, port string) error {
8788
}
8889
return nil
8990
}
91+
92+
// Upload executes the 'arduino-cli upload' command
93+
// and returns its result.
94+
func (c *commander) Upload(fqbn, sketch, port string) error {
95+
req := &rpc.UploadRequest{
96+
Instance: c.Instance,
97+
Fqbn: fqbn,
98+
SketchPath: filepath.Dir(sketch),
99+
Port: port,
100+
Verbose: false,
101+
}
102+
103+
l := logrus.StandardLogger().WithField("source", "arduino-cli").Writer()
104+
if _, err := upload.Upload(context.Background(), req, l, l); err != nil {
105+
err = fmt.Errorf("%s: %w", "uploading sketch", err)
106+
return err
107+
}
108+
return nil
109+
}
110+
111+
// Compile executes the 'arduino-cli compile' command
112+
// and returns its result.
113+
func (c *commander) Compile(fqbn, sketch string) error {
114+
req := &rpc.CompileRequest{
115+
Instance: c.Instance,
116+
Fqbn: fqbn,
117+
SketchPath: filepath.Dir(sketch),
118+
Verbose: true,
119+
}
120+
121+
l := logrus.StandardLogger().WithField("source", "arduino-cli").Writer()
122+
if _, err := compile.Compile(context.Background(), req, l, l, false); err != nil {
123+
err = fmt.Errorf("%s: %w", "compiling sketch", err)
124+
return err
125+
}
126+
return nil
127+
}

arduino/commander.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ import (
2626
type Commander interface {
2727
BoardList() ([]*rpc.DetectedPort, error)
2828
UploadBin(fqbn, bin, port string) error
29-
//Compile() error
29+
Upload(fqbn, sketch, port string) error
30+
Compile(fqbn, sketch string) error
3031
}

arduino/grpc/compile.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ type compileHandler struct {
3232

3333
// Compile executes the 'arduino-cli compile' command
3434
// and returns its result.
35-
func (c compileHandler) Compile() error {
35+
func (c compileHandler) Compile(fqbn, sketch string) error {
36+
return nil
37+
}
38+
39+
// Upload executes the 'arduino-cli upload' command
40+
// and returns its result.
41+
func (c compileHandler) Upload(fqbn, sketch, port string) error {
3642
return nil
3743
}
3844

cli/cli.go

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/arduino/arduino-cloud-cli/cli/dashboard"
3030
"github.com/arduino/arduino-cloud-cli/cli/device"
3131
"github.com/arduino/arduino-cloud-cli/cli/ota"
32+
"github.com/arduino/arduino-cloud-cli/cli/provision"
3233
"github.com/arduino/arduino-cloud-cli/cli/thing"
3334
"github.com/arduino/arduino-cloud-cli/cli/version"
3435
"github.com/sirupsen/logrus"
@@ -54,6 +55,7 @@ func Execute() {
5455
cli.AddCommand(thing.NewCommand())
5556
cli.AddCommand(dashboard.NewCommand())
5657
cli.AddCommand(ota.NewCommand())
58+
cli.AddCommand(provision.NewCommand())
5759

5860
cli.PersistentFlags().BoolVarP(&cliFlags.verbose, "verbose", "v", false, "Print the logs on the standard output.")
5961
cli.PersistentFlags().StringVar(&cliFlags.outputFormat, "format", "text", "The output format, can be {text|json}.")

cli/provision/provision.go

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package provision
19+
20+
import (
21+
"bytes"
22+
"fmt"
23+
"io/ioutil"
24+
"os"
25+
"time"
26+
27+
// old "github.com/arduino/arduino-cli/arduino/sketches"
28+
29+
"github.com/arduino/arduino-cli/arduino/serialutils"
30+
"github.com/arduino/arduino-cli/cli/errorcodes"
31+
"github.com/arduino/arduino-cli/cli/feedback"
32+
"github.com/arduino/arduino-cli/new/arduino/sketch"
33+
"github.com/arduino/arduino-cloud-cli/arduino/cli"
34+
"github.com/arduino/arduino-cloud-cli/command/device"
35+
"github.com/arduino/arduino-cloud-cli/command/thing"
36+
paths "github.com/arduino/go-paths-helper"
37+
"github.com/sirupsen/logrus"
38+
"github.com/spf13/cobra"
39+
)
40+
41+
const (
42+
thingPropertiesFile = "thingProperties.h"
43+
tempPropertiesFile = "thingProperties.temp"
44+
)
45+
46+
var provisionFlags struct {
47+
name string
48+
port string
49+
fqbn string
50+
template string
51+
sketch string
52+
}
53+
54+
type state struct {
55+
skip bool
56+
device *device.DeviceInfo
57+
thing *thing.ThingInfo
58+
}
59+
60+
func (s *state) flush() {
61+
if s.skip {
62+
return
63+
}
64+
if s.device != nil {
65+
device.Delete(&device.DeleteParams{ID: &s.device.ID})
66+
}
67+
if s.thing != nil {
68+
thing.Delete(&thing.DeleteParams{ID: &s.thing.ID})
69+
}
70+
}
71+
72+
func NewCommand() *cobra.Command {
73+
provisionCommand := &cobra.Command{
74+
Use: "provision",
75+
Short: "Provision device, with thing and sketch",
76+
Long: "Provision a device, attaching a thing to it and uploading a sketch to it.",
77+
}
78+
79+
provisionCommand.AddCommand(initProvisionCommand())
80+
81+
return provisionCommand
82+
}
83+
84+
func initProvisionCommand() *cobra.Command {
85+
provisionCommand := &cobra.Command{
86+
Use: "create",
87+
Short: "Create a device",
88+
Long: "Create a device for Arduino IoT Cloud",
89+
Run: runProvisionCommand,
90+
}
91+
provisionCommand.Flags().StringVarP(&provisionFlags.port, "port", "p", "", "Device port")
92+
provisionCommand.Flags().StringVarP(&provisionFlags.name, "name", "n", "", "Device and thing name")
93+
provisionCommand.Flags().StringVarP(&provisionFlags.fqbn, "fqbn", "b", "", "Device fqbn")
94+
provisionCommand.Flags().StringVarP(&provisionFlags.sketch, "sketch", "s", "", "Path of the sketch to upload")
95+
provisionCommand.Flags().StringVarP(&provisionFlags.template, "template", "t", "", "Path of the thing template")
96+
97+
provisionCommand.MarkFlagRequired("port")
98+
provisionCommand.MarkFlagRequired("fqbn")
99+
provisionCommand.MarkFlagRequired("name")
100+
provisionCommand.MarkFlagRequired("sketch")
101+
provisionCommand.MarkFlagRequired("template")
102+
return provisionCommand
103+
}
104+
105+
func runProvisionCommand(cmd *cobra.Command, args []string) {
106+
logrus.Info("Provisioning started")
107+
err := run()
108+
if err != nil {
109+
os.Exit(errorcodes.ErrGeneric)
110+
}
111+
}
112+
113+
func run() error {
114+
s := state{skip: false}
115+
defer s.flush()
116+
117+
deviceName := provisionFlags.name + "-device"
118+
thingName := provisionFlags.name + "-thing"
119+
120+
logrus.Infof("Creating device with name %s", deviceName)
121+
dev, err := deviceCreate(deviceName)
122+
if err != nil {
123+
feedback.Errorf("Error during device create: %v", err)
124+
return err
125+
}
126+
s.device = dev
127+
128+
logrus.Infof("Creating thing from template %s", provisionFlags.template)
129+
th, err := thing.Create(&thing.CreateParams{Name: &thingName, Template: provisionFlags.template})
130+
if err != nil {
131+
feedback.Errorf("Error during thing create: %v", err)
132+
return err
133+
}
134+
s.thing = th
135+
136+
logrus.Info("Binding thing to device")
137+
err = thing.Bind(&thing.BindParams{ID: th.ID, DeviceID: dev.ID})
138+
if err != nil {
139+
feedback.Errorf("Error during thing bind: %v", err)
140+
return err
141+
}
142+
143+
logrus.Info("Edit sketch thing-id")
144+
skPath := paths.New(provisionFlags.sketch)
145+
sk, err := sketch.New(skPath)
146+
if err != nil {
147+
feedback.Errorf("Error during sketch opening: %v", err)
148+
return err
149+
}
150+
err = editSketch(sk, th.ID)
151+
defer restoreSketch(sk)
152+
if err != nil {
153+
feedback.Errorf("Error during sketch edit: %v", err)
154+
return err
155+
}
156+
157+
logrus.Info("Compile and upload the modified sketch")
158+
if err := uploadSketch(sk, provisionFlags.fqbn, "arduino:samd:mkrwifi1010"); err != nil {
159+
feedback.Errorf("Error during sketch upload: %v", err)
160+
return err
161+
}
162+
163+
// Don't flush state if command was successful
164+
s.skip = true
165+
logrus.Infof("Device provisioned, device-id: %s, thing-id: %s", dev.ID, th.ID)
166+
return nil
167+
}
168+
169+
func deviceCreate(name string) (*device.DeviceInfo, error) {
170+
params := &device.CreateParams{
171+
Name: name,
172+
}
173+
if provisionFlags.port != "" {
174+
params.Port = &provisionFlags.port
175+
}
176+
if provisionFlags.fqbn != "" {
177+
params.Fqbn = &provisionFlags.fqbn
178+
}
179+
return device.Create(params)
180+
}
181+
182+
func editSketch(sk *sketch.Sketch, thingID string) (err error) {
183+
var propPath *paths.Path
184+
for _, f := range sk.AdditionalFiles {
185+
if f.Base() == thingPropertiesFile {
186+
propPath, err = f.Abs()
187+
if err != nil {
188+
return fmt.Errorf("retrieving '%s': %w", thingPropertiesFile, err)
189+
}
190+
break
191+
}
192+
}
193+
if propPath == nil {
194+
return fmt.Errorf("this is not a cloud sketch: '%s' not found", thingPropertiesFile)
195+
}
196+
197+
// Change name of the original properties file. It will be restored later
198+
tempPath := sk.FullPath.Join(tempPropertiesFile)
199+
err = propPath.Rename(tempPath)
200+
if err != nil {
201+
return err
202+
}
203+
204+
// Read the properties file content
205+
file, err := tempPath.Open()
206+
if err != nil {
207+
return err
208+
}
209+
content, err := ioutil.ReadAll(file)
210+
file.Close()
211+
if err != nil {
212+
return err
213+
}
214+
215+
// Write the properties file with the new content
216+
thID := "setThingId(\"" + thingID + "\")"
217+
content = bytes.Replace(content, []byte("setThingId(THING_ID)"), []byte(thID), -1)
218+
if err = propPath.WriteFile(content); err != nil {
219+
return err
220+
}
221+
222+
return nil
223+
}
224+
225+
func restoreSketch(sk *sketch.Sketch) {
226+
tempFile := sk.FullPath.Join(tempPropertiesFile)
227+
if ex, err := tempFile.ExistCheck(); !ex || err != nil {
228+
return
229+
}
230+
originalFile := sk.FullPath.Join(thingPropertiesFile)
231+
tempFile.Rename(originalFile)
232+
}
233+
234+
func uploadSketch(sk *sketch.Sketch, fqbn, port string) error {
235+
comm, err := cli.NewCommander()
236+
if err != nil {
237+
return err
238+
}
239+
240+
// Compile the sketch
241+
err = comm.Compile(fqbn, sk.MainFile.String())
242+
if err != nil {
243+
return fmt.Errorf("cannot compile sketch: %w", err)
244+
}
245+
246+
errMsg := "Error while uploading the custom sketch, try to put the board in bootloader mode"
247+
err = device.Retry(5, time.Millisecond*500, errMsg, func() error {
248+
serialutils.Reset(port, true, nil)
249+
time.Sleep(300 * time.Millisecond)
250+
return comm.Upload(fqbn, sk.MainFile.String(), port)
251+
})
252+
if err != nil {
253+
return fmt.Errorf("cannot upload sketch: %w", err)
254+
}
255+
256+
return nil
257+
}

command/device/provision.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (p provision) run() error {
5858
time.Sleep(500 * time.Millisecond)
5959
// Try to upload the provisioning sketch
6060
errMsg := "Error while uploading the provisioning sketch"
61-
err = retry(5, time.Millisecond*1000, errMsg, func() error {
61+
err = Retry(5, time.Millisecond*1000, errMsg, func() error {
6262
//serialutils.Reset(dev.port, true, nil)
6363
return p.UploadBin(p.board.fqbn, bin, p.board.port)
6464
})
@@ -71,13 +71,15 @@ func (p provision) run() error {
7171
time.Sleep(1500 * time.Millisecond)
7272
p.ser = serial.NewSerial()
7373
errMsg = "Error while connecting to the board"
74-
err = retry(5, time.Millisecond*1000, errMsg, func() error {
74+
err = Retry(5, time.Millisecond*1000, errMsg, func() error {
7575
return p.ser.Connect(p.board.port)
7676
})
7777
if err != nil {
7878
return err
7979
}
8080
defer p.ser.Close()
81+
82+
time.Sleep(5000 * time.Millisecond)
8183
logrus.Infof("%s\n\n", "Connected to board")
8284

8385
// Send configuration commands to the board
@@ -265,7 +267,7 @@ func downloadProvisioningFile(fqbn string) (string, error) {
265267
//return path, nil
266268
}
267269

268-
func retry(tries int, sleep time.Duration, errMsg string, fun func() error) error {
270+
func Retry(tries int, sleep time.Duration, errMsg string, fun func() error) error {
269271
var err error
270272
for n := 0; n < tries; n++ {
271273
err = fun()

0 commit comments

Comments
 (0)