Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Z21.Client.UnitTest/Core/Codecs/AddressCodecTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,33 @@ public void CombineCvAddress_IsInverseOfSplit(ushort cvAddress)
Assert.That(_codec.CombineCvAddress(msb, lsb), Is.EqualTo(cvAddress));
}

[Test]
[TestCase((ushort)0, 0x00, 0x00)]
[TestCase((ushort)1, 0x00, 0x01)]
[TestCase((ushort)255, 0x00, 0xFF)]
[TestCase((ushort)256, 0x01, 0x00)]
[TestCase((ushort)768, 0x03, 0x00)]
[TestCase((ushort)1023, 0x03, 0xFF)]
public void SplitPomCvAddress_ReturnsCvHighBitsAndLsb(ushort cvAddress, byte expectedHighBits, byte expectedLsb)
{
(byte cvHighBits, byte cvLsb) = _codec.SplitPomCvAddress(cvAddress);
Assert.Multiple(() =>
{
Assert.That(cvHighBits, Is.EqualTo(expectedHighBits), "CV high bits are incorrect");
Assert.That(cvLsb, Is.EqualTo(expectedLsb), "CV LSB is incorrect");
});
}

[Test]
[TestCase((ushort)1024)]
[TestCase((ushort)1025)]
[TestCase((ushort)65535)]
public void SplitPomCvAddress_AboveTenBitRange_ThrowsWithMessage(ushort cvAddress)
{
ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() => _codec.SplitPomCvAddress(cvAddress))!;
Assert.That(exception.Message, Does.Contain("0 and 1023"));
}

[Test]
public void EncodeAccessoryPomAddress_WholeDecoder_SetsCddNibbleToZero()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Z21.UnitTest.Core.Command.Programming
public class CvPomAccessoryWriteBitCommandTest : CommandTestFixture
{
[Test]
[TestCase((ushort)1, true, (byte)0, (ushort)0, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE8, 0x00, 0x0A, 0x25 })]
[TestCase((ushort)1, true, (byte)0, (ushort)0, (byte)2, false, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE8, 0x00, 0x02, 0x2D })]
[TestCase((ushort)1, true, (byte)0, (ushort)256, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE9, 0x00, 0x0A, 0x24 })]
[TestCase((ushort)1, true, (byte)0, (ushort)0, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE8, 0x00, 0xFA, 0xD5 })]
[TestCase((ushort)1, true, (byte)0, (ushort)0, (byte)2, false, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE8, 0x00, 0xF2, 0xDD })]
[TestCase((ushort)1, true, (byte)0, (ushort)256, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x31, 0x00, 0x10, 0xE9, 0x00, 0xFA, 0xD4 })]
public void Ctor_SetsCorrectDataBits(ushort decoderAddress, bool wholeDecoder, byte output, ushort cvAddress, byte bitPosition, bool bitValue, byte[] expected)
{
CvPomAccessoryWriteBitCommand command = Factory.Create<CvPomAccessoryWriteBitCommand>(decoderAddress, wholeDecoder, output, cvAddress, bitPosition, bitValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Z21.UnitTest.Core.Command.Programming
public class CvPomWriteBitCommandTest : CommandTestFixture
{
[Test]
[TestCase((ushort)3, (ushort)0, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE8, 0x00, 0x0A, 0x37 })]
[TestCase((ushort)3, (ushort)0, (byte)2, false, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE8, 0x00, 0x02, 0x3F })]
[TestCase((ushort)3, (ushort)256, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE9, 0x00, 0x0A, 0x36 })]
[TestCase((ushort)3, (ushort)0, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE8, 0x00, 0xFA, 0xC7 })]
[TestCase((ushort)3, (ushort)0, (byte)2, false, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE8, 0x00, 0xF2, 0xCF })]
[TestCase((ushort)3, (ushort)256, (byte)2, true, new byte[] { 0x0C, 0x00, 0x40, 0x00, 0xE6, 0x30, 0x00, 0x03, 0xE9, 0x00, 0xFA, 0xC6 })]
public void Ctor_SetsCorrectDataBits(ushort locoAddress, ushort cvAddress, byte bitPosition, bool bitValue, byte[] expected)
{
CvPomWriteBitCommand command = Factory.Create<CvPomWriteBitCommand>(locoAddress, cvAddress, bitPosition, bitValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private static IEnumerable<TestCaseData> Handlers()
{
TestCaseData Case(string name, IZ21ResponseHandler handler, byte[] valid) => new TestCaseData(handler, valid).SetName(name);

yield return Case("CvResult", new CvResultResponseHandler(new AddressCodec()), Set(Frame(6, 0x40), (4, 0x64), (5, 0x14)));
yield return Case("CvResult", new CvResultResponseHandler(new AddressCodec()), Set(Frame(9, 0x40), (4, 0x64), (5, 0x14)));
yield return Case("CvNack", new CvNackResponseHandler(), Set(Frame(6, 0x40), (4, 0x61), (5, 0x13)));
yield return Case("CvNackSc", new CvNackShortCircuitResponseHandler(), Set(Frame(6, 0x40), (4, 0x61), (5, 0x12)));
yield return Case("RmBus", new RmBusDataChangedResponseHandler(), Frame(15, 0x80));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public void CanHandle_ValidResponse_ReturnsTrue()
[Test]
[TestCase(new byte[] { 0x0A, 0x00, 0x40, 0x00, 0x64, 0x13, 0x00, 0x1C, 0x05, 0x00 }, TestName = "Wrong DB0")]
[TestCase(new byte[] { 0x00 }, TestName = "Response too small")]
[TestCase(new byte[] { 0x07, 0x00, 0x40, 0x00, 0x64, 0x14, 0x00 }, TestName = "Signature matches but value byte truncated (length 7)")]
[TestCase(new byte[] { 0x08, 0x00, 0x40, 0x00, 0x64, 0x14, 0x00, 0x1C }, TestName = "Signature matches but value byte missing (length 8)")]
public void CanHandle_InvalidResponse_ReturnsFalse(byte[] response)
{
Assert.That(_handler.CanHandle(response), Is.False);
Expand Down
10 changes: 10 additions & 0 deletions src/Z21.Client/Core/Codecs/AddressCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ public ushort CombineCvAddress(byte msb, byte lsb)
return (ushort)((msb << 8) + lsb);
}

public (byte cvHighBits, byte cvLsb) SplitPomCvAddress(ushort cvAddress)
{
if (cvAddress > 0x3FF)
throw new ArgumentOutOfRangeException(nameof(cvAddress), cvAddress, "POM CV address must be between 0 and 1023 (CV1..CV1024); higher CVs are not addressable on the main track.");

byte cvHighBits = (byte)((cvAddress >> 8) & 0x03);
byte cvLsb = (byte)(cvAddress & 0xFF);
return (cvHighBits, cvLsb);
}

public (byte db1, byte db2) EncodeAccessoryPomAddress(ushort decoderAddress, bool wholeDecoder, byte output)
{
int cddd = wholeDecoder ? 0x00 : (0x08 | (output & 0x07));
Expand Down
8 changes: 8 additions & 0 deletions src/Z21.Client/Core/Codecs/IAddressCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public interface IAddressCodec
/// </summary>
ushort CombineCvAddress(byte msb, byte lsb);

/// <summary>
/// Splits a CV address (0 = CV1) for a POM (main-track) command into the two high <c>MM</c> option
/// bits (folded into the <c>DB3</c> option byte) and the <c>CVAdr_LSB</c> wire byte. POM addresses
/// CVs through a 10-bit field, so only CV addresses 0..1023 (CV1..CV1024) are representable.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="cvAddress"/> exceeds 1023.</exception>
(byte cvHighBits, byte cvLsb) SplitPomCvAddress(ushort cvAddress);

/// <summary>
/// Encodes an accessory decoder address for POM commands into the two wire bytes
/// <c>aaaaa</c> / <c>AAAACDDD</c>. When <paramref name="wholeDecoder"/> is true the CV refers to the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class CvPomAccessoryReadByteCommand : IZ21Command
public CvPomAccessoryReadByteCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort decoderAddress, bool wholeDecoder, byte output, ushort cvAddress)
{
(byte db1, byte db2) = addressCodec.EncodeAccessoryPomAddress(decoderAddress, wholeDecoder, output);
byte db3 = (byte)(0xE4 | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xE4 | cvHighBits);
Data = frameBuilder.BuildXBus(0xE6, 0x31, db1, db2, db3, cvLsb, 0x00);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ public class CvPomAccessoryWriteBitCommand : IZ21Command
public CvPomAccessoryWriteBitCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort decoderAddress, bool wholeDecoder, byte output, ushort cvAddress, byte bitPosition, bool bitValue)
{
(byte db1, byte db2) = addressCodec.EncodeAccessoryPomAddress(decoderAddress, wholeDecoder, output);
byte db3 = (byte)(0xE8 | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
byte db5 = (byte)((bitValue ? 0x08 : 0x00) | (bitPosition & 0x07));
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xE8 | cvHighBits);
// DB5 is the DCC bit-manipulation data byte 1111VPPP (S-9.2.1): high nibble 0xF0 marks a write
// (the "111K" opcode with K=1), V = new bit value, PPP = bit position.
byte db5 = (byte)(0xF0 | (bitValue ? 0x08 : 0x00) | (bitPosition & 0x07));
Data = frameBuilder.BuildXBus(0xE6, 0x31, db1, db2, db3, cvLsb, db5);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class CvPomAccessoryWriteByteCommand : IZ21Command
public CvPomAccessoryWriteByteCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort decoderAddress, bool wholeDecoder, byte output, ushort cvAddress, byte value)
{
(byte db1, byte db2) = addressCodec.EncodeAccessoryPomAddress(decoderAddress, wholeDecoder, output);
byte db3 = (byte)(0xEC | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xEC | cvHighBits);
Data = frameBuilder.BuildXBus(0xE6, 0x31, db1, db2, db3, cvLsb, value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public class CvPomReadByteCommand : IZ21Command
public CvPomReadByteCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort locoAddress, ushort cvAddress)
{
(byte lsb, byte msb) = addressCodec.SplitLocoAddress(locoAddress);
byte db3 = (byte)(0xE4 | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xE4 | cvHighBits);
Data = frameBuilder.BuildXBus(0xE6, 0x30, msb, lsb, db3, cvLsb, 0x00);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ public class CvPomWriteBitCommand : IZ21Command
public CvPomWriteBitCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort locoAddress, ushort cvAddress, byte bitPosition, bool bitValue)
{
(byte lsb, byte msb) = addressCodec.SplitLocoAddress(locoAddress);
byte db3 = (byte)(0xE8 | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
byte db5 = (byte)((bitValue ? 0x08 : 0x00) | (bitPosition & 0x07));
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xE8 | cvHighBits);
// DB5 is the DCC bit-manipulation data byte 1111VPPP (S-9.2.1): high nibble 0xF0 marks a write
// (the "111K" opcode with K=1), V = new bit value, PPP = bit position.
byte db5 = (byte)(0xF0 | (bitValue ? 0x08 : 0x00) | (bitPosition & 0x07));
Data = frameBuilder.BuildXBus(0xE6, 0x30, msb, lsb, db3, cvLsb, db5);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class CvPomWriteByteCommand : IZ21Command
public CvPomWriteByteCommand(IZ21FrameBuilder frameBuilder, IAddressCodec addressCodec, ushort locoAddress, ushort cvAddress, byte value)
{
(byte lsb, byte msb) = addressCodec.SplitLocoAddress(locoAddress);
byte db3 = (byte)(0xEC | ((cvAddress >> 8) & 0x03));
byte cvLsb = (byte)(cvAddress & 0xFF);
(byte cvHighBits, byte cvLsb) = addressCodec.SplitPomCvAddress(cvAddress);
byte db3 = (byte)(0xEC | cvHighBits);
Data = frameBuilder.BuildXBus(0xE6, 0x30, msb, lsb, db3, cvLsb, value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class CvResultResponseHandler(IAddressCodec addressCodec) : ICvResultResp
public string Name => "LAN_X_CV_RESULT";

public bool CanHandle(byte[] response) =>
((IZ21ResponseHandler)this).MatchesFrame(response, 6, (2, 0x40), (3, 0x00), (4, 0x64), (5, 0x14));
((IZ21ResponseHandler)this).MatchesFrame(response, 9, (2, 0x40), (3, 0x00), (4, 0x64), (5, 0x14));

public void Handle(byte[] response)
{
Expand Down
Loading