Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored various packages for performance and for better component configuration #26

Merged
merged 2 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 99 additions & 88 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type appControllers struct {

func main() {

log.Println("Starting BLE Sync Cycle 0.5.0")
log.Println("Starting BLE Sync Cycle 0.6.0")

// Load configuration
cfg, err := config.LoadFile("internal/configuration/config.toml")
Expand Down
85 changes: 61 additions & 24 deletions internal/ble/sensor_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/binary"
"errors"
"fmt"
"math"
"strconv"
"time"

Expand All @@ -23,12 +22,24 @@ type BLEController struct {
bleAdapter bluetooth.Adapter
}

type SpeedMeasurement struct {
wheelRevs uint32
wheelTime uint16
}

var (
// CSC speed tracking variables
lastWheelRevs uint32
lastWheelTime uint16
)

const (
minDataLength = 7
wheelRevFlag = uint8(0x01)
kphConversion = 3.6
mphConversion = 2.23694
)

// NewBLEController creates a new BLE central controller for accessing a BLE peripheral
func NewBLEController(bleConfig config.BLEConfig, speedConfig config.SpeedConfig) (*BLEController, error) {

Expand Down Expand Up @@ -107,8 +118,6 @@ func (m *BLEController) GetBLEUpdates(ctx context.Context, speedController *spee

}

// TODO

// ScanForBLEPeripheral scans for a BLE peripheral with the specified UUID
func (m *BLEController) ScanForBLEPeripheral(ctx context.Context) (bluetooth.ScanResult, error) {

Expand Down Expand Up @@ -174,47 +183,75 @@ func (m *BLEController) startScanning(found chan<- bluetooth.ScanResult) error {

}

// processBLESpeed processes raw BLE CSC speed data and returns the adjusted current speed
// ProcessBLESpeed processes the raw speed data from the BLE peripheral
func (m *BLEController) ProcessBLESpeed(data []byte) float64 {
if len(data) < 1 {

// Parse speed data
newSpeedData, err := m.parseSpeedData(data)
if err != nil {
logger.Error("[SPEED] Invalid BLE data: %v", err)
return 0.0
}

logger.Info("[SPEED] Processing speed data from BLE peripheral...")
// Calculate speed from parsed data
speed := m.calculateSpeed(newSpeedData)
logger.Info("[SPEED] BLE sensor speed: " + strconv.FormatFloat(speed, 'f', 2, 64) + " " + m.speedConfig.SpeedUnits)

flags := data[0]
hasWheelRev := flags&0x01 != 0
return speed

if !hasWheelRev || len(data) < 7 {
return 0.0
}
}

wheelRevs := binary.LittleEndian.Uint32(data[1:])
wheelEventTime := binary.LittleEndian.Uint16(data[5:])
// calculateSpeed calculates the current speed based on the sensor data
func (m *BLEController) calculateSpeed(sm SpeedMeasurement) float64 {

// First time through the loop set the last wheel revs and time
if lastWheelTime == 0 {
lastWheelRevs = wheelRevs
lastWheelTime = wheelEventTime
lastWheelRevs = sm.wheelRevs
lastWheelTime = sm.wheelTime
return 0.0
}

timeDiff := uint16(wheelEventTime - lastWheelTime)
// Calculate delta between time intervals
timeDiff := sm.wheelTime - lastWheelTime
if timeDiff == 0 {
return 0.0
}

revDiff := int32(wheelRevs - lastWheelRevs)
speedConversion := 3.6
if m.speedConfig.SpeedUnits == "mph" {
speedConversion = 2.23694
// Calculate delta between wheel revs
revDiff := int32(sm.wheelRevs - lastWheelRevs)

// Determine speed unit conversion muliplier
speedConversion := kphConversion
if m.speedConfig.SpeedUnits == config.SpeedUnitsMPH {
speedConversion = mphConversion
}

// Calculate new speed
speed := float64(revDiff) * float64(m.speedConfig.WheelCircumferenceMM) * speedConversion / float64(timeDiff)
lastWheelRevs = sm.wheelRevs
lastWheelTime = sm.wheelTime

return speed

logger.Info("[SPEED] BLE sensor speed: " + strconv.FormatFloat(math.Round(speed*100)/100, 'f', 2, 64) + " " + m.speedConfig.SpeedUnits)
}

lastWheelRevs = wheelRevs
lastWheelTime = wheelEventTime
// parseSpeedData parses the raw speed data from the BLE peripheral
func (m *BLEController) parseSpeedData(data []byte) (SpeedMeasurement, error) {

// Check for data
if len(data) < 1 {
return SpeedMeasurement{}, errors.New("empty data")
}

// Validate data
if data[0]&wheelRevFlag == 0 || len(data) < minDataLength {
return SpeedMeasurement{}, errors.New("invalid data format or length")
}

// Return new speed data
return SpeedMeasurement{
wheelRevs: binary.LittleEndian.Uint32(data[1:]),
wheelTime: binary.LittleEndian.Uint16(data[5:]),
}, nil

return speed
}
29 changes: 19 additions & 10 deletions internal/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ type SpeedConfig struct {

// VideoConfig represents the MPV video player configuration
type VideoConfig struct {
FilePath string `toml:"file_path"`
DisplayCycleSpeed bool `toml:"display_cycle_speed"`
DisplayPlaybackSpeed bool `toml:"display_playback_speed"`
WindowScaleFactor float64 `toml:"window_scale_factor"`
UpdateIntervalSec int `toml:"update_interval_sec"`
SpeedMultiplier float64 `toml:"speed_multiplier"`
FilePath string `toml:"file_path"`
WindowScaleFactor float64 `toml:"window_scale_factor"`
UpdateIntervalSec float64 `toml:"update_interval_sec"`
SpeedMultiplier float64 `toml:"speed_multiplier"`
OnScreenDisplay VideoOSDConfig `toml:"OSD"`
}

type VideoOSDConfig struct {
DisplayCycleSpeed bool `toml:"display_cycle_speed"`
DisplayPlaybackSpeed bool `toml:"display_playback_speed"`
ShowOSD bool
}

Expand All @@ -53,8 +57,8 @@ const (
logLevelError = "error"
logLevelFatal = "fatal"

speedUnitsKMH = "km/h"
speedUnitsMPH = "mph"
SpeedUnitsKMH = "km/h"
SpeedUnitsMPH = "mph"
)

// LoadFile loads the application configuration from the given filepath
Expand Down Expand Up @@ -117,7 +121,7 @@ func (ac *AppConfig) validate() error {
func (sc *SpeedConfig) validate() error {

switch sc.SpeedUnits {
case speedUnitsKMH, speedUnitsMPH:
case SpeedUnitsKMH, SpeedUnitsMPH:
return nil
default:
return errors.New("invalid speed units: " + sc.SpeedUnits)
Expand All @@ -144,8 +148,13 @@ func (vc *VideoConfig) validate() error {
return err
}

// Confirm that update_interval_sec is >0.0
if vc.UpdateIntervalSec <= 0.0 {
return errors.New("update_interval_sec must be greater than 0.0")
}

// Check if at least one OSD display flag is set
vc.ShowOSD = (vc.DisplayCycleSpeed || vc.DisplayPlaybackSpeed)
vc.OnScreenDisplay.ShowOSD = (vc.OnScreenDisplay.DisplayCycleSpeed || vc.OnScreenDisplay.DisplayPlaybackSpeed)

return nil

Expand Down
13 changes: 7 additions & 6 deletions internal/configuration/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# BLE Sync Cycle TOML configuration
# 0.5.0
# 0.6.0

[app]
logging_level = "debug" # Log messages to see during execution: "debug", "info", "warn", "error"
Expand All @@ -17,9 +17,10 @@

[video]
file_path = "cycling_test.mp4" # Path to the video file to play
display_cycle_speed = true # Display cycle speed on the screen (true/false)
display_playback_speed = true # Display video playback speed on the screen (true/false)
window_scale_factor = 1.0 # Scale factor for the video window (1.0 = full screen)
update_interval_sec = 1 # Seconds to wait between video player updates
speed_multiplier = 0.6 # Multiplier that translates sensor speed (km/h or mph) to video
# playback speed (0.0 = stopped, 1.0 = normal speed)
update_interval_sec = 0.3 # Seconds (>0.0) to wait between video player updates
speed_multiplier = 0.6 # Multiplier that translates sensor speed to video playback speed
# (0.0 = stopped, 1.0 = normal speed)
[video.OSD]
display_cycle_speed = true # Display cycle speed on the on-screen display (true/false)
display_playback_speed = true # Display video playback speed on the on-screen display (true/false)
26 changes: 15 additions & 11 deletions internal/configuration/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,22 +236,26 @@ func TestValidateVideoConfig(t *testing.T) {
{
name: "valid config",
config: VideoConfig{
FilePath: "test.mp4",
DisplayPlaybackSpeed: true,
WindowScaleFactor: 1.0,
UpdateIntervalSec: 1,
SpeedMultiplier: 1.0,
FilePath: "test.mp4",
OnScreenDisplay: VideoOSDConfig{
DisplayPlaybackSpeed: true,
},
WindowScaleFactor: 1.0,
UpdateIntervalSec: 1,
SpeedMultiplier: 1.0,
},
wantErr: false,
},
{
name: "invalid config",
config: VideoConfig{
FilePath: "non-existent-file.mp4",
DisplayPlaybackSpeed: true,
WindowScaleFactor: -1.0,
UpdateIntervalSec: -1,
SpeedMultiplier: -1.0,
FilePath: "non-existent-file.mp4",
OnScreenDisplay: VideoOSDConfig{
DisplayPlaybackSpeed: true,
},
WindowScaleFactor: -1.0,
UpdateIntervalSec: -1,
SpeedMultiplier: -1.0,
},
wantErr: true,
},
Expand Down Expand Up @@ -379,7 +383,7 @@ func TestValidateSpeedConfig(t *testing.T) {
SmoothingWindow: 5,
SpeedThreshold: 10.0,
WheelCircumferenceMM: 2000,
SpeedUnits: speedUnitsKMH,
SpeedUnits: SpeedUnitsKMH,
},
wantErr: false,
},
Expand Down
11 changes: 6 additions & 5 deletions internal/video-player/playback_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func (p *PlaybackController) Start(ctx context.Context, speedController *speed.S
return err
}

ticker := time.NewTicker(time.Second * time.Duration(p.config.UpdateIntervalSec))
// Set the MPV playback loop interval
ticker := time.NewTicker(time.Millisecond * time.Duration(p.config.UpdateIntervalSec*1000))
defer ticker.Stop()

lastSpeed := 0.0
Expand Down Expand Up @@ -132,7 +133,7 @@ func (p *PlaybackController) checkSpeedState(currentSpeed float64, lastSpeed *fl
// pausePlayback pauses the video playback in the MPV media player
func (p *PlaybackController) pausePlayback() error {

logger.Info("[VIDEO] No speed detected, so pausing video...")
logger.Info("[VIDEO] No speed detected, so pausing video")

// Update the on-screen display
if err := p.updateMPVdisplay(0.0, 0.0); err != nil {
Expand Down Expand Up @@ -168,17 +169,17 @@ func (p *PlaybackController) adjustPlayback(currentSpeed float64) error {
func (p *PlaybackController) updateMPVdisplay(cycleSpeed, playbackSpeed float64) error {

// Return if no OSD options are enabled in TOML
if !p.config.ShowOSD {
if !p.config.OnScreenDisplay.ShowOSD {
return nil
}

// Build the OSD message based on TOML configuration
var osdMsg string
if p.config.DisplayCycleSpeed {
if p.config.OnScreenDisplay.DisplayCycleSpeed {
osdMsg += "Sensor Speed: " + strconv.FormatFloat(cycleSpeed, 'f', 2, 64) + " " + p.speedConfig.SpeedUnits + "\n"
}

if p.config.DisplayPlaybackSpeed {
if p.config.OnScreenDisplay.DisplayPlaybackSpeed {
osdMsg += "Playback Speed: " + strconv.FormatFloat(playbackSpeed, 'f', 2, 64) + "\n"
}

Expand Down
Loading
Loading