Skip to content

Commit

Permalink
Tests on performance ajchellew#1
Browse files Browse the repository at this point in the history
- Added throttling to BLE characteristic processing.
- Optimized encrypted data processing, reuse buffers instead of creating new arrays.
- Implemented batch processing for controller notifications
- Implemented a dedicated processing queue with a single worker thread
  • Loading branch information
ollypsilon committed Jan 3, 2025
1 parent 59a1969 commit ba672d7
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 40 deletions.
78 changes: 55 additions & 23 deletions Windows/ConsoleApp/BLE/ZwiftPlayBleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using ZwiftPlayConsoleApp.Logging;
using ZwiftPlayConsoleApp.Zap;
using ZwiftPlayConsoleApp.Configuration;
using System.Collections.Concurrent;

namespace ZwiftPlayConsoleApp.BLE;

Expand All @@ -13,19 +14,26 @@ public partial class ZwiftPlayBleManager : IDisposable
private readonly IZwiftLogger _logger;
private bool _isDisposed;
private readonly object _lock = new();

private readonly Config _config;
private static GattCharacteristic? _asyncCharacteristic;
private static GattCharacteristic? _syncRxCharacteristic;
private static GattCharacteristic? _syncTxCharacteristic;

private DateTime _lastProcessTime = DateTime.MinValue;
private const int MINIMUM_PROCESS_INTERVAL_MS = 16; // ~60Hz
private readonly ConcurrentQueue<(string source, byte[] value)> _characteristicQueue = new();
private readonly Thread _processingThread;
public ZwiftPlayBleManager(BluetoothDevice device, bool isLeft, IZwiftLogger logger, Config config)
{
_device = device;
_isLeft = isLeft;
_logger = new ConfigurableLogger(((ConfigurableLogger)logger)._config, nameof(ZwiftPlayBleManager));
_config = config;
_zapDevice = new ZwiftPlayDevice(new ConfigurableLogger(((ConfigurableLogger)logger)._config, nameof(ZwiftPlayDevice)), config);
_processingThread = new Thread(ProcessCharacteristicQueue)
{
IsBackground = true
};
_processingThread.Start();
}

public async Task ConnectAsync()
Expand Down Expand Up @@ -95,35 +103,43 @@ private async Task RegisterCharacteristics(RemoteGattServer gatt)

_logger.LogInfo("Characteristic registration completed");
}
public void Dispose()
{
lock (_lock)
public void Dispose()
{
if (_isDisposed) return;

if (_asyncCharacteristic != null)
lock (_lock)
{
_asyncCharacteristic.CharacteristicValueChanged -= (sender, eventArgs) =>
ProcessCharacteristic("Async", eventArgs.Value);
}
if (_syncTxCharacteristic != null)
{
_syncTxCharacteristic.CharacteristicValueChanged -= (sender, eventArgs) =>
ProcessCharacteristic("Sync Tx", eventArgs.Value);
}
if (_isDisposed) return;

if (_device?.Gatt != null && _device.Gatt.IsConnected)
{
_device.Gatt.Disconnect();
}
if (_asyncCharacteristic != null)
{
_asyncCharacteristic.CharacteristicValueChanged -= (sender, eventArgs) =>
ProcessCharacteristic("Async", eventArgs.Value);
}
if (_syncTxCharacteristic != null)
{
_syncTxCharacteristic.CharacteristicValueChanged -= (sender, eventArgs) =>
ProcessCharacteristic("Sync Tx", eventArgs.Value);
}

_isDisposed = true;
GC.SuppressFinalize(this);
if (_device?.Gatt != null && _device.Gatt.IsConnected)
{
_device.Gatt.Disconnect();
}

_isDisposed = true;
GC.SuppressFinalize(this);
}
}
}
private void ProcessCharacteristic(string source, byte[] value)
{
if (_isDisposed) return;

// Add throttling
var now = DateTime.UtcNow;
if ((now - _lastProcessTime).TotalMilliseconds < MINIMUM_PROCESS_INTERVAL_MS)
return;

_lastProcessTime = now;

_logger.LogDebug($"Processing {source} characteristic: {BitConverter.ToString(value)}");
_zapDevice.ProcessCharacteristic(source, value);
}
Expand All @@ -137,4 +153,20 @@ private void OnSyncTxCharacteristicChanged(object sender, GattCharacteristicValu
{
ProcessCharacteristic("Sync Tx", e.Value);
}
private void OnCharacteristicChanged(string source, byte[] value)
{
_characteristicQueue.Enqueue((source, value));
}

private void ProcessCharacteristicQueue()
{
while (!_isDisposed)
{
if (_characteristicQueue.TryDequeue(out var item))
{
ProcessCharacteristic(item.source, item.value);
}
Thread.Sleep(1); // Prevent tight loop
}
}
}
34 changes: 17 additions & 17 deletions Windows/ConsoleApp/BLE/ZwiftPlayDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,48 @@ public class ZwiftPlayDevice : AbstractZapDevice
//private readonly IZwiftLogger _logger;
private int _batteryLevel;
private ControllerNotification? _lastButtonState;

private readonly Config _config;

private byte[] _counterBuffer = new byte[4];
private byte[] _payloadBuffer = new byte[1024]; // Adjust size as needed
public ZwiftPlayDevice(IZwiftLogger logger, Config config) : base(logger)
{
_config = config;
_logger.LogInfo($"ZwiftPlayDevice initialized with SendKeys: {config.SendKeys}, UseMapping: {config.UseMapping}");
Console.WriteLine($"ZwiftPlayDevice initialized with SendKeys: {config.SendKeys}, UseMapping: {config.UseMapping}");
}

protected override void ProcessEncryptedData(byte[] bytes)
{
_logger.LogDebug($"Processing encrypted data length: {bytes.Length}");
try
{
// Reuse buffers instead of creating new arrays
Buffer.BlockCopy(bytes, 0, _counterBuffer, 0, _counterBuffer.Length);
var counter = new ByteBuffer(_counterBuffer).ReadInt32();

var payloadLength = bytes.Length - 4 - EncryptionUtils.MAC_LENGTH;
Buffer.BlockCopy(bytes, 4, _payloadBuffer, 0, payloadLength);

if (Debug)
_logger.LogDebug($"Processing encrypted data: {Utils.Utils.ByteArrayToStringHex(bytes)}");
var counterBytes = new byte[4];
Array.Copy(bytes, 0, counterBytes, 0, counterBytes.Length);
var counter = new ByteBuffer(counterBytes).ReadInt32();
if (Debug)
_logger.LogDebug($"Counter bytes: {Utils.Utils.ByteArrayToStringHex(counterBytes)}");
var payloadBytes = new byte[bytes.Length - 4 - EncryptionUtils.MAC_LENGTH];
Array.Copy(bytes, 4, payloadBytes, 0, payloadBytes.Length);
_logger.LogDebug($"Counter bytes: {Utils.Utils.ByteArrayToStringHex(_counterBuffer)}");
if (Debug)
_logger.LogDebug($"Attempting payload extraction, length: {payloadBytes.Length}");
_logger.LogDebug($"Attempting payload extraction, length: {payloadLength}");

var tagBytes = new byte[EncryptionUtils.MAC_LENGTH];
Array.Copy(bytes, EncryptionUtils.MAC_LENGTH + payloadBytes.Length, tagBytes, 0, tagBytes.Length);
Array.Copy(bytes, EncryptionUtils.MAC_LENGTH + payloadLength, tagBytes, 0, tagBytes.Length);
if (Debug)
_logger.LogDebug($"Attempting tag extraction, starting at index: {EncryptionUtils.MAC_LENGTH + payloadBytes.Length}");
_logger.LogDebug($"Attempting tag extraction, starting at index: {EncryptionUtils.MAC_LENGTH + payloadLength}");

var data = new byte[payloadBytes.Length];
byte[] data;
try
{
data = _zapEncryption.Decrypt(counter, payloadBytes, tagBytes);
data = _zapEncryption.Decrypt(counter, _payloadBuffer.AsSpan(0, payloadLength).ToArray(), tagBytes);
}
catch (Exception ex)
{
_logger.LogError($"Decrypt failed - Counter: {counter}, Payload: {BitConverter.ToString(payloadBytes)}, Tag: {BitConverter.ToString(tagBytes)}", ex);
_logger.LogError($"Decrypt failed - Counter: {counter}, Payload: {BitConverter.ToString(_payloadBuffer, 0, payloadLength)}, Tag: {BitConverter.ToString(tagBytes)}", ex);
return;
}
if (Debug)
_logger.LogDebug($"Decrypted data: {BitConverter.ToString(data)}");
Expand Down Expand Up @@ -91,8 +93,6 @@ protected override void ProcessEncryptedData(byte[] bytes)
_logger.LogError("Decrypt failed", ex);
}
}
private bool SendKeys { get; set; } = false;

private void ProcessButtonNotification(ControllerNotification notification)
{

Expand Down

0 comments on commit ba672d7

Please sign in to comment.