Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save smdn/8793a78532f9107f60af2b90862a4282 to your computer and use it in GitHub Desktop.
Save smdn/8793a78532f9107f60af2b90862a4282 to your computer and use it in GitHub Desktop.
Smdn.Net.EchonetLite 2.0.0-preview1 Release Notes

main/Smdn.Net.EchonetLite-2.0.0-preview1

diff --git a/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net6.0.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net6.0.apilist.cs
index 3ba4506..43a384f 100644
--- a/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net6.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net6.0.apilist.cs
@@ -1,315 +1,256 @@
-// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-1.0.0)
+// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-2.0.0-preview1)
// Name: Smdn.Net.EchonetLite
-// AssemblyVersion: 1.0.0.0
-// InformationalVersion: 1.0.0+b4bb897cd7ff315f38b5c1be40f3049db3c5b72f
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0-preview1+2afe0aa023b391033e8606759aaf401afa325ddb
// TargetFramework: .NETCoreApp,Version=v6.0
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
-// Smdn.Net.EchonetLite.Specifications, Version=1.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Appendix, Version=2.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
// System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+// System.ComponentModel.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// 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.Net.Sockets, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.ObjectModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
// System.Threading, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
#nullable enable annotations
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using EchoDotNetLite;
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Models;
-using EchoDotNetLite.Specifications;
-using EchoDotNetLiteLANBridge;
-
-namespace EchoDotNetLite {
- public interface IEchonetLiteHandler {
- event EventHandler<(IPAddress Address, ReadOnlyMemory<byte> Data)> Received;
-
- ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
- }
+using Smdn.Net.EchonetLite;
+using Smdn.Net.EchonetLite.Appendix;
+using Smdn.Net.EchonetLite.Protocol;
+using Smdn.Net.EchonetLite.Transport;
- public class EchoClient :
+namespace Smdn.Net.EchonetLite {
+ public class EchonetClient :
IAsyncDisposable,
IDisposable
{
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListPropertyMapAcquiring;
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListUpdated;
- public event EventHandler<EchoNode>? InstanceListUpdating;
- public event EventHandler<EchoNode>? NodeJoined;
- [Obsolete("Use OnNodeJoined instead.")]
- public event EventHandler<EchoNode>? OnNodeJoined { add; remove; }
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquired;
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquiring;
-
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchoClient>? logger = null) {}
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchoClient>? logger) {}
-
- [Obsolete("Use Nodes instead.")]
- public ICollection<EchoNode> NodeList { get; }
- public ICollection<EchoNode> Nodes { get; }
- public EchoNode SelfNode { get; }
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListPropertyMapAcquiring;
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListUpdated;
+ public event EventHandler<EchonetNode>? InstanceListUpdating;
+ public event EventHandler<EchonetNode>? NodeJoined;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquired;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquiring;
+
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchonetClient>? logger = null) {}
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchonetClient>? logger) {}
+
+ public ICollection<EchonetNode> Nodes { get; }
+ public EchonetNode SelfNode { get; }
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual async ValueTask DisposeAsyncCore() {}
- protected virtual void OnInstanceListPropertyMapAcquiring(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdated(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdating(EchoNode node) {}
- protected virtual void OnPropertyMapAcquired(EchoNode node, EchoObjectInstance device) {}
- protected virtual void OnPropertyMapAcquiring(EchoNode node, EchoObjectInstance device) {}
+ protected virtual void OnInstanceListPropertyMapAcquiring(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdated(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdating(EchonetNode node) {}
+ protected virtual void OnPropertyMapAcquired(EchonetNode node, EchonetObject device) {}
+ protected virtual void OnPropertyMapAcquiring(EchonetNode node, EchonetObject device) {}
public async ValueTask PerformInstanceListNotificationAsync(CancellationToken cancellationToken = default) {}
+ public async Task PerformInstanceListNotificationRequestAsync<TState>(Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListPropertyMapAcquiring, Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListUpdated, Func<EchonetClient, EchonetNode, EchonetObject, TState, bool>? onPropertyMapAcquired, TState state, CancellationToken cancellationToken = default) {}
public async ValueTask PerformInstanceListNotificationRequestAsync(CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchonetObject sourceObject, EchonetNode destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> PropertiesSet, IReadOnlyCollection<PropertyRequest> PropertiesGet)> PerformPropertyValueWriteReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> propertiesSet, IEnumerable<EchonetProperty> propertiesGet, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
protected void ThrowIfDisposed() {}
- [Obsolete("Use PerformInstanceListNotificationAsync instead.")]
- public async Task インスタンスリスト通知Async() {}
- [Obsolete("Use PerformInstanceListNotificationRequestAsync instead.")]
- public async Task インスタンスリスト通知要求Async() {}
- [Obsolete("Use PerformPropertyValueWriteRequestResponseRequiredAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み応答要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>?)> プロパティ値書き込み要求応答不要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationResponseRequiredAsync instead.")]
- public async Task<IReadOnlyCollection<PropertyRequest>> プロパティ値通知応答要(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationRequestAsync instead.")]
- public async Task プロパティ値通知要求(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
- [Obsolete("Use PerformPropertyValueNotificationAsync instead.")]
- public async Task 自発プロパティ値通知(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
}
- public static class FrameSerializer {
- public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
- public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
- public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
- public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
- }
+ public sealed class EchonetNode {
+ public event NotifyCollectionChangedEventHandler? DevicesChanged;
- public static class PropertyContentSerializer {
- public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
- public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
- public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
+ public EchonetNode(IPAddress address, EchonetObject nodeProfile) {}
+
+ public IPAddress Address { get; }
+ public ICollection<EchonetObject> Devices { get; }
+ public EchonetObject NodeProfile { get; }
}
-}
-namespace EchoDotNetLite.Common {
- [Obsolete("Use NotifyCollectionChangedEventArgs instead.")]
- public enum CollectionChangeType : int {
- Add = 1,
- Remove = 2,
+ public sealed class EchonetObject {
+ public event NotifyCollectionChangedEventHandler? PropertiesChanged;
+
+ public EchonetObject(EOJ eoj) {}
+ public EchonetObject(EchonetObjectSpecification classObject, byte instanceCode) {}
+
+ public IEnumerable<EchonetProperty> AnnoProperties { get; }
+ public IEnumerable<EchonetProperty> GetProperties { get; }
+ public bool HasPropertyMapAcquired { get; }
+ public byte InstanceCode { get; }
+ public IReadOnlyCollection<EchonetProperty> Properties { get; }
+ public IEnumerable<EchonetProperty> SetProperties { get; }
+ public EchonetObjectSpecification Spec { get; }
}
- public static class Extentions {
- public static string GetDebugString(this EchoObjectInstance echoObjectInstance) {}
- public static string GetDebugString(this EchoPropertyInstance echoPropertyInstance) {}
- public static bool TryGetAddedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? addedItem) where TItem : class {}
- public static bool TryGetRemovedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? removedItem) where TItem : class {}
+ public sealed class EchonetProperty {
+ public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
+
+ public EchonetProperty(EchonetPropertySpecification spec) {}
+ public EchonetProperty(EchonetPropertySpecification spec, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+
+ public bool CanAnnounceStatusChange { get; }
+ public bool CanGet { get; }
+ public bool CanSet { get; }
+ public EchonetPropertySpecification Spec { get; }
+ public ReadOnlyMemory<byte> ValueMemory { get; }
+ public ReadOnlySpan<byte> ValueSpan { get; }
+
+ public void SetValue(ReadOnlyMemory<byte> newValue) {}
+ public void WriteValue(Action<IBufferWriter<byte>> write) {}
}
}
-namespace EchoDotNetLite.Enums {
+namespace Smdn.Net.EchonetLite.Protocol {
+ public interface IEData {
+ }
+
public enum EHD1 : byte {
- ECHONETLite = 16,
+ EchonetLite = 16,
+ MaskEchonet = 128,
+ None = 0,
}
public enum EHD2 : byte {
Type1 = 129,
Type2 = 130,
}
public enum ESV : byte {
Get = 98,
- Get_Res = 114,
- Get_SNA = 82,
- INF = 115,
- INFC = 116,
- INFC_Res = 122,
- INF_REQ = 99,
- INF_SNA = 83,
+ GetResponse = 114,
+ GetServiceNotAvailable = 82,
+ Inf = 115,
+ InfC = 116,
+ InfCResponse = 122,
+ InfRequest = 99,
+ InfServiceNotAvailable = 83,
+ Invalid = 0,
SetC = 97,
- SetC_SNA = 81,
+ SetCServiceNotAvailable = 81,
SetGet = 110,
- SetGet_Res = 126,
- SetGet_SNA = 94,
+ SetGetResponse = 126,
+ SetGetServiceNotAvailable = 94,
SetI = 96,
- SetI_SNA = 80,
- Set_Res = 113,
+ SetIServiceNotAvailable = 80,
+ SetResponse = 113,
}
-}
-namespace EchoDotNetLite.Models {
- public interface IEDATA {
- }
-
- public sealed class EDATA1 : IEDATA {
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
+ public sealed class EData1 : IEData {
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
public EOJ DEOJ { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public ESV ESV { get; }
[MemberNotNullWhen(false, "OPCList")]
[MemberNotNullWhen(true, "OPCGetList")]
[MemberNotNullWhen(true, "OPCSetList")]
[JsonIgnore]
public bool IsWriteOrReadService { [MemberNotNullWhen(false, "OPCList"), MemberNotNullWhen(true, "OPCGetList"), MemberNotNullWhen(true, "OPCSetList")] get; }
public IReadOnlyCollection<PropertyRequest>? OPCGetList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCSetList { get; }
public EOJ SEOJ { get; }
- public (IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>) GetOPCSetGetList() {}
+ public (IReadOnlyCollection<PropertyRequest> OPCSetList, IReadOnlyCollection<PropertyRequest> OPCGetList) GetOPCSetGetList() {}
}
- public sealed class EDATA2 : IEDATA {
- public EDATA2(ReadOnlyMemory<byte> message) {}
+ public sealed class EData2 : IEData {
+ public EData2(ReadOnlyMemory<byte> message) {}
public ReadOnlyMemory<byte> Message { get; }
}
- public sealed class EchoNode {
- public event NotifyCollectionChangedEventHandler? DevicesChanged;
- [Obsolete("Use DevicesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoObjectInstance)>? OnCollectionChanged;
-
- public EchoNode(IPAddress address, EchoObjectInstance nodeProfile) {}
-
- public IPAddress Address { get; }
- public ICollection<EchoObjectInstance> Devices { get; }
- public EchoObjectInstance NodeProfile { get; }
- }
-
- public sealed class EchoObjectInstance {
- [Obsolete("Use PropertiesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoPropertyInstance)>? OnCollectionChanged;
- public event NotifyCollectionChangedEventHandler? PropertiesChanged;
-
- public EchoObjectInstance(EOJ eoj) {}
- public EchoObjectInstance(IEchonetObject classObject, byte instanceCode) {}
-
- [Obsolete("Use AnnoProperties instead.")]
- public IEnumerable<EchoPropertyInstance> ANNOProperties { get; }
- public IEnumerable<EchoPropertyInstance> AnnoProperties { get; }
- public EOJ EOJ { get; }
- [Obsolete("Use GetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> GETProperties { get; }
- public IEnumerable<EchoPropertyInstance> GetProperties { get; }
- public bool HasPropertyMapAcquired { get; }
- public byte InstanceCode { get; }
- [Obsolete("Use HasPropertyMapAcquired instead.")]
- public bool IsPropertyMapGet { get; }
- public IReadOnlyCollection<EchoPropertyInstance> Properties { get; }
- [Obsolete("Use SetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> SETProperties { get; }
- public IEnumerable<EchoPropertyInstance> SetProperties { get; }
- public IEchonetObject Spec { get; }
+ public static class FrameSerializer {
+ public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
+ public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
+ public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
+ public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
}
- public sealed class EchoPropertyInstance {
- public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
- [Obsolete("Use ValueChanged instead.")]
- public event EventHandler<ReadOnlyMemory<byte>>? ValueSet;
-
- public EchoPropertyInstance(EchoProperty spec) {}
- public EchoPropertyInstance(EchoProperty spec, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
-
- public bool Anno { get; }
- public bool Get { get; }
- public bool Set { get; }
- public EchoProperty Spec { get; }
- public ReadOnlyMemory<byte> ValueMemory { get; }
- public ReadOnlySpan<byte> ValueSpan { get; }
-
- public void SetValue(ReadOnlyMemory<byte> newValue) {}
- public void WriteValue(Action<IBufferWriter<byte>> write) {}
+ public static class PropertyContentSerializer {
+ public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
+ public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
+ public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
}
public readonly struct EOJ : IEquatable<EOJ> {
public static bool operator == (EOJ c1, EOJ c2) {}
public static bool operator != (EOJ c1, EOJ c2) {}
public EOJ(byte classGroupCode, byte classCode, byte instanceCode) {}
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassGroupCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte InstanceCode { get; }
public bool Equals(EOJ other) {}
- public override bool Equals(object? other) {}
+ public override bool Equals(object? obj) {}
public override int GetHashCode() {}
}
public readonly struct Frame {
- public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEDATA edata) {}
+ public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEData edata) {}
- public IEDATA? EDATA { get; }
+ public IEData? EData { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD1 EHD1 { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD2 EHD2 { get; }
[JsonConverter(typeof(SingleUInt16JsonConverter))]
public ushort TID { get; }
}
public readonly struct PropertyRequest {
public PropertyRequest(byte epc) {}
public PropertyRequest(byte epc, ReadOnlyMemory<byte> edt) {}
[JsonConverter(typeof(ByteSequenceJsonConverter))]
public ReadOnlyMemory<byte> EDT { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte EPC { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte PDC { get; }
}
}
-namespace EchoDotNetLiteLANBridge {
- [Obsolete("Use UdpEchonetLiteHandler instead.")]
- public class LANClient : UdpEchonetLiteHandler {
- public LANClient(ILogger<LANClient> logger) {}
- }
-
- public class UdpEchonetLiteHandler :
- IDisposable,
- IEchonetLiteHandler
- {
- public event EventHandler<(IPAddress, ReadOnlyMemory<byte>)>? Received;
-
+namespace Smdn.Net.EchonetLite.Transport {
+ public class UdpEchonetLiteHandler : EchonetLiteHandler {
public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger) {}
- public void Dispose() {}
- public async ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken) {}
+ public override IPAddress? LocalAddress { get; }
+ public override ISynchronizeInvoke? SynchronizingObject { get; set; }
+
+ protected override void Dispose(bool disposing) {}
+ protected override ValueTask DisposeAsyncCore() {}
+ protected override async ValueTask<IPAddress> ReceiveAsyncCore(IBufferWriter<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendAsyncCore(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendToAsyncCore(IPAddress remoteAddress, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
}
}
// 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.Net.EchonetLite/Smdn.Net.EchonetLite-net8.0.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net8.0.apilist.cs
index 4ebd47e..2376f2c 100644
--- a/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net8.0.apilist.cs
+++ b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-net8.0.apilist.cs
@@ -1,315 +1,256 @@
-// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-1.0.0)
+// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-2.0.0-preview1)
// Name: Smdn.Net.EchonetLite
-// AssemblyVersion: 1.0.0.0
-// InformationalVersion: 1.0.0+b4bb897cd7ff315f38b5c1be40f3049db3c5b72f
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0-preview1+2afe0aa023b391033e8606759aaf401afa325ddb
// TargetFramework: .NETCoreApp,Version=v8.0
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
-// Smdn.Net.EchonetLite.Specifications, Version=1.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Appendix, Version=2.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
// System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+// System.ComponentModel.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Linq, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// 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.Net.Sockets, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.ObjectModel, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Text.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
// System.Threading, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
#nullable enable annotations
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using EchoDotNetLite;
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Models;
-using EchoDotNetLite.Specifications;
-using EchoDotNetLiteLANBridge;
-
-namespace EchoDotNetLite {
- public interface IEchonetLiteHandler {
- event EventHandler<(IPAddress Address, ReadOnlyMemory<byte> Data)> Received;
-
- ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
- }
+using Smdn.Net.EchonetLite;
+using Smdn.Net.EchonetLite.Appendix;
+using Smdn.Net.EchonetLite.Protocol;
+using Smdn.Net.EchonetLite.Transport;
- public class EchoClient :
+namespace Smdn.Net.EchonetLite {
+ public class EchonetClient :
IAsyncDisposable,
IDisposable
{
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListPropertyMapAcquiring;
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListUpdated;
- public event EventHandler<EchoNode>? InstanceListUpdating;
- public event EventHandler<EchoNode>? NodeJoined;
- [Obsolete("Use OnNodeJoined instead.")]
- public event EventHandler<EchoNode>? OnNodeJoined { add; remove; }
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquired;
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquiring;
-
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchoClient>? logger = null) {}
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchoClient>? logger) {}
-
- [Obsolete("Use Nodes instead.")]
- public ICollection<EchoNode> NodeList { get; }
- public ICollection<EchoNode> Nodes { get; }
- public EchoNode SelfNode { get; }
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListPropertyMapAcquiring;
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListUpdated;
+ public event EventHandler<EchonetNode>? InstanceListUpdating;
+ public event EventHandler<EchonetNode>? NodeJoined;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquired;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquiring;
+
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchonetClient>? logger = null) {}
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchonetClient>? logger) {}
+
+ public ICollection<EchonetNode> Nodes { get; }
+ public EchonetNode SelfNode { get; }
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual async ValueTask DisposeAsyncCore() {}
- protected virtual void OnInstanceListPropertyMapAcquiring(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdated(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdating(EchoNode node) {}
- protected virtual void OnPropertyMapAcquired(EchoNode node, EchoObjectInstance device) {}
- protected virtual void OnPropertyMapAcquiring(EchoNode node, EchoObjectInstance device) {}
+ protected virtual void OnInstanceListPropertyMapAcquiring(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdated(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdating(EchonetNode node) {}
+ protected virtual void OnPropertyMapAcquired(EchonetNode node, EchonetObject device) {}
+ protected virtual void OnPropertyMapAcquiring(EchonetNode node, EchonetObject device) {}
public async ValueTask PerformInstanceListNotificationAsync(CancellationToken cancellationToken = default) {}
+ public async Task PerformInstanceListNotificationRequestAsync<TState>(Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListPropertyMapAcquiring, Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListUpdated, Func<EchonetClient, EchonetNode, EchonetObject, TState, bool>? onPropertyMapAcquired, TState state, CancellationToken cancellationToken = default) {}
public async ValueTask PerformInstanceListNotificationRequestAsync(CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchonetObject sourceObject, EchonetNode destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> PropertiesSet, IReadOnlyCollection<PropertyRequest> PropertiesGet)> PerformPropertyValueWriteReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> propertiesSet, IEnumerable<EchonetProperty> propertiesGet, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
protected void ThrowIfDisposed() {}
- [Obsolete("Use PerformInstanceListNotificationAsync instead.")]
- public async Task インスタンスリスト通知Async() {}
- [Obsolete("Use PerformInstanceListNotificationRequestAsync instead.")]
- public async Task インスタンスリスト通知要求Async() {}
- [Obsolete("Use PerformPropertyValueWriteRequestResponseRequiredAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み応答要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>?)> プロパティ値書き込み要求応答不要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationResponseRequiredAsync instead.")]
- public async Task<IReadOnlyCollection<PropertyRequest>> プロパティ値通知応答要(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationRequestAsync instead.")]
- public async Task プロパティ値通知要求(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
- [Obsolete("Use PerformPropertyValueNotificationAsync instead.")]
- public async Task 自発プロパティ値通知(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
}
- public static class FrameSerializer {
- public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
- public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
- public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
- public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
- }
+ public sealed class EchonetNode {
+ public event NotifyCollectionChangedEventHandler? DevicesChanged;
- public static class PropertyContentSerializer {
- public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
- public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
- public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
+ public EchonetNode(IPAddress address, EchonetObject nodeProfile) {}
+
+ public IPAddress Address { get; }
+ public ICollection<EchonetObject> Devices { get; }
+ public EchonetObject NodeProfile { get; }
}
-}
-namespace EchoDotNetLite.Common {
- [Obsolete("Use NotifyCollectionChangedEventArgs instead.")]
- public enum CollectionChangeType : int {
- Add = 1,
- Remove = 2,
+ public sealed class EchonetObject {
+ public event NotifyCollectionChangedEventHandler? PropertiesChanged;
+
+ public EchonetObject(EOJ eoj) {}
+ public EchonetObject(EchonetObjectSpecification classObject, byte instanceCode) {}
+
+ public IEnumerable<EchonetProperty> AnnoProperties { get; }
+ public IEnumerable<EchonetProperty> GetProperties { get; }
+ public bool HasPropertyMapAcquired { get; }
+ public byte InstanceCode { get; }
+ public IReadOnlyCollection<EchonetProperty> Properties { get; }
+ public IEnumerable<EchonetProperty> SetProperties { get; }
+ public EchonetObjectSpecification Spec { get; }
}
- public static class Extentions {
- public static string GetDebugString(this EchoObjectInstance echoObjectInstance) {}
- public static string GetDebugString(this EchoPropertyInstance echoPropertyInstance) {}
- public static bool TryGetAddedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? addedItem) where TItem : class {}
- public static bool TryGetRemovedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? removedItem) where TItem : class {}
+ public sealed class EchonetProperty {
+ public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
+
+ public EchonetProperty(EchonetPropertySpecification spec) {}
+ public EchonetProperty(EchonetPropertySpecification spec, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+
+ public bool CanAnnounceStatusChange { get; }
+ public bool CanGet { get; }
+ public bool CanSet { get; }
+ public EchonetPropertySpecification Spec { get; }
+ public ReadOnlyMemory<byte> ValueMemory { get; }
+ public ReadOnlySpan<byte> ValueSpan { get; }
+
+ public void SetValue(ReadOnlyMemory<byte> newValue) {}
+ public void WriteValue(Action<IBufferWriter<byte>> write) {}
}
}
-namespace EchoDotNetLite.Enums {
+namespace Smdn.Net.EchonetLite.Protocol {
+ public interface IEData {
+ }
+
public enum EHD1 : byte {
- ECHONETLite = 16,
+ EchonetLite = 16,
+ MaskEchonet = 128,
+ None = 0,
}
public enum EHD2 : byte {
Type1 = 129,
Type2 = 130,
}
public enum ESV : byte {
Get = 98,
- Get_Res = 114,
- Get_SNA = 82,
- INF = 115,
- INFC = 116,
- INFC_Res = 122,
- INF_REQ = 99,
- INF_SNA = 83,
+ GetResponse = 114,
+ GetServiceNotAvailable = 82,
+ Inf = 115,
+ InfC = 116,
+ InfCResponse = 122,
+ InfRequest = 99,
+ InfServiceNotAvailable = 83,
+ Invalid = 0,
SetC = 97,
- SetC_SNA = 81,
+ SetCServiceNotAvailable = 81,
SetGet = 110,
- SetGet_Res = 126,
- SetGet_SNA = 94,
+ SetGetResponse = 126,
+ SetGetServiceNotAvailable = 94,
SetI = 96,
- SetI_SNA = 80,
- Set_Res = 113,
+ SetIServiceNotAvailable = 80,
+ SetResponse = 113,
}
-}
-namespace EchoDotNetLite.Models {
- public interface IEDATA {
- }
-
- public sealed class EDATA1 : IEDATA {
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
+ public sealed class EData1 : IEData {
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
public EOJ DEOJ { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public ESV ESV { get; }
[MemberNotNullWhen(false, "OPCList")]
[MemberNotNullWhen(true, "OPCGetList")]
[MemberNotNullWhen(true, "OPCSetList")]
[JsonIgnore]
public bool IsWriteOrReadService { [MemberNotNullWhen(false, "OPCList"), MemberNotNullWhen(true, "OPCGetList"), MemberNotNullWhen(true, "OPCSetList")] get; }
public IReadOnlyCollection<PropertyRequest>? OPCGetList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCSetList { get; }
public EOJ SEOJ { get; }
- public (IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>) GetOPCSetGetList() {}
+ public (IReadOnlyCollection<PropertyRequest> OPCSetList, IReadOnlyCollection<PropertyRequest> OPCGetList) GetOPCSetGetList() {}
}
- public sealed class EDATA2 : IEDATA {
- public EDATA2(ReadOnlyMemory<byte> message) {}
+ public sealed class EData2 : IEData {
+ public EData2(ReadOnlyMemory<byte> message) {}
public ReadOnlyMemory<byte> Message { get; }
}
- public sealed class EchoNode {
- public event NotifyCollectionChangedEventHandler? DevicesChanged;
- [Obsolete("Use DevicesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoObjectInstance)>? OnCollectionChanged;
-
- public EchoNode(IPAddress address, EchoObjectInstance nodeProfile) {}
-
- public IPAddress Address { get; }
- public ICollection<EchoObjectInstance> Devices { get; }
- public EchoObjectInstance NodeProfile { get; }
- }
-
- public sealed class EchoObjectInstance {
- [Obsolete("Use PropertiesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoPropertyInstance)>? OnCollectionChanged;
- public event NotifyCollectionChangedEventHandler? PropertiesChanged;
-
- public EchoObjectInstance(EOJ eoj) {}
- public EchoObjectInstance(IEchonetObject classObject, byte instanceCode) {}
-
- [Obsolete("Use AnnoProperties instead.")]
- public IEnumerable<EchoPropertyInstance> ANNOProperties { get; }
- public IEnumerable<EchoPropertyInstance> AnnoProperties { get; }
- public EOJ EOJ { get; }
- [Obsolete("Use GetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> GETProperties { get; }
- public IEnumerable<EchoPropertyInstance> GetProperties { get; }
- public bool HasPropertyMapAcquired { get; }
- public byte InstanceCode { get; }
- [Obsolete("Use HasPropertyMapAcquired instead.")]
- public bool IsPropertyMapGet { get; }
- public IReadOnlyCollection<EchoPropertyInstance> Properties { get; }
- [Obsolete("Use SetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> SETProperties { get; }
- public IEnumerable<EchoPropertyInstance> SetProperties { get; }
- public IEchonetObject Spec { get; }
+ public static class FrameSerializer {
+ public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
+ public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
+ public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
+ public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
}
- public sealed class EchoPropertyInstance {
- public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
- [Obsolete("Use ValueChanged instead.")]
- public event EventHandler<ReadOnlyMemory<byte>>? ValueSet;
-
- public EchoPropertyInstance(EchoProperty spec) {}
- public EchoPropertyInstance(EchoProperty spec, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
-
- public bool Anno { get; }
- public bool Get { get; }
- public bool Set { get; }
- public EchoProperty Spec { get; }
- public ReadOnlyMemory<byte> ValueMemory { get; }
- public ReadOnlySpan<byte> ValueSpan { get; }
-
- public void SetValue(ReadOnlyMemory<byte> newValue) {}
- public void WriteValue(Action<IBufferWriter<byte>> write) {}
+ public static class PropertyContentSerializer {
+ public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
+ public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
+ public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
}
public readonly struct EOJ : IEquatable<EOJ> {
public static bool operator == (EOJ c1, EOJ c2) {}
public static bool operator != (EOJ c1, EOJ c2) {}
public EOJ(byte classGroupCode, byte classCode, byte instanceCode) {}
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassGroupCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte InstanceCode { get; }
public bool Equals(EOJ other) {}
- public override bool Equals(object? other) {}
+ public override bool Equals(object? obj) {}
public override int GetHashCode() {}
}
public readonly struct Frame {
- public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEDATA edata) {}
+ public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEData edata) {}
- public IEDATA? EDATA { get; }
+ public IEData? EData { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD1 EHD1 { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD2 EHD2 { get; }
[JsonConverter(typeof(SingleUInt16JsonConverter))]
public ushort TID { get; }
}
public readonly struct PropertyRequest {
public PropertyRequest(byte epc) {}
public PropertyRequest(byte epc, ReadOnlyMemory<byte> edt) {}
[JsonConverter(typeof(ByteSequenceJsonConverter))]
public ReadOnlyMemory<byte> EDT { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte EPC { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte PDC { get; }
}
}
-namespace EchoDotNetLiteLANBridge {
- [Obsolete("Use UdpEchonetLiteHandler instead.")]
- public class LANClient : UdpEchonetLiteHandler {
- public LANClient(ILogger<LANClient> logger) {}
- }
-
- public class UdpEchonetLiteHandler :
- IDisposable,
- IEchonetLiteHandler
- {
- public event EventHandler<(IPAddress, ReadOnlyMemory<byte>)>? Received;
-
+namespace Smdn.Net.EchonetLite.Transport {
+ public class UdpEchonetLiteHandler : EchonetLiteHandler {
public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger) {}
- public void Dispose() {}
- public async ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken) {}
+ public override IPAddress? LocalAddress { get; }
+ public override ISynchronizeInvoke? SynchronizingObject { get; set; }
+
+ protected override void Dispose(bool disposing) {}
+ protected override ValueTask DisposeAsyncCore() {}
+ protected override async ValueTask<IPAddress> ReceiveAsyncCore(IBufferWriter<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendAsyncCore(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendToAsyncCore(IPAddress remoteAddress, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
}
}
// 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.Net.EchonetLite/Smdn.Net.EchonetLite-netstandard2.1.apilist.cs b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-netstandard2.1.apilist.cs
index bf47e4f..bbab1c6 100644
--- a/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-netstandard2.1.apilist.cs
+++ b/doc/api-list/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite-netstandard2.1.apilist.cs
@@ -1,304 +1,244 @@
-// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-1.0.0)
+// Smdn.Net.EchonetLite.dll (Smdn.Net.EchonetLite-2.0.0-preview1)
// Name: Smdn.Net.EchonetLite
-// AssemblyVersion: 1.0.0.0
-// InformationalVersion: 1.0.0+b4bb897cd7ff315f38b5c1be40f3049db3c5b72f
+// AssemblyVersion: 2.0.0.0
+// InformationalVersion: 2.0.0-preview1+2afe0aa023b391033e8606759aaf401afa325ddb
// TargetFramework: .NETStandard,Version=v2.1
// Configuration: Release
// Referenced assemblies:
// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
-// Smdn.Net.EchonetLite.Specifications, Version=1.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Appendix, Version=2.0.0.0, Culture=neutral
+// Smdn.Net.EchonetLite.Transport, Version=2.0.0.0, Culture=neutral
// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
// netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
#nullable enable annotations
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using EchoDotNetLite;
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Models;
-using EchoDotNetLite.Specifications;
-using EchoDotNetLiteLANBridge;
-
-namespace EchoDotNetLite {
- public interface IEchonetLiteHandler {
- event EventHandler<(IPAddress Address, ReadOnlyMemory<byte> Data)> Received;
-
- ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
- }
+using Smdn.Net.EchonetLite;
+using Smdn.Net.EchonetLite.Appendix;
+using Smdn.Net.EchonetLite.Protocol;
+using Smdn.Net.EchonetLite.Transport;
- public class EchoClient :
+namespace Smdn.Net.EchonetLite {
+ public class EchonetClient :
IAsyncDisposable,
IDisposable
{
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListPropertyMapAcquiring;
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListUpdated;
- public event EventHandler<EchoNode>? InstanceListUpdating;
- public event EventHandler<EchoNode>? NodeJoined;
- [Obsolete("Use OnNodeJoined instead.")]
- public event EventHandler<EchoNode>? OnNodeJoined { add; remove; }
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquired;
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquiring;
-
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchoClient>? logger = null) {}
- public EchoClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchoClient>? logger) {}
-
- [Obsolete("Use Nodes instead.")]
- public ICollection<EchoNode> NodeList { get; }
- public ICollection<EchoNode> Nodes { get; }
- public EchoNode SelfNode { get; }
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListPropertyMapAcquiring;
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListUpdated;
+ public event EventHandler<EchonetNode>? InstanceListUpdating;
+ public event EventHandler<EchonetNode>? NodeJoined;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquired;
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquiring;
+
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, ILogger<EchonetClient>? logger = null) {}
+ public EchonetClient(IPAddress nodeAddress, IEchonetLiteHandler echonetLiteHandler, bool shouldDisposeEchonetLiteHandler, ILogger<EchonetClient>? logger) {}
+
+ public ICollection<EchonetNode> Nodes { get; }
+ public EchonetNode SelfNode { get; }
protected virtual void Dispose(bool disposing) {}
public void Dispose() {}
public async ValueTask DisposeAsync() {}
protected virtual async ValueTask DisposeAsyncCore() {}
- protected virtual void OnInstanceListPropertyMapAcquiring(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdated(EchoNode node, IReadOnlyList<EchoObjectInstance> instances) {}
- protected virtual void OnInstanceListUpdating(EchoNode node) {}
- protected virtual void OnPropertyMapAcquired(EchoNode node, EchoObjectInstance device) {}
- protected virtual void OnPropertyMapAcquiring(EchoNode node, EchoObjectInstance device) {}
+ protected virtual void OnInstanceListPropertyMapAcquiring(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdated(EchonetNode node, IReadOnlyList<EchonetObject> instances) {}
+ protected virtual void OnInstanceListUpdating(EchonetNode node) {}
+ protected virtual void OnPropertyMapAcquired(EchonetNode node, EchonetObject device) {}
+ protected virtual void OnPropertyMapAcquiring(EchonetNode node, EchonetObject device) {}
public async ValueTask PerformInstanceListNotificationAsync(CancellationToken cancellationToken = default) {}
+ public async Task PerformInstanceListNotificationRequestAsync<TState>(Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListPropertyMapAcquiring, Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListUpdated, Func<EchonetClient, EchonetNode, EchonetObject, TState, bool>? onPropertyMapAcquired, TState state, CancellationToken cancellationToken = default) {}
public async ValueTask PerformInstanceListNotificationRequestAsync(CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public ValueTask PerformPropertyValueNotificationRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteReadRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, CancellationToken cancellationToken = default) {}
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public ValueTask PerformPropertyValueNotificationRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(EchonetObject sourceObject, EchonetNode destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> PropertiesSet, IReadOnlyCollection<PropertyRequest> PropertiesGet)> PerformPropertyValueWriteReadRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> propertiesSet, IEnumerable<EchonetProperty> propertiesGet, CancellationToken cancellationToken = default) {}
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
+ public async Task<(bool Result, IReadOnlyCollection<PropertyRequest> Properties)> PerformPropertyValueWriteRequestResponseRequiredAsync(EchonetObject sourceObject, EchonetNode? destinationNode, EchonetObject destinationObject, IEnumerable<EchonetProperty> properties, CancellationToken cancellationToken = default) {}
protected void ThrowIfDisposed() {}
- [Obsolete("Use PerformInstanceListNotificationAsync instead.")]
- public async Task インスタンスリスト通知Async() {}
- [Obsolete("Use PerformInstanceListNotificationRequestAsync instead.")]
- public async Task インスタンスリスト通知要求Async() {}
- [Obsolete("Use PerformPropertyValueWriteRequestResponseRequiredAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み応答要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>?)> プロパティ値書き込み要求応答不要(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueWriteReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> propertiesSet, IEnumerable<EchoPropertyInstance> propertiesGet, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueReadRequestAsync instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値読み出し(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationResponseRequiredAsync instead.")]
- public async Task<IReadOnlyCollection<PropertyRequest>> プロパティ値通知応答要(EchoObjectInstance sourceObject, EchoNode destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties, int timeoutMilliseconds = 1000) {}
- [Obsolete("Use PerformPropertyValueNotificationRequestAsync instead.")]
- public async Task プロパティ値通知要求(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
- [Obsolete("Use PerformPropertyValueNotificationAsync instead.")]
- public async Task 自発プロパティ値通知(EchoObjectInstance sourceObject, EchoNode? destinationNode, EchoObjectInstance destinationObject, IEnumerable<EchoPropertyInstance> properties) {}
}
- public static class FrameSerializer {
- public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
- public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
- public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
- public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
- }
+ public sealed class EchonetNode {
+ public event NotifyCollectionChangedEventHandler? DevicesChanged;
- public static class PropertyContentSerializer {
- public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
- public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
- public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
+ public EchonetNode(IPAddress address, EchonetObject nodeProfile) {}
+
+ public IPAddress Address { get; }
+ public ICollection<EchonetObject> Devices { get; }
+ public EchonetObject NodeProfile { get; }
}
-}
-namespace EchoDotNetLite.Common {
- [Obsolete("Use NotifyCollectionChangedEventArgs instead.")]
- public enum CollectionChangeType : int {
- Add = 1,
- Remove = 2,
+ public sealed class EchonetObject {
+ public event NotifyCollectionChangedEventHandler? PropertiesChanged;
+
+ public EchonetObject(EOJ eoj) {}
+ public EchonetObject(EchonetObjectSpecification classObject, byte instanceCode) {}
+
+ public IEnumerable<EchonetProperty> AnnoProperties { get; }
+ public IEnumerable<EchonetProperty> GetProperties { get; }
+ public bool HasPropertyMapAcquired { get; }
+ public byte InstanceCode { get; }
+ public IReadOnlyCollection<EchonetProperty> Properties { get; }
+ public IEnumerable<EchonetProperty> SetProperties { get; }
+ public EchonetObjectSpecification Spec { get; }
}
- public static class Extentions {
- public static string GetDebugString(this EchoObjectInstance echoObjectInstance) {}
- public static string GetDebugString(this EchoPropertyInstance echoPropertyInstance) {}
- public static bool TryGetAddedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? addedItem) where TItem : class {}
- public static bool TryGetRemovedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? removedItem) where TItem : class {}
+ public sealed class EchonetProperty {
+ public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
+
+ public EchonetProperty(EchonetPropertySpecification spec) {}
+ public EchonetProperty(EchonetPropertySpecification spec, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc) {}
+ public EchonetProperty(byte classGroupCode, byte classCode, byte epc, bool canAnnounceStatusChange, bool canSet, bool canGet) {}
+
+ public bool CanAnnounceStatusChange { get; }
+ public bool CanGet { get; }
+ public bool CanSet { get; }
+ public EchonetPropertySpecification Spec { get; }
+ public ReadOnlyMemory<byte> ValueMemory { get; }
+ public ReadOnlySpan<byte> ValueSpan { get; }
+
+ public void SetValue(ReadOnlyMemory<byte> newValue) {}
+ public void WriteValue(Action<IBufferWriter<byte>> write) {}
}
}
-namespace EchoDotNetLite.Enums {
+namespace Smdn.Net.EchonetLite.Protocol {
+ public interface IEData {
+ }
+
public enum EHD1 : byte {
- ECHONETLite = 16,
+ EchonetLite = 16,
+ MaskEchonet = 128,
+ None = 0,
}
public enum EHD2 : byte {
Type1 = 129,
Type2 = 130,
}
public enum ESV : byte {
Get = 98,
- Get_Res = 114,
- Get_SNA = 82,
- INF = 115,
- INFC = 116,
- INFC_Res = 122,
- INF_REQ = 99,
- INF_SNA = 83,
+ GetResponse = 114,
+ GetServiceNotAvailable = 82,
+ Inf = 115,
+ InfC = 116,
+ InfCResponse = 122,
+ InfRequest = 99,
+ InfServiceNotAvailable = 83,
+ Invalid = 0,
SetC = 97,
- SetC_SNA = 81,
+ SetCServiceNotAvailable = 81,
SetGet = 110,
- SetGet_Res = 126,
- SetGet_SNA = 94,
+ SetGetResponse = 126,
+ SetGetServiceNotAvailable = 94,
SetI = 96,
- SetI_SNA = 80,
- Set_Res = 113,
+ SetIServiceNotAvailable = 80,
+ SetResponse = 113,
}
-}
-namespace EchoDotNetLite.Models {
- public interface IEDATA {
- }
-
- public sealed class EDATA1 : IEDATA {
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
+ public sealed class EData1 : IEData {
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList) {}
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList) {}
public EOJ DEOJ { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public ESV ESV { get; }
[JsonIgnore]
public bool IsWriteOrReadService { get; }
public IReadOnlyCollection<PropertyRequest>? OPCGetList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCList { get; }
public IReadOnlyCollection<PropertyRequest>? OPCSetList { get; }
public EOJ SEOJ { get; }
- public (IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>) GetOPCSetGetList() {}
+ public (IReadOnlyCollection<PropertyRequest> OPCSetList, IReadOnlyCollection<PropertyRequest> OPCGetList) GetOPCSetGetList() {}
}
- public sealed class EDATA2 : IEDATA {
- public EDATA2(ReadOnlyMemory<byte> message) {}
+ public sealed class EData2 : IEData {
+ public EData2(ReadOnlyMemory<byte> message) {}
public ReadOnlyMemory<byte> Message { get; }
}
- public sealed class EchoNode {
- public event NotifyCollectionChangedEventHandler? DevicesChanged;
- [Obsolete("Use DevicesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoObjectInstance)>? OnCollectionChanged;
-
- public EchoNode(IPAddress address, EchoObjectInstance nodeProfile) {}
-
- public IPAddress Address { get; }
- public ICollection<EchoObjectInstance> Devices { get; }
- public EchoObjectInstance NodeProfile { get; }
- }
-
- public sealed class EchoObjectInstance {
- [Obsolete("Use PropertiesChanged instead.")]
- public event EventHandler<(CollectionChangeType, EchoPropertyInstance)>? OnCollectionChanged;
- public event NotifyCollectionChangedEventHandler? PropertiesChanged;
-
- public EchoObjectInstance(EOJ eoj) {}
- public EchoObjectInstance(IEchonetObject classObject, byte instanceCode) {}
-
- [Obsolete("Use AnnoProperties instead.")]
- public IEnumerable<EchoPropertyInstance> ANNOProperties { get; }
- public IEnumerable<EchoPropertyInstance> AnnoProperties { get; }
- public EOJ EOJ { get; }
- [Obsolete("Use GetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> GETProperties { get; }
- public IEnumerable<EchoPropertyInstance> GetProperties { get; }
- public bool HasPropertyMapAcquired { get; }
- public byte InstanceCode { get; }
- [Obsolete("Use HasPropertyMapAcquired instead.")]
- public bool IsPropertyMapGet { get; }
- public IReadOnlyCollection<EchoPropertyInstance> Properties { get; }
- [Obsolete("Use SetProperties instead.")]
- public IEnumerable<EchoPropertyInstance> SETProperties { get; }
- public IEnumerable<EchoPropertyInstance> SetProperties { get; }
- public IEchonetObject Spec { get; }
+ public static class FrameSerializer {
+ public static void Serialize(Frame frame, IBufferWriter<byte> buffer) {}
+ public static void SerializeEchonetLiteFrameFormat1(IBufferWriter<byte> buffer, ushort tid, EOJ sourceObject, EOJ destinationObject, ESV esv, IEnumerable<PropertyRequest> opcListOrOpcSetList, IEnumerable<PropertyRequest>? opcGetList = null) {}
+ public static void SerializeEchonetLiteFrameFormat2(IBufferWriter<byte> buffer, ushort tid, ReadOnlySpan<byte> edata) {}
+ public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame) {}
}
- public sealed class EchoPropertyInstance {
- public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
- [Obsolete("Use ValueChanged instead.")]
- public event EventHandler<ReadOnlyMemory<byte>>? ValueSet;
-
- public EchoPropertyInstance(EchoProperty spec) {}
- public EchoPropertyInstance(EchoProperty spec, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc) {}
- public EchoPropertyInstance(byte classGroupCode, byte classCode, byte epc, bool isPropertyAnno, bool isPropertySet, bool isPropertyGet) {}
-
- public bool Anno { get; }
- public bool Get { get; }
- public bool Set { get; }
- public EchoProperty Spec { get; }
- public ReadOnlyMemory<byte> ValueMemory { get; }
- public ReadOnlySpan<byte> ValueSpan { get; }
-
- public void SetValue(ReadOnlyMemory<byte> newValue) {}
- public void WriteValue(Action<IBufferWriter<byte>> write) {}
+ public static class PropertyContentSerializer {
+ public static bool TryDeserializeInstanceListNotification(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList) {}
+ public static bool TryDeserializePropertyMap(ReadOnlySpan<byte> content, [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap) {}
+ public static bool TrySerializeInstanceListNotification(IEnumerable<EOJ> instanceList, Span<byte> destination, out int bytesWritten) {}
}
public readonly struct EOJ : IEquatable<EOJ> {
public static bool operator == (EOJ c1, EOJ c2) {}
public static bool operator != (EOJ c1, EOJ c2) {}
public EOJ(byte classGroupCode, byte classCode, byte instanceCode) {}
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte ClassGroupCode { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte InstanceCode { get; }
public bool Equals(EOJ other) {}
- public override bool Equals(object? other) {}
+ public override bool Equals(object? obj) {}
public override int GetHashCode() {}
}
public readonly struct Frame {
- public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEDATA edata) {}
+ public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEData edata) {}
- public IEDATA? EDATA { get; }
+ public IEData? EData { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD1 EHD1 { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public EHD2 EHD2 { get; }
[JsonConverter(typeof(SingleUInt16JsonConverter))]
public ushort TID { get; }
}
public readonly struct PropertyRequest {
public PropertyRequest(byte epc) {}
public PropertyRequest(byte epc, ReadOnlyMemory<byte> edt) {}
[JsonConverter(typeof(ByteSequenceJsonConverter))]
public ReadOnlyMemory<byte> EDT { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte EPC { get; }
[JsonConverter(typeof(SingleByteJsonConverterFactory))]
public byte PDC { get; }
}
}
-namespace EchoDotNetLiteLANBridge {
- [Obsolete("Use UdpEchonetLiteHandler instead.")]
- public class LANClient : UdpEchonetLiteHandler {
- public LANClient(ILogger<LANClient> logger) {}
- }
-
- public class UdpEchonetLiteHandler :
- IDisposable,
- IEchonetLiteHandler
- {
- public event EventHandler<(IPAddress, ReadOnlyMemory<byte>)>? Received;
-
+namespace Smdn.Net.EchonetLite.Transport {
+ public class UdpEchonetLiteHandler : EchonetLiteHandler {
public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger) {}
- public void Dispose() {}
- public async ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken) {}
+ public override IPAddress? LocalAddress { get; }
+ public override ISynchronizeInvoke? SynchronizingObject { get; set; }
+
+ protected override void Dispose(bool disposing) {}
+ protected override ValueTask DisposeAsyncCore() {}
+ protected override async ValueTask<IPAddress> ReceiveAsyncCore(IBufferWriter<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendAsyncCore(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
+ protected override async ValueTask SendToAsyncCore(IPAddress remoteAddress, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) {}
}
}
// 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/src/Smdn.Net.EchonetLite/.editorconfig b/src/Smdn.Net.EchonetLite/.editorconfig
deleted file mode 100644
index 4ba0aac..0000000
--- a/src/Smdn.Net.EchonetLite/.editorconfig
+++ /dev/null
@@ -1,3 +0,0 @@
-[*.cs]
-dotnet_style_namespace_match_folder = true
-dotnet_diagnostic.IDE0130.severity = warning
\ No newline at end of file
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/CollectionChangeType.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/CollectionChangeType.cs
deleted file mode 100644
index 7519fa7..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/CollectionChangeType.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Collections.Specialized;
-
-namespace EchoDotNetLite.Common
-{
- [Obsolete($"Use {nameof(NotifyCollectionChangedEventArgs)} instead.")]
-#pragma warning disable CA1008
- public enum CollectionChangeType
- {
- Add = 1,
- Remove = 2,
- }
-#pragma warning restore CA1008
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/Extentions.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/Extentions.cs
deleted file mode 100644
index e119837..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Common/Extentions.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Models;
-using System;
-using System.Collections.Specialized;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-
-namespace EchoDotNetLite.Common
-{
- public static class Extentions
- {
- public static string GetDebugString(this EchoObjectInstance echoObjectInstance)
- {
- if (echoObjectInstance == null)
- {
- return "null";
- }
- if(echoObjectInstance.Spec == null)
- {
- return "Spec null";
- }
- return $"0x{echoObjectInstance.Spec.ClassGroup.ClassGroupCode:X2}{echoObjectInstance.Spec.ClassGroup.ClassGroupName} 0x{echoObjectInstance.Spec.Class.ClassCode:X2}{echoObjectInstance.Spec.Class.ClassName} {echoObjectInstance.InstanceCode:X2}";
- }
- public static string GetDebugString(this EchoPropertyInstance echoPropertyInstance)
- {
- if (echoPropertyInstance == null)
- {
- return "null";
- }
- if (echoPropertyInstance.Spec == null)
- {
- return "Spec null";
- }
- var sb = new StringBuilder();
- sb.AppendFormat(provider: null, "0x{0:X2}", echoPropertyInstance.Spec.Code);
- sb.Append(echoPropertyInstance.Spec.Name);
- sb.Append(' ');
- sb.Append(echoPropertyInstance.Get ? "Get" : "");
- sb.Append(echoPropertyInstance.Spec.GetRequired ? "(Req)" : "");
- sb.Append(' ');
- sb.Append(echoPropertyInstance.Set ? "Set" : "");
- sb.Append(echoPropertyInstance.Spec.SetRequired ? "(Req)" : "");
- sb.Append(' ');
- sb.Append(echoPropertyInstance.Anno ? "Anno" : "");
- sb.Append(echoPropertyInstance.Spec.AnnoRequired ? "(Req)" : "");
- return sb.ToString();
- }
-
- /// <summary>
- /// <see cref="NotifyCollectionChangedEventArgs"/>から、コレクションに追加されたアイテムの取得を試みます。
- /// </summary>
- /// <typeparam name="TItem">取得を試みるコレクションアイテムの型。</typeparam>
- /// <param name="e">イベント引数<see cref="NotifyCollectionChangedEventArgs"/>。</param>
- /// <param name="addedItem">取得できた場合は、コレクションに追加されたアイテム。</param>
- /// <returns>取得できた場合は<see langword="true"/>、そうでなければ<see langword="false"/>。</returns>
- /// <exception cref="ArgumentNullException">イベント引数を<see langword="null"/>にすることはできません。</exception>
- public static bool TryGetAddedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? addedItem) where TItem : class
- {
- if (e is null)
- throw new ArgumentNullException(nameof(e));
-
- addedItem = default;
-
- if (e.Action != NotifyCollectionChangedAction.Add && e.Action != NotifyCollectionChangedAction.Replace)
- return false;
-
- addedItem = e.NewItems?.OfType<TItem>()?.FirstOrDefault();
-
- return addedItem is not null;
- }
-
- /// <summary>
- /// <see cref="NotifyCollectionChangedEventArgs"/>から、コレクションから削除されたアイテムの取得を試みます。
- /// </summary>
- /// <typeparam name="TItem">取得を試みるコレクションアイテムの型。</typeparam>
- /// <param name="e">イベント引数<see cref="NotifyCollectionChangedEventArgs"/>。</param>
- /// <param name="removedItem">取得できた場合は、コレクションから削除されたアイテム。</param>
- /// <returns>取得できた場合は<see langword="true"/>、そうでなければ<see langword="false"/>。</returns>
- /// <exception cref="ArgumentNullException">イベント引数を<see langword="null"/>にすることはできません。</exception>
- public static bool TryGetRemovedItem<TItem>(this NotifyCollectionChangedEventArgs? e, [NotNullWhen(true)] out TItem? removedItem) where TItem : class
- {
- if (e is null)
- throw new ArgumentNullException(nameof(e));
-
- removedItem = default;
-
- if (e.Action != NotifyCollectionChangedAction.Remove && e.Action != NotifyCollectionChangedAction.Replace)
- return false;
-
- removedItem = e.OldItems?.OfType<TItem>()?.FirstOrDefault();
-
- return removedItem is not null;
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD1.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD1.cs
deleted file mode 100644
index c0e5ef3..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD1.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-namespace EchoDotNetLite.Enums
-{
- public enum EHD1 : byte
- {
- //図 3-2 EHD1 詳細規定
- //プロトコル種別
- //1* * * :従来のECHONET規格
- //0001:ECHONET Lite規格
- ECHONETLite = 0x10,
- //0000:使用不可
- //その他:future reserved
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD2.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD2.cs
deleted file mode 100644
index 2b98a29..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/EHD2.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-namespace EchoDotNetLite.Enums
-{
- public enum EHD2 : byte
- {
- //図 3-3 EHD2 詳細規定
- /// <summary>
- /// 形式1
- /// </summary>
- Type1 = 0x81,
- /// <summary>
- /// 形式2
- /// </summary>
- Type2 = 0x82,
- //その他:future reserved
- //ただし、b7=1固定
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/ESV.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/ESV.cs
deleted file mode 100644
index 3a391f8..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Enums/ESV.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-namespace EchoDotNetLite.Enums
-{
-#pragma warning disable CA1707
- public enum ESV : byte
- {
- //表 3-9 要求用 ESV コード一覧表
- ///<summary>0x60 プロパティ値書き込み要求(応答不要) SetI 一斉同報可</summary>
- SetI = 0x60,
- ///<summary>0x61 プロパティ値書き込み要求(応答要) SetC </summary>
- SetC = 0x61,
- ///<summary>0x62 プロパティ値読み出し要求 Get 一斉同報可</summary>
- Get = 0x62,
- ///<summary>0x63 プロパティ値通知要求 INF_REQ 一斉同報可</summary>
- INF_REQ = 0x63,
- //0x64-0x6D for future reserved,
- ///<summary>0x6E プロパティ値書き込み・読み出し要求 SetGet 一斉同報可</summary>
- SetGet = 0x6E,
- //0x6F for future reserved,
-
- //表 3-10 応答・通知用 ESV コード一覧表
- ///<summary>0x71 プロパティ値書き込み応答 Set_Res ESV=0x61 の応答、個別応答</summary>
- Set_Res = 0x71,
- ///<summary>0x72 プロパティ値読み出し応答 Get_Res ESV=0x62 の応答、個別応答</summary>
- Get_Res = 0x72,
- ///<summary>0x73 プロパティ値通知 INF *1個別通知、一斉同報通知共に可</summary>
- INF = 0x73,
- ///<summary>0x74 プロパティ値通知(応答要) INFC 個別通知</summary>
- INFC = 0x74,
- //0x75-0x79 for future reserved,
- ///<summary>0x7A プロパティ値通知応答 INFC_Res ESV=0x74 の応答、個別応答</summary>
- INFC_Res = 0x7A,
- //0x7B-0x7D for future reserved,
- ///<summary>0x7E プロパティ値書き込み・読み出し応答 SetGet_Res ESV=0x6E の応答、個別応答</summary>
- SetGet_Res = 0x7E,
- //0x7F for future reserved,
-
- //表 3-11 不可応答用 ESV コード一覧表
- ///<summary>0x50 プロパティ値書き込み要求不可応答 SetI_SNA ESV=0x60 の不可応答、個別応答</summary>
- SetI_SNA = 0x50,
- ///<summary>0x51 プロパティ値書き込み要求不可応答 SetC_SNA ESV=0x61 の不可応答、個別応答</summary>
- SetC_SNA = 0x51,
- ///<summary>0x52 プロパティ値読み出し不可応答 Get_SNA ESV=0x62 の不可応答、個別応答</summary>
- Get_SNA = 0x52,
- ///<summary>0x53 プロパティ値通知不可応答 INF_SNA ESV=0x63 の不可応答、個別応答</summary>
- INF_SNA = 0x53,
- //0x54-0x5D for future reserved,
- ///<summary>0x5E プロパティ値書き込み・読み出し不可応答 SetGet_SNA ESV=0x6E の不可応答、個別応答</summary>
- SetGet_SNA = 0x5E,
- //0x5F for future reserved
- }
-#pragma warning restore CA1707
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA1.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA1.cs
deleted file mode 100644
index e1414e8..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA1.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Serialization;
-using System;
-using System.Collections.Generic;
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
-using System.Diagnostics.CodeAnalysis;
-#endif
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Models
-{
-
- /// <summary>
- /// 電文形式 1(規定電文形式)
- /// </summary>
- public sealed class EDATA1 : IEDATA
- {
- /// <summary>
- /// ECHONET Liteフレームの電文形式 1(規定電文形式)の電文を記述する<see cref="EDATA1"/>を作成します。
- /// </summary>
- /// <remarks>
- /// このオーバーロードでは、<see cref="OPCGetList"/>および<see cref="OPCSetList"/>に<see langword="null"/>を設定します。
- /// </remarks>
- /// <param name="seoj"><see cref="SEOJ"/>に指定する値。</param>
- /// <param name="deoj"><see cref="DEOJ"/>に指定する値。</param>
- /// <param name="esv"><see cref="ESV"/>に指定する値。</param>
- /// <param name="opcList"><see cref="OPCList"/>に指定する値。</param>
- /// <exception cref="ArgumentException">
- /// <paramref name="esv"/>が<see cref="ESV.SetGet"/>, <see cref="ESV.SetGet_Res"/>, <see cref="ESV.SetGet_SNA"/>のいずれかです。
- /// この場合、<see cref="OPCSetList"/>および<see cref="OPCGetList"/>を指定する必要があります。
- /// </exception>
- /// <exception cref="ArgumentNullException"><paramref name="opcList"/>が<see langword="null"/>です。</exception>
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList)
- {
- if (FrameSerializer.IsESVWriteOrReadService(esv))
- throw new ArgumentException(message: $"ESV must be other than {nameof(ESV.SetGet)}, {nameof(ESV.SetGet_Res)}, or {nameof(ESV.SetGet_SNA)}.", paramName: nameof(esv));
-
- SEOJ = seoj;
- DEOJ = deoj;
- ESV = esv;
- OPCList = opcList ?? throw new ArgumentNullException(nameof(opcList));
- }
-
- /// <summary>
- /// ECHONET Liteフレームの電文形式 1(規定電文形式)の電文を記述する<see cref="EDATA1"/>を作成します。
- /// </summary>
- /// <remarks>
- /// このオーバーロードでは、<see cref="OPCList"/>に<see langword="null"/>を設定します。
- /// </remarks>
- /// <param name="seoj"><see cref="SEOJ"/>に指定する値。</param>
- /// <param name="deoj"><see cref="DEOJ"/>に指定する値。</param>
- /// <param name="esv"><see cref="ESV"/>に指定する値。</param>
- /// <param name="opcSetList"><see cref="OPCSetList"/>に指定する値。</param>
- /// <param name="opcGetList"><see cref="OPCGetList"/>に指定する値。</param>
- /// <exception cref="ArgumentException">
- /// <paramref name="esv"/>が<see cref="ESV.SetGet"/>, <see cref="ESV.SetGet_Res"/>, <see cref="ESV.SetGet_SNA"/>のいずれかではありません。
- /// この場合、<see cref="OPCList"/>のみを指定する必要があります。
- /// </exception>
- /// <exception cref="ArgumentNullException"><paramref name="opcSetList"/>もしくは<paramref name="opcGetList"/>が<see langword="null"/>です。</exception>
- public EDATA1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList)
- {
- if (!FrameSerializer.IsESVWriteOrReadService(esv))
- throw new ArgumentException(message: $"ESV must be {nameof(ESV.SetGet)}, {nameof(ESV.SetGet_Res)}, or {nameof(ESV.SetGet_SNA)}.", paramName: nameof(esv));
-
- SEOJ = seoj;
- DEOJ = deoj;
- ESV = esv;
- OPCSetList = opcSetList ?? throw new ArgumentNullException(nameof(opcSetList));
- OPCGetList = opcGetList ?? throw new ArgumentNullException(nameof(opcGetList));
- }
-
- /// <summary>
- /// 送信元ECHONET Liteオブジェクト指定(3B)
- /// </summary>
- public EOJ SEOJ { get; }
- /// <summary>
- /// 相手先ECHONET Liteオブジェクト指定(3B)
- /// </summary>
- public EOJ DEOJ { get; }
- /// <summary>
- /// ECHONET Liteサービス(1B)
- /// ECHONET Liteサービスコード
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public ESV ESV { get; }
-
- public IReadOnlyCollection<PropertyRequest>? OPCList { get; }
- /// <summary>
- /// 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- /// のみ使用
- /// </summary>
- public IReadOnlyCollection<PropertyRequest>? OPCGetList { get; }
- /// <summary>
- /// 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- /// のみ使用
- /// </summary>
- public IReadOnlyCollection<PropertyRequest>? OPCSetList { get; }
-
- [JsonIgnore]
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
- [MemberNotNullWhen(false, nameof(OPCList))]
- [MemberNotNullWhen(true, nameof(OPCGetList))]
- [MemberNotNullWhen(true, nameof(OPCSetList))]
-#endif
- public bool IsWriteOrReadService => FrameSerializer.IsESVWriteOrReadService(ESV);
-
- internal IReadOnlyCollection<PropertyRequest> GetOPCList()
- {
- if (IsWriteOrReadService)
- throw new InvalidOperationException($"invalid operation for the ESV of the current instance (ESV={ESV})");
-
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
- return OPCList;
-#else
- return OPCList!;
-#endif
- }
-
- public (IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>) GetOPCSetGetList()
- {
- if (!IsWriteOrReadService)
- throw new InvalidOperationException($"invalid operation for the ESV of the current instance (ESV={ESV})");
-
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
- return (OPCSetList, OPCGetList);
-#else
- return (OPCSetList!, OPCGetList!);
-#endif
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA2.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA2.cs
deleted file mode 100644
index b064ee3..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EDATA2.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-
-namespace EchoDotNetLite.Models
-{
-
- /// <summary>
- /// 電文形式2(任意電文形式)
- /// </summary>
- public sealed class EDATA2 : IEDATA
- {
- /// <summary>
- /// ECHONET Liteフレームの電文形式2(任意電文形式)の電文を記述する<see cref="EDATA2"/>を作成します。
- /// </summary>
- /// <param name="message"><see cref="Message"/>に指定する値。</param>
- public EDATA2(ReadOnlyMemory<byte> message)
- {
- Message = message;
- }
-
- /// <summary>
- /// 任意電文形式の電文を表す<see cref="ReadOnlyMemory{Byte}"/>。
- /// </summary>
- public ReadOnlyMemory<byte> Message { get; }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EOJ.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EOJ.cs
deleted file mode 100644
index b44b1e9..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EOJ.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Serialization;
-using System;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Models
-{
- /// <summary>
- /// ECHONET オブジェクト(EOJ)
- /// </summary>
- public readonly struct EOJ:IEquatable<EOJ>
- {
- /// <summary>
- /// ECHONET オブジェクト(EOJ)を記述する<see cref="EOJ"/>を作成します。
- /// </summary>
- /// <param name="classGroupCode"><see cref="ClassGroupCode"/>に指定する値。</param>
- /// <param name="classCode"><see cref="ClassCode"/>に指定する値。</param>
- /// <param name="instanceCode"><see cref="InstanceCode"/>に指定する値。</param>
- public EOJ(byte classGroupCode, byte classCode, byte instanceCode)
- {
- ClassGroupCode = classGroupCode;
- ClassCode = classCode;
- InstanceCode = instanceCode;
- }
-
- /// <summary>
- /// クラスグループコード
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public byte ClassGroupCode { get; }
- /// <summary>
- /// クラスクラスコード
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public byte ClassCode { get; }
- /// <summary>
- /// インスタンスコード
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public byte InstanceCode { get; }
-
- public bool Equals(EOJ other)
- {
- return ClassGroupCode == other.ClassGroupCode
- && ClassCode == other.ClassCode
- && InstanceCode == other.InstanceCode;
- }
-
- public override bool Equals(object? other)
- {
- if (other is null)
- return false;
- if (other is EOJ)
- return Equals((EOJ)other);
- return false;
- }
-
- public override int GetHashCode()
- {
- return ClassGroupCode.GetHashCode()
- ^ ClassCode.GetHashCode()
- ^ InstanceCode.GetHashCode();
- }
-
- public static bool operator ==(EOJ c1, EOJ c2)
- {
- return c1.ClassGroupCode == c2.ClassGroupCode
- && c1.ClassCode == c2.ClassCode
- && c1.InstanceCode == c2.InstanceCode;
- }
- public static bool operator !=(EOJ c1, EOJ c2)
- {
- return !(c1 == c2);
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoNode.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoNode.cs
deleted file mode 100644
index c516cc2..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoNode.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Specifications;
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Net;
-
-namespace EchoDotNetLite.Models
-{
- /// <summary>
- /// ECHONET Liteノード
- /// </summary>
- public sealed class EchoNode
- {
- public EchoNode(IPAddress address, EchoObjectInstance nodeProfile)
- {
- Address = address ?? throw new ArgumentNullException(nameof(address));
- NodeProfile = nodeProfile ?? throw new ArgumentNullException(nameof(nodeProfile));
-
- var devices = new ObservableCollection<EchoObjectInstance>();
-
- devices.CollectionChanged += (_, e) => OnDevicesChanged(e);
-
- Devices = devices;
- }
-
- private void OnDevicesChanged(NotifyCollectionChangedEventArgs e)
- {
- DevicesChanged?.Invoke(this, e);
-
- // translate event args to raise obsolete OnCollectionChanged event
-#pragma warning disable CS0618
- var handler = OnCollectionChanged;
-
- if (handler is null)
- return;
-
- if (e.TryGetAddedItem<EchoObjectInstance>(out var addedObject))
- handler(this, (CollectionChangeType.Add, addedObject));
-
- if (e.TryGetRemovedItem<EchoObjectInstance>(out var removedObject))
- handler(this, (CollectionChangeType.Remove, removedObject));
-#pragma warning restore CS0618
- }
-
- /// <summary>
- /// 下位スタックのアドレス
- /// </summary>
- public IPAddress Address { get; }
-
- /// <summary>
- /// ノードプロファイルオブジェクト
- /// </summary>
- public EchoObjectInstance NodeProfile { get; }
-
- /// <summary>
- /// 機器オブジェクトのリスト
- /// </summary>
- public ICollection<EchoObjectInstance> Devices { get; }
-
- /// <summary>
- /// 機器オブジェクトのリスト<see cref="Devices"/>に変更があったときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// 現在のノードにECHONET Lite オブジェクトが追加・削除された際にイベントが発生します。
- /// 変更の詳細は、イベント引数<see cref="NotifyCollectionChangedEventArgs"/>を参照してください。
- /// </remarks>
- public event NotifyCollectionChangedEventHandler? DevicesChanged;
-
- /// <summary>
- /// イベント オブジェクトインスタンス増減通知
- /// </summary>
- [Obsolete($"Use {nameof(DevicesChanged)} instead.")]
- public event EventHandler<(CollectionChangeType, EchoObjectInstance)>? OnCollectionChanged;
- }
-
-
- internal static class SpecificationUtil
- {
- public static Specifications.EchoProperty? FindProperty(byte classGroupCode, byte classCode, byte epc)
- {
- var @class = FindClass(classGroupCode, classCode);
- if (@class is not null)
- {
- Specifications.EchoProperty? property;
- property = @class.AnnoProperties.FirstOrDefault(p => p.Code == epc);
- if (property is not null)
- {
- return property;
- }
- property = @class.GetProperties.FirstOrDefault(p => p.Code == epc);
- if (property is not null)
- {
- return property;
- }
- property = @class.SetProperties.FirstOrDefault(p => p.Code == epc);
- if (property is not null)
- {
- return property;
- }
- }
- return null;
- }
-
- internal static IEchonetObject GenerateUnknownClass(byte classGroupCode, byte classCode)
- {
- return new UnknownEchoObject
- (
- classGroup: new EchoClassGroup
- (
- classGroupCode: classGroupCode,
- classGroupName: "Unknown",
- classGroupNameOfficial: "Unknown",
- classList: Array.Empty<EchoClass>(),
- superClass: null
- ),
- @class: new EchoClass
- (
- classCode: classCode,
- className: "Unknown",
- classNameOfficial: "Unknown",
- status: false
- )
- );
- }
- private class UnknownEchoObject : IEchonetObject
- {
- public UnknownEchoObject(EchoClassGroup classGroup, EchoClass @class)
- {
- ClassGroup = classGroup ?? throw new ArgumentNullException(nameof(classGroup));
- Class = @class ?? throw new ArgumentNullException(nameof(@class));
- }
-
- public EchoClassGroup ClassGroup { get; }
- public EchoClass Class { get; }
-
- public IEnumerable<EchoProperty> GetProperties => Enumerable.Empty<EchoProperty>();
-
- public IEnumerable<EchoProperty> SetProperties => Enumerable.Empty<EchoProperty>();
-
- public IEnumerable<EchoProperty> AnnoProperties => Enumerable.Empty<EchoProperty>();
- }
-
- public static Specifications.IEchonetObject? FindClass(byte classGroupCode, byte classCode)
- {
- var profileClass = Specifications.プロファイル.クラス一覧.FirstOrDefault(
- g => g.ClassGroup.ClassGroupCode == classGroupCode
- && g.Class.ClassCode == classCode);
- if (profileClass != null)
- {
- return profileClass;
- }
- var deviceClass = Specifications.機器.クラス一覧.FirstOrDefault(
- g => g.ClassGroup.ClassGroupCode == classGroupCode
- && g.Class.ClassCode == classCode);
- if (deviceClass != null)
- {
- return deviceClass;
- }
- return null;
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoObjectInstance.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoObjectInstance.cs
deleted file mode 100644
index f6fe2cb..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoObjectInstance.cs
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Collections.ObjectModel;
-using System.Linq;
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Specifications;
-
-namespace EchoDotNetLite.Models
-{
-
- /// <summary>
- /// ECHONET Lite オブジェクトインスタンス
- /// </summary>
-#pragma warning disable CA1708
- public sealed class EchoObjectInstance
-#pragma warning restore CA1708
- {
- /// <summary>
- /// デフォルトコンストラクタ
- /// </summary>
- public EchoObjectInstance(EOJ eoj)
- : this
- (
- classObject:
- SpecificationUtil.FindClass(eoj.ClassGroupCode, eoj.ClassCode) ??
- SpecificationUtil.GenerateUnknownClass(eoj.ClassGroupCode, eoj.ClassCode),
- instanceCode: eoj.InstanceCode
- )
- {
- }
-
- /// <summary>
- /// スペック指定のコンストラクタ
- /// プロパティは仕様から取得する
- /// </summary>
- /// <param name="classObject">オブジェクトクラス</param>
- /// <param name="instanceCode"></param>
- public EchoObjectInstance(IEchonetObject classObject,byte instanceCode)
- {
- Spec = classObject ?? throw new ArgumentNullException(nameof(classObject));
- InstanceCode = instanceCode;
-
- properties = new();
-
- foreach (var prop in classObject.GetProperties)
- {
- properties.Add(new EchoPropertyInstance(prop));
- }
- foreach (var prop in classObject.SetProperties)
- {
- properties.Add(new EchoPropertyInstance(prop));
- }
- foreach (var prop in classObject.AnnoProperties)
- {
- properties.Add(new EchoPropertyInstance(prop));
- }
-
- properties.CollectionChanged += (_, e) => OnPropertiesChanged(e);
- }
-
- private void OnPropertiesChanged(NotifyCollectionChangedEventArgs e)
- {
- PropertiesChanged?.Invoke(this, e);
-
-#pragma warning disable CS0618
- // translate event args to raise obsolete OnCollectionChanged event
- var handler = OnCollectionChanged;
-
- if (handler is null)
- return;
-
- if (e.TryGetAddedItem<EchoPropertyInstance>(out var addedProperty))
- handler(this, (CollectionChangeType.Add, addedProperty));
-
- if (e.TryGetRemovedItem<EchoPropertyInstance>(out var removedProperty))
- handler(this, (CollectionChangeType.Remove, removedProperty));
-#pragma warning restore CS0618
- }
-
- internal void AddProperty(EchoPropertyInstance prop)
- => properties.Add(prop);
-
- internal void ResetProperties(IEnumerable<EchoPropertyInstance> props)
- {
- properties.Clear();
-
- foreach (var prop in props)
- {
- properties.Add(prop);
- }
- }
-
- /// <summary>
- /// EOJ
- /// </summary>
- public EOJ EOJ => new
- (
- classGroupCode: Spec.ClassGroup.ClassGroupCode,
- classCode: Spec.Class.ClassCode,
- instanceCode: InstanceCode
- );
-
- /// <summary>
- /// プロパティの一覧<see cref="Properties"/>に変更があったときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)などによって
- /// 現在のオブジェクトにECHONET Lite プロパティが追加・削除された際にイベントが発生します。
- /// 変更の詳細は、イベント引数<see cref="NotifyCollectionChangedEventArgs"/>を参照してください。
- /// </remarks>
- public event NotifyCollectionChangedEventHandler? PropertiesChanged;
-
- /// <summary>
- /// イベント オブジェクトインスタンス増減通知
- /// </summary>
- [Obsolete($"Use {nameof(PropertiesChanged)} instead.")]
- public event EventHandler<(CollectionChangeType, EchoPropertyInstance)>? OnCollectionChanged;
-
- /// <summary>
- /// プロパティマップ取得状態
- /// </summary>
- /// <seealso cref="EchoClient.PropertyMapAcquiring"/>
- /// <seealso cref="EchoClient.PropertyMapAcquired"/>
- public bool HasPropertyMapAcquired { get; internal set; } = false;
-
- [Obsolete($"Use {nameof(HasPropertyMapAcquired)} instead.")]
- public bool IsPropertyMapGet => HasPropertyMapAcquired;
-
- /// <summary>
- /// クラスグループコード、クラスグループ名
- /// ECHONET機器オブジェクト詳細規定がある場合、詳細仕様
- /// </summary>
- public Specifications.IEchonetObject Spec { get; }
- /// <summary>
- /// インスタンスコード
- /// </summary>
- public byte InstanceCode { get; }
-
- /// <summary>
- /// プロパティの一覧
- /// </summary>
- public IReadOnlyCollection<EchoPropertyInstance> Properties => properties;
-
- private readonly ObservableCollection<EchoPropertyInstance> properties;
-
- /// <summary>
- /// GETプロパティの一覧
- /// </summary>
-#pragma warning disable CA1708
- public IEnumerable<EchoPropertyInstance> GetProperties => Properties.Where(static p => p.Spec.Get);
-
- [Obsolete($"Use {nameof(GetProperties)} instead.")]
- public IEnumerable<EchoPropertyInstance> GETProperties => GetProperties;
-#pragma warning restore CA1708
-
- /// <summary>
- /// SETプロパティの一覧
- /// </summary>
-#pragma warning disable CA1708
- public IEnumerable<EchoPropertyInstance> SetProperties => Properties.Where(static p => p.Spec.Set);
-
- [Obsolete($"Use {nameof(SetProperties)} instead.")]
- public IEnumerable<EchoPropertyInstance> SETProperties => SetProperties;
-#pragma warning restore CA1708
-
- /// <summary>
- /// ANNOプロパティの一覧
- /// </summary>
-#pragma warning disable CA1708
- public IEnumerable<EchoPropertyInstance> AnnoProperties => Properties.Where(static p => p.Spec.Anno);
-
- [Obsolete($"Use {nameof(AnnoProperties)} instead.")]
- public IEnumerable<EchoPropertyInstance> ANNOProperties => AnnoProperties;
-#pragma warning restore CA1708
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoPropertyInstance.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoPropertyInstance.cs
deleted file mode 100644
index 194e6a5..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/EchoPropertyInstance.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Buffers;
-
-namespace EchoDotNetLite.Models
-{
-
- /// <summary>
- /// プロパティクラス
- /// </summary>
- public sealed class EchoPropertyInstance
- {
- internal static Specifications.EchoProperty GenerateUnknownProperty(byte epc)
- => new
- (
- code: epc,
- name: "Unknown",
- detail: "Unknown",
- value: null,
- dataType: "Unknown",
- logicalDataType: "Unknown",
- minSize: null,
- maxSize: null,
- get: false,
- getRequired: false,
- set: false,
- setRequired: false,
- anno: false,
- annoRequired: false,
- optionRequired: null,
- description: null,
- unit: null
- );
-
- public EchoPropertyInstance
- (
- byte classGroupCode,
- byte classCode,
- byte epc
- )
- : this
- (
- classGroupCode: classGroupCode,
- classCode: classCode,
- epc: epc,
- isPropertyAnno: false,
- isPropertySet: false,
- isPropertyGet: false
- )
- {
- }
-
- public EchoPropertyInstance
- (
- byte classGroupCode,
- byte classCode,
- byte epc,
- bool isPropertyAnno,
- bool isPropertySet,
- bool isPropertyGet
- )
- : this
- (
- spec:
- SpecificationUtil.FindProperty(classGroupCode, classCode, epc) ??
- GenerateUnknownProperty(epc),
- isPropertyAnno: isPropertyAnno,
- isPropertySet: isPropertySet,
- isPropertyGet: isPropertyGet
- )
- {
- }
-
- public EchoPropertyInstance(Specifications.EchoProperty spec)
- : this
- (
- spec: spec,
- isPropertyAnno: false,
- isPropertySet: false,
- isPropertyGet: false
- )
- {
- }
-
- public EchoPropertyInstance
- (
- Specifications.EchoProperty spec,
- bool isPropertyAnno,
- bool isPropertySet,
- bool isPropertyGet
- )
- {
- Spec = spec ?? throw new ArgumentNullException(nameof(spec));
- Anno = isPropertyAnno;
- Set = isPropertySet;
- Get = isPropertyGet;
- }
-
- /// <summary>
- /// EPC
- /// ECHONET機器オブジェクト詳細規定がある場合、詳細仕様
- /// </summary>
- public Specifications.EchoProperty Spec { get; }
- /// <summary>
- /// プロパティ値の読み出し・通知要求のサービスを処理する。
- /// プロパティ値読み出し要求(0x62)、プロパティ値書き込み・読み出し要求(0x6E)、プロパティ値通知要求(0x63)の要求受付処理を実施する。
- /// </summary>
- public bool Get { get; }
- /// <summary>
- /// プロパティ値の書き込み要求のサービスを処理する。
- /// プロパティ値書き込み要求(応答不要)(0x60)、プロパティ値書き込み要求(応答要)(0x61)、プロパティ値書き込み・読み出し要求(0x6E)の要求受付処理を実施する。
- /// </summary>
- public bool Set { get; }
- /// <summary>
- /// プロパティ値の通知要求のサービスを処理する。
- /// プロパティ値通知要求(0x63)の要求受付処理を実施する。
- /// </summary>
- public bool Anno { get; }
-
- private ArrayBufferWriter<byte>? _value = null;
-
- /// <summary>
- /// プロパティ値を表す<see cref="ReadOnlyMemory{Byte}"/>を取得します。
- /// </summary>
- public ReadOnlyMemory<byte> ValueMemory => _value is null ? ReadOnlyMemory<byte>.Empty : _value.WrittenMemory;
-
- /// <summary>
- /// プロパティ値を表す<see cref="ReadOnlySpan{Byte}"/>を取得します。
- /// </summary>
- public ReadOnlySpan<byte> ValueSpan => _value is null ? ReadOnlySpan<byte>.Empty : _value.WrittenSpan;
-
- /// <summary>
- /// プロパティ値変更イベント
- /// </summary>
- /// <remarks>
- /// このイベントは、プロパティ値の設定が行われる場合に発生します。
- /// このイベントは、設定される値が以前と同じ値の場合でも発生します。
- /// </remarks>
- /// <seealso cref="ValueChanged"/>
- [Obsolete($"Use {nameof(ValueChanged)} instead.")]
- public event EventHandler<ReadOnlyMemory<byte>>? ValueSet;
-
- /// <summary>
- /// プロパティ値に変更があった場合に発生するイベント。
- /// </summary>
- /// <remarks>
- /// このイベントは、プロパティに異なる値が設定された場合にのみ発生します。
- /// プロパティに値が設定される際、その値が以前と同じ値だった場合には発生しません。
- /// </remarks>
- /// <seealso cref="ValueSet"/>
- public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
-
- /// <summary>
- /// プロパティ値を設定します。
- /// </summary>
- /// <remarks>
- /// プロパティ値の設定が行われたあと、イベント<see cref="ValueSet"/>が発生します。
- /// 設定によって値が変更された場合は、イベント<see cref="ValueChanged"/>も発生します。
- /// </remarks>
- /// <param name="newValue">プロパティ値として設定する値を表す<see cref="ReadOnlyMemory{Byte}"/>。</param>
- /// <seealso cref="ValueSet"/>
- /// <seealso cref="ValueChanged"/>
- public void SetValue(ReadOnlyMemory<byte> newValue)
- => WriteValue(writer => writer.Write(newValue.Span), newValueSize: newValue.Length);
-
- /// <summary>
- /// プロパティ値を書き込みます。
- /// </summary>
- /// <remarks>
- /// プロパティ値の設定が行われたあと、イベント<see cref="ValueSet"/>が発生します。
- /// 書き込みによって値が変更された場合は、イベント<see cref="ValueChanged"/>も発生します。
- /// </remarks>
- /// <param name="write">
- /// プロパティ値を書き込むための<see cref="Action{T}"/>デリゲート。
- /// 引数で渡される<see cref="IBufferWriter{Byte}"/>を介してプロパティ値として設定する内容を書き込んでください。
- /// </param>
- /// <exception cref="ArgumentNullException"><paramref name="write"/>が<see langword="null"/>です。</exception>
- /// <seealso cref="ValueSet"/>
- /// <seealso cref="ValueChanged"/>
- public void WriteValue(Action<IBufferWriter<byte>> write)
- => WriteValue(write ?? throw new ArgumentNullException(nameof(write)), newValueSize: 0);
-
- private void WriteValue(Action<IBufferWriter<byte>> write, int newValueSize)
- {
- var valueChangedHandlers = ValueChanged;
- byte[]? oldValue = null;
-
- try
- {
- var oldValueLength = 0;
-
- if (_value is null)
- {
- var initialCapacity = 0 < newValueSize ? newValueSize : 8; // TODO: best initial capacity
-
- _value = new(initialCapacity);
- }
- else
- {
- oldValueLength = _value.WrittenSpan.Length;
-
- oldValue = ArrayPool<byte>.Shared.Rent(oldValueLength);
-
- _value.WrittenSpan.CopyTo(oldValue.AsSpan(0, oldValueLength));
-
-#if SYSTEM_BUFFERS_ARRAYBUFFERWRITER_RESETWRITTENCOUNT
- _value.ResetWrittenCount();
-#else
- _value.Clear();
-#endif
- }
-
- write(_value);
-
- // 変更がなくてもValueSetイベントを起こす
- ValueSet?.Invoke(this, _value.WrittenMemory);
-
- if (valueChangedHandlers is not null)
- {
- // 値が新規に設定される場合、以前の値から変更がある場合はValueChangedイベントを起こす
- if (oldValue is null || !oldValue.AsSpan(0, oldValueLength).SequenceEqual(_value.WrittenSpan))
- {
- var oldValueMemory = oldValue is null ? ReadOnlyMemory<byte>.Empty : oldValue.AsMemory(0, oldValueLength);
- var newValueMemory = _value.WrittenMemory;
-
- valueChangedHandlers.Invoke(this, (oldValueMemory, newValueMemory));
- }
- }
- }
- finally
- {
- if (oldValue is not null)
- ArrayPool<byte>.Shared.Return(oldValue);
- }
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/Frame.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/Frame.cs
deleted file mode 100644
index 651ce1e..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/Frame.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Serialization;
-using System;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Models
-{
- /// <summary>
- /// ECHONET Liteフレーム
- /// </summary>
- public readonly struct Frame
- {
- /// <summary>
- /// ECHONET Liteフレームを記述する<see cref="Frame"/>を作成します。
- /// </summary>
- /// <param name="ehd1"><see cref="EHD1"/>に指定する値。</param>
- /// <param name="ehd2"><see cref="EHD2"/>に指定する値。</param>
- /// <param name="tid"><see cref="TID"/>に指定する値。</param>
- /// <param name="edata"><see cref="EDATA"/>に指定する値。</param>
- /// <exception cref="ArgumentNullException"><paramref name="edata"/>が<see langword="null"/>です。</exception>
- /// <exception cref="ArgumentException">
- /// <paramref name="edata"/>の型が<paramref name="ehd2"/>と矛盾しています。
- /// または<paramref name="ehd2"/>に不正な値が指定されています。
- /// </exception>
- public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEDATA edata)
- {
- if (edata is null)
- throw new ArgumentNullException(nameof(edata));
-
- switch (ehd2)
- {
- case EHD2.Type1:
- if (edata is not EDATA1)
- throw new ArgumentException(message: "type mismatch", paramName: nameof(edata));
- break;
-
- case EHD2.Type2:
- if (edata is not EDATA2)
- throw new ArgumentException(message: "type mismatch", paramName: nameof(edata));
- break;
-
- default:
- throw new ArgumentException(message: "undefined EHD2", paramName: nameof(ehd2));
- }
-
- EHD1 = ehd1;
- EHD2 = ehd2;
- TID = tid;
- EDATA = edata;
- }
-
- /// <summary>
- /// ECHONET Lite電文ヘッダー1(1B)
- /// ECHONETのプロトコル種別を指定する。
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public EHD1 EHD1 { get; }
- /// <summary>
- /// ECHONET Lite電文ヘッダー2(1B)
- /// EDATA部の電文形式を指定する。
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public EHD2 EHD2 { get; }
- /// <summary>
- /// トランザクションID(2B)
- /// </summary>
- [JsonConverter(typeof(SingleUInt16JsonConverter))]
- public ushort TID { get; }
- /// <summary>
- /// ECHONET Liteデータ
- /// ECHONET Lite 通信ミドルウェアにてやり取りされる電文のデータ領域。
- /// </summary>
- public IEDATA? EDATA { get; }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/PropertyRequest.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/PropertyRequest.cs
deleted file mode 100644
index 434a062..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/PropertyRequest.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Serialization;
-using System;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Models
-{
- public readonly struct PropertyRequest
- {
- /// <summary>
- /// <see cref="EPC"/>のみを指定して、<see cref="PropertyRequest"/>を作成します。
- /// </summary>
- /// <remarks>
- /// <see cref="PDC"/>には<c>0</c>、<see cref="EDT"/>には<see langword="null"/>が設定されます。
- /// </remarks>
- /// <param name="epc"><see cref="EPC"/>に指定する値。</param>
- public PropertyRequest(byte epc)
- {
- EPC = epc;
- EDT = ReadOnlyMemory<byte>.Empty;
- }
-
- /// <summary>
- /// <see cref="EPC"/>および<see cref="EDT"/>を指定して、<see cref="PropertyRequest"/>を作成します。
- /// </summary>
- /// <remarks>
- /// <see cref="PDC"/>は、常に<see cref="EDT"/>の長さを返します。
- /// </remarks>
- /// <param name="epc"><see cref="EPC"/>に指定する値。</param>
- /// <param name="edt"><see cref="EDT"/>に指定する値。</param>
- /// <exception cref="ArgumentException"><paramref name="edt"/>の長さが、255を超えています。</exception>
- public PropertyRequest(byte epc, ReadOnlyMemory<byte> edt)
- {
- if (byte.MaxValue < edt.Length)
- throw new ArgumentException(message: "The length of the EDT exceeds the maximum allowed by the specification.", nameof(edt));
-
- EPC = epc;
- EDT = edt;
- }
-
- /// <summary>
- /// ECHONET Liteプロパティ(1B)
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public byte EPC { get; }
- /// <summary>
- /// EDTのバイト数(1B)
- /// </summary>
- [JsonConverter(typeof(SingleByteJsonConverterFactory))]
- public byte PDC => (byte)EDT.Length;
- /// <summary>
- /// プロパティ値データ(PDCで指定)
- /// </summary>
- [JsonConverter(typeof(ByteSequenceJsonConverter))]
- public ReadOnlyMemory<byte> EDT { get; }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/ByteSequenceJsonConverter.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/ByteSequenceJsonConverter.cs
deleted file mode 100644
index 645a417..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/ByteSequenceJsonConverter.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-#pragma warning disable CA1812
-
-using System;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Serialization;
-
-internal sealed class ByteSequenceJsonConverter : JsonConverter<ReadOnlyMemory<byte>>
-{
- public override ReadOnlyMemory<byte> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- => throw new NotSupportedException();
-
- public override void Write(Utf8JsonWriter writer, ReadOnlyMemory<byte> value, JsonSerializerOptions options)
- {
- if (value.Length == 0)
- {
- writer.WriteStringValue(string.Empty);
- return;
- }
-
- var sb = new StringBuilder(capacity: value.Length * 2);
- var span = value.Span;
-
- for (var i = 0; i < span.Length; i++)
- {
- sb.Append(Hexadecimals.ToHexChar(span[i] >> 4));
- sb.Append(Hexadecimals.ToHexChar(span[i] & 0xF));
- }
-
- writer.WriteStringValue(sb.ToString());
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/Hexadecimals.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/Hexadecimals.cs
deleted file mode 100644
index 87c6547..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/Hexadecimals.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-
-namespace EchoDotNetLite.Serialization;
-
-internal static class Hexadecimals
-{
- internal static char ToHexChar(int value)
- => value switch {
- >= 0x0 and <= 0x9 => (char)('0' + value),
- >= 0xA and <= 0xF => (char)('A' + value - 0xA),
- _ => throw new ArgumentOutOfRangeException(message: "invalid hexadecimal number", paramName: nameof(value), actualValue: value),
- };
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverter.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverter.cs
deleted file mode 100644
index aa02331..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverter.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Serialization;
-
-internal sealed class SingleByteJsonConverter<TByte> : JsonConverter<TByte>
-// where TByte : System.Numerics.IUnsignedNumber<byte>
-{
- public override TByte Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- => throw new NotSupportedException();
-
- private static byte GetByte(TByte value)
- => Convert.ToByte(value, provider: null);
-
- public override void Write(Utf8JsonWriter writer, TByte value, JsonSerializerOptions options)
- {
- var by = GetByte(value);
-
- Span<char> hex = stackalloc char[2] {
- Hexadecimals.ToHexChar(by >> 4),
- Hexadecimals.ToHexChar(by & 0xF)
- };
-
- writer.WriteStringValue(hex);
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverterFactory.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverterFactory.cs
deleted file mode 100644
index 678cbdc..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleByteJsonConverterFactory.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-#pragma warning disable CA1812
-
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Reflection;
-
-namespace EchoDotNetLite.Serialization;
-
-internal sealed class SingleByteJsonConverterFactory : JsonConverterFactory
-{
- public override bool CanConvert(Type typeToConvert)
- {
- if (typeToConvert == typeof(byte))
- return true;
-
- if (typeToConvert.IsEnum && Enum.GetUnderlyingType(typeToConvert) == typeof(byte))
- return true;
-
- return false;
- }
-
- public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
- {
- if (typeToConvert == typeof(byte))
- return new SingleByteJsonConverter<byte>();
-
- if (typeToConvert.IsEnum && Enum.GetUnderlyingType(typeToConvert) == typeof(byte))
- {
- return (JsonConverter)Activator.CreateInstance(
- type: typeof(SingleByteJsonConverter<>).MakeGenericType(typeToConvert),
- bindingAttr: BindingFlags.Instance | BindingFlags.Public,
- binder: null,
- args: null,
- culture: null
- )!;
- }
-
- throw new InvalidOperationException($"unexpected type: {typeToConvert}");
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleUInt16JsonConverter.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleUInt16JsonConverter.cs
deleted file mode 100644
index 6642d21..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Serialization/SingleUInt16JsonConverter.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-#pragma warning disable CA1812
-
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace EchoDotNetLite.Serialization;
-
-internal sealed class SingleUInt16JsonConverter : JsonConverter<ushort>
-{
- public override ushort Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- => throw new NotSupportedException();
-
- public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options)
- {
- Span<char> hex = BitConverter.IsLittleEndian
- ? stackalloc char[4] {
- Hexadecimals.ToHexChar((value >> 4) & 0xF),
- Hexadecimals.ToHexChar(value & 0xF),
- Hexadecimals.ToHexChar(value >> 12),
- Hexadecimals.ToHexChar((value >> 8) & 0xF)
- }
- : stackalloc char[4] {
- Hexadecimals.ToHexChar(value >> 12),
- Hexadecimals.ToHexChar((value >> 8) & 0xF),
- Hexadecimals.ToHexChar((value >> 4) & 0xF),
- Hexadecimals.ToHexChar(value & 0xF)
- };
-
- writer.WriteStringValue(hex);
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.Extensions.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.Extensions.cs
deleted file mode 100644
index 341d339..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.Extensions.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Models;
-using System;
-using System.Collections.Generic;
-
-namespace EchoDotNetLite
-{
-#pragma warning disable IDE0040
- partial class EchoClient
-#pragma warning restore IDE0040
- {
- /// <summary>
- /// インスタンスリスト通知の受信による更新を開始するときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// <para>
- /// イベント引数には、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchoNode"/>が設定されます。
- /// </para>
- /// <para>
- /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
- /// <list type="number">
- /// <item><description><see cref="InstanceListUpdating"/></description></item>
- /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
- /// <item><description><see cref="InstanceListUpdated"/></description></item>
- /// </list>
- /// </para>
- /// </remarks>
- /// <seealso cref="InstanceListPropertyMapAcquiring"/>
- /// <seealso cref="InstanceListUpdated"/>
- public event EventHandler<EchoNode>? InstanceListUpdating;
-
- /// <summary>
- /// インスタンスリスト通知を受信した際に、プロパティマップの取得を開始するときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// <para>
- /// イベント引数には、<see cref="ValueTuple{T1,T2}"/>が設定されます。
- /// イベント引数は、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchoNode"/>、
- /// および通知されたインスタンスリストを表す<see cref="IReadOnlyList{EchoObjectInstance}"/>を保持します。
- /// </para>
- /// <para>
- /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
- /// <list type="number">
- /// <item><description><see cref="InstanceListUpdating"/></description></item>
- /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
- /// <item><description><see cref="InstanceListUpdated"/></description></item>
- /// </list>
- /// </para>
- /// </remarks>
- /// <seealso cref="InstanceListUpdating"/>
- /// <seealso cref="InstanceListUpdated"/>
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListPropertyMapAcquiring;
-
- /// <summary>
- /// インスタンスリスト通知の受信による更新が完了したときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// <para>
- /// イベント引数には、<see cref="ValueTuple{EchoNode,T2}"/>が設定されます。
- /// イベント引数は、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchoNode"/>、
- /// および通知されたインスタンスリストを表す<see cref="IReadOnlyList{EchoObjectInstance}"/>を保持します。
- /// </para>
- /// <para>
- /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
- /// <list type="number">
- /// <item><description><see cref="InstanceListUpdating"/></description></item>
- /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
- /// <item><description><see cref="InstanceListUpdated"/></description></item>
- /// </list>
- /// </para>
- /// </remarks>
- /// <seealso cref="InstanceListUpdating"/>
- /// <seealso cref="InstanceListPropertyMapAcquiring"/>
- public event EventHandler<(EchoNode, IReadOnlyList<EchoObjectInstance>)>? InstanceListUpdated;
-
- protected virtual void OnInstanceListUpdating(EchoNode node)
- => InstanceListUpdating?.Invoke(this, node);
-
- protected virtual void OnInstanceListPropertyMapAcquiring(EchoNode node, IReadOnlyList<EchoObjectInstance> instances)
- => InstanceListPropertyMapAcquiring?.Invoke(this, (node, instances));
-
- protected virtual void OnInstanceListUpdated(EchoNode node, IReadOnlyList<EchoObjectInstance> instances)
- => InstanceListUpdated?.Invoke(this, (node, instances));
-
- /// <summary>
- /// プロパティマップの取得を開始するときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// イベント引数には、<see cref="ValueTuple{EchoNode,EchoObjectInstance}"/>が設定されます。
- /// イベント引数は、対象オブジェクトが属するECHONET Lite ノードを表す<see cref="EchoNode"/>、
- /// およびプロパティマップ取得対象のECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>を保持します。
- /// </remarks>
- /// <seealso cref="PropertyMapAcquired"/>
- /// <seealso cref="EchoObjectInstance.HasPropertyMapAcquired"/>
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquiring;
-
- /// <summary>
- /// プロパティマップの取得を完了したときに発生するイベント。
- /// </summary>
- /// <remarks>
- /// イベント引数には、<see cref="ValueTuple{EchoNode,EchoObjectInstance}"/>が設定されます。
- /// イベント引数は、対象オブジェクトが属するECHONET Lite ノードを表す<see cref="EchoNode"/>、
- /// およびプロパティマップ取得対象のECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>を保持します。
- /// </remarks>
- /// <seealso cref="PropertyMapAcquiring"/>
- /// <seealso cref="EchoObjectInstance.HasPropertyMapAcquired"/>
- public event EventHandler<(EchoNode, EchoObjectInstance)>? PropertyMapAcquired;
-
- protected virtual void OnPropertyMapAcquiring(EchoNode node, EchoObjectInstance device)
- => PropertyMapAcquiring?.Invoke(this, (node, device));
-
- protected virtual void OnPropertyMapAcquired(EchoNode node, EchoObjectInstance device)
- => PropertyMapAcquired?.Invoke(this, (node, device));
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.OriginalAPI.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.OriginalAPI.cs
deleted file mode 100644
index 0f1f113..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.OriginalAPI.cs
+++ /dev/null
@@ -1,226 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Models;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace EchoDotNetLite
-{
-#pragma warning disable IDE0040
- partial class EchoClient
-#pragma warning restore IDE0040
- {
- [Obsolete($"Use {nameof(Nodes)} instead.")]
- public ICollection<EchoNode> NodeList => Nodes;
-
- [Obsolete($"Use {nameof(OnNodeJoined)} instead.")]
- public event EventHandler<EchoNode>? OnNodeJoined {
- add => NodeJoined += value;
- remove => NodeJoined -= value;
- }
-
- /// <inheritdoc cref="PerformInstanceListNotificationAsync(CancellationToken)"/>
- [Obsolete($"Use {nameof(PerformInstanceListNotificationAsync)} instead.")]
- public async Task インスタンスリスト通知Async()
- => await PerformInstanceListNotificationAsync().ConfigureAwait(false);
-
- /// <inheritdoc cref="PerformInstanceListNotificationRequestAsync(CancellationToken)"/>
- [Obsolete($"Use {nameof(PerformInstanceListNotificationRequestAsync)} instead.")]
- public async Task インスタンスリスト通知要求Async()
- => await PerformInstanceListNotificationRequestAsync().ConfigureAwait(false);
-
- /// <summary>
- /// 指定された時間でタイムアウトする<see cref="CancellationTokenSource"/>を作成します。
- /// </summary>
- /// <param name="timeoutMilliseconds">
- /// ミリ秒単位でのタイムアウト時間。
- /// 値が<see cref="Timeout.Infinite"/>に等しい場合は、タイムアウトしない<see cref="CancellationTokenSource"/>を返します。
- /// </param>
- /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeoutMilliseconds"/>に負の値を指定することはできません。</exception>
- private static CancellationTokenSource CreateTimeoutCancellationTokenSource(int timeoutMilliseconds)
- {
- if (0 > timeoutMilliseconds)
- throw new ArgumentOutOfRangeException(message: "タイムアウト時間に負の値を指定することはできません。", actualValue: timeoutMilliseconds, paramName: nameof(timeoutMilliseconds));
-
- if (timeoutMilliseconds == Timeout.Infinite)
- return new CancellationTokenSource();
-
- return new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMilliseconds));
- }
-
- /// <inheritdoc cref="PerformPropertyValueWriteRequestAsync(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- /// <param name="timeoutMilliseconds">ミリ秒単位でのタイムアウト時間。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// タイムアウトまでに不可応答(SetI_SNA <c>0x50</c>)がなかった場合は<see langword="true"/>、不可応答による応答があった場合は<see langword="false"/>を返します。
- /// また、書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
-#pragma warning disable CS1573
- [Obsolete($"Use {nameof(PerformPropertyValueWriteRequestAsync)} instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>?)> プロパティ値書き込み要求応答不要(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , int timeoutMilliseconds = 1000)
-#pragma warning disable CS1573
- {
- using var cts = CreateTimeoutCancellationTokenSource(timeoutMilliseconds);
-
- try {
- var processedProperties = await PerformPropertyValueWriteRequestAsync(
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cts.Token
- ).ConfigureAwait(false);
-
- return (false, processedProperties);
- }
- catch (OperationCanceledException ex) when (cts.Token.Equals(ex.CancellationToken)) {
- return (true, null);
- }
- }
-
- /// <inheritdoc cref="PerformPropertyValueWriteRequestResponseRequiredAsync(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- /// <param name="timeoutMilliseconds">ミリ秒単位でのタイムアウト時間。</param>
- [Obsolete($"Use {nameof(PerformPropertyValueWriteRequestResponseRequiredAsync)} instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み応答要(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , int timeoutMilliseconds = 1000)
- {
- using var cts = CreateTimeoutCancellationTokenSource(timeoutMilliseconds);
-
- try {
- return await PerformPropertyValueWriteRequestResponseRequiredAsync(
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cts.Token
- ).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (cts.Token.Equals(ex.CancellationToken)) {
- throw new TimeoutException($"'{nameof(プロパティ値書き込み応答要)}'が指定されたタイムアウト時間を超過しました", ex);
- }
- }
-
- /// <inheritdoc cref="PerformPropertyValueReadRequestAsync(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- /// <param name="timeoutMilliseconds">ミリ秒単位でのタイムアウト時間。</param>
- [Obsolete($"Use {nameof(PerformPropertyValueReadRequestAsync)} instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> プロパティ値読み出し(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , int timeoutMilliseconds = 1000)
- {
- using var cts = CreateTimeoutCancellationTokenSource(timeoutMilliseconds);
-
- try {
- return await PerformPropertyValueReadRequestAsync(
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cts.Token
- ).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (cts.Token.Equals(ex.CancellationToken)) {
- throw new TimeoutException($"'{nameof(プロパティ値読み出し)}'が指定されたタイムアウト時間を超過しました", ex);
- }
- }
-
- /// <inheritdoc cref="PerformPropertyValueWriteReadRequestAsync(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- /// <param name="timeoutMilliseconds">ミリ秒単位でのタイムアウト時間。</param>
- [Obsolete($"Use {nameof(PerformPropertyValueWriteReadRequestAsync)} instead.")]
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> プロパティ値書き込み読み出し(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> propertiesSet
- , IEnumerable<EchoPropertyInstance> propertiesGet
- , int timeoutMilliseconds = 1000)
- {
- using var cts = CreateTimeoutCancellationTokenSource(timeoutMilliseconds);
-
- try {
- return await PerformPropertyValueWriteReadRequestAsync(
- sourceObject,
- destinationNode,
- destinationObject,
- propertiesSet,
- propertiesGet,
- cts.Token
- ).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (cts.Token.Equals(ex.CancellationToken)) {
- throw new TimeoutException($"'{nameof(プロパティ値書き込み読み出し)}'が指定されたタイムアウト時間を超過しました", ex);
- }
- }
-
- /// <inheritdoc cref="PerformPropertyValueNotificationRequestAsync(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- [Obsolete($"Use {nameof(PerformPropertyValueNotificationRequestAsync)} instead.")]
- public async Task プロパティ値通知要求(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties)
- => await PerformPropertyValueNotificationRequestAsync
- (
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cancellationToken: default
- ).ConfigureAwait(false);
-
- /// <inheritdoc cref="自発プロパティ値通知(EchoObjectInstance, EchoNode?, EchoObjectInstance, IEnumerable{EchoPropertyInstance})"/>
- [Obsolete($"Use {nameof(PerformPropertyValueNotificationAsync)} instead.")]
- public async Task 自発プロパティ値通知(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties)
- => await PerformPropertyValueNotificationAsync
- (
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cancellationToken: default
- ).ConfigureAwait(false);
-
- /// <inheritdoc cref="PerformPropertyValueNotificationResponseRequiredAsync(EchoObjectInstance, EchoNode, EchoObjectInstance, IEnumerable{EchoPropertyInstance}, CancellationToken)"/>
- /// <param name="timeoutMilliseconds">ミリ秒単位でのタイムアウト時間。</param>
- [Obsolete($"Use {nameof(PerformPropertyValueNotificationResponseRequiredAsync)} instead.")]
- public async Task<IReadOnlyCollection<PropertyRequest>> プロパティ値通知応答要(
- EchoObjectInstance sourceObject
- , EchoNode destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , int timeoutMilliseconds = 1000)
- {
- using var cts = CreateTimeoutCancellationTokenSource(timeoutMilliseconds);
-
- try {
- return await PerformPropertyValueNotificationResponseRequiredAsync(
- sourceObject,
- destinationNode,
- destinationObject,
- properties,
- cts.Token
- ).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (cts.Token.Equals(ex.CancellationToken)) {
- throw new TimeoutException($"'{nameof(プロパティ値通知応答要)}'が指定されたタイムアウト時間を超過しました", ex);
- }
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.cs
deleted file mode 100644
index c37ff51..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/Client.cs
+++ /dev/null
@@ -1,1914 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Common;
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Models;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace EchoDotNetLite
-{
- public partial class EchoClient : IDisposable, IAsyncDisposable
- {
- private readonly bool _shouldDisposeEchonetLiteHandler;
- private IEchonetLiteHandler _echonetLiteHandler; // null if disposed
- private readonly ILogger? _logger;
-
- /// <summary>
- /// 送信するECHONET Lite フレームを書き込むバッファ。
- /// <see cref="_echonetLiteHandler"/>によって送信する内容を書き込むために使用する。
- /// </summary>
- private readonly ArrayBufferWriter<byte> _requestFrameBuffer = new(initialCapacity: 0x100);
-
- /// <summary>
- /// ECHONET Lite フレームのリクエスト送信時の排他区間を定義するセマフォ。
- /// <see cref="_requestFrameBuffer"/>への書き込み、および<see cref="_echonetLiteHandler"/>による送信を排他制御するために使用する。
- /// </summary>
- private readonly SemaphoreSlim _requestSemaphore = new(initialCount: 1, maxCount: 1);
-
- /// <summary>
- /// <see cref="IEchonetLiteHandler.Received"/>イベントにてECHONET Lite フレームを受信した場合に発生するイベント。
- /// ECHONET Lite ノードに対して送信されてくる要求を処理するほか、他ノードに対する要求への応答を待機する場合にも使用する。
- /// </summary>
- private event EventHandler<(IPAddress, Frame)>? FrameReceived;
-
- private ushort tid;
-
- /// <inheritdoc cref="EchoClient(IPAddress, IEchonetLiteHandler, bool, ILogger{EchoClient})"/>
- public EchoClient
- (
- IPAddress nodeAddress,
- IEchonetLiteHandler echonetLiteHandler,
- ILogger<EchoClient>? logger = null
- )
- : this
- (
- nodeAddress: nodeAddress,
- echonetLiteHandler: echonetLiteHandler,
- shouldDisposeEchonetLiteHandler: false,
- logger: logger
- )
- {
- }
-
- /// <summary>
- /// <see cref="EchoClient"/>クラスのインスタンスを初期化します。
- /// </summary>
- /// <param name="nodeAddress">自ノードのアドレスを表す<see cref="IPAddress"/>。</param>
- /// <param name="echonetLiteHandler">このインスタンスがECHONET Lite フレームを送受信するために使用する<see cref="IEchonetLiteHandler"/>。</param>
- /// <param name="shouldDisposeEchonetLiteHandler">オブジェクトが破棄される際に、<paramref name="echonetLiteHandler"/>も破棄するかどうかを表す値。</param>
- /// <param name="logger">このインスタンスの動作を記録する<see cref="ILogger{EchoClient}"/>。</param>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="nodeAddress"/>が<see langword="null"/>です。
- /// あるいは、<paramref name="echonetLiteHandler"/>が<see langword="null"/>です。
- /// </exception>
- public EchoClient
- (
- IPAddress nodeAddress,
- IEchonetLiteHandler echonetLiteHandler,
- bool shouldDisposeEchonetLiteHandler,
- ILogger<EchoClient>? logger
- )
- {
- _logger = logger;
- _shouldDisposeEchonetLiteHandler = shouldDisposeEchonetLiteHandler;
- _echonetLiteHandler = echonetLiteHandler ?? throw new ArgumentNullException(nameof(echonetLiteHandler));
- _echonetLiteHandler.Received += EchonetDataReceived;
- SelfNode = new EchoNode
- (
- address: nodeAddress ?? throw new ArgumentNullException(nameof(nodeAddress)),
- nodeProfile: new EchoObjectInstance(Specifications.プロファイル.ノードプロファイル, 0x01)
- );
- Nodes = new List<EchoNode>();
- //自己消費用
- FrameReceived += HandleFrameReceived;
- }
-
- /// <summary>
- /// 現在の<see cref="EchoClient"/>インスタンスが扱う自ノードを表す<see cref="SelfNode"/>。
- /// </summary>
- public EchoNode SelfNode { get; }
-
- /// <summary>
- /// 既知のECHONET Lite ノードのコレクションを表す<see cref="ICollection{EchoNode}"/>。
- /// </summary>
- public ICollection<EchoNode> Nodes { get; }
-
- /// <summary>
- /// 新しいECHONET Lite ノードが発見されたときに発生するイベント。
- /// </summary>
- public event EventHandler<EchoNode>? NodeJoined;
-
- /// <summary>
- /// 現在の<see cref="EchoClient"/>インスタンスによって使用されているリソースを解放して、インスタンスを破棄します。
- /// </summary>
- public void Dispose()
- {
- Dispose(disposing: true);
-
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// 現在の<see cref="EchoClient"/>インスタンスによって使用されているリソースを非同期に解放して、インスタンスを破棄します。
- /// </summary>
- /// <returns>非同期の破棄操作を表す<see cref="ValueTask"/>。</returns>
- public async ValueTask DisposeAsync()
- {
- await DisposeAsyncCore().ConfigureAwait(false);
-
- Dispose(disposing: false);
-
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// 現在の<see cref="EchoClient"/>インスタンスが使用しているアンマネージド リソースを解放します。 オプションで、マネージド リソースも解放します。
- /// </summary>
- /// <param name="disposing">
- /// マネージド リソースとアンマネージド リソースの両方を解放する場合は<see langword="true"/>。
- /// アンマネージド リソースだけを解放する場合は<see langword="false"/>。
- /// </param>
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- FrameReceived = null; // unsubscribe
-
- if (_echonetLiteHandler is not null)
- {
- _echonetLiteHandler.Received -= EchonetDataReceived;
-
- if (_shouldDisposeEchonetLiteHandler && _echonetLiteHandler is IDisposable disposableEchonetLiteHandler)
- disposableEchonetLiteHandler.Dispose();
-
- _echonetLiteHandler = null!;
- }
- }
- }
-
- /// <summary>
- /// 管理対象リソースの非同期の解放、リリース、またはリセットに関連付けられているアプリケーション定義のタスクを実行します。
- /// </summary>
- /// <returns>非同期の破棄操作を表す<see cref="ValueTask"/>。</returns>
- protected virtual async ValueTask DisposeAsyncCore()
- {
- FrameReceived = null; // unsubscribe
-
- if (_echonetLiteHandler is not null)
- {
- _echonetLiteHandler.Received -= EchonetDataReceived;
-
- if (_shouldDisposeEchonetLiteHandler && _echonetLiteHandler is IAsyncDisposable disposableEchonetLiteHandler)
- await disposableEchonetLiteHandler.DisposeAsync().ConfigureAwait(false);
-
- _echonetLiteHandler = null!;
- }
- }
-
- /// <summary>
- /// 現在の<see cref="EchoClient"/>インスタンスが破棄されている場合に、<see cref="ObjectDisposedException"/>をスローします。
- /// </summary>
- /// <exception cref="ObjectDisposedException">現在のインスタンスはすでに破棄されています。</exception>
- protected void ThrowIfDisposed()
- {
- if (_echonetLiteHandler is null)
- throw new ObjectDisposedException(GetType().FullName);
- }
-
- /// <summary>
- /// ECHONET Lite フレームの新しいトランザクションID(TID)を生成して取得します。
- /// </summary>
- /// <returns>新しいトランザクションID。</returns>
- private ushort GetNewTid()
- {
- return ++tid;
- }
-
- /// <summary>
- /// インスタンスリスト通知を行います。
- /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)を設定し、ECHONET Lite サービス「INF:プロパティ値通知」(ESV <c>0x73</c>)を送信します。
- /// </summary>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.3.1 ECHONET Lite ノードスタート時の基本シーケンス
- /// </seealso>
- public async ValueTask PerformInstanceListNotificationAsync(
- CancellationToken cancellationToken = default
- )
- {
- //インスタンスリスト通知プロパティ
- var property = SelfNode.NodeProfile.AnnoProperties.First(p => p.Spec.Code == 0xD5);
-
- property.WriteValue(writer => {
- var contents = writer.GetSpan(253); // インスタンスリスト通知 0xD5 unsigned char×(MAX)253
-
- _ = PropertyContentSerializer.TrySerializeInstanceListNotification
- (
- SelfNode.Devices.Select(static o => o.EOJ),
- contents,
- out var bytesWritten
- );
-
- writer.Advance(bytesWritten);
- });
-
- //インスタンスリスト通知
- await PerformPropertyValueNotificationAsync(
- SelfNode.NodeProfile//ノードプロファイルから
- , null//一斉通知
- , new EchoObjectInstance(new EOJ(
- classGroupCode: Specifications.プロファイル.ノードプロファイル.ClassGroup.ClassGroupCode,
- classCode: Specifications.プロファイル.ノードプロファイル.Class.ClassCode,
- instanceCode: 0x01
- ))
- , Enumerable.Repeat(property, 1)
- , cancellationToken
- ).ConfigureAwait(false);
- }
-
- /// <summary>
- /// インスタンスリスト通知要求を行います。
- /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)に対するECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を送信します。
- /// </summary>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.1 サービス内容に関する基本シーケンス (C)通知要求受信時の基本シーケンス
- /// </seealso>
- public async ValueTask PerformInstanceListNotificationRequestAsync(
- CancellationToken cancellationToken = default
- )
- {
- var properties = Enumerable.Repeat
- (
- new EchoPropertyInstance
- (
- Specifications.プロファイル.ノードプロファイル.ClassGroup.ClassGroupCode,
- Specifications.プロファイル.ノードプロファイル.Class.ClassCode,
- 0xD5//インスタンスリスト通知
- ),
- 1
- );
-
- await PerformPropertyValueNotificationRequestAsync(
- SelfNode.NodeProfile//ノードプロファイルから
- , null//一斉通知
- , new EchoObjectInstance(new EOJ(
- classGroupCode: Specifications.プロファイル.ノードプロファイル.ClassGroup.ClassGroupCode,
- classCode: Specifications.プロファイル.ノードプロファイル.Class.ClassCode,
- instanceCode: 0x01
- ))
- , properties
- , cancellationToken
- ).ConfigureAwait(false);
- }
-
- /// <summary>
- /// ECHONET Lite サービス「SetI:プロパティ値書き込み要求(応答不要)」(ESV <c>0x60</c>)を行います。 このサービスは一斉同報が可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// 書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.1 プロパティ値書き込みサービス(応答不要)[0x60, 0x50]
- /// </seealso>
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- var responseTCS = new TaskCompletionSource<IReadOnlyCollection<PropertyRequest>>();
-
- void HandleFrameSetISNA(object? _, (IPAddress address, Frame response) value)
- {
- try
- {
- if (cancellationToken.IsCancellationRequested)
- {
- _ = responseTCS.TrySetCanceled(cancellationToken);
- return;
- }
-
- if (destinationNode is not null && !destinationNode.Address.Equals(value.address))
- return;
- if (value.response.EDATA is not EDATA1 edata)
- return;
- if (edata.SEOJ != destinationObject.EOJ)
- return;
- if (edata.ESV != ESV.SetI_SNA)
- return;
-
- var opcList = edata.GetOPCList();
-
- foreach (var prop in opcList)
- {
- //一部成功した書き込みを反映
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
- if (prop.PDC == 0x00)
- {
- //書き込み成功
- target.SetValue(properties.First(p => p.Spec.Code == prop.EPC).ValueMemory);
- }
- }
-
- responseTCS.SetResult(opcList);
-
- //TODO 一斉通知の不可応答の扱いが…
- }
- finally
- {
- FrameReceived -= HandleFrameSetISNA;
- }
- };
-
- FrameReceived += HandleFrameSetISNA;
-
- await SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.SetI,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
- ),
- cancellationToken
- ).ConfigureAwait(false);
-
- try {
- using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
-
- return await responseTCS.Task.ConfigureAwait(false);
- }
- catch (Exception ex) {
- if (ex is OperationCanceledException exOperationCanceled && cancellationToken.Equals(exOperationCanceled.CancellationToken))
- {
- foreach (var prop in properties)
- {
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.Spec.Code);
- //成功した書き込みを反映(全部OK)
- target.SetValue(prop.ValueMemory);
- }
- }
-
- FrameReceived -= HandleFrameSetISNA;
-
- throw;
- }
- }
-
- /// <summary>
- /// ECHONET Lite サービス「SetC:プロパティ値書き込み要求(応答要)」(ESV <c>0x61</c>)を行います。 このサービスは一斉同報が可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// 成功応答(Set_Res <c>0x71</c>)の場合は<see langword="true"/>、不可応答(SetC_SNA <c>0x51</c>)その他の場合は<see langword="false"/>を返します。
- /// また、書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.2 プロパティ値書き込みサービス(応答要)[0x61,0x71,0x51]
- /// </seealso>
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteRequestResponseRequiredAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>)>();
-
- void HandleFrameSetResOrSetCSNA(object? _, (IPAddress address, Frame response) value)
- {
- try
- {
- if (cancellationToken.IsCancellationRequested)
- {
- _ = responseTCS.TrySetCanceled(cancellationToken);
- return;
- }
-
- if (destinationNode is not null && !destinationNode.Address.Equals(value.address))
- return;
- if (value.response.EDATA is not EDATA1 edata)
- return;
- if (edata.SEOJ != destinationObject.EOJ)
- return;
- if (edata.ESV != ESV.SetC_SNA && edata.ESV != ESV.Set_Res)
- return;
-
- var opcList = edata.GetOPCList();
-
- foreach (var prop in opcList)
- {
- //成功した書き込みを反映
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
- if(prop.PDC == 0x00)
- {
- //書き込み成功
- target.SetValue(properties.First(p => p.Spec.Code == prop.EPC).ValueMemory);
- }
- }
- responseTCS.SetResult((edata.ESV == ESV.Set_Res, opcList));
-
- //TODO 一斉通知の応答の扱いが…
- }
- finally
- {
- FrameReceived -= HandleFrameSetResOrSetCSNA;
- }
- };
-
- FrameReceived += HandleFrameSetResOrSetCSNA;
-
- await SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.SetC,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
- ),
- cancellationToken
- ).ConfigureAwait(false);
-
- try {
- using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
-
- return await responseTCS.Task.ConfigureAwait(false);
- }
- catch {
- FrameReceived -= HandleFrameSetResOrSetCSNA;
-
- throw;
- }
- }
-
- /// <summary>
- /// ECHONET Lite サービス「Get:プロパティ値読み出し要求」(ESV <c>0x62</c>)を行います。 このサービスは一斉同報が可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// 成功応答(Get_Res <c>0x72</c>)の場合は<see langword="true"/>、不可応答(Get_SNA <c>0x52</c>)その他の場合は<see langword="false"/>を返します。
- /// また、書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.3 プロパティ値読み出しサービス[0x62,0x72,0x52]
- /// </seealso>
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueReadRequestAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>)>();
-
- void HandleFrameGetResOrGetSNA(object? _, (IPAddress address, Frame response) value)
- {
- try
- {
- if (cancellationToken.IsCancellationRequested)
- {
- _ = responseTCS.TrySetCanceled(cancellationToken);
- return;
- }
-
- if (destinationNode is not null && !destinationNode.Address.Equals(value.address))
- return;
- if (value.response.EDATA is not EDATA1 edata)
- return;
- if (edata.SEOJ != destinationObject.EOJ)
- return;
- if (edata.ESV != ESV.Get_Res && edata.ESV != ESV.Get_SNA)
- return;
-
- var opcList = edata.GetOPCList();
-
- foreach (var prop in opcList)
- {
- //成功した読み込みを反映
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
- if (prop.PDC != 0x00)
- {
- //読み込み成功
- target.SetValue(prop.EDT);
- }
- }
- responseTCS.SetResult((edata.ESV == ESV.Get_Res, opcList));
-
- //TODO 一斉通知の応答の扱いが…
- }
- finally
- {
- FrameReceived -= HandleFrameGetResOrGetSNA;
- }
- };
-
- FrameReceived += HandleFrameGetResOrGetSNA;
-
- await SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.Get,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequestExceptValueData)
- ),
- cancellationToken
- ).ConfigureAwait(false);
-
- try {
- using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
-
- return await responseTCS.Task.ConfigureAwait(false);
- }
- catch {
- FrameReceived -= HandleFrameGetResOrGetSNA;
-
- throw;
- }
- }
-
- /// <summary>
- /// ECHONET Lite サービス「SetGet:プロパティ値書き込み・読み出し要求」(ESV <c>0x6E</c>)を行います。 このサービスは一斉同報が可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="propertiesSet">書き込み対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="propertiesGet">読み出し対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// 成功応答(SetGet_Res <c>0x7E</c>)の場合は<see langword="true"/>、不可応答(SetGet_SNA <c>0x5E</c>)その他の場合は<see langword="false"/>を返します。
- /// また、処理に成功したプロパティを書き込み対象プロパティ・読み出し対象プロパティの順にて<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="propertiesSet"/>が<see langword="null"/>です。
- /// または、<paramref name="propertiesGet"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- /// </seealso>
- public async Task<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)> PerformPropertyValueWriteReadRequestAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> propertiesSet
- , IEnumerable<EchoPropertyInstance> propertiesGet
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (propertiesSet is null)
- throw new ArgumentNullException(nameof(propertiesSet));
- if (propertiesGet is null)
- throw new ArgumentNullException(nameof(propertiesGet));
-
- var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)>();
-
- void HandleFrameSetGetResOrSetGetSNA(object? _, (IPAddress address, Frame response) value)
- {
- try
- {
- if (cancellationToken.IsCancellationRequested)
- {
- _ = responseTCS.TrySetCanceled(cancellationToken);
- return;
- }
-
- if (destinationNode is not null && !destinationNode.Address.Equals(value.address))
- return;
- if (value.response.EDATA is not EDATA1 edata)
- return;
- if (edata.SEOJ != destinationObject.EOJ)
- return;
- if (edata.ESV != ESV.SetGet_Res && edata.ESV != ESV.SetGet_SNA)
- return;
-
- var (opcSetList, opcGetList) = edata.GetOPCSetGetList();
-
- foreach (var prop in opcSetList)
- {
- //成功した書き込みを反映
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
- if (prop.PDC == 0x00)
- {
- //書き込み成功
- target.SetValue(propertiesSet.First(p => p.Spec.Code == prop.EPC).ValueMemory);
- }
- }
- foreach (var prop in opcGetList)
- {
- //成功した読み込みを反映
- var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
- if (prop.PDC != 0x00)
- {
- //読み込み成功
- target.SetValue(prop.EDT);
- }
- }
- responseTCS.SetResult((edata.ESV == ESV.SetGet_Res, opcSetList, opcGetList));
-
- //TODO 一斉通知の応答の扱いが…
- }
- finally
- {
- FrameReceived -= HandleFrameSetGetResOrSetGetSNA;
- }
- };
-
- FrameReceived += HandleFrameSetGetResOrSetGetSNA;
-
- await SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.SetGet,
- opcListOrOpcSetList: propertiesSet.Select(ConvertToPropertyRequest),
- opcGetList: propertiesGet.Select(ConvertToPropertyRequestExceptValueData)
- ),
- cancellationToken
- ).ConfigureAwait(false);
-
- try {
- using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
-
- return await responseTCS.Task.ConfigureAwait(false);
- }
- catch {
- FrameReceived -= HandleFrameSetGetResOrSetGetSNA;
-
- throw;
- }
- }
-
-
- /// <summary>
- /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を行います。 このサービスは一斉同報が可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="ValueTask"/>。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
- /// </seealso>
- public ValueTask PerformPropertyValueNotificationRequestAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- return SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.INF_REQ,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequestExceptValueData)
- ),
- cancellationToken
- );
- }
-
-
- /// <summary>
- /// ECHONET Lite サービス「INF:プロパティ値通知」(ESV <c>0x73</c>)を行います。 このサービスは個別通知・一斉同報通知ともに可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="ValueTask"/>。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
- /// </seealso>
- public ValueTask PerformPropertyValueNotificationAsync(
- EchoObjectInstance sourceObject
- , EchoNode? destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- return SendFrameAsync
- (
- destinationNode?.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.INF,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
- ),
- cancellationToken
- );
- }
-
- /// <summary>
- /// ECHONET Lite サービス「INFC:プロパティ値通知(応答要)」(ESV <c>0x74</c>)を行います。 このサービスは個別通知のみ可能です。
- /// </summary>
- /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchoNode"/>。</param>
- /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchoPropertyInstance}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
- /// <returns>
- /// 非同期の操作を表す<see cref="Task{T}"/>。
- /// 通知に成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
- /// </returns>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="sourceObject"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationNode"/>が<see langword="null"/>です。
- /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
- /// または、<paramref name="properties"/>が<see langword="null"/>です。
- /// </exception>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.6 プロパティ値通知(応答要)サービス[0x74, 0x7A]
- /// </seealso>
- public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(
- EchoObjectInstance sourceObject
- , EchoNode destinationNode
- , EchoObjectInstance destinationObject
- , IEnumerable<EchoPropertyInstance> properties
- , CancellationToken cancellationToken = default)
- {
- if (sourceObject is null)
- throw new ArgumentNullException(nameof(sourceObject));
- if (destinationNode is null)
- throw new ArgumentNullException(nameof(destinationNode));
- if (destinationObject is null)
- throw new ArgumentNullException(nameof(destinationObject));
- if (properties is null)
- throw new ArgumentNullException(nameof(properties));
-
- var responseTCS = new TaskCompletionSource<IReadOnlyCollection<PropertyRequest>>();
-
- void HandleFrameINFCRes(object? _, (IPAddress address, Frame response) value)
- {
- try
- {
- if (cancellationToken.IsCancellationRequested)
- {
- _ = responseTCS.TrySetCanceled(cancellationToken);
- return;
- }
-
- if (!destinationNode.Address.Equals(value.address))
- return;
- if (value.response.EDATA is not EDATA1 edata)
- return;
- if (edata.SEOJ != destinationObject.EOJ)
- return;
- if (edata.ESV != ESV.INFC_Res)
- return;
-
- responseTCS.SetResult(edata.GetOPCList());
- }
- finally
- {
- FrameReceived -= HandleFrameINFCRes;
- }
- };
-
- FrameReceived += HandleFrameINFCRes;
-
- await SendFrameAsync
- (
- destinationNode.Address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: GetNewTid(),
- sourceObject: sourceObject.EOJ,
- destinationObject: destinationObject.EOJ,
- esv: ESV.INFC,
- opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
- ),
- cancellationToken
- ).ConfigureAwait(false);
-
- try {
- using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
-
- return await responseTCS.Task.ConfigureAwait(false);
- }
- catch {
- FrameReceived -= HandleFrameINFCRes;
-
- throw;
- }
- }
-
- private static PropertyRequest ConvertToPropertyRequest(EchoPropertyInstance p)
- => new(epc: p.Spec.Code, edt: p.ValueMemory);
-
- private static PropertyRequest ConvertToPropertyRequestExceptValueData(EchoPropertyInstance p)
- => new(epc: p.Spec.Code);
-
- /// <summary>
- /// イベント<see cref="IEchonetLiteHandler.Received"/>をハンドルするメソッドを実装します。
- /// </summary>
- /// <remarks>
- /// 受信したデータがECHONET Lite フレームの場合は、イベント<see cref="FrameReceived"/>をトリガします。
- /// それ以外の場合は、無視して処理を中断します。
- /// </remarks>
- /// <param name="sender">イベントのソース。</param>
- /// <param name="value">
- /// イベントデータを格納している<see cref="ValueTuple{T1,T2}"/>。
- /// データの送信元を表す<see cref="IPAddress"/>と、受信したデータを表す<see cref="ReadOnlyMemory{Byte}"/>を保持します。
- /// </param>
- private void EchonetDataReceived(object? sender, (IPAddress address, ReadOnlyMemory<byte> data) value)
- {
- if (!FrameSerializer.TryDeserialize(value.data.Span, out var frame))
- // ECHONETLiteフレームではないため無視
- return;
-
- _logger?.LogTrace($"Echonet Lite Frame受信: address:{value.address}\r\n,{JsonSerializer.Serialize(frame)}");
-
- FrameReceived?.Invoke(this, (value.address, frame));
- }
-
- /// <summary>
- /// ECHONET Lite フレームを送信します。
- /// </summary>
- /// <param name="address">送信先となるECHONET Lite ノードの<see cref="IPAddress"/>。 <see langword="null"/>の場合は、サブネット内のすべてのノードに対して一斉同報送信を行います。</param>
- /// <param name="writeFrame">
- /// 送信するECHONET Lite フレームをバッファへ書き込むための<see cref="Action{T}"/>デリゲート。
- /// 呼び出し元は、送信するECHONET Lite フレームを、引数として渡される<see cref="IBufferWriter{Byte}"/>に書き込む必要があります。
- /// </param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。</param>
- /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
- /// <exception cref="ObjectDisposedException">オブジェクトはすでに破棄されています。</exception>
- private async ValueTask SendFrameAsync(IPAddress? address, Action<IBufferWriter<byte>> writeFrame, CancellationToken cancellationToken)
- {
- ThrowIfDisposed();
-
- await _requestSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- writeFrame(_requestFrameBuffer);
-
- if (_logger is not null && _logger.IsEnabled(LogLevel.Trace))
- {
- if (FrameSerializer.TryDeserialize(_requestFrameBuffer.WrittenSpan, out var frame))
- {
- _logger.LogTrace($"Echonet Lite Frame送信: address:{address}\r\n,{JsonSerializer.Serialize(frame)}");
- }
-#if DEBUG
- else
- {
- throw new InvalidOperationException("attempted to request an invalid format of frame");
- }
-#endif
- }
-
- await _echonetLiteHandler.SendAsync(address, _requestFrameBuffer.WrittenMemory, cancellationToken).ConfigureAwait(false);
- }
- finally {
- // reset written count to reuse the buffer for the next write
-#if SYSTEM_BUFFERS_ARRAYBUFFERWRITER_RESETWRITTENCOUNT
- _requestFrameBuffer.ResetWrittenCount();
-#else
- _requestFrameBuffer.Clear();
-#endif
- _requestSemaphore.Release();
- }
- }
-
- /// <summary>
- /// インスタンスリスト通知受信時の処理を行います。
- /// </summary>
- /// <param name="sourceNode">送信元のECHONET Lite ノードを表す<see cref="EchoNode"/>。</param>
- /// <param name="edt">受信したインスタンスリスト通知を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
- /// <seealso cref="PerformInstanceListNotificationAsync"/>
- /// <seealso cref="AcquirePropertyMapsAsync"/>
- private async ValueTask HandleInstanceListNotificationReceivedAsync(EchoNode sourceNode, ReadOnlyMemory<byte> edt)
- {
- _logger?.LogTrace("インスタンスリスト通知を受信しました");
-
- if (!PropertyContentSerializer.TryDeserializeInstanceListNotification(edt.Span, out var instanceList))
- return; // XXX
-
- OnInstanceListUpdating(sourceNode);
-
- var instances = new List<EchoObjectInstance>(capacity: instanceList.Count);
-
- foreach (var eoj in instanceList)
- {
- var device = sourceNode.Devices.FirstOrDefault(d => d.EOJ == eoj);
- if (device == null)
- {
- device = new EchoObjectInstance(eoj);
- sourceNode.Devices.Add(device);
- }
-
- instances.Add(device);
- }
-
- OnInstanceListPropertyMapAcquiring(sourceNode, instances);
-
- foreach (var device in instances)
- {
- if (!device.HasPropertyMapAcquired)
- {
- _logger?.LogTrace($"{device.GetDebugString()} プロパティマップを読み取ります");
- await AcquirePropertyMapsAsync(sourceNode, device).ConfigureAwait(false);
- }
- }
-
- if (!sourceNode.NodeProfile.HasPropertyMapAcquired)
- {
- _logger?.LogTrace($"{sourceNode.NodeProfile.GetDebugString()} プロパティマップを読み取ります");
- await AcquirePropertyMapsAsync(sourceNode, sourceNode.NodeProfile).ConfigureAwait(false);
- }
-
- OnInstanceListUpdated(sourceNode, instances);
- }
-
- private class PropertyCapability
- {
- public bool Anno { get; set; }
- public bool Set { get; set; }
- public bool Get { get; set; }
- }
-
- /// <summary>
- /// 指定されたECHONET Lite オブジェクトに対して、ECHONETプロパティ「状変アナウンスプロパティマップ」(EPC <c>0x9D</c>)・
- /// 「Set プロパティマップ」(EPC <c>0x9E</c>)・「Get プロパティマップ」(EPC <c>0x9F</c>)の読み出しを行います。
- /// </summary>
- /// <param name="sourceNode">対象のECHONET Lite ノードを表す<see cref="EchoNode"/>。</param>
- /// <param name="device">対象のECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。</param>
- /// <exception cref="InvalidOperationException">受信したEDTは無効なプロパティマップです。</exception>
- /// <seealso cref="HandleInstanceListNotificationReceivedAsync"/>
- private async ValueTask AcquirePropertyMapsAsync(EchoNode sourceNode, EchoObjectInstance device)
- {
- OnPropertyMapAcquiring(sourceNode, device); // TODO: support setting cancel and timeout
-
- using var ctsTimeout = CreateTimeoutCancellationTokenSource(20_000);
-
- bool result;
- IReadOnlyCollection<PropertyRequest> props;
-
- try
- {
- (result, props) = await PerformPropertyValueReadRequestAsync
- (
- sourceObject: SelfNode.NodeProfile,
- destinationNode: sourceNode,
- destinationObject: device,
- properties: device.Properties.Where(static p =>
- p.Spec.Code == 0x9D //状変アナウンスプロパティマップ
- || p.Spec.Code == 0x9E //Set プロパティマップ
- || p.Spec.Code == 0x9F //Get プロパティマップ
- ),
- cancellationToken: ctsTimeout.Token
- ).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (ctsTimeout.Token.Equals(ex.CancellationToken))
- {
- _logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りがタイムアウトしました");
- return;
- }
-
- //不可応答は無視
- if (!result)
- {
- _logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りで不可応答が返答されました");
- return;
- }
-
- _logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りが成功しました");
-
- var propertyCapabilityMap = new Dictionary<byte, PropertyCapability>(capacity: 16);
- foreach (var pr in props)
- {
- switch (pr.EPC)
- {
- //状変アナウンスプロパティマップ
- case 0x9D:
- {
- if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
- throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
-
- foreach (var propertyCode in propertyMap)
- {
- if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
- cap.Anno = true;
- else
- propertyCapabilityMap[propertyCode] = new() { Anno = true };
- }
- break;
- }
- //Set プロパティマップ
- case 0x9E:
- {
- if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
- throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
-
- foreach (var propertyCode in propertyMap)
- {
- if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
- cap.Set = true;
- else
- propertyCapabilityMap[propertyCode] = new() { Set = true };
- }
- break;
- }
- //Get プロパティマップ
- case 0x9F:
- {
- if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
- throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
-
- foreach (var propertyCode in propertyMap)
- {
- if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
- cap.Get = true;
- else
- propertyCapabilityMap[propertyCode] = new() { Get = true };
- }
- break;
- }
- }
- }
-
- device.ResetProperties
- (
- propertyCapabilityMap.Select
- (
- map =>
- {
- var (code, caps) = map;
-
- return new EchoPropertyInstance
- (
- device.Spec.ClassGroup.ClassGroupCode,
- device.Spec.Class.ClassCode,
- code,
- caps.Anno,
- caps.Set,
- caps.Get
- );
- }
- )
- );
-
- if (_logger is not null)
- {
- var sb = new StringBuilder();
- sb.AppendLine("------");
- foreach (var temp in device.Properties)
- {
- sb.Append('\t').Append(temp.GetDebugString()).AppendLine();
- }
- sb.AppendLine("------");
- _logger.LogTrace(sb.ToString());
- }
-
- device.HasPropertyMapAcquired = true;
-
- OnPropertyMapAcquired(sourceNode, device);
- }
-
- /// <summary>
- /// イベント<see cref="FrameReceived"/>をハンドルするメソッドを実装します。
- /// 受信したECHONET Lite フレームを処理し、必要に応じて要求に対する応答を返します。
- /// </summary>
- /// <param name="sender">イベントのソース。</param>
- /// <param name="value">
- /// イベントデータを格納している<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// ECHONET Lite フレームの送信元を表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <exception cref="InvalidOperationException">電文形式 1(規定電文形式)を期待しましたが、<see cref="EDATA1"/>を取得できませんでした。</exception>
-#pragma warning disable CA1502 // TODO: reduce complexity
- private void HandleFrameReceived(object? sender, (IPAddress address, Frame frame) value)
- {
- if (value.frame.EHD1 != EHD1.ECHONETLite)
- return;
- if (value.frame.EHD2 != EHD2.Type1)
- return;
-
- if (value.frame.EDATA is not EDATA1 edata)
- throw new InvalidOperationException($"expected {nameof(EDATA1)}, but was {value.frame.EDATA?.GetType()}");
-
- var sourceNode = Nodes.SingleOrDefault(n => value.address is not null && value.address.Equals(n.Address));
- //未知のノードの場合
- if (sourceNode == null)
- {
- //ノードを生成
- sourceNode = new EchoNode
- (
- address: value.address,
- nodeProfile: new EchoObjectInstance(Specifications.プロファイル.ノードプロファイル, 0x01)
- );
- Nodes.Add(sourceNode);
- NodeJoined?.Invoke(this,sourceNode);
- }
- EchoObjectInstance? destObject = null;
- //自ノードプロファイル宛てのリクエストの場合
- if (SelfNode.NodeProfile.EOJ == edata.DEOJ)
- {
- destObject = SelfNode.NodeProfile;
- }
- else
- {
- destObject = SelfNode.Devices.FirstOrDefault(d => d.EOJ == edata.DEOJ);
- }
- Task? task = null;
-
- switch (edata.ESV)
- {
- case ESV.SetI://プロパティ値書き込み要求(応答不要)
- //あれば、書き込んでおわり
- //なければ、プロパティ値書き込み要求不可応答 SetI_SNA
- task = Task.Run(() => HandlePropertyValueWriteRequestAsync(value, edata, destObject));
- break;
- case ESV.SetC://プロパティ値書き込み要求(応答要)
- //あれば、書き込んで プロパティ値書き込み応答 Set_Res
- //なければ、プロパティ値書き込み要求不可応答 SetC_SNA
- task = Task.Run(() => HandlePropertyValueWriteRequestResponseRequiredAsync(value, edata, destObject));
- break;
- case ESV.Get://プロパティ値読み出し要求
- //あれば、プロパティ値読み出し応答 Get_Res
- //なければ、プロパティ値読み出し不可応答 Get_SNA
- task = Task.Run(() => HandlePropertyValueReadRequest(value, edata, destObject));
- break;
- case ESV.INF_REQ://プロパティ値通知要求
- //あれば、プロパティ値通知 INF
- //なければ、プロパティ値通知不可応答 INF_SNA
- break;
- case ESV.SetGet: //プロパティ値書き込み・読み出し要求
- //あれば、プロパティ値書き込み・読み出し応答 SetGet_Res
- //なければ、プロパティ値書き込み・読み出し不可応答 SetGet_SNA
- task = Task.Run(() => HandlePropertyValueWriteReadRequestAsync(value, edata, destObject));
- break;
- case ESV.INF: //プロパティ値通知
- //プロパティ値通知要求 INF_REQのレスポンス
- //または、自発的な通知のケースがある。
- //なので、要求送信(INF_REQ)のハンドラでも対処するが、こちらでも自発として対処をする。
- task = Task.Run(() => HandlePropertyValueNotificationRequestAsync(value, edata, sourceNode));
- break;
- case ESV.INFC: //プロパティ値通知(応答要)
- //プロパティ値通知応答 INFC_Res
- task = Task.Run(() => HandlePropertyValueNotificationResponseRequiredAsync(value, edata, sourceNode, destObject));
- break;
-
- case ESV.SetI_SNA: //プロパティ値書き込み要求不可応答
- //プロパティ値書き込み要求(応答不要)SetIのレスポンスなので、要求送信(SETI)のハンドラで対処
- break;
-
- case ESV.Set_Res: //プロパティ値書き込み応答
- case ESV.SetC_SNA: //プロパティ値書き込み要求不可応答
- //プロパティ値書き込み要求(応答要) SetCのレスポンスなので、要求送信(SETC)のハンドラで対処
- break;
-
- case ESV.Get_Res: //プロパティ値読み出し応答
- case ESV.Get_SNA: //プロパティ値読み出し不可応答
- //プロパティ値読み出し要求 Getのレスポンスなので、要求送信(GET)のハンドラで対処
- break;
-
- case ESV.INFC_Res: //プロパティ値通知応答
- //プロパティ値通知(応答要) INFCのレスポンスなので、要求送信(INFC)のハンドラで対処
- break;
-
- case ESV.INF_SNA: //プロパティ値通知不可応答
- //プロパティ値通知要求 INF_REQ のレスポンスなので、要求送信(INF_REQ)のハンドラで対処
- break;
-
- case ESV.SetGet_Res://プロパティ値書き込み・読み出し応答
- case ESV.SetGet_SNA: //プロパティ値書き込み・読み出し不可応答
- //プロパティ値書き込み・読み出し要求 SetGet のレスポンスなので、要求送信(SETGET)のハンドラで対処
- break;
- default:
- break;
- }
-
- task?.ContinueWith((t) =>
- {
- if (t.Exception != null)
- {
- _logger?.LogTrace(t.Exception, "Exception");
- }
- });
- }
-#pragma warning restore CA1502
-
- /// <summary>
- /// ECHONET Lite サービス「SetI:プロパティ値書き込み要求(応答不要)」(ESV <c>0x60</c>)を処理します。
- /// </summary>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。 対象がない場合は<see langword="null"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueWriteRequestAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.1 プロパティ値書き込みサービス(応答不要)[0x60, 0x50]
- /// </seealso>
- private async Task<bool> HandlePropertyValueWriteRequestAsync((IPAddress address, Frame frame) request, EDATA1 edata, EchoObjectInstance? destObject)
- {
- if (edata.OPCList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
-
- if (destObject == null)
- {
- //対象となるオブジェクト自体が存在しない場合には、「不可応答」も返さないものとする。
- return false;
- }
-
- var hasError = false;
- var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
- foreach (var opc in edata.OPCList)
- {
- var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null
- || (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- hasError = true;
- //要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
- //要求された EDT を付け、要求を受理できなかったことを示す。
- opcList.Add(opc);
- }
- else
- {
- //要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
- property.SetValue(opc.EDT);
-
- opcList.Add(new(opc.EPC));
- }
- }
- if (hasError)
- {
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.SetI_SNA, //SetI_SNA(0x50)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return false;
- }
- return true;
- }
-
- /// <summary>
- /// ECHONET Lite サービス「SetC:プロパティ値書き込み要求(応答要)」(ESV <c>0x61</c>)を処理します。
- /// </summary>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。 対象がない場合は<see langword="null"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueWriteRequestResponseRequiredAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.2 プロパティ値書き込みサービス(応答要)[0x61,0x71,0x51]
- /// </seealso>
- private async Task<bool> HandlePropertyValueWriteRequestResponseRequiredAsync((IPAddress address, Frame frame) request, EDATA1 edata, EchoObjectInstance? destObject)
- {
- if (edata.OPCList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
-
- var hasError = false;
- var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
- if (destObject == null)
- {
- //DEOJがない場合、全OPCをそのまま返す
- hasError = true;
- opcList.AddRange(edata.OPCList);
- }
- else
- {
- foreach (var opc in edata.OPCList)
- {
- var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null
- || (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- hasError = true;
- //要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
- //要求された EDT を付け、要求を受理できなかったことを示す。
- opcList.Add(opc);
- }
- else
- {
- //要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
- property.SetValue(opc.EDT);
-
- opcList.Add(new(opc.EPC));
- }
- }
- }
- if (hasError)
- {
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.SetC_SNA, //SetC_SNA(0x51)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return false;
- }
-
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.Set_Res, //Set_Res(0x71)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return true;
- }
-
- /// <summary>
- /// ECHONET Lite サービス「Get:プロパティ値読み出し要求」(ESV <c>0x62</c>)を処理します。
- /// </summary>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。 対象がない場合は<see langword="null"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueReadRequestAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.3 プロパティ値読み出しサービス[0x62,0x72,0x52]
- /// </seealso>
- private async Task<bool> HandlePropertyValueReadRequest((IPAddress address, Frame frame) request, EDATA1 edata, EchoObjectInstance? destObject)
- {
- if (edata.OPCList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
-
- var hasError = false;
- var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
- if (destObject == null)
- {
- //DEOJがない場合、全OPCをそのまま返す
- hasError = true;
- opcList.AddRange(edata.OPCList);
- }
- else
- {
- foreach (var opc in edata.OPCList)
- {
- var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null
- || (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- hasError = true;
- //要求を受理しなかった EPC に対しては、それに続く PDC に 0 を設定して
- //EDT はつけず、要求を受理できなかったことを示す。
- //(そのままでよい)
- opcList.Add(opc);
- }
- else
- {
- //要求を受理した EPCに対しては、それに続く PDC に読み出したプロパティの長さを、
- //EDT には読み出したプロパティ値を設定する
- opcList.Add(new(opc.EPC, property.ValueMemory));
- }
- }
- }
- if (hasError)
- {
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.Get_SNA, //Get_SNA(0x52)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return false;
- }
-
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.Get_Res, //Get_Res(0x72)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return true;
- }
-
- /// <summary>
- /// ECHONET Lite サービス「SetGet:プロパティ値書き込み・読み出し要求」(ESV <c>0x6E</c>)を処理します。
- /// </summary>
- /// <remarks>
- /// 本実装は書き込み後、読み込む
- /// </remarks>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。 対象がない場合は<see langword="null"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueWriteReadRequestAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- /// </seealso>
- private async Task<bool> HandlePropertyValueWriteReadRequestAsync((IPAddress address, Frame frame) request, EDATA1 edata, EchoObjectInstance? destObject)
- {
- if (edata.OPCSetList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCSetList)} is null");
- if (edata.OPCGetList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCGetList)} is null");
-
- var hasError = false;
- var opcSetList = new List<PropertyRequest>(capacity: edata.OPCSetList.Count);
- var opcGetList = new List<PropertyRequest>(capacity: edata.OPCGetList.Count);
- if (destObject == null)
- {
- //DEOJがない場合、全OPCをそのまま返す
- hasError = true;
- opcSetList.AddRange(edata.OPCSetList);
- opcGetList.AddRange(edata.OPCGetList);
- }
- else
- {
- foreach (var opc in edata.OPCSetList)
- {
- var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null
- || (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- hasError = true;
- //要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
- //要求された EDT を付け、要求を受理できなかったことを示す。
- opcSetList.Add(opc);
- }
- else
- {
- //要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
- property.SetValue(opc.EDT);
-
- opcSetList.Add(new(opc.EPC));
- }
- }
- foreach (var opc in edata.OPCGetList)
- {
- var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null
- || (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- hasError = true;
- //要求を受理しなかった EPC に対しては、それに続く PDC に 0 を設定して
- //EDT はつけず、要求を受理できなかったことを示す。
- //(そのままでよい)
- opcGetList.Add(opc);
- }
- else
- {
- //要求を受理した EPCに対しては、それに続く PDC に読み出したプロパティの長さを、
- //EDT には読み出したプロパティ値を設定する
- opcSetList.Add(new(opc.EPC, property.ValueMemory));
- }
- }
- }
- if (hasError)
- {
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.SetGet_SNA, //SetGet_SNA(0x5E)
- opcListOrOpcSetList: opcSetList,
- opcGetList: opcGetList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return false;
- }
-
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.SetGet_Res, //SetGet_Res(0x7E)
- opcListOrOpcSetList: opcSetList,
- opcGetList: opcGetList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
-
- return true;
- }
-
- /// <summary>
- /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を処理します。
- /// </summary>
- /// <remarks>
- /// 自発なので、0x73のみ。
- /// </remarks>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="sourceNode">要求元CHONET Lite ノードを表す<see cref="EchoNode"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueNotificationRequestAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
- /// </seealso>
-#pragma warning disable IDE0060
- private async Task<bool> HandlePropertyValueNotificationRequestAsync((IPAddress address, Frame frame) request, EDATA1 edata, EchoNode sourceNode)
-#pragma warning restore IDE0060
- {
- if (edata.OPCList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
-
- var hasError = false;
- var sourceObject = sourceNode.Devices.FirstOrDefault(d => d.EOJ == edata.SEOJ);
- if (sourceObject == null)
- {
- //ノードプロファイルからの通知の場合
- if (sourceNode.NodeProfile.EOJ == edata.SEOJ)
- {
- sourceObject = sourceNode.NodeProfile;
- }
- else
- {
- //未知のオブジェクト
- //新規作成(プロパティはない状態)
- sourceObject = new EchoObjectInstance(edata.SEOJ);
- sourceNode.Devices.Add(sourceObject);
- }
- }
- foreach (var opc in edata.OPCList)
- {
- var property = sourceObject.Properties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null)
- {
- //未知のプロパティ
- //新規作成
- property = new EchoPropertyInstance(edata.SEOJ.ClassGroupCode, edata.SEOJ.ClassCode, opc.EPC);
- sourceObject.AddProperty(property);
- }
- if ((property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- //スペック外なので、格納しない
- hasError = true;
- }
- else
- {
- property.SetValue(opc.EDT);
- //ノードプロファイルのインスタンスリスト通知の場合
- if (sourceNode.NodeProfile == sourceObject
- && opc.EPC == 0xD5)
- {
- await HandleInstanceListNotificationReceivedAsync(sourceNode, opc.EDT).ConfigureAwait(false);
- }
- }
- }
- return !hasError;
- }
-
- /// <summary>
- /// ECHONET Lite サービス「INFC:プロパティ値通知(応答要)」(ESV <c>0x74</c>)を処理します。
- /// </summary>
- /// <param name="request">
- /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
- /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
- /// </param>
- /// <param name="edata">受信したEDATAを表す<see cref="EDATA1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
- /// <param name="sourceNode">要求元CHONET Lite ノードを表す<see cref="EchoNode"/>。</param>
- /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchoObjectInstance"/>。 対象がない場合は<see langword="null"/>。</param>
- /// <returns>
- /// 非同期の読み取り操作を表す<see cref="Task{Boolean}"/>。
- /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
- /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
- /// </returns>
- /// <seealso cref="PerformPropertyValueNotificationResponseRequiredAsync"/>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.6 プロパティ値通知(応答要)サービス[0x74, 0x7A]
- /// </seealso>
- private async Task<bool> HandlePropertyValueNotificationResponseRequiredAsync((IPAddress address, Frame frame) request, EDATA1 edata, EchoNode sourceNode, EchoObjectInstance? destObject)
- {
- if (edata.OPCList is null)
- throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
-
- var hasError = false;
- var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
- if (destObject == null)
- {
- //指定された DEOJ が存在しない場合には電文を廃棄する。
- //"けどこっそり保持する"
- hasError = true;
- }
- var sourceObject = sourceNode.Devices.FirstOrDefault(d => d.EOJ == edata.SEOJ);
- if (sourceObject == null)
- {
- //ノードプロファイルからの通知の場合
- if (sourceNode.NodeProfile.EOJ == edata.SEOJ)
- {
- sourceObject = sourceNode.NodeProfile;
- }
- else
- {
- //未知のオブジェクト
- //新規作成(プロパティはない状態)
- sourceObject = new EchoObjectInstance(edata.SEOJ);
- sourceNode.Devices.Add(sourceObject);
- }
- }
- foreach (var opc in edata.OPCList)
- {
- var property = sourceObject.Properties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
- if (property == null)
- {
- //未知のプロパティ
- //新規作成
- property = new EchoPropertyInstance(edata.SEOJ.ClassGroupCode, edata.SEOJ.ClassCode, opc.EPC);
- sourceObject.AddProperty(property);
- }
-
- if ((property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize)
- || (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize))
- {
- //スペック外なので、格納しない
- hasError = true;
-
- }
- else
- {
- property.SetValue(opc.EDT);
- //ノードプロファイルのインスタンスリスト通知の場合
- if (sourceNode.NodeProfile == sourceObject
- && opc.EPC == 0xD5)
- {
- await HandleInstanceListNotificationReceivedAsync(sourceNode, opc.EDT).ConfigureAwait(false);
- }
- }
- //EPC には通知時と同じプロパティコードを設定するが、
- //通知を受信したことを示すため、PDCには 0 を設定し、EDT は付けない。
- opcList.Add(new(opc.EPC));
- }
- if (destObject != null)
- {
- await SendFrameAsync
- (
- request.address,
- buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1
- (
- buffer: buffer,
- tid: request.frame.TID,
- sourceObject: edata.DEOJ, //入れ替え
- destinationObject: edata.SEOJ, //入れ替え
- esv: ESV.INFC_Res, //INFC_Res(0x74)
- opcListOrOpcSetList: opcList
- ),
- cancellationToken: default
- ).ConfigureAwait(false);
- }
- return !hasError;
-
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/FrameSerializer.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/FrameSerializer.cs
deleted file mode 100644
index 93b9577..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/FrameSerializer.cs
+++ /dev/null
@@ -1,418 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite.Enums;
-using EchoDotNetLite.Models;
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-
-namespace EchoDotNetLite
-{
- public static class FrameSerializer
- {
- public static void Serialize(Frame frame, IBufferWriter<byte> buffer)
- {
- if (buffer is null)
- throw new ArgumentNullException(nameof(buffer));
-
- if (!frame.EHD1.HasFlag(EHD1.ECHONETLite))
- throw new InvalidOperationException($"undefined EHD1 ({(byte)frame.EHD1:X2})");
-
- switch (frame.EHD2)
- {
- case EHD2.Type1:
- if (frame.EDATA is not EDATA1 edata1)
- throw new ArgumentException($"{nameof(EDATA1)} must be set to {nameof(Frame)}.{nameof(Frame.EDATA)}.", paramName: nameof(frame));
-
-#if !NET5_0_OR_GREATER // NotNullWhenAttribute
-#pragma warning disable CS8604
-#endif
- SerializeEchonetLiteFrameFormat1
- (
- buffer,
- frame.TID,
- edata1.SEOJ,
- edata1.DEOJ,
- edata1.ESV,
- edata1.IsWriteOrReadService ? edata1.OPCSetList : edata1.OPCList,
- edata1.IsWriteOrReadService ? edata1.OPCGetList : null
- );
-#pragma warning restore CS8604
-
- break;
-
- case EHD2.Type2:
- if (frame.EDATA is not EDATA2 edata2)
- throw new ArgumentException($"{nameof(EDATA2)} must be set to {nameof(Frame)}.{nameof(Frame.EDATA)}.", paramName: nameof(frame));
-
- SerializeEchonetLiteFrameFormat2(buffer, frame.TID, edata2.Message.Span);
-
- break;
-
- default:
- throw new InvalidOperationException($"undefined EHD2 ({(byte)frame.EHD2:X2})");
- }
- }
-
- public static void SerializeEchonetLiteFrameFormat1(
- IBufferWriter<byte> buffer,
- ushort tid,
- EOJ sourceObject,
- EOJ destinationObject,
- ESV esv,
- IEnumerable<PropertyRequest> opcListOrOpcSetList,
- IEnumerable<PropertyRequest>? opcGetList = null
- )
- {
- if (buffer is null)
- throw new ArgumentNullException(nameof(buffer));
- if (opcListOrOpcSetList is null)
- throw new ArgumentNullException(nameof(opcListOrOpcSetList));
-
- WriteEchonetLiteEHDAndTID(buffer, EHD1.ECHONETLite, EHD2.Type1, tid);
-
- WriteEOJ(buffer, sourceObject); // SEOJ
- WriteEOJ(buffer, destinationObject); // DEOJ
- Write(buffer, (byte)esv); // ESV
-
- // OPC 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
-
- //4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- // OPCSet 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
- var failIfOpcSetOrOpcGetIsZero = IsESVWriteOrReadService(esv) && esv != ESV.SetGet_SNA;
-
- if (!TryWriteEDATAType1ProcessingTargetProperties(buffer, opcListOrOpcSetList, failIfEmpty: failIfOpcSetOrOpcGetIsZero))
- throw new InvalidOperationException("OPCSet can not be zero when ESV is other than SetGet_SNA.");
-
- if (IsESVWriteOrReadService(esv))
- {
- if (opcGetList is null)
- throw new ArgumentNullException(nameof(opcGetList));
-
- // OPCGet 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
- if (!TryWriteEDATAType1ProcessingTargetProperties(buffer, opcGetList, failIfEmpty: failIfOpcSetOrOpcGetIsZero))
- throw new InvalidOperationException("OPCGet can not be zero when ESV is other than SetGet_SNA.");
- }
- }
-
- public static void SerializeEchonetLiteFrameFormat2(
- IBufferWriter<byte> buffer,
- ushort tid,
- ReadOnlySpan<byte> edata
- )
- {
- if (buffer is null)
- throw new ArgumentNullException(nameof(buffer));
-
- WriteEchonetLiteEHDAndTID(buffer, EHD1.ECHONETLite, EHD2.Type2, tid);
-
- var bufferEDATASpan = buffer.GetSpan(edata.Length);
-
- edata.CopyTo(bufferEDATASpan);
-
- buffer.Advance(edata.Length);
- }
-
-
- public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame)
- {
- frame = default;
-
- //ECHONETLiteフレームとしての最小長に満たない
- if (bytes.Length < 4)
- return false;
-
- //EHD1が0x1*(0001***)以外の場合、ECHONETLiteフレームではない
- if ((bytes[0] & 0xF0) != (byte)EHD1.ECHONETLite)
- return false;
-
- // ECHONET Lite電文ヘッダー1(1B)
- var ehd1 = (EHD1)bytes[0];
- // ECHONET Lite電文ヘッダー2(1B)
- var ehd2 = (EHD2)bytes[1];
- // トランザクションID(2B)
- var tid = BitConverter.ToUInt16(bytes.Slice(2, 2));
-
- // ECHONET Liteデータ(残り全部)
- var edataSpan = bytes.Slice(4);
-
- switch (ehd2)
- {
- case EHD2.Type1:
- if (TryReadEDATAType1(edataSpan, out var edata))
- {
- frame = new Frame(ehd1, ehd2, tid, edata);
- return true;
- }
- break;
-
- case EHD2.Type2:
- frame = new Frame
- (
- ehd1,
- ehd2,
- tid,
- new EDATA2
- (
- edataSpan.ToArray() // TODO: reduce allocation
- )
- );
- return true;
- }
-
- return false;
- }
-
- internal static bool IsESVWriteOrReadService(ESV esv)
- => esv switch {
- ESV.SetGet => true,
- ESV.SetGet_Res => true,
- ESV.SetGet_SNA => true,
- _ => false,
- };
-
- private static bool TryReadEDATAType1(ReadOnlySpan<byte> bytes, [NotNullWhen(true)] out EDATA1? edata)
- {
- edata = null;
-
- if (bytes.Length < 7)
- return false;
-
- var seoj = ReadEDATA1EOJ(bytes.Slice(0, 3));
- var deoj = ReadEDATA1EOJ(bytes.Slice(3, 3));
- var esv = (ESV)bytes[6];
-
- bytes = bytes.Slice(7);
-
- if (IsESVWriteOrReadService(esv))
- {
- //4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- // OPCSet 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
- if (!TryReadEDATA1ProcessingTargetProperties(bytes, out var opcSetList, out var bytesReadForOPCSetList))
- return false;
-
- bytes = bytes.Slice(bytesReadForOPCSetList);
-
- // OPCGet 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
- if (!TryReadEDATA1ProcessingTargetProperties(bytes, out var opcGetList, out _ /* var bytesReadForOPCGetList */))
- return false;
-
- edata = new
- (
- seoj,
- deoj,
- esv,
- opcSetList,
- opcGetList
- );
-
- // bytes = bytes.Slice(bytesReadForOPCGetList);
- }
- else
- {
- // OPC 処理プロパティ数(1B)
- // ECHONET Liteプロパティ(1B)
- // EDTのバイト数(1B)
- // プロパティ値データ(PDCで指定)
- if (!TryReadEDATA1ProcessingTargetProperties(bytes, out var opcList, out _ /* var bytesRead */))
- return false;
-
- // bytes = bytes.Slice(bytesRead);
-
- edata = new
- (
- seoj,
- deoj,
- esv,
- opcList
- );
- }
-
- return true;
- }
-
- private static EOJ ReadEDATA1EOJ(ReadOnlySpan<byte> bytes)
- {
-#if DEBUG
- if (bytes.Length < 3)
- throw new InvalidOperationException("input too short");
-#endif
-
- return new EOJ
- (
- classGroupCode: bytes[0],
- classCode: bytes[1],
- instanceCode: bytes[2]
- );
- }
-
- private static bool TryReadEDATA1ProcessingTargetProperties(
- ReadOnlySpan<byte> bytes,
- [NotNullWhen(true)] out IReadOnlyCollection<PropertyRequest>? processingTargetProperties,
- out int bytesRead
- )
- {
- processingTargetProperties = null;
- bytesRead = 0;
-
- if (bytes.Length < 1)
- return false;
-
- var initialLength = bytes.Length;
-
- // 4.2.3 サービス内容に関する詳細シーケンス
- // OPC 処理プロパティ数(1B)
- // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- // OPCSet 処理プロパティ数(1B)
- // OPCGet 処理プロパティ数(1B)
- var opc = bytes[0];
- var props = new List<PropertyRequest>(capacity: opc);
-
- bytes = bytes.Slice(1);
-
- for (byte i = 0; i < opc; i++)
- {
- if (bytes.Length < 2)
- return false;
-
- // ECHONET Liteプロパティ(1B)
- var epc = bytes[0];
- // EDTのバイト数(1B)
- var pdc = bytes[1];
-
- bytes = bytes.Slice(2);
-
- if (bytes.Length < pdc)
- return false;
-
- if (0 < pdc)
- {
- // プロパティ値データ(PDCで指定)
- var edt = bytes.Slice(0, pdc).ToArray(); // TODO: reduce allocation
-
- props.Add(new(epc, edt));
-
- bytes = bytes.Slice(pdc);
- }
- else
- {
- props.Add(new(epc));
- }
- }
-
- bytesRead = initialLength - bytes.Length;
- processingTargetProperties = props;
-
- return true;
- }
-
-
- private static void Write(IBufferWriter<byte> buffer, byte value)
- {
- var span = buffer.GetSpan(1);
-
- span[0] = value;
-
- buffer.Advance(1);
- }
-
- private static void WriteEchonetLiteEHDAndTID(IBufferWriter<byte> buffer, EHD1 ehd1, EHD2 ehd2, ushort tid)
- {
- var span = buffer.GetSpan(4);
-
- span[0] = (byte)ehd1; // EHD1
- span[1] = (byte)ehd2; // EHD2
-
- // TID
- _ = BitConverter.TryWriteBytes(span.Slice(2, 2), tid);
-
- buffer.Advance(4);
- }
-
- private static void WriteEOJ(IBufferWriter<byte> buffer, EOJ eoj)
- {
- var span = buffer.GetSpan(3);
-
- span[0] = eoj.ClassGroupCode;
- span[1] = eoj.ClassCode;
- span[2] = eoj.InstanceCode;
-
- buffer.Advance(3);
- }
-
- private static bool TryWriteEDATAType1ProcessingTargetProperties(
- IBufferWriter<byte> buffer,
- IEnumerable<PropertyRequest> opcList,
- bool failIfEmpty
- )
- {
- IEnumerable<PropertyRequest> opcListNonEnumerated;
-
-#if SYSTEM_LINQ_ENUMERABLE_TRYGETNONENUMERATEDCOUNT
- if (opcList.TryGetNonEnumeratedCount(out var countOfProps)) {
- opcListNonEnumerated = opcList;
- }
- else {
- var evaluatedOpcList = opcList.ToList();
-
- countOfProps = evaluatedOpcList.Count;
- opcListNonEnumerated = evaluatedOpcList;
- }
-#else
- var evaluatedOpcList = opcList.ToList();
- var countOfProps = evaluatedOpcList.Count;
-
- opcListNonEnumerated = evaluatedOpcList;
-#endif
-
- // 4.2.3 サービス内容に関する詳細シーケンス
- // OPC 処理プロパティ数(1B)
- // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
- // OPCSet 処理プロパティ数(1B)
- // OPCGet 処理プロパティ数(1B)
- if (failIfEmpty && countOfProps == 0)
- return false;
-
- Write(buffer, (byte)countOfProps);
-
- foreach (var prp in opcListNonEnumerated)
- {
- // ECHONET Liteプロパティ(1B)
- Write(buffer, prp.EPC);
- // EDTのバイト数(1B)
- Write(buffer, prp.PDC);
-
- if (prp.PDC != 0)
- {
- // プロパティ値データ(PDCで指定)
- var edtSpan = buffer.GetSpan(prp.PDC);
-
- prp.EDT.Span.CopyTo(edtSpan);
-
- buffer.Advance(prp.PDC);
- }
- }
-
- return true;
- }
- }
-
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/IEchonetLiteHandler.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/IEchonetLiteHandler.cs
deleted file mode 100644
index 9ceedcf..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/IEchonetLiteHandler.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace EchoDotNetLite
-{
-
- /// <summary>
- /// ECHONET Lite プロトコルおよび同通信ミドルウェアにおける下位通信層を抽象化し、ECHONET Lite フレームを送受信するためのメカニズムを提供します。
- /// </summary>
- public interface IEchonetLiteHandler
- {
- /// <summary>
- /// ECHONET Lite フレームを送信します。
- /// </summary>
- /// <param name="address">送信先を表す<see cref="IPAddress"/>。 <see langword="null"/>の場合は、サブネット内のすべてのノードに対して一斉同報送信を行います。</param>
- /// <param name="data">送信内容を表す<see cref="ReadOnlyMemory{Byte}"/>。</param>
- /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。</param>
- /// <returns>非同期の送信操作を表す<see cref="ValueTask"/>。</returns>
- ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
-
- /// <summary>
- /// ECHONET Lite フレームを受信した場合に発生します。
- /// </summary>
- /// <remarks>
- /// イベント引数<see cref="ValueTuple{T1,T2}"/>は、送信元を表す<see cref="IPAddress"/>、および受信内容を表す<see cref="ReadOnlyMemory{Byte}"/>を保持します。
- /// </remarks>
- event EventHandler<(IPAddress Address, ReadOnlyMemory<byte> Data)> Received;
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite/PropertyContentSerializer.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLite/PropertyContentSerializer.cs
deleted file mode 100644
index 7d35005..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite/PropertyContentSerializer.cs
+++ /dev/null
@@ -1,223 +0,0 @@
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-
-using EchoDotNetLite.Models;
-
-namespace EchoDotNetLite;
-
-public static class PropertyContentSerializer
-{
- /// <summary>
- /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)のプロパティ内容をシリアライズします。
- /// </summary>
- /// <remarks>
- /// プロパティ内容が253バイト以上となる場合、その時点で書き込みを中断して<see langword="true"/>を返します。
- /// つまり、<paramref name="instanceList"/>の85個目以降の要素は無視されます。
- /// </remarks>
- /// <param name="instanceList">インスタンスリストを表す<see cref="IEnumerable{EOJ}"/>。</param>
- /// <param name="destination">シリアライズした結果が書き込まれる<see cref="Span{Byte}"/></param>
- /// <param name="bytesWritten"><paramref name="destination"/>に書き込まれた長さ。</param>
- /// <returns>
- /// 正常に書き込まれた場合は<see langword="true"/>。
- /// <paramref name="instanceList"/>が<see langword="null"/>の場合、<paramref name="destination"/>の長さが足りない場合は<see langword="false"/>。
- /// </returns>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 6.11.1 ノードプロファイルクラス詳細規定
- /// </seealso>
- public static bool TrySerializeInstanceListNotification
- (
- IEnumerable<EOJ> instanceList,
- Span<byte> destination,
- out int bytesWritten
- )
- {
- bytesWritten = 0;
-
- if (instanceList is null)
- return false;
-
- //1 バイト目:通報インスタンス数
- if (destination.Length < 1)
- return false;
-
- ref var refNumberOfInstance = ref destination[0];
-
- bytesWritten++;
- destination = destination.Slice(1);
-
- //2~253 バイト目:ECHONET オブジェクトコード(EOJ3 バイト)を列挙。
- var numberOfInstances = 0;
-
- foreach (var instance in instanceList)
- {
- if (253 <= bytesWritten)
- break;
-
- if (destination.Length < 3)
- return false;
-
- destination[0] = instance.ClassGroupCode;
- destination[1] = instance.ClassCode;
- destination[2] = instance.InstanceCode;
-
- bytesWritten += 3;
- destination = destination.Slice(3);
-
- numberOfInstances++;
- }
-
- refNumberOfInstance = (byte)numberOfInstances;
-
- return true;
- }
-
- /// <summary>
- /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)のプロパティ内容をデシリアライズします。
- /// </summary>
- /// <param name="content">「インスタンスリスト通知」のプロパティ内容を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
- /// <param name="instanceList">デシリアライズした結果を格納した<see cref="IReadOnlyList{EOJ}"/>。</param>
- /// <returns>
- /// 正常にデシリアライズされた場合は<see langword="true"/>。
- /// <paramref name="content"/>の内容に不足がある場合は<see langword="false"/>。
- /// </returns>
- /// <seealso href="https://echonet.jp/spec_v114_lite/">
- /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 6.11.1 ノードプロファイルクラス詳細規定
- /// </seealso>
- public static bool TryDeserializeInstanceListNotification
- (
- ReadOnlySpan<byte> content,
- [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList
- )
- {
- instanceList = default;
-
- //1 バイト目:通報インスタンス数
- if (content.Length < 1)
- return false;
-
- var numberOfInstance = (int)content[0];
- var list = new List<EOJ>(capacity: numberOfInstance);
-
- content = content.Slice(1);
-
- //2~253 バイト目:ECHONET オブジェクトコード(EOJ3 バイト)を列挙。
- for (var i = 0; i < numberOfInstance; i++)
- {
- if (content.Length < 3)
- return false;
-
- var eoj = new EOJ
- (
- classGroupCode: content[0],
- classCode: content[1],
- instanceCode: content[2]
- );
-
- list.Add(eoj);
-
- content = content.Slice(3);
- }
-
- instanceList = list;
-
- return true;
- }
-
- /// <summary>
- /// ECHONETプロパティ「プロパティマップ」のプロパティ内容をデシリアライズします。 以下の「プロパティマップ」プロパティのデシリアライズに使用します。
- /// <list type="bullet">
- /// <item><description>「SetM プロパティマップ」(EPC <c>0x9B</c>)</description></item>
- /// <item><description>「GetM プロパティマップ」(EPC <c>0x9C</c>)</description></item>
- /// <item><description>「状変アナウンスプロパティマップ」(EPC <c>0x9D</c>)</description></item>
- /// <item><description>「Set プロパティマップ」(EPC <c>0x9E</c>)</description></item>
- /// <item><description>「Get プロパティマップ」(EPC <c>0x9F</c>)</description></item>
- /// </list>
- /// </summary>
- /// <param name="content">「プロパティマップ」のプロパティ内容を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
- /// <param name="propertyMap">デシリアライズした結果を格納した<see cref="IReadOnlyList{Byte}"/>。</param>
- /// <returns>
- /// 正常にデシリアライズされた場合は<see langword="true"/>。
- /// <paramref name="content"/>の内容に不足がある場合は<see langword="false"/>。
- /// </returns>
- /// <seealso href="https://echonet.jp/spec_g/">
- /// APPENDIX ECHONET 機器オブジェクト詳細規定 第2章 機器オブジェクトスーパークラス規定
- /// </seealso>
- /// <seealso href="https://echonet.jp/spec_g/">
- /// APPENDIX ECHONET 機器オブジェクト詳細規定 付録1 プロパティマップ記述形式
- /// </seealso>
- public static bool TryDeserializePropertyMap
- (
- ReadOnlySpan<byte> content,
- [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap
- )
- {
- propertyMap = default;
-
- //1 バイト目:プロパティの数。バイナリ表示。
- if (content.Length < 1)
- return false;
-
- var numberOfProperties = (int)content[0];
-
- if (numberOfProperties == 0)
- {
- propertyMap = Array.Empty<byte>();
- return true;
- }
-
- var props = new List<byte>(capacity: numberOfProperties);
-
- content = content.Slice(1);
-
- if (numberOfProperties < 0x10)
- {
- // 記述形式(1)
- // 1 バイト目:プロパティの数。バイナリ表示。
- // 2 バイト目以降:プロパティのコード(1 バイトコード)をそのまま列挙する。
- if (content.Length < numberOfProperties)
- return false;
-
-#if SYSTEM_COLLECTIONS_GENERIC_COLLECTIONEXTENSIONS_ADDRANGE
- props.AddRange(content.Slice(0, numberOfProperties));
-#else
- for (var i = 0; i < numberOfProperties; i++)
- {
- props.Add(content[i]);
- }
-#endif
- }
- else
- {
- // 記述形式(2)
- // 1 バイト目:プロパティの数。バイナリ表示。
- // 2~17 バイト目:下図の 16 バイトのテーブルにおいて、存在するプロパティコードを示
- // すビット位置に 1 をセットして 2 バイト目から順に列挙する。
- if (content.Length < 16)
- return false;
-
- for (var i = 0; i < 16; i++)
- {
- var propertyBits = content[i];
- var lower = i;
-
- for (var j = 0; j < 8; j++)
- {
- var upper = 0x80 + (0x10 * j);
- var bitMask = 1 << j;
-
- if ((propertyBits & bitMask) != 0)
- {
- props.Add((byte)(upper | lower));
- }
- }
- }
- }
-
- propertyMap = props;
-
- return true;
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/LANClient.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/LANClient.cs
deleted file mode 100644
index 6f16082..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/LANClient.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using System;
-using Microsoft.Extensions.Logging;
-
-namespace EchoDotNetLiteLANBridge
-{
- [Obsolete($"Use {nameof(UdpEchonetLiteHandler)} instead.")]
- public class LANClient : UdpEchonetLiteHandler
- {
- public LANClient(ILogger<LANClient> logger) : base(logger)
- {
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/UdpEchonetLiteHandler.cs b/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/UdpEchonetLiteHandler.cs
deleted file mode 100644
index d715380..0000000
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLiteLANBridge/UdpEchonetLiteHandler.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
-// SPDX-License-Identifier: MIT
-using EchoDotNetLite;
-using Microsoft.Extensions.Logging;
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-#if !SYSTEM_CONVERT_TOHEXSTRING
-using System.Runtime.InteropServices; // MemoryMarshal
-#endif
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace EchoDotNetLiteLANBridge
-{
- public class UdpEchonetLiteHandler : IEchonetLiteHandler, IDisposable
- {
- private readonly UdpClient receiveUdpClient;
- private readonly ILogger _logger;
- private const int DefaultUdpPort = 3610;
- public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger)
- {
- var selfAddresses = NetworkInterface.GetAllNetworkInterfaces().SelectMany(ni => ni.GetIPProperties().UnicastAddresses.Select(ua => ua.Address));
- _logger = logger;
- try
- {
- receiveUdpClient = new UdpClient(DefaultUdpPort)
- {
- EnableBroadcast = true
- };
- }
- catch (Exception ex)
- {
- _logger.LogDebug(ex, "Exception");
- throw;
- }
- Task.Run(async () =>
- {
- try
- {
- while (true)
- {
- var receivedResults = await receiveUdpClient.ReceiveAsync().ConfigureAwait(false);
- if (selfAddresses.Contains(receivedResults.RemoteEndPoint.Address))
- {
- //ブロードキャストを自分で受信(無視)
- continue;
- }
- _logger.LogDebug($"UDP受信:{receivedResults.RemoteEndPoint.Address} {BitConverter.ToString(receivedResults.Buffer)}");
- Received?.Invoke(this, (receivedResults.RemoteEndPoint.Address, receivedResults.Buffer.AsMemory()));
- }
- }
- catch (System.ObjectDisposedException)
- {
- //握りつぶす
- }
- catch (Exception ex)
- {
- _logger.LogDebug(ex, "Exception");
- }
- });
- }
-
- public event EventHandler<(IPAddress, ReadOnlyMemory<byte>)>? Received;
-
- public void Dispose()
- {
- _logger.LogDebug("Dispose");
- try
- {
- receiveUdpClient?.Close();
- }
- catch (Exception ex)
- {
- _logger.LogDebug(ex, "Exception");
- }
- }
-
- public async ValueTask SendAsync(IPAddress? address, ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
- {
- IPEndPoint remote;
- if (address == null)
- {
- remote = new IPEndPoint(IPAddress.Broadcast, DefaultUdpPort);
- }
- else
- {
- remote = new IPEndPoint(address, DefaultUdpPort);
- }
-
-#if SYSTEM_CONVERT_TOHEXSTRING
- _logger.LogDebug($"UDP送信:{remote.Address} {Convert.ToHexString(data.Span)}");
-#else
- if (MemoryMarshal.TryGetArray(data, out var segment))
- {
- _logger.LogDebug($"UDP送信:{remote.Address} {BitConverter.ToString(segment.Array!, segment.Offset, segment.Count)}");
- }
- else
- {
- _logger.LogDebug($"UDP送信:{remote.Address} {BitConverter.ToString(data.ToArray())}");
- }
-#endif
-
- var sendUdpClient = new UdpClient()
- {
- EnableBroadcast = true
- };
- sendUdpClient.Connect(remote);
-#if SYSTEM_NET_SOCKETS_UDPCLIENT_SENDASYNC_READONLYMEMORY_OF_BYTE
- await sendUdpClient.SendAsync(data, cancellationToken).ConfigureAwait(false);
-#else
- await sendUdpClient.SendAsync(data.ToArray(), data.Length).ConfigureAwait(false);
-#endif
- sendUdpClient.Close();
- }
- }
-}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData1.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData1.cs
new file mode 100644
index 0000000..c628d35
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData1.cs
@@ -0,0 +1,139 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+using System.Diagnostics.CodeAnalysis;
+#endif
+using System.Text.Json.Serialization;
+
+using Smdn.Net.EchonetLite.Serialization.Json;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// 電文形式 1(規定電文形式)
+/// </summary>
+public sealed class EData1 : IEData {
+ /// <summary>
+ /// 送信元ECHONET Liteオブジェクト指定(3B)
+ /// </summary>
+ public EOJ SEOJ { get; }
+
+ /// <summary>
+ /// 相手先ECHONET Liteオブジェクト指定(3B)
+ /// </summary>
+ public EOJ DEOJ { get; }
+
+ /// <summary>
+ /// ECHONET Liteサービス(1B)
+ /// ECHONET Liteサービスコード
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public ESV ESV { get; }
+
+ public IReadOnlyCollection<PropertyRequest>? OPCList { get; }
+
+ /// <summary>
+ /// 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ /// のみ使用
+ /// </summary>
+ public IReadOnlyCollection<PropertyRequest>? OPCGetList { get; }
+
+ /// <summary>
+ /// 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ /// のみ使用
+ /// </summary>
+ public IReadOnlyCollection<PropertyRequest>? OPCSetList { get; }
+
+ [JsonIgnore]
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+ [MemberNotNullWhen(false, nameof(OPCList))]
+ [MemberNotNullWhen(true, nameof(OPCGetList))]
+ [MemberNotNullWhen(true, nameof(OPCSetList))]
+#endif
+ public bool IsWriteOrReadService => FrameSerializer.IsESVWriteOrReadService(ESV);
+
+ /// <summary>
+ /// ECHONET Liteフレームの電文形式 1(規定電文形式)の電文を記述する<see cref="EData1"/>を作成します。
+ /// </summary>
+ /// <remarks>
+ /// このオーバーロードでは、<see cref="OPCGetList"/>および<see cref="OPCSetList"/>に<see langword="null"/>を設定します。
+ /// </remarks>
+ /// <param name="seoj"><see cref="SEOJ"/>に指定する値。</param>
+ /// <param name="deoj"><see cref="DEOJ"/>に指定する値。</param>
+ /// <param name="esv"><see cref="ESV"/>に指定する値。</param>
+ /// <param name="opcList"><see cref="OPCList"/>に指定する値。</param>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="esv"/>が<see cref="ESV.SetGet"/>, <see cref="ESV.SetGetResponse"/>, <see cref="ESV.SetGetServiceNotAvailable"/>のいずれかです。
+ /// この場合、<see cref="OPCSetList"/>および<see cref="OPCGetList"/>を指定する必要があります。
+ /// </exception>
+ /// <exception cref="ArgumentNullException"><paramref name="opcList"/>が<see langword="null"/>です。</exception>
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcList)
+ {
+ if (FrameSerializer.IsESVWriteOrReadService(esv))
+ throw new ArgumentException(message: $"ESV must be other than {nameof(ESV.SetGet)}, {nameof(ESV.SetGetResponse)}, or {nameof(ESV.SetGetServiceNotAvailable)}.", paramName: nameof(esv));
+
+ SEOJ = seoj;
+ DEOJ = deoj;
+ ESV = esv;
+ OPCList = opcList ?? throw new ArgumentNullException(nameof(opcList));
+ }
+
+ /// <summary>
+ /// ECHONET Liteフレームの電文形式 1(規定電文形式)の電文を記述する<see cref="EData1"/>を作成します。
+ /// </summary>
+ /// <remarks>
+ /// このオーバーロードでは、<see cref="OPCList"/>に<see langword="null"/>を設定します。
+ /// </remarks>
+ /// <param name="seoj"><see cref="SEOJ"/>に指定する値。</param>
+ /// <param name="deoj"><see cref="DEOJ"/>に指定する値。</param>
+ /// <param name="esv"><see cref="ESV"/>に指定する値。</param>
+ /// <param name="opcSetList"><see cref="OPCSetList"/>に指定する値。</param>
+ /// <param name="opcGetList"><see cref="OPCGetList"/>に指定する値。</param>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="esv"/>が<see cref="ESV.SetGet"/>, <see cref="ESV.SetGetResponse"/>, <see cref="ESV.SetGetServiceNotAvailable"/>のいずれかではありません。
+ /// この場合、<see cref="OPCList"/>のみを指定する必要があります。
+ /// </exception>
+ /// <exception cref="ArgumentNullException"><paramref name="opcSetList"/>もしくは<paramref name="opcGetList"/>が<see langword="null"/>です。</exception>
+ public EData1(EOJ seoj, EOJ deoj, ESV esv, IReadOnlyCollection<PropertyRequest> opcSetList, IReadOnlyCollection<PropertyRequest> opcGetList)
+ {
+ if (!FrameSerializer.IsESVWriteOrReadService(esv))
+ throw new ArgumentException(message: $"ESV must be {nameof(ESV.SetGet)}, {nameof(ESV.SetGetResponse)}, or {nameof(ESV.SetGetServiceNotAvailable)}.", paramName: nameof(esv));
+
+ SEOJ = seoj;
+ DEOJ = deoj;
+ ESV = esv;
+ OPCSetList = opcSetList ?? throw new ArgumentNullException(nameof(opcSetList));
+ OPCGetList = opcGetList ?? throw new ArgumentNullException(nameof(opcGetList));
+ }
+
+ internal IReadOnlyCollection<PropertyRequest> GetOPCList()
+ {
+ if (IsWriteOrReadService)
+ throw new InvalidOperationException($"invalid operation for the ESV of the current instance (ESV={ESV})");
+
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+ return OPCList;
+#else
+ return OPCList!;
+#endif
+ }
+
+ public (
+ IReadOnlyCollection<PropertyRequest> OPCSetList,
+ IReadOnlyCollection<PropertyRequest> OPCGetList
+ )
+ GetOPCSetGetList()
+ {
+ if (!IsWriteOrReadService)
+ throw new InvalidOperationException($"invalid operation for the ESV of the current instance (ESV={ESV})");
+
+#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE
+ return (OPCSetList, OPCGetList);
+#else
+ return (OPCSetList!, OPCGetList!);
+#endif
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData2.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData2.cs
new file mode 100644
index 0000000..ad98b03
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EData2.cs
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// 電文形式2(任意電文形式)
+/// </summary>
+public sealed class EData2 : IEData {
+ /// <summary>
+ /// ECHONET Liteフレームの電文形式2(任意電文形式)の電文を記述する<see cref="EData2"/>を作成します。
+ /// </summary>
+ /// <param name="message"><see cref="Message"/>に指定する値。</param>
+ public EData2(ReadOnlyMemory<byte> message)
+ {
+ Message = message;
+ }
+
+ /// <summary>
+ /// 任意電文形式の電文を表す<see cref="ReadOnlyMemory{Byte}"/>。
+ /// </summary>
+ public ReadOnlyMemory<byte> Message { get; }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD1.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD1.cs
new file mode 100644
index 0000000..2055676
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD1.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// ECHONET Lite ヘッダ1 (EDH1)の値を表す列挙体です。
+/// ECHONETのプロトコル種別を規定します。
+/// </summary>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.1.1 ECHONET Lite ヘッダ1(EHD1)
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 図 3-2 EHD1 詳細規定
+/// </seealso>
+#pragma warning disable CA1027
+public enum EHD1 : byte {
+#pragma warning restore CA1027
+ /// <summary>
+ /// プロトコル種別として「使用不可」を表す値を示します。
+ /// </summary>
+ None = 0b_0000_0000,
+
+ /// <summary>
+ /// プロトコル種別として「ECHONET Lite規格」を表す値を示します。
+ /// </summary>
+ EchonetLite = 0b_0001_0000,
+
+ /// <summary>
+ /// プロトコル種別として「従来のECHONET規格」を表すビットをマスクする値を示します。
+ /// </summary>
+ MaskEchonet = 0b_1000_0000,
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD2.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD2.cs
new file mode 100644
index 0000000..f044058
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EHD2.cs
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// ECHONET Lite ヘッダ2 (EDH2)の値を表す列挙体です。
+/// ECHONET Lite フレームのEDATA部の電文形式を規定します。
+/// </summary>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.1.1 ECHONET Lite ヘッダ2(EHD2)
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 図 3-3 EHD2 詳細規定
+/// </seealso>
+#pragma warning disable CA1008
+public enum EHD2 : byte {
+#pragma warning restore CA1008
+ /// <summary>
+ /// EDATA部の電文形式として「電文形式 1(規定電文形式)」を表す値を示します。
+ /// </summary>
+ Type1 = 0b_1000_0001,
+
+ /// <summary>
+ /// EDATA部の電文形式として「電文形式 2(任意電文形式)」を表す値を示します。
+ /// </summary>
+ Type2 = 0b_1000_0010,
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EOJ.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EOJ.cs
new file mode 100644
index 0000000..18685ec
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/EOJ.cs
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json.Serialization;
+
+using Smdn.Net.EchonetLite.Serialization.Json;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// ECHONET オブジェクト(EOJ)
+/// </summary>
+public readonly struct EOJ : IEquatable<EOJ> {
+ /// <summary>
+ /// クラスグループコード
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public byte ClassGroupCode { get; }
+
+ /// <summary>
+ /// クラスクラスコード
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public byte ClassCode { get; }
+
+ /// <summary>
+ /// インスタンスコード
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public byte InstanceCode { get; }
+
+ /// <summary>
+ /// ECHONET オブジェクト(EOJ)を記述する<see cref="EOJ"/>を作成します。
+ /// </summary>
+ /// <param name="classGroupCode"><see cref="ClassGroupCode"/>に指定する値。</param>
+ /// <param name="classCode"><see cref="ClassCode"/>に指定する値。</param>
+ /// <param name="instanceCode"><see cref="InstanceCode"/>に指定する値。</param>
+ public EOJ(byte classGroupCode, byte classCode, byte instanceCode)
+ {
+ ClassGroupCode = classGroupCode;
+ ClassCode = classCode;
+ InstanceCode = instanceCode;
+ }
+
+ public bool Equals(EOJ other)
+ =>
+ ClassGroupCode == other.ClassGroupCode &&
+ ClassCode == other.ClassCode &&
+ InstanceCode == other.InstanceCode;
+
+ public override bool Equals(object? obj)
+ => obj switch {
+ EOJ other => Equals(other),
+ null => false,
+ _ => false,
+ };
+
+ public override int GetHashCode()
+ =>
+ ClassGroupCode.GetHashCode() ^
+ ClassCode.GetHashCode() ^
+ InstanceCode.GetHashCode();
+
+ public static bool operator ==(EOJ c1, EOJ c2)
+ =>
+ c1.ClassGroupCode == c2.ClassGroupCode &&
+ c1.ClassCode == c2.ClassCode &&
+ c1.InstanceCode == c2.InstanceCode;
+
+ public static bool operator !=(EOJ c1, EOJ c2)
+ => !(c1 == c2);
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/ESV.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/ESV.cs
new file mode 100644
index 0000000..6b832a6
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/ESV.cs
@@ -0,0 +1,156 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// ECHONET Lite ヘッダ2 (EDH2)の値を表す列挙体です。
+/// ECHONET Lite サービスコードを規定します。
+/// </summary>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 図 3-5 ESV コードの詳細規定
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 表 3-9 要求用 ESV コード一覧表
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 表 3-10 応答・通知用 ESV コード一覧表
+/// </seealso>
+/// <seealso href="https://echonet.jp/spec_v114_lite/">
+/// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 表 3-11 不可応答用 ESV コード一覧表
+/// </seealso>
+// #pragma warning disable CA1027
+public enum ESV : byte {
+ /// <summary>
+ /// 未定義または無効なサービスコードを表す値を示します。
+ /// </summary>
+ Invalid = 0,
+
+ /// <summary>
+ /// サービスコード<c>0x60</c>、「プロパティ値書き込み要求(応答不要)」(記号:<c>SetI</c>) を表す値を示します。
+ /// このサービスは、一斉同報可です。
+ /// </summary>
+ SetI = 0x60,
+
+ /// <summary>
+ /// サービスコード<c>0x61</c>、「プロパティ値書き込み要求(応答要)」(記号:<c>SetC</c>) を表す値を示します。
+ /// このサービスは、一斉同報可です。
+ /// </summary>
+ SetC = 0x61,
+
+ /// <summary>
+ /// サービスコード<c>0x62</c>、「プロパティ値読み出し要求」(記号:<c>Get</c>) を表す値を示します。
+ /// このサービスは、一斉同報可です。
+ /// </summary>
+ Get = 0x62,
+
+ /// <summary>
+ /// サービスコード<c>0x63</c>、「プロパティ値通知要求」(記号:<c>INF_REQ</c>) を表す値を示します。
+ /// このサービスは、一斉同報可です。
+ /// </summary>
+ InfRequest = 0x63,
+
+ /*
+ * 0x64-0x6D: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x6E</c>、「プロパティ値書き込み・読み出し要求」(記号:<c>SetGet</c>) を表す値を示します。
+ /// このサービスは、一斉同報可です。
+ /// </summary>
+ SetGet = 0x6E,
+
+ /*
+ * 0x6F: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x71</c>、「プロパティ値書き込み応答」(記号:<c>Set_Res</c>) を表す値を示します。
+ /// <see cref="SetC">ESV=0x61</see>の応答、個別応答です。
+ /// </summary>
+ SetResponse = 0x71,
+
+ /// <summary>
+ /// サービスコード<c>0x72</c>、「プロパティ値読み出し応答」(記号:<c>Get_Res</c>) を表す値を示します。
+ /// <see cref="Get">ESV=0x62</see>の応答、個別応答です。
+ /// </summary>
+ GetResponse = 0x72,
+
+ /// <summary>
+ /// サービスコード<c>0x73</c>、「プロパティ値通知」(記号:<c>INF</c>) を表す値を示します。
+ /// 自発的なプロパティ値通知、及び、<see cref="InfRequest">ESV=0x63</see>の応答に使用します。
+ /// このサービスは、個別通知、一斉同報通知共に可です。
+ /// </summary>
+ Inf = 0x73,
+
+ /// <summary>
+ /// サービスコード<c>0x74</c>、「プロパティ値通知(応答要)」(記号:<c>INFC</c>) を表す値を示します。
+ /// このサービスは、個別通知のみです。
+ /// </summary>
+ InfC = 0x74,
+
+ /*
+ * 0x75-0x79: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x7A</c>、「プロパティ値通知応答」(記号:<c>INFC_Res</c>) を表す値を示します。
+ /// <see cref="InfC">ESV=0x74</see>の応答、個別応答です。
+ /// </summary>
+ InfCResponse = 0x7A,
+
+ /*
+ * 0x7B-0x7D: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x7E</c>、「プロパティ値書き込み・読み出し応答」(記号:<c>SetGet_Res</c>) を表す値を示します。
+ /// <see cref="SetGet">ESV=0x6E</see>の応答、個別応答です。
+ /// </summary>
+ SetGetResponse = 0x7E,
+
+ /*
+ * 0x7F: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x50</c>、「プロパティ値書き込み要求不可応答」(記号:<c>SetI_SNA</c>) を表す値を示します。
+ /// <see cref="SetI">ESV=0x60</see>の不可応答、個別応答です。
+ /// </summary>
+ SetIServiceNotAvailable = 0x50,
+
+ /// <summary>
+ /// サービスコード<c>0x51</c>、「プロパティ値書き込み要求不可応答」(記号:<c>SetC_SNA</c>) を表す値を示します。
+ /// <see cref="SetC">ESV=0x61</see>の不可応答、個別応答です。
+ /// </summary>
+ SetCServiceNotAvailable = 0x51,
+
+ /// <summary>
+ /// サービスコード<c>0x52</c>、「プロパティ値読み出し不可応答」(記号:<c>Get_SNA</c>) を表す値を示します。
+ /// <see cref="Get">ESV=0x62</see>の不可応答、個別応答です。
+ /// </summary>
+ GetServiceNotAvailable = 0x52,
+
+ /// <summary>
+ /// サービスコード<c>0x53</c>、「プロパティ値通知不可応答」(記号:<c>INF_SNA</c>) を表す値を示します。
+ /// <see cref="InfRequest">ESV=0x63</see>の不可応答、個別応答です。
+ /// </summary>
+ InfServiceNotAvailable = 0x53,
+
+ /*
+ * 0x54-0x5D: for future reserved
+ */
+
+ /// <summary>
+ /// サービスコード<c>0x5E</c>、「プロパティ値書き込み・読み出し不可応答」(記号:<c>SetGet_SNA</c>) を表す値を示します。
+ /// <see cref="SetGet">ESV=0x6E</see>の不可応答、個別応答です。
+ /// </summary>
+ SetGetServiceNotAvailable = 0x5E,
+
+ /*
+ * 0x5F: for future reserved
+ */
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/Frame.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/Frame.cs
new file mode 100644
index 0000000..e6273b6
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/Frame.cs
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json.Serialization;
+
+using Smdn.Net.EchonetLite.Serialization.Json;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+/// <summary>
+/// ECHONET Liteフレーム
+/// </summary>
+public readonly struct Frame {
+ /// <summary>
+ /// ECHONET Lite電文ヘッダー1(1B)
+ /// ECHONETのプロトコル種別を指定する。
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public EHD1 EHD1 { get; }
+
+ /// <summary>
+ /// ECHONET Lite電文ヘッダー2(1B)
+ /// EDATA部の電文形式を指定する。
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public EHD2 EHD2 { get; }
+
+ /// <summary>
+ /// トランザクションID(2B)
+ /// </summary>
+ [CLSCompliant(false)]
+ [JsonConverter(typeof(SingleUInt16JsonConverter))]
+ public ushort TID { get; }
+
+ /// <summary>
+ /// ECHONET Liteデータ
+ /// ECHONET Lite 通信ミドルウェアにてやり取りされる電文のデータ領域。
+ /// </summary>
+ public IEData? EData { get; }
+
+ /// <summary>
+ /// ECHONET Liteフレームを記述する<see cref="Frame"/>を作成します。
+ /// </summary>
+ /// <param name="ehd1"><see cref="EHD1"/>に指定する値。</param>
+ /// <param name="ehd2"><see cref="EHD2"/>に指定する値。</param>
+ /// <param name="tid"><see cref="TID"/>に指定する値。</param>
+ /// <param name="edata"><see cref="EData"/>に指定する値。</param>
+ /// <exception cref="ArgumentNullException"><paramref name="edata"/>が<see langword="null"/>です。</exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="edata"/>の型が<paramref name="ehd2"/>と矛盾しています。
+ /// または<paramref name="ehd2"/>に不正な値が指定されています。
+ /// </exception>
+ [CLSCompliant(false)]
+ public Frame(EHD1 ehd1, EHD2 ehd2, ushort tid, IEData edata)
+ {
+ if (edata is null)
+ throw new ArgumentNullException(nameof(edata));
+
+ switch (ehd2) {
+ case EHD2.Type1:
+ if (edata is not EData1)
+ throw new ArgumentException(message: "type mismatch", paramName: nameof(edata));
+ break;
+
+ case EHD2.Type2:
+ if (edata is not EData2)
+ throw new ArgumentException(message: "type mismatch", paramName: nameof(edata));
+ break;
+
+ default:
+ throw new ArgumentException(message: "undefined EHD2", paramName: nameof(ehd2));
+ }
+
+ EHD1 = ehd1;
+ EHD2 = ehd2;
+ TID = tid;
+ EData = edata;
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/FrameSerializer.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/FrameSerializer.cs
new file mode 100644
index 0000000..77da67b
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/FrameSerializer.cs
@@ -0,0 +1,393 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+public static class FrameSerializer {
+ public static void Serialize(Frame frame, IBufferWriter<byte> buffer)
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+
+ if (!frame.EHD1.HasFlag(EHD1.EchonetLite))
+ throw new InvalidOperationException($"undefined EHD1 ({(byte)frame.EHD1:X2})");
+
+ switch (frame.EHD2) {
+ case EHD2.Type1:
+ if (frame.EData is not EData1 edata1)
+ throw new ArgumentException($"{nameof(EData1)} must be set to {nameof(Frame)}.{nameof(Frame.EData)}.", paramName: nameof(frame));
+
+ SerializeEchonetLiteFrameFormat1(
+ buffer,
+ frame.TID,
+ edata1.SEOJ,
+ edata1.DEOJ,
+ edata1.ESV,
+ edata1.IsWriteOrReadService ? edata1.OPCSetList! : edata1.OPCList!,
+ edata1.IsWriteOrReadService ? edata1.OPCGetList : null
+ );
+
+ break;
+
+ case EHD2.Type2:
+ if (frame.EData is not EData2 edata2)
+ throw new ArgumentException($"{nameof(EData2)} must be set to {nameof(Frame)}.{nameof(Frame.EData)}.", paramName: nameof(frame));
+
+ SerializeEchonetLiteFrameFormat2(buffer, frame.TID, edata2.Message.Span);
+
+ break;
+
+ default:
+ throw new InvalidOperationException($"undefined EHD2 ({(byte)frame.EHD2:X2})");
+ }
+ }
+
+ [CLSCompliant(false)]
+ public static void SerializeEchonetLiteFrameFormat1(
+ IBufferWriter<byte> buffer,
+ ushort tid,
+ EOJ sourceObject,
+ EOJ destinationObject,
+ ESV esv,
+ IEnumerable<PropertyRequest> opcListOrOpcSetList,
+ IEnumerable<PropertyRequest>? opcGetList = null
+ )
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+ if (opcListOrOpcSetList is null)
+ throw new ArgumentNullException(nameof(opcListOrOpcSetList));
+
+ WriteEchonetLiteEHDAndTID(buffer, EHD1.EchonetLite, EHD2.Type1, tid);
+
+ WriteEOJ(buffer, sourceObject); // SEOJ
+ WriteEOJ(buffer, destinationObject); // DEOJ
+ Write(buffer, (byte)esv); // ESV
+
+ // OPC 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+
+ // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ // OPCSet 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+ var failIfOpcSetOrOpcGetIsZero = IsESVWriteOrReadService(esv) && esv != ESV.SetGetServiceNotAvailable;
+
+ if (!TryWriteEDataType1ProcessingTargetProperties(buffer, opcListOrOpcSetList, failIfEmpty: failIfOpcSetOrOpcGetIsZero))
+ throw new InvalidOperationException("OPCSet can not be zero when ESV is other than SetGet_SNA.");
+
+ if (IsESVWriteOrReadService(esv)) {
+ if (opcGetList is null)
+ throw new ArgumentNullException(nameof(opcGetList));
+
+ // OPCGet 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+ if (!TryWriteEDataType1ProcessingTargetProperties(buffer, opcGetList, failIfEmpty: failIfOpcSetOrOpcGetIsZero))
+ throw new InvalidOperationException("OPCGet can not be zero when ESV is other than SetGet_SNA.");
+ }
+ }
+
+ [CLSCompliant(false)]
+ public static void SerializeEchonetLiteFrameFormat2(
+ IBufferWriter<byte> buffer,
+ ushort tid,
+ ReadOnlySpan<byte> edata
+ )
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+
+ WriteEchonetLiteEHDAndTID(buffer, EHD1.EchonetLite, EHD2.Type2, tid);
+
+ var bufferEDataSpan = buffer.GetSpan(edata.Length);
+
+ edata.CopyTo(bufferEDataSpan);
+
+ buffer.Advance(edata.Length);
+ }
+
+ public static bool TryDeserialize(ReadOnlySpan<byte> bytes, out Frame frame)
+ {
+ frame = default;
+
+ // ECHONETLiteフレームとしての最小長に満たない
+ if (bytes.Length < 4)
+ return false;
+
+ // EHD1が0x1*(0001***)以外の場合、ECHONETLiteフレームではない
+ if ((bytes[0] & 0xF0) != (byte)EHD1.EchonetLite)
+ return false;
+
+ // ECHONET Lite電文ヘッダー1(1B)
+ var ehd1 = (EHD1)bytes[0];
+ // ECHONET Lite電文ヘッダー2(1B)
+ var ehd2 = (EHD2)bytes[1];
+ // トランザクションID(2B)
+ var tid = BitConverter.ToUInt16(bytes.Slice(2, 2));
+
+ // ECHONET Liteデータ(残り全部)
+ var edataSpan = bytes.Slice(4);
+
+ switch (ehd2) {
+ case EHD2.Type1:
+ if (TryReadEDataType1(edataSpan, out var edata)) {
+ frame = new(ehd1, ehd2, tid, edata);
+ return true;
+ }
+
+ break;
+
+ case EHD2.Type2:
+ frame = new(
+ ehd1,
+ ehd2,
+ tid,
+ new EData2(
+ edataSpan.ToArray() // TODO: reduce allocation
+ )
+ );
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool IsESVWriteOrReadService(ESV esv)
+ => esv switch {
+ ESV.SetGet => true,
+ ESV.SetGetResponse => true,
+ ESV.SetGetServiceNotAvailable => true,
+ _ => false,
+ };
+
+ private static bool TryReadEDataType1(ReadOnlySpan<byte> bytes, [NotNullWhen(true)] out EData1? edata)
+ {
+ edata = null;
+
+ if (bytes.Length < 7)
+ return false;
+
+ var seoj = ReadEData1EOJ(bytes.Slice(0, 3));
+ var deoj = ReadEData1EOJ(bytes.Slice(3, 3));
+ var esv = (ESV)bytes[6];
+
+ bytes = bytes.Slice(7);
+
+ if (IsESVWriteOrReadService(esv)) {
+ // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ // OPCSet 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+ if (!TryReadEData1ProcessingTargetProperties(bytes, out var opcSetList, out var bytesReadForOPCSetList))
+ return false;
+
+ bytes = bytes.Slice(bytesReadForOPCSetList);
+
+ // OPCGet 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+ if (!TryReadEData1ProcessingTargetProperties(bytes, out var opcGetList, out _ /* var bytesReadForOPCGetList */))
+ return false;
+
+ edata = new(
+ seoj,
+ deoj,
+ esv,
+ opcSetList,
+ opcGetList
+ );
+
+ // bytes = bytes.Slice(bytesReadForOPCGetList);
+ }
+ else {
+ // OPC 処理プロパティ数(1B)
+ // ECHONET Liteプロパティ(1B)
+ // EDTのバイト数(1B)
+ // プロパティ値データ(PDCで指定)
+ if (!TryReadEData1ProcessingTargetProperties(bytes, out var opcList, out _ /* var bytesRead */))
+ return false;
+
+ // bytes = bytes.Slice(bytesRead);
+
+ edata = new(
+ seoj,
+ deoj,
+ esv,
+ opcList
+ );
+ }
+
+ return true;
+ }
+
+ private static EOJ ReadEData1EOJ(ReadOnlySpan<byte> bytes)
+ {
+#if DEBUG
+ if (bytes.Length < 3)
+ throw new InvalidOperationException("input too short");
+#endif
+
+ return new(
+ classGroupCode: bytes[0],
+ classCode: bytes[1],
+ instanceCode: bytes[2]
+ );
+ }
+
+ private static bool TryReadEData1ProcessingTargetProperties(
+ ReadOnlySpan<byte> bytes,
+ [NotNullWhen(true)] out IReadOnlyCollection<PropertyRequest>? processingTargetProperties,
+ out int bytesRead
+ )
+ {
+ processingTargetProperties = null;
+ bytesRead = 0;
+
+ if (bytes.Length < 1)
+ return false;
+
+ var initialLength = bytes.Length;
+
+ // 4.2.3 サービス内容に関する詳細シーケンス
+ // OPC 処理プロパティ数(1B)
+ // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ // OPCSet 処理プロパティ数(1B)
+ // OPCGet 処理プロパティ数(1B)
+ var opc = bytes[0];
+ var props = new List<PropertyRequest>(capacity: opc);
+
+ bytes = bytes.Slice(1);
+
+ for (byte i = 0; i < opc; i++) {
+ if (bytes.Length < 2)
+ return false;
+
+ // ECHONET Liteプロパティ(1B)
+ var epc = bytes[0];
+ // EDTのバイト数(1B)
+ var pdc = bytes[1];
+
+ bytes = bytes.Slice(2);
+
+ if (bytes.Length < pdc)
+ return false;
+
+ if (0 < pdc) {
+ // プロパティ値データ(PDCで指定)
+ var edt = bytes.Slice(0, pdc).ToArray(); // TODO: reduce allocation
+
+ props.Add(new(epc, edt));
+
+ bytes = bytes.Slice(pdc);
+ }
+ else {
+ props.Add(new(epc));
+ }
+ }
+
+ bytesRead = initialLength - bytes.Length;
+ processingTargetProperties = props;
+
+ return true;
+ }
+
+ private static void Write(IBufferWriter<byte> buffer, byte value)
+ {
+ var span = buffer.GetSpan(1);
+
+ span[0] = value;
+
+ buffer.Advance(1);
+ }
+
+ private static void WriteEchonetLiteEHDAndTID(IBufferWriter<byte> buffer, EHD1 ehd1, EHD2 ehd2, ushort tid)
+ {
+ var span = buffer.GetSpan(4);
+
+ span[0] = (byte)ehd1; // EHD1
+ span[1] = (byte)ehd2; // EHD2
+
+ // TID
+ _ = BitConverter.TryWriteBytes(span.Slice(2, 2), tid);
+
+ buffer.Advance(4);
+ }
+
+ private static void WriteEOJ(IBufferWriter<byte> buffer, EOJ eoj)
+ {
+ var span = buffer.GetSpan(3);
+
+ span[0] = eoj.ClassGroupCode;
+ span[1] = eoj.ClassCode;
+ span[2] = eoj.InstanceCode;
+
+ buffer.Advance(3);
+ }
+
+ private static bool TryWriteEDataType1ProcessingTargetProperties(
+ IBufferWriter<byte> buffer,
+ IEnumerable<PropertyRequest> opcList,
+ bool failIfEmpty
+ )
+ {
+ IEnumerable<PropertyRequest> opcListNonEnumerated;
+
+#if SYSTEM_LINQ_ENUMERABLE_TRYGETNONENUMERATEDCOUNT
+ if (opcList.TryGetNonEnumeratedCount(out var countOfProps)) {
+ opcListNonEnumerated = opcList;
+ }
+ else {
+ var evaluatedOpcList = opcList.ToList();
+
+ countOfProps = evaluatedOpcList.Count;
+ opcListNonEnumerated = evaluatedOpcList;
+ }
+#else
+ var evaluatedOpcList = opcList.ToList();
+ var countOfProps = evaluatedOpcList.Count;
+
+ opcListNonEnumerated = evaluatedOpcList;
+#endif
+
+ // 4.2.3 サービス内容に関する詳細シーケンス
+ // OPC 処理プロパティ数(1B)
+ // 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ // OPCSet 処理プロパティ数(1B)
+ // OPCGet 処理プロパティ数(1B)
+ if (failIfEmpty && countOfProps == 0)
+ return false;
+
+ Write(buffer, (byte)countOfProps);
+
+ foreach (var prp in opcListNonEnumerated) {
+ // ECHONET Liteプロパティ(1B)
+ Write(buffer, prp.EPC);
+ // EDTのバイト数(1B)
+ Write(buffer, prp.PDC);
+
+ if (prp.PDC != 0) {
+ // プロパティ値データ(PDCで指定)
+ var edtSpan = buffer.GetSpan(prp.PDC);
+
+ prp.EDT.Span.CopyTo(edtSpan);
+
+ buffer.Advance(prp.PDC);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/IEDATA.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/IEData.cs
similarity index 63%
rename from src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/IEDATA.cs
rename to src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/IEData.cs
index 3f3d6f4..02dfef2 100644
--- a/src/Smdn.Net.EchonetLite/EchoDotNetLite.Models/IEDATA.cs
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/IEData.cs
@@ -1,9 +1,6 @@
// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
// SPDX-License-Identifier: MIT
-namespace EchoDotNetLite.Models
-{
- public interface IEDATA
- {
- }
-}
+namespace Smdn.Net.EchonetLite.Protocol;
+
+public interface IEData { }
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyContentSerializer.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyContentSerializer.cs
new file mode 100644
index 0000000..8ddae19
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyContentSerializer.cs
@@ -0,0 +1,207 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+public static class PropertyContentSerializer {
+ /// <summary>
+ /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)のプロパティ内容をシリアライズします。
+ /// </summary>
+ /// <remarks>
+ /// プロパティ内容が253バイト以上となる場合、その時点で書き込みを中断して<see langword="true"/>を返します。
+ /// つまり、<paramref name="instanceList"/>の85個目以降の要素は無視されます。
+ /// </remarks>
+ /// <param name="instanceList">インスタンスリストを表す<see cref="IEnumerable{EOJ}"/>。</param>
+ /// <param name="destination">シリアライズした結果が書き込まれる<see cref="Span{Byte}"/></param>
+ /// <param name="bytesWritten"><paramref name="destination"/>に書き込まれた長さ。</param>
+ /// <returns>
+ /// 正常に書き込まれた場合は<see langword="true"/>。
+ /// <paramref name="instanceList"/>が<see langword="null"/>の場合、<paramref name="destination"/>の長さが足りない場合は<see langword="false"/>。
+ /// </returns>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 6.11.1 ノードプロファイルクラス詳細規定
+ /// </seealso>
+ public static bool TrySerializeInstanceListNotification(
+ IEnumerable<EOJ> instanceList,
+ Span<byte> destination,
+ out int bytesWritten
+ )
+ {
+ bytesWritten = 0;
+
+ if (instanceList is null)
+ return false;
+
+ // 1 バイト目:通報インスタンス数
+ if (destination.Length < 1)
+ return false;
+
+ ref var refNumberOfInstance = ref destination[0];
+
+ bytesWritten++;
+ destination = destination.Slice(1);
+
+ // 2~253 バイト目:ECHONET オブジェクトコード(EOJ3 バイト)を列挙。
+ var numberOfInstances = 0;
+
+ foreach (var instance in instanceList) {
+ if (253 <= bytesWritten)
+ break;
+
+ if (destination.Length < 3)
+ return false;
+
+ destination[0] = instance.ClassGroupCode;
+ destination[1] = instance.ClassCode;
+ destination[2] = instance.InstanceCode;
+
+ bytesWritten += 3;
+ destination = destination.Slice(3);
+
+ numberOfInstances++;
+ }
+
+ refNumberOfInstance = (byte)numberOfInstances;
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)のプロパティ内容をデシリアライズします。
+ /// </summary>
+ /// <param name="content">「インスタンスリスト通知」のプロパティ内容を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
+ /// <param name="instanceList">デシリアライズした結果を格納した<see cref="IReadOnlyList{EOJ}"/>。</param>
+ /// <returns>
+ /// 正常にデシリアライズされた場合は<see langword="true"/>。
+ /// <paramref name="content"/>の内容に不足がある場合は<see langword="false"/>。
+ /// </returns>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 6.11.1 ノードプロファイルクラス詳細規定
+ /// </seealso>
+ public static bool TryDeserializeInstanceListNotification(
+ ReadOnlySpan<byte> content,
+ [NotNullWhen(true)] out IReadOnlyList<EOJ>? instanceList
+ )
+ {
+ instanceList = default;
+
+ // 1 バイト目:通報インスタンス数
+ if (content.Length < 1)
+ return false;
+
+ var numberOfInstance = (int)content[0];
+ var list = new List<EOJ>(capacity: numberOfInstance);
+
+ content = content.Slice(1);
+
+ // 2~253 バイト目:ECHONET オブジェクトコード(EOJ3 バイト)を列挙。
+ for (var i = 0; i < numberOfInstance; i++) {
+ if (content.Length < 3)
+ return false;
+
+ var eoj = new EOJ(
+ classGroupCode: content[0],
+ classCode: content[1],
+ instanceCode: content[2]
+ );
+
+ list.Add(eoj);
+
+ content = content.Slice(3);
+ }
+
+ instanceList = list;
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONETプロパティ「プロパティマップ」のプロパティ内容をデシリアライズします。 以下の「プロパティマップ」プロパティのデシリアライズに使用します。
+ /// <list type="bullet">
+ /// <item><description>「SetM プロパティマップ」(EPC <c>0x9B</c>)</description></item>
+ /// <item><description>「GetM プロパティマップ」(EPC <c>0x9C</c>)</description></item>
+ /// <item><description>「状変アナウンスプロパティマップ」(EPC <c>0x9D</c>)</description></item>
+ /// <item><description>「Set プロパティマップ」(EPC <c>0x9E</c>)</description></item>
+ /// <item><description>「Get プロパティマップ」(EPC <c>0x9F</c>)</description></item>
+ /// </list>
+ /// </summary>
+ /// <param name="content">「プロパティマップ」のプロパティ内容を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
+ /// <param name="propertyMap">デシリアライズした結果を格納した<see cref="IReadOnlyList{Byte}"/>。</param>
+ /// <returns>
+ /// 正常にデシリアライズされた場合は<see langword="true"/>。
+ /// <paramref name="content"/>の内容に不足がある場合は<see langword="false"/>。
+ /// </returns>
+ /// <seealso href="https://echonet.jp/spec_g/">
+ /// APPENDIX ECHONET 機器オブジェクト詳細規定 第2章 機器オブジェクトスーパークラス規定
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_g/">
+ /// APPENDIX ECHONET 機器オブジェクト詳細規定 付録1 プロパティマップ記述形式
+ /// </seealso>
+ public static bool TryDeserializePropertyMap(
+ ReadOnlySpan<byte> content,
+ [NotNullWhen(true)] out IReadOnlyList<byte>? propertyMap
+ )
+ {
+ propertyMap = default;
+
+ // 1 バイト目:プロパティの数。バイナリ表示。
+ if (content.Length < 1)
+ return false;
+
+ var numberOfProperties = (int)content[0];
+
+ if (numberOfProperties == 0) {
+ propertyMap = Array.Empty<byte>();
+ return true;
+ }
+
+ var props = new List<byte>(capacity: numberOfProperties);
+
+ content = content.Slice(1);
+
+ if (numberOfProperties < 0x10) {
+ // 記述形式(1)
+ // 1 バイト目:プロパティの数。バイナリ表示。
+ // 2 バイト目以降:プロパティのコード(1 バイトコード)をそのまま列挙する。
+ if (content.Length < numberOfProperties)
+ return false;
+
+#if SYSTEM_COLLECTIONS_GENERIC_COLLECTIONEXTENSIONS_ADDRANGE
+ props.AddRange(content.Slice(0, numberOfProperties));
+#else
+ for (var i = 0; i < numberOfProperties; i++) {
+ props.Add(content[i]);
+ }
+#endif
+ }
+ else {
+ // 記述形式(2)
+ // 1 バイト目:プロパティの数。バイナリ表示。
+ // 2~17 バイト目:下図の 16 バイトのテーブルにおいて、存在するプロパティコードを示
+ // すビット位置に 1 をセットして 2 バイト目から順に列挙する。
+ if (content.Length < 16)
+ return false;
+
+ for (var i = 0; i < 16; i++) {
+ var propertyBits = content[i];
+ var lower = i;
+
+ for (var j = 0; j < 8; j++) {
+ var upper = 0x80 + (0x10 * j);
+ var bitMask = 1 << j;
+
+ if ((propertyBits & bitMask) != 0) {
+ props.Add((byte)(upper | lower));
+ }
+ }
+ }
+ }
+
+ propertyMap = props;
+
+ return true;
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyRequest.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyRequest.cs
new file mode 100644
index 0000000..f775bea
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Protocol/PropertyRequest.cs
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json.Serialization;
+
+using Smdn.Net.EchonetLite.Serialization.Json;
+
+namespace Smdn.Net.EchonetLite.Protocol;
+
+public readonly struct PropertyRequest {
+ /// <summary>
+ /// ECHONET Liteプロパティ(1B)
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public byte EPC { get; }
+
+ /// <summary>
+ /// EDTのバイト数(1B)
+ /// </summary>
+ [JsonConverter(typeof(SingleByteJsonConverterFactory))]
+ public byte PDC => (byte)EDT.Length;
+
+ /// <summary>
+ /// プロパティ値データ(PDCで指定)
+ /// </summary>
+ [JsonConverter(typeof(ByteSequenceJsonConverter))]
+ public ReadOnlyMemory<byte> EDT { get; }
+
+ /// <summary>
+ /// <see cref="EPC"/>のみを指定して、<see cref="PropertyRequest"/>を作成します。
+ /// </summary>
+ /// <remarks>
+ /// <see cref="PDC"/>には<c>0</c>、<see cref="EDT"/>には<see langword="null"/>が設定されます。
+ /// </remarks>
+ /// <param name="epc"><see cref="EPC"/>に指定する値。</param>
+ public PropertyRequest(byte epc)
+ {
+ EPC = epc;
+ EDT = ReadOnlyMemory<byte>.Empty;
+ }
+
+ /// <summary>
+ /// <see cref="EPC"/>および<see cref="EDT"/>を指定して、<see cref="PropertyRequest"/>を作成します。
+ /// </summary>
+ /// <remarks>
+ /// <see cref="PDC"/>は、常に<see cref="EDT"/>の長さを返します。
+ /// </remarks>
+ /// <param name="epc"><see cref="EPC"/>に指定する値。</param>
+ /// <param name="edt"><see cref="EDT"/>に指定する値。</param>
+ /// <exception cref="ArgumentException"><paramref name="edt"/>の長さが、255を超えています。</exception>
+ public PropertyRequest(byte epc, ReadOnlyMemory<byte> edt)
+ {
+ if (byte.MaxValue < edt.Length)
+ throw new ArgumentException(message: "The length of the EDT exceeds the maximum allowed by the specification.", nameof(edt));
+
+ EPC = epc;
+ EDT = edt;
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/ByteSequenceJsonConverter.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/ByteSequenceJsonConverter.cs
new file mode 100644
index 0000000..220d725
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/ByteSequenceJsonConverter.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1812
+
+using System;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+internal sealed class ByteSequenceJsonConverter : JsonConverter<ReadOnlyMemory<byte>> {
+ public override ReadOnlyMemory<byte> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => throw new NotSupportedException();
+
+ public override void Write(Utf8JsonWriter writer, ReadOnlyMemory<byte> value, JsonSerializerOptions options)
+ {
+ if (value.Length == 0) {
+ writer.WriteStringValue(string.Empty);
+ return;
+ }
+
+ var sb = new StringBuilder(capacity: value.Length * 2);
+ var span = value.Span;
+
+ for (var i = 0; i < span.Length; i++) {
+ sb.Append(Hexadecimals.ToHexChar(span[i] >> 4));
+ sb.Append(Hexadecimals.ToHexChar(span[i] & 0xF));
+ }
+
+ writer.WriteStringValue(sb.ToString());
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/Hexadecimals.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/Hexadecimals.cs
new file mode 100644
index 0000000..512340c
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/Hexadecimals.cs
@@ -0,0 +1,14 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+internal static class Hexadecimals {
+ internal static char ToHexChar(int value)
+ => value switch {
+ >= 0x0 and <= 0x9 => (char)('0' + value),
+ >= 0xA and <= 0xF => (char)('A' + value - 0xA),
+ _ => throw new ArgumentOutOfRangeException(message: "invalid hexadecimal number", paramName: nameof(value), actualValue: value),
+ };
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/JsonSerializerSourceGenerationContext.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/JsonSerializerSourceGenerationContext.cs
new file mode 100644
index 0000000..794f0a1
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/JsonSerializerSourceGenerationContext.cs
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.Text.Json.Serialization;
+
+using Smdn.Net.EchonetLite.Protocol;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+// use source generation in System.Text.Json
+// ref: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
+[JsonSerializable(typeof(Frame))]
+internal partial class JsonSerializerSourceGenerationContext : JsonSerializerContext { }
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverter.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverter.cs
new file mode 100644
index 0000000..137a750
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverter.cs
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+internal sealed class SingleByteJsonConverter<TByte> : JsonConverter<TByte>
+// where TByte : System.Numerics.IUnsignedNumber<byte>
+{
+ public override TByte Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => throw new NotSupportedException();
+
+ private static byte GetByte(TByte value)
+ => Convert.ToByte(value, provider: null);
+
+ public override void Write(Utf8JsonWriter writer, TByte value, JsonSerializerOptions options)
+ {
+ var by = GetByte(value);
+
+ Span<char> hex = stackalloc char[2] {
+ Hexadecimals.ToHexChar(by >> 4),
+ Hexadecimals.ToHexChar(by & 0xF),
+ };
+
+ writer.WriteStringValue(hex);
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverterFactory.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverterFactory.cs
new file mode 100644
index 0000000..442f18e
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleByteJsonConverterFactory.cs
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1812
+
+using System;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+internal sealed class SingleByteJsonConverterFactory : JsonConverterFactory {
+ public override bool CanConvert(Type typeToConvert)
+ {
+ if (typeToConvert == typeof(byte))
+ return true;
+
+ if (typeToConvert.IsEnum && Enum.GetUnderlyingType(typeToConvert) == typeof(byte))
+ return true;
+
+ return false;
+ }
+
+ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeToConvert == typeof(byte))
+ return new SingleByteJsonConverter<byte>();
+
+ if (typeToConvert.IsEnum && Enum.GetUnderlyingType(typeToConvert) == typeof(byte)) {
+ return (JsonConverter)Activator.CreateInstance(
+ type: typeof(SingleByteJsonConverter<>).MakeGenericType(typeToConvert),
+ bindingAttr: BindingFlags.Instance | BindingFlags.Public,
+ binder: null,
+ args: null,
+ culture: null
+ )!;
+ }
+
+ throw new InvalidOperationException($"unexpected type: {typeToConvert}");
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleUInt16JsonConverter.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleUInt16JsonConverter.cs
new file mode 100644
index 0000000..eefad51
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Serialization.Json/SingleUInt16JsonConverter.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1812
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.Net.EchonetLite.Serialization.Json;
+
+internal sealed class SingleUInt16JsonConverter : JsonConverter<ushort> {
+ public override ushort Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => throw new NotSupportedException();
+
+ public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options)
+ {
+ Span<char> hex = BitConverter.IsLittleEndian
+ ? stackalloc char[4] {
+ Hexadecimals.ToHexChar((value >> 4) & 0xF),
+ Hexadecimals.ToHexChar(value & 0xF),
+ Hexadecimals.ToHexChar(value >> 12),
+ Hexadecimals.ToHexChar((value >> 8) & 0xF),
+ }
+ : stackalloc char[4] {
+ Hexadecimals.ToHexChar(value >> 12),
+ Hexadecimals.ToHexChar((value >> 8) & 0xF),
+ Hexadecimals.ToHexChar((value >> 4) & 0xF),
+ Hexadecimals.ToHexChar(value & 0xF),
+ };
+
+ writer.WriteStringValue(hex);
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Transport/UdpEchonetLiteHandler.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Transport/UdpEchonetLiteHandler.cs
new file mode 100644
index 0000000..8d366b6
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.Transport/UdpEchonetLiteHandler.cs
@@ -0,0 +1,193 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1848 // CA1848: パフォーマンスを向上させるには、LoggerMessage デリゲートを使用します -->
+#pragma warning disable CA2254 // CA2254: ログ メッセージ テンプレートは、LoggerExtensions.Log****(ILogger, string?, params object?[])' への呼び出しによって異なるべきではありません。 -->
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+#if !SYSTEM_CONVERT_TOHEXSTRING
+using System.Runtime.InteropServices; // MemoryMarshal
+#endif
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+namespace Smdn.Net.EchonetLite.Transport;
+
+public class UdpEchonetLiteHandler : EchonetLiteHandler {
+ private UdpClient? receiveUdpClient;
+ private readonly IReadOnlyList<IPAddress> selfAddresses;
+ private readonly ILogger logger;
+ private const int DefaultUdpPort = 3610;
+
+ /// <inheritdoc/>
+ public override IPAddress? LocalAddress => throw new NotSupportedException(); // TODO
+
+ /// <inheritdoc/>
+ public override ISynchronizeInvoke? SynchronizingObject { get; set; }
+
+ public UdpEchonetLiteHandler(ILogger<UdpEchonetLiteHandler> logger)
+ {
+ selfAddresses = NetworkInterface.GetAllNetworkInterfaces().SelectMany(ni => ni.GetIPProperties().UnicastAddresses.Select(ua => ua.Address)).ToArray();
+
+ this.logger = logger;
+
+ try {
+ receiveUdpClient = new UdpClient(DefaultUdpPort) {
+ EnableBroadcast = true,
+ };
+ }
+ catch (Exception ex) {
+ this.logger.LogError(ex, $"unexpected exception occured while initialization");
+ throw;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing) {
+ try {
+ receiveUdpClient?.Close();
+ receiveUdpClient?.Dispose();
+ receiveUdpClient = null;
+ }
+#pragma warning disable CA1031
+ catch (Exception ex) {
+ logger.LogWarning(ex, $"unexpected exception occured while disposing {nameof(UdpClient)} for receiving");
+
+ // swallow all exceptions
+ }
+#pragma warning restore CA1031
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected override ValueTask DisposeAsyncCore()
+ {
+ try {
+ receiveUdpClient?.Close();
+ receiveUdpClient?.Dispose();
+ receiveUdpClient = null;
+ }
+#pragma warning disable CA1031
+ catch (Exception ex) {
+ logger.LogWarning(ex, $"unexpected exception occured while disposing {nameof(UdpClient)} for receiving");
+
+ // swallow all exceptions
+ }
+#pragma warning restore CA1031
+
+ return base.DisposeAsyncCore();
+ }
+
+ /// <inheritdoc/>
+ protected override async ValueTask<IPAddress> ReceiveAsyncCore(
+ IBufferWriter<byte> buffer,
+ CancellationToken cancellationToken
+ )
+ {
+ if (buffer is null)
+ throw new ArgumentNullException(nameof(buffer));
+ if (receiveUdpClient is null)
+ throw new ObjectDisposedException(GetType().FullName);
+
+ for (; ; ) {
+ var receivedResults = await receiveUdpClient
+#if SYSTEM_NET_SOCKETS_UDPCLIENT_RECEIVEASYNC_CANCELLATIONTOKEN
+ .ReceiveAsync(cancellationToken)
+#else
+ .ReceiveAsync()
+#endif
+ .ConfigureAwait(false);
+
+ if (selfAddresses.Contains(receivedResults.RemoteEndPoint.Address))
+ // ブロードキャストを自分で受信した(無視)
+ continue;
+
+ logger.LogDebug($"UDP受信:{receivedResults.RemoteEndPoint.Address} {BitConverter.ToString(receivedResults.Buffer)}");
+
+ buffer.Write(receivedResults.Buffer);
+
+ return receivedResults.RemoteEndPoint.Address;
+ }
+ }
+
+ private void LogSend(IPEndPoint remoteEndPoint, ReadOnlyMemory<byte> buffer)
+ {
+#if SYSTEM_CONVERT_TOHEXSTRING
+ logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {Convert.ToHexString(buffer.Span)}");
+#else
+ if (MemoryMarshal.TryGetArray(buffer, out var segment))
+ logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {BitConverter.ToString(segment.Array!, segment.Offset, segment.Count)}");
+ else
+ logger.LogDebug($"UDP送信:{remoteEndPoint.Address} {BitConverter.ToString(buffer.ToArray())}");
+#endif
+ }
+
+ /// <summary>
+ /// Performs multicast send.
+ /// </summary>
+ /// <param name="buffer">The <see cref="ReadOnlyMemory{Byte}"/> in which the data to be sent is written.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
+ protected override async ValueTask SendAsyncCore(
+ ReadOnlyMemory<byte> buffer,
+ CancellationToken cancellationToken
+ )
+ {
+ var remoteEndPoint = new IPEndPoint(IPAddress.Broadcast, DefaultUdpPort);
+
+ LogSend(remoteEndPoint, buffer);
+
+ using var udpClient = new UdpClient() {
+ EnableBroadcast = true,
+ };
+
+ udpClient.Connect(remoteEndPoint);
+
+#if SYSTEM_NET_SOCKETS_UDPCLIENT_SENDASYNC_READONLYMEMORY_OF_BYTE
+ await udpClient.SendAsync(buffer, cancellationToken).ConfigureAwait(false);
+#else
+ await udpClient.SendAsync(buffer.ToArray(), buffer.Length).ConfigureAwait(false);
+#endif
+
+ udpClient.Close();
+ }
+
+ /// <summary>
+ /// Performs unicast send to a specific remote address.
+ /// </summary>
+ /// <param name="remoteAddress">The <see cref="IPAddress"/> to which the data to be sent.</param>
+ /// <param name="buffer">The <see cref="ReadOnlyMemory{Byte}"/> in which the data to be sent is written.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
+ protected override async ValueTask SendToAsyncCore(
+ IPAddress remoteAddress,
+ ReadOnlyMemory<byte> buffer,
+ CancellationToken cancellationToken
+ )
+ {
+ var remoteEndPoint = new IPEndPoint(remoteAddress, DefaultUdpPort);
+
+ LogSend(remoteEndPoint, buffer);
+
+ using var udpClient = new UdpClient();
+
+ udpClient.Connect(remoteEndPoint);
+
+#if SYSTEM_NET_SOCKETS_UDPCLIENT_SENDASYNC_READONLYMEMORY_OF_BYTE
+ await udpClient.SendAsync(buffer, cancellationToken).ConfigureAwait(false);
+#else
+ await udpClient.SendAsync(buffer.ToArray(), buffer.Length).ConfigureAwait(false);
+#endif
+
+ udpClient.Close();
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.csproj b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.csproj
index bcc3a10..ffedcf7 100644
--- a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.csproj
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite.csproj
@@ -7,9 +7,8 @@ SPDX-License-Identifier: MIT
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net6.0;net8.0</TargetFrameworks>
- <VersionPrefix>1.0.0</VersionPrefix>
- <VersionSuffix></VersionSuffix>
- <LangVersion>10</LangVersion>
+ <VersionPrefix>2.0.0</VersionPrefix>
+ <VersionSuffix>preview1</VersionSuffix>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
@@ -19,9 +18,9 @@ SPDX-License-Identifier: MIT
<PropertyGroup Label="assembly attributes">
<Description>
-<![CDATA[Provides the implementation based on the specifications described in the "ECHONET Lite SPECIFICATION II ECHONET Lite Communication Middleware Specifications". Including APIs such as `EchoClient`, which is an implementation corresponding to the "Communication Middleware" in the specification, and `IEchonetLiteHandler`, which is the interface for implementing the communication endpoint to the "Lower Communication Layers".
+<![CDATA[Provides the implementation based on the specifications described in the "ECHONET Lite SPECIFICATION II ECHONET Lite Communication Middleware Specifications". Including APIs such as `EchonetClient`, which is an implementation corresponding to the "Communication Middleware" in the specification.
-「ECHONET Lite SPECIFICATION 第2部 ECHONET Lite 通信ミドルウェア仕様」に記載されている仕様に基づく実装を提供します。 同仕様書における「通信ミドルウェア」に相当する`EchoClient`、および「下位通信層」との通信エンドポイントを実装するためのインターフェース`IEchonetLiteHandler`などのAPIを提供します。]]>
+「ECHONET Lite SPECIFICATION 第2部 ECHONET Lite 通信ミドルウェア仕様」に記載されている仕様に基づく実装を提供します。 同仕様書における「通信ミドルウェア」に相当する`EchonetClient`などのAPIを提供します。]]>
</Description>
</PropertyGroup>
@@ -34,14 +33,10 @@ SPDX-License-Identifier: MIT
</ItemGroup>
<ItemGroup>
- <ProjectOrPackageReference ReferencePackageVersion="1.0.0" Include="..\Smdn.Net.EchonetLite.Specifications\Smdn.Net.EchonetLite.Specifications.csproj" />
+ <ProjectOrPackageReference ReferencePackageVersion="2.0.0-preview1" Include="..\Smdn.Net.EchonetLite.Appendix\Smdn.Net.EchonetLite.Appendix.csproj" />
+ <ProjectOrPackageReference ReferencePackageVersion="2.0.0-preview1" Include="..\Smdn.Net.EchonetLite.Transport\Smdn.Net.EchonetLite.Transport.csproj" />
</ItemGroup>
- <!-- disables code style/code analysis warnings configured by Smdn.MSBuild.ProjectAssets.Library -->
- <PropertyGroup>
- <NoWarn>CS3001;CS3003;CS3005;IL2026;SA1000;SA1001;SA1004;SA1005;SA1024;SA1106;SA1110;SA1113;SA1122;SA1137;SA1208;SA1210;SA1309;SA1313;SA1316;SA1402;SA1413;SA1414;SA1505;SA1507;SA1508;SA1513;SA1514;SA1614;SA1623;SA1629;SA1642;$(NoWarn)</NoWarn>
- </PropertyGroup>
-
<Target Name="GenerateReadmeFileContent">
<PropertyGroup>
<PackageReadmeFileContent><![CDATA[# $(PackageId) $(PackageVersion)
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Events.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Events.cs
new file mode 100644
index 0000000..c884101
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Events.cs
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+
+namespace Smdn.Net.EchonetLite;
+
+#pragma warning disable IDE0040
+partial class EchonetClient
+#pragma warning restore IDE0040
+{
+ /// <summary>
+ /// 新しいECHONET Lite ノードが発見されたときに発生するイベント。
+ /// </summary>
+ public event EventHandler<EchonetNode>? NodeJoined;
+
+ /// <summary>
+ /// インスタンスリスト通知の受信による更新を開始するときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// イベント引数には、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchonetNode"/>が設定されます。
+ /// </para>
+ /// <para>
+ /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
+ /// <list type="number">
+ /// <item><description><see cref="InstanceListUpdating"/></description></item>
+ /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
+ /// <item><description><see cref="InstanceListUpdated"/></description></item>
+ /// </list>
+ /// </para>
+ /// </remarks>
+ /// <seealso cref="InstanceListPropertyMapAcquiring"/>
+ /// <seealso cref="InstanceListUpdated"/>
+ public event EventHandler<EchonetNode>? InstanceListUpdating;
+
+ /// <summary>
+ /// インスタンスリスト通知を受信した際に、プロパティマップの取得を開始するときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// イベント引数には、<see cref="ValueTuple{T1,T2}"/>が設定されます。
+ /// イベント引数は、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchonetNode"/>、
+ /// および通知されたインスタンスリストを表す<see cref="IReadOnlyList{EchonetObject}"/>を保持します。
+ /// </para>
+ /// <para>
+ /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
+ /// <list type="number">
+ /// <item><description><see cref="InstanceListUpdating"/></description></item>
+ /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
+ /// <item><description><see cref="InstanceListUpdated"/></description></item>
+ /// </list>
+ /// </para>
+ /// </remarks>
+ /// <seealso cref="InstanceListUpdating"/>
+ /// <seealso cref="InstanceListUpdated"/>
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListPropertyMapAcquiring;
+
+ /// <summary>
+ /// インスタンスリスト通知の受信による更新が完了したときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// イベント引数には、<see cref="ValueTuple{EchonetNode,T2}"/>が設定されます。
+ /// イベント引数は、インスタンスリスト通知の送信元のECHONET Lite ノードを表す<see cref="EchonetNode"/>、
+ /// および通知されたインスタンスリストを表す<see cref="IReadOnlyList{EchonetObject}"/>を保持します。
+ /// </para>
+ /// <para>
+ /// インスタンスリスト通知を受信した場合、以下の順でイベントが発生します。
+ /// <list type="number">
+ /// <item><description><see cref="InstanceListUpdating"/></description></item>
+ /// <item><description><see cref="InstanceListPropertyMapAcquiring"/></description></item>
+ /// <item><description><see cref="InstanceListUpdated"/></description></item>
+ /// </list>
+ /// </para>
+ /// </remarks>
+ /// <seealso cref="InstanceListUpdating"/>
+ /// <seealso cref="InstanceListPropertyMapAcquiring"/>
+ public event EventHandler<(EchonetNode, IReadOnlyList<EchonetObject>)>? InstanceListUpdated;
+
+ protected virtual void OnInstanceListUpdating(EchonetNode node)
+ => InstanceListUpdating?.Invoke(this, node);
+
+ protected virtual void OnInstanceListPropertyMapAcquiring(EchonetNode node, IReadOnlyList<EchonetObject> instances)
+ => InstanceListPropertyMapAcquiring?.Invoke(this, (node, instances));
+
+ protected virtual void OnInstanceListUpdated(EchonetNode node, IReadOnlyList<EchonetObject> instances)
+ => InstanceListUpdated?.Invoke(this, (node, instances));
+
+ /// <summary>
+ /// プロパティマップの取得を開始するときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// イベント引数には、<see cref="ValueTuple{EchonetNode,EchonetObject}"/>が設定されます。
+ /// イベント引数は、対象オブジェクトが属するECHONET Lite ノードを表す<see cref="EchonetNode"/>、
+ /// およびプロパティマップ取得対象のECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>を保持します。
+ /// </remarks>
+ /// <seealso cref="PropertyMapAcquired"/>
+ /// <seealso cref="EchonetObject.HasPropertyMapAcquired"/>
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquiring;
+
+ /// <summary>
+ /// プロパティマップの取得を完了したときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// イベント引数には、<see cref="ValueTuple{EchonetNode,EchonetObject}"/>が設定されます。
+ /// イベント引数は、対象オブジェクトが属するECHONET Lite ノードを表す<see cref="EchonetNode"/>、
+ /// およびプロパティマップ取得対象のECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>を保持します。
+ /// </remarks>
+ /// <seealso cref="PropertyMapAcquiring"/>
+ /// <seealso cref="EchonetObject.HasPropertyMapAcquired"/>
+ public event EventHandler<(EchonetNode, EchonetObject)>? PropertyMapAcquired;
+
+ protected virtual void OnPropertyMapAcquiring(EchonetNode node, EchonetObject device)
+ => PropertyMapAcquiring?.Invoke(this, (node, device));
+
+ protected virtual void OnPropertyMapAcquired(EchonetNode node, EchonetObject device)
+ => PropertyMapAcquired?.Invoke(this, (node, device));
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.LowerLayer.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.LowerLayer.cs
new file mode 100644
index 0000000..eee3417
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.LowerLayer.cs
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1848 // CA1848: パフォーマンスを向上させるには、LoggerMessage デリゲートを使用します -->
+#pragma warning disable CA2254 // CA2254: ログ メッセージ テンプレートは、LoggerExtensions.Log****(ILogger, string?, params object?[])' への呼び出しによって異なるべきではありません。 -->
+
+using System;
+using System.Buffers;
+using System.Net;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+using Smdn.Net.EchonetLite.Protocol;
+using Smdn.Net.EchonetLite.Serialization.Json;
+
+namespace Smdn.Net.EchonetLite;
+
+#pragma warning disable IDE0040
+partial class EchonetClient
+#pragma warning restore IDE0040
+{
+ /// <summary>
+ /// 送信するECHONET Lite フレームを書き込むバッファ。
+ /// <see cref="echonetLiteHandler"/>によって送信する内容を書き込むために使用する。
+ /// </summary>
+ private readonly ArrayBufferWriter<byte> requestFrameBuffer = new(initialCapacity: 0x100);
+
+ /// <summary>
+ /// ECHONET Lite フレームのリクエスト送信時の排他区間を定義するセマフォ。
+ /// <see cref="requestFrameBuffer"/>への書き込み、および<see cref="echonetLiteHandler"/>による送信を排他制御するために使用する。
+ /// </summary>
+ private SemaphoreSlim requestSemaphore = new(initialCount: 1, maxCount: 1);
+
+ /// <summary>
+ /// <see cref="IEchonetLiteHandler.Received"/>イベントにてECHONET Lite フレームを受信した場合に発生するイベント。
+ /// ECHONET Lite ノードに対して送信されてくる要求を処理するほか、他ノードに対する要求への応答を待機する場合にも使用する。
+ /// </summary>
+ private event EventHandler<(IPAddress, Frame)>? FrameReceived;
+
+ private ushort tid;
+
+ /// <summary>
+ /// ECHONET Lite フレームの新しいトランザクションID(TID)を生成して取得します。
+ /// </summary>
+ /// <returns>新しいトランザクションID。</returns>
+ private ushort GetNewTid()
+ {
+ return ++tid;
+ }
+
+ /// <summary>
+ /// イベント<see cref="IEchonetLiteHandler.Received"/>をハンドルするメソッドを実装します。
+ /// </summary>
+ /// <remarks>
+ /// 受信したデータがECHONET Lite フレームの場合は、イベント<see cref="FrameReceived"/>をトリガします。
+ /// それ以外の場合は、無視して処理を中断します。
+ /// </remarks>
+ /// <param name="sender">イベントのソース。</param>
+ /// <param name="value">
+ /// イベントデータを格納している<see cref="ValueTuple{T1,T2}"/>。
+ /// データの送信元を表す<see cref="IPAddress"/>と、受信したデータを表す<see cref="ReadOnlyMemory{Byte}"/>を保持します。
+ /// </param>
+ private void EchonetDataReceived(object? sender, (IPAddress Address, ReadOnlyMemory<byte> Data) value)
+ {
+ if (!FrameSerializer.TryDeserialize(value.Data.Span, out var frame))
+ // ECHONETLiteフレームではないため無視
+ return;
+
+ logger?.LogTrace($"Echonet Lite Frame受信: address:{value.Address}\r\n,{JsonSerializer.Serialize(frame, JsonSerializerSourceGenerationContext.Default.Frame)}");
+
+ FrameReceived?.Invoke(this, (value.Address, frame));
+ }
+
+ /// <summary>
+ /// ECHONET Lite フレームを送信します。
+ /// </summary>
+ /// <param name="address">送信先となるECHONET Lite ノードの<see cref="IPAddress"/>。 <see langword="null"/>の場合は、サブネット内のすべてのノードに対して一斉同報送信を行います。</param>
+ /// <param name="writeFrame">
+ /// 送信するECHONET Lite フレームをバッファへ書き込むための<see cref="Action{T}"/>デリゲート。
+ /// 呼び出し元は、送信するECHONET Lite フレームを、引数として渡される<see cref="IBufferWriter{Byte}"/>に書き込む必要があります。
+ /// </param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。</param>
+ /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
+ /// <exception cref="ObjectDisposedException">オブジェクトはすでに破棄されています。</exception>
+ private async ValueTask SendFrameAsync(IPAddress? address, Action<IBufferWriter<byte>> writeFrame, CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
+
+ await requestSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try {
+ writeFrame(requestFrameBuffer);
+
+ if (logger is not null && logger.IsEnabled(LogLevel.Trace)) {
+ if (FrameSerializer.TryDeserialize(requestFrameBuffer.WrittenSpan, out var frame)) {
+ logger.LogTrace($"Echonet Lite Frame送信: address:{address}\r\n,{JsonSerializer.Serialize(frame, JsonSerializerSourceGenerationContext.Default.Frame)}");
+ }
+#if DEBUG
+ else {
+ throw new InvalidOperationException("attempted to request an invalid format of frame");
+ }
+#endif
+ }
+
+ await echonetLiteHandler.SendAsync(address, requestFrameBuffer.WrittenMemory, cancellationToken).ConfigureAwait(false);
+ }
+ finally {
+ // reset written count to reuse the buffer for the next write
+#if SYSTEM_BUFFERS_ARRAYBUFFERWRITER_RESETWRITTENCOUNT
+ requestFrameBuffer.ResetWrittenCount();
+#else
+ requestFrameBuffer.Clear();
+#endif
+ requestSemaphore.Release();
+ }
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Services.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Services.cs
new file mode 100644
index 0000000..7ec7360
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.Services.cs
@@ -0,0 +1,1749 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+#pragma warning disable CA1848 // CA1848: パフォーマンスを向上させるには、LoggerMessage デリゲートを使用します -->
+#pragma warning disable CA2254 // CA2254: ログ メッセージ テンプレートは、LoggerExtensions.Log****(ILogger, string?, params object?[])' への呼び出しによって異なるべきではありません。 -->
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+using Smdn.Net.EchonetLite.Protocol;
+
+namespace Smdn.Net.EchonetLite;
+
+#pragma warning disable IDE0040
+partial class EchonetClient
+#pragma warning restore IDE0040
+{
+ /// <summary>
+ /// 指定された時間でタイムアウトする<see cref="CancellationTokenSource"/>を作成します。
+ /// </summary>
+ /// <param name="timeoutMilliseconds">
+ /// ミリ秒単位でのタイムアウト時間。
+ /// 値が<see cref="Timeout.Infinite"/>に等しい場合は、タイムアウトしない<see cref="CancellationTokenSource"/>を返します。
+ /// </param>
+ /// <exception cref="ArgumentOutOfRangeException"><paramref name="timeoutMilliseconds"/>に負の値を指定することはできません。</exception>
+ private static CancellationTokenSource CreateTimeoutCancellationTokenSource(int timeoutMilliseconds)
+ {
+ if (0 > timeoutMilliseconds)
+ throw new ArgumentOutOfRangeException(message: "タイムアウト時間に負の値を指定することはできません。", actualValue: timeoutMilliseconds, paramName: nameof(timeoutMilliseconds));
+
+ if (timeoutMilliseconds == Timeout.Infinite)
+ return new CancellationTokenSource();
+
+ return new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutMilliseconds));
+ }
+
+ private static PropertyRequest ConvertToPropertyRequest(EchonetProperty p)
+ => new(epc: p.Spec.Code, edt: p.ValueMemory);
+
+ private static PropertyRequest ConvertToPropertyRequestExceptValueData(EchonetProperty p)
+ => new(epc: p.Spec.Code);
+
+ private class PropertyCapability {
+ public bool Anno { get; set; }
+ public bool Set { get; set; }
+ public bool Get { get; set; }
+ }
+
+ /// <summary>
+ /// インスタンスリスト通知を行います。
+ /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)を設定し、ECHONET Lite サービス「INF:プロパティ値通知」(ESV <c>0x73</c>)を送信します。
+ /// </summary>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.3.1 ECHONET Lite ノードスタート時の基本シーケンス
+ /// </seealso>
+ public async ValueTask PerformInstanceListNotificationAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ // インスタンスリスト通知プロパティ
+ var property = SelfNode.NodeProfile.AnnoProperties.First(p => p.Spec.Code == 0xD5);
+
+ property.WriteValue(writer => {
+ var contents = writer.GetSpan(253); // インスタンスリスト通知 0xD5 unsigned char×(MAX)253
+
+ _ = PropertyContentSerializer.TrySerializeInstanceListNotification(
+ SelfNode.Devices.Select(static o => o.EOJ),
+ contents,
+ out var bytesWritten
+ );
+
+ writer.Advance(bytesWritten);
+ });
+
+ // インスタンスリスト通知
+ await PerformPropertyValueNotificationAsync(
+ SelfNode.NodeProfile, // ノードプロファイルから
+ null, // 一斉通知
+ new(
+ new EOJ(
+ classGroupCode: Profiles.NodeProfile.ClassGroup.Code,
+ classCode: Profiles.NodeProfile.Class.Code,
+ instanceCode: 0x01
+ )
+ ),
+ Enumerable.Repeat(property, 1),
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// インスタンスリスト通知要求を行います。
+ /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)に対するECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を送信します。
+ /// </summary>
+ /// <param name="onInstanceListPropertyMapAcquiring">
+ /// インスタンスリスト受信後・プロパティマップの取得前に呼び出されるコールバックを表すデリゲートを指定します。
+ /// このコールバックが<see langword="true"/>を返す場合、結果を確定して処理を終了します。 <see langword="false"/>の場合、処理を継続します。
+ /// </param>
+ /// <param name="onInstanceListUpdated">
+ /// インスタンスリスト受信後・プロパティマップの取得完了後に呼び出されるコールバックを表すデリゲートを指定します。
+ /// このコールバックが<see langword="true"/>を返す場合、結果を確定して処理を終了します。 <see langword="false"/>の場合、処理を継続します。
+ /// </param>
+ /// <param name="onPropertyMapAcquired">
+ /// ノードの各インスタンスに対するプロパティマップの取得完了後に呼び出されるコールバックを表すデリゲートを指定します。
+ /// このコールバックが<see langword="true"/>を返す場合、結果を確定して処理を終了します。 <see langword="false"/>の場合、処理を継続します。
+ /// </param>
+ /// <param name="state">各コールバックに共通して渡される状態変数を指定します。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <typeparam name="TState">各コールバックに共通して渡される状態変数<paramref name="state"/>の型を指定します。</typeparam>
+ /// <returns>非同期の操作を表す<see cref="Task"/>。</returns>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.1 サービス内容に関する基本シーケンス (C)通知要求受信時の基本シーケンス
+ /// </seealso>
+ public async Task PerformInstanceListNotificationRequestAsync<TState>(
+ Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListPropertyMapAcquiring,
+ Func<EchonetClient, EchonetNode, TState, bool>? onInstanceListUpdated,
+ Func<EchonetClient, EchonetNode, EchonetObject, TState, bool>? onPropertyMapAcquired,
+ TState state,
+ CancellationToken cancellationToken = default
+ )
+ {
+ const bool RetVoid = default;
+
+ var tcs = new TaskCompletionSource<bool>();
+
+ // インスタンスリスト受信後・プロパティマップの取得前に発生するイベントをハンドリングする
+ void HandleInstanceListPropertyMapAcquiring(object? sender, (EchonetNode Node, IReadOnlyList<EchonetObject> Instances) e)
+ {
+ // この時点で条件がtrueとなったら、結果を確定する
+ if (onInstanceListPropertyMapAcquiring(this, e.Node, state))
+ _ = tcs.TrySetResult(RetVoid);
+ }
+
+ // インスタンスリスト受信後・プロパティマップの取得完了後に発生するイベントをハンドリングする
+ void HandleInstanceListUpdated(object? sender, (EchonetNode Node, IReadOnlyList<EchonetObject> Instances) e)
+ {
+ // この時点で条件がtrueとなったら、結果を確定する
+ if (onInstanceListUpdated(this, e.Node, state))
+ _ = tcs.TrySetResult(RetVoid);
+ }
+
+ // ノードの各インスタンスに対するプロパティマップの取得完了後に発生するイベントをハンドリングする
+ void HandlePropertyMapAcquired(object? sender, (EchonetNode Node, EchonetObject Device) e)
+ {
+ // この時点で条件がtrueとなったら、結果を確定する
+ if (onPropertyMapAcquired(this, e.Node, e.Device, state))
+ _ = tcs.TrySetResult(RetVoid);
+ }
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = tcs.TrySetCanceled(cancellationToken));
+
+ if (onInstanceListPropertyMapAcquiring is not null)
+ InstanceListPropertyMapAcquiring += HandleInstanceListPropertyMapAcquiring;
+ if (onInstanceListUpdated is not null)
+ InstanceListUpdated += HandleInstanceListUpdated;
+ if (onPropertyMapAcquired is not null)
+ PropertyMapAcquired += HandlePropertyMapAcquired;
+
+ await PerformInstanceListNotificationRequestAsync(cancellationToken).ConfigureAwait(false);
+
+ // イベントの発生およびコールバックの処理を待機する
+ _ = await tcs.Task.ConfigureAwait(false);
+ }
+ finally {
+ if (onInstanceListPropertyMapAcquiring is not null)
+ InstanceListPropertyMapAcquiring -= HandleInstanceListPropertyMapAcquiring;
+ if (onInstanceListUpdated is not null)
+ InstanceListUpdated -= HandleInstanceListUpdated;
+ if (onPropertyMapAcquired is not null)
+ PropertyMapAcquired -= HandlePropertyMapAcquired;
+ }
+ }
+
+ /// <summary>
+ /// インスタンスリスト通知要求を行います。
+ /// ECHONETプロパティ「インスタンスリスト通知」(EPC <c>0xD5</c>)に対するECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を送信します。
+ /// </summary>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>非同期の操作を表す<see cref="ValueTask"/>。</returns>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.1 サービス内容に関する基本シーケンス (C)通知要求受信時の基本シーケンス
+ /// </seealso>
+ public async ValueTask PerformInstanceListNotificationRequestAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ var propsInstanceListNotification = Enumerable.Repeat(
+ new EchonetProperty(
+ Profiles.NodeProfile.ClassGroup.Code,
+ Profiles.NodeProfile.Class.Code,
+ 0xD5 // インスタンスリスト通知
+ ),
+ 1
+ );
+
+ await PerformPropertyValueNotificationRequestAsync(
+ SelfNode.NodeProfile, // ノードプロファイルから
+ null, // 一斉通知
+ new(
+ new EOJ(
+ classGroupCode: Profiles.NodeProfile.ClassGroup.Code,
+ classCode: Profiles.NodeProfile.Class.Code,
+ instanceCode: 0x01
+ )
+ ),
+ propsInstanceListNotification,
+ cancellationToken
+ ).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetI:プロパティ値書き込み要求(応答不要)」(ESV <c>0x60</c>)を行います。 このサービスは一斉同報が可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="Task{T}"/>。
+ /// 書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.1 プロパティ値書き込みサービス(応答不要)[0x60, 0x50]
+ /// </seealso>
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueWriteRequestAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ var responseTCS = new TaskCompletionSource<IReadOnlyCollection<PropertyRequest>>();
+
+ void HandleFrameSetISNA(object? sender, (IPAddress Address, Frame Frame) value)
+ {
+ try {
+ if (cancellationToken.IsCancellationRequested) {
+ _ = responseTCS.TrySetCanceled(cancellationToken);
+ return;
+ }
+
+ if (destinationNode is not null && !destinationNode.Address.Equals(value.Address))
+ return;
+ if (value.Frame.EData is not EData1 edata)
+ return;
+ if (edata.SEOJ != destinationObject.EOJ)
+ return;
+ if (edata.ESV != ESV.SetIServiceNotAvailable)
+ return;
+
+ var opcList = edata.GetOPCList();
+
+ foreach (var prop in opcList) {
+ // 一部成功した書き込みを反映
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
+
+ if (prop.PDC == 0x00) {
+ // 書き込み成功
+ target.SetValue(properties.First(p => p.Spec.Code == prop.EPC).ValueMemory);
+ }
+ }
+
+ responseTCS.SetResult(opcList);
+
+ // TODO 一斉通知の不可応答の扱いが…
+ }
+ finally {
+ FrameReceived -= HandleFrameSetISNA;
+ }
+ }
+
+ FrameReceived += HandleFrameSetISNA;
+
+ await SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.SetI,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
+ ),
+ cancellationToken
+ ).ConfigureAwait(false);
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
+
+ return await responseTCS.Task.ConfigureAwait(false);
+ }
+ catch (Exception ex) {
+ if (ex is OperationCanceledException exOperationCanceled && cancellationToken.Equals(exOperationCanceled.CancellationToken)) {
+ foreach (var prop in properties) {
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.Spec.Code);
+ // 成功した書き込みを反映(全部OK)
+ target.SetValue(prop.ValueMemory);
+ }
+ }
+
+ FrameReceived -= HandleFrameSetISNA;
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetC:プロパティ値書き込み要求(応答要)」(ESV <c>0x61</c>)を行います。 このサービスは一斉同報が可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="Task{T}"/>。
+ /// 成功応答(Set_Res <c>0x71</c>)の場合は<see langword="true"/>、不可応答(SetC_SNA <c>0x51</c>)その他の場合は<see langword="false"/>を返します。
+ /// また、書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.2 プロパティ値書き込みサービス(応答要)[0x61,0x71,0x51]
+ /// </seealso>
+ public async Task<(
+ bool Result,
+ IReadOnlyCollection<PropertyRequest> Properties
+ )>
+ PerformPropertyValueWriteRequestResponseRequiredAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>)>();
+
+ void HandleFrameSetResOrSetCSNA(object? sender_, (IPAddress Address, Frame Frame) value)
+ {
+ try {
+ if (cancellationToken.IsCancellationRequested) {
+ _ = responseTCS.TrySetCanceled(cancellationToken);
+ return;
+ }
+
+ if (destinationNode is not null && !destinationNode.Address.Equals(value.Address))
+ return;
+ if (value.Frame.EData is not EData1 edata)
+ return;
+ if (edata.SEOJ != destinationObject.EOJ)
+ return;
+ if (edata.ESV != ESV.SetCServiceNotAvailable && edata.ESV != ESV.SetResponse)
+ return;
+
+ var opcList = edata.GetOPCList();
+
+ foreach (var prop in opcList) {
+ // 成功した書き込みを反映
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
+
+ if (prop.PDC == 0x00) {
+ // 書き込み成功
+ target.SetValue(properties.First(p => p.Spec.Code == prop.EPC).ValueMemory);
+ }
+ }
+
+ responseTCS.SetResult((edata.ESV == ESV.SetResponse, opcList));
+
+ // TODO 一斉通知の応答の扱いが…
+ }
+ finally {
+ FrameReceived -= HandleFrameSetResOrSetCSNA;
+ }
+ }
+
+ FrameReceived += HandleFrameSetResOrSetCSNA;
+
+ await SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.SetC,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
+ ),
+ cancellationToken
+ ).ConfigureAwait(false);
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
+
+ return await responseTCS.Task.ConfigureAwait(false);
+ }
+ catch {
+ FrameReceived -= HandleFrameSetResOrSetCSNA;
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「Get:プロパティ値読み出し要求」(ESV <c>0x62</c>)を行います。 このサービスは一斉同報が可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="Task{T}"/>。
+ /// 成功応答(Get_Res <c>0x72</c>)の場合は<see langword="true"/>、不可応答(Get_SNA <c>0x52</c>)その他の場合は<see langword="false"/>を返します。
+ /// また、書き込みに成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.3 プロパティ値読み出しサービス[0x62,0x72,0x52]
+ /// </seealso>
+ public async Task<(
+ bool Result,
+ IReadOnlyCollection<PropertyRequest> Properties
+ )>
+ PerformPropertyValueReadRequestAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>)>();
+
+ void HandleFrameGetResOrGetSNA(object? sender, (IPAddress Address, Frame Frame) value)
+ {
+ try {
+ if (cancellationToken.IsCancellationRequested) {
+ _ = responseTCS.TrySetCanceled(cancellationToken);
+ return;
+ }
+
+ if (destinationNode is not null && !destinationNode.Address.Equals(value.Address))
+ return;
+ if (value.Frame.EData is not EData1 edata)
+ return;
+ if (edata.SEOJ != destinationObject.EOJ)
+ return;
+ if (edata.ESV != ESV.GetResponse && edata.ESV != ESV.GetServiceNotAvailable)
+ return;
+
+ var opcList = edata.GetOPCList();
+
+ foreach (var prop in opcList) {
+ // 成功した読み込みを反映
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
+ if (prop.PDC != 0x00) {
+ // 読み込み成功
+ target.SetValue(prop.EDT);
+ }
+ }
+
+ responseTCS.SetResult((edata.ESV == ESV.GetResponse, opcList));
+
+ // TODO 一斉通知の応答の扱いが…
+ }
+ finally {
+ FrameReceived -= HandleFrameGetResOrGetSNA;
+ }
+ }
+
+ FrameReceived += HandleFrameGetResOrGetSNA;
+
+ await SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.Get,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequestExceptValueData)
+ ),
+ cancellationToken
+ ).ConfigureAwait(false);
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
+
+ return await responseTCS.Task.ConfigureAwait(false);
+ }
+ catch {
+ FrameReceived -= HandleFrameGetResOrGetSNA;
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetGet:プロパティ値書き込み・読み出し要求」(ESV <c>0x6E</c>)を行います。 このサービスは一斉同報が可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="propertiesSet">書き込み対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="propertiesGet">読み出し対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="Task{T}"/>。
+ /// 成功応答(SetGet_Res <c>0x7E</c>)の場合は<see langword="true"/>、不可応答(SetGet_SNA <c>0x5E</c>)その他の場合は<see langword="false"/>を返します。
+ /// また、処理に成功したプロパティを書き込み対象プロパティ・読み出し対象プロパティの順にて<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="propertiesSet"/>が<see langword="null"/>です。
+ /// または、<paramref name="propertiesGet"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ /// </seealso>
+ public async Task<(
+ bool Result,
+ IReadOnlyCollection<PropertyRequest> PropertiesSet,
+ IReadOnlyCollection<PropertyRequest> PropertiesGet
+ )>
+ PerformPropertyValueWriteReadRequestAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> propertiesSet,
+ IEnumerable<EchonetProperty> propertiesGet,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (propertiesSet is null)
+ throw new ArgumentNullException(nameof(propertiesSet));
+ if (propertiesGet is null)
+ throw new ArgumentNullException(nameof(propertiesGet));
+
+ var responseTCS = new TaskCompletionSource<(bool, IReadOnlyCollection<PropertyRequest>, IReadOnlyCollection<PropertyRequest>)>();
+
+ void HandleFrameSetGetResOrSetGetSNA(object? sender_, (IPAddress Address, Frame Frame) value)
+ {
+ try {
+ if (cancellationToken.IsCancellationRequested) {
+ _ = responseTCS.TrySetCanceled(cancellationToken);
+ return;
+ }
+
+ if (destinationNode is not null && !destinationNode.Address.Equals(value.Address))
+ return;
+ if (value.Frame.EData is not EData1 edata)
+ return;
+ if (edata.SEOJ != destinationObject.EOJ)
+ return;
+ if (edata.ESV != ESV.SetGetResponse && edata.ESV != ESV.SetGetServiceNotAvailable)
+ return;
+
+ var (opcSetList, opcGetList) = edata.GetOPCSetGetList();
+
+ foreach (var prop in opcSetList) {
+ // 成功した書き込みを反映
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
+
+ if (prop.PDC == 0x00) {
+ // 書き込み成功
+ target.SetValue(propertiesSet.First(p => p.Spec.Code == prop.EPC).ValueMemory);
+ }
+ }
+
+ foreach (var prop in opcGetList) {
+ // 成功した読み込みを反映
+ var target = destinationObject.Properties.First(p => p.Spec.Code == prop.EPC);
+
+ if (prop.PDC != 0x00) {
+ // 読み込み成功
+ target.SetValue(prop.EDT);
+ }
+ }
+
+ responseTCS.SetResult((edata.ESV == ESV.SetGetResponse, opcSetList, opcGetList));
+
+ // TODO 一斉通知の応答の扱いが…
+ }
+ finally {
+ FrameReceived -= HandleFrameSetGetResOrSetGetSNA;
+ }
+ }
+
+ FrameReceived += HandleFrameSetGetResOrSetGetSNA;
+
+ await SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.SetGet,
+ opcListOrOpcSetList: propertiesSet.Select(ConvertToPropertyRequest),
+ opcGetList: propertiesGet.Select(ConvertToPropertyRequestExceptValueData)
+ ),
+ cancellationToken
+ ).ConfigureAwait(false);
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
+
+ return await responseTCS.Task.ConfigureAwait(false);
+ }
+ catch {
+ FrameReceived -= HandleFrameSetGetResOrSetGetSNA;
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を行います。 このサービスは一斉同報が可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="ValueTask"/>。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
+ /// </seealso>
+ public ValueTask PerformPropertyValueNotificationRequestAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ return SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.InfRequest,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequestExceptValueData)
+ ),
+ cancellationToken
+ );
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「INF:プロパティ値通知」(ESV <c>0x73</c>)を行います。 このサービスは個別通知・一斉同報通知ともに可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。 <see langword="null"/>の場合、一斉同報通知を行います。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="ValueTask"/>。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
+ /// </seealso>
+ public ValueTask PerformPropertyValueNotificationAsync(
+ EchonetObject sourceObject,
+ EchonetNode? destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ return SendFrameAsync(
+ destinationNode?.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.Inf,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
+ ),
+ cancellationToken
+ );
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「INFC:プロパティ値通知(応答要)」(ESV <c>0x74</c>)を行います。 このサービスは個別通知のみ可能です。
+ /// </summary>
+ /// <param name="sourceObject">送信元ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="destinationNode">相手先ECHONET Lite ノードを表す<see cref="EchonetNode"/>。</param>
+ /// <param name="destinationObject">相手先ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <param name="properties">処理対象のECHONET Lite プロパティとなる<see cref="IEnumerable{EchonetProperty}"/>。</param>
+ /// <param name="cancellationToken">キャンセル要求を監視するためのトークン。 既定値は<see cref="CancellationToken.None"/>です。</param>
+ /// <returns>
+ /// 非同期の操作を表す<see cref="Task{T}"/>。
+ /// 通知に成功したプロパティを<see cref="IReadOnlyCollection{PropertyRequest}"/>で返します。
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="sourceObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationNode"/>が<see langword="null"/>です。
+ /// または、<paramref name="destinationObject"/>が<see langword="null"/>です。
+ /// または、<paramref name="properties"/>が<see langword="null"/>です。
+ /// </exception>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.6 プロパティ値通知(応答要)サービス[0x74, 0x7A]
+ /// </seealso>
+ public async Task<IReadOnlyCollection<PropertyRequest>> PerformPropertyValueNotificationResponseRequiredAsync(
+ EchonetObject sourceObject,
+ EchonetNode destinationNode,
+ EchonetObject destinationObject,
+ IEnumerable<EchonetProperty> properties,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (sourceObject is null)
+ throw new ArgumentNullException(nameof(sourceObject));
+ if (destinationNode is null)
+ throw new ArgumentNullException(nameof(destinationNode));
+ if (destinationObject is null)
+ throw new ArgumentNullException(nameof(destinationObject));
+ if (properties is null)
+ throw new ArgumentNullException(nameof(properties));
+
+ var responseTCS = new TaskCompletionSource<IReadOnlyCollection<PropertyRequest>>();
+
+ void HandleFrameINFCRes(object? sender, (IPAddress Address, Frame Frame) value)
+ {
+ try {
+ if (cancellationToken.IsCancellationRequested) {
+ _ = responseTCS.TrySetCanceled(cancellationToken);
+ return;
+ }
+
+ if (!destinationNode.Address.Equals(value.Address))
+ return;
+ if (value.Frame.EData is not EData1 edata)
+ return;
+ if (edata.SEOJ != destinationObject.EOJ)
+ return;
+ if (edata.ESV != ESV.InfCResponse)
+ return;
+
+ responseTCS.SetResult(edata.GetOPCList());
+ }
+ finally {
+ FrameReceived -= HandleFrameINFCRes;
+ }
+ }
+
+ FrameReceived += HandleFrameINFCRes;
+
+ await SendFrameAsync(
+ destinationNode.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: GetNewTid(),
+ sourceObject: sourceObject.EOJ,
+ destinationObject: destinationObject.EOJ,
+ esv: ESV.InfC,
+ opcListOrOpcSetList: properties.Select(ConvertToPropertyRequest)
+ ),
+ cancellationToken
+ ).ConfigureAwait(false);
+
+ try {
+ using var ctr = cancellationToken.Register(() => _ = responseTCS.TrySetCanceled(cancellationToken));
+
+ return await responseTCS.Task.ConfigureAwait(false);
+ }
+ catch {
+ FrameReceived -= HandleFrameINFCRes;
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// インスタンスリスト通知受信時の処理を行います。
+ /// </summary>
+ /// <param name="sourceNode">送信元のECHONET Lite ノードを表す<see cref="EchonetNode"/>。</param>
+ /// <param name="edt">受信したインスタンスリスト通知を表す<see cref="ReadOnlySpan{Byte}"/>。</param>
+ /// <seealso cref="PerformInstanceListNotificationAsync"/>
+ /// <seealso cref="AcquirePropertyMapsAsync"/>
+ private async ValueTask HandleInstanceListNotificationReceivedAsync(EchonetNode sourceNode, ReadOnlyMemory<byte> edt)
+ {
+ logger?.LogTrace("インスタンスリスト通知を受信しました");
+
+ if (!PropertyContentSerializer.TryDeserializeInstanceListNotification(edt.Span, out var instanceList))
+ return; // XXX
+
+ OnInstanceListUpdating(sourceNode);
+
+ var instances = new List<EchonetObject>(capacity: instanceList.Count);
+
+ foreach (var eoj in instanceList) {
+ var device = sourceNode.Devices.FirstOrDefault(d => d.EOJ == eoj);
+
+ if (device is null) {
+ device = new(eoj);
+ sourceNode.Devices.Add(device);
+ }
+
+ instances.Add(device);
+ }
+
+ OnInstanceListPropertyMapAcquiring(sourceNode, instances);
+
+ foreach (var device in instances) {
+ if (!device.HasPropertyMapAcquired) {
+ logger?.LogTrace($"{device.GetDebugString()} プロパティマップを読み取ります");
+ await AcquirePropertyMapsAsync(sourceNode, device).ConfigureAwait(false);
+ }
+ }
+
+ if (!sourceNode.NodeProfile.HasPropertyMapAcquired) {
+ logger?.LogTrace($"{sourceNode.NodeProfile.GetDebugString()} プロパティマップを読み取ります");
+ await AcquirePropertyMapsAsync(sourceNode, sourceNode.NodeProfile).ConfigureAwait(false);
+ }
+
+ OnInstanceListUpdated(sourceNode, instances);
+ }
+
+ /// <summary>
+ /// 指定されたECHONET Lite オブジェクトに対して、ECHONETプロパティ「状変アナウンスプロパティマップ」(EPC <c>0x9D</c>)・
+ /// 「Set プロパティマップ」(EPC <c>0x9E</c>)・「Get プロパティマップ」(EPC <c>0x9F</c>)の読み出しを行います。
+ /// </summary>
+ /// <param name="sourceNode">対象のECHONET Lite ノードを表す<see cref="EchonetNode"/>。</param>
+ /// <param name="device">対象のECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。</param>
+ /// <exception cref="InvalidOperationException">受信したEDTは無効なプロパティマップです。</exception>
+ /// <seealso cref="HandleInstanceListNotificationReceivedAsync"/>
+ private async ValueTask AcquirePropertyMapsAsync(EchonetNode sourceNode, EchonetObject device)
+ {
+ OnPropertyMapAcquiring(sourceNode, device); // TODO: support setting cancel and timeout
+
+ using var ctsTimeout = CreateTimeoutCancellationTokenSource(20_000);
+
+ bool result;
+ IReadOnlyCollection<PropertyRequest> props;
+
+ try {
+ (result, props) = await PerformPropertyValueReadRequestAsync(
+ sourceObject: SelfNode.NodeProfile,
+ destinationNode: sourceNode,
+ destinationObject: device,
+ properties: device.Properties.Where(static p =>
+ p.Spec.Code == 0x9D || // 状変アナウンスプロパティマップ
+ p.Spec.Code == 0x9E || // Set プロパティマップ
+ p.Spec.Code == 0x9F // Get プロパティマップ
+ ),
+ cancellationToken: ctsTimeout.Token
+ ).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException ex) when (ctsTimeout.Token.Equals(ex.CancellationToken)) {
+ logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りがタイムアウトしました");
+ return;
+ }
+
+ // 不可応答は無視
+ if (!result) {
+ logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りで不可応答が返答されました");
+ return;
+ }
+
+ logger?.LogTrace($"{device.GetDebugString()} プロパティマップの読み取りが成功しました");
+
+ var propertyCapabilityMap = new Dictionary<byte, PropertyCapability>(capacity: 16);
+
+ foreach (var pr in props) {
+ switch (pr.EPC) {
+ // 状変アナウンスプロパティマップ
+ case 0x9D: {
+ if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
+ throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
+
+ foreach (var propertyCode in propertyMap) {
+ if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
+ cap.Anno = true;
+ else
+ propertyCapabilityMap[propertyCode] = new() { Anno = true };
+ }
+
+ break;
+ }
+
+ // Set プロパティマップ
+ case 0x9E: {
+ if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
+ throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
+
+ foreach (var propertyCode in propertyMap) {
+ if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
+ cap.Set = true;
+ else
+ propertyCapabilityMap[propertyCode] = new() { Set = true };
+ }
+
+ break;
+ }
+
+ // Get プロパティマップ
+ case 0x9F: {
+ if (!PropertyContentSerializer.TryDeserializePropertyMap(pr.EDT.Span, out var propertyMap))
+ throw new InvalidOperationException($"EDT contains invalid property map (EPC={pr.EPC:X2})");
+
+ foreach (var propertyCode in propertyMap) {
+ if (propertyCapabilityMap.TryGetValue(propertyCode, out var cap))
+ cap.Get = true;
+ else
+ propertyCapabilityMap[propertyCode] = new() { Get = true };
+ }
+
+ break;
+ }
+ }
+ }
+
+ device.ResetProperties(
+ propertyCapabilityMap.Select(
+ map => {
+ var (code, caps) = map;
+
+ return new EchonetProperty(
+ device.Spec.ClassGroup.Code,
+ device.Spec.Class.Code,
+ code,
+ caps.Anno,
+ caps.Set,
+ caps.Get
+ );
+ }
+ )
+ );
+
+ if (logger is not null) {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("------");
+
+ foreach (var temp in device.Properties) {
+ sb.Append('\t').Append(temp.GetDebugString()).AppendLine();
+ }
+
+ sb.AppendLine("------");
+
+ logger.LogTrace(sb.ToString());
+ }
+
+ device.HasPropertyMapAcquired = true;
+
+ OnPropertyMapAcquired(sourceNode, device);
+ }
+
+ /// <summary>
+ /// イベント<see cref="FrameReceived"/>をハンドルするメソッドを実装します。
+ /// 受信したECHONET Lite フレームを処理し、必要に応じて要求に対する応答を返します。
+ /// </summary>
+ /// <param name="sender">イベントのソース。</param>
+ /// <param name="value">
+ /// イベントデータを格納している<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// ECHONET Lite フレームの送信元を表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <exception cref="InvalidOperationException">電文形式 1(規定電文形式)を期待しましたが、<see cref="EData1"/>を取得できませんでした。</exception>
+#pragma warning disable CA1502 // TODO: reduce complexity
+ private void HandleFrameReceived(object? sender, (IPAddress Address, Frame Frame) value)
+ {
+ if (value.Frame.EHD1 != EHD1.EchonetLite)
+ return;
+ if (value.Frame.EHD2 != EHD2.Type1)
+ return;
+
+ if (value.Frame.EData is not EData1 edata)
+ throw new InvalidOperationException($"expected {nameof(EData1)}, but was {value.Frame.EData?.GetType()}");
+
+ var sourceNode = Nodes.SingleOrDefault(n => value.Address is not null && value.Address.Equals(n.Address));
+
+ // 未知のノードの場合
+ if (sourceNode is null) {
+ // ノードを生成
+ sourceNode = new(
+ address: value.Address,
+ nodeProfile: new(Profiles.NodeProfile, 0x01)
+ );
+
+ Nodes.Add(sourceNode);
+
+ NodeJoined?.Invoke(this, sourceNode);
+ }
+
+ var destObject = SelfNode.NodeProfile.EOJ == edata.DEOJ
+ ? SelfNode.NodeProfile // 自ノードプロファイル宛てのリクエストの場合
+ : SelfNode.Devices.FirstOrDefault(d => d.EOJ == edata.DEOJ);
+
+ Task? task = null;
+
+ switch (edata.ESV) {
+ case ESV.SetI: // プロパティ値書き込み要求(応答不要)
+ // あれば、書き込んでおわり
+ // なければ、プロパティ値書き込み要求不可応答 SetI_SNA
+ task = Task.Run(() => HandlePropertyValueWriteRequestAsync(value, edata, destObject));
+ break;
+
+ case ESV.SetC: // プロパティ値書き込み要求(応答要)
+ // あれば、書き込んで プロパティ値書き込み応答 Set_Res
+ // なければ、プロパティ値書き込み要求不可応答 SetC_SNA
+ task = Task.Run(() => HandlePropertyValueWriteRequestResponseRequiredAsync(value, edata, destObject));
+ break;
+
+ case ESV.Get: // プロパティ値読み出し要求
+ // あれば、プロパティ値読み出し応答 Get_Res
+ // なければ、プロパティ値読み出し不可応答 Get_SNA
+ task = Task.Run(() => HandlePropertyValueReadRequest(value, edata, destObject));
+ break;
+
+ case ESV.InfRequest: // プロパティ値通知要求
+ // あれば、プロパティ値通知 INF
+ // なければ、プロパティ値通知不可応答 INF_SNA
+ break;
+
+ case ESV.SetGet: // プロパティ値書き込み・読み出し要求
+ // あれば、プロパティ値書き込み・読み出し応答 SetGet_Res
+ // なければ、プロパティ値書き込み・読み出し不可応答 SetGet_SNA
+ task = Task.Run(() => HandlePropertyValueWriteReadRequestAsync(value, edata, destObject));
+ break;
+
+ case ESV.Inf: // プロパティ値通知
+ // プロパティ値通知要求 INF_REQのレスポンス
+ // または、自発的な通知のケースがある。
+ // なので、要求送信(INF_REQ)のハンドラでも対処するが、こちらでも自発として対処をする。
+ task = Task.Run(() => HandlePropertyValueNotificationRequestAsync(value, edata, sourceNode));
+ break;
+
+ case ESV.InfC: // プロパティ値通知(応答要)
+ // プロパティ値通知応答 INFC_Res
+ task = Task.Run(() => HandlePropertyValueNotificationResponseRequiredAsync(value, edata, sourceNode, destObject));
+ break;
+
+ case ESV.SetIServiceNotAvailable: // プロパティ値書き込み要求不可応答
+ // プロパティ値書き込み要求(応答不要)SetIのレスポンスなので、要求送信(SETI)のハンドラで対処
+ break;
+
+ case ESV.SetResponse: // プロパティ値書き込み応答
+ case ESV.SetCServiceNotAvailable: // プロパティ値書き込み要求不可応答
+ // プロパティ値書き込み要求(応答要) SetCのレスポンスなので、要求送信(SETC)のハンドラで対処
+ break;
+
+ case ESV.GetResponse: // プロパティ値読み出し応答
+ case ESV.GetServiceNotAvailable: // プロパティ値読み出し不可応答
+ // プロパティ値読み出し要求 Getのレスポンスなので、要求送信(GET)のハンドラで対処
+ break;
+
+ case ESV.InfCResponse: // プロパティ値通知応答
+ // プロパティ値通知(応答要) INFCのレスポンスなので、要求送信(INFC)のハンドラで対処
+ break;
+
+ case ESV.InfServiceNotAvailable: // プロパティ値通知不可応答
+ // プロパティ値通知要求 INF_REQ のレスポンスなので、要求送信(INF_REQ)のハンドラで対処
+ break;
+
+ case ESV.SetGetResponse: // プロパティ値書き込み・読み出し応答
+ case ESV.SetGetServiceNotAvailable: // プロパティ値書き込み・読み出し不可応答
+ // プロパティ値書き込み・読み出し要求 SetGet のレスポンスなので、要求送信(SETGET)のハンドラで対処
+ break;
+
+ default:
+ break;
+ }
+
+ task?.ContinueWith((t) => {
+ if (t.Exception is not null) {
+ logger?.LogTrace(t.Exception, "Exception");
+ }
+ });
+ }
+#pragma warning restore CA1502
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetI:プロパティ値書き込み要求(応答不要)」(ESV <c>0x60</c>)を処理します。
+ /// </summary>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。 対象がない場合は<see langword="null"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueWriteRequestAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.1 プロパティ値書き込みサービス(応答不要)[0x60, 0x50]
+ /// </seealso>
+ private async Task<bool> HandlePropertyValueWriteRequestAsync(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetObject? destObject
+ )
+ {
+ if (edata.OPCList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
+
+ if (destObject is null) {
+ // 対象となるオブジェクト自体が存在しない場合には、「不可応答」も返さないものとする。
+ return false;
+ }
+
+ var hasError = false;
+ var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
+
+ foreach (var opc in edata.OPCList) {
+ var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (
+ property is null ||
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ hasError = true;
+ // 要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
+ // 要求された EDT を付け、要求を受理できなかったことを示す。
+ opcList.Add(opc);
+ }
+ else {
+ // 要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
+ property.SetValue(opc.EDT);
+
+ opcList.Add(new(opc.EPC));
+ }
+ }
+
+ if (hasError) {
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.SetIServiceNotAvailable, // SetI_SNA(0x50)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetC:プロパティ値書き込み要求(応答要)」(ESV <c>0x61</c>)を処理します。
+ /// </summary>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。 対象がない場合は<see langword="null"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueWriteRequestResponseRequiredAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.2 プロパティ値書き込みサービス(応答要)[0x61,0x71,0x51]
+ /// </seealso>
+ private async Task<bool> HandlePropertyValueWriteRequestResponseRequiredAsync(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetObject? destObject
+ )
+ {
+ if (edata.OPCList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
+
+ var hasError = false;
+ var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
+
+ if (destObject is null) {
+ // DEOJがない場合、全OPCをそのまま返す
+ hasError = true;
+ opcList.AddRange(edata.OPCList);
+ }
+ else {
+ foreach (var opc in edata.OPCList) {
+ var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (
+ property is null ||
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ hasError = true;
+ // 要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
+ // 要求された EDT を付け、要求を受理できなかったことを示す。
+ opcList.Add(opc);
+ }
+ else {
+ // 要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
+ property.SetValue(opc.EDT);
+
+ opcList.Add(new(opc.EPC));
+ }
+ }
+ }
+
+ if (hasError) {
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.SetCServiceNotAvailable, // SetC_SNA(0x51)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return false;
+ }
+
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.SetResponse, // Set_Res(0x71)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「Get:プロパティ値読み出し要求」(ESV <c>0x62</c>)を処理します。
+ /// </summary>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。 対象がない場合は<see langword="null"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueReadRequestAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.3 プロパティ値読み出しサービス[0x62,0x72,0x52]
+ /// </seealso>
+ private async Task<bool> HandlePropertyValueReadRequest(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetObject? destObject
+ )
+ {
+ if (edata.OPCList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
+
+ var hasError = false;
+ var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
+
+ if (destObject is null) {
+ // DEOJがない場合、全OPCをそのまま返す
+ hasError = true;
+ opcList.AddRange(edata.OPCList);
+ }
+ else {
+ foreach (var opc in edata.OPCList) {
+ var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (
+ property is null ||
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ hasError = true;
+ // 要求を受理しなかった EPC に対しては、それに続く PDC に 0 を設定して
+ // EDT はつけず、要求を受理できなかったことを示す。
+ // (そのままでよい)
+ opcList.Add(opc);
+ }
+ else {
+ // 要求を受理した EPCに対しては、それに続く PDC に読み出したプロパティの長さを、
+ // EDT には読み出したプロパティ値を設定する
+ opcList.Add(new(opc.EPC, property.ValueMemory));
+ }
+ }
+ }
+
+ if (hasError) {
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.GetServiceNotAvailable, // Get_SNA(0x52)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return false;
+ }
+
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.GetResponse, // Get_Res(0x72)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「SetGet:プロパティ値書き込み・読み出し要求」(ESV <c>0x6E</c>)を処理します。
+ /// </summary>
+ /// <remarks>
+ /// 本実装は書き込み後、読み込む
+ /// </remarks>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。 対象がない場合は<see langword="null"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueWriteReadRequestAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.4 プロパティ値書き込み読み出しサービス[0x6E,0x7E,0x5E]
+ /// </seealso>
+ private async Task<bool> HandlePropertyValueWriteReadRequestAsync(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetObject? destObject
+ )
+ {
+ if (edata.OPCSetList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCSetList)} is null");
+ if (edata.OPCGetList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCGetList)} is null");
+
+ var hasError = false;
+ var opcSetList = new List<PropertyRequest>(capacity: edata.OPCSetList.Count);
+ var opcGetList = new List<PropertyRequest>(capacity: edata.OPCGetList.Count);
+
+ if (destObject is null) {
+ // DEOJがない場合、全OPCをそのまま返す
+ hasError = true;
+ opcSetList.AddRange(edata.OPCSetList);
+ opcGetList.AddRange(edata.OPCGetList);
+ }
+ else {
+ foreach (var opc in edata.OPCSetList) {
+ var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (
+ property is null ||
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ hasError = true;
+ // 要求を受理しなかったEPCに対しては、それに続く PDC に要求時と同じ値を設定し、
+ // 要求された EDT を付け、要求を受理できなかったことを示す。
+ opcSetList.Add(opc);
+ }
+ else {
+ // 要求を受理した EPC に対しては、それに続くPDCに0を設定してEDTは付けない
+ property.SetValue(opc.EDT);
+
+ opcSetList.Add(new(opc.EPC));
+ }
+ }
+
+ foreach (var opc in edata.OPCGetList) {
+ var property = destObject.SetProperties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (
+ property is null ||
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ hasError = true;
+ // 要求を受理しなかった EPC に対しては、それに続く PDC に 0 を設定して
+ // EDT はつけず、要求を受理できなかったことを示す。
+ // (そのままでよい)
+ opcGetList.Add(opc);
+ }
+ else {
+ // 要求を受理した EPCに対しては、それに続く PDC に読み出したプロパティの長さを、
+ // EDT には読み出したプロパティ値を設定する
+ opcSetList.Add(new(opc.EPC, property.ValueMemory));
+ }
+ }
+ }
+
+ if (hasError) {
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.SetGetServiceNotAvailable, // SetGet_SNA(0x5E)
+ opcListOrOpcSetList: opcSetList,
+ opcGetList: opcGetList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return false;
+ }
+
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.SetGetResponse, // SetGet_Res(0x7E)
+ opcListOrOpcSetList: opcSetList,
+ opcGetList: opcGetList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+
+ return true;
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)を処理します。
+ /// </summary>
+ /// <remarks>
+ /// 自発なので、0x73のみ。
+ /// </remarks>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="sourceNode">要求元CHONET Lite ノードを表す<see cref="EchonetNode"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{T}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueNotificationRequestAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.5 プロパティ値通知サービス[0x63,0x73,0x53]
+ /// </seealso>
+#pragma warning disable IDE0060
+ private async Task<bool> HandlePropertyValueNotificationRequestAsync(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetNode sourceNode
+ )
+#pragma warning restore IDE0060
+ {
+ if (edata.OPCList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
+
+ var hasError = false;
+ var sourceObject = sourceNode.Devices.FirstOrDefault(d => d.EOJ == edata.SEOJ);
+
+ if (sourceObject is null) {
+ // ノードプロファイルからの通知の場合
+ if (sourceNode.NodeProfile.EOJ == edata.SEOJ) {
+ sourceObject = sourceNode.NodeProfile;
+ }
+ else {
+ // 未知のオブジェクト
+ // 新規作成(プロパティはない状態)
+ sourceObject = new(edata.SEOJ);
+ sourceNode.Devices.Add(sourceObject);
+ }
+ }
+
+ foreach (var opc in edata.OPCList) {
+ var property = sourceObject.Properties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (property is null) {
+ // 未知のプロパティ
+ // 新規作成
+ property = new(edata.SEOJ.ClassGroupCode, edata.SEOJ.ClassCode, opc.EPC);
+ sourceObject.AddProperty(property);
+ }
+
+ if (
+ opc.EDT.Length > property.Spec.MaxSize ||
+ opc.EDT.Length < property.Spec.MinSize
+ ) {
+ // スペック外なので、格納しない
+ hasError = true;
+ }
+ else {
+ property.SetValue(opc.EDT);
+
+ // ノードプロファイルのインスタンスリスト通知の場合
+ if (sourceNode.NodeProfile == sourceObject && opc.EPC == 0xD5)
+ await HandleInstanceListNotificationReceivedAsync(sourceNode, opc.EDT).ConfigureAwait(false);
+ }
+ }
+
+ return !hasError;
+ }
+
+ /// <summary>
+ /// ECHONET Lite サービス「INFC:プロパティ値通知(応答要)」(ESV <c>0x74</c>)を処理します。
+ /// </summary>
+ /// <param name="request">
+ /// 受信した内容を表す<see cref="ValueTuple{IPAddress,Frame}"/>。
+ /// 送信元アドレスを表す<see cref="IPAddress"/>と、受信したECHONET Lite フレームを表す<see cref="Frame"/>を保持します。
+ /// </param>
+ /// <param name="edata">受信したEDATAを表す<see cref="EData1"/>。 ここで渡されるEDATAは電文形式 1(規定電文形式)のECHONET Lite データです。</param>
+ /// <param name="sourceNode">要求元CHONET Lite ノードを表す<see cref="EchonetNode"/>。</param>
+ /// <param name="destObject">対象ECHONET Lite オブジェクトを表す<see cref="EchonetObject"/>。 対象がない場合は<see langword="null"/>。</param>
+ /// <returns>
+ /// 非同期の読み取り操作を表す<see cref="Task{Boolean}"/>。
+ /// <see cref="Task{T}.Result"/>には処理の結果が含まれます。
+ /// 要求を正常に処理した場合は<see langword="true"/>、そうでなければ<see langword="false"/>が設定されます。
+ /// </returns>
+ /// <seealso cref="PerformPropertyValueNotificationResponseRequiredAsync"/>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 3.2.5 ECHONET Lite サービス(ESV)
+ /// </seealso>
+ /// <seealso href="https://echonet.jp/spec_v114_lite/">
+ /// ECHONET Lite規格書 Ver.1.14 第2部 ECHONET Lite 通信ミドルウェア仕様 4.2.3.6 プロパティ値通知(応答要)サービス[0x74, 0x7A]
+ /// </seealso>
+ private async Task<bool> HandlePropertyValueNotificationResponseRequiredAsync(
+ (IPAddress Address, Frame Frame) request,
+ EData1 edata,
+ EchonetNode sourceNode,
+ EchonetObject? destObject
+ )
+ {
+ if (edata.OPCList is null)
+ throw new InvalidOperationException($"{nameof(edata.OPCList)} is null");
+
+ var hasError = false;
+ var opcList = new List<PropertyRequest>(capacity: edata.OPCList.Count);
+
+ if (destObject is null) {
+ // 指定された DEOJ が存在しない場合には電文を廃棄する。
+ // "けどこっそり保持する"
+ hasError = true;
+ }
+
+ var sourceObject = sourceNode.Devices.FirstOrDefault(d => d.EOJ == edata.SEOJ);
+
+ if (sourceObject is null) {
+ // ノードプロファイルからの通知の場合
+ if (sourceNode.NodeProfile.EOJ == edata.SEOJ) {
+ sourceObject = sourceNode.NodeProfile;
+ }
+ else {
+ // 未知のオブジェクト
+ // 新規作成(プロパティはない状態)
+ sourceObject = new(edata.SEOJ);
+ sourceNode.Devices.Add(sourceObject);
+ }
+ }
+
+ foreach (var opc in edata.OPCList) {
+ var property = sourceObject.Properties.FirstOrDefault(p => p.Spec.Code == opc.EPC);
+
+ if (property is null) {
+ // 未知のプロパティ
+ // 新規作成
+ property = new(edata.SEOJ.ClassGroupCode, edata.SEOJ.ClassCode, opc.EPC);
+ sourceObject.AddProperty(property);
+ }
+
+ if (
+ (property.Spec.MaxSize != null && opc.EDT.Length > property.Spec.MaxSize) ||
+ (property.Spec.MinSize != null && opc.EDT.Length < property.Spec.MinSize)
+ ) {
+ // スペック外なので、格納しない
+ hasError = true;
+ }
+ else {
+ property.SetValue(opc.EDT);
+
+ // ノードプロファイルのインスタンスリスト通知の場合
+ if (sourceNode.NodeProfile == sourceObject && opc.EPC == 0xD5)
+ await HandleInstanceListNotificationReceivedAsync(sourceNode, opc.EDT).ConfigureAwait(false);
+ }
+
+ // EPC には通知時と同じプロパティコードを設定するが、
+ // 通知を受信したことを示すため、PDCには 0 を設定し、EDT は付けない。
+ opcList.Add(new(opc.EPC));
+ }
+
+ if (destObject is not null) {
+ await SendFrameAsync(
+ request.Address,
+ buffer => FrameSerializer.SerializeEchonetLiteFrameFormat1(
+ buffer: buffer,
+ tid: request.Frame.TID,
+ sourceObject: edata.DEOJ, // 入れ替え
+ destinationObject: edata.SEOJ, // 入れ替え
+ esv: ESV.InfCResponse, // INFC_Res(0x74)
+ opcListOrOpcSetList: opcList
+ ),
+ cancellationToken: default
+ ).ConfigureAwait(false);
+ }
+
+ return !hasError;
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.cs
new file mode 100644
index 0000000..bc1d973
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetClient.cs
@@ -0,0 +1,153 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+
+using Microsoft.Extensions.Logging;
+
+namespace Smdn.Net.EchonetLite;
+
+public partial class EchonetClient : IDisposable, IAsyncDisposable {
+ private readonly bool shouldDisposeEchonetLiteHandler;
+ private IEchonetLiteHandler echonetLiteHandler; // null if disposed
+ private readonly ILogger? logger;
+
+ /// <summary>
+ /// 現在の<see cref="EchonetClient"/>インスタンスが扱う自ノードを表す<see cref="SelfNode"/>。
+ /// </summary>
+ public EchonetNode SelfNode { get; }
+
+ /// <summary>
+ /// 既知のECHONET Lite ノードのコレクションを表す<see cref="ICollection{EchonetNode}"/>。
+ /// </summary>
+ public ICollection<EchonetNode> Nodes { get; }
+
+ /// <inheritdoc cref="EchonetClient(IPAddress, IEchonetLiteHandler, bool, ILogger{EchonetClient})"/>
+ public EchonetClient(
+ IPAddress nodeAddress,
+ IEchonetLiteHandler echonetLiteHandler,
+ ILogger<EchonetClient>? logger = null
+ )
+ : this(
+ nodeAddress: nodeAddress,
+ echonetLiteHandler: echonetLiteHandler,
+ shouldDisposeEchonetLiteHandler: false,
+ logger: logger
+ )
+ {
+ }
+
+ /// <summary>
+ /// <see cref="EchonetClient"/>クラスのインスタンスを初期化します。
+ /// </summary>
+ /// <param name="nodeAddress">自ノードのアドレスを表す<see cref="IPAddress"/>。</param>
+ /// <param name="echonetLiteHandler">このインスタンスがECHONET Lite フレームを送受信するために使用する<see cref="IEchonetLiteHandler"/>。</param>
+ /// <param name="shouldDisposeEchonetLiteHandler">オブジェクトが破棄される際に、<paramref name="echonetLiteHandler"/>も破棄するかどうかを表す値。</param>
+ /// <param name="logger">このインスタンスの動作を記録する<see cref="ILogger{EchonetClient}"/>。</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="nodeAddress"/>が<see langword="null"/>です。
+ /// あるいは、<paramref name="echonetLiteHandler"/>が<see langword="null"/>です。
+ /// </exception>
+ public EchonetClient(
+ IPAddress nodeAddress,
+ IEchonetLiteHandler echonetLiteHandler,
+ bool shouldDisposeEchonetLiteHandler,
+ ILogger<EchonetClient>? logger
+ )
+ {
+ this.logger = logger;
+ this.shouldDisposeEchonetLiteHandler = shouldDisposeEchonetLiteHandler;
+ this.echonetLiteHandler = echonetLiteHandler ?? throw new ArgumentNullException(nameof(echonetLiteHandler));
+ this.echonetLiteHandler.Received += EchonetDataReceived;
+ SelfNode = new(
+ address: nodeAddress ?? throw new ArgumentNullException(nameof(nodeAddress)),
+ nodeProfile: new(Profiles.NodeProfile, 0x01)
+ );
+ Nodes = new List<EchonetNode>();
+ // 自己消費用
+ FrameReceived += HandleFrameReceived;
+ }
+
+ /// <summary>
+ /// 現在の<see cref="EchonetClient"/>インスタンスによって使用されているリソースを解放して、インスタンスを破棄します。
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// 現在の<see cref="EchonetClient"/>インスタンスによって使用されているリソースを非同期に解放して、インスタンスを破棄します。
+ /// </summary>
+ /// <returns>非同期の破棄操作を表す<see cref="ValueTask"/>。</returns>
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore().ConfigureAwait(false);
+
+ Dispose(disposing: false);
+
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// 現在の<see cref="EchonetClient"/>インスタンスが使用しているアンマネージド リソースを解放します。 オプションで、マネージド リソースも解放します。
+ /// </summary>
+ /// <param name="disposing">
+ /// マネージド リソースとアンマネージド リソースの両方を解放する場合は<see langword="true"/>。
+ /// アンマネージド リソースだけを解放する場合は<see langword="false"/>。
+ /// </param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing) {
+ FrameReceived = null; // unsubscribe
+
+ requestSemaphore?.Dispose();
+ requestSemaphore = null!;
+
+ if (echonetLiteHandler is not null) {
+ echonetLiteHandler.Received -= EchonetDataReceived;
+
+ if (shouldDisposeEchonetLiteHandler && echonetLiteHandler is IDisposable disposableEchonetLiteHandler)
+ disposableEchonetLiteHandler.Dispose();
+
+ echonetLiteHandler = null!;
+ }
+ }
+ }
+
+ /// <summary>
+ /// 管理対象リソースの非同期の解放、リリース、またはリセットに関連付けられているアプリケーション定義のタスクを実行します。
+ /// </summary>
+ /// <returns>非同期の破棄操作を表す<see cref="ValueTask"/>。</returns>
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ FrameReceived = null; // unsubscribe
+
+ requestSemaphore?.Dispose();
+ requestSemaphore = null!;
+
+ if (echonetLiteHandler is not null) {
+ echonetLiteHandler.Received -= EchonetDataReceived;
+
+ if (shouldDisposeEchonetLiteHandler && echonetLiteHandler is IAsyncDisposable disposableEchonetLiteHandler)
+ await disposableEchonetLiteHandler.DisposeAsync().ConfigureAwait(false);
+
+ echonetLiteHandler = null!;
+ }
+ }
+
+ /// <summary>
+ /// 現在の<see cref="EchonetClient"/>インスタンスが破棄されている場合に、<see cref="ObjectDisposedException"/>をスローします。
+ /// </summary>
+ /// <exception cref="ObjectDisposedException">現在のインスタンスはすでに破棄されています。</exception>
+ protected void ThrowIfDisposed()
+ {
+ if (echonetLiteHandler is null)
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetNode.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetNode.cs
new file mode 100644
index 0000000..19c4ef9
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetNode.cs
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Net;
+
+namespace Smdn.Net.EchonetLite;
+
+/// <summary>
+/// ECHONET Liteノード
+/// </summary>
+public sealed class EchonetNode {
+ /// <summary>
+ /// 下位スタックのアドレス
+ /// </summary>
+ public IPAddress Address { get; }
+
+ /// <summary>
+ /// ノードプロファイルオブジェクト
+ /// </summary>
+ public EchonetObject NodeProfile { get; }
+
+ /// <summary>
+ /// 機器オブジェクトのリスト
+ /// </summary>
+ public ICollection<EchonetObject> Devices { get; }
+
+ /// <summary>
+ /// 機器オブジェクトのリスト<see cref="Devices"/>に変更があったときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// 現在のノードにECHONET Lite オブジェクトが追加・削除された際にイベントが発生します。
+ /// 変更の詳細は、イベント引数<see cref="NotifyCollectionChangedEventArgs"/>を参照してください。
+ /// </remarks>
+ public event NotifyCollectionChangedEventHandler? DevicesChanged;
+
+ public EchonetNode(IPAddress address, EchonetObject nodeProfile)
+ {
+ Address = address ?? throw new ArgumentNullException(nameof(address));
+ NodeProfile = nodeProfile ?? throw new ArgumentNullException(nameof(nodeProfile));
+
+ var devices = new ObservableCollection<EchonetObject>();
+
+ devices.CollectionChanged += (_, e) => OnDevicesChanged(e);
+
+ Devices = devices;
+ }
+
+ private void OnDevicesChanged(NotifyCollectionChangedEventArgs e)
+ {
+ DevicesChanged?.Invoke(this, e);
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObject.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObject.cs
new file mode 100644
index 0000000..bd76087
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObject.cs
@@ -0,0 +1,125 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+using Smdn.Net.EchonetLite.Appendix;
+using Smdn.Net.EchonetLite.Protocol;
+
+namespace Smdn.Net.EchonetLite;
+
+/// <summary>
+/// ECHONET Lite オブジェクトインスタンス
+/// </summary>
+public sealed class EchonetObject {
+ /// <summary>
+ /// プロパティの一覧<see cref="Properties"/>に変更があったときに発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// ECHONET Lite サービス「INF_REQ:プロパティ値通知要求」(ESV <c>0x63</c>)などによって
+ /// 現在のオブジェクトにECHONET Lite プロパティが追加・削除された際にイベントが発生します。
+ /// 変更の詳細は、イベント引数<see cref="NotifyCollectionChangedEventArgs"/>を参照してください。
+ /// </remarks>
+ public event NotifyCollectionChangedEventHandler? PropertiesChanged;
+
+ /// <summary>
+ /// プロパティマップ取得状態
+ /// </summary>
+ /// <seealso cref="EchonetClient.PropertyMapAcquiring"/>
+ /// <seealso cref="EchonetClient.PropertyMapAcquired"/>
+ public bool HasPropertyMapAcquired { get; internal set; } = false;
+
+ /// <summary>
+ /// クラスグループコード、クラスグループ名
+ /// ECHONET機器オブジェクト詳細規定がある場合、詳細仕様
+ /// </summary>
+ public EchonetObjectSpecification Spec { get; }
+
+ /// <summary>
+ /// インスタンスコード
+ /// </summary>
+ public byte InstanceCode { get; }
+
+ /// <summary>
+ /// プロパティの一覧
+ /// </summary>
+ public IReadOnlyCollection<EchonetProperty> Properties => properties;
+
+ private readonly ObservableCollection<EchonetProperty> properties;
+
+ /// <summary>
+ /// GETプロパティの一覧
+ /// </summary>
+ public IEnumerable<EchonetProperty> GetProperties => Properties.Where(static p => p.Spec.CanGet);
+
+ /// <summary>
+ /// SETプロパティの一覧
+ /// </summary>
+ public IEnumerable<EchonetProperty> SetProperties => Properties.Where(static p => p.Spec.CanSet);
+
+ /// <summary>
+ /// ANNOプロパティの一覧
+ /// </summary>
+ public IEnumerable<EchonetProperty> AnnoProperties => Properties.Where(static p => p.Spec.CanAnnounceStatusChange);
+
+ /// <summary>
+ /// EOJ
+ /// </summary>
+ internal EOJ EOJ => new(
+ classGroupCode: Spec.ClassGroup.Code,
+ classCode: Spec.Class.Code,
+ instanceCode: InstanceCode
+ );
+
+ /// <summary>
+ /// デフォルトコンストラクタ
+ /// </summary>
+ public EchonetObject(EOJ eoj)
+ : this(
+ classObject: DeviceClasses.LookupOrCreateClass(eoj.ClassGroupCode, eoj.ClassCode, includeProfiles: true),
+ instanceCode: eoj.InstanceCode
+ )
+ {
+ }
+
+ /// <summary>
+ /// スペック指定のコンストラクタ
+ /// プロパティは仕様から取得する
+ /// </summary>
+ /// <param name="classObject">オブジェクトクラス</param>
+ /// <param name="instanceCode">インスタンスコード</param>
+ public EchonetObject(EchonetObjectSpecification classObject, byte instanceCode)
+ {
+ Spec = classObject ?? throw new ArgumentNullException(nameof(classObject));
+ InstanceCode = instanceCode;
+
+ properties = new();
+
+ foreach (var prop in classObject.AllProperties.Values) {
+ properties.Add(new(prop));
+ }
+
+ properties.CollectionChanged += (_, e) => OnPropertiesChanged(e);
+ }
+
+ private void OnPropertiesChanged(NotifyCollectionChangedEventArgs e)
+ {
+ PropertiesChanged?.Invoke(this, e);
+ }
+
+ internal void AddProperty(EchonetProperty prop)
+ => properties.Add(prop);
+
+ internal void ResetProperties(IEnumerable<EchonetProperty> props)
+ {
+ properties.Clear();
+
+ foreach (var prop in props) {
+ properties.Add(prop);
+ }
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObjectExtentions.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObjectExtentions.cs
new file mode 100644
index 0000000..e1b7415
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetObjectExtentions.cs
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.Net.EchonetLite;
+
+internal static class EchonetObjectExtentions {
+ public static string GetDebugString(this EchonetObject obj)
+ {
+ if (obj is null)
+ return "null";
+
+ if (obj.Spec is null)
+ return "Spec null";
+
+ return $"0x{obj.Spec.ClassGroup.Code:X2}{obj.Spec.ClassGroup.Name} 0x{obj.Spec.Class.Code:X2}{obj.Spec.Class.Name} {obj.InstanceCode:X2}";
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetProperty.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetProperty.cs
new file mode 100644
index 0000000..3dd4e05
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetProperty.cs
@@ -0,0 +1,186 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Buffers;
+
+using Smdn.Net.EchonetLite.Appendix;
+
+namespace Smdn.Net.EchonetLite;
+
+/// <summary>
+/// プロパティクラス
+/// </summary>
+public sealed class EchonetProperty {
+ /// <summary>
+ /// EPC
+ /// ECHONET機器オブジェクト詳細規定がある場合、詳細仕様
+ /// </summary>
+ public EchonetPropertySpecification Spec { get; }
+
+ /// <summary>
+ /// プロパティ値の読み出し・通知要求のサービスを処理する。
+ /// プロパティ値読み出し要求(0x62)、プロパティ値書き込み・読み出し要求(0x6E)、プロパティ値通知要求(0x63)の要求受付処理を実施する。
+ /// </summary>
+ public bool CanGet { get; }
+
+ /// <summary>
+ /// プロパティ値の書き込み要求のサービスを処理する。
+ /// プロパティ値書き込み要求(応答不要)(0x60)、プロパティ値書き込み要求(応答要)(0x61)、プロパティ値書き込み・読み出し要求(0x6E)の要求受付処理を実施する。
+ /// </summary>
+ public bool CanSet { get; }
+
+ /// <summary>
+ /// プロパティ値の通知要求のサービスを処理する。
+ /// プロパティ値通知要求(0x63)の要求受付処理を実施する。
+ /// </summary>
+ public bool CanAnnounceStatusChange { get; }
+
+ private ArrayBufferWriter<byte>? value = null;
+
+ /// <summary>
+ /// プロパティ値を表す<see cref="ReadOnlyMemory{Byte}"/>を取得します。
+ /// </summary>
+ public ReadOnlyMemory<byte> ValueMemory => value is null ? ReadOnlyMemory<byte>.Empty : value.WrittenMemory;
+
+ /// <summary>
+ /// プロパティ値を表す<see cref="ReadOnlySpan{Byte}"/>を取得します。
+ /// </summary>
+ public ReadOnlySpan<byte> ValueSpan => value is null ? ReadOnlySpan<byte>.Empty : value.WrittenSpan;
+
+ /// <summary>
+ /// プロパティ値に変更があった場合に発生するイベント。
+ /// </summary>
+ /// <remarks>
+ /// このイベントは、プロパティに異なる値が設定された場合にのみ発生します。
+ /// プロパティに値が設定される際、その値が以前と同じ値だった場合には発生しません。
+ /// </remarks>
+ public event EventHandler<(ReadOnlyMemory<byte> OldValue, ReadOnlyMemory<byte> NewValue)>? ValueChanged;
+
+ public EchonetProperty(
+ byte classGroupCode,
+ byte classCode,
+ byte epc
+ )
+ : this(
+ classGroupCode: classGroupCode,
+ classCode: classCode,
+ epc: epc,
+ canAnnounceStatusChange: false,
+ canSet: false,
+ canGet: false
+ )
+ {
+ }
+
+ public EchonetProperty(
+ byte classGroupCode,
+ byte classCode,
+ byte epc,
+ bool canAnnounceStatusChange,
+ bool canSet,
+ bool canGet
+ )
+ : this(
+ spec: DeviceClasses.LookupOrCreateProperty(classGroupCode, classCode, epc, includeProfiles: true),
+ canAnnounceStatusChange: canAnnounceStatusChange,
+ canSet: canSet,
+ canGet: canGet
+ )
+ {
+ }
+
+ public EchonetProperty(EchonetPropertySpecification spec)
+ : this(
+ spec: spec,
+ canAnnounceStatusChange: false,
+ canSet: false,
+ canGet: false
+ )
+ {
+ }
+
+ public EchonetProperty(
+ EchonetPropertySpecification spec,
+ bool canAnnounceStatusChange,
+ bool canSet,
+ bool canGet
+ )
+ {
+ Spec = spec ?? throw new ArgumentNullException(nameof(spec));
+ CanAnnounceStatusChange = canAnnounceStatusChange;
+ CanSet = canSet;
+ CanGet = canGet;
+ }
+
+ /// <summary>
+ /// プロパティ値を設定します。
+ /// </summary>
+ /// <remarks>
+ /// 設定によって値が変更された場合は、イベント<see cref="ValueChanged"/>が発生します。
+ /// </remarks>
+ /// <param name="newValue">プロパティ値として設定する値を表す<see cref="ReadOnlyMemory{Byte}"/>。</param>
+ /// <seealso cref="ValueChanged"/>
+ public void SetValue(ReadOnlyMemory<byte> newValue)
+ => WriteValue(writer => writer.Write(newValue.Span), newValueSize: newValue.Length);
+
+ /// <summary>
+ /// プロパティ値を書き込みます。
+ /// </summary>
+ /// <remarks>
+ /// 書き込みによって値が変更された場合は、イベント<see cref="ValueChanged"/>が発生します。
+ /// </remarks>
+ /// <param name="write">
+ /// プロパティ値を書き込むための<see cref="Action{T}"/>デリゲート。
+ /// 引数で渡される<see cref="IBufferWriter{Byte}"/>を介してプロパティ値として設定する内容を書き込んでください。
+ /// </param>
+ /// <exception cref="ArgumentNullException"><paramref name="write"/>が<see langword="null"/>です。</exception>
+ /// <seealso cref="ValueChanged"/>
+ public void WriteValue(Action<IBufferWriter<byte>> write)
+ => WriteValue(write ?? throw new ArgumentNullException(nameof(write)), newValueSize: 0);
+
+ private void WriteValue(Action<IBufferWriter<byte>> write, int newValueSize)
+ {
+ var valueChangedHandlers = ValueChanged;
+ byte[]? oldValue = null;
+
+ try {
+ var oldValueLength = 0;
+
+ if (value is null) {
+ var initialCapacity = 0 < newValueSize ? newValueSize : 8; // TODO: best initial capacity
+
+ value = new(initialCapacity);
+ }
+ else {
+ oldValueLength = value.WrittenSpan.Length;
+
+ oldValue = ArrayPool<byte>.Shared.Rent(oldValueLength);
+
+ value.WrittenSpan.CopyTo(oldValue.AsSpan(0, oldValueLength));
+
+#if SYSTEM_BUFFERS_ARRAYBUFFERWRITER_RESETWRITTENCOUNT
+ value.ResetWrittenCount();
+#else
+ value.Clear();
+#endif
+ }
+
+ write(value);
+
+ if (valueChangedHandlers is not null) {
+ // 値が新規に設定される場合、以前の値から変更がある場合はValueChangedイベントを起こす
+ if (oldValue is null || !oldValue.AsSpan(0, oldValueLength).SequenceEqual(value.WrittenSpan)) {
+ var oldValueMemory = oldValue is null ? ReadOnlyMemory<byte>.Empty : oldValue.AsMemory(0, oldValueLength);
+ var newValueMemory = value.WrittenMemory;
+
+ valueChangedHandlers.Invoke(this, (oldValueMemory, newValueMemory));
+ }
+ }
+ }
+ finally {
+ if (oldValue is not null)
+ ArrayPool<byte>.Shared.Return(oldValue);
+ }
+ }
+}
diff --git a/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetPropertyExtentions.cs b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetPropertyExtentions.cs
new file mode 100644
index 0000000..2ed6df8
--- /dev/null
+++ b/src/Smdn.Net.EchonetLite/Smdn.Net.EchonetLite/EchonetPropertyExtentions.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2018 HiroyukiSakoh
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.Text;
+
+namespace Smdn.Net.EchonetLite;
+
+internal static class EchonetPropertyExtentions {
+ public static string GetDebugString(this EchonetProperty property)
+ {
+ if (property is null)
+ return "null";
+
+ if (property.Spec is null)
+ return "Spec null";
+
+ var sb = new StringBuilder();
+
+ sb.AppendFormat(provider: null, "0x{0:X2}", property.Spec.Code);
+ sb.Append(property.Spec.Name);
+ sb.Append(' ');
+ sb.Append(property.CanGet ? "Get" : string.Empty);
+ sb.Append(property.Spec.IsGetMandatory ? "(Req)" : string.Empty);
+ sb.Append(' ');
+ sb.Append(property.CanSet ? "Set" : string.Empty);
+ sb.Append(property.Spec.IsSetMandatory ? "(Req)" : string.Empty);
+ sb.Append(' ');
+ sb.Append(property.CanAnnounceStatusChange ? "Anno" : string.Empty);
+ sb.Append(property.Spec.IsStatusChangeAnnouncementMandatory ? "(Req)" : string.Empty);
+
+ return sb.ToString();
+ }
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment