diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs index 130d76eb..1652775e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureBreakoutBoard.cs @@ -11,10 +11,14 @@ public class ConfigureBreakoutBoard : HubDeviceFactory [TypeConverter(typeof(HubDeviceConverter))] public ConfigureAnalogIO AnalogIO { get; set; } = new(); + [TypeConverter(typeof(HubDeviceConverter))] + public ConfigureMemoryMonitor MemoryMonitor { get; set; } = new(); + internal override IEnumerable GetDevices() { yield return Heartbeat; yield return AnalogIO; + yield return MemoryMonitor; } } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs new file mode 100644 index 00000000..633407c2 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureMemoryMonitor.cs @@ -0,0 +1,57 @@ +using System; +using System.ComponentModel; +using Bonsai; + +namespace OpenEphys.Onix +{ + public class ConfigureMemoryMonitor : SingleDeviceFactory + { + + public ConfigureMemoryMonitor() + : base(typeof(MemoryMonitor)) + { + DeviceAddress = 10; + } + + [Category(ConfigurationCategory)] + [Description("Specifies whether the monitor device is enabled.")] + public bool Enable { get; set; } = false; + + [Range(1, 1000)] + [Category(ConfigurationCategory)] + [Description("Frequency at which memory usage is recorded (Hz).")] + public uint SampleFrequency { get; set; } = 10; + + public override IObservable Process(IObservable source) + { + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + var device = context.GetDeviceContext(deviceAddress, MemoryMonitor.ID); + device.WriteRegister(MemoryMonitor.ENABLE, 1); + device.WriteRegister(MemoryMonitor.CLK_DIV, device.ReadRegister(MemoryMonitor.CLK_HZ) / SampleFrequency); + + return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + }); + } + } + + static class MemoryMonitor + { + public const int ID = 28; + + 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. + public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV + public const uint TOTAL_MEM = 3; // Total available memory in 32-bit words + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(MemoryMonitor)) + { + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs new file mode 100644 index 00000000..045eb8c5 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsage.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix +{ + public class MemoryUsage : Source + { + [TypeConverter(typeof(MemoryMonitor.NameConverter))] + public string DeviceName { get; set; } + + public override IObservable Generate() + { + return Observable.Using( + () => DeviceManager.ReserveDevice(DeviceName), + disposable => disposable.Subject.SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(MemoryMonitor)); + var totalMemory = device.ReadRegister(MemoryMonitor.TOTAL_MEM); + + return deviceInfo.Context.FrameReceived + .Where(frame => frame.DeviceAddress == device.Address) + .Select(frame => new MemoryUsageDataFrame(frame, totalMemory)); + })); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs new file mode 100644 index 00000000..b2cb8c0d --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/MemoryUsageDataFrame.cs @@ -0,0 +1,37 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class MemoryUsageDataFrame + { + public unsafe MemoryUsageDataFrame(oni.Frame frame, uint totalMemory) + { + var payload = (MemoryUsagePayload*)frame.Data.ToPointer(); + + FrameClock = frame.Clock; + DeviceAddress = frame.DeviceAddress; + HubClock = payload->HubClock; + PercentUsed = 100.0 * payload->Usage / totalMemory; + BytesUsed = payload->Usage * 4; + + } + + public ulong FrameClock { get; private set; } + + public uint DeviceAddress { get; private set; } + + public ulong HubClock { get; } + + public double PercentUsed { get; } + + public uint BytesUsed { get; } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MemoryUsagePayload + { + public ulong HubClock; + public uint Usage; + } +}