feat(brake): implement brake feature

This commit is contained in:
Cyrille Nofficial
2022-09-05 15:30:26 +02:00
parent dd0102ff44
commit 838c9b4ef3
11 changed files with 425 additions and 34 deletions

54
pkg/brake/config.go Normal file
View File

@ -0,0 +1,54 @@
package brake
import (
"encoding/json"
"fmt"
"github.com/cyrilix/robocar-throttle/pkg/types"
"os"
)
var (
defaultBrakeConfig = Config{
DeltaSteps: []float32{0.05, 0.3, 0.5},
Data: []types.Throttle{-0.1, -0.5, -1.},
}
)
func NewConfig() *Config {
return &defaultBrakeConfig
}
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 nil, fmt.Errorf("unable to unmarshal json content from %s file: %w", fileName, err)
}
return &ft, nil
}
type Config struct {
DeltaSteps []float32 `json:"delta_steps"`
Data []types.Throttle `json:"data"`
}
func (tc *Config) ValueOf(currentThrottle, targetThrottle types.Throttle) types.Throttle {
delta := float32(currentThrottle - targetThrottle)
if delta < tc.DeltaSteps[0] {
return targetThrottle
}
if delta >= tc.DeltaSteps[len(tc.DeltaSteps)-1] {
return tc.Data[len(tc.Data)-1]
}
for idx, step := range tc.DeltaSteps {
if delta < step {
return tc.Data[idx-1]
}
}
return tc.Data[len(tc.Data)-1]
}

132
pkg/brake/config_test.go Normal file
View File

@ -0,0 +1,132 @@
package brake
import (
"github.com/cyrilix/robocar-throttle/pkg/types"
"reflect"
"testing"
)
func TestNewConfigFromJson(t *testing.T) {
type args struct {
fileName string
}
tests := []struct {
name string
args args
want *Config
wantErr bool
}{
{
name: "default config",
args: args{
fileName: "test_data/config.json",
},
want: &defaultBrakeConfig,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewConfigFromJson(tt.args.fileName)
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)
}
if !reflect.DeepEqual(got.DeltaSteps, tt.want.DeltaSteps) {
t.Errorf("NewConfigFromJson(), bad DeltaSteps: got = %v, want %v", got.DeltaSteps, tt.want.DeltaSteps)
}
})
}
}
func TestConfig_ValueOf(t *testing.T) {
type fields struct {
DeltaSteps []float32
MinValue int
Data []types.Throttle
}
type args struct {
currentThrottle, targetThrottle types.Throttle
}
tests := []struct {
name string
fields fields
args args
want types.Throttle
}{
{
name: "delta > 0",
fields: fields{
DeltaSteps: defaultBrakeConfig.DeltaSteps,
Data: defaultBrakeConfig.Data,
},
args: args{
currentThrottle: 0.5,
targetThrottle: 0.8,
},
want: 0.8,
},
{
name: "no delta",
fields: fields{
DeltaSteps: defaultBrakeConfig.DeltaSteps,
Data: defaultBrakeConfig.Data,
},
args: args{
currentThrottle: 0.5,
targetThrottle: 0.5,
},
want: 0.5,
},
{
name: "delta very low (< 1st step)",
fields: fields{
DeltaSteps: defaultBrakeConfig.DeltaSteps,
Data: defaultBrakeConfig.Data,
},
args: args{
currentThrottle: 0.5,
targetThrottle: 0.495,
},
want: 0.495,
},
{
name: "low delta ( 1st step < delta < 2nd step )",
fields: fields{
DeltaSteps: defaultBrakeConfig.DeltaSteps,
Data: defaultBrakeConfig.Data,
},
args: args{
currentThrottle: 0.5,
targetThrottle: 0.38,
},
want: -0.1,
},
{
name: "high delta",
fields: fields{
DeltaSteps: defaultBrakeConfig.DeltaSteps,
Data: defaultBrakeConfig.Data,
},
args: args{
currentThrottle: 0.8,
targetThrottle: 0.3,
},
want: -1.,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := &Config{
DeltaSteps: tt.fields.DeltaSteps,
Data: tt.fields.Data,
}
got := f.ValueOf(tt.args.currentThrottle, tt.args.targetThrottle)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ValueOf() = %v, want %v", got, tt.want)
}
})
}
}

55
pkg/brake/controller.go Normal file
View File

@ -0,0 +1,55 @@
package brake
import (
"github.com/cyrilix/robocar-throttle/pkg/types"
"go.uber.org/zap"
"sync"
)
type Controller interface {
SetRealThrottle(t types.Throttle)
AdjustThrottle(targetThrottle types.Throttle) types.Throttle
}
func NewCustomController() *CustomController {
return &CustomController{cfg: NewConfig()}
}
func NewCustomControllerWithJsonConfig(filename string) *CustomController {
config, err := NewConfigFromJson(filename)
if err != nil {
zap.S().Panicf("unable to init brake controller with json config '%s': %v", filename, err)
}
return &CustomController{cfg: config}
}
type CustomController struct {
muRealThrottle sync.RWMutex
realThrottle types.Throttle
cfg *Config
}
func (b *CustomController) SetRealThrottle(t types.Throttle) {
b.muRealThrottle.Lock()
defer b.muRealThrottle.Unlock()
b.realThrottle = t
}
func (b *CustomController) GetRealThrottle() types.Throttle {
b.muRealThrottle.RLock()
defer b.muRealThrottle.RUnlock()
res := b.realThrottle
return res
}
func (b *CustomController) AdjustThrottle(targetThrottle types.Throttle) types.Throttle {
return b.cfg.ValueOf(b.GetRealThrottle(), targetThrottle)
}
type DisabledController struct{}
func (d *DisabledController) SetRealThrottle(_ types.Throttle) {}
func (d *DisabledController) AdjustThrottle(targetThrottle types.Throttle) types.Throttle {
return targetThrottle
}

View File

@ -0,0 +1,92 @@
package brake
import (
"github.com/cyrilix/robocar-throttle/pkg/types"
"testing"
)
func TestController_AdjustThrottle(t *testing.T) {
type fields struct {
realThrottle types.Throttle
}
type args struct {
targetThrottle types.Throttle
}
tests := []struct {
name string
fields fields
args args
want types.Throttle
}{
{
name: "target same as current throttle",
fields: fields{realThrottle: 0.2},
args: args{targetThrottle: 0.2},
want: 0.2,
},
{
name: "target > as current throttle",
fields: fields{realThrottle: 0.2},
args: args{targetThrottle: 0.3},
want: 0.3,
},
{
name: "target >> as current throttle",
fields: fields{realThrottle: 0.2},
args: args{targetThrottle: 0.5},
want: 0.5,
},
{
name: "target < as current throttle",
fields: fields{realThrottle: 0.8},
args: args{targetThrottle: 0.7},
want: -0.1,
},
{
name: "target << as current throttle",
fields: fields{realThrottle: 0.8},
args: args{targetThrottle: 0.5},
want: -0.5,
},
{
name: "target <<< as current throttle",
fields: fields{realThrottle: 0.8},
args: args{targetThrottle: 0.2},
want: -1.,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &CustomController{cfg: NewConfig()}
b.SetRealThrottle(tt.fields.realThrottle)
if got := b.AdjustThrottle(tt.args.targetThrottle); got != tt.want {
t.Errorf("AdjustThrottle() = %v, want %v", got, tt.want)
}
})
}
}
func TestDisabledController_AdjustThrottle(t *testing.T) {
type args struct {
targetThrottle types.Throttle
}
tests := []struct {
name string
args args
want types.Throttle
}{
{
name: "doesn't modify value",
args: args{targetThrottle: 0.5},
want: 0.5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DisabledController{}
if got := d.AdjustThrottle(tt.args.targetThrottle); got != tt.want {
t.Errorf("AdjustThrottle() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,4 @@
{
"delta_steps": [ 0.05, 0.3, 0.5 ],
"data": [ -0.1, -0.5, -1.0 ]
}