Added FLIP MODE Constant, Randomised

Add support for Flip Mode configuration in iPerl: Implement `SetFlipMode` functionality in `OptoHeadTest` and `RadioService`, integrate constants, enhance UI, and add unit and integration tests for constant/randomized flip mode settings.
This commit is contained in:
Michal Buzik 2026-08-18 13:59:59 +02:00
parent c71fe75446
commit 8738edd0c5
11 changed files with 508 additions and 20 deletions

View File

@ -48,6 +48,8 @@ namespace TBF.Rig.TestMethods.SmartMeterFlyingStartMassCollection
var retVal = new List<string>();
retVal.Add(iPerlCommunicationConstants.ReadConfigurationStr);
retVal.Add(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr);
retVal.Add(iPerlCommunicationConstants.SetFlipModeConstantStr);
retVal.Add(iPerlCommunicationConstants.SetFlipModeRandomizedStr);
retVal.Add(string.Format("{0} A0", iPerlCommunicationConstants.SetTestModeStr));
retVal.Add(string.Format("{0} A4", iPerlCommunicationConstants.SetTestModeStr));
retVal.Add(iPerlCommunicationConstants.ReadCalibrationStr);

View File

@ -2,7 +2,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommon
{
public enum FlipMode : byte
{
Disabled = 0x00,
Enabled = 0x01
DisabledConstant = 0x00, //Constant
EnabledRandomized = 0x01 //Randomized
}
}

View File

@ -173,6 +173,14 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommon
/// </summary>
ViewFlipMode = 0xB9,
/// <summary>
/// Set randomized field flip-rate mode.
/// Payload:
/// 0x00 = constant flip rate,
/// 0x01 = randomized flip rate.
/// </summary>
SetFlipMode = 0xBA,
// ==========================================================
// Diagnostic LED / Hardware
// ==========================================================

View File

@ -568,6 +568,59 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
}
}
/// <summary>
/// Sets constant (0x00) or randomized (0x01) field flip-rate mode.
/// </summary>
public bool SetFlipMode(FlipMode flipMode)
{
if (iperlHead == null)
{
log.Warn("SetFlipMode: IperlHead is null.");
return false;
}
if (!Enum.IsDefined(typeof(FlipMode), flipMode))
{
log.WarnFormat("SetFlipMode: Unsupported value 0x{0:X2}.", (byte)flipMode);
return false;
}
if (iperlHead.DebugLevel == DebugMode.Simulate)
{
if (iperlHead.ConfigStruct == null)
iperlHead.ConfigStruct = new ConfigStruct();
iperlHead.ConfigStruct.FlipMode = flipMode;
return true;
}
try
{
if (serialDriver == null)
serialDriver = BuildConnection(iperlHead);
log.DebugFormat(
"SetFlipMode started: Head={0}, Mode={1} (0x{2:X2})",
iperlHead,
flipMode,
(byte)flipMode);
var headService = new RadioService(serialDriver);
bool successful = headService.SetFlipMode(iperlHead, flipMode);
log.InfoFormat(
"SetFlipMode finished: Head={0}, Mode={1}, successful={2}",
iperlHead,
flipMode,
successful);
return successful;
}
catch (Exception ex)
{
log.Error("SetFlipMode failed.", ex);
return false;
}
}
/// <summary>
/// Reads the calibration factor.
/// </summary>

View File

@ -361,6 +361,46 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.communication
}
}
public bool SetFlipMode(IperlHead iHead, FlipMode flipMode)
{
if (!Enum.IsDefined(typeof(FlipMode), flipMode))
throw new ArgumentOutOfRangeException(nameof(flipMode));
if (!serialDriver.IsOpen())
serialDriver.Open();
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetFlipMode)
.AddPayload((byte)flipMode)
.BuildBytes();
byte[] rawData = serialDriver.SendAndWait(request, 5000);
if (rawData == null || rawData.Length == 0)
{
log.Warn("SetFlipMode: No response received.");
return false;
}
var parser = new IperlHatFrameParser();
IperlHatResponse decoded = parser.Parse(rawData);
log.Debug(
string.Format(
"SetFlipMode({0}, 0x{1:X2}) isOK: {2}",
flipMode,
(byte)flipMode,
decoded.IsOk));
if (!decoded.IsOk)
return false;
if (iHead != null && iHead.ConfigStruct != null)
iHead.ConfigStruct.FlipMode = flipMode;
return true;
}
public CalibrationFactorResult ViewCalibration( IperlHead iHead)
{
if (!serialDriver.IsOpen())

View File

@ -239,6 +239,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add(NewMenuItem("Read PCB Number" , "ReadPCB"));
cm.MenuItems.Add(NewMenuItem( iPerlCommunicationConstants.ReadAdditionalCommonParametersStr, "ReadAdditionalCommonParameters"));
cm.MenuItems.Add(NewMenuItem( iPerlCommunicationConstants.SetFlipModeConstantStr, "SetFlipModeConstant"));
cm.MenuItems.Add(NewMenuItem( iPerlCommunicationConstants.SetFlipModeRandomizedStr, "SetFlipModeRandomized"));
cm.MenuItems.Add(NewMenuItem("Enter Test Mode" , "StartTestMode"));
cm.MenuItems.Add(NewMenuItem("Turn Off Test Mode (Enter Active Mode)", "TurnOffTestMode"));
cm.MenuItems.Add(NewMenuItem("Set Production Mode (Radio not start with flow)" , "SetProductionMode"));
@ -506,7 +508,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
}
///
/// Set checkbox states accroding to iPerlHeads[i].Disabled states
/// Set checkbox states accroding to iPerlHeads[i].Disabled_Constant states
///
for (int i = 0; i < textBoxesCount; i++)
{
@ -718,6 +720,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
else if (currentActivity.ToLower().Equals(SetIdleModeStr.ToLower())) error = SetIdleMode(threadID, ihead, ref resultStr);
else if (currentActivity.ToLower().Contains(ReadConfigurationStr.ToLower())) error = ReadConfiguration(ihead, wm, ref resultStr);
else if (currentActivity.ToLower().Contains( iPerlCommunicationConstants.ReadAdditionalCommonParametersStr.ToLower())) error = ReadAdditionalCommonParameters( ihead,wm, ref resultStr);
else if (currentActivity.ToLower().Contains( iPerlCommunicationConstants.SetFlipModeConstantStr.ToLower())) error = SetFlipModeConstant( ihead,wm, ref resultStr);
else if (currentActivity.ToLower().Contains( iPerlCommunicationConstants.SetFlipModeRandomizedStr.ToLower())) error = SetFlipModeRandomized( ihead,wm, ref resultStr);
///
/// RFID communication functions below require a reference to water meter entity (wm != null)
///
@ -810,6 +814,75 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
}
}
private CommErr SetFlipModeRandomized(IperlHead ihead, WaterMeter wm, ref string resultStr)
{
return SetFlipMode(
ihead,
FlipMode.EnabledRandomized,
iPerlCommunicationConstants.SetFlipModeRandomizedStr,
ref resultStr);
}
private CommErr SetFlipModeConstant(IperlHead ihead, WaterMeter wm, ref string resultStr)
{
return SetFlipMode(
ihead,
FlipMode.DisabledConstant,
iPerlCommunicationConstants.SetFlipModeConstantStr,
ref resultStr);
}
private CommErr SetFlipMode(
IperlHead ihead,
FlipMode flipMode,
string activityName,
ref string resultStr)
{
if (ihead == null)
{
resultStr = activityName + ": iPerl head is null.";
return CommErr.CommFailed;
}
if (ihead.OptoHeadTest == null)
{
resultStr = activityName + ": OptoHeadTest is not available.";
return CommErr.CommFailed;
}
ihead.CommFailed = false;
try
{
log.DebugFormat(
"{0} started: Head={1}, payload=0x{2:X2}",
activityName,
ihead,
(byte)flipMode);
bool successful = ihead.OptoHeadTest.SetFlipMode(flipMode);
if (successful)
{
resultStr = string.Format(
"{0}: OK (0x{1:X2})",
activityName,
(byte)flipMode);
return CommErr.None;
}
resultStr = activityName + ": FAILED";
ihead.CommFailed = true;
return CommErr.Write;
}
catch (Exception ex)
{
resultStr = activityName + " failed: " + ex.Message;
ihead.CommFailed = true;
log.Error(activityName + " failed.", ex);
return CommErr.Write;
}
}
private CommErr ReadSerialNr(int threadId, IperlHead ihead, ref string resultStr)
{
log.Debug("ReadSerialNr threadId=" + threadId + ", ihead=" + ihead.ToString());

View File

@ -53,6 +53,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication
var retVal = new List<string>();
retVal.Add(iPerlCommunicationForm.ReadConfigurationStr);
retVal.Add(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr);
retVal.Add(iPerlCommunicationConstants.SetFlipModeConstantStr);
retVal.Add(iPerlCommunicationConstants.SetFlipModeRandomizedStr);
retVal.Add(iPerlCommunicationForm.ReadSerialNrStr);
retVal.Add(string.Format("{0} A0", iPerlCommunicationForm.SetTestModeStr));
retVal.Add(string.Format("{0} A4", iPerlCommunicationForm.SetTestModeStr));

View File

@ -5,6 +5,7 @@ using System.Web.UI.WebControls;
using System.Windows.Forms;
using TBF.Rig.Sequences;
using TBF.Rig.TestMethods.iPerlCommunication.communication;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
{
@ -38,6 +39,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
new {Name = "Read Additional Common Parameters", Value = "ReadAdditionalCommonParameters" },
new {Name = "Set Test Mode", Value = "SetTestMode" },
new {Name = "Set Active Mode", Value = "SetActiveMode" },
new {Name = "Set Flip Mode Constant", Value = "SetFlipModeConstant" },
new {Name = "Set Flip Mode Randomized", Value = "SetFlipModeRandomized" },
#if DEBUG
new {Name = "Start Read Opto Data", Value = "ReadOptoData" },
new {Name = "Stop Read Opto Data", Value = "StopReadOptoData" },
@ -143,6 +146,34 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
stopWorkerThread = true;
iPerlHead.StopDataStreamProcessing(); // close opto port
break;
case "SetFlipModeConstant":
{
if (iPerlHead == null || iPerlHead.OptoHeadTest == null)
{
rfidListItem.Text = "Set Flip Mode Constant (0x00) - FAILED: iPerl head is not available.";
break;
}
bool successful = iPerlHead.OptoHeadTest.SetFlipMode(FlipMode.DisabledConstant);
rfidListItem.Text = successful
? "Set Flip Mode Constant (0x00) - OK"
: "Set Flip Mode Constant (0x00) - FAILED";
break;
}
case "SetFlipModeRandomized":
{
if (iPerlHead == null || iPerlHead.OptoHeadTest == null)
{
rfidListItem.Text = "Set Flip Mode Randomized (0x01) - FAILED: iPerl head is not available.";
break;
}
bool successful = iPerlHead.OptoHeadTest.SetFlipMode(FlipMode.EnabledRandomized);
rfidListItem.Text = successful
? "Set Flip Mode Randomized (0x01) - OK"
: "Set Flip Mode Randomized (0x01) - FAILED";
break;
}
case "ResetNfcHead":
iPerlHead.ResetNfcInterface();
break;

View File

@ -13,6 +13,8 @@ namespace TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication
public const string ReadAdditionalCommonParametersStr = "Read Additional Common Parameters";
public const string SetTestModeStr = "Set Test mode"; /// Example: "Set Test mode" or "Set Test mode A0" (hexadecimal number is the required 'testModeConfig'
public const string SetActiveModeStr = "Set Active mode";
public const string SetFlipModeConstantStr = "Set Flip Mode Constant";
public const string SetFlipModeRandomizedStr = "Set Flip Mode Randomized";
public const string ReadCalibrationStr = "Read calibration";
public const string ReadCalibrationV4Str = "Read calibration_V4";
public const string WriteCalibrationFactorStr = "Write calibration factor";

View File

@ -537,6 +537,226 @@ namespace TBFTests.Rig.RegisterReaders.iPerlASICReader.communication.C4.IperlHat
}
}
[TestMethod]
[TestCategory("Hardware")]
[TestCategory("Serial")]
public void Serial_SetFlipModes_ActiveOptical_Idle_VerifyReadBack()
{
using (var port = new SerialPort(
ComPort,
BaudRate,
Parity.None,
8,
StopBits.One))
{
port.Handshake = Handshake.None;
port.ReadTimeout = ReadTimeoutMs;
port.WriteTimeout = ReadTimeoutMs;
Console.WriteLine(
"Flip-mode integration: command={0}/{1}, optical={2}/{3}",
ComPort,
BaudRate,
ComPortOptho,
BaudRateOpto);
port.Open();
bool opticalOutputEnabled = false;
bool mainTestCompleted = false;
Exception cleanupFailure = null;
try
{
SetAndVerifyMeterState(port, ProtocolStatuses.Idle);
SetAndVerifyFlipMode(port, FlipMode.DisabledConstant, "Constant");
SetAndVerifyMeterState(port, ProtocolStatuses.Active);
SetDiagnosticLed(port, DiagnosticLedState.State4);
opticalOutputEnabled = true;
Console.WriteLine(
"Reading optical packets from {0} while the meter is Active...",
ComPortOptho);
ReadAndVerifyOpticalPacket();
SetAndVerifyFlipMode(port, FlipMode.EnabledRandomized, "Randomized");
mainTestCompleted = true;
}
finally
{
if (opticalOutputEnabled && port.IsOpen)
{
try
{
SetDiagnosticLed(port, DiagnosticLedState.StateOFF);
}
catch (Exception ex)
{
cleanupFailure = ex;
Console.WriteLine("CLEANUP FAIL: optical output could not be disabled: " + ex);
}
}
if (port.IsOpen)
{
try
{
SetAndVerifyMeterState(port, ProtocolStatuses.Idle);
}
catch (Exception ex)
{
if (cleanupFailure == null)
cleanupFailure = ex;
Console.WriteLine("CLEANUP FAIL: Idle state could not be restored: " + ex);
}
}
if (mainTestCompleted && cleanupFailure != null)
{
Assert.Fail(
"Main flip-mode scenario passed, but cleanup failed: " +
cleanupFailure.Message);
}
}
}
}
private static void SetAndVerifyFlipMode(
SerialPort port,
FlipMode expectedMode,
string modeName)
{
byte[] setRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetFlipMode)
.AddPayload((byte)expectedMode)
.BuildBytes();
SendRequest(
port,
setRequest,
"SetFlipMode" + modeName,
string.Format(
"Sets flip mode to {0}; payload=0x{1:X2}.",
modeName,
(byte)expectedMode));
byte[] viewRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.ViewFlipMode)
.BuildBytes();
IperlHatResponse viewResponse = SendRequest(
port,
viewRequest,
"ViewFlipMode after " + modeName,
"Reads flip mode back to verify the SetFlipMode command.");
FlipMode actualMode = viewResponse.GetResponse<FlipMode>(out bool responseOk);
Assert.IsTrue(
responseOk,
"ViewFlipMode returned an invalid payload after setting " + modeName + ".");
Assert.AreEqual(
expectedMode,
actualMode,
string.Format(
"Flip-mode verification failed. Expected {0} (0x{1:X2}), actual {2} (0x{3:X2}).",
modeName,
(byte)expectedMode,
actualMode,
(byte)actualMode));
Console.WriteLine(
"VERIFY PASS: FlipMode={0}, value=0x{1:X2}",
modeName,
(byte)actualMode);
}
private static void SetAndVerifyMeterState(
SerialPort port,
ProtocolStatuses expectedState)
{
byte[] setRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.SetState)
.AddSubCommand(expectedState)
.BuildBytes();
SendRequest(
port,
setRequest,
"SetState " + expectedState,
"Sets the meter activity state to " + expectedState + ".");
byte[] viewRequest = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddCommand(ProtocolCommand.ViewState)
.BuildBytes();
IperlHatResponse viewResponse = SendRequest(
port,
viewRequest,
"ViewState after " + expectedState,
"Reads the activity state back to verify SetState.");
ProtocolStatuses actualState =
viewResponse.GetResponse<ProtocolStatuses>(out bool responseOk);
Assert.IsTrue(
responseOk,
"ViewState returned an invalid payload after setting " + expectedState + ".");
Assert.AreEqual(
expectedState,
actualState,
"Meter-state verification failed.");
Console.WriteLine("VERIFY PASS: MeterState=" + actualState);
}
private static void SetDiagnosticLed(
SerialPort port,
DiagnosticLedState state)
{
byte[] request = new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState)
.AddPayload(state)
.BuildBytes();
SendRequest(
port,
request,
"SetDiagnosticLEDState " + state,
"Controls optical output on " + ComPortOptho + ".");
}
private static void ReadAndVerifyOpticalPacket()
{
using (var opticalPort = new SerialPort(
ComPortOptho,
BaudRateOpto,
Parity.None,
8,
StopBits.One))
{
opticalPort.Handshake = Handshake.None;
opticalPort.ReadTimeout = 10000;
opticalPort.NewLine = "\r\n";
opticalPort.Encoding = Encoding.ASCII;
opticalPort.Open();
string line = opticalPort.ReadLine();
string opticalHex = FormatOpticalHexPacket(line);
Console.WriteLine("OPTO HEX COM13 <- " + opticalHex);
var parser = new DiagnosticLedParser(DiagnosticLedState.State4);
DiagnosticLedState4Data parsed =
(DiagnosticLedState4Data)parser.ParseLine(line, false);
Assert.IsNotNull(parsed, "COM13 optical parser returned null.");
Console.WriteLine("OPTO PARSE PASS COM13: " + parsed);
}
}
private static IperlHatResponse SendRequest(
SerialPort port,
byte[] request,

View File

@ -4,7 +4,6 @@ using JetBrains.Annotations;
using TBF.Rig.TestMethods.iPerlCommunication.communication;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.wiredProtocol;
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
@ -224,25 +223,16 @@ namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
public void ViewFlipMode_DisabledResponse_ReturnsDisabled()
{
// Arrange
serialDriver.Response =
IperlResponseFactory.CreateOkResponse(
(byte)FlipMode.Disabled);
serialDriver.Response = IperlResponseFactory.CreateOkResponse( (byte)FlipMode.DisabledConstant);
// Act
FlipMode? result =
service.ViewFlipMode(head);
FlipMode? result = service.ViewFlipMode(head);
// Assert
Assert.IsTrue(result.HasValue);
Assert.AreEqual(
FlipMode.Disabled,
result.Value);
CollectionAssert.AreEqual(
CreateDeviceCommandRequest(
ProtocolDeviceSubCommand.ViewFlipMode),
serialDriver.LastRequest);
Assert.AreEqual( FlipMode.DisabledConstant, result.Value);
CollectionAssert.AreEqual( CreateDeviceCommandRequest( ProtocolDeviceSubCommand.ViewFlipMode), serialDriver.LastRequest);
}
[TestMethod]
@ -251,7 +241,7 @@ namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
// Arrange
serialDriver.Response =
IperlResponseFactory.CreateOkResponse(
(byte)FlipMode.Enabled);
(byte)FlipMode.EnabledRandomized);
// Act
FlipMode? result =
@ -261,7 +251,7 @@ namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
Assert.IsTrue(result.HasValue);
Assert.AreEqual(
FlipMode.Enabled,
FlipMode.EnabledRandomized,
result.Value);
}
@ -295,6 +285,73 @@ namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
Assert.IsFalse(result.HasValue);
}
// ==========================================================
// SetFlipMode
// ==========================================================
[TestMethod]
public void SetFlipMode_Constant_SendsZeroPayloadAndReturnsTrue()
{
serialDriver.Response = IperlResponseFactory.CreateOkResponse();
head.ConfigStruct = new ConfigStruct();
bool result = service.SetFlipMode(head, FlipMode.DisabledConstant);
Assert.IsTrue(result);
Assert.AreEqual(FlipMode.DisabledConstant, head.ConfigStruct.FlipMode.Value);
CollectionAssert.AreEqual(
new byte[] { 0x53, 0x57, 0x07, 0xFD, 0xBA, 0x00, 0x0D },
serialDriver.LastRequest);
Assert.AreEqual(5000, serialDriver.LastTimeout);
}
[TestMethod]
public void SetFlipMode_Randomized_SendsOnePayloadAndReturnsTrue()
{
serialDriver.Response = IperlResponseFactory.CreateOkResponse();
head.ConfigStruct = new ConfigStruct();
bool result = service.SetFlipMode(head, FlipMode.EnabledRandomized);
Assert.IsTrue(result);
Assert.AreEqual(FlipMode.EnabledRandomized, head.ConfigStruct.FlipMode.Value);
CollectionAssert.AreEqual(
new byte[] { 0x53, 0x57, 0x07, 0xFD, 0xBA, 0x01, 0x0D },
serialDriver.LastRequest);
}
[TestMethod]
public void SetFlipMode_NokResponse_ReturnsFalseAndDoesNotChangeConfig()
{
serialDriver.Response = IperlResponseFactory.CreateNokResponse();
head.ConfigStruct = new ConfigStruct
{
FlipMode = FlipMode.DisabledConstant
};
bool result = service.SetFlipMode(head, FlipMode.EnabledRandomized);
Assert.IsFalse(result);
Assert.AreEqual(FlipMode.DisabledConstant, head.ConfigStruct.FlipMode.Value);
}
[TestMethod]
public void SetFlipMode_MissingResponse_ReturnsFalse()
{
serialDriver.Response = null;
bool result = service.SetFlipMode(head, FlipMode.EnabledRandomized);
Assert.IsFalse(result);
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void SetFlipMode_UnsupportedValue_ThrowsArgumentOutOfRangeException()
{
service.SetFlipMode(head, (FlipMode)0x02);
}
// ==========================================================
// ViewCalibration
// ==========================================================
@ -440,7 +497,7 @@ namespace TBFTests.Rig.TestMethods.iPerlCommunication.communication
private static byte[] CreateDeviceCommandRequest(
ProtocolDeviceSubCommand subCommand)
{
return new TouchReadFrameBuilder()
return new IperlHatFrameBuilder()
.RequestResponse(true)
.AddDeviceCommand(subCommand)
.BuildBytes();