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).
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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"]);
|
||||
}
|
||||
}
|
||||
@@ -239,11 +239,12 @@ public sealed class FocasWireProtocolTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadNameRecord_reads_two_bytes_and_strips_padding()
|
||||
public void ReadNameRecord_strips_padding()
|
||||
{
|
||||
// Full record semantics (first NUL, all four bytes) are pinned in FocasNameRecordTests.
|
||||
FocasWireProtocol.ReadNameRecord("X \0\0"u8.ToArray()).ShouldBe("X");
|
||||
FocasWireProtocol.ReadNameRecord("XY"u8.ToArray()).ShouldBe("XY");
|
||||
FocasWireProtocol.ReadNameRecord(new byte[] { 0x58 }).ShouldBe(string.Empty); // < 2 bytes
|
||||
FocasWireProtocol.ReadNameRecord(ReadOnlySpan<byte>.Empty).ShouldBe(string.Empty);
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
|
||||
Reference in New Issue
Block a user