Skip to content

Commit

Permalink
Allow port, protocol and port settings to be specified in profiles. (#…
Browse files Browse the repository at this point in the history
…2717)

* Allow port/protocol keys in sketch profile

* Allow port/protocol in profiles

* Added port settings in sketch profile

* Allow port configuration from sketch profile

* Added docs

* fix: Moved port-from-profile logic in args.Port.GetPort(...)

This allows to implement the selection logic on more commands.

* Fixed FQBN selection logic in monitor/arg command

* Fixed incorrect tests

The previous fixes now let the CLI to produce the correct output.
  • Loading branch information
cmaglie authored Oct 7, 2024
1 parent 863c1ec commit 24bd145
Show file tree
Hide file tree
Showing 22 changed files with 653 additions and 339 deletions.
26 changes: 24 additions & 2 deletions docs/sketch-project-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Each profile will define:
- A possible core platform name and version, that is a dependency of the target core platform (with the 3rd party
platform index URL if needed)
- The libraries used in the sketch (including their version)
- The port and protocol to upload the sketch and monitor the board

The format of the file is the following:

Expand All @@ -33,7 +34,11 @@ profiles:
- <LIB_NAME> (<LIB_VERSION>)
- <LIB_NAME> (<LIB_VERSION>)
- <LIB_NAME> (<LIB_VERSION>)
port: <PORT_NAME>
port_config:
<PORT_SETTING_NAME>: <PORT_SETTING_VALUE>
...
protocol: <PORT_PROTOCOL>
...more profiles here...
```

Expand All @@ -54,6 +59,15 @@ otherwise below). The available fields are:
- `<USER_NOTES>` is a free text string available to the developer to add comments. This field is optional.
- `<PROGRAMMER>` is the programmer that will be used. This field is optional.

The following fields are available since Arduino CLI 1.1.0:

- `<PORT_NAME>` is the port that will be used to upload and monitor the board (unless explicitly set otherwise). This
field is optional.
- `port_config` section with `<PORT_SETTING_NAME>` and `<PORT_SETTING_VALUE>` defines the port settings that will be
used in the `monitor` command. Typically is used to set the baudrate for the serial port (for example
`baudrate: 115200`) but any setting/value can be specified. Multiple settings can be set. These fields are optional.
- `<PORT_PROTOCOL>` is the protocol for the port used to upload and monitor the board. This field is optional.

A complete example of a sketch project file may be the following:

```
Expand All @@ -76,6 +90,9 @@ profiles:
- VitconMQTT (1.0.1)
- Arduino_ConnectionHandler (0.6.4)
- TinyDHT sensor library (1.1.0)
port: /dev/ttyACM0
port_config:
baudrate: 115200
tiny:
notes: testing the very limit of the AVR platform, it will be very unstable
Expand Down Expand Up @@ -139,6 +156,8 @@ particular:
- The `default_fqbn` key sets the default value for the `--fqbn` flag
- The `default_programmer` key sets the default value for the `--programmer` flag
- The `default_port` key sets the default value for the `--port` flag
- The `default_port_config` key sets the default values for the `--config` flag in the `monitor` command (available
since Arduino CLI 1.1.0)
- The `default_protocol` key sets the default value for the `--protocol` flag
- The `default_profile` key sets the default value for the `--profile` flag

Expand All @@ -148,11 +167,14 @@ For example:
default_fqbn: arduino:samd:mkr1000
default_programmer: atmel_ice
default_port: /dev/ttyACM0
default_port_config:
baudrate: 115200
default_protocol: serial
default_profile: myprofile
```

With this configuration set, it is not necessary to specify the `--fqbn`, `--programmer`, `--port`, `--protocol` or
`--profile` flags to the [`arduino-cli compile`](commands/arduino-cli_compile.md),
[`arduino-cli upload`](commands/arduino-cli_upload.md) or [`arduino-cli debug`](commands/arduino-cli_debug.md) commands
when compiling, uploading or debugging the sketch.
when compiling, uploading or debugging the sketch. Moreover in the `monitor` command it is not necessary to specify the
`--config baudrate=115200` to communicate with the monitor port of the board.
49 changes: 43 additions & 6 deletions internal/arduino/sketch/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ import (

// projectRaw is a support struct used only to unmarshal the yaml
type projectRaw struct {
ProfilesRaw yaml.Node `yaml:"profiles"`
DefaultProfile string `yaml:"default_profile"`
DefaultFqbn string `yaml:"default_fqbn"`
DefaultPort string `yaml:"default_port,omitempty"`
DefaultProtocol string `yaml:"default_protocol,omitempty"`
DefaultProgrammer string `yaml:"default_programmer,omitempty"`
ProfilesRaw yaml.Node `yaml:"profiles"`
DefaultProfile string `yaml:"default_profile"`
DefaultFqbn string `yaml:"default_fqbn"`
DefaultPort string `yaml:"default_port,omitempty"`
DefaultPortConfig map[string]string `yaml:"default_port_config,omitempty"`
DefaultProtocol string `yaml:"default_protocol,omitempty"`
DefaultProgrammer string `yaml:"default_programmer,omitempty"`
}

// Project represents the sketch project file
Expand All @@ -48,6 +49,7 @@ type Project struct {
DefaultProfile string
DefaultFqbn string
DefaultPort string
DefaultPortConfig map[string]string
DefaultProtocol string
DefaultProgrammer string
}
Expand All @@ -70,6 +72,12 @@ func (p *Project) AsYaml() string {
if p.DefaultPort != "" {
res += fmt.Sprintf("default_port: %s\n", p.DefaultPort)
}
if len(p.DefaultPortConfig) > 0 {
res += "default_port_config:\n"
for k, v := range p.DefaultPortConfig {
res += fmt.Sprintf(" %s: %s\n", k, v)
}
}
if p.DefaultProtocol != "" {
res += fmt.Sprintf("default_protocol: %s\n", p.DefaultProtocol)
}
Expand Down Expand Up @@ -103,17 +111,33 @@ type Profile struct {
Name string
Notes string `yaml:"notes"`
FQBN string `yaml:"fqbn"`
Port string `yaml:"port"`
PortConfig map[string]string `yaml:"port_config"`
Protocol string `yaml:"protocol"`
Programmer string `yaml:"programmer"`
Platforms ProfileRequiredPlatforms `yaml:"platforms"`
Libraries ProfileRequiredLibraries `yaml:"libraries"`
}

// ToRpc converts this Profile to an rpc.SketchProfile
func (p *Profile) ToRpc() *rpc.SketchProfile {
var portConfig *rpc.MonitorPortConfiguration
if len(p.PortConfig) > 0 {
portConfig = &rpc.MonitorPortConfiguration{}
for k, v := range p.PortConfig {
portConfig.Settings = append(portConfig.Settings, &rpc.MonitorPortSetting{
SettingId: k,
Value: v,
})
}
}
return &rpc.SketchProfile{
Name: p.Name,
Fqbn: p.FQBN,
Programmer: p.Programmer,
Port: p.Port,
PortConfig: portConfig,
Protocol: p.Protocol,
}
}

Expand All @@ -127,6 +151,18 @@ func (p *Profile) AsYaml() string {
if p.Programmer != "" {
res += fmt.Sprintf(" programmer: %s\n", p.Programmer)
}
if p.Port != "" {
res += fmt.Sprintf(" port: %s\n", p.Port)
}
if p.Protocol != "" {
res += fmt.Sprintf(" protocol: %s\n", p.Protocol)
}
if len(p.PortConfig) > 0 {
res += " port_config:\n"
for k, v := range p.PortConfig {
res += fmt.Sprintf(" %s: %s\n", k, v)
}
}
res += p.Platforms.AsYaml()
res += p.Libraries.AsYaml()
return res
Expand Down Expand Up @@ -291,6 +327,7 @@ func LoadProjectFile(file *paths.Path) (*Project, error) {
DefaultProfile: raw.DefaultProfile,
DefaultFqbn: raw.DefaultFqbn,
DefaultPort: raw.DefaultPort,
DefaultPortConfig: raw.DefaultPortConfig,
DefaultProtocol: raw.DefaultProtocol,
DefaultProgrammer: raw.DefaultProgrammer,
}, nil
Expand Down
11 changes: 11 additions & 0 deletions internal/arduino/sketch/sketch.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ func (s *Sketch) Hash() string {
// ToRpc converts this Sketch into a rpc.LoadSketchResponse
func (s *Sketch) ToRpc() *rpc.Sketch {
defaultPort, defaultProtocol := s.GetDefaultPortAddressAndProtocol()
var defaultPortConfig *rpc.MonitorPortConfiguration
if len(s.Project.DefaultPortConfig) > 0 {
defaultPortConfig = &rpc.MonitorPortConfiguration{}
for k, v := range s.Project.DefaultPortConfig {
defaultPortConfig.Settings = append(defaultPortConfig.Settings, &rpc.MonitorPortSetting{
SettingId: k,
Value: v,
})
}
}
res := &rpc.Sketch{
MainFile: s.MainFile.String(),
LocationPath: s.FullPath.String(),
Expand All @@ -297,6 +307,7 @@ func (s *Sketch) ToRpc() *rpc.Sketch {
RootFolderFiles: s.RootFolderFiles.AsStrings(),
DefaultFqbn: s.GetDefaultFQBN(),
DefaultPort: defaultPort,
DefaultPortConfig: defaultPortConfig,
DefaultProtocol: defaultProtocol,
DefaultProgrammer: s.GetDefaultProgrammer(),
Profiles: f.Map(s.Project.Profiles, (*Profile).ToRpc),
Expand Down
8 changes: 6 additions & 2 deletions internal/cli/arguments/fqbn.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ func (f *Fqbn) Set(fqbn string) {
// parameters provided by the user.
// This determine the FQBN based on:
// - the value of the FQBN flag if explicitly specified, otherwise
// - the FQBN of the selected profile if available, otherwise
// - the default FQBN value in sketch.yaml (`default_fqbn` key) if available, otherwise
// - it tries to autodetect the board connected to the given port flags
// If all above methods fails, it returns the empty string.
// The Port metadata are always returned except if:
// - the port is not found, in this case nil is returned
// - the FQBN autodetection fail, in this case the function prints an error and
// terminates the execution
func CalculateFQBNAndPort(ctx context.Context, portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultFQBN, defaultAddress, defaultProtocol string) (string, *rpc.Port) {
func CalculateFQBNAndPort(ctx context.Context, portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultFQBN, defaultAddress, defaultProtocol string, profile *rpc.SketchProfile) (string, *rpc.Port) {
fqbn := fqbnArg.String()
if fqbn == "" {
fqbn = profile.GetFqbn()
}
if fqbn == "" {
fqbn = defaultFQBN
}
Expand All @@ -87,7 +91,7 @@ func CalculateFQBNAndPort(ctx context.Context, portArgs *Port, fqbnArg *Fqbn, in
return fqbn, port
}

port, err := portArgs.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol)
port, err := portArgs.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol, profile)
if err != nil {
feedback.Fatal(i18n.Tr("Error getting port metadata: %v", err), feedback.ErrGeneric)
}
Expand Down
12 changes: 9 additions & 3 deletions internal/cli/arguments/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ func (p *Port) AddToCommand(cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer
// This method allows will bypass the discoveries if:
// - a nil instance is passed: in this case the plain port and protocol arguments are returned (even if empty)
// - a protocol is specified: in this case the discoveries are not needed to autodetect the protocol.
func (p *Port) GetPortAddressAndProtocol(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string) (string, string, error) {
func (p *Port) GetPortAddressAndProtocol(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string, profile *rpc.SketchProfile) (string, string, error) {
if p.protocol != "" || instance == nil {
return p.address, p.protocol, nil
}

port, err := p.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol)
port, err := p.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol, profile)
if err != nil {
return "", "", err
}
Expand All @@ -71,7 +71,13 @@ func (p *Port) GetPortAddressAndProtocol(ctx context.Context, instance *rpc.Inst

// GetPort returns the Port obtained by parsing command line arguments.
// The extra metadata for the ports is obtained using the pluggable discoveries.
func (p *Port) GetPort(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string) (*rpc.Port, error) {
func (p *Port) GetPort(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string, profile *rpc.SketchProfile) (*rpc.Port, error) {
if profile.GetPort() != "" {
defaultAddress = profile.GetPort()
}
if profile.GetProtocol() != "" {
defaultProtocol = profile.GetProtocol()
}
address := p.address
protocol := p.protocol
if address == "" && (defaultAddress != "" || defaultProtocol != "") {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/board/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func initAttachCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
func runAttachCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, path string, port *arguments.Port, fqbn string, programmer *arguments.Programmer) {
sketchPath := arguments.InitSketchPath(path)

portAddress, portProtocol, _ := port.GetPortAddressAndProtocol(ctx, nil, srv, "", "")
portAddress, portProtocol, _ := port.GetPortAddressAndProtocol(ctx, nil, srv, "", "", nil)
newDefaults, err := srv.SetSketchDefaults(ctx, &rpc.SetSketchDefaultsRequest{
SketchPath: sketchPath.String(),
DefaultFqbn: fqbn,
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/burnbootloader/burnbootloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func runBootloaderCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer)
logrus.Info("Executing `arduino-cli burn-bootloader`")

// We don't need a Sketch to upload a board's bootloader
discoveryPort, err := port.GetPort(ctx, instance, srv, "", "")
discoveryPort, err := port.GetPort(ctx, instance, srv, "", "", nil)
if err != nil {
feedback.Fatal(i18n.Tr("Error during Upload: %v", err), feedback.ErrGeneric)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func runCompileCommand(cmd *cobra.Command, args []string, srv rpc.ArduinoCoreSer
fqbnArg.Set(profile.GetFqbn())
}

fqbn, port := arguments.CalculateFQBNAndPort(ctx, &portArgs, &fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
fqbn, port := arguments.CalculateFQBNAndPort(ctx, &portArgs, &fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol(), profile)

if keysKeychain != "" || signKey != "" || encryptKey != "" {
arguments.CheckFlagsMandatory(cmd, "keys-keychain", "sign-key", "encrypt-key")
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/debug/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func runDebugCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args
fqbnArg.Set(profile.GetFqbn())
}

fqbn, port := arguments.CalculateFQBNAndPort(ctx, portArgs, fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
fqbn, port := arguments.CalculateFQBNAndPort(ctx, portArgs, fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol(), profile)

prog := profile.GetProgrammer()
if prog == "" || programmer.GetProgrammer() != "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/debug/debug_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func runDebugCheckCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer,
instance := instance.CreateAndInit(ctx, srv)
logrus.Info("Executing `arduino-cli debug`")

port, err := portArgs.GetPort(ctx, instance, srv, "", "")
port, err := portArgs.GetPort(ctx, instance, srv, "", "", nil)
if err != nil {
feedback.FatalError(err, feedback.ErrBadArgument)
}
Expand Down
Loading

0 comments on commit 24bd145

Please sign in to comment.