Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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: 1 addition & 1 deletion Generators/Generators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<FirmwarePath>..\Firmware\Harp.MultiPwm</FirmwarePath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Harp.Generators" Version="0.3.0" GeneratePathProperty="true" />
<PackageReference Include="Harp.Generators" Version="0.4.0" GeneratePathProperty="true" />
</ItemGroup>
<Target Name="TextTransform" BeforeTargets="AfterBuild">
<PropertyGroup>
Expand Down
7 changes: 5 additions & 2 deletions Interface/Harp.MultiPwm/AsyncDevice.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ public partial class Device
/// <param name="portName">
/// The name of the serial port used to communicate with the Harp device.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the operation.
/// </param>
/// <returns>
/// A task that represents the asynchronous initialization operation. The value of
/// the <see cref="Task{TResult}.Result"/> parameter contains a new instance of
/// the <see cref="AsyncDevice"/> class.
/// </returns>
public static async Task<AsyncDevice> CreateAsync(string portName)
public static async Task<AsyncDevice> 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(
Expand Down
152 changes: 151 additions & 1 deletion Interface/Harp.MultiPwm/Device.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ static string GetDeviceMetadata()
/// describing the <see cref="MultiPwm"/> device registers.
/// </summary>
[Description("Returns the contents of the metadata file describing the MultiPwm device registers.")]
public partial class GetMetadata : Source<string>
public partial class GetDeviceMetadata : Source<string>
{
/// <summary>
/// Returns an observable sequence with the contents of the metadata file
Expand Down Expand Up @@ -139,6 +139,156 @@ public override IObservable<IGroupedObservable<Type, HarpMessage>> Process(IObse
}
}

/// <summary>
/// Represents an operator that writes the sequence of <see cref="MultiPwm"/>" messages
/// to the standard Harp storage format.
/// </summary>
[Description("Writes the sequence of MultiPwm messages to the standard Harp storage format.")]
public partial class DeviceDataWriter : Sink<HarpMessage>, INamedElement
{
const string BinaryExtension = ".bin";
const string MetadataFileName = "device.yml";
readonly Bonsai.Harp.MessageWriter writer = new();

string INamedElement.Name => nameof(MultiPwm) + "DataWriter";

/// <summary>
/// Gets or sets the relative or absolute path on which to save the message data.
/// </summary>
[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(MultiPwm) + BinaryExtension);
}

/// <summary>
/// Gets or sets a value indicating whether element writing should be buffered. If <see langword="true"/>,
/// 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.
/// </summary>
[Description("Indicates whether writing should be buffered.")]
public bool Buffered
{
get => writer.Buffered;
set => writer.Buffered = value;
}

/// <summary>
/// Gets or sets a value indicating whether to overwrite the output file if it already exists.
/// </summary>
[Description("Indicates whether to overwrite the output file if it already exists.")]
public bool Overwrite
{
get => writer.Overwrite;
set => writer.Overwrite = value;
}

/// <summary>
/// Gets or sets a value specifying how the message filter will use the matching criteria.
/// </summary>
[Description("Specifies how the message filter will use the matching criteria.")]
public FilterType FilterType
{
get => writer.FilterType;
set => writer.FilterType = value;
}

/// <summary>
/// Gets or sets a value specifying the expected message type. If no value is
/// specified, all messages will be accepted.
/// </summary>
[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<TSource> WriteDeviceMetadata<TSource>(IObservable<TSource> source)
{
var basePath = Path;
if (string.IsNullOrEmpty(basePath))
return source;

var metadataPath = System.IO.Path.Combine(basePath, MetadataFileName);
return Observable.Create<TSource>(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);
});
}

/// <summary>
/// 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.
/// </summary>
/// <param name="source">The sequence of messages to write to the file.</param>
/// <returns>
/// An observable sequence that is identical to the <paramref name="source"/>
/// 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.
/// </returns>
public override IObservable<HarpMessage> Process(IObservable<HarpMessage> source)
{
return source.Publish(ps => ps.Merge(
WriteDeviceMetadata(writer.Process(ps.GroupBy(message => message.Address)))
.IgnoreElements()
.Cast<HarpMessage>()));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="source">
/// A sequence of observable groups, each of which corresponds to a unique register
/// address.
/// </param>
/// <returns>
/// An observable sequence that is identical to the <paramref name="source"/>
/// 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.
/// </returns>
public IObservable<IGroupedObservable<int, HarpMessage>> Process(IObservable<IGroupedObservable<int, HarpMessage>> source)
{
return WriteDeviceMetadata(writer.Process(source));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="source">
/// A sequence of observable groups, each of which corresponds to a unique register
/// type.
/// </param>
/// <returns>
/// An observable sequence that is identical to the <paramref name="source"/>
/// 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.
/// </returns>
public IObservable<IGroupedObservable<Type, HarpMessage>> Process(IObservable<IGroupedObservable<Type, HarpMessage>> source)
{
return WriteDeviceMetadata(writer.Process(source));
}
}

/// <summary>
/// Represents an operator that filters register-specific messages
/// reported by the <see cref="MultiPwm"/> device.
Expand Down