diff --git a/Generators/Generators.csproj b/Generators/Generators.csproj
index 9e36287..9323234 100644
--- a/Generators/Generators.csproj
+++ b/Generators/Generators.csproj
@@ -15,7 +15,7 @@
..\Firmware\Harp.LedArray
-
+
diff --git a/Interface/Harp.LedArray/AsyncDevice.Generated.cs b/Interface/Harp.LedArray/AsyncDevice.Generated.cs
index 4df5654..d32c5fb 100644
--- a/Interface/Harp.LedArray/AsyncDevice.Generated.cs
+++ b/Interface/Harp.LedArray/AsyncDevice.Generated.cs
@@ -14,15 +14,18 @@ public partial class Device
///
/// The name of the serial port used to communicate with the Harp device.
///
+ ///
+ /// A which can be used to cancel the operation.
+ ///
///
/// A task that represents the asynchronous initialization operation. The value of
/// the parameter contains a new instance of
/// the class.
///
- public static async Task CreateAsync(string portName)
+ public static async Task CreateAsync(string portName, CancellationToken cancellationToken = default)
{
var device = new AsyncDevice(portName);
- var whoAmI = await device.ReadWhoAmIAsync();
+ var whoAmI = await device.ReadWhoAmIAsync(cancellationToken);
if (whoAmI != Device.WhoAmI)
{
var errorMessage = string.Format(
diff --git a/Interface/Harp.LedArray/Device.Generated.cs b/Interface/Harp.LedArray/Device.Generated.cs
index a154781..b480947 100644
--- a/Interface/Harp.LedArray/Device.Generated.cs
+++ b/Interface/Harp.LedArray/Device.Generated.cs
@@ -93,7 +93,7 @@ static string GetDeviceMetadata()
/// describing the device registers.
///
[Description("Returns the contents of the metadata file describing the LedArray device registers.")]
- public partial class GetMetadata : Source
+ public partial class GetDeviceMetadata : Source
{
///
/// Returns an observable sequence with the contents of the metadata file
@@ -130,6 +130,156 @@ public override IObservable> Process(IObse
}
}
+ ///
+ /// Represents an operator that writes the sequence of " messages
+ /// to the standard Harp storage format.
+ ///
+ [Description("Writes the sequence of LedArray messages to the standard Harp storage format.")]
+ public partial class DeviceDataWriter : Sink, INamedElement
+ {
+ const string BinaryExtension = ".bin";
+ const string MetadataFileName = "device.yml";
+ readonly Bonsai.Harp.MessageWriter writer = new();
+
+ string INamedElement.Name => nameof(LedArray) + "DataWriter";
+
+ ///
+ /// Gets or sets the relative or absolute path on which to save the message data.
+ ///
+ [Description("The relative or absolute path of the directory on which to save the message data.")]
+ [Editor("Bonsai.Design.SaveFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
+ public string Path
+ {
+ get => System.IO.Path.GetDirectoryName(writer.FileName);
+ set => writer.FileName = System.IO.Path.Combine(value, nameof(LedArray) + BinaryExtension);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether element writing should be buffered. If ,
+ /// the write commands will be queued in memory as fast as possible and will be processed
+ /// by the writer in a different thread. Otherwise, writing will be done in the same
+ /// thread in which notifications arrive.
+ ///
+ [Description("Indicates whether writing should be buffered.")]
+ public bool Buffered
+ {
+ get => writer.Buffered;
+ set => writer.Buffered = value;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to overwrite the output file if it already exists.
+ ///
+ [Description("Indicates whether to overwrite the output file if it already exists.")]
+ public bool Overwrite
+ {
+ get => writer.Overwrite;
+ set => writer.Overwrite = value;
+ }
+
+ ///
+ /// Gets or sets a value specifying how the message filter will use the matching criteria.
+ ///
+ [Description("Specifies how the message filter will use the matching criteria.")]
+ public FilterType FilterType
+ {
+ get => writer.FilterType;
+ set => writer.FilterType = value;
+ }
+
+ ///
+ /// Gets or sets a value specifying the expected message type. If no value is
+ /// specified, all messages will be accepted.
+ ///
+ [Description("Specifies the expected message type. If no value is specified, all messages will be accepted.")]
+ public MessageType? MessageType
+ {
+ get => writer.MessageType;
+ set => writer.MessageType = value;
+ }
+
+ private IObservable WriteDeviceMetadata(IObservable source)
+ {
+ var basePath = Path;
+ if (string.IsNullOrEmpty(basePath))
+ return source;
+
+ var metadataPath = System.IO.Path.Combine(basePath, MetadataFileName);
+ return Observable.Create(observer =>
+ {
+ Bonsai.IO.PathHelper.EnsureDirectory(metadataPath);
+ if (System.IO.File.Exists(metadataPath) && !Overwrite)
+ {
+ throw new System.IO.IOException(string.Format("The file '{0}' already exists.", metadataPath));
+ }
+
+ System.IO.File.WriteAllText(metadataPath, Device.Metadata);
+ return source.SubscribeSafe(observer);
+ });
+ }
+
+ ///
+ /// Writes each Harp message in the sequence to the specified binary file, and the
+ /// contents of the device metadata file to a separate text file.
+ ///
+ /// The sequence of messages to write to the file.
+ ///
+ /// An observable sequence that is identical to the
+ /// sequence but where there is an additional side effect of writing the
+ /// messages to a raw binary file, and the contents of the device metadata file
+ /// to a separate text file.
+ ///
+ public override IObservable Process(IObservable source)
+ {
+ return source.Publish(ps => ps.Merge(
+ WriteDeviceMetadata(writer.Process(ps.GroupBy(message => message.Address)))
+ .IgnoreElements()
+ .Cast()));
+ }
+
+ ///
+ /// Writes each Harp message in the sequence of observable groups to the
+ /// corresponding binary file, where the name of each file is generated from
+ /// the common group register address. The contents of the device metadata file are
+ /// written to a separate text file.
+ ///
+ ///
+ /// A sequence of observable groups, each of which corresponds to a unique register
+ /// address.
+ ///
+ ///
+ /// An observable sequence that is identical to the
+ /// sequence but where there is an additional side effect of writing the Harp
+ /// messages in each group to the corresponding file, and the contents of the device
+ /// metadata file to a separate text file.
+ ///
+ public IObservable> Process(IObservable> source)
+ {
+ return WriteDeviceMetadata(writer.Process(source));
+ }
+
+ ///
+ /// Writes each Harp message in the sequence of observable groups to the
+ /// corresponding binary file, where the name of each file is generated from
+ /// the common group register name. The contents of the device metadata file are
+ /// written to a separate text file.
+ ///
+ ///
+ /// A sequence of observable groups, each of which corresponds to a unique register
+ /// type.
+ ///
+ ///
+ /// An observable sequence that is identical to the
+ /// sequence but where there is an additional side effect of writing the Harp
+ /// messages in each group to the corresponding file, and the contents of the device
+ /// metadata file to a separate text file.
+ ///
+ public IObservable> Process(IObservable> source)
+ {
+ return WriteDeviceMetadata(writer.Process(source));
+ }
+ }
+
///
/// Represents an operator that filters register-specific messages
/// reported by the device.
diff --git a/Interface/Harp.LedArray/Harp.LedArray.csproj b/Interface/Harp.LedArray/Harp.LedArray.csproj
index 1adb6c6..f3a7968 100644
--- a/Interface/Harp.LedArray/Harp.LedArray.csproj
+++ b/Interface/Harp.LedArray/Harp.LedArray.csproj
@@ -18,7 +18,7 @@
LICENSE
..\bin\$(Configuration)
net462;netstandard2.0
- 0.2.0
+ 0.3.0
9.0