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