1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00

Added SoIP device-provider (WIP)

This commit is contained in:
Darth Affe 2018-06-10 22:01:03 +02:00
parent 263dddcee3
commit 4b2aba8447
15 changed files with 681 additions and 0 deletions

View File

@ -0,0 +1,41 @@
using RGB.NET.Devices.SoIP.Generic;
namespace RGB.NET.Devices.SoIP.Client
{
public class SoIPClientDeviceDefinition : ISoIPDeviceDefinition
{
#region Properties & Fields
/// <summary>
/// Gets or sets the hostname of the device.
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Gets or sets the port to device is listening to.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the manufacturer of the device.
/// </summary>
public string Manufacturer { get; set; } = "Unknown";
/// <summary>
/// Gets or sets the model name of the device.
/// </summary>
public string Model { get; set; } = "Generic SoIP-Device";
#endregion
#region Constructors
public SoIPClientDeviceDefinition(string hostname, int port)
{
this.Hostname = hostname;
this.Port = port;
}
#endregion
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RGB.NET.Core;
using RGB.NET.Devices.SoIP.Generic;
using SimpleTCP;
namespace RGB.NET.Devices.SoIP.Client
{
public class SoIPClientRGBDevice : AbstractRGBDevice<SoIPClientRGBDeviceInfo>, ISoIPRGBDevice
{
#region Properties & Fields
private readonly Dictionary<LedId, Color> _syncbackCache = new Dictionary<LedId, Color>();
private readonly SimpleTcpClient _tcpClient;
public override SoIPClientRGBDeviceInfo DeviceInfo { get; }
#endregion
#region Constructors
public SoIPClientRGBDevice(SoIPClientRGBDeviceInfo deviceInfo)
{
this.DeviceInfo = deviceInfo;
_tcpClient = new SimpleTcpClient();
_tcpClient.DelimiterDataReceived += TcpClientOnDelimiterDataReceived;
}
#endregion
#region Methods
void ISoIPRGBDevice.Initialize(IDeviceUpdateTrigger updateTrigger)
{
_tcpClient.Connect(DeviceInfo.Hostname, DeviceInfo.Port);
}
/// <inheritdoc />
protected override void UpdateLeds(IEnumerable<Led> ledsToUpdate)
{ }
/// <inheritdoc />
public override void SyncBack()
{
lock (_syncbackCache)
{
foreach (KeyValuePair<LedId, Color> cacheEntry in _syncbackCache)
{
LedId ledId = cacheEntry.Key;
Color color = cacheEntry.Value;
if (!LedMapping.TryGetValue(ledId, out Led led))
led = InitializeLed(cacheEntry.Key, new Rectangle(0, 0, 10, 10)); //TODO DarthAffe 10.06.2018: Send layout with initial package
SetLedColorWithoutRequest(led, color);
}
_syncbackCache.Clear();
}
}
private void TcpClientOnDelimiterDataReceived(object sender, Message message)
{
List<(LedId, Color)> leds = message.MessageString.Split(';').Select(x =>
{
string[] led = x.Split('|');
return ((LedId)Enum.Parse(typeof(LedId), led[0]), Color.FromHexString(led[1]));
}).ToList();
lock (_syncbackCache)
foreach ((LedId ledId, Color color) in leds)
_syncbackCache[ledId] = color;
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
_tcpClient.Disconnect();
_tcpClient.Dispose();
}
#endregion
}
}

View File

@ -0,0 +1,56 @@
using System;
using RGB.NET.Core;
namespace RGB.NET.Devices.SoIP.Client
{
/// <inheritdoc />
/// <summary>
/// Represents device information for a <see cref="SoIPClientRGBDevice"/> />.
/// </summary>
public class SoIPClientRGBDeviceInfo : IRGBDeviceInfo
{
#region Properties & Fields
/// <inheritdoc />
public RGBDeviceType DeviceType => RGBDeviceType.Unknown;
/// <inheritdoc />
public string Manufacturer { get; }
/// <inheritdoc />
public string Model { get; }
/// <inheritdoc />
public RGBDeviceLighting Lighting => RGBDeviceLighting.None;
/// <inheritdoc />
public bool SupportsSyncBack => true;
/// <inheritdoc />
public Uri Image { get; set; }
/// <summary>
/// The hostname of the device.
/// </summary>
public string Hostname { get; }
/// <summary>
/// The port of the device.
/// </summary>
public int Port { get; }
#endregion
#region Constructors
internal SoIPClientRGBDeviceInfo(SoIPClientDeviceDefinition deviceDefinition)
{
this.Manufacturer = deviceDefinition.Manufacturer;
this.Model = deviceDefinition.Model;
this.Hostname = deviceDefinition.Hostname;
this.Port = deviceDefinition.Port;
}
#endregion
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
<Costura IncludeDebugSymbols='false' IncludeAssemblies='SimpleTCP' />
</Weavers>

View File

@ -0,0 +1,8 @@
namespace RGB.NET.Devices.SoIP.Generic
{
/// <summary>
/// Marker interface for SoIP device definitions.
/// </summary>
public interface ISoIPDeviceDefinition
{ }
}

View File

@ -0,0 +1,11 @@
using System;
using RGB.NET.Core;
namespace RGB.NET.Devices.SoIP.Generic
{
// ReSharper disable once InconsistentNaming
internal interface ISoIPRGBDevice : IRGBDevice, IDisposable
{
void Initialize(IDeviceUpdateTrigger updateTrigger);
}
}

View File

@ -0,0 +1,74 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<RuntimeIdentifiers>win7-x86;win7-x64</RuntimeIdentifiers>
<Authors>Darth Affe</Authors>
<Company>Wyrez</Company>
<Language>en-US</Language>
<NeutralLanguage>en-US</NeutralLanguage>
<Title>RGB.NET.Devices.SoIP</Title>
<AssemblyName>RGB.NET.Devices.SoIP</AssemblyName>
<AssemblyTitle>RGB.NET.Devices.SoIP</AssemblyTitle>
<PackageId>RGB.NET.Devices.SoIP</PackageId>
<RootNamespace>RGB.NET.Devices.SoIP</RootNamespace>
<Description>SoIP-Device-Implementations of RGB.NET</Description>
<Summary>SoIP-Device-Implementations of RGB.NET, a C# (.NET) library</Summary>
<Copyright>Copyright © Wyrez 2017</Copyright>
<PackageCopyright>Copyright © Wyrez 2017</PackageCopyright>
<PackageIconUrl>http://lib.arge.be/icon.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/DarthAffe/RGB.NET</PackageProjectUrl>
<PackageLicenseUrl>https://raw.githubusercontent.com/DarthAffe/RGB.NET/master/LICENSE</PackageLicenseUrl>
<RepositoryType>Github</RepositoryType>
<RepositoryUrl>https://github.com/DarthAffe/RGB.NET</RepositoryUrl>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageReleaseNotes></PackageReleaseNotes>
<Version>0.0.1</Version>
<AssemblyVersion>0.0.1</AssemblyVersion>
<FileVersion>0.0.1</FileVersion>
<OutputPath>..\bin\</OutputPath>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IncludeSource>True</IncludeSource>
<IncludeSymbols>True</IncludeSymbols>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>TRACE;NETCORE;NETSTANDARD;NETSTANDARD2_0;DEBUG;NETSTANDARD2_0</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net45'">
<DefineConstants>NET45;NETFULL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="2.0.1" />
<PackageReference Include="Fody" Version="3.0.3" />
<PackageReference Include="SimpleTCP" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RGB.NET.Core\RGB.NET.Core.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
using RGB.NET.Core;
using RGB.NET.Devices.SoIP.Generic;
namespace RGB.NET.Devices.SoIP.Server
{
public class SoIPServerDeviceDefinition : ISoIPDeviceDefinition
{
#region Properties & Fields
/// <summary>
/// Gets or sets the port to device is listening to.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the manufacturer of the device.
/// </summary>
public string Manufacturer { get; set; } = "Unknown";
/// <summary>
/// Gets or sets the model name of the device.
/// </summary>
public string Model { get; set; } = "Generic SoIP-Device";
/// <summary>
/// Gets the IDs of the leds represented by this device.
/// </summary>
public List<LedId> Leds { get; }
#endregion
#region Constructors
public SoIPServerDeviceDefinition(int port, params LedId[] ledIds)
{
this.Port = port;
this.Leds = ledIds.ToList();
}
#endregion
}
}

View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using RGB.NET.Core;
using RGB.NET.Devices.SoIP.Generic;
using SimpleTCP;
namespace RGB.NET.Devices.SoIP.Server
{
public class SoIPServerRGBDevice : AbstractRGBDevice<SoIPServerRGBDeviceInfo>, ISoIPRGBDevice
{
#region Properties & Fields
private readonly List<LedId> _leds;
private readonly SimpleTcpServer _tcpServer;
private SoIPServerUpdateQueue _updateQueue;
public override SoIPServerRGBDeviceInfo DeviceInfo { get; }
#endregion
#region Constructors
public SoIPServerRGBDevice(SoIPServerRGBDeviceInfo deviceInfo, List<LedId> leds)
{
this.DeviceInfo = deviceInfo;
this._leds = leds;
_tcpServer = new SimpleTcpServer();
_tcpServer.ClientConnected += TcpServerOnClientConnected;
}
#endregion
#region Methods
void ISoIPRGBDevice.Initialize(IDeviceUpdateTrigger updateTrigger)
{
int count = 0;
foreach (LedId id in _leds)
InitializeLed(id, new Rectangle((count++) * 10, 0, 10, 10));
//TODO DarthAffe 10.06.2018: Allow to load a layout.
if (Size == Size.Invalid)
{
Rectangle ledRectangle = new Rectangle(this.Select(x => x.LedRectangle));
Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
}
_tcpServer.Start(DeviceInfo.Port);
_updateQueue = new SoIPServerUpdateQueue(updateTrigger, _tcpServer);
}
protected override void UpdateLeds(IEnumerable<Led> ledsToUpdate) => _updateQueue.SetData(ledsToUpdate);
private void TcpServerOnClientConnected(object sender, TcpClient tcpClient)
{
string message = GetLedString(LedMapping.Values);
byte[] messageData = _tcpServer.StringEncoder.GetBytes(message + _tcpServer.StringEncoder.GetString(new[] { _tcpServer.Delimiter }));
tcpClient.GetStream().WriteAsync(messageData, 0, messageData.Length);
}
private string GetLedString(IEnumerable<Led> leds) => string.Join(";", leds.Select(x => x.Id.ToString() + "|" + x.Color.AsARGBHexString()));
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
_tcpServer.Stop();
}
#endregion
}
}

View File

@ -0,0 +1,50 @@
using System;
using RGB.NET.Core;
namespace RGB.NET.Devices.SoIP.Server
{
/// <inheritdoc />
/// <summary>
/// Represents device information for a <see cref="SoIPServerRGBDevice"/> />.
/// </summary>
public class SoIPServerRGBDeviceInfo : IRGBDeviceInfo
{
#region Properties & Fields
/// <inheritdoc />
public RGBDeviceType DeviceType => RGBDeviceType.Unknown;
/// <inheritdoc />
public string Manufacturer { get; }
/// <inheritdoc />
public string Model { get; }
/// <inheritdoc />
public RGBDeviceLighting Lighting => RGBDeviceLighting.Key;
/// <inheritdoc />
public bool SupportsSyncBack => false;
/// <inheritdoc />
public Uri Image { get; set; }
/// <summary>
/// The port of the device.
/// </summary>
public int Port { get; }
#endregion
#region Constructors
internal SoIPServerRGBDeviceInfo(SoIPServerDeviceDefinition deviceDefinition)
{
this.Manufacturer = deviceDefinition.Manufacturer;
this.Model = deviceDefinition.Model;
this.Port = deviceDefinition.Port;
}
#endregion
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RGB.NET.Core;
using SimpleTCP;
namespace RGB.NET.Devices.SoIP.Server
{
/// <inheritdoc />
/// <summary>
/// Represents the update-queue performing updates for E131-DMX devices.
/// </summary>
public class SoIPServerUpdateQueue : UpdateQueue
{
#region Properties & Fields
private readonly SimpleTcpServer _tcpServer;
#endregion
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.SoIP.Server.SoIPServerUpdateQueue" /> class.
/// </summary>
/// <param name="updateTrigger">The update trigger used by this queue.</param>
/// <param name="tcpServer">The hostname of the device this queue is performing updates for.</param>
public SoIPServerUpdateQueue(IDeviceUpdateTrigger updateTrigger, SimpleTcpServer tcpServer)
: base(updateTrigger)
{
this._tcpServer = tcpServer;
}
#endregion
#region Methods
/// <inheritdoc />
protected override void Update(Dictionary<object, Color> dataSet)
{
if ((dataSet != null) && (dataSet.Count > 0))
{
string m = GetLedString(dataSet);
_tcpServer.BroadcastLine(m);
Console.WriteLine("send " + m);
}
}
private string GetLedString(Dictionary<object, Color> dataSet) => string.Join(";", dataSet.Select(x => x.Key.ToString() + "|" + x.Value.AsARGBHexString()));
#endregion
}
}

View File

@ -0,0 +1,139 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using RGB.NET.Core;
using RGB.NET.Devices.SoIP.Client;
using RGB.NET.Devices.SoIP.Generic;
using RGB.NET.Devices.SoIP.Server;
namespace RGB.NET.Devices.SoIP
{
/// <inheritdoc />
/// <summary>
/// Represents a device provider responsible for debug devices.
/// </summary>
public class SoIPDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
private static SoIPDeviceProvider _instance;
/// <summary>
/// Gets the singleton <see cref="SoIPDeviceProvider"/> instance.
/// </summary>
public static SoIPDeviceProvider Instance => _instance ?? new SoIPDeviceProvider();
/// <inheritdoc />
public bool IsInitialized { get; private set; }
/// <inheritdoc />
public IEnumerable<IRGBDevice> Devices { get; private set; }
/// <inheritdoc />
public bool HasExclusiveAccess => false;
/// <summary>
/// Gets a list of all defined device-definitions.
/// </summary>
public List<ISoIPDeviceDefinition> DeviceDefinitions { get; } = new List<ISoIPDeviceDefinition>();
/// <summary>
/// The <see cref="DeviceUpdateTrigger"/> used to trigger the updates for dmx devices.
/// </summary>
public DeviceUpdateTrigger UpdateTrigger { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SoIPDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public SoIPDeviceProvider()
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(SoIPDeviceProvider)}");
_instance = this;
UpdateTrigger = new DeviceUpdateTrigger();
}
#endregion
#region Methods
/// <summary>
/// Adds the given <see cref="ISoIPDeviceDefinition" /> to this device-provider.
/// </summary>
/// <param name="deviceDefinition">The <see cref="ISoIPDeviceDefinition"/> to add.</param>
public void AddDeviceDefinition(ISoIPDeviceDefinition deviceDefinition) => DeviceDefinitions.Add(deviceDefinition);
/// <inheritdoc />
public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.Unknown, bool exclusiveAccessIfPossible = false, bool throwExceptions = false)
{
IsInitialized = false;
try
{
UpdateTrigger.Stop();
IList<IRGBDevice> devices = new List<IRGBDevice>();
foreach (ISoIPDeviceDefinition deviceDefinition in DeviceDefinitions)
{
try
{
ISoIPRGBDevice device = null;
switch (deviceDefinition)
{
case SoIPServerDeviceDefinition serverDeviceDefinition:
if (serverDeviceDefinition.Leds.Count > 0)
device = new SoIPServerRGBDevice(new SoIPServerRGBDeviceInfo(serverDeviceDefinition), serverDeviceDefinition.Leds);
break;
case SoIPClientDeviceDefinition clientDeviceDefinition:
device = new SoIPClientRGBDevice(new SoIPClientRGBDeviceInfo(clientDeviceDefinition));
break;
}
if (device != null)
{
device.Initialize(UpdateTrigger);
devices.Add(device);
}
}
catch { if (throwExceptions) throw; }
}
UpdateTrigger.Start();
Devices = new ReadOnlyCollection<IRGBDevice>(devices);
IsInitialized = true;
}
catch
{
if (throwExceptions) throw;
return false;
}
return true;
}
/// <inheritdoc />
public void ResetDevices()
{ }
/// <inheritdoc />
public void Dispose()
{
foreach (IRGBDevice device in Devices)
device.Dispose();
}
#endregion
}
}

View File

@ -0,0 +1,24 @@
using RGB.NET.Core;
namespace RGB.NET.Devices.SoIP
{
/// <summary>
/// Represents a device provider loaded used to dynamically load SoIP (syncback over IP) devices into an application.
/// </summary>
public class SoIPDeviceProviderLoader : IRGBDeviceProviderLoader
{
#region Properties & Fields
/// <inheritdoc />
public bool RequiresInitialization => true;
#endregion
#region Methods
/// <inheritdoc />
public IRGBDeviceProvider GetDeviceProvider() => SoIPDeviceProvider.Instance;
#endregion
}
}

View File

@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.Razer", "RG
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.Roccat", "RGB.NET.Devices.Roccat\RGB.NET.Devices.Roccat.csproj", "{DF4A0267-24FA-4D32-8841-3E91F31E51ED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RGB.NET.Devices.SoIP", "RGB.NET.Devices.SoIP\RGB.NET.Devices.SoIP.csproj", "{562CE11D-4EAA-4E98-A066-309300F3A023}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -97,6 +99,14 @@ Global
{DF4A0267-24FA-4D32-8841-3E91F31E51ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF4A0267-24FA-4D32-8841-3E91F31E51ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF4A0267-24FA-4D32-8841-3E91F31E51ED}.Release|Any CPU.Build.0 = Release|Any CPU
{3B677BC5-E654-496A-8132-29DFB5CFC01A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B677BC5-E654-496A-8132-29DFB5CFC01A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B677BC5-E654-496A-8132-29DFB5CFC01A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B677BC5-E654-496A-8132-29DFB5CFC01A}.Release|Any CPU.Build.0 = Release|Any CPU
{562CE11D-4EAA-4E98-A066-309300F3A023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{562CE11D-4EAA-4E98-A066-309300F3A023}.Debug|Any CPU.Build.0 = Debug|Any CPU
{562CE11D-4EAA-4E98-A066-309300F3A023}.Release|Any CPU.ActiveCfg = Release|Any CPU
{562CE11D-4EAA-4E98-A066-309300F3A023}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -115,6 +125,8 @@ Global
{6666A8B9-DC90-46DA-A689-57489B64FF98} = {59DA384B-BE24-4486-AFC5-7DC3C9B81EB4}
{A9942536-A397-405F-873A-B62CEC9C1146} = {59DA384B-BE24-4486-AFC5-7DC3C9B81EB4}
{DF4A0267-24FA-4D32-8841-3E91F31E51ED} = {59DA384B-BE24-4486-AFC5-7DC3C9B81EB4}
{3B677BC5-E654-496A-8132-29DFB5CFC01A} = {59DA384B-BE24-4486-AFC5-7DC3C9B81EB4}
{562CE11D-4EAA-4E98-A066-309300F3A023} = {59DA384B-BE24-4486-AFC5-7DC3C9B81EB4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {918A9698-4B2E-44E0-8CC9-E7A7F36CCB1D}

View File

@ -275,6 +275,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IBAN/@EntryIndexedValue">IBAN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IO/@EntryIndexedValue">IO</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LL/@EntryIndexedValue">LL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PDF/@EntryIndexedValue">PDF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PLZ/@EntryIndexedValue">PLZ</s:String>