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
./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
./motorsim -c ./cfg/motors.yaml controller serve -p 8080
./motorsim -c ./cfg/motors.yaml controller serve
```
### Client usage
#### Motor controller commands
@ -21,9 +21,9 @@ In root directory, call `make` to generate binary file. To clean up, call `make
```bash
./motorsim -c ./cfg/motors.yaml controller getJoints
```
- Set joints values (Beware, currently set the same values for all joints):
- Set joints values:
```bash
./motorsim -c ./cfg/motors.yaml controller setJoints --j [Joint goals]
./motorsim -c ./cfg/motors.yaml controller setJoints [Joint goals]
```
#### Motor commands
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:
```bash
./motorsim -c ./cfg/motors.yaml motor --id [ID] moveVel --vel [Velocity]
./motorsim -c ./cfg/motors.yaml motor --id [ID] moveVel -- [Velocity]
```
## 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.
- [ ] Fix get Motor State service (Behavior not robust).
- [ ] 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
var controllerCmd = &cobra.Command{
Use: "controller",
Short: "controller command description",
Long: `controller command description`,
Short: "Motor controller command",
Long: `Motor controller command`,
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
var getJointsCmd = &cobra.Command{
Use: "getJoints",
Short: "getJoints command descritpion",
Long: `getJoints command descritpion`,
Short: "Retreive motor positions",
Long: `Retreive the positions of the motor controlled by Motor Controller server`,
Run: func(cmd *cobra.Command, args []string) {
// fmt.Println("getJoints called")
GetJoints()
},
}

View file

@ -30,8 +30,8 @@ var (
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "serve command descritpion",
Long: `serve command descritpion`,
Short: "Start Motor Controller server",
Long: `Start Motor Controller server`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("serve called with: Port ", port)
fmt.Println(cmd.CommandPath())
@ -66,7 +66,7 @@ type server struct {
func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, error) {
//TODO: use as a coroutine to prevent blocking the main thread
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
motor_pos = []float64{} //Current motor positions
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
for i := range tgt_angles {
for i := range motor_configs {
//TODO: Use a coroutine to avoid blocking the main thread
motor_state := getMotorState(i) //Get current motor state
motor_pos = append(motor_pos, motor_state.Angle)
@ -92,10 +92,14 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
// log.Printf("t: %v - %v", slices.Min(traj_times), traj_times)
if controller_error ==""{ //If no error, check for valid target angles
for i, angle := range tgt_angles {
if angle > motor_configs[i].Max_pos || angle < motor_configs[i].Min_pos{
controller_error = "InvalidTargetAngles"
break
if len(tgt_angles)> len(motor_configs){ //Check size
controller_error = "InvalidRequestSize"
} else { //Check angles limit
for i, angle := range tgt_angles {
if angle > motor_configs[i].Max_pos || angle < motor_configs[i].Min_pos{
controller_error = "InvalidTargetAngles"
break
}
}
}
}
@ -136,6 +140,9 @@ func (s *server) SetJoints(ctx context.Context, in *pb.Angles) (*pb.Angles, erro
case "MotorFault":
log.Printf("ERROR - Motor error preventing command: %v", motor_errors)
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":
log.Print("ERROR - out of limits:")
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
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{}
for i, _ := range motor_configs {
angles = append(angles,getMotorState(i).Angle)

View file

@ -6,7 +6,7 @@ package controller
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"context"
@ -22,16 +22,21 @@ import (
// setJointsCmd represents the setJoints command
var setJointsCmd = &cobra.Command{
Use: "setJoints",
Short: "setJoints command descritpion",
Long: `setJoints command descritpion`,
Short: "Set target joint angles for the motors",
Long: `Request the Motor Controller to command the motors to reach the target joint angles`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("setJoints called with target :", cmd.Flag("j").Value)
tgt_angles, err:=cmd.Flags().GetFloat64Slice("j")
if err!= nil {
log.Fatalf("Failed read requested velocities %v", err)
}else{
SetJoints(tgt_angles)
//Parse target angles
var tgt_angles []float64
for _, arg := range args {
angle, err := strconv.ParseFloat(arg, 64)
if err!= nil{
log.Fatalf("Failed to parse joint angle %v", err)
}else{
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
// is called directly, e.g.:
//TODO: Anonymous flag (with anonymous flag group ?)
//TODO: Fix input of multiple values as slice
setJointsCmd.Flags().Float64Slice("j", nil, "Target joint values") //or IntSlice ?
setJointsCmd.MarkFlagRequired("j")
setJointsCmd.Flags().Float64Slice("", nil, "Target joint values") //Only used for help message
// setJointsCmd.MarkFlagRequired("")
}
//gRPC Client
@ -70,5 +73,5 @@ func SetJoints(tgt_angles []float64){
if err != nil {
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
var listenCmd = &cobra.Command{
Use: "listen",
Short: "listen command descritpion",
Long: `listen command descritpion`,
Short: "Listen motor state",
Long: `Continuous retrieval of complete motor state from motor server`,
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()
listen() //Blocking call
},

View file

@ -39,10 +39,10 @@ var(
// motorCmd represents the motor command
var motorCmd = &cobra.Command{
Use: "motor",
Short: "motor command descritpion",
Long: `motor command descritpion`,
Short: "Motor command",
Long: `Motor command`,
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
// and all subcommands, e.g.:
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
// is called directly, e.g.:

View file

@ -6,6 +6,7 @@ package motor
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
@ -22,16 +23,20 @@ import (
// moveVelCmd represents the moveVel command
var moveVelCmd = &cobra.Command{
Use: "moveVel",
Short: "moveVel command descritpion",
Long: `moveVel command descritpion`,
Short: "Set velocity of motor",
Long: `Request motor server to move at given velocity`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("moveVel called with: ID ", cmd.Flag("id").Value, " Vel ", cmd.Flag("vel").Value)
updateConfig()
var vel, err = cmd.Flags().GetFloat64("vel")
if err!= nil {
log.Fatalf("Failed read requested velocity %v", err)
//Parse target velocity
if len(args)!=1 {
log.Fatalf("Wrong number of arguments")
}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
// is called directly, e.g.:
//TODO: Anonymous flag (with anonymous flag group ?)
moveVelCmd.Flags().Float64("vel", 0, "Velocity (degrees/s)")
moveVelCmd.Flags().Float64("", 0, "Velocity (degrees/s)") //Only used for help message
// moveVelCmd.MarkFlagRequired("vel")
}
//gRPC Client
@ -68,5 +73,7 @@ func moveVel(cmd_vel float64){
_, err = c.SetVelocity(ctx, &pb.Velocity{Velocity: cmd_vel})
if err != nil {
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
var serveCmd = &cobra.Command{
Use: "serve",
Short: "serve command descritpion",
Long: `serve command descritpion`,
Short: "Start motor server",
Long: `Start motor server`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("serve called with ID ", cmd.Flag("id").Value)
// fmt.Println(cmd.CommandPath())
updateConfig()
init_sim()
serve()

View file

@ -42,12 +42,12 @@ func init() {
// will be global for your application.
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"))
// Cobra also supports local flags, which will only run
// 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.