18 Commits

Author SHA1 Message Date
Michael Croes
5318f94dd7 Release S7NetPlus 0.12.0
Release highlights:
- Add synchronization to methods interacting with the PLC
2021-06-02 21:28:48 +02:00
Michael Croes
ea3beff481 Merge pull request #394 from mycroes/sync
Add synchronization
2021-06-02 21:25:51 +02:00
Michael Croes
f67b1e773f Add missing ConfigureAwait in OpenAsync 2021-06-01 20:59:20 +02:00
Michael Croes
e93a656312 Fix locking for OpenAsync 2021-06-01 20:28:34 +02:00
Michael Croes
8035f71a16 Apply synchronization to stream actions 2021-05-31 22:57:13 +02:00
Michael Croes
df4f258290 Move GetStreamIfAvailable to PLC.cs 2021-05-31 21:23:32 +02:00
Michael Croes
5636b93a53 PlcSynchronous: Remove unused usings 2021-05-31 21:17:42 +02:00
Michael Croes
8ed1b840bc PlcSynchronous: Clenaup trailing whitespace 2021-05-31 21:17:23 +02:00
Michael Croes
2afed88231 Consolidate sync stream calls 2021-05-31 21:17:03 +02:00
Michael Croes
1ded47971b Consolidate async stream calls 2021-05-31 21:07:16 +02:00
Michael Croes
ced10b4eca Release S7NetPlus 0.11.0
Release highlights:
- Fix for byte length calculation of bits
- Fix for Boolean.ClearBit
- Added Boolean.ClearBit and .SetBit by reference
2021-05-10 22:55:28 +02:00
Michael Croes
632e1c14ac Merge pull request #391 from mycroes/set-bit-clear-bit
SetBit and ClearBit tests, fixes
2021-05-10 21:24:43 +02:00
Michael Croes
aa50280233 Boolean: Add SetBit and ClearBit by reference 2021-05-10 21:14:43 +02:00
Michael Croes
3a794e8a46 Cleanup trailing whitespace in PLC and PLCHelpers 2021-05-10 21:05:02 +02:00
Michael Croes
0b8bd66bf7 Fix bitwise operator in Boolean.ClearBit
Fixes #249
2021-05-10 20:50:46 +02:00
Michael Croes
e66d21af05 Add tests for Boolean.SetBit and Boolean.ClearBit 2021-05-10 20:49:15 +02:00
Michael Croes
44ee651ac4 Merge pull request #390 from mycroes/byte-length-bits
Fix byte length calculation for VarType.Bit
2021-05-10 20:32:34 +02:00
Michael Croes
a1b4694ef6 Fix calculation of byte length for VarType.Bit
Fixes #389
2021-05-10 20:25:38 +02:00
7 changed files with 197 additions and 86 deletions

View File

@@ -0,0 +1,38 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Boolean = S7.Net.Types.Boolean;
namespace S7.Net.UnitTest.TypeTests
{
[TestClass]
public class BooleanTests
{
[DataTestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(2)]
[DataRow(3)]
[DataRow(4)]
[DataRow(5)]
[DataRow(6)]
[DataRow(7)]
public void TestValidSetBitValues(int index)
{
Assert.AreEqual(Math.Pow(2, index), Boolean.SetBit(0, index));
}
[DataTestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(2)]
[DataRow(3)]
[DataRow(4)]
[DataRow(5)]
[DataRow(6)]
[DataRow(7)]
public void TestValidClearBitValues(int index)
{
Assert.AreEqual((byte) ((uint) Math.Pow(2, index) ^ uint.MaxValue), Boolean.ClearBit(byte.MaxValue, index));
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace S7.Net.Internal
{
internal class TaskQueue
{
private static readonly object Sentinel = new object();
private Task prev = Task.FromResult(Sentinel);
public async Task<T> Enqueue<T>(Func<Task<T>> action)
{
var tcs = new TaskCompletionSource<object>();
await Interlocked.Exchange(ref prev, tcs.Task).ConfigureAwait(false);
try
{
return await action.Invoke().ConfigureAwait(false);
}
finally
{
tcs.SetResult(Sentinel);
}
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using S7.Net.Internal;
using S7.Net.Protocol;
using S7.Net.Types;
@@ -13,8 +15,10 @@ namespace S7.Net
/// </summary>
public partial class Plc : IDisposable
{
private readonly TaskQueue queue = new TaskQueue();
private const int CONNECTION_TIMED_OUT_ERROR_CODE = 10060;
//TCP connection to device
private TcpClient? tcpClient;
private NetworkStream? _stream;
@@ -242,6 +246,16 @@ namespace S7.Net
}
}
private Stream GetStreamIfAvailable()
{
if (_stream == null)
{
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
}
return _stream;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

View File

@@ -30,8 +30,8 @@ namespace S7.Net
}
/// <summary>
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
/// the address of the memory, the address of the byte and the bytes count.
/// Create the bytes-package to request data from the PLC. You have to specify the memory type (dataType),
/// the address of the memory, the address of the byte and the bytes count.
/// </summary>
/// <param name="dataType">MemoryType (DB, Timer, Counter, etc.)</param>
/// <param name="db">Address of the memory to be read</param>
@@ -184,7 +184,7 @@ namespace S7.Net
switch (varType)
{
case VarType.Bit:
return varCount + 7 / 8;
return (varCount + 7) / 8;
case VarType.Byte:
return (varCount < 1) ? 1 : varCount;
case VarType.String:

View File

@@ -1,11 +1,11 @@
using S7.Net.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
using S7.Net.Protocol;
using System.IO;
using System.Threading;
using S7.Net.Protocol.S7;
@@ -28,9 +28,14 @@ namespace S7.Net
var stream = await ConnectAsync().ConfigureAwait(false);
try
{
cancellationToken.ThrowIfCancellationRequested();
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
_stream = stream;
await queue.Enqueue(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
_stream = stream;
return default(object);
}).ConfigureAwait(false);
}
catch(Exception)
{
@@ -47,29 +52,30 @@ namespace S7.Net
return tcpClient.GetStream();
}
private async Task EstablishConnection(NetworkStream stream, CancellationToken cancellationToken)
private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken)
{
await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
}
private async Task RequestConnection(NetworkStream stream, CancellationToken cancellationToken)
private async Task RequestConnection(Stream stream, CancellationToken cancellationToken)
{
var requestData = ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot);
await stream.WriteAsync(requestData, 0, requestData.Length).ConfigureAwait(false);
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false);
if (response.PDUType != COTP.PduType.ConnectionConfirmed)
{
throw new InvalidDataException("Connection request was denied", response.TPkt.Data, 1, 0x0d);
}
}
private async Task SetupConnection(NetworkStream stream, CancellationToken cancellationToken)
private async Task SetupConnection(Stream stream, CancellationToken cancellationToken)
{
var setupData = GetS7ConnectionSetup();
await stream.WriteAsync(setupData, 0, setupData.Length).ConfigureAwait(false);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var s7data = await NoLockRequestTsduAsync(stream, setupData, 0, setupData.Length, cancellationToken)
.ConfigureAwait(false);
if (s7data.Length < 2)
throw new WrongNumberOfBytesException("Not enough data received in response to Communication Setup");
@@ -112,7 +118,7 @@ namespace S7.Net
}
/// <summary>
/// Read and decode a certain number of bytes of the "VarType" provided.
/// Read and decode a certain number of bytes of the "VarType" provided.
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
/// If the read was not successful, check LastErrorCode or LastErrorString.
/// </summary>
@@ -179,10 +185,10 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <param name="sourceClass">Instance of the class that will store the values</param>
/// <param name="sourceClass">Instance of the class that will store the values</param>
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.
@@ -205,7 +211,7 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic
/// type, the class needs a default constructor.
/// </summary>
@@ -221,7 +227,7 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <typeparam name="T">The class that will be instantiated</typeparam>
@@ -245,10 +251,10 @@ namespace S7.Net
}
/// <summary>
/// Reads multiple vars in a single request.
/// Reads multiple vars in a single request.
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
/// Values are stored in the property "Value" of the dataItem and are already converted.
/// If you don't want the conversion, just create a dataItem of bytes.
/// If you don't want the conversion, just create a dataItem of bytes.
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
/// </summary>
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
@@ -256,18 +262,15 @@ namespace S7.Net
/// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.</param>
public async Task<List<DataItem>> ReadMultipleVarsAsync(List<DataItem> dataItems, CancellationToken cancellationToken = default)
{
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
var dataToSend = BuildReadRequestPackage(dataItems.Select(d => DataItem.GetDataItemAddress(d)).ToList());
await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
ParseDataIntoDataItems(s7data, dataItems);
@@ -435,12 +438,9 @@ namespace S7.Net
private async Task ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var stream = GetStreamIfAvailable();
var dataToSend = BuildReadRequestPackage(new [] { new DataItemAddress(dataType, db, startByteAdr, count)});
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken);
AssertReadResponse(s7data, count);
Array.Copy(s7data, 18, buffer, offset, count);
@@ -456,13 +456,11 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
await stream.WriteAsync(message.Array, 0, length).ConfigureAwait(false);
var response = await COTP.TSDU.ReadAsync(stream, CancellationToken.None).ConfigureAwait(false);
var response = await RequestTsduAsync(message.Array, 0, length).ConfigureAwait(false);
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
@@ -476,15 +474,11 @@ namespace S7.Net
/// <returns>A task that represents the asynchronous write operation.</returns>
private async Task WriteBytesWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, byte[] value, int dataOffset, int count, CancellationToken cancellationToken)
{
try
{
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(dataToSend, 0, dataToSend.Length, cancellationToken).ConfigureAwait(false);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
}
catch (OperationCanceledException)
@@ -499,15 +493,11 @@ namespace S7.Net
private async Task WriteBitWithASingleRequestAsync(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue, CancellationToken cancellationToken)
{
var stream = GetStreamIfAvailable();
try
{
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
var s7data = await RequestTsduAsync(dataToSend, cancellationToken).ConfigureAwait(false);
await stream.WriteAsync(dataToSend, 0, dataToSend.Length).ConfigureAwait(false);
var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
}
catch (OperationCanceledException)
@@ -520,13 +510,33 @@ namespace S7.Net
}
}
private Stream GetStreamIfAvailable()
private Task<byte[]> RequestTsduAsync(byte[] requestData, CancellationToken cancellationToken = default) =>
RequestTsduAsync(requestData, 0, requestData.Length, cancellationToken);
private Task<byte[]> RequestTsduAsync(byte[] requestData, int offset, int length, CancellationToken cancellationToken = default)
{
if (_stream == null)
{
throw new PlcException(ErrorCode.ConnectionError, "Plc is not connected");
}
return _stream;
var stream = GetStreamIfAvailable();
return queue.Enqueue(() =>
NoLockRequestTsduAsync(stream, requestData, offset, length, cancellationToken));
}
private static async Task<COTP.TPDU> NoLockRequestTpduAsync(Stream stream, byte[] requestData,
CancellationToken cancellationToken = default)
{
await stream.WriteAsync(requestData, 0, requestData.Length, cancellationToken).ConfigureAwait(false);
var response = await COTP.TPDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
return response;
}
private static async Task<byte[]> NoLockRequestTsduAsync(Stream stream, byte[] requestData, int offset, int length,
CancellationToken cancellationToken = default)
{
await stream.WriteAsync(requestData, offset, length, cancellationToken).ConfigureAwait(false);
var response = await COTP.TSDU.ReadAsync(stream, cancellationToken).ConfigureAwait(false);
return response;
}
}
}

View File

@@ -2,8 +2,6 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using S7.Net.Protocol;
using S7.Net.Helper;
@@ -54,7 +52,7 @@ namespace S7.Net
}
/// <summary>
/// Read and decode a certain number of bytes of the "VarType" provided.
/// Read and decode a certain number of bytes of the "VarType" provided.
/// This can be used to read multiple consecutive variables of the same type (Word, DWord, Int, etc).
/// If the read was not successful, check LastErrorCode or LastErrorString.
/// </summary>
@@ -115,10 +113,10 @@ namespace S7.Net
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <param name="sourceClass">Instance of the class that will store the values</param>
/// <param name="sourceClass">Instance of the class that will store the values</param>
/// <param name="db">Index of the DB; es.: 1 is for DB1</param>
/// <param name="startByteAdr">Start byte address. If you want to read DB1.DBW200, this is 200.</param>
/// <returns>The number of read bytes</returns>
@@ -138,7 +136,7 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified. To instantiate the class defined by the generic
/// type, the class needs a default constructor.
/// </summary>
@@ -152,7 +150,7 @@ namespace S7.Net
}
/// <summary>
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// Reads all the bytes needed to fill a class in C#, starting from a certain address, and set all the properties values to the value that are read from the PLC.
/// This reads only properties, it doesn't read private variable or public variable without {get;set;} specified.
/// </summary>
/// <typeparam name="T">The class that will be instantiated</typeparam>
@@ -186,7 +184,7 @@ namespace S7.Net
while (count > 0)
{
//TODO: Figure out how to use MaxPDUSize here
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//Snap7 seems to choke on PDU sizes above 256 even if snap7
//replies with bigger PDU size in connection setup.
var maxToWrite = Math.Min(count, MaxPDUSize - 28);//TODO tested only when the MaxPDUSize is 480
WriteBytesWithASingleRequest(dataType, db, startByteAdr + localIndex, value, localIndex, maxToWrite);
@@ -298,7 +296,6 @@ namespace S7.Net
private void ReadBytesWithSingleRequest(DataType dataType, int db, int startByteAdr, byte[] buffer, int offset, int count)
{
var stream = GetStreamIfAvailable();
try
{
// first create the header
@@ -309,9 +306,7 @@ namespace S7.Net
BuildReadDataRequestPackage(package, dataType, db, startByteAdr, count);
var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
var s7data = RequestTsdu(dataToSend);
AssertReadResponse(s7data, count);
Array.Copy(s7data, 18, buffer, offset, count);
@@ -331,13 +326,11 @@ namespace S7.Net
{
AssertPduSizeForWrite(dataItems);
var stream = GetStreamIfAvailable();
var message = new ByteArray();
var length = S7WriteMultiple.CreateRequest(message, dataItems);
stream.Write(message.Array, 0, length);
var response = RequestTsdu(message.Array, 0, length);
var response = COTP.TSDU.Read(stream);
S7WriteMultiple.ParseResponse(response, response.Length, dataItems);
}
@@ -345,12 +338,9 @@ namespace S7.Net
{
try
{
var stream = GetStreamIfAvailable();
var dataToSend = BuildWriteBytesPackage(dataType, db, startByteAdr, value, dataOffset, count);
var s7data = RequestTsdu(dataToSend);
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
}
catch (Exception exc)
@@ -425,14 +415,11 @@ namespace S7.Net
private void WriteBitWithASingleRequest(DataType dataType, int db, int startByteAdr, int bitAdr, bool bitValue)
{
var stream = GetStreamIfAvailable();
try
{
var dataToSend = BuildWriteBitPackage(dataType, db, startByteAdr, bitValue, bitAdr);
var s7data = RequestTsdu(dataToSend);
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
}
catch (Exception exc)
@@ -442,10 +429,10 @@ namespace S7.Net
}
/// <summary>
/// Reads multiple vars in a single request.
/// Reads multiple vars in a single request.
/// You have to create and pass a list of DataItems and you obtain in response the same list with the values.
/// Values are stored in the property "Value" of the dataItem and are already converted.
/// If you don't want the conversion, just create a dataItem of bytes.
/// If you don't want the conversion, just create a dataItem of bytes.
/// The number of DataItems as well as the total size of the requested data can not exceed a certain limit (protocol restriction).
/// </summary>
/// <param name="dataItems">List of dataitems that contains the list of variables that must be read.</param>
@@ -453,8 +440,6 @@ namespace S7.Net
{
AssertPduSizeForRead(dataItems);
var stream = GetStreamIfAvailable();
try
{
// first create the header
@@ -468,9 +453,7 @@ namespace S7.Net
}
var dataToSend = package.ToArray();
stream.Write(dataToSend, 0, dataToSend.Length);
var s7data = COTP.TSDU.Read(stream); //TODO use Async
var s7data = RequestTsdu(dataToSend);
ValidateResponseCode((ReadWriteErrorCode)s7data[14]);
@@ -481,5 +464,12 @@ namespace S7.Net
throw new PlcException(ErrorCode.ReadData, exc);
}
}
private byte[] RequestTsdu(byte[] requestData) => RequestTsdu(requestData, 0, requestData.Length);
private byte[] RequestTsdu(byte[] requestData, int offset, int length)
{
return RequestTsduAsync(requestData, offset, length).GetAwaiter().GetResult();
}
}
}

View File

@@ -14,20 +14,51 @@
}
/// <summary>
/// Sets the value of a bit to 1 (true), given the address of the bit
/// Sets the value of a bit to 1 (true), given the address of the bit. Returns
/// a copy of the value with the bit set.
/// </summary>
/// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to set.</param>
/// <returns>The modified value with the bit at index set.</returns>
public static byte SetBit(byte value, int bit)
{
return (byte)((value | (1 << bit)) & 0xFF);
SetBit(ref value, bit);
return value;
}
/// <summary>
/// Sets the value of a bit to 1 (true), given the address of the bit.
/// </summary>
/// <param name="value">The value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to set.</param>
public static void SetBit(ref byte value, int bit)
{
value = (byte) ((value | (1 << bit)) & 0xFF);
}
/// <summary>
/// Resets the value of a bit to 0 (false), given the address of the bit. Returns
/// a copy of the value with the bit cleared.
/// </summary>
/// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to clear.</param>
/// <returns>The modified value with the bit at index cleared.</returns>
public static byte ClearBit(byte value, int bit)
{
ClearBit(ref value, bit);
return value;
}
/// <summary>
/// Resets the value of a bit to 0 (false), given the address of the bit
/// </summary>
public static byte ClearBit(byte value, int bit)
/// <param name="value">The input value to modify.</param>
/// <param name="bit">The index (zero based) of the bit to clear.</param>
public static void ClearBit(ref byte value, int bit)
{
return (byte)((value | (~(1 << bit))) & 0xFF);
value = (byte) (value & ~(1 << bit) & 0xFF);
}
}
}