Skip to content

[pluggable monitor] Add monitor --describe command #1493

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
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
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