Skip to content

Smdn.Devices.BP35XX version 1.0.0

Compare
Choose a tag to compare
@smdn smdn released this 06 Apr 15:49
· 4 commits to main since this release
7a26b7a

Released package

Release notes

The full release notes are available at gist.

Change log

Change log in this release:

API changes

API changes in this release:
diff --git a/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net6.0.apilist.cs b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net6.0.apilist.cs
new file mode 100644
index 0000000..b86796d
--- /dev/null
+++ b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net6.0.apilist.cs
@@ -0,0 +1,110 @@
+// Smdn.Devices.BP35XX.dll (Smdn.Devices.BP35XX-1.0.0)
+//   Name: Smdn.Devices.BP35XX
+//   AssemblyVersion: 1.0.0.0
+//   InformationalVersion: 1.0.0+e4163ada5b034b45c5cc0dac179b412cf54198cd
+//   TargetFramework: .NETCoreApp,Version=v6.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral
+//     Smdn.Net.SkStackIP, Version=1.0.0.0, Culture=neutral
+//     System.ComponentModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.IO.Ports, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading;
+using System.Threading.Tasks;
+using Smdn.Devices.BP35XX;
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX {
+  public interface IBP35Configurations {
+    BP35UartBaudRate BaudRate { get; }
+    SkStackERXUDPDataFormat ERXUDPDataFormat { get; }
+    string? SerialPortName { get; }
+    bool TryLoadFlashMemory { get; }
+  }
+
+  public interface IBP35SerialPortStreamFactory {
+    Stream CreateSerialPortStream(IBP35Configurations configurations);
+  }
+
+  public enum BP35UartBaudRate : byte {
+    Baud115200 = 0,
+    Baud19200 = 4,
+    Baud2400 = 1,
+    Baud38400 = 5,
+    Baud4800 = 2,
+    Baud57600 = 6,
+    Baud9600 = 3,
+  }
+
+  public enum BP35UartCharacterInterval : byte {
+    Microseconds100 = 16,
+    Microseconds200 = 32,
+    Microseconds300 = 48,
+    Microseconds400 = 64,
+    Microseconds50 = 80,
+    None = 0,
+  }
+
+  public enum BP35UartFlowControl : byte {
+    Disabled = 0,
+    Enabled = 128,
+  }
+
+  public enum BP35UdpReceiveDataFormat : byte {
+    Binary = 0,
+    HexAscii = 1,
+  }
+
+  public class BP35A1 : BP35Base {
+    public static ValueTask<BP35A1> CreateAsync(BP35A1Configurations configurations, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+    public static ValueTask<BP35A1> CreateAsync(string? serialPortName, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+  }
+
+  public sealed class BP35A1Configurations : IBP35Configurations {
+    public BP35A1Configurations() {}
+
+    public BP35UartBaudRate BaudRate { get; set; }
+    public string? SerialPortName { get; set; }
+    SkStackERXUDPDataFormat IBP35Configurations.ERXUDPDataFormat { get; }
+    public bool TryLoadFlashMemory { get; set; }
+  }
+
+  public abstract class BP35Base : SkStackClient {
+    public IPAddress LinkLocalAddress { get; }
+    public PhysicalAddress MacAddress { get; }
+    public string RohmPassword { get; }
+    public string RohmUserId { get; }
+    public string SkStackAppVersion { get; }
+    public Version SkStackVersion { get; }
+
+    public async ValueTask<BP35UartConfigurations> GetUartOptionsAsync(CancellationToken cancellationToken = default) {}
+    public async ValueTask<BP35UdpReceiveDataFormat> GetUdpDataFormatAsync(CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval = BP35UartCharacterInterval.None, BP35UartFlowControl flowControl = BP35UartFlowControl.Disabled, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartConfigurations uartConfigurations, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUdpDataFormatAsync(BP35UdpReceiveDataFormat format, CancellationToken cancellationToken = default) {}
+  }
+
+  public readonly struct BP35UartConfigurations {
+    public BP35UartConfigurations(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval, BP35UartFlowControl flowControl) {}
+
+    public BP35UartBaudRate BaudRate { get; }
+    public BP35UartCharacterInterval CharacterInterval { get; }
+    public BP35UartFlowControl FlowControl { get; }
+
+    public void Deconstruct(out BP35UartBaudRate baudRate, out BP35UartCharacterInterval characterInterval, out BP35UartFlowControl flowControl) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net8.0.apilist.cs b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net8.0.apilist.cs
new file mode 100644
index 0000000..d0204eb
--- /dev/null
+++ b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-net8.0.apilist.cs
@@ -0,0 +1,110 @@
+// Smdn.Devices.BP35XX.dll (Smdn.Devices.BP35XX-1.0.0)
+//   Name: Smdn.Devices.BP35XX
+//   AssemblyVersion: 1.0.0.0
+//   InformationalVersion: 1.0.0+e4163ada5b034b45c5cc0dac179b412cf54198cd
+//   TargetFramework: .NETCoreApp,Version=v8.0
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral
+//     Smdn.Net.SkStackIP, Version=1.0.0.0, Culture=neutral
+//     System.ComponentModel, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.IO.Ports, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     System.Net.NetworkInformation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+//     System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+#nullable enable annotations
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading;
+using System.Threading.Tasks;
+using Smdn.Devices.BP35XX;
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX {
+  public interface IBP35Configurations {
+    BP35UartBaudRate BaudRate { get; }
+    SkStackERXUDPDataFormat ERXUDPDataFormat { get; }
+    string? SerialPortName { get; }
+    bool TryLoadFlashMemory { get; }
+  }
+
+  public interface IBP35SerialPortStreamFactory {
+    Stream CreateSerialPortStream(IBP35Configurations configurations);
+  }
+
+  public enum BP35UartBaudRate : byte {
+    Baud115200 = 0,
+    Baud19200 = 4,
+    Baud2400 = 1,
+    Baud38400 = 5,
+    Baud4800 = 2,
+    Baud57600 = 6,
+    Baud9600 = 3,
+  }
+
+  public enum BP35UartCharacterInterval : byte {
+    Microseconds100 = 16,
+    Microseconds200 = 32,
+    Microseconds300 = 48,
+    Microseconds400 = 64,
+    Microseconds50 = 80,
+    None = 0,
+  }
+
+  public enum BP35UartFlowControl : byte {
+    Disabled = 0,
+    Enabled = 128,
+  }
+
+  public enum BP35UdpReceiveDataFormat : byte {
+    Binary = 0,
+    HexAscii = 1,
+  }
+
+  public class BP35A1 : BP35Base {
+    public static ValueTask<BP35A1> CreateAsync(BP35A1Configurations configurations, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+    public static ValueTask<BP35A1> CreateAsync(string? serialPortName, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+  }
+
+  public sealed class BP35A1Configurations : IBP35Configurations {
+    public BP35A1Configurations() {}
+
+    public BP35UartBaudRate BaudRate { get; set; }
+    public string? SerialPortName { get; set; }
+    SkStackERXUDPDataFormat IBP35Configurations.ERXUDPDataFormat { get; }
+    public bool TryLoadFlashMemory { get; set; }
+  }
+
+  public abstract class BP35Base : SkStackClient {
+    public IPAddress LinkLocalAddress { get; }
+    public PhysicalAddress MacAddress { get; }
+    public string RohmPassword { get; }
+    public string RohmUserId { get; }
+    public string SkStackAppVersion { get; }
+    public Version SkStackVersion { get; }
+
+    public async ValueTask<BP35UartConfigurations> GetUartOptionsAsync(CancellationToken cancellationToken = default) {}
+    public async ValueTask<BP35UdpReceiveDataFormat> GetUdpDataFormatAsync(CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval = BP35UartCharacterInterval.None, BP35UartFlowControl flowControl = BP35UartFlowControl.Disabled, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartConfigurations uartConfigurations, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUdpDataFormatAsync(BP35UdpReceiveDataFormat format, CancellationToken cancellationToken = default) {}
+  }
+
+  public readonly struct BP35UartConfigurations {
+    public BP35UartConfigurations(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval, BP35UartFlowControl flowControl) {}
+
+    public BP35UartBaudRate BaudRate { get; }
+    public BP35UartCharacterInterval CharacterInterval { get; }
+    public BP35UartFlowControl FlowControl { get; }
+
+    public void Deconstruct(out BP35UartBaudRate baudRate, out BP35UartCharacterInterval characterInterval, out BP35UartFlowControl flowControl) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-netstandard2.1.apilist.cs
new file mode 100644
index 0000000..e9184a4
--- /dev/null
+++ b/doc/api-list/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX-netstandard2.1.apilist.cs
@@ -0,0 +1,106 @@
+// Smdn.Devices.BP35XX.dll (Smdn.Devices.BP35XX-1.0.0)
+//   Name: Smdn.Devices.BP35XX
+//   AssemblyVersion: 1.0.0.0
+//   InformationalVersion: 1.0.0+e4163ada5b034b45c5cc0dac179b412cf54198cd
+//   TargetFramework: .NETStandard,Version=v2.1
+//   Configuration: Release
+//   Referenced assemblies:
+//     Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+//     Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.0.0, Culture=neutral
+//     Smdn.Net.SkStackIP, Version=1.0.0.0, Culture=neutral
+//     System.IO.Ports, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+//     netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading;
+using System.Threading.Tasks;
+using Smdn.Devices.BP35XX;
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX {
+  public interface IBP35Configurations {
+    BP35UartBaudRate BaudRate { get; }
+    SkStackERXUDPDataFormat ERXUDPDataFormat { get; }
+    string? SerialPortName { get; }
+    bool TryLoadFlashMemory { get; }
+  }
+
+  public interface IBP35SerialPortStreamFactory {
+    Stream CreateSerialPortStream(IBP35Configurations configurations);
+  }
+
+  public enum BP35UartBaudRate : byte {
+    Baud115200 = 0,
+    Baud19200 = 4,
+    Baud2400 = 1,
+    Baud38400 = 5,
+    Baud4800 = 2,
+    Baud57600 = 6,
+    Baud9600 = 3,
+  }
+
+  public enum BP35UartCharacterInterval : byte {
+    Microseconds100 = 16,
+    Microseconds200 = 32,
+    Microseconds300 = 48,
+    Microseconds400 = 64,
+    Microseconds50 = 80,
+    None = 0,
+  }
+
+  public enum BP35UartFlowControl : byte {
+    Disabled = 0,
+    Enabled = 128,
+  }
+
+  public enum BP35UdpReceiveDataFormat : byte {
+    Binary = 0,
+    HexAscii = 1,
+  }
+
+  public class BP35A1 : BP35Base {
+    public static ValueTask<BP35A1> CreateAsync(BP35A1Configurations configurations, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+    public static ValueTask<BP35A1> CreateAsync(string? serialPortName, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) {}
+  }
+
+  public sealed class BP35A1Configurations : IBP35Configurations {
+    public BP35A1Configurations() {}
+
+    public BP35UartBaudRate BaudRate { get; set; }
+    public string? SerialPortName { get; set; }
+    SkStackERXUDPDataFormat IBP35Configurations.ERXUDPDataFormat { get; }
+    public bool TryLoadFlashMemory { get; set; }
+  }
+
+  public abstract class BP35Base : SkStackClient {
+    public IPAddress LinkLocalAddress { get; }
+    public PhysicalAddress MacAddress { get; }
+    public string RohmPassword { get; }
+    public string RohmUserId { get; }
+    public string SkStackAppVersion { get; }
+    public Version SkStackVersion { get; }
+
+    public async ValueTask<BP35UartConfigurations> GetUartOptionsAsync(CancellationToken cancellationToken = default) {}
+    public async ValueTask<BP35UdpReceiveDataFormat> GetUdpDataFormatAsync(CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval = BP35UartCharacterInterval.None, BP35UartFlowControl flowControl = BP35UartFlowControl.Disabled, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUartOptionsAsync(BP35UartConfigurations uartConfigurations, CancellationToken cancellationToken = default) {}
+    public ValueTask SetUdpDataFormatAsync(BP35UdpReceiveDataFormat format, CancellationToken cancellationToken = default) {}
+  }
+
+  public readonly struct BP35UartConfigurations {
+    public BP35UartConfigurations(BP35UartBaudRate baudRate, BP35UartCharacterInterval characterInterval, BP35UartFlowControl flowControl) {}
+
+    public BP35UartBaudRate BaudRate { get; }
+    public BP35UartCharacterInterval CharacterInterval { get; }
+    public BP35UartFlowControl FlowControl { get; }
+
+    public void Deconstruct(out BP35UartBaudRate baudRate, out BP35UartCharacterInterval characterInterval, out BP35UartFlowControl flowControl) {}
+  }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.4.1.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.3.1.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)

Full changes

Full changes in this release:
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX.csproj b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX.csproj
new file mode 100644
index 0000000..2551c0f
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX.csproj
@@ -0,0 +1,59 @@
+<!--
+SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+SPDX-License-Identifier: MIT
+-->
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>net8.0;net6.0;netstandard2.1</TargetFrameworks>
+    <VersionPrefix>1.0.0</VersionPrefix>
+    <VersionSuffix></VersionSuffix>
+    <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
+    <Nullable>enable</Nullable>
+    <RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 -->
+    <NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
+  </PropertyGroup>
+
+  <PropertyGroup Label="metadata">
+    <Description>Provides APIs to operate ROHM BP35A1 and other ROHM Wi-SUN modules using the SKSTACK-IP command.</Description>
+    <CopyrightYear>2021</CopyrightYear>
+  </PropertyGroup>
+
+  <PropertyGroup Label="package properties">
+    <PackageTags>SKSTACK,SKSTACK-IP,BP35A1,ROHM-BP35A1,Wi-SUN</PackageTags>
+    <GenerateNupkgReadmeFileDependsOnTargets>$(GenerateNupkgReadmeFileDependsOnTargets);GenerateReadmeFileContent</GenerateNupkgReadmeFileDependsOnTargets>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="System.IO.Ports" Version="8.0.0" />
+    <ProjectOrPackageReference ReferencePackageVersion="[1.0.0,2.0.0)" Include="..\Smdn.Net.SkStackIP\Smdn.Net.SkStackIP.csproj" />
+  </ItemGroup>
+
+  <Target Name="GenerateReadmeFileContent" DependsOnTargets="ReadReadmeFileNoticeSectionContent">
+    <PropertyGroup>
+      <PackageReadmeFileContent><![CDATA[# $(PackageId) $(PackageVersion)
+`$(PackageId)` is a library that provides APIs to operate [ROHM BP35A1](https://www.rohm.co.jp/products/wireless-communication/specified-low-power-radio-modules/bp35a1-product) and other [ROHM Wi-SUN modules](https://www.rohm.co.jp/products/wireless-communication/specified-low-power-radio-modules) using the [Skyley Networks](https://www.skyley.com/)' [SKSTACK IP](https://www.skyley.com/wiki/?SKSTACK+IP+for+HAN) command.
+
+## Getting started
+First, add package [$(PackageId)](https://www.nuget.org/packages/$(PackageId)) to the project file.
+
+```
+dotnet add package $(PackageId)
+```
+
+For using BP35A1, use the `BP35A1.CreateAsync` method to create an instance of the device.
+
+```cs
+$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\..\examples\$(PackageId)\getting-started\Program.cs').TrimEnd())
+```
+
+More examples can be found on the [GitHub repository]($(RepositoryUrl)/tree/main/examples/$(PackageId)/), including examples of using library features.
+
+## Contributing
+This project welcomes contributions, feedbacks and suggestions. You can contribute to this project by submitting [Issues]($(RepositoryUrl)/issues/new/choose) or [Pull Requests]($(RepositoryUrl)/pulls/) on the [GitHub repository]($(RepositoryUrl)).
+
+## Notice
+$(ReadmeFileNoticeSectionContent)
+]]></PackageReadmeFileContent>
+    </PropertyGroup>
+  </Target>
+</Project>
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1.cs
new file mode 100644
index 0000000..29ceb35
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1.cs
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Smdn.Devices.BP35XX;
+
+public class BP35A1 : BP35Base {
+  /// <summary>
+  /// Refer to the initial value of baud rate for UART setting in the BP35A1.
+  /// </summary>
+  /// <remarks>
+  /// See 'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)' for detailed specifications.
+  /// </remarks>
+  internal const BP35UartBaudRate DefaultValueForBP35UartBaudRate = BP35UartBaudRate.Baud115200;
+
+  public static ValueTask<BP35A1> CreateAsync(
+    string? serialPortName,
+    IServiceProvider? serviceProvider = null,
+    CancellationToken cancellationToken = default
+  )
+    => CreateAsync(
+      configurations: new BP35A1Configurations() {
+        SerialPortName = serialPortName,
+      },
+      serviceProvider: serviceProvider,
+      cancellationToken: cancellationToken
+    );
+
+  public static ValueTask<BP35A1> CreateAsync(
+    BP35A1Configurations configurations,
+    IServiceProvider? serviceProvider = null,
+    CancellationToken cancellationToken = default
+  )
+    => InitializeAsync(
+#pragma warning disable CA2000
+      device: new BP35A1(
+        configurations: configurations ?? throw new ArgumentNullException(nameof(configurations)),
+        serviceProvider: serviceProvider
+      ),
+#pragma warning restore CA2000
+      tryLoadFlashMemory: configurations.TryLoadFlashMemory,
+      serviceProvider: serviceProvider,
+      cancellationToken: cancellationToken
+    );
+
+  private BP35A1(
+    IBP35Configurations configurations,
+    IServiceProvider? serviceProvider = null
+  )
+    : base(
+      configurations: configurations,
+      serialPortStreamFactory: serviceProvider?.GetService<IBP35SerialPortStreamFactory>(),
+      logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<BP35A1>()
+    )
+  {
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1Configurations.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1Configurations.cs
new file mode 100644
index 0000000..eeffe6a
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35A1Configurations.cs
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX;
+
+public sealed class BP35A1Configurations : IBP35Configurations {
+  /// <inheritdoc cref="IBP35Configurations.SerialPortName"/>
+  public string? SerialPortName { get; set; }
+
+  /// <inheritdoc cref="IBP35Configurations.BaudRate"/>
+  public BP35UartBaudRate BaudRate { get; set; } = BP35A1.DefaultValueForBP35UartBaudRate;
+
+  /// <inheritdoc cref="IBP35Configurations.TryLoadFlashMemory"/>
+  public bool TryLoadFlashMemory { get; set; } = BP35Base.DefaultValueForTryLoadFlashMemory;
+
+  SkStackERXUDPDataFormat IBP35Configurations.ERXUDPDataFormat => SkStackERXUDPDataFormat.Binary;
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.DefaultSerialPortStreamFactory.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.DefaultSerialPortStreamFactory.cs
new file mode 100644
index 0000000..2b62f11
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.DefaultSerialPortStreamFactory.cs
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.IO;
+using System.IO.Ports;
+
+namespace Smdn.Devices.BP35XX;
+
+#pragma warning disable IDE0040
+partial class BP35Base {
+#pragma warning restore IDE0040
+  private class DefaultSerialPortStreamFactory : IBP35SerialPortStreamFactory {
+    public static DefaultSerialPortStreamFactory Instance { get; } = new();
+
+    public Stream CreateSerialPortStream(IBP35Configurations configurations)
+    {
+      if (string.IsNullOrEmpty(configurations.SerialPortName)) {
+        throw new ArgumentException(
+          message: $"The {nameof(configurations.SerialPortName)} is not set for the {configurations.GetType().Name}",
+          paramName: nameof(configurations)
+        );
+      }
+
+      const string CRLF = "\r\n";
+
+#pragma warning disable CA2000
+      var port = new SerialPort(
+        portName: configurations.SerialPortName,
+        baudRate: configurations.BaudRate switch {
+          BP35UartBaudRate.Baud2400 => 2_400,
+          BP35UartBaudRate.Baud4800 => 4_800,
+          BP35UartBaudRate.Baud9600 => 9_600,
+          BP35UartBaudRate.Baud19200 => 19_200,
+          BP35UartBaudRate.Baud38400 => 38_400,
+          BP35UartBaudRate.Baud57600 => 57_600,
+          BP35UartBaudRate.Baud115200 => 115_200,
+          _ => throw new ArgumentException(
+            message: $"A valid {nameof(BP35UartBaudRate)} value is not set for the {configurations.GetType().Name}",
+            paramName: nameof(configurations)
+          ),
+        },
+        parity: Parity.None,
+        dataBits: 8,
+        stopBits: StopBits.One
+      ) {
+        Handshake = Handshake.None, // TODO: RequestToSend
+        DtrEnable = false,
+        RtsEnable = false,
+        NewLine = CRLF,
+      };
+#pragma warning restore CA2000
+
+      port.Open();
+
+      // discard input buffer to avoid reading previously received data
+      port.DiscardInBuffer();
+
+      return port.BaseStream;
+    }
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.Functions.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.Functions.cs
new file mode 100644
index 0000000..a04999c
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.Functions.cs
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Smdn.Devices.BP35XX;
+
+#pragma warning disable IDE0040
+partial class BP35Base {
+#pragma warning restore IDE0040
+  public ValueTask SetUdpDataFormatAsync(
+    BP35UdpReceiveDataFormat format,
+    CancellationToken cancellationToken = default
+  )
+  {
+    return SendWOPTAsync(
+      mode: format switch {
+        BP35UdpReceiveDataFormat.Binary => BP35ERXUDPFormatBinary,
+        BP35UdpReceiveDataFormat.HexAscii => BP35ERXUDPFormatHexAscii,
+        _ => throw new ArgumentException($"undefined value of {nameof(BP35UdpReceiveDataFormat)}", nameof(format)),
+      },
+      cancellationToken: cancellationToken
+    );
+  }
+
+  public async ValueTask<BP35UdpReceiveDataFormat> GetUdpDataFormatAsync(
+    CancellationToken cancellationToken = default
+  )
+  {
+    var mode = await SendROPTAsync(
+      cancellationToken: cancellationToken
+    ).ConfigureAwait(false);
+
+    return (mode & BP35ERXUDPFormatMask) switch {
+      BP35ERXUDPFormatBinary => BP35UdpReceiveDataFormat.Binary,
+      BP35ERXUDPFormatHexAscii => BP35UdpReceiveDataFormat.HexAscii,
+      _ => BP35UdpReceiveDataFormat.Binary, // XXX
+    };
+  }
+
+  public ValueTask SetUartOptionsAsync(
+    BP35UartBaudRate baudRate,
+    BP35UartCharacterInterval characterInterval = default,
+    BP35UartFlowControl flowControl = default,
+    CancellationToken cancellationToken = default
+  )
+    => SetUartOptionsAsync(
+      uartConfigurations: new(baudRate, characterInterval, flowControl),
+      cancellationToken: cancellationToken
+    );
+
+  public ValueTask SetUartOptionsAsync(
+    BP35UartConfigurations uartConfigurations,
+    CancellationToken cancellationToken = default
+  )
+    => SendWUARTAsync(
+      mode: uartConfigurations.Mode,
+      cancellationToken: cancellationToken
+    );
+
+  public async ValueTask<BP35UartConfigurations> GetUartOptionsAsync(
+    CancellationToken cancellationToken = default
+  )
+  {
+    var mode = await SendRUARTAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
+
+    return new(mode);
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.SkStackIP.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.SkStackIP.cs
new file mode 100644
index 0000000..ad77ea4
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.SkStackIP.cs
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.Buffers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Smdn.Formats;
+using Smdn.Net.SkStackIP.Protocol;
+
+namespace Smdn.Devices.BP35XX;
+
+#pragma warning disable IDE0040
+partial class BP35Base {
+#pragma warning restore IDE0040
+  private static readonly SkStackProtocolSyntax RMCommandSyntax = new BP35CommandSyntax();
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private const byte BP35ERXUDPFormatMask = 0b_0000000_1;
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private const byte BP35ERXUDPFormatBinary = 0b_0000000_0;
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private const byte BP35ERXUDPFormatHexAscii = 0b_0000000_1;
+
+  /// <summary>
+  ///   <para>Sends a command <c>WOPT</c>.</para>
+  /// </summary>
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private protected async ValueTask SendWOPTAsync(
+    byte mode,
+    CancellationToken cancellationToken = default
+  )
+  {
+    byte[]? modeBytes = null;
+
+    try {
+      modeBytes = ArrayPool<byte>.Shared.Rent(2);
+
+      _ = Hexadecimal.TryEncodeUpperCase(mode, modeBytes.AsSpan(), out var lengthOfMODE);
+
+      _ = await SendCommandAsync(
+        command: BP35CommandNames.WOPT,
+        writeArguments: writer => writer.WriteToken(modeBytes.AsSpan(0, lengthOfMODE)),
+        syntax: RMCommandSyntax,
+        cancellationToken: cancellationToken,
+        throwIfErrorStatus: true
+      ).ConfigureAwait(false);
+    }
+    finally {
+      if (modeBytes is not null)
+        ArrayPool<byte>.Shared.Return(modeBytes);
+    }
+  }
+
+  /// <summary>
+  ///   <para>Sends a command <c>ROPT</c>.</para>
+  /// </summary>
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.31. ROPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private protected async ValueTask<byte> SendROPTAsync(
+    CancellationToken cancellationToken = default
+  )
+  {
+    var resp = await SendCommandAsync(
+      command: BP35CommandNames.ROPT,
+      writeArguments: null,
+      syntax: RMCommandSyntax,
+      cancellationToken: cancellationToken,
+      throwIfErrorStatus: true
+    ).ConfigureAwait(false);
+
+    return Convert.ToByte(Encoding.ASCII.GetString(resp.StatusText.Span), 16);
+  }
+
+  /// <summary>
+  ///   <para>Sends a command <c>WUART</c>.</para>
+  /// </summary>
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private protected async ValueTask SendWUARTAsync(
+    byte mode,
+    CancellationToken cancellationToken = default
+  )
+  {
+    byte[]? modeBytes = null;
+
+    try {
+      modeBytes = ArrayPool<byte>.Shared.Rent(2);
+
+      _ = Hexadecimal.TryEncodeUpperCase(mode, modeBytes.AsSpan(), out var lengthOfMODE);
+
+      _ = await SendCommandAsync(
+        command: BP35CommandNames.WUART,
+        writeArguments: writer => writer.WriteToken(modeBytes.AsSpan(0, lengthOfMODE)),
+        syntax: RMCommandSyntax,
+        cancellationToken: cancellationToken,
+        throwIfErrorStatus: true
+      ).ConfigureAwait(false);
+    }
+    finally {
+      if (modeBytes is not null)
+        ArrayPool<byte>.Shared.Return(modeBytes);
+    }
+  }
+
+  /// <summary>
+  ///   <para>Sends a command <c>RUART</c>.</para>
+  /// </summary>
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  private protected async ValueTask<byte> SendRUARTAsync(
+    CancellationToken cancellationToken = default
+  )
+  {
+    var resp = await SendCommandAsync(
+      command: BP35CommandNames.RUART,
+      writeArguments: null,
+      syntax: RMCommandSyntax,
+      cancellationToken: cancellationToken,
+      throwIfErrorStatus: true
+    ).ConfigureAwait(false);
+
+    return Convert.ToByte(Encoding.ASCII.GetString(resp.StatusText.Span), 16);
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.cs
new file mode 100644
index 0000000..a498b91
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35Base.cs
@@ -0,0 +1,225 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1848
+
+using System;
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+using System.Diagnostics.CodeAnalysis;
+#endif
+#if !SYSTEM_CONVERT_TOHEXSTRING
+using System.Buffers; // ArrayPool
+#endif
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX;
+
+public abstract partial class BP35Base : SkStackClient {
+  internal const bool DefaultValueForTryLoadFlashMemory = true;
+
+  private protected static async ValueTask<TBP35XX> InitializeAsync<TBP35XX>(
+    TBP35XX device,
+    bool tryLoadFlashMemory = DefaultValueForTryLoadFlashMemory,
+    IServiceProvider? serviceProvider = null,
+    CancellationToken cancellationToken = default
+  )
+    where TBP35XX : BP35Base
+  {
+#pragma warning disable CA1510
+    if (device is null)
+      throw new ArgumentNullException(nameof(device));
+#pragma warning disable CA1510
+
+    try {
+      await device.InitializeAsync(
+        tryLoadFlashMemory,
+        serviceProvider,
+        cancellationToken
+      ).ConfigureAwait(false);
+
+      return device;
+    }
+    catch {
+      device.Dispose();
+
+      throw;
+    }
+  }
+
+  private protected static InvalidOperationException CreateNotInitializedException()
+    => new(message: "not initialized");
+
+  /*
+   * instance members
+   */
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+  [MemberNotNullWhen(true, nameof(skstackVersion))]
+  [MemberNotNullWhen(true, nameof(skstackAppVersion))]
+  [MemberNotNullWhen(true, nameof(linkLocalAddress))]
+  [MemberNotNullWhen(true, nameof(macAddress))]
+  [MemberNotNullWhen(true, nameof(rohmUserId))]
+  [MemberNotNullWhen(true, nameof(rohmPassword))]
+#endif
+  private protected bool IsInitialized { get; private set; }
+
+#if !SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+#pragma warning disable CS8603
+#endif
+  private Version? skstackVersion;
+  public Version SkStackVersion => IsInitialized ? skstackVersion : throw CreateNotInitializedException();
+
+  private string? skstackAppVersion;
+  public string SkStackAppVersion => IsInitialized ? skstackAppVersion : throw CreateNotInitializedException();
+
+  private IPAddress? linkLocalAddress;
+  public IPAddress LinkLocalAddress => IsInitialized ? linkLocalAddress : throw CreateNotInitializedException();
+
+  private PhysicalAddress? macAddress;
+  public PhysicalAddress MacAddress => IsInitialized ? macAddress : throw CreateNotInitializedException();
+
+  private string? rohmUserId;
+  public string RohmUserId => IsInitialized ? rohmUserId : throw CreateNotInitializedException();
+
+  private string? rohmPassword;
+  public string RohmPassword => IsInitialized ? rohmPassword : throw CreateNotInitializedException();
+#pragma warning restore CS8603
+
+  /// <summary>
+  /// Initializes a new instance of the <see cref="BP35Base"/> class with specifying the serial port name.
+  /// </summary>
+  /// <param name="configurations">
+  /// A <see cref="IBP35Configurations"/> that holds the configurations to the <see cref="BP35Base"/> instance.
+  /// </param>
+  /// <param name="serialPortStreamFactory">
+  /// A <see cref="IBP35SerialPortStreamFactory"/> that provides the function to create the serial port stream according to the <paramref name="configurations"/>.
+  /// </param>
+  /// <param name="logger">The <see cref="ILogger"/> to report the situation.</param>
+#pragma warning disable IDE0290
+  private protected BP35Base(
+    IBP35Configurations configurations,
+    IBP35SerialPortStreamFactory? serialPortStreamFactory,
+    ILogger? logger
+  )
+#pragma warning restore IDE0290
+    : base(
+      stream: (serialPortStreamFactory ?? DefaultSerialPortStreamFactory.Instance).CreateSerialPortStream(
+        configurations ?? throw new ArgumentNullException(nameof(configurations))
+      ),
+      leaveStreamOpen: false, // should close the opened stream
+      erxudpDataFormat: configurations.ERXUDPDataFormat,
+      logger: logger
+    )
+  {
+  }
+
+  private async ValueTask InitializeAsync(
+    bool tryLoadFlashMemory,
+    IServiceProvider? serviceProvider,
+    CancellationToken cancellationToken
+  )
+  {
+    // retrieve firmware version
+    skstackVersion = (await SendSKVERAsync(cancellationToken).ConfigureAwait(false)).Payload;
+
+    Logger?.LogInformation("{Name}: {Value}", nameof(SkStackVersion), skstackVersion);
+
+    skstackAppVersion = (await SendSKAPPVERAsync(cancellationToken).ConfigureAwait(false)).Payload;
+
+    Logger?.LogInformation("{Name}: {Value}", nameof(SkStackAppVersion), skstackAppVersion);
+
+    // retrieve EINFO
+    var respInfo = await SendSKINFOAsync(cancellationToken).ConfigureAwait(false);
+    var einfo = respInfo.Payload!;
+
+    linkLocalAddress = einfo.LinkLocalAddress;
+    macAddress = einfo.MacAddress;
+
+    Logger?.LogInformation("{Name}: {Value}", nameof(LinkLocalAddress), linkLocalAddress);
+    Logger?.LogInformation("{Name}: {Value}", nameof(MacAddress), macAddress);
+
+    Logger?.LogInformation("{Name}: {Value}", nameof(einfo.Channel), einfo.Channel);
+    Logger?.LogInformation("{Name}: {Value} (0x{ValueToBeDisplayedInHex:X4})", nameof(einfo.PanId), einfo.PanId, einfo.PanId);
+
+    // parse ROHM user ID and password
+    (rohmUserId, rohmPassword) = ParseRohmUserIdAndPassword(linkLocalAddress);
+
+    // try load configuration from flash memory
+    if (tryLoadFlashMemory) {
+      try {
+        await SendSKLOADAsync(cancellationToken).ConfigureAwait(false);
+      }
+      catch (SkStackFlashMemoryIOException) {
+        Logger?.LogWarning("Could not load configuration from flash memory.");
+      }
+    }
+
+    // disable echoback (override loaded configuration)
+    await SendSKSREGAsync(
+      register: SkStackRegister.EnableEchoback,
+      value: false,
+      cancellationToken: cancellationToken
+    ).ConfigureAwait(false);
+
+    // set ERXUDP data format
+    var udpDataFormat = await GetUdpDataFormatAsync(cancellationToken).ConfigureAwait(false);
+
+#pragma warning disable IDE0055, IDE0072
+    ERXUDPDataFormat = udpDataFormat switch {
+      BP35UdpReceiveDataFormat.HexAscii => SkStackERXUDPDataFormat.HexAsciiText,
+      /*BP35UdpReceiveDataFormat.Binary,*/ _ => SkStackERXUDPDataFormat.Binary,
+    };
+#pragma warning restore IDE0055, IDE0072
+
+    await InitializeAsyncCore(serviceProvider, cancellationToken).ConfigureAwait(false);
+
+    IsInitialized = true;
+
+    static (string, string) ParseRohmUserIdAndPassword(IPAddress linkLocalAddress)
+    {
+#if SYSTEM_CONVERT_TOHEXSTRING
+      Span<byte> addressBytes = stackalloc byte[16];
+
+      if (linkLocalAddress.TryWriteBytes(addressBytes, out var bytesWritten) && (8 + 2) <= bytesWritten) {
+        return (
+          Convert.ToHexString(addressBytes.Slice(0, 2)),
+          Convert.ToHexString(addressBytes.Slice(8, 2))
+        );
+      }
+#else
+      byte[]? addressBytes = null;
+
+      try {
+        addressBytes = ArrayPool<byte>.Shared.Rent(16);
+
+        if (linkLocalAddress.TryWriteBytes(addressBytes, out var bytesWritten) && (8 + 2) <= bytesWritten) {
+          return (
+            $"{addressBytes[0]:X2}{addressBytes[1]:X2}",
+            $"{addressBytes[8]:X2}{addressBytes[9]:X2}"
+          );
+        }
+      }
+      finally {
+        if (addressBytes is not null)
+          ArrayPool<byte>.Shared.Return(addressBytes);
+      }
+#endif
+
+      return default; // or throw exception?
+    }
+  }
+
+  private protected virtual ValueTask InitializeAsyncCore(
+    IServiceProvider? serviceProvider,
+    CancellationToken cancellationToken
+  )
+  {
+    // nothing to do in this class
+    return default;
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandNames.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandNames.cs
new file mode 100644
index 0000000..e940474
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandNames.cs
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+using System.Text;
+
+namespace Smdn.Devices.BP35XX;
+
+/// <remarks>
+///   <para>See 'BP35A1コマンドリファレンス 3. コマンドリファレンス' for detailed specifications.</para>
+/// </remarks>
+internal class BP35CommandNames {
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  public static ReadOnlyMemory<byte> WOPT { get; } = Encoding.ASCII.GetBytes(nameof(WOPT));
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.31. ROPT (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  public static ReadOnlyMemory<byte> ROPT { get; } = Encoding.ASCII.GetBytes(nameof(ROPT));
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  public static ReadOnlyMemory<byte> WUART { get; } = Encoding.ASCII.GetBytes(nameof(WUART));
+
+  /// <remarks>
+  ///   <para>See 'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)' for detailed specifications.</para>
+  /// </remarks>
+  public static ReadOnlyMemory<byte> RUART { get; } = Encoding.ASCII.GetBytes(nameof(RUART));
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandSyntax.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandSyntax.cs
new file mode 100644
index 0000000..b522260
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35CommandSyntax.cs
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+using System;
+
+using Smdn.Net.SkStackIP.Protocol;
+
+namespace Smdn.Devices.BP35XX;
+
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.31. ROPT (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+/// <seealso cref="SkStackProtocolSyntax"/>
+internal sealed class BP35CommandSyntax : SkStackProtocolSyntax {
+  /// <summary>
+  /// Gets the newline character used in the product configuration commands (プロダクト設定コマンド).
+  /// Only <c>CR</c> is used as a newline character in the product configuration command and its response.
+  /// </summary>
+  public override ReadOnlySpan<byte> EndOfCommandLine => "\r"u8;
+  public override bool ExpectStatusLine => true;
+  public override ReadOnlySpan<byte> EndOfStatusLine => "\r"u8;
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartBaudRate.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartBaudRate.cs
new file mode 100644
index 0000000..6b3e9d7
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartBaudRate.cs
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+namespace Smdn.Devices.BP35XX;
+
+/// <summary>
+/// An enumeration type representing the configuration values for the UART baud rate, to be set or get by the <c>WUART</c> and <c>RUART</c> commands.
+/// </summary>
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+#pragma warning disable CA1027
+public enum BP35UartBaudRate : byte {
+#pragma warning restore CA1027
+  Baud115200  = 0b_0_000_0_000, // default(BP35UartBaudRate)
+  Baud2400    = 0b_0_000_0_001,
+  Baud4800    = 0b_0_000_0_010,
+  Baud9600    = 0b_0_000_0_011,
+  Baud19200   = 0b_0_000_0_100,
+  Baud38400   = 0b_0_000_0_101,
+  Baud57600   = 0b_0_000_0_110,
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartCharacterInterval.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartCharacterInterval.cs
new file mode 100644
index 0000000..7be4179
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartCharacterInterval.cs
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+namespace Smdn.Devices.BP35XX;
+
+/// <summary>
+/// An enumeration type representing the configuration values for the inter-character intervals in UART, to be set or get by the <c>WUART</c> and <c>RUART</c> commands.
+/// </summary>
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+#pragma warning disable CA1027
+public enum BP35UartCharacterInterval : byte {
+#pragma warning restore CA1027
+  None            = 0b_0_000_0_000, // default(BP35UartCharacterInterval)
+  Microseconds100 = 0b_0_001_0_000,
+  Microseconds200 = 0b_0_010_0_000,
+  Microseconds300 = 0b_0_011_0_000,
+  Microseconds400 = 0b_0_100_0_000,
+  Microseconds50  = 0b_0_101_0_000, // or may be 500μsecs?
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartConfigurations.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartConfigurations.cs
new file mode 100644
index 0000000..8211e61
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartConfigurations.cs
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.Devices.BP35XX;
+
+/// <summary>
+/// A read-only structure that represents the configuration values relevant to UART, set and get by the <c>WUART</c> and <c>RUART</c> commands.
+/// </summary>
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+public readonly struct BP35UartConfigurations {
+  private const byte BaudRateMask = 0b_0_000_0_111;
+  // private const byte ReservedBitMask = 0b_0_000_1_000;
+  private const byte CharacterIntervalMask = 0b_0_111_0_000;
+  private const byte FlowControlMask = 0b_1_000_0_000;
+
+  internal byte Mode { get; }
+
+  public BP35UartBaudRate BaudRate => (BP35UartBaudRate)(Mode & BaudRateMask);
+  public BP35UartCharacterInterval CharacterInterval => (BP35UartCharacterInterval)(Mode & CharacterIntervalMask);
+  public BP35UartFlowControl FlowControl => (BP35UartFlowControl)(Mode & FlowControlMask);
+
+  internal BP35UartConfigurations(
+    byte mode
+  )
+  {
+    Mode = mode;
+  }
+
+  public BP35UartConfigurations(
+    BP35UartBaudRate baudRate,
+    BP35UartCharacterInterval characterInterval,
+    BP35UartFlowControl flowControl
+  )
+  {
+#if SYSTEM_ENUM_ISDEFINED_OF_TENUM
+    if (!Enum.IsDefined(baudRate))
+#else
+    if (!Enum.IsDefined(typeof(BP35UartBaudRate), baudRate))
+#endif
+      throw new ArgumentException($"undefined value of {nameof(BP35UartBaudRate)}", nameof(baudRate));
+
+#if SYSTEM_ENUM_ISDEFINED_OF_TENUM
+    if (!Enum.IsDefined(flowControl))
+#else
+    if (!Enum.IsDefined(typeof(BP35UartFlowControl), flowControl))
+#endif
+      throw new ArgumentException($"undefined value of {nameof(BP35UartFlowControl)}", nameof(flowControl));
+
+#if SYSTEM_ENUM_ISDEFINED_OF_TENUM
+    if (!Enum.IsDefined(characterInterval))
+#else
+    if (!Enum.IsDefined(typeof(BP35UartCharacterInterval), characterInterval))
+#endif
+      throw new ArgumentException($"undefined value of {nameof(BP35UartCharacterInterval)}", nameof(characterInterval));
+
+    Mode = (byte)((byte)baudRate | (byte)characterInterval | (byte)flowControl);
+  }
+
+  public void Deconstruct(
+    out BP35UartBaudRate baudRate,
+    out BP35UartCharacterInterval characterInterval,
+    out BP35UartFlowControl flowControl
+  )
+  {
+    baudRate = BaudRate;
+    characterInterval = CharacterInterval;
+    flowControl = FlowControl;
+  }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartFlowControl.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartFlowControl.cs
new file mode 100644
index 0000000..b69bc38
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UartFlowControl.cs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+namespace Smdn.Devices.BP35XX;
+
+/// <summary>
+/// An enumeration type representing the configuration values for the flow control in UART, to be set or get by the <c>WUART</c> and <c>RUART</c> commands.
+/// </summary>
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.32. WUART (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.33. RUART (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+#pragma warning disable CA1027
+public enum BP35UartFlowControl : byte {
+#pragma warning restore CA1027
+  Disabled = 0b_0_000_0_000, // default(BP35UartFlowControl)
+  Enabled  = 0b_1_000_0_000,
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UdpReceiveDataFormat.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UdpReceiveDataFormat.cs
new file mode 100644
index 0000000..cd4e642
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/BP35UdpReceiveDataFormat.cs
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+
+namespace Smdn.Devices.BP35XX;
+
+/// <summary>
+/// An enumeration type representing the configuration values for the display format of the data part in ERXUDP event, to be set or get by the <c>WOPT</c> and <c>ROPT</c> commands.
+/// </summary>
+/// <remarks>
+///   <para>See below for detailed specifications.</para>
+///   <list type="bullet">
+///     <item><description>'BP35A1コマンドリファレンス 3.30. WOPT (プロダクト設定コマンド)'</description></item>
+///     <item><description>'BP35A1コマンドリファレンス 3.31. ROPT (プロダクト設定コマンド)'</description></item>
+///   </list>
+/// </remarks>
+#pragma warning disable CA1027
+public enum BP35UdpReceiveDataFormat : byte {
+#pragma warning restore CA1027
+  Binary = 0b_0000_0000,
+  HexAscii = 0b_0000_0001,
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35Configurations.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35Configurations.cs
new file mode 100644
index 0000000..457e941
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35Configurations.cs
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using Smdn.Net.SkStackIP;
+
+namespace Smdn.Devices.BP35XX;
+
+public interface IBP35Configurations {
+  /// <summary>
+  /// Gets the <see cref="string"/> value that holds the serial port name for communicating with the device that implements the SKSTACK-IP protocol.
+  /// </summary>
+  string? SerialPortName { get; }
+
+  /// <summary>
+  /// Gets the <see cref="BP35UartBaudRate"/> value that specifies the baud rate of the serial port for communicating with the device.
+  /// </summary>
+  BP35UartBaudRate BaudRate { get; }
+
+  /// <summary>
+  /// Gets a value indicating whether or not to attempt to load the configuration from flash memory during initialization.
+  /// </summary>
+  bool TryLoadFlashMemory { get; }
+
+  /// <summary>
+  /// Gets the value that specifies the format of the data part received in the event <c>ERXUDP</c>. See <see cref="SkStackClient.ERXUDPDataFormat"/>.
+  /// </summary>
+  /// <seealso cref="SkStackClient.ERXUDPDataFormat"/>
+  /// <seealso cref="SkStackERXUDPDataFormat"/>
+  SkStackERXUDPDataFormat ERXUDPDataFormat { get; }
+}
diff --git a/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35SerialPortStreamFactory.cs b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35SerialPortStreamFactory.cs
new file mode 100644
index 0000000..b6a192f
--- /dev/null
+++ b/src/Smdn.Devices.BP35XX/Smdn.Devices.BP35XX/IBP35SerialPortStreamFactory.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.IO;
+
+namespace Smdn.Devices.BP35XX;
+
+public interface IBP35SerialPortStreamFactory {
+  Stream CreateSerialPortStream(IBP35Configurations configurations);
+}

Notes

Full Changelog: releases/Smdn.Net.SkStackIP-1.0.0...releases/Smdn.Devices.BP35XX-1.0.0