diff --git a/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs b/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs index a8f8fb3e..c125b773 100644 --- a/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs +++ b/BrickController2/BrickController2/DeviceManagement/Lego/RemoteControl.cs @@ -15,13 +15,13 @@ namespace BrickController2.DeviceManagement.Lego; /// /// Represents a LEGO® Powered Up 88010 Remote Control /// -internal class RemoteControl : BluetoothDevice +internal class RemoteControl : BluetoothDevice, IDynamicInputDevice { private const string ENABLED_SETTING_NAME = "RemoteControlEnabled"; private const bool DEFAULT_ENABLED = false; private IGattCharacteristic? _characteristic; - private InputDeviceBase? _inputController; + private IInputDeviceConnector? _inputDeviceConnector; public RemoteControl(string name, string address, IEnumerable settings, IDeviceRepository deviceRepository, IBluetoothLEService bleService) : base(name, address, deviceRepository, bleService) @@ -41,14 +41,14 @@ public RemoteControl(string name, string address, IEnumerable sett public override void SetOutput(int channel, float value) => throw new InvalidOperationException(); - internal void ConnectInputController(TController inputController) where TController : InputDeviceBase + public void ConnectInputController(IInputDeviceConnector inputController) { - _inputController = inputController; + _inputDeviceConnector = inputController; } - internal void DisconnectInputController() + public void DisconnectInputController() { - _inputController = default; + _inputDeviceConnector = default; } internal void ResetEvents() => RaiseButtonEvents( @@ -156,16 +156,16 @@ private void OnButtonEvents(string plus, string stop, string minus, ReadOnlySpan private void RaiseButtonEvents((string eventName, float value)[] buttonEvents) { - if (_inputController is null) + if (_inputDeviceConnector is null) { return; } var events = buttonEvents - .Where(e => _inputController.HasValueChanged(e.eventName, e.value)) + .Where(e => _inputDeviceConnector.HasValueChanged(e.eventName, e.value)) .ToDictionary(e => (InputDeviceEventType.Button, e.eventName), e => e.value); - _inputController.RaiseEvent(events); + _inputDeviceConnector.RaiseEvent(events); } private static float GetButtonValue(byte flag) => flag != 0 ? BUTTON_PRESSED : BUTTON_RELEASED; diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs new file mode 100644 index 00000000..3b84c938 --- /dev/null +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/IDynamicInputDevice.cs @@ -0,0 +1,7 @@ +namespace BrickController2.PlatformServices.InputDevice; + +internal interface IDynamicInputDevice +{ + void ConnectInputController(IInputDeviceConnector controller); + void DisconnectInputController(); +} diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs new file mode 100644 index 00000000..67bc615e --- /dev/null +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/IInputDeviceConnector.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace BrickController2.PlatformServices.InputDevice; + +internal interface IInputDeviceConnector +{ + internal bool HasValueChanged(string axisName, float value); + + internal void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events); +} diff --git a/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs b/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs index eb213228..822485d9 100644 --- a/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs +++ b/BrickController2/BrickController2/PlatformServices/InputDevice/InputDeviceBase.cs @@ -9,7 +9,7 @@ namespace BrickController2.PlatformServices.InputDevice; /// abstract base class for input devices /// /// Type of native instance of inputdevice device -public abstract class InputDeviceBase : IInputDevice +public abstract class InputDeviceBase : IInputDevice, IInputDeviceConnector where TInputDeviceDevice : class { /// stored last value per axis to detect changes @@ -66,7 +66,7 @@ public virtual void Stop() protected bool ContainsAxisValue(string axisName) => _lastAxisValues.ContainsKey(axisName); - protected internal bool HasValueChanged(string axisName, float value) + public bool HasValueChanged(string axisName, float value) { // get last reported value or the default one _lastAxisValues.TryGetValue(axisName, out float lastValue); @@ -80,7 +80,7 @@ protected internal bool HasValueChanged(string axisName, float value) return true; } - protected internal void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) + public void RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) { if (!events.Any()) { diff --git a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml index a65f72db..c5475454 100644 --- a/BrickController2/BrickController2/UI/Pages/DevicePage.xaml +++ b/BrickController2/BrickController2/UI/Pages/DevicePage.xaml @@ -133,6 +133,27 @@ + + + + + + + + + + + + + + + + diff --git a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs index a4515b61..964eed4b 100644 --- a/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs +++ b/BrickController2/BrickController2/UI/ViewModels/DevicePageViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,19 +9,22 @@ using BrickController2.CreationManagement; using BrickController2.DeviceManagement; using BrickController2.Helpers; +using BrickController2.PlatformServices.InputDevice; using BrickController2.UI.Commands; using BrickController2.UI.Services.Navigation; using BrickController2.UI.Services.Dialog; using BrickController2.UI.Services.Translation; using Device = BrickController2.DeviceManagement.Device; using static BrickController2.CreationManagement.ControllerDefaults; +using static BrickController2.PlatformServices.InputDevice.InputDevices; namespace BrickController2.UI.ViewModels { - public class DevicePageViewModel : PageViewModelBase + public class DevicePageViewModel : PageViewModelBase, IInputDeviceConnector { private readonly IDeviceManager _deviceManager; private readonly IDialogService _dialogService; + private readonly Dictionary _lastAxisValues = []; private CancellationTokenSource? _connectionTokenSource; private Task? _connectionTask; @@ -32,6 +36,7 @@ public DevicePageViewModel( ITranslationService translationService, IDeviceManager deviceManager, IDialogService dialogService, + IServiceProvider serviceProvider, NavigationParameters parameters) : base(navigationService, translationService) { @@ -57,6 +62,7 @@ public DevicePageViewModel( } public Device Device { get; } + internal IDynamicInputDevice? InputDevice => Device as IDynamicInputDevice; public bool IsBuWizzDevice => Device.DeviceType == DeviceType.BuWizz; public bool IsBuWizz2Device => Device.DeviceType == DeviceType.BuWizz2; public bool CanBePowerSource => Device.CanBePowerSource; @@ -70,6 +76,8 @@ public DevicePageViewModel( public bool IsAdvertisingDevice => Device is BluetoothAdvertisingDevice; + public bool IsInputDevice => Device is IDynamicInputDevice; + public bool IsServoOrStepperSupported => DeviceOutputs.Any(x => x.IsServoOrStepperSupported); public ICommand RenameCommand { get; } @@ -84,6 +92,8 @@ public DevicePageViewModel( public IEnumerable DeviceOutputs { get; } + public ObservableCollection InputEventList { get; } = new(); + public override async void OnAppearing() { _isDisappearing = false; @@ -104,6 +114,9 @@ await _dialogService.ShowMessageBoxAsync( } } + // connect input device if available + InputDevice?.ConnectInputController(this); + _connectionTokenSource = new CancellationTokenSource(); _connectionTask = ConnectAsync(); } @@ -112,6 +125,9 @@ public override async void OnDisappearing() { base.OnDisappearing(); + // disconnect input device if available + InputDevice?.DisconnectInputController(); + if (_connectionTokenSource is not null && _connectionTask is not null) { _connectionTokenSource?.Cancel(); @@ -121,6 +137,42 @@ public override async void OnDisappearing() await Device.DisconnectAsync(); } + + bool IInputDeviceConnector.HasValueChanged(string axisName, float value) + { + // get last reported value or the default one + _lastAxisValues.TryGetValue(axisName, out float lastValue); + // skip value if there is no change + if (AreAlmostEqual(value, lastValue)) + { + return false; + } + // persist + _lastAxisValues[axisName] = value; + return true; + } + + void IInputDeviceConnector.RaiseEvent(IDictionary<(InputDeviceEventType, string), float> events) + { + foreach (var inputDeviceEvent in events) + { + var item = InputEventList.FirstOrDefault(x => x.EventCode == inputDeviceEvent.Key.Item2); + + if (AXIS_DELTA_VALUE >= Math.Abs(inputDeviceEvent.Value)) + { + InputEventList.Remove(item); + } + else if (item != null) + { + item.Value = inputDeviceEvent.Value; + } + else + { + InputEventList.Add(new InputDeviceEventViewModel(inputDeviceEvent.Key.Item1, inputDeviceEvent.Key.Item2, inputDeviceEvent.Value)); + } + } + } + private async Task RenameDeviceAsync() { try