Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasNameRecordTests.cs
T
Joseph Doherty 9460da513c fix(focas): decode all four bytes of an axis/spindle name record, up to the first NUL
ReadNameRecord read only the first 2 bytes of each 4-byte 0x0089/0x008A record,
so the real 31i-B's paired axes 'AA6'/'AA7' (live capture ref-axisname-0x0089.bin)
both decoded as 'AA' -- the 'Makino with two AA axes' that #548 then had to
disambiguate as AA_1/AA_2. The name is now the record's ASCII up to its FIRST
NUL (not trailing-NUL trimming), so buffer residue such as Z-34184's spindle
record 53 31 00 4e ('S1' + an 'N' left from an earlier 'CNC') is discarded.

Client-visible: on such a machine Axes/AA_1 and Axes/AA_2 become Axes/AA6 and
Axes/AA7 (the old names were a decode artefact). #548's disambiguation stays as
the guard for genuinely duplicate names.

Ported from the sister ignitionoee FOCAS driver (Focas.Wire-001, Focas.Wire-022
/ v0.18.1).
2026-10-07 13:58:40 -04:00

79 lines
3.2 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Wire;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
/// <summary>
/// Axis-name (0x0089) / spindle-name (0x008A) record decode. Each record is 4 bytes; the name is
/// the ASCII up to the record's FIRST NUL. Ported from the sister ignitionoee FOCAS driver
/// (Focas.Wire-001 and Focas.Wire-022, v0.18.1).
/// </summary>
/// <remarks>
/// <para><b>Reading only 2 bytes collides real names.</b> The live 31i-B capture
/// (<c>ref-axisname-0x0089.bin</c>) carries <c>"AA6"</c> and <c>"AA7"</c>; the old 2-byte read
/// turned both into <c>"AA"</c>, which is exactly the "one observed Makino returns two axis
/// folders both called AA" that Gitea #548 then had to disambiguate as <c>AA_1</c>/<c>AA_2</c>.</para>
/// <para><b>Trailing-only trimming keeps buffer residue.</b> A control writes only the bytes the
/// name needs; on Makino Z-34184 (2026-08-19) <c>cnc_rdspdlname</c> came back
/// <c>53 31 00 4e</c> — <c>"S1"</c> then the <c>'N'</c> of an earlier <c>"CNC"</c>. The name
/// ends at the first NUL.</para>
/// </remarks>
[Trait("Category", "Unit")]
public sealed class FocasNameRecordTests
{
[Fact]
public void The_live_axis_name_capture_keeps_three_character_names_distinct()
{
var payload = WireFixtures.Payload("ref-axisname-0x0089.bin", 0x0089);
var names = FocasWireClient.ReadNameRecords(payload, 32, (index, name) => (index, name));
names.Select(n => n.name).ShouldBe(["X", "Y", "Z", "B", "C", "AA6", "AA7"]);
names.Select(n => n.index).ShouldBe([(short)1, 2, 3, 4, 5, 6, 7]);
}
[Fact]
public void The_live_spindle_name_capture_decodes()
{
var payload = WireFixtures.Payload("ref-spdlname-0x008a.bin", 0x008a);
FocasWireClient.ReadNameRecords(payload, 8, (_, name) => name).ShouldBe(["S1"]);
}
[Fact]
public void A_name_ends_at_its_first_NUL_so_residue_after_it_is_discarded()
{
// The Z-34184 spindle-name record: "S1", NUL, then an 'N' left over from a previous "CNC".
FocasWireProtocol.ReadNameRecord([0x53, 0x31, 0x00, 0x4e]).ShouldBe("S1");
// A single-character name with residue in BOTH trailing bytes.
FocasWireProtocol.ReadNameRecord([0x58, 0x00, 0x43, 0x4e]).ShouldBe("X");
}
[Fact]
public void All_four_record_bytes_are_name_bytes()
{
FocasWireProtocol.ReadNameRecord("AA6\0"u8.ToArray()).ShouldBe("AA6");
FocasWireProtocol.ReadNameRecord("ABCD"u8.ToArray()).ShouldBe("ABCD");
FocasWireProtocol.ReadNameRecord("X \0\0"u8.ToArray()).ShouldBe("X");
}
[Fact]
public void A_record_that_starts_with_NUL_is_blank()
{
FocasWireProtocol.ReadNameRecord([0x00, 0x41, 0x41, 0x00]).ShouldBe(string.Empty);
}
[Fact]
public void Servo_meter_names_come_out_whole_too()
{
var blocks = WireFixtures.Blocks("svmeter-pair-0x0056-0x0089.bin");
var sv = blocks.First(b => b.Command == 0x0056).Payload;
var names = blocks.First(b => b.Command == 0x0089).Payload;
var meters = FocasWireClient.ParseServoMeters(sv, names, maxCount: 32);
meters.Take(7).Select(m => m.Name).ShouldBe(["X", "Y", "Z", "B", "C", "AA6", "AA7"]);
}
}