1
0
mirror of synced 2024-12-22 09:03:08 +01:00

Fixed: send initial values when device connects

Implemented various TODO's
This commit is contained in:
Mark van Renswoude 2021-02-28 14:40:51 +01:00
parent de27a6ccee
commit cd1ab91f23
19 changed files with 204 additions and 119 deletions

View File

@ -6,7 +6,7 @@ namespace MassiveKnob.Plugin.CoreAudio.Base
{ {
public Guid? DeviceId { get; set; } public Guid? DeviceId { get; set; }
// TODO more options, like positioning and style // TODO (nice to have) more options, like positioning and style
public bool OSD { get; set; } = true; public bool OSD { get; set; } = true;
} }
} }

View File

@ -1,24 +1,9 @@
using System; namespace MassiveKnob.Plugin.CoreAudio.Base
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MassiveKnob.Plugin.CoreAudio.Base
{ {
/// <summary> /// <summary>
/// Interaction logic for BaseDeviceSettingsView.xaml /// Interaction logic for BaseDeviceSettingsView.xaml
/// </summary> /// </summary>
public partial class BaseDeviceSettingsView : UserControl public partial class BaseDeviceSettingsView
{ {
public BaseDeviceSettingsView() public BaseDeviceSettingsView()
{ {

View File

@ -6,8 +6,6 @@ using Microsoft.Extensions.Logging;
namespace MassiveKnob.Plugin.CoreAudio.GetMuted namespace MassiveKnob.Plugin.CoreAudio.GetMuted
{ {
// TODO send out initial muted state after proper initialization
public class DeviceGetMutedAction : IMassiveKnobAction public class DeviceGetMutedAction : IMassiveKnobAction
{ {
public Guid ActionId { get; } = new Guid("86646ca7-f472-4c5a-8d0f-7e5d2d162ab9"); public Guid ActionId { get; } = new Guid("86646ca7-f472-4c5a-8d0f-7e5d2d162ab9");
@ -54,6 +52,9 @@ namespace MassiveKnob.Plugin.CoreAudio.GetMuted
deviceChanged?.Dispose(); deviceChanged?.Dispose();
deviceChanged = playbackDevice?.MuteChanged.Subscribe(MuteChanged); deviceChanged = playbackDevice?.MuteChanged.Subscribe(MuteChanged);
if (playbackDevice != null)
actionContext.SetDigitalOutput(settings.Inverted ? !playbackDevice.IsMuted : playbackDevice.IsMuted);
} }

View File

@ -6,8 +6,6 @@ using Microsoft.Extensions.Logging;
namespace MassiveKnob.Plugin.CoreAudio.GetVolume namespace MassiveKnob.Plugin.CoreAudio.GetVolume
{ {
// TODO send out initial volume after proper initialization
public class DeviceGetVolumeAction : IMassiveKnobAction public class DeviceGetVolumeAction : IMassiveKnobAction
{ {
public Guid ActionId { get; } = new Guid("6ebf91af-8240-4a75-9729-c6a1eb60dcba"); public Guid ActionId { get; } = new Guid("6ebf91af-8240-4a75-9729-c6a1eb60dcba");
@ -54,6 +52,9 @@ namespace MassiveKnob.Plugin.CoreAudio.GetVolume
deviceChanged?.Dispose(); deviceChanged?.Dispose();
deviceChanged = playbackDevice?.VolumeChanged.Subscribe(VolumeChanged); deviceChanged = playbackDevice?.VolumeChanged.Subscribe(VolumeChanged);
if (playbackDevice != null)
actionContext.SetAnalogOutput((byte)playbackDevice.Volume);
} }

View File

@ -1,6 +1,4 @@
using MassiveKnob.Plugin.CoreAudio.SetMuted; namespace MassiveKnob.Plugin.CoreAudio.GetVolume
namespace MassiveKnob.Plugin.CoreAudio.GetVolume
{ {
/// <summary> /// <summary>
/// Interaction logic for DeviceGetVolumeActionSettingsView.xaml /// Interaction logic for DeviceGetVolumeActionSettingsView.xaml

View File

@ -1,6 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.RightsManagement;
using System.Windows; using System.Windows;
using AudioSwitcher.AudioApi; using AudioSwitcher.AudioApi;

View File

@ -14,34 +14,35 @@
x:Key="SpeakerIcon" x:Key="SpeakerIcon"
d:DataContext="{d:DesignInstance osd:OSDWindowViewModel}"> d:DataContext="{d:DesignInstance osd:OSDWindowViewModel}">
<Canvas Width="256" Height="256"> <Canvas Width="256" Height="256">
<Polygon Visibility="{Binding IsNotMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Points=" 133.5,215.101 61.75,168 8.75,168 8.75,88 61.75,88 133.5,40.899 " Name="Speaker_1_" FillRule="NonZero" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round"/> <Polygon Visibility="{Binding IsNotMutedVisibility}"
<Path Visibility="{Binding VolumeLowVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Low" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"> Points=" 133.5,215.101 61.75,168 8.75,168 8.75,88 61.75,88 133.5,40.899 " Name="Speaker_1_" FillRule="NonZero" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round"/>
<Path Visibility="{Binding VolumeLowVisibility}" Name="Low" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data> <Path.Data>
<PathGeometry Figures=" M166.806 86c0 0 12.528 15.833 12.528 40.167s-12.528 43.823-12.528 43.823" FillRule="NonZero"/> <PathGeometry Figures=" M166.806 86c0 0 12.528 15.833 12.528 40.167s-12.528 43.823-12.528 43.823" FillRule="NonZero"/>
</Path.Data> </Path.Data>
</Path> </Path>
<Path Visibility="{Binding VolumeMediumVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="Medium" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"> <Path Visibility="{Binding VolumeMediumVisibility}" Name="Medium" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data> <Path.Data>
<PathGeometry Figures=" M188.479 57c0 0 21.183 26.769 21.183 67.91c0 41.141-21.183 74.089-21.183 74.089" FillRule="NonZero"/> <PathGeometry Figures=" M188.479 57c0 0 21.183 26.769 21.183 67.91c0 41.141-21.183 74.089-21.183 74.089" FillRule="NonZero"/>
</Path.Data> </Path.Data>
</Path> </Path>
<Path Visibility="{Binding VolumeHighVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="High" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round"> <Path Visibility="{Binding VolumeHighVisibility}" Name="High" StrokeThickness="12" Stroke="#FFFFFFFF" StrokeMiterLimit="10" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round">
<Path.Data> <Path.Data>
<PathGeometry Figures=" M216.737 35.517c0 0 27.944 35.316 27.944 89.593s-27.944 97.75-27.944 97.75" FillRule="NonZero"/> <PathGeometry Figures=" M216.737 35.517c0 0 27.944 35.316 27.944 89.593s-27.944 97.75-27.944 97.75" FillRule="NonZero"/>
</Path.Data> </Path.Data>
</Path> </Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4561" Fill="#FFFFFFFF"> <Path Visibility="{Binding IsMutedVisibility}" Name="path4561" Fill="#FFFFFFFF">
<Path.Data> <Path.Data>
<PathGeometry Figures="M160.503 221.101c-1.717 0-3.421-0.732-4.608-2.153L10.395 44.746c-2.125-2.543-1.785-6.327 0.759-8.451 c2.544-2.125 6.328-1.784 8.451 0.759l145.5 174.201c2.124 2.544 1.784 6.327-0.759 8.452 C163.224 220.644 161.859 221.101 160.503 221.101z" FillRule="NonZero"/> <PathGeometry Figures="M160.503 221.101c-1.717 0-3.421-0.732-4.608-2.153L10.395 44.746c-2.125-2.543-1.785-6.327 0.759-8.451 c2.544-2.125 6.328-1.784 8.451 0.759l145.5 174.201c2.124 2.544 1.784 6.327-0.759 8.452 C163.224 220.644 161.859 221.101 160.503 221.101z" FillRule="NonZero"/>
</Path.Data> </Path.Data>
</Path> </Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4563" Fill="#FFFFFFFF"> <Path Visibility="{Binding IsMutedVisibility}" Name="path4563" Fill="#FFFFFFFF">
<Path.Data> <Path.Data>
<PathGeometry Figures="M127.5 203.984l-62.458-41C64.064 162.342 62.92 162 61.75 162h-47V94h28.967L33.694 82H8.75c-3.313 0-6 2.687-6 6v80 c0 3.313 2.687 6 6 6h51.207l70.25 46.116c0.997 0.654 2.144 0.984 3.293 0.984c0.979 0 1.958-0.238 2.85-0.72 c1.94-1.048 3.15-3.075 3.15-5.28v-6.423l-12-14.367V203.984z" FillRule="NonZero"/> <PathGeometry Figures="M127.5 203.984l-62.458-41C64.064 162.342 62.92 162 61.75 162h-47V94h28.967L33.694 82H8.75c-3.313 0-6 2.687-6 6v80 c0 3.313 2.687 6 6 6h51.207l70.25 46.116c0.997 0.654 2.144 0.984 3.293 0.984c0.979 0 1.958-0.238 2.85-0.72 c1.94-1.048 3.15-3.075 3.15-5.28v-6.423l-12-14.367V203.984z" FillRule="NonZero"/>
</Path.Data> </Path.Data>
</Path> </Path>
<Path Visibility="{Binding IsMutedVisibility}" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path4565" Fill="#FFFFFFFF"> <Path Visibility="{Binding IsMutedVisibility}" Name="path4565" Fill="#FFFFFFFF">
<Path.Data> <Path.Data>
<PathGeometry Figures="M127.5 52.016v104.856l12 14.367V40.899c0-2.205-1.21-4.232-3.15-5.28c-1.939-1.047-4.299-0.947-6.143 0.264L63.19 79.877 l7.744 9.271L127.5 52.016z" FillRule="NonZero"/> <PathGeometry Figures="M127.5 52.016v104.856l12 14.367V40.899c0-2.205-1.21-4.232-3.15-5.28c-1.939-1.047-4.299-0.947-6.143 0.264L63.19 79.877 l7.744 9.271L127.5 52.016z" FillRule="NonZero"/>
</Path.Data> </Path.Data>

View File

@ -4,6 +4,5 @@ namespace MassiveKnob.Plugin.CoreAudio.SetVolume
{ {
public class DeviceSetVolumeActionSettings : BaseDeviceSettings public class DeviceSetVolumeActionSettings : BaseDeviceSettings
{ {
// TODO OSD
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;

View File

@ -73,7 +73,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
serialPorts = SerialPort.GetPortNames(); serialPorts = SerialPort.GetPortNames();
// TODO subscribe to device notification to refresh list // TODO (must have - port from old source) subscribe to device notification to refresh list
} }

View File

@ -12,6 +12,10 @@ namespace MassiveKnob.Plugin
/// <summary> /// <summary>
/// Called right after this instance is created. /// Called right after this instance is created.
/// </summary> /// </summary>
/// <remarks>
/// Do not perform anything but basic initialization until this method is called, as the action
/// instance will be created temporarily at startup to verify it is correctly implemented!
/// </remarks>
/// <param name="context">Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed.</param> /// <param name="context">Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed.</param>
void Initialize(IMassiveKnobActionContext context); void Initialize(IMassiveKnobActionContext context);

View File

@ -1,5 +1,7 @@
using System; using System;
// ReSharper disable UnusedMember.Global - public API
namespace MassiveKnob.Plugin namespace MassiveKnob.Plugin
{ {
/// <summary> /// <summary>

View File

@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EOF/@EntryIndexedValue">EOF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MIN/@EntryIndexedValue">MIN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSD/@EntryIndexedValue">OSD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SOF/@EntryIndexedValue">SOF</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SOF/@EntryIndexedValue">SOF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>

View File

@ -5,7 +5,6 @@ using System.Reactive.Subjects;
using MassiveKnob.Helpers; using MassiveKnob.Helpers;
using MassiveKnob.Plugin; using MassiveKnob.Plugin;
using MassiveKnob.Settings; using MassiveKnob.Settings;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Serilog.Extensions.Logging; using Serilog.Extensions.Logging;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
@ -30,6 +29,9 @@ namespace MassiveKnob.Model
private readonly List<ActionMapping> analogOutputs = new List<ActionMapping>(); private readonly List<ActionMapping> analogOutputs = new List<ActionMapping>();
private readonly List<ActionMapping> digitalOutputs = new List<ActionMapping>(); private readonly List<ActionMapping> digitalOutputs = new List<ActionMapping>();
private readonly Dictionary<int, byte> analogOutputValues = new Dictionary<int, byte>();
private readonly Dictionary<int, bool> digitalOutputValues = new Dictionary<int, bool>();
public MassiveKnobDeviceInfo ActiveDevice public MassiveKnobDeviceInfo ActiveDevice
{ {
@ -58,17 +60,22 @@ namespace MassiveKnob.Model
{ {
activeDevice?.Instance?.Dispose(); activeDevice?.Instance?.Dispose();
void DisposeMappings(IEnumerable<ActionMapping> mappings) void DisposeMappings(ICollection<ActionMapping> mappings)
{ {
foreach (var mapping in mappings) foreach (var mapping in mappings)
mapping?.ActionInfo.Instance?.Dispose(); mapping?.ActionInfo.Instance?.Dispose();
mappings.Clear();
} }
DisposeMappings(analogInputs); lock (settingsLock)
DisposeMappings(digitalInputs); {
DisposeMappings(analogOutputs); DisposeMappings(analogInputs);
DisposeMappings(digitalOutputs); DisposeMappings(digitalInputs);
DisposeMappings(analogOutputs);
DisposeMappings(digitalOutputs);
}
activeDeviceInfoSubject?.Dispose(); activeDeviceInfoSubject?.Dispose();
} }
@ -101,24 +108,29 @@ namespace MassiveKnob.Model
public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index) public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index)
{ {
var list = GetActionMappingList(actionType); lock (settingsLock)
return index >= list.Count ? null : list[index]?.ActionInfo; {
var list = GetActionMappingList(actionType);
return index >= list.Count ? null : list[index]?.ActionInfo;
}
} }
public MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action) public MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action)
{ {
var list = GetActionMappingList(actionType); List<ActionMapping> list;
if (index >= list.Count)
return null;
if (list[index]?.ActionInfo.Info == action)
return list[index].ActionInfo;
list[index]?.ActionInfo.Instance?.Dispose();
lock (settingsLock) lock (settingsLock)
{ {
list = GetActionMappingList(actionType);
if (index >= list.Count)
return null;
if (list[index]?.ActionInfo.Info == action)
return list[index].ActionInfo;
list[index]?.ActionInfo.Instance?.Dispose();
var settingsList = GetActionSettingsList(actionType); var settingsList = GetActionSettingsList(actionType);
while (index >= settingsList.Count) while (index >= settingsList.Count)
settingsList.Add(null); settingsList.Add(null);
@ -139,7 +151,11 @@ namespace MassiveKnob.Model
initializeAfterRegistration = () => actionInstance.Initialize(actionContext); initializeAfterRegistration = () => actionInstance.Initialize(actionContext);
}); });
list[index] = mapping; lock (settingsLock)
{
list[index] = mapping;
}
initializeAfterRegistration?.Invoke(); initializeAfterRegistration?.Invoke();
return mapping?.ActionInfo; return mapping?.ActionInfo;
@ -225,15 +241,15 @@ namespace MassiveKnob.Model
protected T GetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index) where T : class, new() protected T GetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index) where T : class, new()
{ {
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return new T();
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
lock (settingsLock) lock (settingsLock)
{ {
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return new T();
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
var settingsList = GetActionSettingsList(action.ActionType); var settingsList = GetActionSettingsList(action.ActionType);
if (index >= settingsList.Count) if (index >= settingsList.Count)
return new T(); return new T();
@ -245,15 +261,15 @@ namespace MassiveKnob.Model
protected void SetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, T actionSettings) where T : class, new() protected void SetActionSettings<T>(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, T actionSettings) where T : class, new()
{ {
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return;
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
lock (settingsLock) lock (settingsLock)
{ {
var list = GetActionMappingList(action.ActionType);
if (index >= list.Count)
return;
if (list[index]?.Context != context)
throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
var settingsList = GetActionSettingsList(action.ActionType); var settingsList = GetActionSettingsList(action.ActionType);
while (index >= settingsList.Count) while (index >= settingsList.Count)
@ -272,19 +288,29 @@ namespace MassiveKnob.Model
} }
// TODO store output values for when the device connects and should receive initial values
protected void AnalogChanged(IMassiveKnobDeviceContext context, int analogInputIndex, byte value) protected void AnalogChanged(IMassiveKnobDeviceContext context, int analogInputIndex, byte value)
{ {
if (context != activeDeviceContext) if (context != activeDeviceContext)
return; return;
var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog); IMassiveKnobAnalogAction analogAction;
if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null)
return;
if (mapping[analogInputIndex].ActionInfo.Instance is IMassiveKnobAnalogAction analogAction) lock (settingsLock)
analogAction.AnalogChanged(value); {
if (analogOutputValues.TryGetValue(analogInputIndex, out var currentValue) && currentValue == value)
return;
analogOutputValues[analogInputIndex] = value;
var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog);
if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null)
return;
analogAction = mapping[analogInputIndex].ActionInfo.Instance as IMassiveKnobAnalogAction;
}
analogAction?.AnalogChanged(value);
} }
@ -293,44 +319,70 @@ namespace MassiveKnob.Model
if (context != activeDeviceContext) if (context != activeDeviceContext)
return; return;
var mapping = GetActionMappingList(MassiveKnobActionType.InputDigital); IMassiveKnobDigitalAction digitalAction;
if (mapping == null || digitalInputIndex >= mapping.Count || mapping[digitalInputIndex] == null)
return;
if (mapping[digitalInputIndex].ActionInfo.Instance is IMassiveKnobDigitalAction digitalAction) lock (settingsLock)
digitalAction.DigitalChanged(on); {
if (digitalOutputValues.TryGetValue(digitalInputIndex, out var currentValue) && currentValue == on)
return;
digitalOutputValues[digitalInputIndex] = on;
var mapping = GetActionMappingList(MassiveKnobActionType.InputDigital);
if (mapping == null || digitalInputIndex >= mapping.Count || mapping[digitalInputIndex] == null)
return;
digitalAction = mapping[digitalInputIndex].ActionInfo.Instance as IMassiveKnobDigitalAction;
}
digitalAction?.DigitalChanged(on);
} }
public void SetAnalogOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, byte value) public void SetAnalogOutput(IMassiveKnobActionContext context, int index, byte value)
{ {
if (activeDevice == null) if (activeDevice == null)
return; return;
var list = GetActionMappingList(action.ActionType); IMassiveKnobDeviceInstance deviceInstance;
if (index >= list.Count)
return;
if (list[index]?.Context != context) lock (settingsLock)
return; {
var list = GetActionMappingList(MassiveKnobActionType.OutputAnalog);
if (index >= list.Count)
return;
activeDevice.Instance.SetAnalogOutput(index, value); if (context != null && list[index]?.Context != context)
return;
deviceInstance = activeDevice.Instance;
}
deviceInstance.SetAnalogOutput(index, value);
} }
public void SetDigitalOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, bool on) public void SetDigitalOutput(IMassiveKnobActionContext context, int index, bool on)
{ {
if (activeDevice == null) if (activeDevice == null)
return; return;
var list = GetActionMappingList(action.ActionType); IMassiveKnobDeviceInstance deviceInstance;
if (index >= list.Count)
return;
if (list[index]?.Context != context) lock (settingsLock)
return; {
var list = GetActionMappingList(MassiveKnobActionType.OutputDigital);
if (index >= list.Count)
return;
activeDevice.Instance.SetDigitalOutput(index, on); if (context != null && list[index]?.Context != context)
return;
deviceInstance = activeDevice.Instance;
}
deviceInstance.SetDigitalOutput(index, on);
} }
@ -424,6 +476,14 @@ namespace MassiveKnob.Model
ActiveDevice.Info, ActiveDevice.Info,
ActiveDevice.Instance, ActiveDevice.Instance,
specs); specs);
// Send out all cached values to initialize the device's outputs
foreach (var pair in analogOutputValues.Where(pair => pair.Key < specs.AnalogOutputCount))
SetAnalogOutput(null, pair.Key, pair.Value);
foreach (var pair in digitalOutputValues.Where(pair => pair.Key < specs.DigitalOutputCount))
SetDigitalOutput(null, pair.Key, pair.Value);
} }
@ -520,14 +580,13 @@ namespace MassiveKnob.Model
public void Connecting() public void Connecting()
{ {
// TODO update status ? // TODO (should have) update status ?
} }
public void Connected(DeviceSpecs specs) public void Connected(DeviceSpecs specs)
{ {
// TODO update status ? // TODO (should have) update status ?
// TODO send out initial values for outputs
owner.UpdateActiveDeviceSpecs(this, specs); owner.UpdateActiveDeviceSpecs(this, specs);
} }
@ -535,7 +594,7 @@ namespace MassiveKnob.Model
public void Disconnected() public void Disconnected()
{ {
// TODO update status ? // TODO (should have) update status ?
} }
@ -581,13 +640,13 @@ namespace MassiveKnob.Model
public void SetAnalogOutput(byte value) public void SetAnalogOutput(byte value)
{ {
owner.SetAnalogOutput(this, action, index, value); owner.SetAnalogOutput(this, index, value);
} }
public void SetDigitalOutput(bool on) public void SetDigitalOutput(bool on)
{ {
owner.SetDigitalOutput(this, action, index, on); owner.SetDigitalOutput(this, index, on);
} }
} }
} }

View File

@ -4,6 +4,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using MassiveKnob.Plugin; using MassiveKnob.Plugin;
using Serilog;
using Serilog.Extensions.Logging;
namespace MassiveKnob.Model namespace MassiveKnob.Model
{ {
@ -29,9 +31,16 @@ namespace MassiveKnob.Model
public class PluginManager : IPluginManager public class PluginManager : IPluginManager
{ {
private readonly ILogger logger;
private readonly List<IMassiveKnobPlugin> plugins = new List<IMassiveKnobPlugin>(); private readonly List<IMassiveKnobPlugin> plugins = new List<IMassiveKnobPlugin>();
public PluginManager(ILogger logger)
{
this.logger = logger;
}
public IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins() public IEnumerable<IMassiveKnobDevicePlugin> GetDevicePlugins()
{ {
return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast<IMassiveKnobDevicePlugin>(); return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast<IMassiveKnobDevicePlugin>();
@ -101,7 +110,7 @@ namespace MassiveKnob.Model
} }
private static void ValidateRegistration(string filename, IMassiveKnobPlugin plugin, RegisteredIds registeredIds) private void ValidateRegistration(string filename, IMassiveKnobPlugin plugin, RegisteredIds registeredIds)
{ {
// Make sure all GUIDs are actually unique and someone has not copy/pasted a plugin without // Make sure all GUIDs are actually unique and someone has not copy/pasted a plugin without
// modifying the values. This way we can safely make that assumption in other code. // modifying the values. This way we can safely make that assumption in other code.
@ -133,13 +142,42 @@ namespace MassiveKnob.Model
throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename); throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename);
registeredIds.ActionById.Add(action.ActionId, filename); registeredIds.ActionById.Add(action.ActionId, filename);
ValidateActionType(action);
// TODO check ActionType vs. implemented interfaces
} }
} }
} }
private void ValidateActionType(IMassiveKnobAction action)
{
var instance = action.Create(new SerilogLoggerProvider(logger).CreateLogger(null));
if (instance == null)
throw new NullReferenceException("Create method must not return null");
switch (action.ActionType)
{
case MassiveKnobActionType.InputAnalog:
if (!(instance is IMassiveKnobAnalogAction))
throw new InvalidCastException("InputAnalog action must implement IMassiveKnobAnalogAction");
break;
case MassiveKnobActionType.InputDigital:
if (!(instance is IMassiveKnobDigitalAction))
throw new InvalidCastException("InputDigital action must implement IMassiveKnobDigitalAction");
break;
case MassiveKnobActionType.OutputAnalog:
case MassiveKnobActionType.OutputDigital:
break;
default:
throw new ArgumentOutOfRangeException(nameof(action.ActionType), action.ActionType, @"Unsupported action type: " + (int)action.ActionType);
}
}
private class RegisteredIds private class RegisteredIds
{ {
public readonly Dictionary<Guid, string> PluginById = new Dictionary<Guid, string>(); public readonly Dictionary<Guid, string> PluginById = new Dictionary<Guid, string>();

View File

@ -21,7 +21,7 @@ namespace MassiveKnob
[STAThread] [STAThread]
public static int Main() public static int Main()
{ {
// TODO make configurable // TODO (should have) make configurable
var loggingLevelSwitch = new LoggingLevelSwitch(); var loggingLevelSwitch = new LoggingLevelSwitch();
//var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose); //var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
@ -35,7 +35,7 @@ namespace MassiveKnob
.CreateLogger(); .CreateLogger();
var pluginManager = new PluginManager(); var pluginManager = new PluginManager(logger);
var messages = new StringBuilder(); var messages = new StringBuilder();
pluginManager.Load((exception, filename) => pluginManager.Load((exception, filename) =>

View File

@ -10,7 +10,6 @@ namespace MassiveKnob.ViewModel
{ {
public class InputOutputViewModel : INotifyPropertyChanged public class InputOutputViewModel : INotifyPropertyChanged
{ {
private readonly SettingsViewModel settingsViewModel;
private readonly IMassiveKnobOrchestrator orchestrator; private readonly IMassiveKnobOrchestrator orchestrator;
private readonly MassiveKnobActionType actionType; private readonly MassiveKnobActionType actionType;
private readonly int index; private readonly int index;
@ -61,7 +60,6 @@ namespace MassiveKnob.ViewModel
public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType actionType, int index) public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType actionType, int index)
{ {
this.settingsViewModel = settingsViewModel;
this.orchestrator = orchestrator; this.orchestrator = orchestrator;
this.actionType = actionType; this.actionType = actionType;
this.index = index; this.index = index;

View File

@ -10,7 +10,7 @@ using MassiveKnob.Plugin;
namespace MassiveKnob.ViewModel namespace MassiveKnob.ViewModel
{ {
// TODO better design-time version // TODO (nice to have) better design-time version
public class SettingsViewModel : INotifyPropertyChanged public class SettingsViewModel : INotifyPropertyChanged
{ {
private readonly IMassiveKnobOrchestrator orchestrator; private readonly IMassiveKnobOrchestrator orchestrator;

@ -1 +1 @@
Subproject commit 223cfafaf40e0e26e7660860ddfd755cb671f81b Subproject commit 6db7da6234713a50a2c278c00bcd710249738e5e