Skip to content

Commit

Permalink
Merge pull request #65 from richbl/dev
Browse files Browse the repository at this point in the history
feat(app): ✨ Added support for displaying time remaining in …
  • Loading branch information
richbl authored Jan 5, 2025
2 parents eaa085e + 1417a4a commit 3aa86e7
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 25 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Edit the `config.toml` file found in the `internal/configuration` directory. The

```toml
# BLE Sync Cycle TOML configuration
# 0.7.1
# 0.8.0

[app]
logging_level = "debug" # Log messages to see during execution: "debug", "info", "warn", "error"
Expand All @@ -123,8 +123,11 @@ Edit the `config.toml` file found in the `internal/configuration` directory. The
speed_multiplier = 0.6 # Multiplier that translates sensor speed to video playback speed
# (0.0 = stopped, 1.0 = normal speed)
[video.OSD]
font_size = 40 # Font size for on-screen display (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)
display_time_remaining = true # Display time remaining on the on-screen display (true/false)

```

An explanation of the various sections of the `config.toml` file is provided below:
Expand Down Expand Up @@ -167,8 +170,10 @@ The `[video]` section defines the configuration for the MPV video player compone
#### The `[video.OSD]` Section

- `font_size`: The font size for the on-screen display (OSD)
- `display_cycle_speed`: A boolean value that indicates whether to display the cycle sensor speed on the on-screen display (OSD)
- `display_playback_speed`: A boolean value that indicates whether to display the video playback speed on the on-screen display (OSD)
- `display_time_remaining`: A boolean value that indicates whether to display the time remaining (using the format HH:MM:SS) on the on-screen display (OSD)

## Basic Usage

Expand Down Expand Up @@ -197,7 +202,7 @@ go run cmd/*
At this point, you should see the following output:

```console
2024/12/16 15:08:56 ----- ----- Starting BLE Sync Cycle 0.7.1
2024/12/16 15:08:56 ----- ----- Starting BLE Sync Cycle 0.8.0
2024/12/16 15:08:56 [INF] [BLE] created new BLE central controller
2024/12/16 15:08:56 [INF] [BLE] now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16...
2024/12/16 15:08:58 [DBG] [BLE] found BLE peripheral F1:42:D8:DE:35:16
Expand All @@ -206,13 +211,13 @@ At this point, you should see the following output:
2024/12/16 15:09:00 [DBG] [BLE] discovering CSC services 00001816-0000-1000-8000-00805f9b34fb
2024/12/16 15:09:10 [ERR] [BLE] CSC services discovery failed: timeout on DiscoverServices
2024/12/16 15:09:10 [ERR] [BLE] BLE peripheral scan failed: timeout on DiscoverServices
2024/12/16 15:09:10 ----- ----- BLE Sync Cycle 0.7.1 shutdown complete. Goodbye!
2024/12/16 15:09:10 ----- ----- BLE Sync Cycle 0.8.0 shutdown complete. Goodbye!
```

In this first example, while the application was able to find the BLE peripheral, it failed to discover the CSC services and characteristics before timing out. Depending on the BLE peripheral, it may take some time before a BLE peripheral advertises both its device services and characteristics. If the peripheral is not responding, you may need to increase the timeout in the `config.toml` file.

```console
2024/12/16 15:09:47 ----- ----- Starting BLE Sync Cycle 0.7.1
2024/12/16 15:09:47 ----- ----- Starting BLE Sync Cycle 0.8.0
2024/12/16 15:09:47 [INF] [BLE] created new BLE central controller
2024/12/16 15:09:47 [INF] [BLE] now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16...
2024/12/16 15:09:47 [DBG] [BLE] found BLE peripheral F1:42:D8:DE:35:16
Expand Down Expand Up @@ -294,27 +299,33 @@ In this last example, **BLE Sync Cycle** is coordinating with both the BLE perip
2024/12/16 15:13:33 [INF] [SPD] BLE sensor speed: 0.00 mph
2024/12/16 15:13:33 [INF] [VID] user-generated interrupt, stopping video player...
2024/12/16 15:13:33 [ERR] [APP] context canceled
2024/12/16 15:13:33 ----- ----- BLE Sync Cycle 0.7.1 shutdown complete. Goodbye!
2024/12/16 15:13:33 ----- ----- BLE Sync Cycle 0.8.0 shutdown complete. Goodbye!
```

## FAQ

- What is **BLE Sync Cycle**?
- What is **BLE Sync Cycle**?

> In its simplest form, this application makes video playback run faster when you pedal your bike faster, and slows down video playback when you pedal slower. And, when you stop your bike, video playback pauses.
- Do all Bluetooth devices work with **BLE Sync Cycle**?

> Not necessarily. The Bluetooth package used by **BLE Sync Cycle**, [called Go Bluetooth by TinyGo.org](https://github.com/tinygo-org/bluetooth), is based on the [Bluetooth Low Energy (BLE) standard](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy). Some Bluetooth devices may not be compatible with this protocol.
- Can I disable the log messages in **BLE Sync Cycle**?
> While you cannot disable all messages, check out the `logging_level` parameter in the `config.toml` file (see the [Editing the TOML File](#editing-the-toml-file) section above). This parameter can be set to "debug", "info", "warn", or "error", where "debug" is the most verbose and "error" is least verbose.

> Check out the `logging_level` parameter in the `config.toml` file (see the [Editing the TOML File](#editing-the-toml-file) section above). This parameter can be set to "debug", "info", "warn", or "error", where "debug" is the most verbose (all log messages displayed), and "error" is least verbose.
- My BLE sensor takes a long time to connect, and often times out. What can I do?

> The easiest solution is to just rerun the application, as that will usually give the BLE sensor enough time to establish a connection. If the issue persists,try increasing the `ble_connect_timeout` parameter in the `config.toml` file (see the [Editing the TOML File](#editing-the-toml-file) section above). Different BLE devices have different advertising intervals, so you may need to adjust this value accordingly.
- How do I use **BLE Sync Cycle**?

> See the [Basic Usage](#basic-usage) section above
- How do I configure **BLE Sync Cycle**?

> See the [Editing the TOML File](#editing-the-toml-file) section above
## Roadmap
Expand Down
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
const (
appPrefix = "----- -----"
appName = "BLE Sync Cycle"
appVersion = "0.7.1"
appVersion = "0.8.0"
)

// appControllers holds the application component controllers for managing speed, video playback,
Expand Down
6 changes: 4 additions & 2 deletions internal/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ type VideoConfig struct {

// VideoOSDConfig defines on-screen display settings for video playback
type VideoOSDConfig struct {
FontSize int `toml:"font_size"`
DisplayCycleSpeed bool `toml:"display_cycle_speed"`
DisplayPlaybackSpeed bool `toml:"display_playback_speed"`
DisplayTimeRemaining bool `toml:"display_time_remaining"`
ShowOSD bool // Computed field based on display settings
}

Expand Down Expand Up @@ -178,9 +180,9 @@ func (vc *VideoConfig) validate() error {
return fmt.Errorf("update_interval_sec must be greater than 0.0")
}

// Set computed field based on display settings
// Set ShowOSD flag based on display settings
vc.OnScreenDisplay.ShowOSD = vc.OnScreenDisplay.DisplayCycleSpeed ||
vc.OnScreenDisplay.DisplayPlaybackSpeed
vc.OnScreenDisplay.DisplayPlaybackSpeed || vc.OnScreenDisplay.DisplayTimeRemaining

return nil
}
6 changes: 4 additions & 2 deletions internal/configuration/config.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# BLE Sync Cycle TOML configuration
# 0.7.1
# 0.8.0

[app]
logging_level = "debug" # Log messages to see during execution: "debug", "info", "warn", "error"
logging_level = "info" # Log messages to see during execution: "debug", "info", "warn", "error"
# where "debug" is the most verbose and "error" is least verbose

[ble]
Expand All @@ -22,5 +22,7 @@
speed_multiplier = 0.6 # Multiplier that translates sensor speed to video playback speed
# (0.0 = stopped, 1.0 = normal speed)
[video.OSD]
font_size = 40 # Font size for on-screen display (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)
display_time_remaining = true # Display time remaining on the on-screen display (true/false)
25 changes: 16 additions & 9 deletions internal/services/shutdown_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ import (
logger "github.com/richbl/go-ble-sync-cycle/internal/logging"
)

// smContext represents the cancellation context for ShutdownManager
type smContext struct {
ctx context.Context
cancel context.CancelFunc
}

// ShutdownManager represents a shutdown manager that manages a component lifecycle
type ShutdownManager struct {
ctx context.Context
cancel context.CancelFunc
context smContext
wg sync.WaitGroup
timeout time.Duration
errChan chan error
Expand All @@ -28,8 +33,10 @@ func NewShutdownManager(timeout time.Duration) *ShutdownManager {
ctx, cancel := context.WithCancel(context.Background())

return &ShutdownManager{
ctx: ctx,
cancel: cancel,
context: smContext{
ctx: ctx,
cancel: cancel,
},
timeout: timeout,
errChan: make(chan error, 1),
}
Expand All @@ -45,11 +52,11 @@ func (sm *ShutdownManager) Run(name string, fn func(context.Context) error) {
defer sm.wg.Done()

// if the context is canceled, signal the error channel and return
if err := fn(sm.ctx); err != nil && err != context.Canceled {
if err := fn(sm.context.ctx); err != nil && err != context.Canceled {

select {
case sm.errChan <- err:
sm.cancel()
sm.context.cancel()
default:
}

Expand Down Expand Up @@ -81,7 +88,7 @@ func (sm *ShutdownManager) Start() {
// Shutdown shuts down the shutdown manager
func (sm *ShutdownManager) Shutdown() {

sm.cancel()
sm.context.cancel()
done := make(chan struct{})

go func() {
Expand All @@ -104,14 +111,14 @@ func (sm *ShutdownManager) Shutdown() {

// Context returns the shutdown manager's context
func (sm *ShutdownManager) Context() context.Context {
return sm.ctx
return sm.context.ctx
}

// Wait waits for the shutdown manager to finish
func (sm *ShutdownManager) Wait() {

select {
case <-sm.ctx.Done():
case <-sm.context.ctx.Done():
sm.Shutdown()
case err := <-sm.errChan:
if err != nil {
Expand Down
28 changes: 24 additions & 4 deletions internal/video-player/playback_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ func (p *PlaybackController) setup() error {
// configureMPVPlayer sets up the player window based on configuration
func (p *PlaybackController) configureMPVPlayer() error {

// keep open to allow for EOF check
if err := p.player.SetOptionString("keep-open", "yes"); err != nil {
return err
}

if err := p.player.SetOption("osd-font-size", mpv.FormatInt64, p.config.OnScreenDisplay.FontSize); err != nil {
return err
}

if p.config.WindowScaleFactor == 1.0 {
logger.Debug(logger.VIDEO, "maximizing video window")
return p.player.SetOptionString("window-maximized", "yes")
Expand Down Expand Up @@ -211,12 +216,18 @@ func (p *PlaybackController) updateDisplay(cycleSpeed, playbackSpeed float64) er
}

var osdText strings.Builder
if p.config.OnScreenDisplay.DisplayCycleSpeed {
fmt.Fprintf(&osdText, " Cycle Speed: %.2f %s\n", cycleSpeed, p.speedConfig.SpeedUnits)
}

if p.config.OnScreenDisplay.DisplayPlaybackSpeed {
// Build the text string to display in OSD
switch {
case p.config.OnScreenDisplay.DisplayCycleSpeed:
fmt.Fprintf(&osdText, " Cycle Speed: %.2f %s\n", cycleSpeed, p.speedConfig.SpeedUnits)
fallthrough
case p.config.OnScreenDisplay.DisplayPlaybackSpeed:
fmt.Fprintf(&osdText, " Playback Speed: %.2fx\n", playbackSpeed)
fallthrough
case p.config.OnScreenDisplay.DisplayTimeRemaining:
timeRemaining, _ := p.player.GetProperty("time-remaining", mpv.FormatInt64)
fmt.Fprintf(&osdText, " Time Remaining: %s\n", formatSeconds(timeRemaining.(int64)))
}

return p.player.SetOptionString("osd-msg1", osdText.String())
Expand All @@ -234,3 +245,12 @@ func (p *PlaybackController) logDebugInfo(speedController *speed.SpeedController
logger.Debug(logger.VIDEO, logger.Magenta+"playback speed update threshold:",
strconv.FormatFloat(p.speedConfig.SpeedThreshold, 'f', 2, 64), p.speedConfig.SpeedUnits)
}

// FormatSeconds converts seconds into HH:MM:SS format
func formatSeconds(seconds int64) string {
hours := seconds / 3600
minutes := (seconds % 3600) / 60
remainingSeconds := seconds % 60

return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, remainingSeconds)
}

0 comments on commit 3aa86e7

Please sign in to comment.