Commit 762481e8 authored by Andreas Müller's avatar Andreas Müller

Init commit - TCP client should work

parents
* -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.exe filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
\ No newline at end of file
# Project-specific files
setup/bin/
# User-specific files
*.suo
*.user
*.sln.docstates
.vs/
*.lock.json
# Downloaded NuGet packages
/packages/*/
# Build results
[aA]rtifacts
[oO]bj
[bB]in
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Windows Store app package directory
AppPackages/
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# Others
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
sql/
*.[Cc]ache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.[Pp]ublish.xml
*.pfx
*.p12
*.publishsettings
*.bak
.internal
.local
.tmp
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
_NCrunch_*
.*crunch*.local.xml
# Dotfuscator
Dotfuscated/
# PowerShell build framework tools
build/buildscript/private.ps1
# Windows/other detritus
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
.DS_Store
The MIT License
Copyright (c) 2017 Andreas Müller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2003
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Tcp", "src\Modbus.Tcp\Modbus.Tcp.csproj", "{BF60D1D4-3767-4EC4-AC53-C1772958BE14}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A7CE6D2-374F-4EBC-BFD3-DE2587A6701C}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
LICENSE.txt = LICENSE.txt
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4E6650D-2156-4660-B531-0B2AAD475BA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3B6130C0-9168-4208-A677-29DD20301220}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BF60D1D4-3767-4EC4-AC53-C1772958BE14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF60D1D4-3767-4EC4-AC53-C1772958BE14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF60D1D4-3767-4EC4-AC53-C1772958BE14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF60D1D4-3767-4EC4-AC53-C1772958BE14}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BF60D1D4-3767-4EC4-AC53-C1772958BE14} = {D4E6650D-2156-4660-B531-0B2AAD475BA1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FC7F1581-0E3C-4E35-9DBC-CC7952DE19C1}
EndGlobalSection
EndGlobal
# Modbus
This is a .NET Standard 2.0 implementation of the Modbus protocol.
\ No newline at end of file
This diff is collapsed.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(SolutionDir)artifacts\bin\$(RootNamespace)\$(Configuration)</OutputPath>
<IntermediateOutputPath>$(SolutionDir)artifacts\obj\$(RootNamespace)\$(Configuration)</IntermediateOutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(SolutionDir)artifacts\bin\$(RootNamespace)\$(Configuration)</OutputPath>
<IntermediateOutputPath>$(SolutionDir)artifacts\obj\$(RootNamespace)\$(Configuration)</IntermediateOutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
</Project>
using Modbus.Tcp.Utils;
using System;
using System.Linq;
namespace Modbus.Tcp.Protocol
{
internal class Request
{
private static ushort transactionNumber = 0;
private static ushort NextTransactionId
{
get
{
transactionNumber++;
return transactionNumber;
}
}
public Request(MessageType type)
{
Type = type;
TransactionId = NextTransactionId;
}
public MessageType Type { get; private set; }
public ushort TransactionId { get; private set; }
public byte DeviceId { get; set; }
public byte Function { get; set; }
public ushort Address { get; set; }
public ushort Count { get; set; }
public DataBuffer Data { get; set; }
public byte[] Serialize()
{
var buffer = new DataBuffer(10);
buffer.SetUInt16(0, TransactionId);
buffer.SetUInt16(2, 0x0000); // Protocol ID
buffer.SetByte(6, DeviceId);
buffer.SetByte(7, Function);
buffer.SetUInt16(8, Address);
switch (Type)
{
case MessageType.Read:
buffer.AddUInt16(Count);
break;
case MessageType.WriteSingle:
if (Data?.Length > 0)
{
buffer.AddBytes(Data.Buffer);
}
break;
case MessageType.WriteMultiple:
buffer.AddUInt16(Count);
if (Data?.Length > 0)
{
buffer.AddBytes(Data.Buffer);
}
break;
default:
throw new NotImplementedException();
}
var len = buffer.Length - 6;
buffer.SetUInt16(4, (ushort)len);
return buffer.Buffer;
}
public override string ToString()
{
return $"Request#{TransactionId} | Device#{DeviceId}, Fn: {Function}, Address: {Address}, Count: {Count} | {string.Join(" ", Data.Buffer.Select(b => b.ToString("X2")).ToArray())}";
}
}
}
using Modbus.Tcp.Utils;
using System;
using System.Linq;
namespace Modbus.Tcp.Protocol
{
internal class Response
{
public Response(MessageType type, byte[] response)
{
var buffer = new DataBuffer(response);
var ident = buffer.GetUInt16(2);
if (ident != 0)
{
throw new ArgumentException("Response not valid Modbus TCP protocol");
}
var len = buffer.GetUInt16(4);
if (buffer.Length != len + 6)
{
throw new ArgumentException("Response incomplete");
}
TransactionId = buffer.GetUInt16(0);
DeviceId = buffer.GetByte(6);
var fn = buffer.GetByte(7);
if ((fn & Consts.ErrorMask) > 0)
{
IsError = true;
Function = (byte)(fn ^ Consts.ErrorMask);
}
else
{
Function = fn;
}
if (IsError)
{
ErrorCode = buffer.GetByte(8);
}
else
{
switch (type)
{
case MessageType.Read:
len = buffer.GetByte(8);
if (buffer.Length != len + 9)
{
throw new ArgumentException("Response incomplete");
}
Data = new DataBuffer(buffer.Buffer.Skip(9).ToArray());
break;
case MessageType.WriteSingle:
Address = buffer.GetUInt16(8);
Data = new DataBuffer(buffer.Buffer.Skip(10).ToArray());
break;
case MessageType.WriteMultiple:
Address = buffer.GetUInt16(8);
Count = buffer.GetUInt16(10);
break;
default:
throw new NotImplementedException();
}
}
}
public ushort TransactionId { get; private set; }
public byte DeviceId { get; private set; }
public byte Function { get; private set; }
public bool IsError { get; private set; }
public byte ErrorCode { get; private set; }
public string ErrorMessage => Consts.ErrorMessages[ErrorCode];
public ushort Address { get; private set; }
public ushort Count { get; private set; }
public DataBuffer Data { get; private set; }
public override string ToString()
{
return $"Response#{TransactionId} | Device#{DeviceId}, Fn: {Function}, Error: {IsError}, Address: {Address}, Count: {Count} | {string.Join(" ", Data.Buffer.Select(b => b.ToString("X2")).ToArray())}";
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Modbus.Tcp.Server
{
/// <summary>
/// TODO
/// </summary>
public class ModbusServer
{
}
}
namespace Modbus.Tcp.Utils
{
/// <summary>
/// Represents the contents of a coil on a Modbus device.
/// </summary>
public class Coil
{
/// <summary>
/// Gets or sets the address.
/// </summary>
public ushort Address { get; set; }
/// <summary>
/// Gets or sets a value indicating the status of the coil.
/// </summary>
public bool Value { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return $"Coil#{Address} | {Value}";
}
}
}
using System.Collections.Generic;
namespace Modbus.Tcp.Utils
{
internal class Consts
{
public static readonly Dictionary<byte, string> ErrorMessages = new Dictionary<byte, string>
{
{ 0, "No Error" },
{ 1, "Illegal Function" },
{ 2, "Illegal Data Address" },
{ 3, "Illegal Data Value" },
{ 4, "Slave Device Failure" },
{ 5, "Acknowledge" },
{ 6, "Slave Device Busy" },
{ 7, "Negative Acknowledge" },
{ 8, "Memory Parity Error" },
{ 10, "Gateway Path Unavailable" },
{ 11, "Gateway Target Device Failed to Respond" }
};
public const byte ErrorMask = 0x80;
public const byte ReadCoilsFunctionNumber = 0x01;
public const byte ReadDiscreteInputsFunctionNumber = 0x02;
public const byte ReadHoldingRegistersFunctionNumber = 0x03;
public const byte ReadInputRegistersFunctionNumber = 0x04;
public const byte WriteSingleCoilFunctionNumber = 0x05;
public const byte WriteSingleRegisterFunctionNumber = 0x06;
public const byte WriteMultipleCoilsFunctionNumber = 0x0F;
public const byte WriteMultipleRegistersFunctionNumber = 0x10;
public const int MinDeviceId = 0x0000;
public const int MaxDeviceId = 0x00FF; // 255
public const int MinAddress = 0x0000;
public const int MaxAddress = 0xFFFF; // 65535
public const int MinCount = 0x01;
public const int MaxCoilCountRead = 0x7D0; // 2000
public const int MaxCoilCountWrite = 0x7B0; // 1968
public const int MaxRegisterCountRead = 0x7D; // 125
public const int MaxRegisterCountWrite = 0x7B; // 123
}
}
This diff is collapsed.
namespace Modbus.Tcp.Utils
{
/// <summary>
/// Represents the contents of a discrete input on a Modbus device.
/// </summary>
public class DiscreteInput
{
/// <summary>
/// Gets or sets the address.
/// </summary>
public ushort Address { get; set; }
/// <summary>
/// Gets or sets a value indicating the status of the discrete input.
/// </summary>
public bool Value { get; set; }
/// <inheritdoc/>
public override string ToString()
{
return $"Coil#{Address} | {Value}";
}
}
}
using System;
namespace Modbus.Tcp.Utils
{
internal enum MessageType
{
Unset,
Read,
WriteSingle,
WriteMultiple
}
}
This diff is collapsed.
using System;
namespace Modbus.Tcp.Utils
{
/// <summary>
/// Represents errors that occurr during Modbus requests.
/// </summary>
public class ModbusException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ModbusException"/> class.
/// </summary>
public ModbusException()
: base()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ModbusException"/> class
/// with a specified error message.
/// </summary>
/// <param name="message">The specified error message.</param>
public ModbusException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ModbusException"/> class
/// with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The specified error message.</param>
/// <param name="innerException">The inner exception.</param>
public ModbusException(string message, Exception innerException)
: base(message, innerException)
{ }
}
}
using System;
namespace Modbus.Tcp.Utils
{
/// <summary>
/// Represents a register on a Modbus device.
/// </summary>
public class Register
{
/// <summary>
/// Gets or sets the address.
/// </summary>
public ushort Address { get; set; }
/// <summary>
/// Gets or sets the High-Byte of the register.
/// </summary>
public byte HiByte { get; set; }
/// <summary>
/// Gets or sets the Low-Byte of the register.
/// </summary>
public byte LoByte { get; set; }
/// <summary>
/// Gets or sets the value of the register as WORD.
/// </summary>
public ushort Value
{
get
{
var blob = new[] { HiByte, LoByte };
if (BitConverter.IsLittleEndian)
{
Array.Reverse(blob);
}
return BitConverter.ToUInt16(blob, 0);
}
set
{
var blob = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(blob);
}
HiByte = blob[0];
LoByte = blob[1];
}
}
/// <inheritdoc/>
public override string ToString()
{
return $"Register#{Address} | Hi: {HiByte.ToString("X2")} Lo: {LoByte.ToString("X2")} | {Value}";
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment