Skip to content

Commit e777598

Browse files
Paolo Calaopolldo
Paolo Calao
authored andcommitted
Add device create-generic command (#77)
A new command device create-generic has been added A generic device is like a virtual device that doesn't need to be attached to an actual physical board. Any actual physical board can connect to Arduino IoT Cloud using the credentials of a generic device. An optional `--fqbn` flag can be passed to specify the fqbn of the device, otherwise it will be set to `generic:generic:generic`.
1 parent 25b95df commit e777598

File tree

6 files changed

+222
-0
lines changed

6 files changed

+222
-0
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ The list of supported LoRa frequency plans can be retrieved with:
7272

7373
`$ arduino-cloud-cli device list-frequency-plans`
7474

75+
#### Generic device
76+
77+
A generic device is like a virtual device that doesn't need to be attached to an actual physical board.
78+
Any actual physical board can connect to Arduino IoT Cloud using the credentials of a generic device.
79+
Generic devices can be created using a specific command.
80+
An optional `--fqbn` flag can be passed to specify the fqbn of the device, otherwise it will be set to `generic:generic:generic`.
81+
82+
`$ arduino-cloud-cli device create-generic --name <deviceName> --fqbn <fqbn>`
7583

7684
## Device commands
7785

cli/device/creategeneric.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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 device
19+
20+
import (
21+
"fmt"
22+
"os"
23+
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
"github.com/arduino/arduino-cloud-cli/command/device"
27+
"github.com/sirupsen/logrus"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
var createGenericFlags struct {
32+
name string
33+
fqbn string
34+
}
35+
36+
func initCreateGenericCommand() *cobra.Command {
37+
createGenericCommand := &cobra.Command{
38+
Use: "create-generic",
39+
Short: "Create a generic device",
40+
Long: "Create a generic device for Arduino IoT Cloud",
41+
Run: runCreateGenericCommand,
42+
}
43+
createGenericCommand.Flags().StringVarP(&createGenericFlags.name, "name", "n", "", "Device name")
44+
createGenericCommand.Flags().StringVarP(&createGenericFlags.fqbn, "fqbn", "b", "generic:generic:generic", "Device fqbn")
45+
createGenericCommand.MarkFlagRequired("name")
46+
return createGenericCommand
47+
}
48+
49+
func runCreateGenericCommand(cmd *cobra.Command, args []string) {
50+
logrus.Infof("Creating generic device with name %s", createGenericFlags.name)
51+
52+
params := &device.CreateGenericParams{
53+
Name: createGenericFlags.name,
54+
FQBN: createGenericFlags.fqbn,
55+
}
56+
57+
dev, err := device.CreateGeneric(params)
58+
if err != nil {
59+
feedback.Errorf("Error during device create-generic: %v", err)
60+
os.Exit(errorcodes.ErrGeneric)
61+
}
62+
63+
feedback.PrintResult(createGenericResult{dev})
64+
}
65+
66+
type createGenericResult struct {
67+
device *device.DeviceGenericInfo
68+
}
69+
70+
func (r createGenericResult) Data() interface{} {
71+
return r.device
72+
}
73+
74+
func (r createGenericResult) String() string {
75+
return fmt.Sprintf(
76+
"id: %s\nsecret-key: %s\nname: %s\nboard: %s\nserial-number: %s\nfqbn: %s",
77+
r.device.ID,
78+
r.device.Password,
79+
r.device.Name,
80+
r.device.Board,
81+
r.device.Serial,
82+
r.device.FQBN,
83+
)
84+
}

cli/device/device.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func NewCommand() *cobra.Command {
3636
deviceCommand.AddCommand(tag.InitDeleteTagsCommand())
3737
deviceCommand.AddCommand(initListFrequencyPlansCommand())
3838
deviceCommand.AddCommand(initCreateLoraCommand())
39+
deviceCommand.AddCommand(initCreateGenericCommand())
3940

4041
return deviceCommand
4142
}

command/device/creategeneric.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 device
19+
20+
import (
21+
"fmt"
22+
23+
"github.com/arduino/arduino-cloud-cli/internal/config"
24+
"github.com/arduino/arduino-cloud-cli/internal/iot"
25+
)
26+
27+
const (
28+
genericDType = "login_and_secretkey_wifi"
29+
)
30+
31+
// CreateGenericParams contains the parameters needed
32+
// to create a new generic device.
33+
type CreateGenericParams struct {
34+
Name string // Device name
35+
FQBN string // Board FQBN
36+
}
37+
38+
// DeviceGenericInfo contains the most interesting
39+
// parameters of a generic Arduino IoT Cloud device.
40+
type DeviceGenericInfo struct {
41+
DeviceInfo
42+
Password string `json:"secret-key"`
43+
}
44+
45+
// CreateGeneric command is used to add a new generic device to Arduino IoT Cloud.
46+
func CreateGeneric(params *CreateGenericParams) (*DeviceGenericInfo, error) {
47+
conf, err := config.Retrieve()
48+
if err != nil {
49+
return nil, err
50+
}
51+
iotClient, err := iot.NewClient(conf.Client, conf.Secret)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
dev, err := iotClient.DeviceCreate(params.FQBN, params.Name, "", genericDType)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
pass, err := iotClient.DevicePassSet(dev.Id)
62+
if err != nil {
63+
if errDel := iotClient.DeviceDelete(dev.Id); errDel != nil {
64+
return nil, fmt.Errorf(
65+
"device was successfully created on IoT-API but " +
66+
"now we can't set its secret key nor delete it - please check " +
67+
"it on the web application.\n\nFetch error: " + err.Error() +
68+
"\nDeletion error: " + errDel.Error(),
69+
)
70+
}
71+
return nil, fmt.Errorf("cannot create generic device: %w", err)
72+
}
73+
74+
devInfo := &DeviceGenericInfo{
75+
DeviceInfo: DeviceInfo{
76+
Name: dev.Name,
77+
ID: dev.Id,
78+
Board: dev.Type,
79+
Serial: dev.Serial,
80+
FQBN: dev.Fqbn,
81+
},
82+
Password: pass.SuggestedPassword,
83+
}
84+
return devInfo, nil
85+
}

internal/iot/client.go

+21
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Client interface {
3434
DeviceList(tags map[string]string) ([]iotclient.ArduinoDevicev2, error)
3535
DeviceShow(id string) (*iotclient.ArduinoDevicev2, error)
3636
DeviceOTA(id string, file *os.File, expireMins int) error
37+
DevicePassSet(id string) (*iotclient.ArduinoDevicev2Pass, error)
3738
DeviceTagsCreate(id string, tags map[string]string) error
3839
DeviceTagsDelete(id string, keys []string) error
3940
LoraFrequencyPlansList() ([]iotclient.ArduinoLorafreqplanv1, error)
@@ -105,6 +106,26 @@ func (cl *client) DeviceLoraCreate(name, serial, devType, eui, freq string) (*io
105106
return &dev, nil
106107
}
107108

109+
// DevicePassSet sets the device password to the one suggested by Arduino IoT Cloud.
110+
// Returns the set password.
111+
func (cl *client) DevicePassSet(id string) (*iotclient.ArduinoDevicev2Pass, error) {
112+
// Fetch suggested password
113+
opts := &iotclient.DevicesV2PassGetOpts{SuggestedPassword: optional.NewBool(true)}
114+
pass, _, err := cl.api.DevicesV2PassApi.DevicesV2PassGet(cl.ctx, id, opts)
115+
if err != nil {
116+
err = fmt.Errorf("fetching device suggested password: %w", errorDetail(err))
117+
return nil, err
118+
}
119+
// Set password to the suggested one
120+
p := iotclient.Devicev2Pass{Password: pass.SuggestedPassword}
121+
pass, _, err = cl.api.DevicesV2PassApi.DevicesV2PassSet(cl.ctx, id, p)
122+
if err != nil {
123+
err = fmt.Errorf("setting device password: %w", errorDetail(err))
124+
return nil, err
125+
}
126+
return &pass, nil
127+
}
128+
108129
// DeviceDelete deletes the device corresponding to the passed ID
109130
// from Arduino IoT Cloud.
110131
func (cl *client) DeviceDelete(id string) error {

internal/iot/mocks/Client.go

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)