Improve CLI

This commit is contained in:
AntoineH 2024-09-05 15:39:17 +02:00
parent 583f5e2933
commit d3691b10db
10 changed files with 72 additions and 58 deletions

View file

@ -11,9 +11,9 @@ In root directory, call `make` to generate binary file. To clean up, call `make
```bash ```bash
./motorsim -c ./cfg/motors.yaml motor --id [ID] serve ./motorsim -c ./cfg/motors.yaml motor --id [ID] serve
``` ```
2. Start Motor controller server (Beware, controllers commands except Port 8080): 2. Start Motor controller server (Beware, if modifying port controllers commands except Port 8080):
```bash ```bash
./motorsim -c ./cfg/motors.yaml controller serve -p 8080 ./motorsim -c ./cfg/motors.yaml controller serve
``` ```
### Client usage ### Client usage
#### Motor controller commands #### Motor controller commands
@ -21,9 +21,9 @@ In root directory, call `make` to generate binary file. To clean up, call `make
```bash ```bash
./motorsim -c ./cfg/motors.yaml controller getJoints ./motorsim -c ./cfg/motors.yaml controller getJoints
``` ```
- Set joints values (Beware, currently set the same values for all joints): - Set joints values:
```bash ```bash
./motorsim -c ./cfg/motors.yaml controller setJoints --j [Joint goals] ./motorsim -c ./cfg/motors.yaml controller setJoints [Joint goals]
``` ```
#### Motor commands #### Motor commands
Every command should be provided with valid ID from [config file](./cfg/motors.yaml): Every command should be provided with valid ID from [config file](./cfg/motors.yaml):
@ -33,7 +33,7 @@ Every command should be provided with valid ID from [config file](./cfg/motors.y
``` ```
- Set motor velocity: - Set motor velocity:
```bash ```bash
./motorsim -c ./cfg/motors.yaml motor --id [ID] moveVel --vel [Velocity] ./motorsim -c ./cfg/motors.yaml motor --id [ID] moveVel -- [Velocity]
``` ```
## TODO ## TODO
@ -42,4 +42,4 @@ Every command should be provided with valid ID from [config file](./cfg/motors.y
- [ ] Use coroutines for gRPC services callbacks. - [ ] Use coroutines for gRPC services callbacks.
- [ ] Fix get Motor State service (Behavior not robust). - [ ] Fix get Motor State service (Behavior not robust).
- [ ] Fix Controller SetJoint command CLI omitting multiples input values. - [ ] Fix Controller SetJoint command CLI omitting multiples input values.
- [ ] Complete CLI help messages. - [ ] Send error responses to clients of gRPC services.

View file

@ -23,10 +23,10 @@ var(
// controllerCmd represents the controller command // controllerCmd represents the controller command
var controllerCmd = &cobra.Command{ var controllerCmd = &cobra.Command{
Use: "controller", Use: "controller",
Short: "controller command description", Short: "Motor controller command",
Long: `controller command description`, Long: `Motor controller command`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("controller called") cmd.Help() //Nothing to do, show help
}, },
} }

View file

@ -22,10 +22,9 @@ import (
// getJointsCmd represents the getJoints command // getJointsCmd represents the getJoints command
var getJointsCmd = &cobra.Command{ var getJointsCmd = &cobra.Command{
Use: "getJoints", Use: "getJoints",
Short: "getJoints command descritpion", Short: "Retreive motor positions",
Long: `getJoints command descritpion`, Long: `Retreive the positions of the motor controlled by Motor Controller server`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// fmt.Println("getJoints called")
GetJoints() GetJoints()
}, },
} }

View file

@ -30,8 +30,8 @@ var (
// serveCmd represents the serve command // serveCmd represents the serve command
var serveCmd = &cobra.Command{ var serveCmd = &cobra.Command{
Use: "serve", Use: "serve",
Short: "serve command descritpion", Short: "Start Motor Controller server",
Long: `serve command descritpion`, Long: `Start Motor Controller server`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("serve called with: Port ", port) fmt.Println("serve called with: Port ", port)
fmt.Println(cmd.CommandPath()) fmt.Println(cmd.CommandPath())
@ -66,7 +66,7 @@ type server struct {
func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, error) { func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, error) {
//TODO: use as a coroutine to prevent blocking the main thread //TODO: use as a coroutine to prevent blocking the main thread
var( var(
tgt_angles = []float64{in.GetAngles()[0], in.GetAngles()[0], in.GetAngles()[0]} //TODO: Replace by in.GetAngles() tgt_angles = in.GetAngles() //TODO: Replace by in.GetAngles()
cmd_vel = make([]float64, len(tgt_angles)) //Commanded velocities cmd_vel = make([]float64, len(tgt_angles)) //Commanded velocities
motor_pos = []float64{} //Current motor positions motor_pos = []float64{} //Current motor positions
max_vels = []float64{} //Maximum velocities max_vels = []float64{} //Maximum velocities
@ -78,7 +78,7 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
) )
//Get motor states //Get motor states
for i := range tgt_angles { for i := range motor_configs {
//TODO: Use a coroutine to avoid blocking the main thread //TODO: Use a coroutine to avoid blocking the main thread
motor_state := getMotorState(i) //Get current motor state motor_state := getMotorState(i) //Get current motor state
motor_pos = append(motor_pos, motor_state.Angle) motor_pos = append(motor_pos, motor_state.Angle)
@ -92,6 +92,9 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
// log.Printf("t: %v - %v", slices.Min(traj_times), traj_times) // log.Printf("t: %v - %v", slices.Min(traj_times), traj_times)
if controller_error ==""{ //If no error, check for valid target angles if controller_error ==""{ //If no error, check for valid target angles
if len(tgt_angles)> len(motor_configs){ //Check size
controller_error = "InvalidRequestSize"
} else { //Check angles limit
for i, angle := range tgt_angles { for i, angle := range tgt_angles {
if angle > motor_configs[i].Max_pos || angle < motor_configs[i].Min_pos{ if angle > motor_configs[i].Max_pos || angle < motor_configs[i].Min_pos{
controller_error = "InvalidTargetAngles" controller_error = "InvalidTargetAngles"
@ -99,6 +102,7 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
} }
} }
} }
}
//Compute velocities //Compute velocities
if controller_error ==""{ //If no error, compute velocities if controller_error ==""{ //If no error, compute velocities
@ -136,6 +140,9 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
case "MotorFault": case "MotorFault":
log.Printf("ERROR - Motor error preventing command: %v", motor_errors) log.Printf("ERROR - Motor error preventing command: %v", motor_errors)
return &pb.Angles{Angles: motor_pos}, nil //Return motor positions TODO: Return error message return &pb.Angles{Angles: motor_pos}, nil //Return motor positions TODO: Return error message
case "InvalidRequestSize":
log.Printf("ERROR - Request size [%d] is invalid. Expected size [%d]", len(tgt_angles), len(motor_configs))
return &pb.Angles{Angles: motor_pos}, nil
case "InvalidTargetAngles": case "InvalidTargetAngles":
log.Print("ERROR - out of limits:") log.Print("ERROR - out of limits:")
for i, tgt := range tgt_angles{ for i, tgt := range tgt_angles{
@ -191,7 +198,7 @@ func setMotorVel(idx int, vel float64){
//TODO: Fix compiling issue with google.protobuf.Empty message //TODO: Fix compiling issue with google.protobuf.Empty message
func (s *server) GetJoints(ctx context.Context, in *pb.Empty) (*pb.Angles, error) { func (s *server) GetJoints(ctx context.Context, in *pb.Empty) (*pb.Angles, error) {
//TODO: use a coroutines to prevent blocking the main thread //TODO: use a coroutines to prevent blocking the main thread and server crash in case of errors
var angles = []float64{} var angles = []float64{}
for i, _ := range motor_configs { for i, _ := range motor_configs {
angles = append(angles,getMotorState(i).Angle) angles = append(angles,getMotorState(i).Angle)

View file

@ -6,7 +6,7 @@ package controller
import ( import (
"fmt" "fmt"
"strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"context" "context"
@ -22,16 +22,21 @@ import (
// setJointsCmd represents the setJoints command // setJointsCmd represents the setJoints command
var setJointsCmd = &cobra.Command{ var setJointsCmd = &cobra.Command{
Use: "setJoints", Use: "setJoints",
Short: "setJoints command descritpion", Short: "Set target joint angles for the motors",
Long: `setJoints command descritpion`, Long: `Request the Motor Controller to command the motors to reach the target joint angles`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("setJoints called with target :", cmd.Flag("j").Value) //Parse target angles
tgt_angles, err:=cmd.Flags().GetFloat64Slice("j") var tgt_angles []float64
for _, arg := range args {
angle, err := strconv.ParseFloat(arg, 64)
if err!= nil{ if err!= nil{
log.Fatalf("Failed read requested velocities %v", err) log.Fatalf("Failed to parse joint angle %v", err)
}else{ }else{
SetJoints(tgt_angles) tgt_angles = append(tgt_angles, angle)
} }
}
//Set joint angles
SetJoints(tgt_angles)
}, },
} }
@ -46,10 +51,8 @@ func init() {
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
//TODO: Anonymous flag (with anonymous flag group ?) setJointsCmd.Flags().Float64Slice("", nil, "Target joint values") //Only used for help message
//TODO: Fix input of multiple values as slice // setJointsCmd.MarkFlagRequired("")
setJointsCmd.Flags().Float64Slice("j", nil, "Target joint values") //or IntSlice ?
setJointsCmd.MarkFlagRequired("j")
} }
//gRPC Client //gRPC Client
@ -70,5 +73,5 @@ func SetJoints(tgt_angles []float64){
if err != nil { if err != nil {
log.Fatalf("could not send: %v", err) log.Fatalf("could not send: %v", err)
} }
log.Printf("Received: %v", r.GetAngles()) log.Printf("Moving to (°): %v", r.GetAngles())
} }

View file

@ -22,10 +22,10 @@ import (
// listenCmd represents the listen command // listenCmd represents the listen command
var listenCmd = &cobra.Command{ var listenCmd = &cobra.Command{
Use: "listen", Use: "listen",
Short: "listen command descritpion", Short: "Listen motor state",
Long: `listen command descritpion`, Long: `Continuous retrieval of complete motor state from motor server`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("listen called with ID: ", cmd.Flag("id").Value) // fmt.Println("listen called with ID: ", cmd.Flag("id").Value)
updateConfig() updateConfig()
listen() //Blocking call listen() //Blocking call
}, },

View file

@ -39,10 +39,10 @@ var(
// motorCmd represents the motor command // motorCmd represents the motor command
var motorCmd = &cobra.Command{ var motorCmd = &cobra.Command{
Use: "motor", Use: "motor",
Short: "motor command descritpion", Short: "Motor command",
Long: `motor command descritpion`, Long: `Motor command`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("motor called with ID: ", cmd.Flag("id").Value) cmd.Help() //Nothing to do, show help
}, },
} }
@ -54,7 +54,7 @@ func init() {
// Cobra supports Persistent Flags which will work for this command // Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.: // and all subcommands, e.g.:
motorCmd.PersistentFlags().Uint16Var(&motorID, "id", 0, "Identifier number") motorCmd.PersistentFlags().Uint16Var(&motorID, "id", 0, "Identifier number")
motorCmd.MarkFlagRequired("id") motorCmd.MarkPersistentFlagRequired("id")
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:

View file

@ -6,6 +6,7 @@ package motor
import ( import (
"fmt" "fmt"
"strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -22,16 +23,20 @@ import (
// moveVelCmd represents the moveVel command // moveVelCmd represents the moveVel command
var moveVelCmd = &cobra.Command{ var moveVelCmd = &cobra.Command{
Use: "moveVel", Use: "moveVel",
Short: "moveVel command descritpion", Short: "Set velocity of motor",
Long: `moveVel command descritpion`, Long: `Request motor server to move at given velocity`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("moveVel called with: ID ", cmd.Flag("id").Value, " Vel ", cmd.Flag("vel").Value) //Parse target velocity
updateConfig() if len(args)!=1 {
var vel, err = cmd.Flags().GetFloat64("vel") log.Fatalf("Wrong number of arguments")
if err!= nil {
log.Fatalf("Failed read requested velocity %v", err)
}else{ }else{
moveVel(vel) vel, err := strconv.ParseFloat(args[0],64)
if err!= nil{
log.Fatalf("Failed to parse joint velocity %v", err)
} else {
updateConfig()
moveVel(vel) //Set motor velocity
}
} }
}, },
} }
@ -47,8 +52,8 @@ func init() {
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
//TODO: Anonymous flag (with anonymous flag group ?) moveVelCmd.Flags().Float64("", 0, "Velocity (degrees/s)") //Only used for help message
moveVelCmd.Flags().Float64("vel", 0, "Velocity (degrees/s)") // moveVelCmd.MarkFlagRequired("vel")
} }
//gRPC Client //gRPC Client
@ -68,5 +73,7 @@ func moveVel(cmd_vel float64){
_, err = c.SetVelocity(ctx, &pb.Velocity{Velocity: cmd_vel}) _, err = c.SetVelocity(ctx, &pb.Velocity{Velocity: cmd_vel})
if err != nil { if err != nil {
log.Fatalf("could not send: %v", err) log.Fatalf("could not send: %v", err)
} else {
log.Printf("Requested velocity [%f]°/s to motor %d", cmd_vel, curr_config.Id)
} }
} }

View file

@ -27,11 +27,9 @@ var(
// serveCmd represents the serve command // serveCmd represents the serve command
var serveCmd = &cobra.Command{ var serveCmd = &cobra.Command{
Use: "serve", Use: "serve",
Short: "serve command descritpion", Short: "Start motor server",
Long: `serve command descritpion`, Long: `Start motor server`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
fmt.Println("serve called with ID ", cmd.Flag("id").Value)
// fmt.Println(cmd.CommandPath())
updateConfig() updateConfig()
init_sim() init_sim()
serve() serve()

View file

@ -42,12 +42,12 @@ func init() {
// will be global for your application. // will be global for your application.
RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.cobra.yaml)") RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.cobra.yaml)")
//RootCmd.MarkFlagRequired("config") RootCmd.MarkPersistentFlagRequired("config")
// viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config")) // viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config"))
// Cobra also supports local flags, which will only run // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.