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