Skip to content

Allow port, protocol and port settings to be specified in profiles. #2717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading