Skip to content

Commit dacf31e

Browse files
authored
Merge pull request #443 from open-ephys/persistent-heartbeat
Aggregate changes targeting controller gateware version 2.0
2 parents f6d20d4 + 12e0949 commit dacf31e

14 files changed

+202
-70
lines changed

.bonsai/Bonsai.config

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,14 @@
4848
</Packages>
4949
<AssemblyReferences>
5050
<AssemblyReference assemblyName="Bonsai" />
51-
<AssemblyReference assemblyName="Bonsai.Arduino" />
52-
<AssemblyReference assemblyName="Bonsai.Audio" />
5351
<AssemblyReference assemblyName="Bonsai.Core" />
5452
<AssemblyReference assemblyName="Bonsai.Design" />
5553
<AssemblyReference assemblyName="Bonsai.Design.Visualizers" />
5654
<AssemblyReference assemblyName="Bonsai.Dsp" />
5755
<AssemblyReference assemblyName="Bonsai.Dsp.Design" />
5856
<AssemblyReference assemblyName="Bonsai.Editor" />
59-
<AssemblyReference assemblyName="Bonsai.Osc" />
6057
<AssemblyReference assemblyName="Bonsai.Scripting.Expressions" />
6158
<AssemblyReference assemblyName="Bonsai.Scripting.Expressions.Design" />
62-
<AssemblyReference assemblyName="Bonsai.Shaders" />
63-
<AssemblyReference assemblyName="Bonsai.Shaders.Design" />
6459
<AssemblyReference assemblyName="Bonsai.System" />
6560
<AssemblyReference assemblyName="Bonsai.System.Design" />
6661
<AssemblyReference assemblyName="Bonsai.Vision" />

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.Design/OpenEphys.Onix1.Design.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="Bonsai.Design" Version="2.8.5" />
16-
<PackageReference Include="Bonsai.Design.Visualizers" Version="2.8.0" />
15+
<PackageReference Include="Bonsai.Design" Version="2.9.0" />
16+
<PackageReference Include="Bonsai.Design.Visualizers" Version="2.9.0" />
1717
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
1818
<PackageReference Include="OpenEphys.ProbeInterface.NET" Version="0.3.0" />
1919
<PackageReference Include="ZedGraph" Version="5.1.7" />

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: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
[EquivalentDataSource(typeof(Heartbeat))]
78+
static class PersistentHeartbeat
79+
{
80+
public const int ID = 35;
81+
82+
public const uint ENABLE = 0; // Heartbeat enable state (read only; always enabled for this device).
83+
public const uint CLK_DIV = 1; // Heartbeat clock divider ratio. Minimum value is CLK_HZ / 10e6.
84+
// Maximum value is CLK_HZ / MIN_HB_HZ.Attempting to set to a value outside
85+
// this range will result in error.
86+
public const uint CLK_HZ = 2; // The frequency parameter, CLK_HZ, used in the calculation of CLK_DIV
87+
public const uint MIN_HB_HZ = 3; // The minimum allowed beat frequency, in Hz, for this device
88+
89+
internal class NameConverter : DeviceNameConverter
90+
{
91+
public NameConverter()
92+
: base(typeof(PersistentHeartbeat))
93+
{
94+
}
95+
}
96+
}
97+
}

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)

OpenEphys.Onix1/ContextHelper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Reflection;
34
using oni;
45

@@ -68,5 +69,14 @@ static void ThrowInvalidDeviceException(Type expectedType, uint address)
6869
{
6970
throw new InvalidOperationException($"Invalid device ID. The device found at address {address} is not a '{expectedType.Name}' device.");
7071
}
72+
73+
internal static bool CheckDeviceType(Type deviceType, Type targetType)
74+
{
75+
if (deviceType == targetType) return true;
76+
77+
var equivalentTypes = deviceType.GetCustomAttributes(typeof(EquivalentDataSource), false).Cast<EquivalentDataSource>();
78+
79+
return equivalentTypes.Any(t => t.BaseType == targetType);
80+
}
7181
}
7282
}

OpenEphys.Onix1/DeviceInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public DeviceInfo(ContextTask context, Type deviceType, uint deviceAddress)
2424

2525
public void AssertType(Type expectedType)
2626
{
27-
if (DeviceType != expectedType)
27+
if (!ContextHelper.CheckDeviceType(DeviceType, expectedType))
2828
{
2929
throw new InvalidOperationException(
3030
$"Expected device of type {expectedType.Name}. Actual type is {DeviceType.Name}."

OpenEphys.Onix1/DeviceNameConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ from element in SelectContextElements(level)
112112
let factory = ExpressionBuilder.GetWorkflowElement(element) as DeviceFactory
113113
where factory != null
114114
from device in factory.GetDevices()
115-
where device.DeviceType == targetType
115+
where ContextHelper.CheckDeviceType(device.DeviceType,targetType)
116116
select device.DeviceName)
117117
.Distinct()
118118
.ToList();

0 commit comments

Comments
 (0)