Skip to content

Commit

Permalink
Add monitor --describe command (arduino#1493)
Browse files Browse the repository at this point in the history
* Resolve monitors from platform.txt

* Implementation of monitor --describe command

* fix i18n
  • Loading branch information
cmaglie authored Oct 5, 2021
1 parent 802917d commit 73b8aa0
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 50 deletions.
1 change: 1 addition & 0 deletions arduino/cores/cores.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type PlatformRelease struct {
IsIDEBundled bool `json:"-"`
IsTrusted bool `json:"-"`
PluggableDiscoveryAware bool `json:"-"` // true if the Platform supports pluggable discovery (no compatibility layer required)
Monitors map[string]*MonitorDependency
}

// BoardManifest contains information about a board. These metadata are usually
Expand Down
14 changes: 14 additions & 0 deletions arduino/cores/packagemanager/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
} else {
platform.Properties.Set("pluggable_discovery.required.0", "builtin:serial-discovery")
platform.Properties.Set("pluggable_discovery.required.1", "builtin:mdns-discovery")
platform.Properties.Set("pluggable_monitor.required.serial", "builtin:serial-monitor")
}

if platform.Platform.Name == "" {
Expand Down Expand Up @@ -359,6 +360,19 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
if !platform.PluggableDiscoveryAware {
convertLegacyPlatformToPluggableDiscovery(platform)
}

// Build pluggable monitor references
platform.Monitors = map[string]*cores.MonitorDependency{}
for protocol, ref := range platform.Properties.SubTree("pluggable_monitor.required").AsMap() {
split := strings.Split(ref, ":")
if len(split) != 2 {
return fmt.Errorf(tr("invalid pluggable monitor reference: %s"), ref)
}
platform.Monitors[protocol] = &cores.MonitorDependency{
Packager: split[0],
Name: split[1],
}
}
return nil
}

Expand Down
2 changes: 2 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/arduino/arduino-cli/cli/generatedocs"
"github.com/arduino/arduino-cli/cli/globals"
"github.com/arduino/arduino-cli/cli/lib"
"github.com/arduino/arduino-cli/cli/monitor"
"github.com/arduino/arduino-cli/cli/outdated"
"github.com/arduino/arduino-cli/cli/output"
"github.com/arduino/arduino-cli/cli/sketch"
Expand Down Expand Up @@ -93,6 +94,7 @@ func createCliCommandTree(cmd *cobra.Command) {
cmd.AddCommand(daemon.NewCommand())
cmd.AddCommand(generatedocs.NewCommand())
cmd.AddCommand(lib.NewCommand())
cmd.AddCommand(monitor.NewCommand())
cmd.AddCommand(outdated.NewCommand())
cmd.AddCommand(sketch.NewCommand())
cmd.AddCommand(update.NewCommand())
Expand Down
105 changes: 105 additions & 0 deletions cli/monitor/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package monitor

import (
"context"
"os"
"sort"
"strings"

"github.com/arduino/arduino-cli/cli/arguments"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands/monitor"
"github.com/arduino/arduino-cli/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cli/table"
"github.com/fatih/color"
"github.com/spf13/cobra"
)

var tr = i18n.Tr

var portArgs arguments.Port
var describe bool

// NewCommand created a new `monitor` command
func NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "monitor",
Short: tr("Open a communication port with a board."),
Long: tr("Open a communication port with a board."),
Example: "" +
" " + os.Args[0] + " monitor -p /dev/ttyACM0\n" +
" " + os.Args[0] + " monitor -p /dev/ttyACM0 --describe",
Run: runMonitorCmd,
}
portArgs.AddToCommand(cmd)
cmd.Flags().BoolVar(&describe, "describe", false, tr("Show all the settings of the communication port."))
cmd.MarkFlagRequired("port")
return cmd
}

func runMonitorCmd(cmd *cobra.Command, args []string) {
instance := instance.CreateAndInit()

port, err := portArgs.GetPort(instance, nil)
if err != nil {
feedback.Error(err)
os.Exit(errorcodes.ErrGeneric)
}

if describe {
res, err := monitor.EnumerateMonitorPortSettings(context.Background(), &rpc.EnumerateMonitorPortSettingsRequest{
Instance: instance,
Port: port.ToRPC(),
Fqbn: "",
})
if err != nil {
feedback.Error(tr("Error getting port settings details: %s"), err)
os.Exit(errorcodes.ErrGeneric)
}
feedback.PrintResult(&detailsResult{Settings: res.Settings})
return
}

feedback.Error("Monitor functionality not yet implemented")
os.Exit(errorcodes.ErrGeneric)
}

type detailsResult struct {
Settings []*rpc.MonitorPortSettingDescriptor `json:"settings"`
}

func (r *detailsResult) Data() interface{} {
return r
}

func (r *detailsResult) String() string {
t := table.New()
green := color.New(color.FgGreen)
t.SetHeader(tr("ID"), tr("Setting"), tr("Default"), tr("Values"))
sort.Slice(r.Settings, func(i, j int) bool {
return r.Settings[i].Label < r.Settings[j].Label
})
for _, setting := range r.Settings {
values := strings.Join(setting.EnumValues, ", ")
t.AddRow(setting.SettingId, setting.Label, table.NewCell(setting.Value, green), values)
}
return t.Render()
}
6 changes: 4 additions & 2 deletions commands/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/arduino/arduino-cli/commands/compile"
"github.com/arduino/arduino-cli/commands/core"
"github.com/arduino/arduino-cli/commands/lib"
"github.com/arduino/arduino-cli/commands/monitor"
"github.com/arduino/arduino-cli/commands/sketch"
"github.com/arduino/arduino-cli/commands/upload"
"github.com/arduino/arduino-cli/i18n"
Expand Down Expand Up @@ -467,8 +468,9 @@ func (s *ArduinoCoreServerImpl) GitLibraryInstall(req *rpc.GitLibraryInstallRequ
}

// EnumerateMonitorPortSettings FIXMEDOC
func (s *ArduinoCoreServerImpl) EnumerateMonitorPortSettings(context.Context, *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
return nil, status.New(codes.Unimplemented, "Not implemented").Err()
func (s *ArduinoCoreServerImpl) EnumerateMonitorPortSettings(ctx context.Context, req *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
resp, err := monitor.EnumerateMonitorPortSettings(ctx, req)
return resp, convertErrorToRPCStatus(err)
}

// Monitor FIXMEDOC
Expand Down
63 changes: 63 additions & 0 deletions commands/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,32 @@ func (e *MissingPortProtocolError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// MissingPortError is returned when the port is mandatory and not specified
type MissingPortError struct{}

func (e *MissingPortError) Error() string {
return tr("Missing port")
}

// ToRPCStatus converts the error into a *status.Status
func (e *MissingPortError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// NoMonitorAvailableForProtocolError is returned when a monitor for the specified port protocol is not available
type NoMonitorAvailableForProtocolError struct {
Protocol string
}

func (e *NoMonitorAvailableForProtocolError) Error() string {
return tr("No monitor available for the port protocol %s", e.Protocol)
}

// ToRPCStatus converts the error into a *status.Status
func (e *NoMonitorAvailableForProtocolError) ToRPCStatus() *status.Status {
return status.New(codes.InvalidArgument, e.Error())
}

// MissingProgrammerError is returned when the programmer is mandatory and not specified
type MissingProgrammerError struct{}

Expand Down Expand Up @@ -208,6 +234,25 @@ func (e *ProgrammerNotFoundError) ToRPCStatus() *status.Status {
return status.New(codes.NotFound, e.Error())
}

// MonitorNotFoundError is returned when the pluggable monitor is not found
type MonitorNotFoundError struct {
Monitor string
Cause error
}

func (e *MonitorNotFoundError) Error() string {
return composeErrorMsg(tr("Monitor '%s' not found", e.Monitor), e.Cause)
}

func (e *MonitorNotFoundError) Unwrap() error {
return e.Cause
}

// ToRPCStatus converts the error into a *status.Status
func (e *MonitorNotFoundError) ToRPCStatus() *status.Status {
return status.New(codes.NotFound, e.Error())
}

// InvalidPlatformPropertyError is returned when a property in the platform is not valid
type InvalidPlatformPropertyError struct {
Property string
Expand Down Expand Up @@ -454,6 +499,24 @@ func (e *FailedDebugError) ToRPCStatus() *status.Status {
return status.New(codes.Internal, e.Error())
}

// FailedMonitorError is returned when opening the monitor port of a board fails
type FailedMonitorError struct {
Cause error
}

func (e *FailedMonitorError) Error() string {
return composeErrorMsg(tr("Port monitor error"), e.Cause)
}

func (e *FailedMonitorError) Unwrap() error {
return e.Cause
}

// ToRPCStatus converts the error into a *status.Status
func (e *FailedMonitorError) ToRPCStatus() *status.Status {
return status.New(codes.Internal, e.Error())
}

// CompileFailedError is returned when the compile fails
type CompileFailedError struct {
Message string
Expand Down
57 changes: 57 additions & 0 deletions commands/monitor/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package monitor

import (
"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/commands"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

func findMonitorForProtocolAndBoard(pm *packagemanager.PackageManager, port *rpc.Port, fqbn string) (*cores.MonitorDependency, error) {
if port == nil {
return nil, &commands.MissingPortError{}
}
protocol := port.GetProtocol()
if protocol == "" {
return nil, &commands.MissingPortProtocolError{}
}

// If a board is specified search the monitor in the board package first
if fqbn != "" {
fqbn, err := cores.ParseFQBN(fqbn)
if err != nil {
return nil, &commands.InvalidFQBNError{Cause: err}
}

_, boardPlatform, _, _, _, err := pm.ResolveFQBN(fqbn)
if err != nil {
return nil, &commands.UnknownFQBNError{Cause: err}
}
if mon, ok := boardPlatform.Monitors[protocol]; ok {
return mon, nil
}
}

// Otherwise look in all package for a suitable monitor
for _, platformRel := range pm.InstalledPlatformReleases() {
if mon, ok := platformRel.Monitors[protocol]; ok {
return mon, nil
}
}
return nil, &commands.NoMonitorAvailableForProtocolError{Protocol: protocol}
}
69 changes: 69 additions & 0 deletions commands/monitor/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package monitor

import (
"context"

pluggableMonitor "github.com/arduino/arduino-cli/arduino/monitor"
"github.com/arduino/arduino-cli/commands"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

// EnumerateMonitorPortSettings returns a description of the configuration settings of a monitor port
func EnumerateMonitorPortSettings(ctx context.Context, req *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
pm := commands.GetPackageManager(req.GetInstance().GetId())
if pm == nil {
return nil, &commands.InvalidInstanceError{}
}

monitorRef, err := findMonitorForProtocolAndBoard(pm, req.GetPort(), req.GetFqbn())
if err != nil {
return nil, err
}

tool := pm.FindMonitorDependency(monitorRef)
if tool == nil {
return nil, &commands.MonitorNotFoundError{Monitor: monitorRef.String()}
}

m := pluggableMonitor.New(monitorRef.Name, tool.InstallDir.Join(monitorRef.Name).String())

if err := m.Run(); err != nil {
return nil, &commands.FailedMonitorError{Cause: err}
}
defer m.Quit()

desc, err := m.Describe()
if err != nil {
return nil, &commands.FailedMonitorError{Cause: err}
}
return &rpc.EnumerateMonitorPortSettingsResponse{Settings: convert(desc)}, nil
}

func convert(desc *pluggableMonitor.PortDescriptor) []*rpc.MonitorPortSettingDescriptor {
res := []*rpc.MonitorPortSettingDescriptor{}
for settingID, descriptor := range desc.ConfigurationParameters {
res = append(res, &rpc.MonitorPortSettingDescriptor{
SettingId: settingID,
Label: descriptor.Label,
Type: descriptor.Type,
EnumValues: descriptor.Values,
Value: descriptor.Selected,
})
}
return res
}
Loading

0 comments on commit 73b8aa0

Please sign in to comment.