Skip to content

Commit 2327e9a

Browse files
committed
Add support for persistent heartbeat device
- This will come in the next onix firmware verion - Has fixed enable state which obviates the hacks we had to hide the enable property in ConfigureatHeartbeat.cs
1 parent f6d20d4 commit 2327e9a

File tree

6 files changed

+142
-26
lines changed

6 files changed

+142
-26
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1414
<UseArtifactsOutput>true</UseArtifactsOutput>
1515
<PackageIcon>icon.png</PackageIcon>
16-
<VersionPrefix>0.5.1</VersionPrefix>
16+
<VersionPrefix>0.6.0</VersionPrefix>
1717
<VersionSuffix></VersionSuffix>
1818
<LangVersion>10.0</LangVersion>
1919
<Features>strict</Features>

OpenEphys.Onix1/ConfigureBreakoutBoard.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ public class ConfigureBreakoutBoard : MultiDeviceFactory
2626
/// Gets or sets the heartbeat configuration.
2727
/// </summary>
2828
/// <remarks>
29-
/// This heartbeat is always enabled and beats at 100 Hz.
29+
/// This heartbeat is always enabled and beats at a minimum of 100 Hz.
3030
/// </remarks>
31-
[TypeConverter(typeof(HeartbeatSingleDeviceFactoryConverter))]
31+
[TypeConverter(typeof(SingleDeviceFactoryConverter))]
3232
[Description("Specifies the configuration for the heartbeat device in the ONIX breakout board.")]
3333
[Category(DevicesCategory)]
34-
public ConfigureHeartbeat Heartbeat { get; set; } = new ConfigureHeartbeat { Enable = true, BeatsPerSecond = 100 };
34+
public ConfigurePersistentHeartbeat Heartbeat { get; set; } = new ConfigurePersistentHeartbeat { BeatsPerSecond = 100 };
3535

3636
/// <summary>
3737
/// Gets or sets the breakout board's analog IO configuration.

OpenEphys.Onix1/ConfigureHeartbeat.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.ComponentModel;
3-
using System.Linq;
43
using System.Reactive.Disposables;
54
using System.Reactive.Subjects;
65
using Bonsai;
@@ -41,10 +40,6 @@ public ConfigureHeartbeat()
4140
/// <summary>
4241
/// Gets or sets the rate at which beats are produced in Hz.
4342
/// </summary>
44-
/// <remarks>
45-
/// If set to true, a <see cref="HeartbeatData"/> instance that is linked to this configuration will produce data.
46-
/// If set to false, it will not produce data.
47-
/// </remarks>
4843
[Range(1, 10e6)]
4944
[Category(AcquisitionCategory)]
5045
[Description("Rate at which beats are produced (Hz).")]
@@ -102,21 +97,4 @@ public NameConverter()
10297
}
10398
}
10499
}
105-
106-
// NB: Can be used to remove Enable and BeatsPerSecond properties from MultiDeviceFactories that
107-
// include a Heartbeat when having those options would cause confusion
108-
internal class HeartbeatSingleDeviceFactoryConverter : SingleDeviceFactoryConverter
109-
{
110-
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
111-
{
112-
var properties = (from property in base.GetProperties(context, value, attributes).Cast<PropertyDescriptor>()
113-
where !property.IsReadOnly &&
114-
!(property.DisplayName == "Enable") &&
115-
!(property.DisplayName == "BeatsPerSecond") &&
116-
property.ComponentType != typeof(SingleDeviceFactory)
117-
select property)
118-
.ToArray();
119-
return new PropertyDescriptorCollection(properties).Sort(properties.Select(p => p.Name).ToArray());
120-
}
121-
}
122100
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Reactive.Subjects;
4+
using Bonsai;
5+
6+
namespace OpenEphys.Onix1
7+
{
8+
/// <summary>
9+
/// Configures a persistent heartbeat device whose data stream cannot be disabled.
10+
/// </summary>
11+
/// <remarks>
12+
/// This configuration operator can be linked to a data IO operator, such as <see cref="HeartbeatData"/>,
13+
/// using a shared <c>DeviceName</c>.
14+
/// </remarks>
15+
[Description("Configures a heartbeat device.")]
16+
public class ConfigurePersistentHeartbeat : SingleDeviceFactory
17+
{
18+
readonly BehaviorSubject<uint> beatsPerSecond = new(100);
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="ConfigurePersistentHeartbeat"/> class.
22+
/// </summary>
23+
public ConfigurePersistentHeartbeat()
24+
: base(typeof(PersistentHeartbeat))
25+
{
26+
}
27+
28+
/// <summary>
29+
/// Gets or sets the rate at which beats are produced in Hz.
30+
/// </summary>
31+
[Range(100, 10e6)]
32+
[Category(AcquisitionCategory)]
33+
[Description("Rate at which beats are produced (Hz).")]
34+
public uint BeatsPerSecond
35+
{
36+
get => beatsPerSecond.Value;
37+
set => beatsPerSecond.OnNext(value);
38+
}
39+
40+
/// <summary>
41+
/// Configures a persistent heartbeat device.
42+
/// </summary>
43+
/// <remarks>
44+
/// This will schedule configuration actions to be applied by a <see cref="StartAcquisition"/>
45+
/// instance prior to data acquisition.
46+
/// </remarks>
47+
/// <param name="source">A sequence of <see cref="ContextTask"/> instances that holds configuration
48+
/// actions.</param>
49+
/// <returns>The original sequence modified by adding additional configuration actions required to
50+
/// configure a persistent heartbeat device./></returns>
51+
public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
52+
{
53+
//var enable = Enable;
54+
var deviceName = DeviceName;
55+
var deviceAddress = DeviceAddress;
56+
return source.ConfigureDevice((context, observer) =>
57+
{
58+
var device = context.GetDeviceContext(deviceAddress, DeviceType);
59+
var subscription = beatsPerSecond.SubscribeSafe(observer, newValue =>
60+
{
61+
var clkHz = device.ReadRegister(PersistentHeartbeat.CLK_HZ);
62+
var minHeartbeatHz = device.ReadRegister(PersistentHeartbeat.MIN_HB_HZ);
63+
64+
if (newValue < minHeartbeatHz || newValue > 10e6)
65+
{
66+
throw new InvalidOperationException($"Value must be between {minHeartbeatHz} Hz and 10 MHz.");
67+
}
68+
69+
device.WriteRegister(PersistentHeartbeat.CLK_DIV, clkHz / newValue);
70+
});
71+
72+
return DeviceManager.RegisterDevice(deviceName, device, DeviceType);
73+
});
74+
}
75+
}
76+
77+
static class PersistentHeartbeat
78+
{
79+
public const int ID = 34;
80+
81+
public const uint ENABLE = 0; // Heartbeat enable state (read only; always enabled for this device).
82+
public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Minimum value is CLK_HZ / 10e6.
83+
// Maximum value is CLK_HZ / MIN_HB_HZ.Attempting to set to a value outside
84+
// this range will result in error.
85+
public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV
86+
public const uint MIN_HB_HZ = 3; // The minimum allowed beat frequency, in Hz, for this device
87+
88+
internal class NameConverter : DeviceNameConverter
89+
{
90+
public NameConverter()
91+
: base(typeof(PersistentHeartbeat))
92+
{
93+
}
94+
}
95+
}
96+
}

OpenEphys.Onix1/ConfigurePortController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
6161
var device = context.GetDeviceContext(deviceAddress, DeviceType);
6262
void dispose() { device.WriteRegister(PortController.PORTVOLTAGE, 0); }
6363
device.WriteRegister(PortController.ENABLE, 1);
64+
device.WriteRegister(PortController.DESPWR, 1);
6465

6566
bool serdesLock = false;
6667
if (portVoltage.Requested.HasValue)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Linq;
4+
using System.Reactive.Linq;
5+
using Bonsai;
6+
7+
namespace OpenEphys.Onix1
8+
{
9+
/// <summary>
10+
/// Produces an persistent sequence of heartbeat data frames.
11+
/// </summary>
12+
/// <remarks>
13+
/// This data IO operator must be linked to an appropriate configuration, such as a <see
14+
/// cref="ConfigurePersistentHeartbeat"/>, using a shared <c>DeviceName</c>.
15+
/// </remarks>
16+
[Description("Produces a persistent sequence of heartbeat data frames.")]
17+
public class PersistentHeartbeatData : Source<HeartbeatDataFrame>
18+
{
19+
/// <inheritdoc cref = "SingleDeviceFactory.DeviceName"/>
20+
[TypeConverter(typeof(PersistentHeartbeat.NameConverter))]
21+
[Description(SingleDeviceFactory.DeviceNameDescription)]
22+
[Category(DeviceFactory.ConfigurationCategory)]
23+
public string DeviceName { get; set; }
24+
25+
/// <summary>
26+
/// Generates a sequence of <see cref="HeartbeatDataFrame"/> objects, each of which contains period signal from the
27+
/// acquisition system indicating that it is active.
28+
/// </summary>
29+
/// <returns>A sequence of <see cref="HeartbeatDataFrame"/> objects.</returns>
30+
public override IObservable<HeartbeatDataFrame> Generate()
31+
{
32+
return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo =>
33+
{
34+
var device = deviceInfo.GetDeviceContext(typeof(PersistentHeartbeat));
35+
return deviceInfo.Context
36+
.GetDeviceFrames(device.Address)
37+
.Select(frame => new HeartbeatDataFrame(frame));
38+
});
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)