|
|
|
@@ -0,0 +1,829 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.Configuration;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
using LaaPackages.Features.Cordonel.Models;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using SIRTCOM;
|
|
|
|
|
using Xylem.Common.CommonCore.Configuration;
|
|
|
|
|
using Xylem.Common.CommonCore.Consts;
|
|
|
|
|
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
|
|
|
|
using Xylem.Common.Logic.ProductionOrderCore;
|
|
|
|
|
using Xylem.Common.Logic.SoftwareAccessHelper;
|
|
|
|
|
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
|
|
|
|
using Xylem.Common.Production.ProductionUiCordonel.Properties;
|
|
|
|
|
using Xylem.Common.Production.ProductionUiCordonel.UserControls;
|
|
|
|
|
using Button = System.Windows.Controls.Button;
|
|
|
|
|
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
|
|
|
|
|
|
|
|
|
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
|
|
|
|
|
{
|
|
|
|
|
public class CheckRadio : BaseProductionProcess
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
|
|
|
|
|
/// Use the sleep 250 to create x seconds timeout;
|
|
|
|
|
/// </summary>
|
|
|
|
|
private const Int32 TimeoutLoop_ms = 250;
|
|
|
|
|
private const Int32 Timeout_s = 10;
|
|
|
|
|
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Remind the ID of the radio result as feedback from the DB.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Int64 _radioResultCheckId;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Final state of radio system.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Int32 _finalRadioSystemState;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reminder for specific user control
|
|
|
|
|
/// </summary>
|
|
|
|
|
private UcRadioCheck _checkRadioUserControl;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Signal level successfully tested
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Boolean _successSignalLevel;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Radio encryption key converted to string
|
|
|
|
|
/// </summary>
|
|
|
|
|
private String _radioEncryptionKey;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Test finished can be good or abort by done button to exit this production process
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Boolean _testFinished;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// SIRT communication okay
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Boolean _sirtComOkay;
|
|
|
|
|
|
|
|
|
|
private DataContainer _dataContainer;
|
|
|
|
|
private Statemashine _stateMachine;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Minimum PAM signal level of SIRT got from DB
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiPamSirtDbMin;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maximum PAM signal level of SIRT got from DB
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiPamSirtDbMax;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Minimum SEMI signal level of Cordonel response got from DB
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiSemiCordonelDbMin;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maximum SEMI signal level of Cordonel response got from DB
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiSemiCordonelDbMax;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Measured from SIRT the signal strength of the Cordonel SEMI answer
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiSemiCordonelMeasuredBySirt;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Measured from Cordonel the signal strength of the SIRT PAM received
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte? _rssiPamSirtMeasuredByCordonel;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of the SIRT box as these are evaluated for their signal strength
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte _sirtBoxNr;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Comport number of SIRT
|
|
|
|
|
/// </summary>
|
|
|
|
|
private Byte _sirtComport;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Radio address Cordonel
|
|
|
|
|
/// </summary>
|
|
|
|
|
private UInt32 _radioAddressCordonel;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Ctor
|
|
|
|
|
/// </summary>
|
|
|
|
|
public CheckRadio(String name) : base(name)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
protected override void InitUserControl()
|
|
|
|
|
{
|
|
|
|
|
UserControl = new UcRadioCheck();
|
|
|
|
|
_checkRadioUserControl = (UcRadioCheck)UserControl;
|
|
|
|
|
_checkRadioUserControl.OnResetRequested += delegate
|
|
|
|
|
{
|
|
|
|
|
Retry();
|
|
|
|
|
};
|
|
|
|
|
//_checkRadioUserControl.OnDoneRequested += delegate
|
|
|
|
|
//{
|
|
|
|
|
// SaveRssiLevelsToDb();
|
|
|
|
|
//};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check of radio signal strength, skips process on special permission or if NA region
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="2023-Jun-22" author="Thomas Wiedebusch/Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2023-Jun-27" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Skip process on special permission or if NA region,
|
|
|
|
|
/// - Remove userControl from main screen if skipped.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Fire SingleProcessState event with argument.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
|
|
|
|
|
/// these will be automatically set based on return value in
|
|
|
|
|
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Assignment of stateMachine and dataContainer.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2024-Jul-20..22" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Get encryption key moved to process <see cref="ExecFinalParametrization"/> to speed up
|
|
|
|
|
/// this process as this process needs to be run exclusively.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
|
|
|
|
|
/// - EOL progress signaled.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Logout at finalization,
|
|
|
|
|
/// - Log all write accesses.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2025-Jan-21" author="Thomas Wiedebusch">
|
|
|
|
|
/// - SkipRadioCheck not nullable.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2025-Feb-11" author="Thomas Wiedebusch">
|
|
|
|
|
/// - SetTestInit as bool.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2025-Feb-20" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Separate TestInit and SirtInit (only if test shouldn't be skipped).
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
|
|
|
|
/// - CancellationToken.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public override StatusReturn ExecuteProcess()
|
|
|
|
|
{
|
|
|
|
|
// This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress
|
|
|
|
|
if (Meter == null || ProductionRequirements == null ||
|
|
|
|
|
(!Meter.Region.Equals("NA") && RadioEncryptionKey?.Count == 0))
|
|
|
|
|
{
|
|
|
|
|
return StatusReturn.Failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup all parameters being able to execute initially or repeat the test
|
|
|
|
|
TestInit();
|
|
|
|
|
|
|
|
|
|
// Check first if radio is installed,
|
|
|
|
|
// North America Region(NA) doesn't have a Sensus radio application installed
|
|
|
|
|
if (Meter.Region.Equals("NA"))
|
|
|
|
|
{
|
|
|
|
|
EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA;
|
|
|
|
|
LogNewStatusMsg(Resources.StrSkippedMsgRadioCheckRegion);
|
|
|
|
|
PrepareExit();
|
|
|
|
|
return StatusReturn.Skipped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Starting point of time-consuming operations
|
|
|
|
|
SignalRunningState(MaxActualProgressSteps);
|
|
|
|
|
|
|
|
|
|
// If requirement forces skipping but radio is installed, it should at least be set to customer mode and checked for
|
|
|
|
|
// it.
|
|
|
|
|
// Reminder for skip radio check forced by requirements
|
|
|
|
|
var skipRadioCheck = ProductionRequirements.SkipRadioCheck;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!Meter.IsLoggedOn)
|
|
|
|
|
{
|
|
|
|
|
Meter.Login();
|
|
|
|
|
if (!Meter.IsLoggedOn)
|
|
|
|
|
{
|
|
|
|
|
return SetTestFailed(Resources.StrErrorMsgCordonelLogin);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard run for radio signal test (RSSI)
|
|
|
|
|
if (!skipRadioCheck)
|
|
|
|
|
{
|
|
|
|
|
if (UserControl == null)
|
|
|
|
|
{
|
|
|
|
|
return SetTestFailed(Resources.StrErrorMsgRadioCheck);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize the SIRT
|
|
|
|
|
if (!SirtInit())
|
|
|
|
|
{
|
|
|
|
|
return SetTestFailed(Resources.StrErrorMsgSirt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//FrequencyIndicator
|
|
|
|
|
var freq = RegisterConverter.ByteArrayToValue<UInt32>(
|
|
|
|
|
Meter.ReadRegister("SENSUSRADIO_FrequencyIndicator"));
|
|
|
|
|
_radioAddressCordonel =
|
|
|
|
|
RegisterConverter.ByteArrayToValue<UInt32>(Meter.ReadRegister("SENSUSRADIO_RadioAddress"));
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
var meterRawEncryptionKey = Meter.ReadRegister("SENSUSRADIO_EncryptionKey", 16);
|
|
|
|
|
var meterEncryptionKey = "";
|
|
|
|
|
|
|
|
|
|
// convert byte array to string
|
|
|
|
|
if (meterRawEncryptionKey != null)
|
|
|
|
|
{
|
|
|
|
|
meterEncryptionKey = BitConverter.ToString(meterRawEncryptionKey.ToArray()).Replace("-", "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (RadioEncryptionKey != null)
|
|
|
|
|
{
|
|
|
|
|
_radioEncryptionKey = RadioEncryptionKey[0].ToUpper();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare the meterEncryptionKey read out from meter with the database RadioEncryptionKey
|
|
|
|
|
// got with the MeterProcessState/api/FinalCheck/GetEncryptionKey
|
|
|
|
|
if (meterRawEncryptionKey == null || RadioEncryptionKey == null ||
|
|
|
|
|
string.IsNullOrEmpty(meterEncryptionKey) || string.IsNullOrEmpty(_radioEncryptionKey) ||
|
|
|
|
|
!_radioEncryptionKey.Equals(meterEncryptionKey))
|
|
|
|
|
{
|
|
|
|
|
return SetTestFailed(Resources.StrErrorMsgRadioKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SuccessMsgDispatcher(Resources.StrSuccessMsgDbToMeterEncryptionKey);
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
_sirtComport = Convert.ToByte(ConfigurationManager.AppSettings[$"SirtComport{freq}"]);
|
|
|
|
|
_sirtBoxNr = Convert.ToByte(ConfigurationManager.AppSettings["SirtBoxNr"]);
|
|
|
|
|
var ucRadio = (UcRadioCheck)UserControl;
|
|
|
|
|
ucRadio.SetHeadline($"{Resources.StrStateRadioCheck} BOX: {_sirtBoxNr}");
|
|
|
|
|
LogNewStatusMsg($"{Resources.StrStateRadioCheck} Port: COM{_sirtComport}, BOX: {_sirtBoxNr}, " +
|
|
|
|
|
$"f: {freq} MHz");
|
|
|
|
|
|
|
|
|
|
// Tell the radio that something is in the slot which will impact the radio transmission
|
|
|
|
|
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_IrDAModulePresent",
|
|
|
|
|
new Byte[] { 1 }, ProgrammingSource.ProgramInternal));
|
|
|
|
|
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
// Clear the measurement as on a retry started from main control the old values will be displayed
|
|
|
|
|
ClearRssiMeasurement();
|
|
|
|
|
// kick off RSSI test
|
|
|
|
|
StartRssiMeasurement();
|
|
|
|
|
|
|
|
|
|
// Exit if meanwhile cancellation is required
|
|
|
|
|
if (CancellationToken.IsCancellationRequested)
|
|
|
|
|
CancellationProcedure();
|
|
|
|
|
|
|
|
|
|
var exitTime = DateTime.Now + TimeSpan.FromSeconds(Timeout_s);
|
|
|
|
|
var actualTime = DateTime.Now;
|
|
|
|
|
// wait on result or timeout or stop request from main window
|
|
|
|
|
while (!_testFinished && exitTime > actualTime && SingleProcessState != SingleProcessState.Idle)
|
|
|
|
|
{
|
|
|
|
|
// Exit if meanwhile cancellation is required
|
|
|
|
|
if (CancellationToken.IsCancellationRequested)
|
|
|
|
|
CancellationProcedure();
|
|
|
|
|
|
|
|
|
|
Thread.Sleep(TimeoutLoop_ms);
|
|
|
|
|
actualTime = DateTime.Now;
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lock all buttons as process is finished
|
|
|
|
|
DisableAllButtons();
|
|
|
|
|
}// !skipRadioCheck
|
|
|
|
|
|
|
|
|
|
// reset all radio alarms
|
|
|
|
|
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_MainAlarmMask",
|
|
|
|
|
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
|
|
|
|
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_ExtendedAlarmMask",
|
|
|
|
|
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
|
|
|
|
// set system state back to shipping mode
|
|
|
|
|
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
|
|
|
|
|
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
// check radio is in customer mode 0x02 and alarm masks are 0
|
|
|
|
|
var mainAlarmMask =
|
|
|
|
|
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask"));
|
|
|
|
|
var extendedAlarmMask =
|
|
|
|
|
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask"));
|
|
|
|
|
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_SystemState"));
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
if (// Radio mode and alarm masks NOT okay forces always an error
|
|
|
|
|
// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
|
|
|
|
_finalRadioSystemState != 0x02 || mainAlarmMask != 0 || extendedAlarmMask != 0
|
|
|
|
|
// If radio check is enabled the signal level has to be in range and the test has to be finished
|
|
|
|
|
|| (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished)))
|
|
|
|
|
{
|
|
|
|
|
return SetTestFailed(!_sirtComOkay ? Resources.StrErrorMsgSirt : Resources.StrErrorMsgRadioCheck);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SuccessMsgDispatcher(Resources.StrSuccessMsgRadioInCustomerMode);
|
|
|
|
|
SuccessMsgDispatcher(Resources.StrSuccessMsgRadioAlarmsReset);
|
|
|
|
|
|
|
|
|
|
// Skip radio check by requirement
|
|
|
|
|
if (skipRadioCheck)
|
|
|
|
|
{
|
|
|
|
|
EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA;
|
|
|
|
|
WarningMsgDispatcher(Resources.StrSkippedMsgRadioCheckSpecialPermission);
|
|
|
|
|
PrepareExit();
|
|
|
|
|
return StatusReturn.Skipped;
|
|
|
|
|
}
|
|
|
|
|
return SetTestSucceeded();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
return SetTestFailed(Resources.StrErrorMsgRadioCheck);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Common for test Init
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void TestInit()
|
|
|
|
|
{
|
|
|
|
|
// Uncheck executed process to leave the result open needed for retries
|
|
|
|
|
EolProgress.RadioCheckId = null;
|
|
|
|
|
EolProgress.FinalRadioSystemState = null;
|
|
|
|
|
EolProgress.FinalRadioSystemStateCheck = null;
|
|
|
|
|
// Value not saved as test hasn't been executed
|
|
|
|
|
_radioResultCheckId = 0;
|
|
|
|
|
_finalRadioSystemState = 0;
|
|
|
|
|
|
|
|
|
|
_sirtComOkay = false;
|
|
|
|
|
_testFinished = false;
|
|
|
|
|
_successSignalLevel = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize the SIRT
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private Boolean SirtInit()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Reset needed for repeated run
|
|
|
|
|
_stateMachine?.Stop();
|
|
|
|
|
_stateMachine?.ClearBUPRadioAdresses();
|
|
|
|
|
_stateMachine?.ClearPam();
|
|
|
|
|
_stateMachine = null;
|
|
|
|
|
_dataContainer = null;
|
|
|
|
|
_stateMachine = new Statemashine();
|
|
|
|
|
_dataContainer = new DataContainer();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Common to prepare exit for all status returns
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void PrepareExit()
|
|
|
|
|
{
|
|
|
|
|
EolProgress.RadioCheckId = _radioResultCheckId;
|
|
|
|
|
EolProgress.FinalRadioSystemState = _finalRadioSystemState;
|
|
|
|
|
|
|
|
|
|
// Essential to logout from meter at any exit
|
|
|
|
|
Meter?.Logout();
|
|
|
|
|
|
|
|
|
|
_stateMachine?.Stop();
|
|
|
|
|
_stateMachine = null;
|
|
|
|
|
_dataContainer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Common for test failed
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="errorMsg">error message to dispatch and log</param>
|
|
|
|
|
/// <remarks><see cref="StatusReturn.Failed"/></remarks>
|
|
|
|
|
private StatusReturn SetTestFailed(String errorMsg)
|
|
|
|
|
{
|
|
|
|
|
EolProgress.FinalRadioSystemStateCheck = EOLStatus.FAIL;
|
|
|
|
|
ErrorMsgDispatcher(errorMsg);
|
|
|
|
|
PrepareExit();
|
|
|
|
|
return StatusReturn.Failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Common for test succeeded
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks><see cref="StatusReturn.Okay"/></remarks>
|
|
|
|
|
private StatusReturn SetTestSucceeded()
|
|
|
|
|
{
|
|
|
|
|
EolProgress.FinalRadioSystemStateCheck = EOLStatus.OK;
|
|
|
|
|
PrepareExit();
|
|
|
|
|
return StatusReturn.Okay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Disable all buttons of user control.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void DisableAllButtons()
|
|
|
|
|
{
|
|
|
|
|
//EnableBtn(_checkRadioUserControl.btnSkip, false);
|
|
|
|
|
EnableBtn(_checkRadioUserControl.btnRetry, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EnableBtn(Button btn, Boolean enable)
|
|
|
|
|
{
|
|
|
|
|
btn.Dispatcher.Invoke(DispatcherPriority.Send,
|
|
|
|
|
new Action(() =>
|
|
|
|
|
{
|
|
|
|
|
btn.IsEnabled = enable;
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateUc()
|
|
|
|
|
{
|
|
|
|
|
_checkRadioUserControl.SetUi(new UcRadioCheck.UiContainer(_rssiPamSirtDbMin, _rssiSemiCordonelDbMin,
|
|
|
|
|
_rssiPamSirtMeasuredByCordonel, _rssiSemiCordonelMeasuredBySirt, _rssiPamSirtDbMax, _rssiSemiCordonelDbMax));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check of radio signal strength.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void StartRssiMeasurement()
|
|
|
|
|
{
|
|
|
|
|
// disable all buttons during ongoing measurement process
|
|
|
|
|
DisableAllButtons();
|
|
|
|
|
UpdateUc();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
GetRssiMinMaxValuesFromDb(_sirtBoxNr, Meter.OrderNumber);
|
|
|
|
|
SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey);
|
|
|
|
|
UpdateUc();
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_testFinished = true;
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
LogNewStatusMsg(Resources.StrErrorMsgRadioCheck);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clear RSSI values for display of unknown values but leave limits active as
|
|
|
|
|
/// those are already loaded from DB and will not change during retry. Therefore,
|
|
|
|
|
/// the process can be optimized to avoid recurrent DB access!
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void ClearRssiMeasurement()
|
|
|
|
|
{
|
|
|
|
|
_sirtComOkay = false;
|
|
|
|
|
_rssiPamSirtMeasuredByCordonel = null;
|
|
|
|
|
_rssiSemiCordonelMeasuredBySirt = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retry collection of RSSI values (entire process with sending PAM and receiving SEMI).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void Retry()
|
|
|
|
|
{
|
|
|
|
|
// reset process counter to start from scratch
|
|
|
|
|
ActualProcessProgress = 0;
|
|
|
|
|
if (_stateMachine != null)
|
|
|
|
|
{
|
|
|
|
|
_stateMachine.ClearPam();
|
|
|
|
|
_stateMachine.Stop();
|
|
|
|
|
}
|
|
|
|
|
ClearRssiMeasurement();
|
|
|
|
|
UpdateUc();
|
|
|
|
|
SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// ATTENTION:
|
|
|
|
|
/// As those definitions are directly used as DB definitions for pushing the result:
|
|
|
|
|
///
|
|
|
|
|
/// LEAVE THOSE NAMING AS DEFINED HERE!
|
|
|
|
|
///
|
|
|
|
|
/// </summary>
|
|
|
|
|
private class DataContainer
|
|
|
|
|
{
|
|
|
|
|
public Int32 RadioAdress
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public Byte RSSI_PAM
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public Byte RSSI_SEMI
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public Int32 LimitId
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public Byte Result
|
|
|
|
|
{
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Save RSSI values to DB.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void SaveRssiLevelsToDb()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_dataContainer != null)
|
|
|
|
|
{
|
|
|
|
|
_dataContainer.RadioAdress = (Int32)_radioAddressCordonel; // 0-4294967295
|
|
|
|
|
var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}SaveRSSI";
|
|
|
|
|
|
|
|
|
|
var response = LocalWebRequest.PostRequestAsync(url, 30000, out var httpStatus, _dataContainer);
|
|
|
|
|
if (!string.IsNullOrEmpty(response) && httpStatus == HttpStatusCode.OK)
|
|
|
|
|
{
|
|
|
|
|
var jsonObj = JObject.Parse(response);
|
|
|
|
|
_radioResultCheckId = Convert.ToInt32(jsonObj.SelectToken("radiocheckid"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SuccessMsgDispatcher(Resources.StrSuccessMsgSetRadioRssiLimitsToDb);
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_testFinished = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get RSSI values from DB.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void GetRssiMinMaxValuesFromDb(Byte BoxNr, Int32 productionOrderNo)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_dataContainer != null)
|
|
|
|
|
{
|
|
|
|
|
var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}GetLimits/{BoxNr}/{productionOrderNo}";
|
|
|
|
|
var body = LocalWebRequest.GetRequest(url, 300000, out var httpStatus);
|
|
|
|
|
if (httpStatus != HttpStatusCode.OK || string.IsNullOrEmpty(body))
|
|
|
|
|
{
|
|
|
|
|
ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var o = JObject.Parse(body);
|
|
|
|
|
|
|
|
|
|
_dataContainer.LimitId = Convert.ToInt32(o.SelectToken("LimitId")?.ToString());
|
|
|
|
|
_rssiPamSirtDbMin = Convert.ToByte(o.SelectToken("RSSI_PAM_Min")?.ToString());
|
|
|
|
|
_rssiPamSirtDbMax = Convert.ToByte(o.SelectToken("RSSI_PAM_Max")?.ToString());
|
|
|
|
|
_rssiSemiCordonelDbMin = Convert.ToByte(o.SelectToken("RSSI_SEMI_Min")?.ToString());
|
|
|
|
|
_rssiSemiCordonelDbMax = Convert.ToByte(o.SelectToken("RSSI_SEMI_Max")?.ToString());
|
|
|
|
|
SuccessMsgDispatcher(Resources.StrSuccessMsgGetRadioRssiLimitsFromDb +
|
|
|
|
|
$": ID: ({_dataContainer.LimitId}), " +
|
|
|
|
|
$"SIRT Min: ({_rssiPamSirtDbMin}), " +
|
|
|
|
|
$"SIRT Max: ({_rssiPamSirtDbMax}), " +
|
|
|
|
|
$"Cordonel Min: ({_rssiSemiCordonelDbMin}), " +
|
|
|
|
|
$"Cordonel Max: ({_rssiSemiCordonelDbMax})");
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Send PAM to force a BUP and a SEMI and start the RSSI observer to measure the signal strength.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Optimized based on new documentations.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void SendPamListenSemi(Int32 radioAddress, Int32 byteComPort, String strKey)
|
|
|
|
|
{
|
|
|
|
|
if (_dataContainer == null || _stateMachine == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_rssiPamSirtMeasuredByCordonel = null;
|
|
|
|
|
_rssiSemiCordonelMeasuredBySirt = null;
|
|
|
|
|
|
|
|
|
|
Byte P81_1 = 0;
|
|
|
|
|
|
|
|
|
|
_dataContainer.RadioAdress = radioAddress;
|
|
|
|
|
|
|
|
|
|
_stateMachine.initialise((Byte)byteComPort);
|
|
|
|
|
var date = DateTime.Now.Date;
|
|
|
|
|
var loggingPath = $"C:\\GenesisLog\\{date:yyyy-MM-dd}\\ProductionUiCordonel\\{Meter.PcbId}_RadioCheck.log";
|
|
|
|
|
_stateMachine.StartLoggin(loggingPath);
|
|
|
|
|
|
|
|
|
|
LogNewStatusMsg($"{Resources.StrRadioMsgSendPam}: {radioAddress.ToString()}");
|
|
|
|
|
|
|
|
|
|
// Initialize the state machine of the SIRT dll
|
|
|
|
|
_stateMachine.Stop();
|
|
|
|
|
_stateMachine.ClearPam();
|
|
|
|
|
_stateMachine.Timeout_s = 30;
|
|
|
|
|
|
|
|
|
|
// Dummy PAM 00 with radio encryption key, SIRT dll takes the key for the response and the
|
|
|
|
|
// address for the request and response, CommandHex 00 is data size of 0, so it makes no sense
|
|
|
|
|
// to encrypt the 00 PAM, but the key will be used in the SEMI response
|
|
|
|
|
_stateMachine.SendPAM(radioAddress.ToString(), "00", "", P81_1, 1, 0, strKey, "0", true);
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
|
|
|
|
|
// SystemState 0x55 will force meter immediately to send BUP, internal the system state remains
|
|
|
|
|
// on 0x02. This has to be sent after PAM!
|
|
|
|
|
Meter.WriteRegister("SENSUSRADIO_SystemState", 0x55);
|
|
|
|
|
|
|
|
|
|
// Start polling
|
|
|
|
|
_stateMachine.OnStateChanged += StateMachineOnStateChange_Handler;
|
|
|
|
|
_stateMachine.Start();
|
|
|
|
|
|
|
|
|
|
ActualProcessProgress++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get RSSI (Received Signal Strength Indicator) values from SIRT (Sensus Interface Radio Tool).
|
|
|
|
|
/// Set the <see cref="_successSignalLevel"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <remarks date="2024-09-17" author="Thomas Wiedebusch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void GotRssiCallback(Byte rssiSemiCordonel, Byte rssiPamSirt)
|
|
|
|
|
{
|
|
|
|
|
if (_dataContainer == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// RSSI PAM sent by SIRT and measured by Cordonel
|
|
|
|
|
LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiSirt}: ({rssiPamSirt})");
|
|
|
|
|
// RSSI SEMI sent by Cordonel and measured by SIRT
|
|
|
|
|
LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiCordonel}: ({rssiSemiCordonel})");
|
|
|
|
|
|
|
|
|
|
_rssiPamSirtMeasuredByCordonel = rssiPamSirt;
|
|
|
|
|
_rssiSemiCordonelMeasuredBySirt = rssiSemiCordonel;
|
|
|
|
|
_dataContainer.RSSI_PAM = rssiPamSirt;
|
|
|
|
|
_dataContainer.RSSI_SEMI = rssiSemiCordonel;
|
|
|
|
|
|
|
|
|
|
UpdateUc();
|
|
|
|
|
|
|
|
|
|
// threshold check for failed test
|
|
|
|
|
if (_rssiPamSirtMeasuredByCordonel < _rssiPamSirtDbMin ||
|
|
|
|
|
_rssiPamSirtMeasuredByCordonel > _rssiPamSirtDbMax ||
|
|
|
|
|
_rssiSemiCordonelMeasuredBySirt < _rssiSemiCordonelDbMin ||
|
|
|
|
|
_rssiSemiCordonelMeasuredBySirt > _rssiSemiCordonelDbMax)
|
|
|
|
|
{
|
|
|
|
|
_dataContainer.Result = 0;
|
|
|
|
|
// done can finish the test which is equal to abort on error
|
|
|
|
|
//EnableBtn(_checkRadioUserControl.btnSkip, true);
|
|
|
|
|
// reset can repeat the test as often as wanted
|
|
|
|
|
EnableBtn(_checkRadioUserControl.btnRetry, true);
|
|
|
|
|
_successSignalLevel = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// on success there is no choice to made, it is finished
|
|
|
|
|
_dataContainer.Result = 1;
|
|
|
|
|
_successSignalLevel = true;
|
|
|
|
|
|
|
|
|
|
// safe those results to DB for logging
|
|
|
|
|
SaveRssiLevelsToDb();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// State machine state change handler.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <remarks date="????" author="Roland Drabesch">
|
|
|
|
|
/// - Initial
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private void StateMachineOnStateChange_Handler(Object sender, MyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (_stateMachine == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Reading back sent PAM from SIRT to Cordonel
|
|
|
|
|
var pam = _stateMachine.GetPAM(1);
|
|
|
|
|
|
|
|
|
|
if (pam != null)
|
|
|
|
|
{
|
|
|
|
|
if (pam.State == PAM.PAMStates.Timeout)
|
|
|
|
|
{
|
|
|
|
|
LogNewStatusMsg($"Timeout {pam.GetTime()} s");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pam.CountSemi > 0)
|
|
|
|
|
{
|
|
|
|
|
// SEMI received from Cordonel
|
|
|
|
|
_stateMachine.Stop();
|
|
|
|
|
_stateMachine.ClearPam();
|
|
|
|
|
|
|
|
|
|
var hexBytesSemi = pam.Semi.HexData.Replace(" ", "");
|
|
|
|
|
var semiBytes = new Byte[hexBytesSemi.Length / 2];
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < hexBytesSemi.Length; i += 2)
|
|
|
|
|
semiBytes[i / 2] = Convert.ToByte(hexBytesSemi.Substring(i, 2), 16);
|
|
|
|
|
|
|
|
|
|
// SEMI Status okay if 0, if SIRT dll returns unequal to 0 the encryption key is wrong
|
|
|
|
|
// If 0 the encryption key is correct
|
|
|
|
|
if (semiBytes[19] == 0)
|
|
|
|
|
{
|
|
|
|
|
// RSSI SEMI sent by Cordonel and measured by SIRT (signal level of Cordonel in byte 2)
|
|
|
|
|
var rssiSemiMeasuredBySirt = semiBytes[2];
|
|
|
|
|
|
|
|
|
|
// RSSI PAM sent by SIRT and measured by Cordonel (signal level of SIRT packet to SEMI in byte 20)
|
|
|
|
|
var rssiPamMeasuredByCordonel = semiBytes[20];
|
|
|
|
|
|
|
|
|
|
GotRssiCallback(rssiSemiMeasuredBySirt, rssiPamMeasuredByCordonel);
|
|
|
|
|
_sirtComOkay = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// SEMI Status not 0 is internal error which should be logged
|
|
|
|
|
LogNewStatusMsg(pam.Semi.GetPamErrorMessage());
|
|
|
|
|
_sirtComOkay = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LogNewStatusMsg($"BUPS: ({pam.CountBup})");
|
|
|
|
|
_sirtComOkay = false;
|
|
|
|
|
}
|
|
|
|
|
}//pam not null
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_stateMachine.Stop();
|
|
|
|
|
_stateMachine.ClearPam();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Meter?.WriteLog(ex.Message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|