diff --git a/README.md b/README.md index 9b7c375..3ec9837 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Overview -**BLE Sync Cycle** is a Go application designed to synchronize video playback with real-time cycling data from Bluetooth Low Energy (BLE) devices, such as cycling speed and cadence (CSC) sensors. This integration provides users with an immersive indoor cycling experience by matching video playback with their actual cycling pace, making it a valuable option when outdoor cycling isn't feasible. +**BLE Sync Cycle** is a Go application designed to synchronize video playback with real-time cycling data from Bluetooth Low Energy (BLE) devices, such as cycling speed and cadence (CSC) sensors. This integration provides users with a more immersive indoor cycling experience by matching video playback with their actual cycling pace, making it a valuable option when outdoor cycling isn't feasible.
@@ -23,7 +23,11 @@ - Support for different speed units: miles per hour (mph), kilometers per hour (kph) - Speed smoothing option for natural and seamless video playback - Choice of video file for playback + - Various display options for video playback, including: + - The display of sensor speed and/or video playback speed via on-screen display (OSD) + - Video window scaling (full screen, half screen, etc.) - Simple command-line interface provides real-time component feedback +- Configurable logging levels (debug, info, warn, error) to manage the information displayed during application execution - Graceful handling of connection interrupts and system signals ensures all components shut down cleanly ## Rationale @@ -49,14 +53,16 @@ Since I already use a mechanical (no electronics) portable bicycle trainer while - A Bluetooth Low Energy (BLE) Cycling Speed and Cadence (CSC) sensor, configured for speed - A computer that supports Bluetooth (4.0+), preferably with a big screen display to watch video playback -For my own indoor cycling configuration, I use a Performance Travel Trac 3 trainer. The BLE sensor used is a [Magene S3+ Speed/Cadence Dual Mode Sensor](https://www.magene.com/en/sensors/59-s3-speed-cadence-dual-mode-sensor.html) configured for speed, though any BLE-compliant sensor should work. For an overview of Bluetooth BLE, refer to the article ["Introduction to Bluetooth Low Energy" by Kevin Townsend](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/introduction). Finally, I'm running **BLE Sync Cycle** on a Lenovo ThinkPad laptop running Ubuntu 24.04 (LTS) connected to a big screen monitor via HDMI. +For my own indoor cycling configuration, I use a Performance Travel Trac 3 trainer. The BLE sensor used is a [Magene S3+ Speed/Cadence Dual Mode Sensor](https://www.magene.com/en/sensors/59-s3-speed-cadence-dual-mode-sensor.html) configured for speed, though any BLE-compliant sensor should work. + +> For an overview of Bluetooth BLE, refer to the excellent article ["Introduction to Bluetooth Low Energy"](https://learn.adafruit.com/introduction-to-bluetooth-low-energy/introduction) by Kevin Townsend. ### Software Components - The open source, cross-platform [mpv media player](https://mpv.io/), installed and operational - A local video file for playback using mpv, preferably a first-person view cycling video. Check out [YouTube with this query: "first person cycling"](https://www.youtube.com/results?search_query=first+person+cycling) for some ideas - This application. While **BLE Sync Cycle** has been written and tested using Ubuntu 24.04 (LTS) on an Intel processor (amd64), it should work across any recent Unix-like platform and architecture - - In order to compile this project, an operational [Go language](https://go.dev/) environment is required (this release was developed using Go 1.23.2) + - In order to compile the executable for this project, an operational [Go language](https://go.dev/) environment is required (this release was developed using Go 1.23.2) ## Installation @@ -89,7 +95,7 @@ Edit the `config.toml` file found in the `internal/configuration` directory. The ```toml # 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" @@ -107,13 +113,13 @@ Edit the `config.toml` file found in the `internal/configuration` directory. The [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.5 # 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) ``` An explanation of the various sections of the `config.toml` file is provided below: @@ -149,14 +155,16 @@ The `[speed]` section defines the configuration for the speed controller compone The `[video]` section defines the configuration for the MPV video player component. It includes the following parameters: - `file_path`: The path to the video file to play. The video format must be supported by MPV (e.g., MP4, webm, etc.) -- `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) - `window_scale_factor`: A scaling factor for the video window, where 1.0 is full screen. This value can be useful when debugging or when running the video player in a non-maximized window is useful (e.g., 0.5 = half screen) -- `update_interval_sec`: The number of seconds to wait between video player updates -- `speed_multiplier`: The multiplier that translates sensor speed (km/h or mph) to video playback speed (0.0 = stopped, 1.0 = normal speed) +- `update_interval_sec`: The number of seconds (>0.0) to wait between video player updates. > The `speed_multiplier` parameter is used to control the relative playback speed of the video. Usually, a value of 1.0 is used, as this is the default value (normal playback speed). However, since it's typically unknown what the speed of the bicycle rider in the video is during "normal speed" playback, it's recommended to experiment with different values to find a good balance between video playback speed and real-world cycling experience. +#### The `[video.OSD]` Section + +- `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) + ## Basic Usage At a high level, **BLE Sync Cycle** will perform the following: @@ -184,66 +192,72 @@ go run cmd/main.go At this point, you should see the following output: ```bash -2024/12/04 16:38:20 Starting BLE Sync Cycle 0.5.0 -2024/12/04 16:38:20 INFO [BLE] Created new BLE central controller -2024/12/04 16:38:20 INFO [BLE] Now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16... -2024/12/04 16:38:20 INFO [BLE] Found BLE peripheral F1:42:D8:DE:35:16 -2024/12/04 16:38:20 INFO [BLE] Connecting to BLE peripheral device F1:42:D8:DE:35:16 -2024/12/04 16:38:24 INFO [BLE] BLE peripheral device connected -2024/12/04 16:38:24 INFO [BLE] Discovering CSC services 00001816-0000-1000-8000-00805f9b34fb -2024/12/04 16:38:34 WARN [BLE] CSC services discovery failed: timeout on DiscoverServices -2024/12/04 16:38:34 ERROR [BLE] BLE peripheral scan failed: timeout on DiscoverServices - +2024/12/11 22:02:28 Starting BLE Sync Cycle 0.6.0 +2024/12/11 22:02:28 INFO [BLE] Created new BLE central controller +2024/12/11 22:02:28 INFO [BLE] Now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16... +2024/12/11 22:02:42 INFO [BLE] Found BLE peripheral F1:42:D8:DE:35:16 +2024/12/11 22:02:42 INFO [BLE] Connecting to BLE peripheral device F1:42:D8:DE:35:16 +2024/12/11 22:02:44 INFO [BLE] BLE peripheral device connected +2024/12/11 22:02:44 INFO [BLE] Discovering CSC services 00001816-0000-1000-8000-00805f9b34fb +2024/12/11 22:02:54 ERROR [BLE] CSC services discovery failed: timeout on DiscoverServices +2024/12/11 22:02:54 ERROR [BLE] BLE peripheral scan failed: timeout on DiscoverServices +2024/12/11 22:02:54 INFO [APP] Application 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. ```bash -2024/12/04 16:39:07 Starting BLE Sync Cycle 0.5.0 -2024/12/04 16:39:07 INFO [BLE] Created new BLE central controller -2024/12/04 16:39:07 INFO [BLE] Now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16... -2024/12/04 16:39:07 INFO [BLE] Found BLE peripheral F1:42:D8:DE:35:16 -2024/12/04 16:39:07 INFO [BLE] Connecting to BLE peripheral device F1:42:D8:DE:35:16 -2024/12/04 16:39:07 INFO [BLE] BLE peripheral device connected -2024/12/04 16:39:07 INFO [BLE] Discovering CSC services 00001816-0000-1000-8000-00805f9b34fb -2024/12/04 16:39:07 INFO [BLE] Found CSC service 00001816-0000-1000-8000-00805f9b34fb -2024/12/04 16:39:07 INFO [BLE] Discovering CSC characteristics 00002a5b-0000-1000-8000-00805f9b34fb -2024/12/04 16:39:07 INFO [BLE] Found CSC characteristic 00002a5b-0000-1000-8000-00805f9b34fb -2024/12/04 16:39:07 INFO [BLE] Starting real-time monitoring of BLE sensor notifications... -2024/12/04 16:39:07 INFO [VIDEO] Starting MPV video player... -2024/12/04 16:39:07 INFO [VIDEO] Loading video file: cycling_test.mp4 -2024/12/04 16:39:07 INFO [VIDEO] Entering MPV playback loop... -2024/12/04 16:39:08 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 0.00 0.00 0.00] -2024/12/04 16:39:08 INFO [VIDEO] Smoothed sensor speed: 0.00 -2024/12/04 16:39:08 INFO [VIDEO] No speed detected, so pausing video... -2024/12/04 16:39:08 INFO [VIDEO] Video paused successfully +2024/12/11 22:03:59 Starting BLE Sync Cycle 0.6.0 +2024/12/11 22:03:59 INFO [BLE] Created new BLE central controller +2024/12/11 22:03:59 INFO [BLE] Now scanning the ether for BLE peripheral UUID of F1:42:D8:DE:35:16... +2024/12/11 22:03:59 INFO [BLE] Found BLE peripheral F1:42:D8:DE:35:16 +2024/12/11 22:03:59 INFO [BLE] Connecting to BLE peripheral device F1:42:D8:DE:35:16 +2024/12/11 22:03:59 INFO [BLE] BLE peripheral device connected +2024/12/11 22:03:59 INFO [BLE] Discovering CSC services 00001816-0000-1000-8000-00805f9b34fb +2024/12/11 22:03:59 INFO [BLE] Found CSC service 00001816-0000-1000-8000-00805f9b34fb +2024/12/11 22:03:59 INFO [BLE] Discovering CSC characteristics 00002a5b-0000-1000-8000-00805f9b34fb +2024/12/11 22:03:59 INFO [BLE] Found CSC characteristic 00002a5b-0000-1000-8000-00805f9b34fb +2024/12/11 22:03:59 INFO [BLE] Starting real-time monitoring of BLE sensor notifications... +2024/12/11 22:03:59 INFO [VIDEO] Starting MPV video player... +2024/12/11 22:03:59 INFO [VIDEO] Loading video file: cycling_test.mp4 +2024/12/11 22:03:59 INFO [VIDEO] Entering MPV playback loop... +2024/12/11 22:04:00 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 0.00 0.00 0.00] +2024/12/11 22:04:00 INFO [VIDEO] Smoothed sensor speed: 0.00 mph +2024/12/11 22:04:00 INFO [VIDEO] No speed detected, so pausing video +2024/12/11 22:04:00 INFO [VIDEO] Video paused successfully ``` In the example above, the application is now running in a loop, periodically querying the BLE peripheral for speed data. The application will also update the video player to match the speed of the sensor. Here, since the video has just begun, its speed is set to 0.0 (paused). ```bash ... -2024/12/04 19:31:52 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:52 INFO [SPEED] BLE sensor speed: 10.49 mph -2024/12/04 19:31:53 INFO [VIDEO] Sensor speed buffer: [3.17 10.78 10.57 10.34 10.49] -2024/12/04 19:31:53 INFO [VIDEO] Smoothed sensor speed: 9.07 -2024/12/04 19:31:53 INFO [VIDEO] Adjusting video speed to 0.54 -2024/12/04 19:31:53 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:53 INFO [SPEED] BLE sensor speed: 11.10 mph -2024/12/04 19:31:54 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:54 INFO [SPEED] BLE sensor speed: 11.91 mph -2024/12/04 19:31:54 INFO [VIDEO] Sensor speed buffer: [10.57 10.34 10.49 11.10 11.91] -2024/12/04 19:31:54 INFO [VIDEO] Smoothed sensor speed: 10.88 -2024/12/04 19:31:54 INFO [VIDEO] Adjusting video speed to 0.65 -2024/12/04 19:31:54 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:54 INFO [SPEED] BLE sensor speed: 12.53 mph -2024/12/04 19:31:55 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:55 INFO [SPEED] BLE sensor speed: 12.11 mph -2024/12/04 19:31:55 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:31:55 INFO [SPEED] BLE sensor speed: 12.49 mph -2024/12/04 19:31:55 INFO [VIDEO] Sensor speed buffer: [11.10 11.91 12.53 12.11 12.49] -2024/12/04 19:31:55 INFO [VIDEO] Smoothed sensor speed: 12.02 -2024/12/04 19:31:55 INFO [VIDEO] Adjusting video speed to 0.72 +024/12/11 22:05:08 INFO [SPEED] BLE sensor speed: 8.54 mph +2024/12/11 22:05:09 INFO [VIDEO] Sensor speed buffer: [9.06 8.28 8.11 8.20 8.54] +2024/12/11 22:05:09 INFO [VIDEO] Smoothed sensor speed: 8.44 mph +2024/12/11 22:05:09 INFO [VIDEO] Updating video playback speed to 0.51 +2024/12/11 22:05:09 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:09 INFO [VIDEO] Sensor speed buffer: [8.28 8.11 8.20 8.54 0.00] +2024/12/11 22:05:09 INFO [VIDEO] Smoothed sensor speed: 6.63 mph +2024/12/11 22:05:09 INFO [VIDEO] Updating video playback speed to 0.40 +2024/12/11 22:05:09 INFO [SPEED] BLE sensor speed: 8.44 mph +2024/12/11 22:05:10 INFO [VIDEO] Sensor speed buffer: [8.11 8.20 8.54 0.00 8.44] +2024/12/11 22:05:10 INFO [VIDEO] Smoothed sensor speed: 6.66 mph +2024/12/11 22:05:10 INFO [SPEED] BLE sensor speed: 7.84 mph +2024/12/11 22:05:10 INFO [VIDEO] Sensor speed buffer: [8.20 8.54 0.00 8.44 7.84] +2024/12/11 22:05:10 INFO [VIDEO] Smoothed sensor speed: 6.61 mph +2024/12/11 22:05:11 INFO [VIDEO] Sensor speed buffer: [8.20 8.54 0.00 8.44 7.84] +2024/12/11 22:05:11 INFO [VIDEO] Smoothed sensor speed: 6.61 mph +2024/12/11 22:05:11 INFO [SPEED] BLE sensor speed: 6.56 mph +2024/12/11 22:05:11 INFO [SPEED] BLE sensor speed: 6.65 mph +2024/12/11 22:05:11 INFO [VIDEO] Sensor speed buffer: [0.00 8.44 7.84 6.56 6.65] +2024/12/11 22:05:11 INFO [VIDEO] Smoothed sensor speed: 5.90 mph +2024/12/11 22:05:11 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:12 INFO [VIDEO] Sensor speed buffer: [8.44 7.84 6.56 6.65 0.00] +2024/12/11 22:05:12 INFO [VIDEO] Smoothed sensor speed: 5.90 mph +2024/12/11 22:05:12 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:12 INFO [VIDEO] Sensor speed buffer: [7.84 6.56 6.65 0.00 0.00] +2024/12/11 22:05:12 INFO [VIDEO] Smoothed sensor speed: 4.21 mph +2024/12/11 22:05:12 INFO [VIDEO] Updating video playback speed to 0.25 ... ``` @@ -253,30 +267,27 @@ In this last example, **BLE Sync Cycle** is coordinating with both the BLE perip ```bash ... -2024/12/04 19:32:07 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:07 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 8.34 0.00 0.00] -2024/12/04 19:32:07 INFO [VIDEO] Smoothed sensor speed: 1.67 -2024/12/04 19:32:07 INFO [VIDEO] Adjusting video speed to 0.10 -2024/12/04 19:32:07 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:08 INFO [VIDEO] Sensor speed buffer: [0.00 8.34 0.00 0.00 0.00] -2024/12/04 19:32:08 INFO [VIDEO] Smoothed sensor speed: 1.67 -2024/12/04 19:32:08 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:10 INFO [VIDEO] Smoothed sensor speed: 0.00 -2024/12/04 19:32:10 INFO [VIDEO] No speed detected, so pausing video... -2024/12/04 19:32:10 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:10 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:11 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 0.00 0.00 0.00] -2024/12/04 19:32:11 INFO [VIDEO] Smoothed sensor speed: 0.00 -2024/12/04 19:32:11 INFO [VIDEO] No speed detected, so pausing video... -2024/12/04 19:32:11 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:12 INFO [SPEED] Processing speed data from BLE peripheral... -2024/12/04 19:32:12 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 0.00 0.00 0.00] -2024/12/04 19:32:12 INFO [VIDEO] Smoothed sensor speed: 0.00 -2024/12/04 19:32:12 INFO [VIDEO] No speed detected, so pausing video... -2024/12/04 19:32:12 INFO [SPEED] Processing speed data from BLE peripheral... -^C2024/12/04 19:32:12 INFO [APP] Shutdown signal received -2024/12/04 19:32:12 INFO [VIDEO] Context cancelled. Shutting down video player component -2024/12/04 19:32:12 INFO [APP] Application shutdown complete. Goodbye! +2024/12/11 22:05:11 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:12 INFO [VIDEO] Sensor speed buffer: [8.44 7.84 6.56 6.65 0.00] +2024/12/11 22:05:12 INFO [VIDEO] Smoothed sensor speed: 5.90 mph +2024/12/11 22:05:12 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:12 INFO [VIDEO] Sensor speed buffer: [7.84 6.56 6.65 0.00 0.00] +2024/12/11 22:05:12 INFO [VIDEO] Smoothed sensor speed: 4.21 mph +2024/12/11 22:05:12 INFO [VIDEO] Updating video playback speed to 0.25 +2024/12/11 22:05:13 INFO [SPEED] BLE sensor speed: 7.29 mph +2024/12/11 22:05:13 INFO [VIDEO] Sensor speed buffer: [6.56 6.65 0.00 0.00 7.29] +2024/12/11 22:05:13 INFO [VIDEO] Smoothed sensor speed: 4.10 mph +2024/12/11 22:05:13 INFO [VIDEO] Sensor speed buffer: [6.56 6.65 0.00 0.00 7.29] +2024/12/11 22:05:13 INFO [VIDEO] Smoothed sensor speed: 4.10 mph +2024/12/11 22:05:13 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:13 INFO [SPEED] BLE sensor speed: 0.00 mph +2024/12/11 22:05:14 INFO [VIDEO] Sensor speed buffer: [0.00 0.00 7.29 0.00 0.00] +2024/12/11 22:05:14 INFO [VIDEO] Smoothed sensor speed: 1.46 mph +2024/12/11 22:05:14 INFO [VIDEO] Updating video playback speed to 0.09 +2024/12/11 22:05:14 INFO [APP] Shutdown signal received +2024/12/11 22:05:14 INFO [VIDEO] Context cancelled. Shutting down video player component +2024/12/11 22:05:14 INFO [APP] Application shutdown complete. Goodbye! + ``` ## FAQ @@ -288,7 +299,7 @@ Q: Do all Bluetooth devices work with **BLE Sync Cycle**? A: 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. Q: Can I disable the log messages in **BLE Sync Cycle**? -A: While you cannot entirely disable log 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. When set to "error", only error/fatal messages will be displayed which, under normal circumstances, should be none. +A: While you cannot disable all log 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. When set to "error", only error/fatal messages will be displayed which, under normal circumstances, should be none. Q: How do I use **BLE Sync Cycle**? A: See the [Basic Usage](#basic-usage) section above diff --git a/cmd/main.go b/cmd/main.go index 78e58e0..2fdcea6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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") diff --git a/internal/ble/sensor_controller.go b/internal/ble/sensor_controller.go index 7ef487f..1003cac 100644 --- a/internal/ble/sensor_controller.go +++ b/internal/ble/sensor_controller.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "errors" "fmt" - "math" "strconv" "time" @@ -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) { @@ -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) { @@ -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 } diff --git a/internal/configuration/config.go b/internal/configuration/config.go index d661df3..6c4c761 100644 --- a/internal/configuration/config.go +++ b/internal/configuration/config.go @@ -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 } @@ -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 @@ -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) @@ -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 diff --git a/internal/configuration/config.toml b/internal/configuration/config.toml index d7732de..1774dc5 100644 --- a/internal/configuration/config.toml +++ b/internal/configuration/config.toml @@ -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" @@ -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) diff --git a/internal/configuration/config_test.go b/internal/configuration/config_test.go index 382c2c1..71735e8 100644 --- a/internal/configuration/config_test.go +++ b/internal/configuration/config_test.go @@ -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, }, @@ -379,7 +383,7 @@ func TestValidateSpeedConfig(t *testing.T) { SmoothingWindow: 5, SpeedThreshold: 10.0, WheelCircumferenceMM: 2000, - SpeedUnits: speedUnitsKMH, + SpeedUnits: SpeedUnitsKMH, }, wantErr: false, }, diff --git a/internal/video-player/playback_controller.go b/internal/video-player/playback_controller.go index 9ae5d54..636e680 100644 --- a/internal/video-player/playback_controller.go +++ b/internal/video-player/playback_controller.go @@ -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 @@ -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 { @@ -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" } diff --git a/internal/video-player/playback_controller_test.go b/internal/video-player/playback_controller_test.go index 785936e..a8795b2 100644 --- a/internal/video-player/playback_controller_test.go +++ b/internal/video-player/playback_controller_test.go @@ -26,11 +26,13 @@ func TestNewPlaybackController(t *testing.T) { // Create a video configuration for testing videoConfig := config.VideoConfig{ - FilePath: "test.mp4", - WindowScaleFactor: 1.0, - UpdateIntervalSec: 1, - SpeedMultiplier: 1.0, - DisplayPlaybackSpeed: true, + FilePath: "test.mp4", + WindowScaleFactor: 1.0, + UpdateIntervalSec: 1, + SpeedMultiplier: 1.0, + OnScreenDisplay: config.VideoOSDConfig{ + DisplayPlaybackSpeed: true, + }, } // Create a speed configuration for testing @@ -50,11 +52,13 @@ func TestPlaybackController_Start(t *testing.T) { // Create a video configuration for testing. videoConfig := config.VideoConfig{ - FilePath: "test.mp4", - WindowScaleFactor: 1.0, - UpdateIntervalSec: 1, - SpeedMultiplier: 1.0, - DisplayPlaybackSpeed: true, + FilePath: "test.mp4", + WindowScaleFactor: 1.0, + UpdateIntervalSec: 1, + SpeedMultiplier: 1.0, + OnScreenDisplay: config.VideoOSDConfig{ + DisplayPlaybackSpeed: true, + }, } // Create a speed configuration for testing @@ -85,11 +89,13 @@ func TestPlaybackController_configurePlayer(t *testing.T) { // Create a video configuration for testing videoConfig := config.VideoConfig{ - FilePath: "test.mp4", - WindowScaleFactor: 1.0, - UpdateIntervalSec: 1, - SpeedMultiplier: 1.0, - DisplayPlaybackSpeed: true, + FilePath: "test.mp4", + WindowScaleFactor: 1.0, + UpdateIntervalSec: 1, + SpeedMultiplier: 1.0, + OnScreenDisplay: config.VideoOSDConfig{ + DisplayPlaybackSpeed: true, + }, } // Create a speed configuration for testing @@ -113,11 +119,13 @@ func TestPlaybackController_loadVideoFile(t *testing.T) { // Create a video configuration for testing videoConfig := config.VideoConfig{ - FilePath: "test.mp4", - WindowScaleFactor: 1.0, - UpdateIntervalSec: 1, - SpeedMultiplier: 1.0, - DisplayPlaybackSpeed: true, + FilePath: "test.mp4", + WindowScaleFactor: 1.0, + UpdateIntervalSec: 1, + SpeedMultiplier: 1.0, + OnScreenDisplay: config.VideoOSDConfig{ + DisplayPlaybackSpeed: true, + }, } // Create a speed configuration for testing @@ -141,11 +149,13 @@ func TestPlaybackController_setPauseStatus(t *testing.T) { // Create a video configuration for testing videoConfig := config.VideoConfig{ - FilePath: "test.mp4", - WindowScaleFactor: 1.0, - SpeedMultiplier: 1.0, - UpdateIntervalSec: 1, - DisplayPlaybackSpeed: true, + FilePath: "test.mp4", + WindowScaleFactor: 1.0, + SpeedMultiplier: 1.0, + UpdateIntervalSec: 1, + OnScreenDisplay: config.VideoOSDConfig{ + DisplayPlaybackSpeed: true, + }, } // Create a speed configuration for testing