diff --git a/OpenEphys.Onix1/ConfigureAnalogIO.cs b/OpenEphys.Onix1/ConfigureAnalogIO.cs index d1f6ba8..69ce0a0 100644 --- a/OpenEphys.Onix1/ConfigureAnalogIO.cs +++ b/OpenEphys.Onix1/ConfigureAnalogIO.cs @@ -286,6 +286,7 @@ public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContex static class AnalogIO { public const int ID = 22; + public const uint MinimumVersion = 2; // constants public const int ChannelCount = 12; diff --git a/OpenEphys.Onix1/ConfigureBno055.cs b/OpenEphys.Onix1/ConfigureBno055.cs index 07daf71..2ae1b8f 100644 --- a/OpenEphys.Onix1/ConfigureBno055.cs +++ b/OpenEphys.Onix1/ConfigureBno055.cs @@ -70,6 +70,7 @@ public override IObservable Process(IObservable source static class Bno055 { public const int ID = 9; + public const uint MinimumVersion = 2; // constants public const float EulerAngleScale = 1f / 16; // 1 degree = 16 LSB diff --git a/OpenEphys.Onix1/ConfigureDigitalIO.cs b/OpenEphys.Onix1/ConfigureDigitalIO.cs index 6498d69..93a9669 100644 --- a/OpenEphys.Onix1/ConfigureDigitalIO.cs +++ b/OpenEphys.Onix1/ConfigureDigitalIO.cs @@ -131,6 +131,7 @@ public override IObservable Process(IObservable source static class DigitalIO { public const int ID = 18; + public const uint MinimumVersion = 2; // managed registers public const uint ENABLE = 0x0; diff --git a/OpenEphys.Onix1/ConfigureHarpSyncInput.cs b/OpenEphys.Onix1/ConfigureHarpSyncInput.cs index 141c870..21ef788 100644 --- a/OpenEphys.Onix1/ConfigureHarpSyncInput.cs +++ b/OpenEphys.Onix1/ConfigureHarpSyncInput.cs @@ -94,6 +94,7 @@ public override IObservable Process(IObservable source static class HarpSyncInput { public const int ID = 30; + public const uint MinimumVersion = 2; // managed registers public const uint ENABLE = 0x0; // Enable or disable the data stream diff --git a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs index decfb0b..21bcc02 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs @@ -62,6 +62,7 @@ public override IObservable Process(IObservable source static class Headstage64ElectricalStimulator { public const int ID = 4; + public const uint MinimumVersion = 3; // NB: could be read from REZ but these are constant public const double DacBitDepth = 16; diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs index d98500d..0051aec 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -62,6 +62,7 @@ public override IObservable Process(IObservable source static class Headstage64OpticalStimulator { public const int ID = 5; + public const uint MinimumVersion = 3; // NB: can be read with MINRHEOR and POTRES, but will not change public const uint MinRheostatResistanceOhms = 590; diff --git a/OpenEphys.Onix1/ConfigureHeartbeat.cs b/OpenEphys.Onix1/ConfigureHeartbeat.cs index c5bc501..c410ca8 100644 --- a/OpenEphys.Onix1/ConfigureHeartbeat.cs +++ b/OpenEphys.Onix1/ConfigureHeartbeat.cs @@ -84,6 +84,7 @@ public override IObservable Process(IObservable source static class Heartbeat { public const int ID = 12; + public const uint MinimumVersion = 1; public const uint ENABLE = 0; // Enable the heartbeat public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Default results in 10 Hz heartbeat. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. diff --git a/OpenEphys.Onix1/ConfigureLoadTester.cs b/OpenEphys.Onix1/ConfigureLoadTester.cs index 107d0ee..b966826 100644 --- a/OpenEphys.Onix1/ConfigureLoadTester.cs +++ b/OpenEphys.Onix1/ConfigureLoadTester.cs @@ -122,6 +122,7 @@ public override IObservable Process(IObservable source static class LoadTester { public const int ID = 27; + public const uint MinimumVersion = 2; public const uint ENABLE = 0; public const uint CLK_DIV = 1; diff --git a/OpenEphys.Onix1/ConfigureMemoryMonitor.cs b/OpenEphys.Onix1/ConfigureMemoryMonitor.cs index be7cb56..f5408ad 100644 --- a/OpenEphys.Onix1/ConfigureMemoryMonitor.cs +++ b/OpenEphys.Onix1/ConfigureMemoryMonitor.cs @@ -87,6 +87,7 @@ public override IObservable Process(IObservable source static class MemoryMonitor { public const int ID = 28; + public const uint MinimumVersion = 2; public const uint ENABLE = 0; // Enable the monitor public const uint CLK_DIV = 1; // Sample clock divider ratio. Values less than CLK_HZ / 10e6 Hz will result in 1kHz. diff --git a/OpenEphys.Onix1/ConfigureNric1384.cs b/OpenEphys.Onix1/ConfigureNric1384.cs index 3d7d936..1c16dd3 100644 --- a/OpenEphys.Onix1/ConfigureNric1384.cs +++ b/OpenEphys.Onix1/ConfigureNric1384.cs @@ -141,6 +141,7 @@ public override IObservable Process(IObservable source static class Nric1384 { public const int ID = 33; + public const uint MinimumVersion = 1; public const int I2cAddress = 0x70; public const int ChannelCount = 384; diff --git a/OpenEphys.Onix1/ConfigureOutputClock.cs b/OpenEphys.Onix1/ConfigureOutputClock.cs index f5898db..2c71e71 100644 --- a/OpenEphys.Onix1/ConfigureOutputClock.cs +++ b/OpenEphys.Onix1/ConfigureOutputClock.cs @@ -174,6 +174,7 @@ public override IObservable Process(IObservable source static class OutputClock { public const int ID = 20; + public const uint MinimumVersion = 1; public const uint NULL = 0; // No command public const uint CLOCK_GATE = 1; // Output gate. Bit 0 = 0 is disabled, Bit 0 = 1 is enabled. diff --git a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs index 8defeb9..9f6f438 100644 --- a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs +++ b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs @@ -78,6 +78,7 @@ public override IObservable Process(IObservable source static class PersistentHeartbeat { public const int ID = 35; + public const uint MinimumVersion = 1; public const uint ENABLE = 0; // Heartbeat enable state (read only; always enabled for this device). public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Minimum value is CLK_HZ / 10e6. diff --git a/OpenEphys.Onix1/ConfigurePortController.cs b/OpenEphys.Onix1/ConfigurePortController.cs index 1c2e415..0bad98f 100644 --- a/OpenEphys.Onix1/ConfigurePortController.cs +++ b/OpenEphys.Onix1/ConfigurePortController.cs @@ -104,6 +104,7 @@ public override IObservable Process(IObservable source internal static class PortController { public const int ID = 23; + public const uint MinimumVersion = 2; public const uint ENABLE = 0; // The LSB is used to enable or disable the device data stream public const uint GPOSTATE = 1; // GPO output state (bits 31 downto 3: ignore. bits 2 downto 0: ‘1’ = high, ‘0’ = low) diff --git a/OpenEphys.Onix1/ConfigureRhd2164.cs b/OpenEphys.Onix1/ConfigureRhd2164.cs index f9ed506..6b829c8 100644 --- a/OpenEphys.Onix1/ConfigureRhd2164.cs +++ b/OpenEphys.Onix1/ConfigureRhd2164.cs @@ -120,6 +120,7 @@ public override IObservable Process(IObservable source static class Rhd2164 { public const int ID = 3; + public const uint MinimumVersion = 2; // constants public const int AmplifierChannelCount = 64; diff --git a/OpenEphys.Onix1/ConfigureRhs2116.cs b/OpenEphys.Onix1/ConfigureRhs2116.cs index c871c54..ce2adb3 100644 --- a/OpenEphys.Onix1/ConfigureRhs2116.cs +++ b/OpenEphys.Onix1/ConfigureRhs2116.cs @@ -200,6 +200,7 @@ public override IObservable Process(IObservable source static class Rhs2116 { public const int ID = 31; + public const uint MinimumVersion = 3; // constants public const int AmplifierChannelCount = 16; diff --git a/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs b/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs index 7b9ab4d..7262fdb 100644 --- a/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs +++ b/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs @@ -209,6 +209,7 @@ static void WriteStimulusSequence(DeviceContext device, Rhs2116StimulusSequence static class Rhs2116Trigger { public const int ID = 32; + public const uint MinimumVersion = 2; // managed registers public const uint ENABLE = 0; // Enable or disable the trigger event datastream diff --git a/OpenEphys.Onix1/ConfigureTS4231V1.cs b/OpenEphys.Onix1/ConfigureTS4231V1.cs index 31a8634..0069fca 100644 --- a/OpenEphys.Onix1/ConfigureTS4231V1.cs +++ b/OpenEphys.Onix1/ConfigureTS4231V1.cs @@ -60,6 +60,7 @@ public override IObservable Process(IObservable source static class TS4231V1 { public const int ID = 25; + public const uint MinimumVersion = 2; // managed registers public const uint ENABLE = 0x0; // Enable or disable the data output stream diff --git a/OpenEphys.Onix1/ContextHelper.cs b/OpenEphys.Onix1/ContextHelper.cs index 4ef429f..cf8c795 100644 --- a/OpenEphys.Onix1/ContextHelper.cs +++ b/OpenEphys.Onix1/ContextHelper.cs @@ -19,6 +19,13 @@ public static DeviceContext GetDeviceContext(this ContextTask context, uint addr ThrowInvalidDeviceException(expectedType, address); } + var minVersion = GetMinimumFirmwareVersion(expectedType); + + if (device.Version < minVersion) + { + ThrowInvalidDeviceVersionException(expectedType, address, device.Version, minVersion); + } + return new DeviceContext(context, device); } @@ -60,6 +67,23 @@ static int GetDeviceID(Type deviceType) return (int)fieldInfo.GetRawConstantValue(); } + static uint GetMinimumFirmwareVersion(Type deviceType) + { + var fieldInfo = deviceType.GetField( + "MinimumVersion", + BindingFlags.Static | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.IgnoreCase); + if (fieldInfo == null || !fieldInfo.IsLiteral) + { + throw new ArgumentException($"The specified device type {deviceType} does not have a const MinimumVersion field.", nameof(deviceType)); + } + + return (uint)fieldInfo.GetRawConstantValue(); + } + + static void ThrowDeviceNotFoundException(Type expectedType, uint address) { throw new InvalidOperationException($"Device '{expectedType.Name}' was not found in the device table at address {address}."); @@ -70,6 +94,21 @@ static void ThrowInvalidDeviceException(Type expectedType, uint address) throw new InvalidOperationException($"Invalid device ID. The device found at address {address} is not a '{expectedType.Name}' device."); } + static void ThrowInvalidDeviceVersionException(Type expectedType, uint address, uint deviceVersion, uint minimumVersion) + { + var assembly = Assembly.GetExecutingAssembly(); + string libraryName = assembly.GetName().Name ?? "Unknown"; + string libraryVersion = assembly.GetName().Version.ToString() ?? "Unknown"; + + Console.Error.WriteLine($"Error: The {expectedType.Name} device at address {address} is v{deviceVersion}, " + + $"but v{minimumVersion} is required by {libraryName} {libraryVersion}."); + Console.Error.WriteLine($"In order to use {libraryName} {libraryVersion} with this device, you will need to update it firmware. " + + $"Firmware update files and instructions can be found at https://open-ephys.github.io/onix-docs/index.html."); + + throw new InvalidOperationException($"Invalid device version. The {expectedType.Name} device at address {address} is v{deviceVersion}, " + + $"but v{minimumVersion} is required."); + } + internal static bool CheckDeviceType(Type deviceType, Type targetType) { if (deviceType == targetType) return true; diff --git a/OpenEphys.Onix1/DS90UB9x.cs b/OpenEphys.Onix1/DS90UB9x.cs index b984e47..24279de 100644 --- a/OpenEphys.Onix1/DS90UB9x.cs +++ b/OpenEphys.Onix1/DS90UB9x.cs @@ -6,6 +6,7 @@ namespace OpenEphys.Onix1 static class DS90UB9x { public const int ID = 24; + public const uint MinimumVersion = 3; // managed registers public const uint ENABLE = 0x8000;