Skip to content

Commit beb60fb

Browse files
committed
Change the callback signature from Action<string, string, Level> to a
named BitfieldChangedCallback delegate declared alongside IModbusClient. The callback now passes both the previous and current enum values as boxed objects instead of a pre-computed description string. Consumers can call ToString() for flag names, cast to the typed enum, or convert to ulong for the numeric value. Add braces around all generated if statement bodies.
1 parent e03d799 commit beb60fb

3 files changed

Lines changed: 33 additions & 15 deletions

File tree

Modspec.Model/Generation/ModspecModelGenerator.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ namespace {schema.Name};
110110
if (groupHasChangeDetection)
111111
{
112112
hasChangeDetection = true;
113-
mainWriter.WriteLine("\t\tprivate readonly Action<string, string, Level>? _onBitfieldChanged;");
113+
mainWriter.WriteLine("\t\tprivate readonly BitfieldChangedCallback? _onBitfieldChanged;");
114114
}
115115
groupFieldInitialisers.Add("_offset = offset;");
116116
if (groupHasChangeDetection)
@@ -148,7 +148,7 @@ namespace {schema.Name};
148148

149149
if (hasChangeDetection)
150150
{
151-
mainWriter.WriteLine("\tprivate readonly Action<string, string, Level>? _onBitfieldChanged;");
151+
mainWriter.WriteLine("\tprivate readonly BitfieldChangedCallback? _onBitfieldChanged;");
152152
}
153153
WriteFieldsAndConstructor(schema.Name + "Client", mainWriter, bufferInitialisers, fieldInitialisers, constructorParams, hasChangeDetection: hasChangeDetection);
154154
mainWriter.WriteLine("}");
@@ -169,7 +169,7 @@ private static void WriteFieldsAndConstructor(string name, StringWriter mainWrit
169169
}
170170
if (hasChangeDetection)
171171
{
172-
mainWriter.Write(", Action<string, string, Level>? onBitfieldChanged = null");
172+
mainWriter.Write(", BitfieldChangedCallback? onBitfieldChanged = null");
173173
}
174174
mainWriter.WriteLine(")");
175175
mainWriter.WriteLine($"{indent}\t{{");
@@ -240,7 +240,10 @@ private static void WriteGroups(IReadOnlyCollection<Group> groups, StringWriter
240240
mainWriter.WriteLine($"\t\t{indent}await _client.Read{group.Table}Async({readOffsetField}{group.BaseRegister}, {bufferName});");
241241
if (groupBitfieldPoints.Count > 0)
242242
{
243-
mainWriter.WriteLine($"\t\t{indent}if (_onBitfieldChanged is not null) Check{group.Name}();");
243+
mainWriter.WriteLine($"\t\t{indent}if (_onBitfieldChanged is not null)");
244+
mainWriter.WriteLine($"\t\t{indent}{{");
245+
mainWriter.WriteLine($"\t\t\t{indent}Check{group.Name}();");
246+
mainWriter.WriteLine($"\t\t{indent}}}");
244247
}
245248
mainWriter.WriteLine($"\t{indent}}}");
246249
mainWriter.WriteLine();
@@ -250,7 +253,10 @@ private static void WriteGroups(IReadOnlyCollection<Group> groups, StringWriter
250253
mainWriter.WriteLine($"\t\t{indent}_client.Read{group.Table}({readOffsetField}{group.BaseRegister}, {bufferName}.Span);");
251254
if (groupBitfieldPoints.Count > 0)
252255
{
253-
mainWriter.WriteLine($"\t\t{indent}if (_onBitfieldChanged is not null) Check{group.Name}();");
256+
mainWriter.WriteLine($"\t\t{indent}if (_onBitfieldChanged is not null)");
257+
mainWriter.WriteLine($"\t\t{indent}{{");
258+
mainWriter.WriteLine($"\t\t\t{indent}Check{group.Name}();");
259+
mainWriter.WriteLine($"\t\t{indent}}}");
254260
}
255261
mainWriter.WriteLine($"\t{indent}}}");
256262
mainWriter.WriteLine();
@@ -265,7 +271,8 @@ private static void WriteGroups(IReadOnlyCollection<Group> groups, StringWriter
265271
{
266272
mainWriter.WriteLine($"\t\t{indent}if (!current.Slice({bp.Offset}, {bp.SizeInBytes}).SequenceEqual(previous.Slice({bp.Offset}, {bp.SizeInBytes})))");
267273
mainWriter.WriteLine($"\t\t{indent}{{");
268-
mainWriter.WriteLine($"\t\t\t{indent}_onBitfieldChanged!(\"{bp.PointName}\", {bp.PointName}.ToString(), {bp.PointName}.GetLevel());");
274+
mainWriter.WriteLine($"\t\t\t{indent}{bp.PointName} oldValue = ({bp.PointName}){bp.ReadMethod}(previous.Slice({bp.Offset}, {bp.SizeInBytes}));");
275+
mainWriter.WriteLine($"\t\t\t{indent}_onBitfieldChanged!(\"{bp.PointName}\", oldValue, {bp.PointName}, {bp.PointName}.GetLevel());");
269276
mainWriter.WriteLine($"\t\t{indent}}}");
270277
}
271278
mainWriter.WriteLine($"\t\t{indent}current.CopyTo(previous);");
@@ -403,7 +410,7 @@ private static void WritePoint(Point point, string bufferName, Table table, Stri
403410
appendixWriter.WriteLine();
404411
if (isFlags && masksByLevel.Count > 0)
405412
{
406-
groupBitfieldPoints?.Add(new BitfieldPointInfo(point.Name, maxOffset, point.SizeInBytes));
413+
groupBitfieldPoints?.Add(new BitfieldPointInfo(point.Name, maxOffset, point.SizeInBytes, readMethod));
407414
appendixWriter.WriteLine($"public static class {point.Name}Extensions");
408415
appendixWriter.WriteLine("{");
409416
appendixWriter.WriteLine($"\tpublic static Level GetLevel(this {point.Name} self)");
@@ -535,5 +542,5 @@ private static string Pluralise(string self)
535542
}
536543

537544
private record ConstructorParameter(string Name, int Count);
538-
private record BitfieldPointInfo(string PointName, int Offset, int SizeInBytes);
545+
private record BitfieldPointInfo(string PointName, int Offset, int SizeInBytes, string ReadMethod);
539546
}

Modspec.Model/IModbusClient.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88

99
namespace Modspec.Model;
1010

11+
/// <summary>
12+
/// Callback invoked when a bitfield point with level annotations changes between reads.
13+
/// </summary>
14+
/// <param name="name">The name of the bitfield point that changed.</param>
15+
/// <param name="oldValue">The previous value of the bitfield (boxed enum).</param>
16+
/// <param name="newValue">The current value of the bitfield (boxed enum).</param>
17+
/// <param name="level">The highest severity level among the currently set flags.</param>
18+
public delegate void BitfieldChangedCallback(string name, object oldValue, object newValue, Level level);
19+
1120
/// <summary>
1221
/// Interface for a Modbus client.
1322
/// </summary>

Modspec.Test/Tests.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ public void TestErrorLevels()
9292
public async Task TestInlineChangeDetectionNoChange()
9393
{
9494
MockModbusClient mockClient = new MockModbusClient();
95-
List<(string name, string description, Level level)> changes = [];
95+
List<(string name, object oldValue, object newValue, Level level)> changes = [];
9696
SomeBmsClient bmsClient = new SomeBmsClient(mockClient, 2, 2, 480, 100,
97-
onBitfieldChanged: (name, desc, level) => changes.Add((name, desc, level)));
97+
onBitfieldChanged: (name, oldVal, newVal, level) => changes.Add((name, oldVal, newVal, level)));
9898

9999
// no errors — no callback
100100
await bmsClient.ReadWarningsErrorsEmergenciesAsync();
@@ -109,16 +109,18 @@ public async Task TestInlineChangeDetectionNoChange()
109109
public async Task TestInlineChangeDetectionDetectsChange()
110110
{
111111
MockModbusClient mockClient = new MockModbusClient();
112-
List<(string name, string description, Level level)> changes = [];
112+
List<(string name, object oldValue, object newValue, Level level)> changes = [];
113113
SomeBmsClient bmsClient = new SomeBmsClient(mockClient, 2, 2, 480, 100,
114-
onBitfieldChanged: (name, desc, level) => changes.Add((name, desc, level)));
114+
onBitfieldChanged: (name, oldVal, newVal, level) => changes.Add((name, oldVal, newVal, level)));
115115

116116
// introduce an error and read
117117
mockClient.DiscreteInputs.Span[1] = 0b10000000; // StringTerminalDischargeOverCurrentError
118118
await bmsClient.ReadWarningsErrorsEmergenciesAsync();
119119
Assert.That(changes, Has.Count.EqualTo(1));
120120
Assert.That(changes[0].name, Is.EqualTo("StringErrors1"));
121-
Assert.That(changes[0].description, Does.Contain("StringTerminalDischargeOverCurrentError"));
121+
Assert.That(changes[0].oldValue, Is.EqualTo((StringErrors1)0));
122+
Assert.That(changes[0].newValue, Is.EqualTo(StringErrors1.StringTerminalDischargeOverCurrentError));
123+
Assert.That(changes[0].newValue.ToString(), Does.Contain("StringTerminalDischargeOverCurrentError"));
122124
Assert.That(changes[0].level, Is.EqualTo(Level.Error));
123125

124126
// same state again — no callback
@@ -131,9 +133,9 @@ public async Task TestInlineChangeDetectionDetectsChange()
131133
public async Task TestInlineChangeDetectionReportsHighestLevel()
132134
{
133135
MockModbusClient mockClient = new MockModbusClient();
134-
List<(string name, string description, Level level)> changes = [];
136+
List<(string name, object oldValue, object newValue, Level level)> changes = [];
135137
SomeBmsClient bmsClient = new SomeBmsClient(mockClient, 2, 2, 480, 100,
136-
onBitfieldChanged: (name, desc, level) => changes.Add((name, desc, level)));
138+
onBitfieldChanged: (name, oldVal, newVal, level) => changes.Add((name, oldVal, newVal, level)));
137139

138140
// set both a warning (bit 0) and an emergency (bit 2) on StringErrors1
139141
mockClient.DiscreteInputs.Span[1] = 0b00000101;

0 commit comments

Comments
 (0)