feat(brake): implement brake feature
This commit is contained in:
54
pkg/brake/config.go
Normal file
54
pkg/brake/config.go
Normal 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
132
pkg/brake/config_test.go
Normal 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
55
pkg/brake/controller.go
Normal 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
|
||||
}
|
92
pkg/brake/controller_test.go
Normal file
92
pkg/brake/controller_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
4
pkg/brake/test_data/config.json
Normal file
4
pkg/brake/test_data/config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"delta_steps": [ 0.05, 0.3, 0.5 ],
|
||||
"data": [ -0.1, -0.5, -1.0 ]
|
||||
}
|
Reference in New Issue
Block a user