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

refactor(APP): ♻️ Refactor startAppControllers(); general clea… #43

Merged
merged 1 commit into from
Dec 23, 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
173 changes: 87 additions & 86 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,34 @@ type shutdownHandler struct {
restoreTerm func()
}

const (
appPrefix = "----- -----"
appName = "BLE Sync Cycle"
appVersion = "0.6.2"
)

func main() {
log.Println("----- ----- Starting BLE Sync Cycle 0.6.2")

// Load configuration
cfg, err := config.LoadFile("config.toml")
if err != nil {
log.Println(logger.Red + "[FTL]" + logger.Reset + " [APP] failed to load TOML configuration: " + err.Error())
log.Println("----- ----- BLE Sync Cycle 0.6.2 shutdown complete. Goodbye!")
os.Exit(0)
}
// Hello computer!
log.Println(appPrefix, "Starting", appName, appVersion)

// Load configuration from TOML
cfg := loadConfig("config.toml")

// Initialize logger package with display level from TOML configuration
logger.Initialize(cfg.App.LogLevel)

// Initialize shutdown coordination
// Initialize shutdown services: signal handling, context cancellation and term configuration
var wg sync.WaitGroup
rootCtx, rootCancel := context.WithCancel(context.Background())
defer rootCancel()

sh := &shutdownHandler{
done: make(chan struct{}),
componentsDown: make(chan struct{}),
wg: &wg,
rootCancel: rootCancel,
restoreTerm: configureTerminal(),
}
defer rootCancel()

// Set up shutdown handlers
setupSignalHandling(sh)
Expand All @@ -76,33 +79,46 @@ func main() {

// Start components
if componentType, err := startAppControllers(rootCtx, controllers, sh.wg); err != nil {
logger.Error(componentType, err.Error())
sh.cleanup()
logger.Fatal(componentType, err.Error())
<-sh.done
os.Exit(0)
}

<-sh.done
}

// loadConfig loads the TOML configuration file
func loadConfig(file string) *config.Config {
cfg, err := config.LoadFile(file)
if err != nil {
log.Println(logger.Red + "[FTL]" + logger.Reset + " [APP] failed to load TOML configuration: " + err.Error())
waveGoodbye()
os.Exit(0)
}

return cfg
}

// cleanup handles graceful shutdown of all components
func (sh *shutdownHandler) cleanup() {

sh.cleanupOnce.Do(func() {

// Signal components to shut down and wait for them to finish
sh.rootCancel()
sh.wg.Wait()
close(sh.componentsDown)

// Perform final cleanup
sh.restoreTerm()

log.Println("----- ----- BLE Sync Cycle 0.6.2 shutdown complete. Goodbye!")
waveGoodbye()
close(sh.done)
})
}

// waveGoodbye prints a goodbye message to the log
func waveGoodbye() {
log.Println(appPrefix, appName, appVersion, "shutdown complete. Goodbye!")
}

// setupSignalHandling configures OS signal handling
func setupSignalHandling(sh *shutdownHandler) {
sigChan := make(chan os.Signal, 1)
Expand Down Expand Up @@ -153,104 +169,54 @@ func setupAppControllers(cfg config.Config) (appControllers, logger.ComponentTyp

// startAppControllers is responsible for starting and managing the component controllers
func startAppControllers(ctx context.Context, controllers appControllers, wg *sync.WaitGroup) (logger.ComponentType, error) {
// componentErr holds the error type and component type used for logging
type componentErr struct {
componentType logger.ComponentType
err error
}

// Create shutdown signal
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
// Create shutdown signal context.
ctxWithCancel, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer stop()

// Scan for BLE peripheral of interest
bleSpeedCharacter, err := scanForBLESpeedCharacteristic(ctx, controllers)
bleSpeedCharacter, err := runBLEScan(ctxWithCancel, controllers)
if err != nil {

// Check if the context was cancelled (user pressed Ctrl+C)
if ctx.Err() == context.Canceled {
if errors.Is(err, context.Canceled) {
return logger.APP, nil
}

return logger.BLE, errors.New("BLE peripheral scan failed: " + err.Error())
}

// Start component controllers concurrently
errs := make(chan componentErr, 1)
errChan := make(chan componentErr, 2) // Buffered channel for component errors.

// Add the goroutines to WaitGroup before starting them
wg.Add(2)
// Monitor BLE speed (goroutine)
go func() {
defer wg.Done()

if err := monitorBLESpeed(ctx, controllers, bleSpeedCharacter); err != nil {

// Check if the context was cancelled (user pressed Ctrl+C)
if ctx.Err() == context.Canceled {
errs <- componentErr{logger.BLE, nil}
return
}

errs <- componentErr{logger.BLE, err}
return
}

errs <- componentErr{logger.BLE, nil}
}()

// Play video (goroutine)
go func() {
defer wg.Done()

if err := playVideo(ctx, controllers); err != nil {

// Check if the context was cancelled (user pressed Ctrl+C)
if ctx.Err() == context.Canceled {
errs <- componentErr{logger.VIDEO, nil}
return
}
startBLEMonitoring(ctxWithCancel, controllers, wg, bleSpeedCharacter, errChan)
startVideoPlaying(ctxWithCancel, controllers, wg, errChan)

errs <- componentErr{logger.VIDEO, err}
return
}

errs <- componentErr{logger.VIDEO, nil}
}()

// Wait for both component results
// Wait for component results or cancellation.
for i := 0; i < 2; i++ {
compErr := <-errs

if compErr.err != nil {
return compErr.componentType, compErr.err
select {
case compErr := <-errChan:
if compErr.err != nil {
return compErr.componentType, compErr.err
}
case <-ctxWithCancel.Done():
return logger.APP, nil // Context cancelled, no error.
}

}

return logger.APP, nil
}

// scanForBLESpeedCharacteristic scans for the BLE CSC speed characteristic
func scanForBLESpeedCharacteristic(ctx context.Context, controllers appControllers) (*bluetooth.DeviceCharacteristic, error) {
// create a channel to receive the characteristic
// runBLEScan scans for the BLE speed characteristic.
func runBLEScan(ctx context.Context, controllers appControllers) (*bluetooth.DeviceCharacteristic, error) {
results := make(chan *bluetooth.DeviceCharacteristic, 1)
errChan := make(chan error, 1)

// Scan for the BLE CSC speed characteristic
go func() {
characteristic, err := controllers.bleController.GetBLECharacteristic(ctx, controllers.speedController)

if err != nil {
errChan <- err
return
}

// Return the characteristic
results <- characteristic
}()

// Wait for the characteristic or an error
select {
case <-ctx.Done():
logger.Info(logger.BLE, "user-generated interrupt, stopping BLE characteristic scan...")
Expand All @@ -260,15 +226,50 @@ func scanForBLESpeedCharacteristic(ctx context.Context, controllers appControlle
case characteristic := <-results:
return characteristic, nil
}
}

// startBLEMonitoring starts the BLE monitoring goroutine.
func startBLEMonitoring(ctx context.Context, controllers appControllers, wg *sync.WaitGroup, bleSpeedCharacter *bluetooth.DeviceCharacteristic, errChan chan<- componentErr) {
go func() {
defer wg.Done()
if err := monitorBLESpeed(ctx, controllers, bleSpeedCharacter); err != nil {
// Only send error if context was not cancelled.
if !errors.Is(err, context.Canceled) {
errChan <- componentErr{componentType: logger.BLE, err: err}
}
return
}
errChan <- componentErr{componentType: logger.BLE, err: nil}
}()
}

// startVideoPlaying starts the video playing goroutine.
func startVideoPlaying(ctx context.Context, controllers appControllers, wg *sync.WaitGroup, errChan chan<- componentErr) {
go func() {
defer wg.Done()
if err := playVideo(ctx, controllers); err != nil {
// Only send error if context was not cancelled.
if !errors.Is(err, context.Canceled) {
errChan <- componentErr{componentType: logger.VIDEO, err: err}
}
return
}
errChan <- componentErr{componentType: logger.VIDEO, err: nil}
}()
}

// monitorBLESpeed monitors the BLE speed characteristic
// monitorBLESpeed monitors the BLE speed characteristic.
func monitorBLESpeed(ctx context.Context, controllers appControllers, bleSpeedCharacter *bluetooth.DeviceCharacteristic) error {
return controllers.bleController.GetBLEUpdates(ctx, controllers.speedController, bleSpeedCharacter)
}

// playVideo starts the video player
// playVideo starts the video player.
func playVideo(ctx context.Context, controllers appControllers) error {
return controllers.videoPlayer.Start(ctx, controllers.speedController)
}

// componentErr holds the error type and component type used for logging
type componentErr struct {
componentType logger.ComponentType
err error
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ require (
require (
github.com/gen2brain/go-mpv v0.2.3
github.com/stretchr/testify v1.10.0
golang.org/x/term v0.27.0
tinygo.org/x/bluetooth v0.10.0
)
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,8 @@ golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N0
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
Loading