11using System ;
2+ using System . Collections . Generic ;
23using System . ComponentModel ;
34using System . Linq ;
45using System . Reactive . Concurrency ;
6+ using System . Reactive . Disposables ;
57using System . Reactive . Linq ;
68using Bonsai ;
79
@@ -18,7 +20,21 @@ namespace OpenEphys.Onix1
1820 [ Description ( "Polls a Bno055 9-axis IMU to produce a sequence Bno055 data frames." ) ]
1921 public class PolledBno055Data : Source < Bno055DataFrame >
2022 {
21- /// <inheritdoc cref = "SingleDeviceFactory.DeviceName"/>
23+ static readonly HashSet < string > registeredValues = new ( ) ;
24+ static readonly object registrationLock = new ( ) ;
25+
26+ /// <summary>
27+ /// Gets or sets a unique device name. Only a single <see cref="PolledBno055Data"/> operator can be
28+ /// used per distinct device name.
29+ /// </summary>
30+ /// <remarks>
31+ /// The device name provides a unique, human-readable identifier that is used to link software
32+ /// elements for configuration, control, and data streaming to hardware. For instance, it can be used
33+ /// to link configuration operators to data IO operators within a workflow. This value is
34+ /// usually not set manually, but is assigned in a <see cref="MultiDeviceFactory"/> to correspond to a
35+ /// fixed address with a piece of hardware such as a headstage. This address is used for software
36+ /// communication.
37+ /// </remarks>
2238 [ TypeConverter ( typeof ( PolledBno055 . NameConverter ) ) ]
2339 [ Description ( SingleDeviceFactory . DeviceNameDescription ) ]
2440 [ Category ( DeviceFactory . ConfigurationCategory ) ]
@@ -73,8 +89,10 @@ public override IObservable<Bno055DataFrame> Generate()
7389 /// <returns>A sequence of <see cref="Bno055DataFrame">Bno055DataFrames</see>.</returns>
7490 public unsafe IObservable < Bno055DataFrame > Generate < TSource > ( IObservable < TSource > source )
7591 {
92+ var deviceName = DeviceName ;
7693 var polled = PolledRegisters ;
77- return DeviceManager . GetDevice ( DeviceName ) . SelectMany (
94+
95+ return DeviceManager . GetDevice ( deviceName ) . SelectMany (
7896 deviceInfo =>
7997 {
8098 return ! ( ( PolledBno055DeviceInfo ) deviceInfo ) . Enable
@@ -85,7 +103,19 @@ public unsafe IObservable<Bno055DataFrame> Generate<TSource>(IObservable<TSource
85103 var passthrough = device . GetPassthroughDeviceContext ( typeof ( DS90UB9x ) ) ;
86104 var i2c = new I2CRegisterContext ( passthrough , PolledBno055 . BNO055Address ) ;
87105
88- return source . SubscribeSafe ( observer , _ =>
106+ // NB: Only allow one PolledBno055Data operator per unique DeviceName
107+ lock ( registrationLock )
108+ {
109+ if ( registeredValues . Contains ( deviceName ) )
110+ {
111+ throw new InvalidOperationException ( $ "There is already a { nameof ( PolledBno055Data ) } linked to '{ deviceName } '. " +
112+ $ "Only one { nameof ( PolledBno055Data ) } operator is allowed for each distinct { nameof ( DeviceName ) } .") ;
113+ }
114+
115+ registeredValues . Add ( deviceName ) ;
116+ }
117+
118+ var s = source . SubscribeSafe ( observer , _ =>
89119 {
90120 Bno055DataFrame frame = default ;
91121 device . Context . EnsureContext ( ( ) =>
@@ -139,6 +169,15 @@ public unsafe IObservable<Bno055DataFrame> Generate<TSource>(IObservable<TSource
139169 observer . OnNext ( frame ) ;
140170 }
141171 } ) ;
172+
173+ return Disposable . Create ( ( ) =>
174+ {
175+ lock ( registrationLock )
176+ {
177+ registeredValues . Remove ( deviceName ) ;
178+ }
179+ s . Dispose ( ) ;
180+ } ) ;
142181 } ) ;
143182 } ) ;
144183 }
0 commit comments