diff --git a/WirelessMicSuiteServer/IWirelessMicReceiver.cs b/WirelessMicSuiteServer/IWirelessMicReceiver.cs index 75bf928..8f301ae 100644 --- a/WirelessMicSuiteServer/IWirelessMicReceiver.cs +++ b/WirelessMicSuiteServer/IWirelessMicReceiver.cs @@ -127,6 +127,10 @@ public interface IWirelessMic : INotifyPropertyChanged /// public abstract int? Gain { get; set; } /// + /// Transmitter sensitivity in dB. + /// + public abstract int? Sensitivity { get; set; } + /// /// Receiver output gain in dB. /// public abstract int? OutputGain { get; set; } @@ -147,6 +151,10 @@ public interface IWirelessMic : INotifyPropertyChanged /// public abstract int? Channel { get; set; } /// + /// The type of UI lock that is enabled on the wireless transmitter. + /// + public abstract LockMode? LockMode { get; set; } + /// /// Transmitter model type identifier, ie: UR1, UR1H, UR2. /// public abstract string? TransmitterType { get; } @@ -182,6 +190,21 @@ public enum DiversityIndicator D = 1 << 3, } +/// +/// Represents what type of UI lock is enabled on a wireless transmitter. +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +[Flags] +public enum LockMode +{ + None, + Mute = 1 << 0, + Power = 1 << 1, + Frequency = 1 << 2, + FrequencyPower = Power | Frequency, + All = Mute | Power | Frequency +} + /// /// A data structure representing a single sample of metering data. /// @@ -271,6 +294,8 @@ public struct WirelessMicData(IWirelessMic other) : IWirelessMic /// [JsonInclude] public int? Gain { get; set; } = other.Gain; /// + [JsonInclude] public int? Sensitivity { get; set; } = other.Sensitivity; + /// [JsonInclude] public int? OutputGain { get; set; } = other.OutputGain; /// [JsonInclude] public bool? Mute { get; set; } = other.Mute; @@ -281,6 +306,8 @@ public struct WirelessMicData(IWirelessMic other) : IWirelessMic /// [JsonInclude] public int? Channel { get; set; } = other.Channel; /// + [JsonInclude] public LockMode? LockMode { get; set; } = other.LockMode; + /// [JsonInclude] public string? TransmitterType { get; init; } = other.TransmitterType; /// [JsonInclude] public float? BatteryLevel { get; init; } = other.BatteryLevel; diff --git a/WirelessMicSuiteServer/ShureUHFR/ShureWirelessMic.cs b/WirelessMicSuiteServer/ShureUHFR/ShureWirelessMic.cs index 7da0562..043e521 100644 --- a/WirelessMicSuiteServer/ShureUHFR/ShureWirelessMic.cs +++ b/WirelessMicSuiteServer/ShureUHFR/ShureWirelessMic.cs @@ -18,11 +18,13 @@ public class ShureWirelessMic : IWirelessMic private readonly uint uid; private string? name; private int? gain; + private int? sensitivity; private int? outputGain; private bool? mute; private ulong? frequency; private int? group; private int? channel; + private LockMode? lockMode; private string? transmitterType; private float? batteryLevel; @@ -46,17 +48,26 @@ public int? Gain get => gain; set { - if (value != null && value >= 0 && value <= 32) - SetAsync("TX_IR_GAIN", value.Value.ToString()); + if (value != null && value >= -10 && value <= 20) + SetAsync("TX_IR_GAIN", Math.Abs(value.Value+10).ToString()); } } + public int? Sensitivity + { + get => sensitivity; + set + { + if (value != null && value >= -10 && value <= 15) + SetAsync("TX_IR_TRIM", value.Value.ToString()); + } + } public int? OutputGain { get => outputGain; set { - if (value != null && value >= 0 && value <= 32) - SetAsync("AUDIO_GAIN", value.Value.ToString()); + if (value != null && value >= -32 && value <= 0) + SetAsync("AUDIO_GAIN", Math.Abs(value.Value).ToString()); } } public bool? Mute @@ -77,6 +88,33 @@ public ulong? Frequency SetAsync("FREQUENCY", (value.Value / 1000).ToString("000000")); } } + public LockMode? LockMode + { + get => lockMode; + set + { + if (value != null) + { + switch (value) + { + case WirelessMicSuiteServer.LockMode.None: + SetAsync("TX_IR_LOCK", "UNLOCK"); + break; + case WirelessMicSuiteServer.LockMode.Power: + SetAsync("TX_IR_LOCK", "POWER"); + break; + case WirelessMicSuiteServer.LockMode.Frequency: + SetAsync("TX_IR_LOCK", "FREQ"); + break; + case WirelessMicSuiteServer.LockMode.FrequencyPower: + SetAsync("TX_IR_LOCK", "FREQ_AND_POWER"); + break; + default: + break; + } + } + } + } public int? Group { get => group; @@ -142,6 +180,9 @@ private void SendStartupCommands() receiver.Send($"* GET {receiverNo} TX_BAT *"); receiver.Send($"* GET {receiverNo} TX_BAT_MINS *"); receiver.Send($"* GET {receiverNo} TX_POWER *"); + receiver.Send($"* GET {receiverNo} TX_GAIN *"); + receiver.Send($"* GET {receiverNo} TX_TRIM *"); + receiver.Send($"* GET {receiverNo} TX_LOCK *"); } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) @@ -231,6 +272,21 @@ internal void ParseCommand(ShureCommandType type, ReadOnlySpan cmd, ReadOn else CommandError(fullMsg, "Couldn't parse transmitter gain, or gain was out of the range -10:20."); break; + case "TX_IR_TRIM": + case "TX_TRIM": + if (int.TryParse(args, out int ntrim) && ntrim is >= -10 and <= 15) + { + sensitivity = ntrim; + OnPropertyChanged(nameof(Sensitivity)); + } + else if (args.SequenceEqual("UNKNOWN")) + { + sensitivity = null; + OnPropertyChanged(nameof(Sensitivity)); + } + else + CommandError(fullMsg, "Couldn't parse transmitter gain, or gain was out of the range -10:20."); + break; case "SQUELCH": if (int.TryParse(args, out int nsquelch)) { @@ -295,10 +351,33 @@ internal void ParseCommand(ShureCommandType type, ReadOnlySpan cmd, ReadOn transmitterType = args.ToString(); OnPropertyChanged(nameof(TransmitterType)); break; - case "FRONT_PANEL_LOCK": case "TX_IR_LOCK": + case "TX_LOCK": + switch(args) + { + case "UNLOCK": + lockMode = WirelessMicSuiteServer.LockMode.None; + OnPropertyChanged(nameof(LockMode)); + break; + case "POWER": + lockMode = WirelessMicSuiteServer.LockMode.Power; + OnPropertyChanged(nameof(LockMode)); + break; + case "FREQ": + lockMode = WirelessMicSuiteServer.LockMode.Frequency; + OnPropertyChanged(nameof(LockMode)); + break; + case "FREQ_AND_POWER": + lockMode = WirelessMicSuiteServer.LockMode.FrequencyPower; + OnPropertyChanged(nameof(LockMode)); + break; + case "NOCHANGE": + default: + break; + } + break; + case "FRONT_PANEL_LOCK": case "TX_IR_POWER": - case "TX_IR_TRIM": case "TX_IR_BAT_TYPE": case "TX_IR_CUSTOM_GPS": case "AUDIO_INDICATOR": @@ -307,8 +386,6 @@ internal void ParseCommand(ShureCommandType type, ReadOnlySpan cmd, ReadOn case "TX_POWER": case "TX_CHANGE_BAT": case "TX_EXT_DC": - case "TX_TRIM": - case "TX_LOCK": // Unimplemented for now break; default: @@ -391,6 +468,9 @@ private void ParseSampleCommand(ReadOnlySpan args, ReadOnlySpan full private void ParseRFLevelCommand(ReadOnlySpan nargs, ReadOnlySpan args, ReadOnlySpan fullMsg) { + if (rfScanInProgress == null) + return; + // "* RFLEVEL n 10 578000 100 578025 100 578050 100 578075 100 578100 100 578125 100 578150 100 578175 100 578200 100 578225 100 *" // * RFLEVEL n numSamples [freq level]... * // level: is in - dBm diff --git a/WirelessMicSuiteServer/WebSocketAPI.cs b/WirelessMicSuiteServer/WebSocketAPI.cs index 9439ef5..0353014 100644 --- a/WirelessMicSuiteServer/WebSocketAPI.cs +++ b/WirelessMicSuiteServer/WebSocketAPI.cs @@ -142,6 +142,7 @@ public class WebSocketAPIManager : IDisposable private readonly List clients; private readonly Timer meteringTimer; private bool isSendingMeteringMsg; + private readonly JsonSerializerOptions jsonSerializerOptions; public WebSocketAPIManager(WirelessMicManager micManager, int meterInterval) { @@ -150,6 +151,13 @@ public WebSocketAPIManager(WirelessMicManager micManager, int meterInterval) propCache = []; BuildPropCache(); + jsonSerializerOptions = new() + { + IncludeFields = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + }; + ((INotifyCollectionChanged)micManager.Receivers).CollectionChanged += (o, e) => { switch (e.Action) @@ -205,6 +213,7 @@ private void MeteringTimer_Elapsed(object? sender, ElapsedEventArgs e) foreach (var client in clients) client.SendMessage(json); } + catch { } finally { isSendingMeteringMsg = false; @@ -251,7 +260,7 @@ private void OnPropertyChanged(object? target, PropertyChangedEventArgs args) object? val = cached.prop.GetValue(target); var propNotif = new PropertyChangeNotification(cached.name, val, uid); - var json = JsonSerializer.SerializeToUtf8Bytes(propNotif); + var json = JsonSerializer.SerializeToUtf8Bytes(propNotif, jsonSerializerOptions); foreach (var client in clients) client.SendMessage(json); diff --git a/WirelessMicSuiteServer/WirelessMicManager.cs b/WirelessMicSuiteServer/WirelessMicManager.cs index 38bcfa7..ff1fc3e 100644 --- a/WirelessMicSuiteServer/WirelessMicManager.cs +++ b/WirelessMicSuiteServer/WirelessMicManager.cs @@ -13,7 +13,16 @@ public class WirelessMicManager : IDisposable //public ObservableCollection Receivers { get; init; } public ReadOnlyObservableCollection Receivers { get; init; } // TODO: There's a race condition where if receivers get added or removed while this is being enumerated an exception is thrown. - public IEnumerable WirelessMics => new WirelessMicEnumerator(Receivers); + public IEnumerable WirelessMics + { + get + { + lock (receivers) + { + return new WirelessMicEnumerator(Receivers); + } + } + } public WirelessMicManager(IEnumerable? receiverManagers) { @@ -25,32 +34,35 @@ public WirelessMicManager(IEnumerable? receiverMana // Attempt to synchronise the observable collections rm.Receivers.CollectionChanged += (o, e) => { - switch (e.Action) + lock (receivers) { - case System.Collections.Specialized.NotifyCollectionChangedAction.Add: - if (e.NewItems != null) - foreach (IWirelessMicReceiver obj in e.NewItems) - receivers.Add(obj); - break; - case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: - if (e.OldItems != null) - foreach (IWirelessMicReceiver obj in e.OldItems) - receivers.Remove(obj); - break; - case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: - if (e.OldItems != null && e.NewItems != null && e.OldItems.Count == e.NewItems.Count) - for (int i = 0; i < e.OldItems.Count; i++) - { - receivers.Remove((IWirelessMicReceiver)e.OldItems[i]!); - receivers.Add((IWirelessMicReceiver)e.NewItems[i]!); - } - break; - case System.Collections.Specialized.NotifyCollectionChangedAction.Move: - break; - case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: - throw new NotSupportedException(); - default: - throw new InvalidOperationException(); + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + if (e.NewItems != null) + foreach (IWirelessMicReceiver obj in e.NewItems) + receivers.Add(obj); + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + if (e.OldItems != null) + foreach (IWirelessMicReceiver obj in e.OldItems) + receivers.Remove(obj); + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + if (e.OldItems != null && e.NewItems != null && e.OldItems.Count == e.NewItems.Count) + for (int i = 0; i < e.OldItems.Count; i++) + { + receivers.Remove((IWirelessMicReceiver)e.OldItems[i]!); + receivers.Add((IWirelessMicReceiver)e.NewItems[i]!); + } + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Move: + break; + case System.Collections.Specialized.NotifyCollectionChangedAction.Reset: + throw new NotSupportedException(); + default: + throw new InvalidOperationException(); + } } }; } diff --git a/WirelessMicSuiteServer/WirelessMicSuiteServer.csproj b/WirelessMicSuiteServer/WirelessMicSuiteServer.csproj index b9d9f60..c0f1295 100644 --- a/WirelessMicSuiteServer/WirelessMicSuiteServer.csproj +++ b/WirelessMicSuiteServer/WirelessMicSuiteServer.csproj @@ -5,7 +5,7 @@ enable enable Wireless Mic Suite Server - 1.2.1 + 1.3.1 Thomas Mathieson Copyright Thomas Mathieson 2024 https://github.com/space928/WirelessMicSuiteServer