feat(processor): implement CustomSteeringProcessor

This commit is contained in:
Cyrille Nofficial 2023-06-14 20:07:43 +02:00
parent a7b08ff7b4
commit c2b12f297c
4 changed files with 310 additions and 1 deletions

View File

@ -24,6 +24,8 @@ func main() {
var brakeConfig string var brakeConfig string
var enableBrake bool var enableBrake bool
var enableSpeedZone bool var enableSpeedZone bool
var enableCustomSteeringProcessor bool
var configFileSteeringProcessor string
var slowZoneThrottle, normalZoneThrottle, fastZoneThrottle float64 var slowZoneThrottle, normalZoneThrottle, fastZoneThrottle float64
var moderateSteering, fullSteering float64 var moderateSteering, fullSteering float64
@ -54,6 +56,10 @@ func main() {
flag.BoolVar(&enableBrake, "enable-brake-feature", false, "Enable brake to slow car on throttle changes") flag.BoolVar(&enableBrake, "enable-brake-feature", false, "Enable brake to slow car on throttle changes")
flag.StringVar(&brakeConfig, "brake-configuration", "", "Json file to use to configure brake adaptation when --enable-brake is `true`") flag.StringVar(&brakeConfig, "brake-configuration", "", "Json file to use to configure brake adaptation when --enable-brake is `true`")
flag.BoolVar(&enableCustomSteeringProcessor, "enable-custom-steering-processor", false, "Enable custom steering processor to estimate throttle")
flag.StringVar(&configFileSteeringProcessor, "custom-steering-processor-config", "", "Path to json config to parameter custom steering processor")
flag.BoolVar(&enableSpeedZone, "enable-speed-zone", false, "Enable speed zone information to estimate throttle") flag.BoolVar(&enableSpeedZone, "enable-speed-zone", false, "Enable speed zone information to estimate throttle")
flag.Float64Var(&slowZoneThrottle, "slow-zone-throttle", 0.11, "Throttle target for slow speed zone") flag.Float64Var(&slowZoneThrottle, "slow-zone-throttle", 0.11, "Throttle target for slow speed zone")
flag.Float64Var(&normalZoneThrottle, "normal-zone-throttle", 0.12, "Throttle target for normal speed zone") flag.Float64Var(&normalZoneThrottle, "normal-zone-throttle", 0.12, "Throttle target for normal speed zone")
@ -92,6 +98,7 @@ func main() {
zap.S().Infof("Max throttle : %v", maxThrottle) zap.S().Infof("Max throttle : %v", maxThrottle)
zap.S().Infof("Publish frequency : %vHz", publishPilotFrequency) zap.S().Infof("Publish frequency : %vHz", publishPilotFrequency)
zap.S().Infof("Brake enabled : %v", enableBrake) zap.S().Infof("Brake enabled : %v", enableBrake)
zap.S().Infof("CustomSteeringProcessor enabled: %v", enableCustomSteeringProcessor)
zap.S().Infof("SpeedZone enabled : %v", enableSpeedZone) zap.S().Infof("SpeedZone enabled : %v", enableSpeedZone)
zap.S().Infof("SpeedZone slow throttle : %v", slowZoneThrottle) zap.S().Infof("SpeedZone slow throttle : %v", slowZoneThrottle)
zap.S().Infof("SpeedZone normal throttle : %v", normalZoneThrottle) zap.S().Infof("SpeedZone normal throttle : %v", normalZoneThrottle)
@ -111,8 +118,33 @@ func main() {
} else { } else {
brakeCtrl = &brake.DisabledController{} brakeCtrl = &brake.DisabledController{}
} }
if enableSpeedZone && enableCustomSteeringProcessor {
zap.S().Panicf("invalid flag, speedZone and customSteering processor can't be enabled at the same time")
}
var throttleProcessor throttle.Processor
if enableSpeedZone {
throttleProcessor = throttle.NewSpeedZoneProcessor(
types.Throttle(slowZoneThrottle),
types.Throttle(normalZoneThrottle),
types.Throttle(fastZoneThrottle),
moderateSteering,
fullSteering,
)
} else if enableCustomSteeringProcessor {
cfg, err := throttle.NewConfigFromJson(configFileSteeringProcessor)
if err != nil {
zap.S().Fatalf("unable to load config '%v': %v", configFileSteeringProcessor, err)
}
throttleProcessor = throttle.NewCustomSteeringProcessor(cfg)
} else {
throttleProcessor = throttle.NewSteeringProcessor(types.Throttle(minThrottle), types.Throttle(maxThrottle))
}
p := throttle.New(client, throttleTopic, driveModeTopic, rcThrottleTopic, steeringTopic, throttleFeedbackTopic, p := throttle.New(client, throttleTopic, driveModeTopic, rcThrottleTopic, steeringTopic, throttleFeedbackTopic,
types.Throttle(minThrottle), types.Throttle(maxThrottle), 2, throttle.WithBrakeController(brakeCtrl)) speedZoneTopic, types.Throttle(maxThrottle), 2,
throttle.WithThrottleProcessor(throttleProcessor),
throttle.WithBrakeController(brakeCtrl))
defer p.Stop() defer p.Stop()
cli.HandleExit(p) cli.HandleExit(p)

View File

@ -265,6 +265,10 @@ func TestController_Start(t *testing.T) {
throttleTopic, driveModeTopic, rcThrottleTopic, steeringTopic, throttleFeedbackTopic, throttleTopic, driveModeTopic, rcThrottleTopic, steeringTopic, throttleFeedbackTopic,
speedZoneTopic, tt.fields.max, speedZoneTopic, tt.fields.max,
tt.fields.publishPilotFrequency, tt.fields.publishPilotFrequency,
WithThrottleProcessor(&SteeringProcessor{
minThrottle: tt.fields.min,
maxThrottle: tt.fields.max,
}),
WithBrakeController(tt.fields.brakeCtl), WithBrakeController(tt.fields.brakeCtl),
) )

View File

@ -94,3 +94,86 @@ func (sp *SpeedZoneProcessor) Process(steering types.Steering) types.Throttle {
} }
func NewCustomSteeringProcessor(cfg *Config) *CustomSteeringProcessor { func NewCustomSteeringProcessor(cfg *Config) *CustomSteeringProcessor {
return &CustomSteeringProcessor{
cfg: cfg,
}
}
type CustomSteeringProcessor struct {
cfg *Config
}
func (cp *CustomSteeringProcessor) Process(steering types.Steering) types.Throttle {
return cp.cfg.ValueOf(steering)
}
func (cp *CustomSteeringProcessor) SetSpeedZone(_ events.SpeedZone) {
return
}
var emptyConfig = Config{
SteeringValues: []types.Steering{},
ThrottleSteps: []types.Throttle{},
}
func NewConfigFromJson(fileName string) (*Config, error) {
content, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("unable to read content from %s file: %w", fileName, err)
}
var ft Config
err = json.Unmarshal(content, &ft)
if err != nil {
return &emptyConfig, fmt.Errorf("unable to unmarshal json content from %s file: %w", fileName, err)
}
if len(ft.SteeringValues) == 0 {
return &emptyConfig, fmt.Errorf("invalid configuration, none steering value'")
}
if len(ft.SteeringValues) != len(ft.ThrottleSteps) {
return &emptyConfig, fmt.Errorf("invalid config, steering value number must be equals "+
"to throttle value number: %v/%v", len(ft.SteeringValues), len(ft.ThrottleSteps))
}
lastT := types.Throttle(1.)
for _, t := range ft.ThrottleSteps {
if t < 0. || t > 1. {
return &emptyConfig, fmt.Errorf("invalid throttle value: 0.0 < %v <= 1.0", t)
}
if t >= lastT {
return &emptyConfig, fmt.Errorf("invalid throttle value, all values must be decreasing: %v <= %v", lastT, t)
}
lastT = t
}
lastS := types.Steering(-0.001)
for _, s := range ft.SteeringValues {
if s < 0. || s > 1. {
return &emptyConfig, fmt.Errorf("invalid steering value: 0.0 < %v <= 1.0", s)
}
if s <= lastS {
return &emptyConfig, fmt.Errorf("invalid steering value, all values must be increasing: %v <= %v", lastS, s)
}
lastS = s
}
return &ft, nil
}
type Config struct {
SteeringValues []types.Steering `json:"steering_values"`
ThrottleSteps []types.Throttle `json:"throttle_steps"`
}
func (tc *Config) ValueOf(s types.Steering) types.Throttle {
st := s
if s < 0. {
st = s * -1
}
if st < tc.SteeringValues[0] {
return tc.ThrottleSteps[0]
}
for i, steeringStep := range tc.SteeringValues {
if st < steeringStep {
return tc.ThrottleSteps[i-1]
}
}
return tc.ThrottleSteps[len(tc.ThrottleSteps)-1]
}

View File

@ -222,3 +222,193 @@ func TestSpeedZoneProcessor_Process(t *testing.T) {
} }
} }
func TestConfig_ValueOf(t *testing.T) {
type fields struct {
SteeringValue []types.Steering
Data []types.Throttle
}
type args struct {
s types.Steering
}
tests := []struct {
name string
fields fields
args args
want types.Throttle
}{
{
name: "Nil steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{0.0},
want: 0.9,
},
{
name: "Nil steering < min config",
fields: fields{[]types.Steering{0.2, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.3}},
args: args{0.1},
want: 0.9,
},
{
name: "No nil steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{0.2},
want: 0.9,
},
{
name: "Intermediate steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{0.5},
want: 0.6,
},
{
name: "Max steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{1.0},
want: 0.1,
},
{
name: "Over steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{1.1},
want: 0.1,
},
{
name: "Negative steering < min config",
fields: fields{[]types.Steering{0.2, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.3}},
args: args{-0.1},
want: 0.9,
},
{
name: "Negative steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{-0.2},
want: 0.9,
},
{
name: "Negative Intermediate steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{-0.5},
want: 0.6,
},
{
name: "Minimum steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{-1.0},
want: 0.1,
},
{
name: "Negative Over steering",
fields: fields{[]types.Steering{0.0, 0.5, 1.0}, []types.Throttle{0.9, 0.6, 0.1}},
args: args{-1.1},
want: 0.1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc := &Config{
SteeringValues: tt.fields.SteeringValue,
ThrottleSteps: tt.fields.Data,
}
if got := tc.ValueOf(tt.args.s); got != tt.want {
t.Errorf("ValueOf() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewConfigFromJson(t *testing.T) {
type args struct {
configContent string
}
tests := []struct {
name string
args args
want *Config
wantErr bool
}{
{
name: "default",
args: args{
configContent: `{
"steering_values": [0.0, 0.5, 1.0],
"throttle_steps": [0.9, 0.6, 0.1]
}
`,
},
want: &Config{
SteeringValues: []types.Steering{0., 0.5, 1.},
ThrottleSteps: []types.Throttle{0.9, 0.6, 0.1},
},
},
{
name: "invalid config",
args: args{
configContent: `{ "steering_values" }`,
},
want: &emptyConfig,
wantErr: true,
},
{
name: "empty config",
args: args{
configContent: `{
"steering_values": [],
"throttle_steps": []
}`,
},
want: &emptyConfig,
wantErr: true,
},
{
name: "incoherent config",
args: args{
configContent: `{
"steering_values": [0.0, 0.5, 1.0],
"throttle_steps": [0.9, 0.1]
}`,
},
want: &emptyConfig,
wantErr: true,
},
{
name: "steering in bad order",
args: args{
configContent: `{
"steering_values": [0.0, 0.6, 0.5],
"throttle_steps": [0.9, 0.5, 0.1]
}`,
},
want: &emptyConfig,
wantErr: true,
},
{
name: "throttle in bad order",
args: args{
configContent: `{
"steering_values": [0.0, 0.5, 0.9],
"throttle_steps": [0.4, 0.5, 0.1]
}`,
},
want: &emptyConfig,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configName := path.Join(t.TempDir(), "config.json")
err := os.WriteFile(configName, []byte(tt.args.configContent), 0644)
if err != nil {
t.Errorf("unable to create test config: %v", err)
}
got, err := NewConfigFromJson(configName)
if (err != nil) != tt.wantErr {
t.Errorf("NewConfigFromJson() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewConfigFromJson() got = %v, want %v", got, tt.want)
}
})
}
}