ProductionUiCordonel: - manually merged some modules from main EOL_2.6.20
This commit is contained in:
@@ -34,11 +34,11 @@
|
||||
|
||||
|
||||
<appSettings>
|
||||
<add key="Mode" value="2" /> <!--1 = EOL ; 2 = Picking-->
|
||||
<add key="SirtComport433" value="14" /> <!--Individual for Thomas-->
|
||||
<add key="SirtComport868" value="15" /> <!--Individual for Thomas-->
|
||||
<add key="SirtBoxNr" value="2" /> <!--Roland and Thomas, box for development, box at shipping station is 1-->
|
||||
<add key="StationID" value="2" /> <!--Roland and Thomas, shipping station is 1-->
|
||||
<add key="Mode" value="1" /> <!--1 = EOL ; 2 = Picking-->
|
||||
<add key="SirtComport433" value="14" /> <!--Individual for Thomas-->
|
||||
<add key="SirtComport868" value="15" /> <!--Individual for Thomas-->
|
||||
<add key="SirtBoxNr" value="2" /> <!--Roland and Thomas, box for development, box at shipping station is 1-->
|
||||
<add key="StationID" value="1" /> <!--Roland and Thomas, shipping station is 1-->
|
||||
|
||||
</appSettings>
|
||||
|
||||
|
||||
+5
-13
@@ -176,6 +176,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mai-07" author="Thomas Wiedebusch">
|
||||
/// - Removed change and read back of MainAlarmMask and ExtendedAlarmMask.
|
||||
/// </remarks>
|
||||
public override StatusReturn ExecuteProcess()
|
||||
{
|
||||
// This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress
|
||||
@@ -304,27 +307,16 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr
|
||||
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 system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
||||
_finalRadioSystemState != 0x02
|
||||
// If radio check is enabled the signal level has to be in range and the test has to be finished
|
||||
|| (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished)))
|
||||
{
|
||||
|
||||
+829
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,11 +453,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Handle genesis meter is null as new meter.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
|
||||
/// - Check _genesisMeter not null before equality with old pcbid.
|
||||
/// </remarks>
|
||||
private Boolean CheckForNewMeter()
|
||||
{
|
||||
if (_genesisMeter == null ||
|
||||
(!string.IsNullOrEmpty(_lastPcbId) &&
|
||||
!_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
_genesisMeter?.PcbId != null && !_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
{
|
||||
// This loop will be entered after the meter has changed, but not on the initial meter
|
||||
// after program start.
|
||||
|
||||
@@ -0,0 +1,857 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using LaaPackages.Features.Cordonel.Models;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus;
|
||||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation for all methods needed to drive the state machine of the process controller
|
||||
/// </summary>
|
||||
public class ProcessController
|
||||
{
|
||||
#region ------------------------------------------ Variables --------------------------------------------------
|
||||
// actual genesis meter
|
||||
private IGenesisMeter _genesisMeter;
|
||||
// Cordonel requirements for production
|
||||
private CordonelRequirements _cordonelRequirements;
|
||||
// EOL progress states for production
|
||||
private EOLProgressModel _cordonelEolProgress;
|
||||
// last pcbId for check of changed genesisMeter
|
||||
private String _lastPcbId;
|
||||
// batch just used for setup, as only one meter will be assigned
|
||||
private MeterBatch _meterBatch;
|
||||
// state of the state machine
|
||||
private ProcessState _stateMachineState;
|
||||
// locker to avoid repeated state execution
|
||||
private ProcessState _lockedProcessState;
|
||||
// sender of state change event to evaluate in e.g. error process
|
||||
private IProductionProcess _stateChangeRequestProcess;
|
||||
// List of production processes
|
||||
public List<IProductionProcess> Processes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
// Container for production process states
|
||||
private IProcessStateDef _processStateDef;
|
||||
|
||||
// Container for production status information, a list is used to keep the content over all classes of production
|
||||
// processes as a reference seems not to return their value (always reset to the initial value)
|
||||
private GenesisStatus _productionStatus;
|
||||
|
||||
// register restorer
|
||||
private RegisterRestorer _registerRestorer;
|
||||
|
||||
// sensus radio encryption key
|
||||
private List<String> _radioEncryptionKey;
|
||||
|
||||
// Password container with all information, a list is used to keep the content over all classes of production
|
||||
// processes as a reference seems not to return their value (always reset to the initial value)
|
||||
private MeterPwdDb _pwdContainer;
|
||||
|
||||
// reminder for slot
|
||||
private readonly Int32 _slot;
|
||||
|
||||
/// <summary>
|
||||
/// Change the overview table of the GUI to the state of the individual process
|
||||
/// </summary>
|
||||
public event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The overall process chain changed needed to signal to the GUI
|
||||
/// - Can be forced by the state machine to invoke,
|
||||
/// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next
|
||||
/// required sate after break, error or completion to change the state of the state machine.
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Request for logging to GUI output and log-file
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Keep the progress bars of the GUI up to date
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
|
||||
|
||||
// cancellation token
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
// cyclic timer for state machine call
|
||||
private Timer _processCtrlTimer;
|
||||
private const Int32 ProcessTimerCycleMs = 250;
|
||||
|
||||
/// <summary>
|
||||
/// Avoid waiting for a single process completion.
|
||||
/// </summary>
|
||||
public const IProductionProcess DoNotWaitForSingleProcessCompletion = null;
|
||||
|
||||
/// <summary>
|
||||
/// Kick off a parallel execution of the state machine.
|
||||
/// </summary>
|
||||
public const Boolean ExecuteInParallel = true;
|
||||
|
||||
#endregion --------------------------------------- Variables --------------------------------------------------
|
||||
#region ------------------------------------------ Class ------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Ctor:
|
||||
/// - Setup process timer for cyclic call of state machine,
|
||||
/// - Create and setup Cordonel.
|
||||
/// </summary>
|
||||
/// <param name="slot">physical slot for this meter</param>
|
||||
/// <param name="processSateDef"></param>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-04" author="Thomas Wiedebusch">
|
||||
/// - Always set to production mode to load the registers for this FW and not the latest,
|
||||
/// - Always RegisterWatchService on,
|
||||
/// - Always MinMaxCHeck on.
|
||||
/// </remarks>
|
||||
public ProcessController(Int32 slot, IProcessStateDef processSateDef)
|
||||
{
|
||||
_processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs);
|
||||
_slot = slot;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
_processStateDef = processSateDef;
|
||||
InitNewMeter();
|
||||
InitProcesses();
|
||||
// program start with idle process
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization of new meter
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Initialize processes to build a list of all production processes.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes
|
||||
/// </remarks>
|
||||
private void InitNewMeter()
|
||||
{
|
||||
_genesisMeter = new GenesisMeter();
|
||||
_genesisMeter.SetupFromConfigFile(_slot);
|
||||
// Important setup to overwrite setting from "Setup" in GTB
|
||||
_genesisMeter.Configuration.ProductionMode = true;
|
||||
_genesisMeter.Configuration.UseRegisterWatchService = true;
|
||||
_genesisMeter.Configuration.UseMinMaxCheck = true;
|
||||
// Meter batch even if only one meter in use as the meter batch routines can be used
|
||||
_meterBatch = new MeterBatch();
|
||||
_meterBatch.AddMeter(_genesisMeter);
|
||||
// Set lock process to unreachable state delimiter to force initial entry of state machine
|
||||
_lockedProcessState = ProcessState.StateListDelimiter;
|
||||
// Create a register restorer
|
||||
_registerRestorer = new RegisterRestorer(_genesisMeter);
|
||||
_radioEncryptionKey = new List<String>();
|
||||
// Containers for production status and standard or special requirement for production
|
||||
_productionStatus = new GenesisStatus();
|
||||
_cordonelRequirements = new CordonelRequirements();
|
||||
// Password container as reminder for repeated test on identical device
|
||||
_pwdContainer = new MeterPwdDb();
|
||||
// EOL progress to feed the sentinel
|
||||
_cordonelEolProgress = new EOLProgressModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removal of old meter
|
||||
/// </summary>
|
||||
private void RemoveMeter()
|
||||
{
|
||||
_genesisMeter?.ClearPassword();
|
||||
// meter batch dispose disposes all IMeters
|
||||
_meterBatch?.Dispose();
|
||||
_meterBatch = null;
|
||||
_genesisMeter = null;
|
||||
_registerRestorer = null;
|
||||
_radioEncryptionKey = null;
|
||||
_productionStatus = null;
|
||||
_cordonelRequirements = null;
|
||||
_pwdContainer = null;
|
||||
_cordonelEolProgress = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
_processCtrlTimer?.Dispose();
|
||||
_processCtrlTimer = null;
|
||||
_processStateDef = null;
|
||||
KillProcesses();
|
||||
RemoveMeter();
|
||||
}
|
||||
|
||||
#endregion --------------------------------------- Class ------------------------------------------------------
|
||||
#region ------------------------------------------ Events -----------------------------------------------------
|
||||
/// <summary>
|
||||
/// Single process state or process list changed event,
|
||||
/// Use event to force special procedure like uninstalling events
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
||||
/// - Remove process events if process is finished.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - Remove process events if process is finished or skipped.
|
||||
/// </remarks>
|
||||
private void SingleProcessStateChanged_Handler(Object sender, SingleProcessStateArgs e)
|
||||
{
|
||||
IProductionProcess pp = null;
|
||||
if (sender is IProductionProcess process)
|
||||
{
|
||||
pp = process;
|
||||
}
|
||||
// uninstall events
|
||||
switch (e.SingleProcessState)
|
||||
{
|
||||
// after execution independent of the result, the events coming from the process
|
||||
// are needed to be removed
|
||||
case SingleProcessState.Succeeded:
|
||||
case SingleProcessState.Skipped:
|
||||
case SingleProcessState.Abort:
|
||||
case SingleProcessState.Error:
|
||||
RemoveProcessEvents(pp);
|
||||
break;
|
||||
|
||||
case SingleProcessState.Retry:
|
||||
case SingleProcessState.Waiting:
|
||||
case SingleProcessState.Running:
|
||||
break;
|
||||
}
|
||||
// signal state change to GUI
|
||||
OnSingleProcessStateChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e)
|
||||
{
|
||||
OnProcessProgressChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ProcessLog_Handler(Object sender, ProcessLogArgs e)
|
||||
{
|
||||
OnProcessLogRequest?.Invoke(sender, e);
|
||||
}
|
||||
/// <summary>
|
||||
/// Handler to set a new state for the state machine regarding the entire final-process
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="2023-Mar-03" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void OverallProcessStateChanged_Handler(Object sender, ProcessStateArgs e)
|
||||
{
|
||||
// remind caller of the state change request to act on e.g. error processing
|
||||
_stateChangeRequestProcess = (IProductionProcess)sender;
|
||||
|
||||
// received new status from external
|
||||
if (e.State != null)
|
||||
{
|
||||
_stateMachineState = (ProcessState)e.State;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process timer
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void TmrProcessCtrlCycle_Elapsed(Object state)
|
||||
{
|
||||
StateMachine();
|
||||
}
|
||||
|
||||
#endregion --------------------------------------- Events -----------------------------------------------------
|
||||
#region ------------------------------------------ State Machine ----------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// State-machine of ProcessController which serves all production processes.
|
||||
/// This-state machine executes all states defined in the "GenericStateMachineDef" and the individual
|
||||
/// production-process specific "XxxStateMachineDef".
|
||||
///
|
||||
/// A few states with special functions will be caught in advance in this StateMachine
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
||||
/// - Generic implementation
|
||||
/// </remarks>
|
||||
private void StateMachine()
|
||||
{// avoid repeated execution of the state machine on unchanged state
|
||||
if (_stateMachineState == _lockedProcessState)
|
||||
{
|
||||
Thread.Sleep(ProcessTimerCycleMs >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// remind backup state to avoid repeated execution and side effects
|
||||
_lockedProcessState = _stateMachineState;
|
||||
|
||||
// signal overall process progress to GUI based on ready processes
|
||||
CalcAndPushOverAllProgress();
|
||||
|
||||
// signal state change to logger
|
||||
OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState));
|
||||
|
||||
// get the processStateStruct of the actual set stateMachineSTate
|
||||
var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState);
|
||||
|
||||
switch (_stateMachineState)
|
||||
{
|
||||
case ProcessState.Idle:
|
||||
// This state needs an external state change like user input to go ahead
|
||||
break;
|
||||
|
||||
case ProcessState.DetectCordonel:
|
||||
// Wait for cancellation
|
||||
if (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
// Reset the cancellation request
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
// Set up the counter for maximum overall process progress bar in GUI
|
||||
CalcAndPushMaxOverAllProcessSteps();
|
||||
|
||||
// Detection of Cordonel has to be completed before assigning next state, so leave to automatic
|
||||
// to change the state after successfully execution. The detection is the base for all others.
|
||||
StartProcess(processStateStruct);
|
||||
break;
|
||||
|
||||
case ProcessState.CheckNewMeter:
|
||||
// Detection of Cordonel has to be completed before assigning next state. It has to be checked if
|
||||
// the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after
|
||||
// new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then
|
||||
// enter the "ConnectCordonel"!
|
||||
_stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel;
|
||||
break;
|
||||
|
||||
|
||||
case ProcessState.RepeatFailedTests:
|
||||
_stateMachineState = RepeatFailedTests();
|
||||
break;
|
||||
|
||||
case ProcessState.AbortTest:
|
||||
AbortProcesses();
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
case ProcessState.Stop:
|
||||
KillProcesses();
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
case ProcessState.Error:
|
||||
// Common error handling routine
|
||||
ProcessErrorHandler(_stateChangeRequestProcess);
|
||||
// Start the error handler with its user feedback request. It has to be started
|
||||
// always even if the abortion of all processes is required! The error handler
|
||||
// will change the exit state based on the user input.
|
||||
StartProcess(processStateStruct);
|
||||
break;
|
||||
|
||||
case ProcessState.StateListDelimiter:
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is the call to the processes for a specific test
|
||||
StartProcess(processStateStruct);
|
||||
if (processStateStruct.KickOffParallelState != null)
|
||||
{
|
||||
_stateMachineState = (ProcessState)processStateStruct.KickOffParallelState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException ex)
|
||||
{
|
||||
_genesisMeter?.WriteLog(ex.Message);
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_genesisMeter?.WriteLog(ex.Message);
|
||||
_stateMachineState = ProcessState.Error;
|
||||
}
|
||||
}
|
||||
}// state locked against repeated execution
|
||||
|
||||
|
||||
#endregion --------------------------------------- State Machine ----------------------------------------------
|
||||
#region ------------------------------------------ Tools ------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Returns the currentGenesis information for display
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public CordonelRequirements GetProductionRequirements()
|
||||
{
|
||||
return _cordonelRequirements;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the currentGenesis information for display
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public IGenesisMeter GetGenesisMeter()
|
||||
{
|
||||
return _genesisMeter;
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears all collected data on new genesisMeter and reminds the detected for next
|
||||
/// detection and run of this comparison.
|
||||
/// </summary>
|
||||
/// <returns>true for new meter</returns>
|
||||
/// <remarks date="2023-Jun-28" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - The StartProcessSequence has already removed all processes from list to have a clean start,
|
||||
/// avoid it here again, because the detect is now in the list!
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Handle genesis meter is null as new meter.
|
||||
/// </remarks>
|
||||
private Boolean CheckForNewMeter()
|
||||
{
|
||||
if (_genesisMeter == null ||
|
||||
(!string.IsNullOrEmpty(_lastPcbId) &&
|
||||
!_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
{
|
||||
// This loop will be entered after the meter has changed, but not on the initial meter
|
||||
// after program start.
|
||||
KillProcesses();
|
||||
RemoveMeter();
|
||||
InitNewMeter();
|
||||
InitProcesses();
|
||||
// Remind actual genesis
|
||||
_lastPcbId = _genesisMeter?.PcbId;
|
||||
|
||||
// New meter detected
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remind actual genesis
|
||||
_lastPcbId = _genesisMeter?.PcbId;
|
||||
|
||||
// Old meter
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart final test on first failed or aborted test.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jul-18" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes and search in Processes
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Oct-21" author="Thomas Wiedebusch">
|
||||
/// - Leave skipped processes as is.
|
||||
/// </remarks>
|
||||
private ProcessState RepeatFailedTests()
|
||||
{
|
||||
var state = ProcessState.Idle;
|
||||
|
||||
// Search for first test in processes list which failed or aborted due to final test in
|
||||
// parallel processing forced abort of active processes
|
||||
foreach (var p in Processes)
|
||||
{
|
||||
if (p.SingleProcessState == SingleProcessState.Error ||
|
||||
p.SingleProcessState == SingleProcessState.Abort ||
|
||||
p.SingleProcessState == SingleProcessState.Retry)
|
||||
{
|
||||
// set the state to the first entry to kick off the state machine here
|
||||
state = _processStateDef.GetStateOfProcess(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Put all processes which are not successfully completed to init (Waiting for execution)
|
||||
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
|
||||
pp.SingleProcessState != SingleProcessState.Skipped))
|
||||
{
|
||||
pp.IdleProcess();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
/// <summary>
|
||||
/// Error handler
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Feb-07" author="Thomas Wiedebusch">
|
||||
/// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization
|
||||
/// to re-start with ProductionProcessExecFinalParametrization.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Jan-28" author="Thomas Wiedebusch">
|
||||
/// - Kick off exec parametrization if check after reboot failed.
|
||||
/// </remarks>
|
||||
private void ProcessErrorHandler(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
// Special action if parametrization check failed to repeat it
|
||||
if (ProcessState.CheckFinalParametrization == _processStateDef.GetStateOfProcess(process))
|
||||
{
|
||||
// Signal state change to retry sequence exec config, store config, reboot, check config
|
||||
var pp = _processStateDef.GetProcessOfState(ProcessState.ExecFinalParametrization);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.ExecStoreConfiguration);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.RebootCordonel);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.CheckFinalParametrization);
|
||||
pp.RetryProcess();
|
||||
}
|
||||
|
||||
// Error signal needed to abort to all processes
|
||||
AbortProcesses();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the maximum of all processes expected to execute and signal to GUI.
|
||||
/// </summary>
|
||||
private void CalcAndPushMaxOverAllProcessSteps()
|
||||
{
|
||||
if (_processStateDef == null)
|
||||
return;
|
||||
|
||||
var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses();
|
||||
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps,
|
||||
ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculate all processes which are completed or skipped and signal to GUI.
|
||||
/// </summary>
|
||||
private void CalcAndPushOverAllProgress()
|
||||
{
|
||||
if (Processes == null)
|
||||
return;
|
||||
|
||||
// check the readiness of all processes in the loop
|
||||
var _overallReadyProcesses = 0;
|
||||
foreach (var p in Processes)
|
||||
{
|
||||
if (p.SingleProcessState == SingleProcessState.Succeeded ||
|
||||
p.SingleProcessState == SingleProcessState.Skipped)
|
||||
_overallReadyProcesses++;
|
||||
}
|
||||
|
||||
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_overallReadyProcesses,
|
||||
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all processes which are connected to a production process.
|
||||
/// </summary>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void InitProcesses()
|
||||
{
|
||||
if (Processes == null)
|
||||
Processes = new List<IProductionProcess>();
|
||||
|
||||
// get all processes relevant for EOL from table which has to be sorted
|
||||
var processes = _processStateDef.GetAllEolRelevantProcesses();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
// process is standby for execution after idle, this overrides the eventually abort state
|
||||
process.IdleProcess();
|
||||
|
||||
// add process to list to feed the process window in main screen
|
||||
if (Processes.All(pp => pp.ProcessName != process.ProcessName))
|
||||
{
|
||||
Processes.Add(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production process kick off:
|
||||
/// - Sets single process state to waiting within the process init,
|
||||
/// - Adds new process to process list,
|
||||
/// - Optional waits for single process to complete before process is going to be started,
|
||||
/// - Optional waits for all previously assigned and started processes to complete before process is going to
|
||||
/// be started,
|
||||
/// - Assigns slot, productionStatus, programmingParameters and genesisMeter to process,
|
||||
/// - Adds event handlers for process information,
|
||||
/// - Starts process.
|
||||
/// </summary>
|
||||
/// <param name="processStateStruct"></param>
|
||||
/// <returns>actual state</returns>
|
||||
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
||||
/// - Skipped process due to one of the previous processes failed
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - Restructured to avoid process start if processes to wait for are not completed
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
|
||||
/// - Changed from programming parameters to register restorer
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Changed setup process to start process being able to create a process list in advance.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
|
||||
/// - Changed setup process to start process being able to create a process list in advance.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
||||
/// - Feed with processStateStruct.
|
||||
/// </remarks>
|
||||
protected void StartProcess(ProcessStateStruct processStateStruct)
|
||||
{
|
||||
// deny access if uninitialized
|
||||
if (Processes == null || processStateStruct.ProductionProcess == null)
|
||||
return;
|
||||
|
||||
// do not start a new process if any error or abort request is signaled
|
||||
if (AnyProcessRequiresAbortion() && !processStateStruct.StartAlways )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// process is waiting for execution after init, this overrides the eventually abort state
|
||||
processStateStruct.ProductionProcess.InitProcess();
|
||||
|
||||
// wait for a single process to complete before start of new depending on process
|
||||
if (processStateStruct.WaitForSingleProcess != null)
|
||||
{
|
||||
// each process will have its own timer until a timeout will change the state
|
||||
while (Processes.Any(pp => !AnyProcessRequiresAbortion()
|
||||
&& _processStateDef.GetStateOfProcess(pp) == processStateStruct.WaitForSingleProcess
|
||||
&& WaitForProcess(pp)))
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all initialized processes to complete before start the new process
|
||||
if (processStateStruct.WaitForAllProcesses)
|
||||
{
|
||||
// each process will have its own timer until a timeout will change the state,
|
||||
// avoid checking the new process, which is not started due to this waiting loop
|
||||
while (Processes.Any(pp => pp.ProcessName != processStateStruct.ProductionProcess.ProcessName
|
||||
&& WaitForProcess(pp)
|
||||
&& !AnyProcessRequiresAbortion()))
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
// if any process exits meanwhile with an error or abort request avoid a start of this process
|
||||
if (( !processStateStruct.StartAlways && AnyProcessRequiresAbortion())
|
||||
|| processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Idle
|
||||
|| processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Abort)
|
||||
{
|
||||
processStateStruct.ProductionProcess.AbortProcess();
|
||||
return;
|
||||
}
|
||||
|
||||
// References to commonly used production process objects
|
||||
processStateStruct.ProductionProcess.Meter = _genesisMeter;
|
||||
processStateStruct.ProductionProcess.RegisterRestorer = _registerRestorer;
|
||||
processStateStruct.ProductionProcess.RadioEncryptionKey = _radioEncryptionKey;
|
||||
processStateStruct.ProductionProcess.PwdContainer = _pwdContainer;
|
||||
processStateStruct.ProductionProcess.ProductionStatus = _productionStatus;
|
||||
processStateStruct.ProductionProcess.ProductionRequirements = _cordonelRequirements;
|
||||
processStateStruct.ProductionProcess.EolProgress = _cordonelEolProgress;
|
||||
processStateStruct.ProductionProcess.CancellationToken = _cancellationToken;
|
||||
|
||||
// kick off process execution, the breakExitState will not be used
|
||||
processStateStruct.ProductionProcess.StartProcess(processStateStruct.NextStateOnSuccess,
|
||||
processStateStruct.ErrorExitState, processStateStruct.BreakExitState);
|
||||
// connect process events to actual process
|
||||
AddProcessEvents(processStateStruct.ProductionProcess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all processes for error or abort request
|
||||
/// </summary>
|
||||
/// <returns>true if abort required</returns>
|
||||
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
|
||||
/// - Cancellation token.
|
||||
/// </remarks>
|
||||
private Boolean AnyProcessRequiresAbortion()
|
||||
{
|
||||
return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error ||
|
||||
pp.SingleProcessState == SingleProcessState.Abort ||
|
||||
_cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check process if in status waiting or running (incomplete)
|
||||
/// </summary>
|
||||
/// <param name="pp">production process</param>
|
||||
/// <returns>true if process to wait for execution or exit running state</returns>
|
||||
private Boolean WaitForProcess(IProductionProcess pp)
|
||||
{
|
||||
return pp.SingleProcessState == SingleProcessState.Waiting ||
|
||||
pp.SingleProcessState == SingleProcessState.Running;
|
||||
}
|
||||
/// <summary>
|
||||
/// Install process events
|
||||
/// </summary>
|
||||
/// <param name="process"></param>
|
||||
private void AddProcessEvents(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
process.OnProcessProgressChanged += ProcessProgress_Handler;
|
||||
process.OnProcessLogRequest += ProcessLog_Handler;
|
||||
process.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler;
|
||||
process.OnOverallProcessStateChanged += OverallProcessStateChanged_Handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove process events
|
||||
/// </summary>
|
||||
/// <param name="process"></param>
|
||||
private void RemoveProcessEvents(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
process.OnProcessProgressChanged -= ProcessProgress_Handler;
|
||||
process.OnProcessLogRequest -= ProcessLog_Handler;
|
||||
process.OnSingleProcessStateChanged -= SingleProcessStateChanged_Handler;
|
||||
process.OnOverallProcessStateChanged -= OverallProcessStateChanged_Handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the actual process state of the state machine
|
||||
/// </summary>
|
||||
/// <returns>actual process state</returns>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private ProcessState GetProcessState()
|
||||
{
|
||||
return _stateMachineState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the new process state of the state machine
|
||||
/// </summary>
|
||||
/// <returns>true, if process state is set</returns>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public Boolean SetProcessState(ProcessState newProcessState)
|
||||
{
|
||||
if (newProcessState != _stateMachineState)
|
||||
{
|
||||
_stateMachineState = newProcessState;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all processes from the <see cref="Processes"/> list:
|
||||
/// - Signal change to GUI,
|
||||
/// - Remove process events all,
|
||||
/// - Set state to abort.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
|
||||
/// - Abort register access.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - Cancellation token source.
|
||||
/// </remarks>
|
||||
private void KillProcesses()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
if (Processes == null || Processes.Count == 0)
|
||||
return;
|
||||
|
||||
// put all processes to idle and remove events of these processes
|
||||
foreach (var pp in Processes)
|
||||
{
|
||||
pp.IdleProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abort all processes if one previous process failed, but leave the <see cref="Processes"/> list intact.
|
||||
/// Leave all events for those which are already running intact to force a clean stop.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void AbortProcesses()
|
||||
{
|
||||
if (Processes == null || Processes.Count == 0)
|
||||
return;
|
||||
|
||||
// Skip all processes which are not succeeded, erroneous or skipped to leave the status intact
|
||||
// and request abort for all others
|
||||
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
|
||||
pp.SingleProcessState != SingleProcessState.Error &&
|
||||
pp.SingleProcessState != SingleProcessState.Skipped &&
|
||||
pp.SingleProcessState != SingleProcessState.Retry))
|
||||
{
|
||||
pp.AbortProcess();
|
||||
}
|
||||
}
|
||||
#endregion --------------------------------------- Tools ------------------------------------------------------
|
||||
}
|
||||
}
|
||||
@@ -803,6 +803,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel
|
||||
// remove last userControl
|
||||
dpSubProcess.Children.Clear();
|
||||
rtbLog.Document.Blocks.Clear();
|
||||
|
||||
// clear all visual fields as it may be a new pcbId
|
||||
UpdateContentControl(lblOrderNumberValue, "?");
|
||||
UpdateContentControl(lblRadioAddressValue, "");
|
||||
UpdateContentControl(lblRadioAddressText, "");
|
||||
UpdateContentControl(lblFwValue, "?");
|
||||
UpdateContentControl(lblMeterSizeValue, "?");
|
||||
UpdateContentControl(lblLutValue, "?");
|
||||
UpdateContentControl(lblSerialNumberValue, "?");
|
||||
UpdateContentControl(lblRegionValue, "?");
|
||||
UpdateUi(lblPcbIdValue, "?");
|
||||
|
||||
_processCtrl?.SetProcessState(ProcessState.DetectCordonel);
|
||||
}
|
||||
|
||||
@@ -883,7 +895,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel
|
||||
// Returns the compressed file for error report printout
|
||||
File.WriteAllText(fileName, compressedSb.ToString());
|
||||
if (printReport)
|
||||
{
|
||||
Process.Start(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,963 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using LaaPackages.Features.Cordonel.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using Xylem.Common.CommonCore.Configuration;
|
||||
using Xylem.Common.CommonCore.Consts;
|
||||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs;
|
||||
using Xylem.Common.Utils.Logging;
|
||||
using Xylem.Common.Utils.UserAccessCtrl;
|
||||
using Control = System.Windows.Forms.Control;
|
||||
using TextBox = System.Windows.Controls.TextBox;
|
||||
using UserControl = System.Windows.Controls.UserControl;
|
||||
|
||||
namespace Xylem.Common.Production.ProductionUiCordonel
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FinalTest.xaml
|
||||
/// </summary>
|
||||
public sealed partial class ProductionWindow
|
||||
{
|
||||
#region variables and properties
|
||||
private readonly ILogger _logger;
|
||||
// remind manually changed culture setting
|
||||
private static readonly CultureInfo _cultureInfo = Thread.CurrentThread.CurrentCulture;
|
||||
private static readonly AssemblyName _assemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
private static readonly String _assemblyExecRootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
private static readonly Version _version = _assemblyName.Version;
|
||||
private readonly String _strSoftwareNameVersion;
|
||||
private RegisterLDAPUser _registerForm;
|
||||
|
||||
private Int32 Slot
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
// maximum processing steps for calculation of progress bar
|
||||
private Int32 _maxOverallProcessSteps;
|
||||
private Int32 _maxSingleProcessSteps;
|
||||
private ProcessController _processCtrl;
|
||||
private DateTimeOffset _startTime;
|
||||
private DispatcherTimer _timer;
|
||||
private String _lastLoggingTextToAvoidRepetition;
|
||||
private CordonelRequirements _productionRequirements;
|
||||
private UserAccessWindow _userAccessWindow;
|
||||
private IProcessStateDef _processStateDef;
|
||||
|
||||
#endregion
|
||||
|
||||
public ProductionWindow(String softwareName, String projectName, IProcessStateDef processStateDef)
|
||||
{
|
||||
_logger = NLogHelper.CreateOrGetLogger(projectName);
|
||||
_processStateDef = processStateDef;
|
||||
String msg;
|
||||
try
|
||||
{
|
||||
// returns true in DEBUG configuration
|
||||
if (!Logic.SoftwareAccessHelper.Access.HasAccess(_assemblyName))
|
||||
{
|
||||
msg = $"{Properties.Resources.StrStartMessageSwLicenseExpired} {_version}";
|
||||
_logger.Error(msg);
|
||||
MessageBox.Show(msg, Properties.Resources.StrProcessStateError,
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
msg = Properties.Resources.StrErrorMsgMissingNetworkConnetction + " " + e.Message;
|
||||
_logger.Error(msg);
|
||||
MessageBox.Show(msg,
|
||||
Properties.Resources.StrProcessStateError,
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeComponent();
|
||||
// Log version to files for debug purposes
|
||||
_strSoftwareNameVersion = $"{softwareName} {_version}";
|
||||
_logger.Info(_strSoftwareNameVersion);
|
||||
|
||||
Title = _strSoftwareNameVersion;
|
||||
|
||||
//static labels
|
||||
UpdateContentControl(lblSerialNumberTxt, Properties.Resources.StrLblSerialNumberTxt + ":");
|
||||
UpdateContentControl(lblRadioAddressText, Properties.Resources.StrLblRadioAddressTxt + ":");
|
||||
UpdateContentControl(lblOrderNumberText, Properties.Resources.StrLblOrderNumberTxt + ":");
|
||||
UpdateContentControl(lblMeterSizeText, Properties.Resources.StrLblMeterSizeTxt + ":");
|
||||
UpdateContentControl(lblSingleProcessText, Properties.Resources.StrLblSingleProcessText + ":");
|
||||
UpdateContentControl(lblTotalProcessText, Properties.Resources.StrLblTotalProcessText + ":");
|
||||
optionsMenu.Header = Properties.Resources.StrMenuOptionsText;
|
||||
optionsMenuChangePassword.Header = Properties.Resources.StrMenuChangePasswordText;
|
||||
hlpMenu.Header = Properties.Resources.StrMenuHelpText;
|
||||
UpdateUi(lblApprovalText, "");
|
||||
UpdateUi(lblTimeText, Properties.Resources.StrLblTimeText + ":");
|
||||
|
||||
// buttons
|
||||
UpdateContentControl(btnStart, Properties.Resources.StrBtnStart);
|
||||
UpdateContentControl(btnStop, Properties.Resources.StrBtnStop);
|
||||
|
||||
InitializeFormState();
|
||||
|
||||
}
|
||||
|
||||
private ApplicationSettings Settings
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the FinalTest GUI:
|
||||
/// - Read the configuration file for slot and port assignments and settings,
|
||||
/// - Dispatch slots to slot combobox,
|
||||
/// - Read setting from "ProductionUiConfig.json" file for GUI and slot setup,
|
||||
/// - Create final process controller and install events,
|
||||
/// - Create a dispatch timer to display the elapsed time of the process.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Window_Initialized(Object sender, EventArgs e)
|
||||
{
|
||||
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
nameof(Hardware.WaterMeter.Genesis), ProgramConfig.SerialConfigFileName);
|
||||
|
||||
if (!File.Exists(configFile))
|
||||
{
|
||||
var msg = Properties.Resources.StrErrorMsgMissingConfigurationFile;
|
||||
MessageBox.Show(msg,
|
||||
Properties.Resources.StrProcessStateError,
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(msg);
|
||||
throw new ApplicationException(msg);
|
||||
}
|
||||
var tr = new StreamReader(configFile);
|
||||
var meterConfigList = JsonConvert.DeserializeObject<SlotConfig[]>(tr.ReadToEnd());
|
||||
|
||||
cbxSlotBox.Items.Clear();
|
||||
if (meterConfigList != null)
|
||||
{
|
||||
foreach (var item in meterConfigList.OrderBy(s => s.Slot))
|
||||
{
|
||||
cbxSlotBox.Items.Add(item.Slot);
|
||||
}
|
||||
}
|
||||
|
||||
cbxSlotBox.SelectedIndex = 0;
|
||||
Settings = new ApplicationSettings("ProductionUiConfig.json");
|
||||
|
||||
UseSetting(true);
|
||||
|
||||
if (cbxSlotBox.SelectedItem == null || string.IsNullOrEmpty(cbxSlotBox.SelectedItem.ToString()) ||
|
||||
!int.TryParse(cbxSlotBox.SelectedItem.ToString(), out var slot))
|
||||
{
|
||||
Slot = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_processCtrl = new ProcessController(Slot, _processStateDef);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message);
|
||||
MessageBox.Show(ex.Message,
|
||||
Properties.Resources.StrProcessStateError,
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
throw new ApplicationException(ex.Message);
|
||||
}
|
||||
|
||||
//install the process changed event handler for single processes
|
||||
_processCtrl.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler;
|
||||
_processCtrl.OnProcessLogRequest += ProcessLog_Handler;
|
||||
_processCtrl.OnProcessProgressChanged += ProcessProgress_Handler;
|
||||
_processCtrl.OnOverallProcessStateChanged += ProcessStateChange_Handler;
|
||||
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Tick += TmrProgressUpdate_Tick;
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
UpdateStatusGrid();
|
||||
}
|
||||
|
||||
private void Window_Closing(Object sender, CancelEventArgs e)
|
||||
{
|
||||
_userAccessWindow?.Close();
|
||||
_processCtrl.OnSingleProcessStateChanged -= SingleProcessStateChanged_Handler;
|
||||
_processCtrl.OnProcessLogRequest -= ProcessLog_Handler;
|
||||
_processCtrl.OnProcessProgressChanged -= ProcessProgress_Handler;
|
||||
_processCtrl.OnOverallProcessStateChanged -= ProcessStateChange_Handler;
|
||||
_userAccessWindow = null;
|
||||
|
||||
_processCtrl.Dispose();
|
||||
_processCtrl = null;
|
||||
_timer.Stop();
|
||||
_timer.Tick -= TmrProgressUpdate_Tick;
|
||||
_timer = null;
|
||||
_registerForm = null;
|
||||
}
|
||||
private void UseSetting(Boolean setSlot = false)
|
||||
{
|
||||
//tblkAutodetectState.Text = "Off";
|
||||
//if (Settings.AutoDetect)
|
||||
//{
|
||||
// tblkAutodetectState.Text = "On";
|
||||
//}
|
||||
if (setSlot)
|
||||
{
|
||||
cbxSlotBox.SelectedValue = Settings.Slot;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeFormState()
|
||||
{
|
||||
if (Software.IsRegistrationPending)
|
||||
{
|
||||
_registerForm = new RegisterLDAPUser();
|
||||
_registerForm.OnCancellation += OnUserAccessControlCancellation;
|
||||
UiElmEnable(optionsMenu, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiElmEnable(optionsMenu, true);
|
||||
UiElmEnable(optionsMenuChangePassword, Software.IsAutenticated);
|
||||
UiElmEnable(optionsMenuLogout, Software.IsAutenticated);
|
||||
UiElmEnable(optionsMenuLogin, !Software.IsAutenticated);
|
||||
UiElmEnable(btnStart, Software.IsAutenticated);
|
||||
UiElmEnable(btnStop, false);
|
||||
UpdateContentControl(lblOperator,
|
||||
Software.IsAutenticated
|
||||
? $"{Properties.Resources.StrLblOperatorNameText}: {Software.UserName}"
|
||||
: $"{Properties.Resources.StrLblOperatorNameText}: ?");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the user access window with login form or password change form.
|
||||
/// </summary>
|
||||
/// <param name="visible"></param>
|
||||
/// <param name="control">Windows forms control</param>
|
||||
/// <param name="headerTxt"></param>
|
||||
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </remarks>
|
||||
private void ShowUserAccessWindow(Boolean visible = true, Control control = null, String headerTxt = "")
|
||||
{
|
||||
if (visible && control != null)
|
||||
{
|
||||
UiElmEnable(optionsMenu, false);
|
||||
|
||||
if (_userAccessWindow == null)
|
||||
_userAccessWindow = new UserAccessWindow(control, headerTxt);
|
||||
// View the user access window as container for the windows forms password change and login
|
||||
_userAccessWindow.OnCloseWindow += OnUserAccessWindowClosed_Handler;
|
||||
_userAccessWindow.ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_userAccessWindow != null)
|
||||
{
|
||||
_userAccessWindow.OnCloseWindow -= OnUserAccessWindowClosed_Handler;
|
||||
try
|
||||
{
|
||||
_userAccessWindow.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
_userAccessWindow = null;
|
||||
}
|
||||
|
||||
if (_registerForm != null)
|
||||
_registerForm.OnCancellation -= OnUserAccessControlCancellation;
|
||||
|
||||
UiElmEnable(optionsMenu, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close event handler of the user access window with login form or password change form.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <remarks date="2025-Mar-25" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </remarks>
|
||||
private void OnUserAccessWindowClosed_Handler(Object sender, EventArgs eventArgs)
|
||||
{
|
||||
ShowUserAccessWindow(false);
|
||||
InitializeFormState();
|
||||
}
|
||||
|
||||
private void UpdateUi(ContentControl c, String content, Brush color = null)
|
||||
{
|
||||
if (color == null)
|
||||
color = Brushes.Black;
|
||||
|
||||
c.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
c.Content = content;
|
||||
c.Foreground = color;
|
||||
}));
|
||||
}
|
||||
|
||||
private void UpdateUi(TextBox c, String content, Brush color = null)
|
||||
{
|
||||
if (color == null)
|
||||
color = Brushes.Black;
|
||||
|
||||
c.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
c.Text = content;
|
||||
c.Foreground = color;
|
||||
}));
|
||||
}
|
||||
private void UpdateUi(TextBlock c, String content, Brush color = null)
|
||||
{
|
||||
if (color == null)
|
||||
color = Brushes.Black;
|
||||
|
||||
c.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
c.Text = content;
|
||||
c.Foreground = color;
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the elapsed time as information
|
||||
/// </summary>
|
||||
/// <returns>information attached to process depending on region</returns>
|
||||
/// <remarks date="2024-Jul-11" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void SetTimeDisplay()
|
||||
{
|
||||
var time = DateTimeOffset.UtcNow;
|
||||
var timeSpan = time - _startTime;
|
||||
UpdateContentControl(lblTimeValue, $@"{(UInt32)timeSpan.TotalMinutes}:{timeSpan.Seconds:00}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common routine for state change:
|
||||
/// - Log state change,
|
||||
/// - Execute stop procedure on error.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="2020-Dec-12" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Aug-30" author="Thomas Wiedebusch">
|
||||
/// - Extended.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
|
||||
/// - Extended.
|
||||
/// </remarks>
|
||||
private void ProcessStateChange_Handler(Object sender, ProcessStateArgs e)
|
||||
{
|
||||
_logger.Debug($"Slot:{Slot} - Final test state: {e.State}");
|
||||
|
||||
// Nothing to display
|
||||
if (e.State == ProcessState.Idle)
|
||||
{
|
||||
// Update progress bars
|
||||
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
|
||||
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
|
||||
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
|
||||
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
|
||||
}
|
||||
// Stop all procedures
|
||||
if (e.State == ProcessState.Stop)
|
||||
{
|
||||
_timer?.Stop();
|
||||
UiElmEnable(btnStart, true);
|
||||
UiElmEnable(btnStop, false);
|
||||
// update progress bars
|
||||
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
|
||||
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
|
||||
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
|
||||
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
|
||||
// remove user control
|
||||
SetNewUserControl(null);
|
||||
}
|
||||
// Repeat failed tests
|
||||
if (e.State == ProcessState.RepeatFailedTests)
|
||||
{
|
||||
UiElmEnable(btnStart, false);
|
||||
UiElmEnable(btnStop, true);
|
||||
_timer?.Start();
|
||||
}
|
||||
// Stop time for manual operation and visual inspection as this is not part of the configuration
|
||||
if (e.State == ProcessState.CheckOrderNumber)
|
||||
{
|
||||
_timer?.Stop();
|
||||
}
|
||||
// Prepare shipping
|
||||
if (e.State == ProcessState.PrepareShippingMode)
|
||||
{
|
||||
_timer?.Stop();
|
||||
UiElmEnable(btnStart, false);
|
||||
UiElmEnable(btnStop, false);
|
||||
}
|
||||
// Ready for shipping
|
||||
if (e.State == ProcessState.ReadyForShipping)
|
||||
{
|
||||
// Create extended report including all steps and each register write, read and comparison
|
||||
CreateFile(Properties.Resources.StrInfoSuccessPapers);
|
||||
BtnStop_Click(this, null);
|
||||
}
|
||||
// Print return report
|
||||
if (e.State == ProcessState.PrintReturnReport)
|
||||
{
|
||||
PrintErrorNote();
|
||||
BtnStop_Click(this, null);
|
||||
}
|
||||
// Print success report
|
||||
if (e.State == ProcessState.PrintSuccessReport)
|
||||
{
|
||||
PrintSuccessNote();
|
||||
BtnStop_Click(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable UI elements
|
||||
/// </summary>
|
||||
/// <param name="elm"></param>
|
||||
/// <param name="isEnabled"></param>
|
||||
/// <param name="isVisible"></param>
|
||||
private void UiElmEnable(UIElement elm, Boolean isEnabled, Boolean isVisible = true)
|
||||
{
|
||||
elm.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
elm.Visibility = isVisible ? Visibility.Visible : Visibility.Hidden;
|
||||
elm.IsEnabled = isEnabled;
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill logging window with information
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-07-06" author="Thomas Wiedebusch">
|
||||
/// - Changed to rich text edit with colored output.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
|
||||
/// - Avoid output of repeated text or useless empty text.
|
||||
/// </remarks>
|
||||
private void ProcessLog_Handler(Object sender, ProcessLogArgs e)
|
||||
{
|
||||
rtbLog.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
if (e != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// remove unacceptable spacing between the lines
|
||||
rtbLog.Document.LineHeight = 1;
|
||||
|
||||
var color = e.Color ?? Brushes.Black;
|
||||
var text = e.Data;
|
||||
if (!string.IsNullOrEmpty(text) && !text.Equals(_lastLoggingTextToAvoidRepetition))
|
||||
{
|
||||
var tr = new TextRange(rtbLog.Document.ContentEnd, rtbLog.Document.ContentEnd)
|
||||
{
|
||||
Text = text
|
||||
};
|
||||
tr.ApplyPropertyValue(TextElement.ForegroundProperty, color);
|
||||
rtbLog.ScrollToEnd();
|
||||
rtbLog.AppendText(Environment.NewLine);
|
||||
}
|
||||
|
||||
_lastLoggingTextToAvoidRepetition = text;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set values for progress bar, maximum and actual for single process and overall process.
|
||||
/// - Limit values to 0.0..100.0.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-07-18" author="Thomas Wiedebusch">
|
||||
/// - Introduced multi line text with center alignment.
|
||||
/// </remarks>
|
||||
private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e)
|
||||
{
|
||||
if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps)
|
||||
{
|
||||
_maxOverallProcessSteps = e.Progress;
|
||||
}
|
||||
else if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.MaxActualProcessSteps)
|
||||
{
|
||||
_maxSingleProcessSteps = e.Progress;
|
||||
}
|
||||
if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps)
|
||||
{
|
||||
pbTotalProgress.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
// limit output to progress bar to 0..100 as integer for process bar
|
||||
var progress = _maxOverallProcessSteps <= 0 ? 0.0 : e.Progress * 100.0 / _maxOverallProcessSteps;
|
||||
progress = progress > 100.0 ? 100.0 : progress < 0.0 ? 0.0 : progress;
|
||||
pbTotalProgress.Value = (Int32)progress;
|
||||
UpdateContentControl(lblTotalProgressValue, $@"{progress:##0.0} %");
|
||||
}
|
||||
));
|
||||
}
|
||||
else if (e.ProgressType == ProcessProgressArgs.ProcessProgressType.ActualProcessSteps)
|
||||
{
|
||||
pbSubProgress.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
// limit output to progress bar to 0..100 as integer for process bar
|
||||
var progress = _maxSingleProcessSteps <= 0 ? 0.0 : e.Progress * 100.0 / _maxSingleProcessSteps;
|
||||
progress = progress > 100.0 ? 100.0 : progress < 0.0 ? 0.0 : progress;
|
||||
pbSubProgress.Value = (Int32)progress;
|
||||
UpdateContentControl(lblSubProgressValue, $@"{progress:##0.0} %");
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output message to control without access violation.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
|
||||
/// - Introduced multi line text with center alignment.
|
||||
/// </remarks>
|
||||
private static void UpdateContentControl(ContentControl ctl, String text)
|
||||
{
|
||||
ctl.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
ctl.Content = new TextBlock()
|
||||
{
|
||||
Text = text,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status grid update for all single processes
|
||||
/// </summary>
|
||||
private void UpdateStatusGrid()
|
||||
{
|
||||
var dt = new DataTable();
|
||||
dt.Columns.Add(Properties.Resources.StrDataTableColumnProcess);
|
||||
dt.Columns.Add(Properties.Resources.StrDataTableColumnState);
|
||||
var i = 0;
|
||||
if (_processCtrl?.Processes == null)
|
||||
return;
|
||||
foreach (var item in _processCtrl.Processes)
|
||||
{
|
||||
dt.Rows.Add();
|
||||
dt.Rows[i][0] = item.ProcessName;
|
||||
dt.Rows[i][1] = ProductionProcessStateHelper.GetTextFromProductionProcessState(item.SingleProcessState);
|
||||
i++;
|
||||
}
|
||||
|
||||
dgTotalProcess.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
dgTotalProcess.DataContext = dt;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch new user control to main screen
|
||||
/// </summary>
|
||||
/// <param name="uc"></param>
|
||||
private void SetNewUserControl(UserControl uc)
|
||||
{
|
||||
dgTotalProcess.Dispatcher.Invoke(DispatcherPriority.Normal,
|
||||
new Action(() =>
|
||||
{
|
||||
// remove last userControl
|
||||
dpSubProcess.Children.Clear();
|
||||
if (uc != null)
|
||||
{
|
||||
dpSubProcess.Children.Add(uc);
|
||||
DockPanel.SetDock(uc, Dock.Top); // & Dock.Left);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#region TimerControls
|
||||
/// <summary>
|
||||
/// Handle timing depending updates.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2024-07-11" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void TmrProgressUpdate_Tick(Object sender, EventArgs e)
|
||||
{
|
||||
SetTimeDisplay();
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Update of status grid with information returned by user control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-08-28" author="Thomas Wiedebusch">
|
||||
/// - Activate new user control on start of process.
|
||||
/// </remarks>
|
||||
private void SingleProcessStateChanged_Handler(Object sender, SingleProcessStateArgs e)
|
||||
{
|
||||
if (sender is IProductionProcess productionProcess)
|
||||
{
|
||||
if (e.SingleProcessState == SingleProcessState.Running)
|
||||
{
|
||||
// Set new user control
|
||||
SetNewUserControl(productionProcess.GetUserControl());
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove user control on idle, skipped, abort or error
|
||||
SetNewUserControl(null);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatusGrid();
|
||||
UpdateGuiWithProductInfos();
|
||||
}
|
||||
|
||||
private void OnUserAccessControlCancellation(Control control)
|
||||
{
|
||||
OnUserAccessWindowClosed_Handler(this, null);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set labels of device information and order number
|
||||
/// </summary>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-07-15" author="Thomas Wiedebusch">
|
||||
/// - Additional info of Cordonel.
|
||||
/// </remarks>
|
||||
private void UpdateGuiWithProductInfos()
|
||||
{
|
||||
var genesisMeter = _processCtrl?.GetGenesisMeter();
|
||||
_productionRequirements = _processCtrl?.GetProductionRequirements();
|
||||
|
||||
if (genesisMeter == null)
|
||||
return;
|
||||
|
||||
UpdateContentControl(lblOrderNumberValue,
|
||||
genesisMeter.OrderNumber != 0 ? genesisMeter.OrderNumber.ToString() : "?");
|
||||
|
||||
UpdateContentControl(lblRadioAddressValue,
|
||||
genesisMeter.RadioAddress != 0 ? genesisMeter.RadioAddress.ToString() : "");
|
||||
UpdateContentControl(lblRadioAddressText,
|
||||
genesisMeter.RadioAddress != 0 ? Properties.Resources.StrLblRadioAddressTxt + ":" : "");
|
||||
|
||||
UpdateContentControl(lblFwValue,
|
||||
!string.IsNullOrEmpty(genesisMeter.FwVersion) ? genesisMeter.FwVersion : "?");
|
||||
|
||||
UpdateContentControl(lblMeterSizeValue,
|
||||
!string.IsNullOrEmpty(genesisMeter.MeterSize) ? genesisMeter.MeterSize : "?");
|
||||
|
||||
UpdateContentControl(lblLutValue,
|
||||
!string.IsNullOrEmpty(genesisMeter.LutCrc) ? genesisMeter.LutCrc : "?");
|
||||
|
||||
|
||||
UpdateContentControl(lblSerialNumberValue,
|
||||
!string.IsNullOrEmpty(genesisMeter.CustomerSerialNumber) ? genesisMeter.CustomerSerialNumber : "?");
|
||||
|
||||
UpdateContentControl(lblRegionValue,
|
||||
!string.IsNullOrEmpty(genesisMeter.Region) ? genesisMeter.Region : "?");
|
||||
|
||||
UpdateUi(lblPcbIdValue, !string.IsNullOrEmpty(genesisMeter.PcbId) ? genesisMeter.PcbId : "?");
|
||||
|
||||
var msg = "";
|
||||
var color = Brushes.Black;
|
||||
if (_productionRequirements?.IsSpecialActive != null && (Boolean)_productionRequirements.IsSpecialActive &&
|
||||
_productionRequirements?.IsSpecialApproved != null && (Boolean)_productionRequirements.IsSpecialApproved)
|
||||
{
|
||||
msg = $"{Properties.Resources.StrLblSpecialRequirement} - Id.Ver: {_productionRequirements.SpecialId}." +
|
||||
$"{_productionRequirements.SpecialVersion}";
|
||||
color = Brushes.Red;
|
||||
}
|
||||
else if (_productionRequirements?.IsStandardActive != null && (Boolean)_productionRequirements.IsStandardActive &&
|
||||
_productionRequirements?.IsStandardApproved != null && (Boolean)_productionRequirements.IsStandardApproved)
|
||||
{
|
||||
msg = $"{Properties.Resources.StrLblStandardRequirement} - Id.Ver: {_productionRequirements.StandardId}." +
|
||||
$"{_productionRequirements.StandardVersion}";
|
||||
}
|
||||
|
||||
UpdateUi(lblApprovalText, msg, color);
|
||||
}
|
||||
|
||||
//private void BtnSwitchAutoDetect_Click(Object sender, RoutedEventArgs e)
|
||||
//{
|
||||
// //Settings.AutoDetect = !Settings.AutoDetect;
|
||||
// //StoreSetting();
|
||||
// //UseSetting();
|
||||
//}
|
||||
|
||||
private void OnLogout_Click(Object sender, EventArgs e)
|
||||
{
|
||||
Software.Logout();
|
||||
InitializeFormState();
|
||||
}
|
||||
|
||||
private void OnLogin_Click(Object sender, EventArgs args)
|
||||
{
|
||||
ShowUserAccessWindow(true, new LoginForm(OnUserAccessControlCancellation),
|
||||
Properties.Resources.StrLoginMsg);
|
||||
}
|
||||
|
||||
private void OnChangePassword_Click(Object _, EventArgs _1)
|
||||
{
|
||||
ShowUserAccessWindow(true, new ChangePassword(OnUserAccessControlCancellation),
|
||||
Properties.Resources.StrChangePasswordMsg);
|
||||
}
|
||||
|
||||
private void CbxSlotBox_SelectionChanged(Object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void StoreSetting()
|
||||
{
|
||||
Settings.Update("ProductionUiConfig.json");
|
||||
}
|
||||
|
||||
//private void BtnConnect_Click(Object sender, RoutedEventArgs e)
|
||||
//{
|
||||
// Settings.Slot = (Int32)cbxSlotBox.SelectedValue;
|
||||
// StoreSetting();
|
||||
//}
|
||||
|
||||
private void BtnStart_Click(Object sender, RoutedEventArgs e)
|
||||
{
|
||||
UiElmEnable(btnStart, false);
|
||||
UiElmEnable(btnStop, true);
|
||||
|
||||
_startTime = DateTimeOffset.UtcNow;
|
||||
_timer?.Start();
|
||||
|
||||
// remove last userControl
|
||||
dpSubProcess.Children.Clear();
|
||||
rtbLog.Document.Blocks.Clear();
|
||||
_processCtrl?.SetProcessState(ProcessState.DetectCordonel);
|
||||
}
|
||||
|
||||
private void PrintErrorNote()
|
||||
{
|
||||
// Safe report to file
|
||||
CreateFile(Properties.Resources.StrInstructionReturnPapers, true, true);
|
||||
}
|
||||
|
||||
private void PrintSuccessNote()
|
||||
{
|
||||
// Safe report to file
|
||||
CreateFile(Properties.Resources.StrInfoSuccessPapers, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logging a file with all process information, building a compressed report for printout.
|
||||
/// </summary>
|
||||
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
|
||||
/// - Reduced printout.
|
||||
/// </remarks>
|
||||
private void CreateFile(String headerInfo, Boolean shortReport = false, Boolean printReport = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var genesisMeter = _processCtrl.GetGenesisMeter();
|
||||
|
||||
sb.AppendLine("========================================================================");
|
||||
sb.AppendLine(headerInfo);
|
||||
sb.AppendLine("========================================================================");
|
||||
sb.AppendLine(_strSoftwareNameVersion);
|
||||
sb.AppendLine("------------------------------------------------------------------------");
|
||||
sb.AppendLine($"{Properties.Resources.StrDateTime}: {DateTime.Now}");
|
||||
sb.AppendLine("------------------------------------------------------------------------");
|
||||
sb.AppendLine($"PcbId: {genesisMeter.PcbId}");
|
||||
sb.AppendLine($"{Properties.Resources.StrPrintOrderNumberTxt}: {genesisMeter.OrderNumber}");
|
||||
sb.AppendLine($"{Properties.Resources.StrLblSerialNumberTxt}: {genesisMeter.CustomerSerialNumber}");
|
||||
sb.AppendLine($"{Properties.Resources.StrLblOperatorNameText}: {Software.UserName}");
|
||||
sb.AppendLine("------------------------------------------------------------------------");
|
||||
|
||||
//TODO THW take the log path from setup (done with GTB)
|
||||
var loggingPath = $"C:\\GenesisLog\\{DateTime.Now:yyyy-MM-dd}\\ProductionUiCordonel\\";
|
||||
var fileName = $"{loggingPath}{DateTime.Now.ToString("yyyyMMddHHmmss")}_ExtendedReport_{genesisMeter.PcbId}.txt";
|
||||
|
||||
// Copy the header to the compressed string builder
|
||||
var compressedSb = new StringBuilder();
|
||||
if (shortReport)
|
||||
{
|
||||
compressedSb.Append(sb);
|
||||
}
|
||||
|
||||
var rtbText = new TextRange(
|
||||
rtbLog.Document.ContentStart,
|
||||
rtbLog.Document.ContentEnd
|
||||
);
|
||||
sb.Append($"{rtbText.Text}");
|
||||
// Write the complete report to the file
|
||||
File.WriteAllText(fileName, sb.ToString());
|
||||
|
||||
// Reduce sb content to compress it for printout
|
||||
if (shortReport)
|
||||
{
|
||||
|
||||
fileName = $"{loggingPath}{DateTime.Now.ToString("yyyyMMddHHmmss")}" +
|
||||
$"_ShortReport_{genesisMeter.PcbId}.txt";
|
||||
var sr = new StringReader(rtbText.Text);
|
||||
var srLine = sr.ReadLine();
|
||||
while (srLine != null)
|
||||
{
|
||||
if (srLine.Contains(Properties.Resources.StrProcessStateError) ||
|
||||
srLine.Contains(Properties.Resources.StrProcessStateDone) ||
|
||||
srLine.Contains(Properties.Resources.StrProcessStateWarning) ||
|
||||
srLine.Contains(Properties.Resources.StrProcessStateSkipped) ||
|
||||
srLine.Contains(Properties.Resources.StrWarningMsgSpecialRequirementActive))
|
||||
compressedSb.AppendLine(srLine);
|
||||
srLine = sr.ReadLine();
|
||||
}
|
||||
|
||||
// Returns the compressed file for error report printout
|
||||
File.WriteAllText(fileName, compressedSb.ToString());
|
||||
if (printReport)
|
||||
Process.Start(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop button as user interaction to kill the finalization process
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnStop_Click(Object sender, RoutedEventArgs e)
|
||||
{
|
||||
//set the process state to the state machine
|
||||
_processCtrl?.SetProcessState(ProcessState.Stop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open requirement in Web-Browser.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void LblApprovalText_Click(Object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
var requirementId = 0;
|
||||
var requirementWebPageLink = ServiceUrls.InspectCordonelRequirementInfoServiceUrl();
|
||||
if (_productionRequirements?.SpecialId != null &&
|
||||
_productionRequirements?.IsSpecialApproved != null &&
|
||||
_productionRequirements.SpecialId != 0)
|
||||
{
|
||||
requirementId = (Int32)_productionRequirements.SpecialId;
|
||||
requirementWebPageLink += $"SpecialRequirements#{requirementId}";
|
||||
}
|
||||
else if (_productionRequirements?.StandardId != null &&
|
||||
_productionRequirements?.IsStandardApproved != null &&
|
||||
_productionRequirements.StandardId != 0)
|
||||
{
|
||||
requirementId = (Int32)_productionRequirements.StandardId;
|
||||
requirementWebPageLink += $"StandardRequirements#{requirementId}";
|
||||
}
|
||||
|
||||
if (requirementId != 0)
|
||||
{
|
||||
Process.Start(requirementWebPageLink);
|
||||
}
|
||||
}
|
||||
|
||||
private void HlpMenu_Click(Object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filename = "";
|
||||
if (Thread.CurrentThread.CurrentCulture.Name.Contains("de"))
|
||||
{
|
||||
filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Handbuch Cordonel Versandfertigmachen (VFM).pdf");
|
||||
}
|
||||
else if (Thread.CurrentThread.CurrentCulture.Name.Contains("en"))
|
||||
{
|
||||
filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Manual Cordonel End of Line Test (EOL).pdf");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
Process.Start(filename);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(Object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!Software.IsAutenticated)
|
||||
OnLogin_Click(this, null);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user