Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions OpenEphys.Onix1/ConfigureHeadstage64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ public PortName Port
Bno055.DeviceAddress = offset + 1;
TS4231.DeviceAddress = offset + 2;
ElectricalStimulator.DeviceAddress = offset + 3;
ElectricalStimulator.PortControllerDeviceAddress = PortControl.DeviceAddress;
OpticalStimulator.DeviceAddress = offset + 4;
OpticalStimulator.PortControllerDeviceAddress = PortControl.DeviceAddress;
Heartbeat.DeviceAddress = offset + 5;
}
}
Expand Down
29 changes: 5 additions & 24 deletions OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace OpenEphys.Onix1
[Editor("OpenEphys.Onix1.Design.Headstage64ElectricalStimulatorComponentEditor, OpenEphys.Onix1.Design", typeof(ComponentEditor))]
public class ConfigureHeadstage64ElectricalStimulator : SingleDeviceFactory
{
readonly BehaviorSubject<bool> stimEnable = new(false);
internal uint? PortControllerDeviceAddress { get; set; }

readonly BehaviorSubject<double> phaseOneCurrent = new(0);
readonly BehaviorSubject<double> interPhaseCurrent = new(0);
readonly BehaviorSubject<double> phaseTwoCurrent = new(0);
Expand Down Expand Up @@ -48,7 +49,6 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt
DeviceName = electricalStimulator.DeviceName;
DeviceAddress = electricalStimulator.DeviceAddress;
Enable = electricalStimulator.Enable;
StimEnable = electricalStimulator.StimEnable;
PhaseOneCurrent = electricalStimulator.PhaseOneCurrent;
InterPhaseCurrent = electricalStimulator.InterPhaseCurrent;
PhaseTwoCurrent = electricalStimulator.PhaseTwoCurrent;
Expand All @@ -72,25 +72,6 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt
[Description("Specifies whether the headstage-64 electrical stimulator will produce stimulus reports.")]
public bool Enable { get; set; }

/// <summary>
/// Gets or sets the device enable state.
/// </summary>
/// <remarks>
/// If set to true, then the electrical stimulator's ±15V power supplies will be turned on and the
/// electrical stimulator circuit will respect triggers. If set to false, the power supplies will be
/// shut down and triggers will be ignored.It may be desirable to power down the electrical
/// stimulator's power supplies outside of stimulation windows to reduce power consumption and
/// electrical noise. This property must be set to true in order for electrical stimuli to be
/// delivered properly. It takes ~10 milliseconds for these supplies to stabilize.
/// </remarks>
[Description("Specifies whether the electrical stimulator will respect triggers.")]
[Category(AcquisitionCategory)]
public bool StimEnable
{
get => stimEnable.Value;
set => stimEnable.OnNext(value);
}

static double ClampCurrent(double value)
{
if (value > Headstage64ElectricalStimulator.AbsMaxMicroAmps)
Expand Down Expand Up @@ -247,11 +228,11 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
return source.ConfigureDevice((context, observer) =>
{
var device = context.GetDeviceContext(deviceAddress, DeviceType);
var deviceInfo = new Headstage64StimulatorDeviceInfo(context, DeviceType, deviceAddress, PortControllerDeviceAddress);

device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, enable ? 1u : 0u);

return new CompositeDisposable(
stimEnable.SubscribeSafe(observer, value =>
device.WriteRegister(Headstage64ElectricalStimulator.STIMENABLE, value ? 3u : 0u)),
phaseOneCurrent.SubscribeSafe(observer, value =>
device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, Headstage64ElectricalStimulator.MicroampsToCode(value))),
phaseTwoCurrent.SubscribeSafe(observer, value =>
Expand All @@ -263,7 +244,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
interBurstInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, value)),
burstPulseCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, value)),
trainBurstCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, value)),
DeviceManager.RegisterDevice(deviceName, device, DeviceType));
DeviceManager.RegisterDevice(deviceName, deviceInfo));
});
}
}
Expand Down
30 changes: 4 additions & 26 deletions OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace OpenEphys.Onix1
[Editor("OpenEphys.Onix1.Design.Headstage64OpticalStimulatorComponentEditor, OpenEphys.Onix1.Design", typeof(ComponentEditor))]
public class ConfigureHeadstage64OpticalStimulator : SingleDeviceFactory
{
readonly BehaviorSubject<bool> stimEnable = new(false);
internal uint? PortControllerDeviceAddress { get; set; }

readonly BehaviorSubject<bool> enableIndicationLed = new(false);
readonly BehaviorSubject<double> maxCurrent = new(0);
readonly BehaviorSubject<double> channelOneCurrent = new(0);
Expand Down Expand Up @@ -47,7 +48,6 @@ public ConfigureHeadstage64OpticalStimulator(ConfigureHeadstage64OpticalStimulat
DeviceName = opticalStimulator.DeviceName;
DeviceAddress = opticalStimulator.DeviceAddress;
Enable = opticalStimulator.Enable;
StimEnable = opticalStimulator.StimEnable;
MaxCurrent = opticalStimulator.MaxCurrent;
ChannelOneCurrent = opticalStimulator.ChannelOneCurrent;
ChannelTwoCurrent = opticalStimulator.ChannelTwoCurrent;
Expand Down Expand Up @@ -83,21 +83,6 @@ public bool EnableIndicationLed
set => enableIndicationLed.OnNext(value);
}

/// <summary>
/// Gets or sets the device enable state.
/// </summary>
/// <remarks>
/// If set to true, then the optical stimulator circuit will respect triggers. If set to false, triggers will be ignored.
/// </remarks>
[Description("Specifies whether the optical stimulator will respect triggers.")]
[Category(AcquisitionCategory)]
public bool StimEnable
{
get => stimEnable.Value;
set => stimEnable.OnNext(value);
}


/// <summary>
/// Gets or sets the Maximum current per channel per pulse in mA.
/// </summary>
Expand Down Expand Up @@ -272,6 +257,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
return source.ConfigureDevice((context, observer) =>
{
var device = context.GetDeviceContext(deviceAddress, DeviceType);
var deviceInfo = new Headstage64StimulatorDeviceInfo(context, DeviceType, deviceAddress, PortControllerDeviceAddress);

device.WriteRegister(Headstage64OpticalStimulator.ENABLE, enable ? 1u : 0u);

Expand Down Expand Up @@ -306,14 +292,6 @@ static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration)
stimEnableValue &= ~(1u << 8);
device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, stimEnableValue);
}),
stimEnable.SubscribeSafe(observer, value =>
{
if (value)
stimEnableValue |= 1u;
else
stimEnableValue &= ~1u;
device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, stimEnableValue);
}),
maxCurrent.SubscribeSafe(observer, value =>
device.WriteRegister(Headstage64OpticalStimulator.MAXCURRENT, Headstage64OpticalStimulator.MilliampsToPotSetting(value))),
channelOneCurrent.SubscribeSafe(observer, value =>
Expand All @@ -336,7 +314,7 @@ static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration)
device.WriteRegister(Headstage64OpticalStimulator.IBI, (uint)(1000 * value))),
burstsPerTrain.SubscribeSafe(observer, value =>
device.WriteRegister(Headstage64OpticalStimulator.TRAINCOUNT, value)),
DeviceManager.RegisterDevice(deviceName, device, DeviceType));;;
DeviceManager.RegisterDevice(deviceName, deviceInfo));
});
}
}
Expand Down
71 changes: 63 additions & 8 deletions OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.ComponentModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Bonsai;

namespace OpenEphys.Onix1
Expand All @@ -20,36 +22,89 @@ namespace OpenEphys.Onix1
[Description("Controls a headstage-64 onboard electrical stimulus sequencer.")]
public class Headstage64ElectricalStimulatorTrigger : Sink<double>
{

readonly BehaviorSubject<bool> stimEnable = new(false);

/// <inheritdoc cref = "SingleDeviceFactory.DeviceName"/>
[TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))]
[Description(SingleDeviceFactory.DeviceNameDescription)]
[Category(DeviceFactory.ConfigurationCategory)]
public string DeviceName { get; set; }

/// <summary>
/// Gets or sets the device enable state.
/// </summary>
/// <remarks>
/// If set to true, then the electrical stimulator's ±15V power supplies will be turned on and the
/// electrical stimulator circuit will respect triggers. If set to false, the power supplies will be
/// shut down and triggers will be ignored.It may be desirable to power down the electrical
/// stimulator's power supplies outside of stimulation windows to reduce power consumption and
/// electrical noise. This property must be set to true in order for electrical stimuli to be
/// delivered properly. It takes ~10 milliseconds for these supplies to stabilize.
/// </remarks>
[Description("Specifies whether the electrical stimulator will respect triggers.")]
[Category(DeviceFactory.AcquisitionCategory)]
public bool Enable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be Enable or StimEnable? The private field is stimEnable, and the corresponding property in the Optical stimulator is StimEnable

{
get => stimEnable.Value;
set => stimEnable.OnNext(value);
}

/// <summary>
/// Start an electrical stimulus sequence with an optional hardware delay.
/// </summary>
/// <param name="source">A sequence of double values that serve as a combined stimulus trigger and
/// <param name="source">A sequence of doubles that serve as a combined stimulus trigger and
/// delay in microseconds. A value of 0 results in immediate stimulus delivery. A value of 100 results in
/// stimulus delivery following a 100 microsecond delay. Delays are implemented in hardware and are
/// exact. </param>
/// <returns>A sequence of double values that is identical to <paramref name="source"/></returns>
/// <returns>A sequence of doubles that is identical to <paramref name="source"/></returns>
public override IObservable<double> Process(IObservable<double> source)
{
return DeviceManager.GetDevice(DeviceName).SelectMany(
deviceInfo => Observable.Create<double>(observer =>
{
var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator));
var triggerObserver = Observer.Create<double>(
value =>
{
device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, (uint)value << 8 | 0x1);
var info = (Headstage64StimulatorDeviceInfo)deviceInfo;
var device = info.GetDeviceContext(typeof(Headstage64ElectricalStimulator));
IObserver<double> triggerObserver;

if (info.PortControllerAddress != null)
{
var portController = device.Context.GetDeviceContext((uint)info.PortControllerAddress, typeof(PortController));
triggerObserver = Observer.Create<double>(value => {
if (stimEnable.Value)
{
if (value == 0)
{
portController.WriteRegister(PortController.GPOSTATE, (byte)PortControllerGpioState.Pin1);
portController.WriteRegister(PortController.GPOSTATE, 0);
}
else
{
device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, (uint)value << 8 | 0x1);
}
}
observer.OnNext(value);
},
observer.OnError,
observer.OnCompleted);
}
else
{
triggerObserver = Observer.Create<double>(value => {
if (stimEnable.Value)
{
device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, (uint)value << 8 | 0x1);
}
observer.OnNext(value);
},
observer.OnError,
observer.OnCompleted);
}

return source.SubscribeSafe(triggerObserver);
return new CompositeDisposable(
stimEnable.SubscribeSafe(observer, value =>
device.WriteRegister(Headstage64ElectricalStimulator.STIMENABLE, value ? 3u : 0u)),
source.SubscribeSafe(triggerObserver));
}));
}
}
Expand Down
67 changes: 62 additions & 5 deletions OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.ComponentModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Bonsai;

namespace OpenEphys.Onix1
Expand All @@ -20,11 +22,27 @@ namespace OpenEphys.Onix1
[Description("Controls a headstage-64 onboard optical stimulus sequencer.")]
public class Headstage64OpticalStimulatorTrigger : Sink<double>
{
readonly BehaviorSubject<bool> stimEnable = new(false);

/// <inheritdoc cref = "SingleDeviceFactory.DeviceName"/>
[TypeConverter(typeof(Headstage64OpticalStimulator.NameConverter))]
[Category(DeviceFactory.ConfigurationCategory)]
public string DeviceName { get; set; }

/// <summary>
/// Gets or sets the device enable state.
/// </summary>
/// <remarks>
/// If set to true, then the optical stimulator circuit will respect triggers. If set to false, triggers will be ignored.
/// </remarks>
[Description("Specifies whether the optical stimulator will respect triggers.")]
[Category(DeviceFactory.AcquisitionCategory)]
public bool StimEnable
{
get => stimEnable.Value;
set => stimEnable.OnNext(value);
}

/// <summary>
/// Start an optical stimulus sequence with an optional hardware delay.
/// </summary>
Expand All @@ -38,17 +56,56 @@ public override IObservable<double> Process(IObservable<double> source)
return DeviceManager.GetDevice(DeviceName).SelectMany(
deviceInfo => Observable.Create<double>(observer =>
{
var device = deviceInfo.GetDeviceContext(typeof(Headstage64OpticalStimulator));
var triggerObserver = Observer.Create<double>(
value =>
var info = (Headstage64StimulatorDeviceInfo)deviceInfo;
var device = info.GetDeviceContext(typeof(Headstage64OpticalStimulator));
IObserver<double> triggerObserver;

if (info.PortControllerAddress != null)
{
var portController = device.Context.GetDeviceContext((uint)info.PortControllerAddress, typeof(PortController));
triggerObserver = Observer.Create<double>(value =>
{
device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, (uint)value << 8 | 0x1);
if (stimEnable.Value)
{
if (value == 0)
{
portController.WriteRegister(PortController.GPOSTATE, (byte)PortControllerGpioState.Pin1);
portController.WriteRegister(PortController.GPOSTATE, 0);
}
else
{
device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, (uint)value << 8 | 0x1);
}
}
observer.OnNext(value);
},
observer.OnError,
observer.OnCompleted);
}
else
{
triggerObserver = Observer.Create<double>(value => {
if (stimEnable.Value)
{
device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, (uint)value << 8 | 0x1);
}
observer.OnNext(value);
},
observer.OnError,
observer.OnCompleted);
}

return new CompositeDisposable(stimEnable.SubscribeSafe(observer, value =>
{
var stimEnableValue = device.ReadRegister(Headstage64OpticalStimulator.STIMENABLE);
if (value)
stimEnableValue |= 1u;
else
stimEnableValue &= ~1u;
device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, stimEnableValue);
}),
source.SubscribeSafe(triggerObserver));

return source.SubscribeSafe(triggerObserver);
}));
}
}
Expand Down
15 changes: 15 additions & 0 deletions OpenEphys.Onix1/Headstage64StimulatorDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace OpenEphys.Onix1
{
class Headstage64StimulatorDeviceInfo : DeviceInfo
{
public Headstage64StimulatorDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, uint? portControllerAddress)
: base(context, deviceType, deviceAddress)
{
PortControllerAddress = portControllerAddress;
}

public uint? PortControllerAddress {get; }
}
}