common/Production/ProductionUiCordonel/ProductionWindow.xaml.cs
2026-04-23 17:50:07 +02:00

1176 lines
49 KiB
C#

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
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 System.Xml.Linq;
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.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 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 _strAppNameVersion;
private RegisterLDAPUser _registerForm;
private Boolean _lockGenesisInfo;
private Int32 OrderAmount { get; set; }
private Int32 OrderCounter { get; set; }
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 readonly IProcessStateDef _processStateDef;
private readonly Boolean _autoStartFirstProductionProcess;
#endregion
/// <summary>
/// Ctor
/// </summary>
/// <param name="softwareName"></param>
/// <param name="projectName">take the identical window for different software, this is name displayed
/// in the header</param>
/// <param name="processStateDef">table for state definitions for the specific program which feeds the
/// state machine</param>
/// <param name="autoStartFirstProductionProcess">starts the state machine with the first production process
/// defined in the processStateDef with productionProcessNo == 0</param>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Additional info of Cordonel.
/// </remarks>
/// <remarks date="2025-Jul-01" author="Thomas Wiedebusch">
/// - Auto start.
/// </remarks>
/// <remarks date="2025-Sep-15" author="Thomas Wiedebusch">
/// - Order counter.
/// </remarks>
public ProductionWindow(String softwareName, String projectName, IProcessStateDef processStateDef,
Boolean autoStartFirstProductionProcess)
{
_logger = NLogHelper.CreateOrGetLogger(projectName);
_processStateDef = processStateDef;
_autoStartFirstProductionProcess = autoStartFirstProductionProcess;
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
_strAppNameVersion = $"{softwareName} {_version}";
_logger.Info(_strAppNameVersion);
Title = _strAppNameVersion;
//static labels
UpdateContentControl(lblSerialNumberTxt, Properties.Resources.StrLblSerialNumberTxt + ":");
UpdateContentControl(lblRadioAddressText, Properties.Resources.StrLblRadioAddressTxt + ":");
UpdateContentControl(lblOrderNumberText, Properties.Resources.StrLblOrderNumberTxt + ":");
UpdateContentControl(lblOrderCounterText, Properties.Resources.StrLblOrderCounterTxt + ":");
UpdateContentControl(lblMeterSizeText, Properties.Resources.StrLblMeterSizeTxt + ":");
UpdateContentControl(lblMeterLengthText, Properties.Resources.StrLblMeterLengthTxt + ":");
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;
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.OnProcessStateChanged += ProcessStateChanged_Handler;
_processCtrl.OnProcessLogRequest += ProcessLog_Handler;
_processCtrl.OnProcessProgressChanged += ProcessProgress_Handler;
_processCtrl.OnStateMachineStateChanged += StateMachineStateChanged_Handler;
_processCtrl.OnGuiMessageDispatcher += GuiMessageDispatcher_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.OnProcessStateChanged -= ProcessStateChanged_Handler;
_processCtrl.OnProcessLogRequest -= ProcessLog_Handler;
_processCtrl.OnProcessProgressChanged -= ProcessProgress_Handler;
_processCtrl.OnStateMachineStateChanged -= StateMachineStateChanged_Handler;
_processCtrl.OnGuiMessageDispatcher -= GuiMessageDispatcher_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;
cbxSlotBox.SelectedValue = 0;
}
}
/// <summary>
/// Enable/disable the controls
/// </summary>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
/// - Additional info of Cordonel.
/// </remarks>
/// <remarks date="2025-Jul-01" author="Thomas Wiedebusch">
/// - Auto start.
/// </remarks>
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);
// Hide the [Start] button if autostart enabled
UiElmEnable(btnStart, Software.IsAutenticated, !_autoStartFirstProductionProcess);
UiElmEnable(btnStop, false);
UpdateContentControl(lblOperator, Software.IsAutenticated
? $"{Properties.Resources.StrLblOperatorNameText}: {Software.UserName}"
: $"{Properties.Resources.StrLblOperatorNameText}: ?");
// Start first production process if required, user is authenticated and state machine is idle
if (_autoStartFirstProductionProcess &&
Software.IsAutenticated &&
ProcessState.Idle == _processCtrl?.GetProcessState())
{
BtnStart_Click(this, null);
}
}
}
/// <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>
/// <remarks date="2025-Jun-30" author="Thomas Wiedebusch">
/// - Automatic start on idle if required.
/// </remarks>
private void StateMachineStateChanged_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));
// Start first production process if required and user is authenticated
if (_autoStartFirstProductionProcess && Software.IsAutenticated)
{
BtnStart_Click(this, null);
}
}
// Stop all procedures
if (e.State == ProcessState.Stop)
{
_timer?.Stop();
UiElmEnable(btnStart, true, !_autoStartFirstProductionProcess);
UiElmEnable(btnStop, false);
// update progress bars
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
}
// Repeat failed tests
if (e.State == ProcessState.RepeatFailedTests)
{
UiElmEnable(btnStart, false, !_autoStartFirstProductionProcess);
UiElmEnable(btnStop, true);
ProcessProgress_Handler(this, new ProcessProgressArgs(0,
ProcessProgressArgs.ProcessProgressType.ActualProcessSteps));
_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, !_autoStartFirstProductionProcess);
UiElmEnable(btnStop, false);
}
// Prepare assembly line release
if (e.State == ProcessState.PrepareAssemblyLineRelease)
{
_timer?.Stop();
UiElmEnable(btnStart, false, !_autoStartFirstProductionProcess);
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.StrInfoSuccessShipping);
BtnStop_Click(this, null);
}
// Ready after assembly
if (e.State == ProcessState.AssemblyLineRelease)
{
// Create extended report including all steps and each register write, read and comparison
CreateFile(Properties.Resources.StrInfoSuccessAssembly);
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);
}
// Enable display of Genesis information on new detected
if (e.State == ProcessState.ConnectCordonel)
{
_lockGenesisInfo = false;
}
// Enable timer with first automatic process
if (e.State == ProcessState.DetectCordonel)
{
if (_timer != null && !_timer.IsEnabled)
_startTime = DateTimeOffset.UtcNow;
_timer?.Start();
}
}
/// <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>
/// Dispatch GUI messages
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks date="2025-Sep-15" author="Thomas Wiedebusch">
/// - Initial
/// </remarks>
private void GuiMessageDispatcher_Handler(Object sender, GuiMessageArgs e)
{
if (e?.Obj is Int32 obj)
{
switch (e.GuiItem)
{
case GuiItem.OrderAmount:
OrderAmount = e.Obj != null ? obj : 0;
break;
case GuiItem.OrderCounter:
OrderCounter = e.Obj != null ? obj : 0;
break;
}
}
}
/// <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 ProcessStateChanged_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-Jul-15" author="Thomas Wiedebusch">
/// - Additional info of Cordonel.
/// </remarks>
/// <remarks date="2025-Jun-18" author="Thomas Wiedebusch">
/// - Lock display of OLD Genesis after connect until the Genesis has been detected.
/// </remarks>
/// <remarks date="2025-Sep-15" author="Thomas Wiedebusch">
/// - Order counter.
/// </remarks>
private void UpdateGuiWithProductInfos()
{
var genesisMeter = _processCtrl?.GetGenesisMeter();
_productionRequirements = _processCtrl?.GetProductionRequirements();
if (genesisMeter == null )
return;
UpdateContentControl(lblOrderNumberValue,
genesisMeter.OrderNumber != 0 ? genesisMeter.OrderNumber.ToString() : "?");
UpdateContentControl(lblOrderCounterValue,
OrderAmount != 0 && genesisMeter.OrderNumber != 0 ? $"{OrderCounter} / " +
$"{OrderAmount} {Properties.Resources.StrLblPieces}" : "");
UpdateContentControl(lblOrderCounterText,
OrderAmount != 0 && genesisMeter.OrderNumber != 0 ? Properties.Resources.StrLblOrderCounterTxt + ":" : "");
if (_lockGenesisInfo)
return;
//UpdateContentControl(lblOrderNumberValue,
// genesisMeter.OrderNumber != 0 ? genesisMeter.OrderNumber.ToString() : "?");
//UpdateContentControl(lblOrderCounterValue,
// OrderAmount != 0 && genesisMeter.OrderNumber != 0 ? $"{OrderCounter} / " +
// $"{OrderAmount} {Properties.Resources.StrLblPieces}" : "");
//UpdateContentControl(lblOrderCounterText,
// OrderAmount != 0 && genesisMeter.OrderNumber != 0 ? Properties.Resources.StrLblOrderCounterTxt + ":" : "");
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(lblMeterLengthValue,
!string.IsNullOrEmpty(genesisMeter.MeterLength) && !genesisMeter.MeterLength.Equals("?") ?
genesisMeter.MeterLength + " mm" : "?");
UpdateContentControl(lblInterfaceVersionValue,
!string.IsNullOrEmpty(genesisMeter.InterfaceInfo.InterfaceVersion) ?
genesisMeter.InterfaceInfo.InterfaceVersion : "?");
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();
//}
/// <summary>
/// Start assembly process
/// </summary>
/// <remarks date="????" author="Roland Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2025-Sep-15" author="Thomas Wiedebusch">
/// - Order counter.
/// </remarks>
/// <remarks date="2025-Sep-16" author="Thomas Wiedebusch">
/// - Reset order number to avoid display glitch on changed PcbId.
/// </remarks>
private void BtnStart_Click(Object sender, RoutedEventArgs e)
{
UiElmEnable(btnStart, false, !_autoStartFirstProductionProcess);
UiElmEnable(btnStop, true);
_startTime = DateTimeOffset.UtcNow;
_timer?.Start();
// Avoid display of genesis info before the detect new meter has been executed
_lockGenesisInfo = true;
// Reset order amount until data from database has been collected
OrderAmount = 0;
// Reset order number to avoid display glitch on changed PcbId
var genesisMeter = _processCtrl?.GetGenesisMeter();
if (genesisMeter != null)
{
genesisMeter.OrderNumber = 0;
}
rtbLog.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(() =>
{
rtbLog.Document.Blocks.Clear();
}
));
// clear all visual fields as it may be a new pcbId
UpdateContentControl(lblOrderNumberValue, "?");
UpdateContentControl(lblRadioAddressValue, "");
UpdateContentControl(lblRadioAddressText, "");
UpdateContentControl(lblOrderCounterValue, "");
UpdateContentControl(lblOrderCounterText, "");
UpdateContentControl(lblFwValue, "?");
UpdateContentControl(lblMeterSizeValue, "?");
UpdateContentControl(lblInterfaceVersionValue, "?");
UpdateContentControl(lblMeterLengthValue, "?");
UpdateContentControl(lblLutValue, "?");
UpdateContentControl(lblSerialNumberValue, "?");
UpdateContentControl(lblRegionValue, "?");
UpdateUi(lblPcbIdValue, "?");
UpdateUi(lblApprovalText, "", Brushes.Black);
_processCtrl?.StartFirstProcess();
}
private void PrintErrorNote()
{
// Safe report to file
CreateFile(Properties.Resources.StrInstructionReturnPapers, true, true);
}
private void PrintSuccessNote()
{
// Safe report to file
var msg = "";
if (_strAppNameVersion.Contains(Properties.Resources.StrEolSoftwareNameVersion))
msg = Properties.Resources.StrInfoSuccessShipping;
else if (_strAppNameVersion.Contains(Properties.Resources.StrPickingSoftwareNameVersion))
msg = Properties.Resources.StrInfoSuccessAssembly;
CreateFile(msg, 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>
/// <remarks date="2025-Jun-17" author="Thomas Wiedebusch">
/// - Base logging path taken from NLogHelper as log-path setting made in GTB.
/// </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(_strAppNameVersion);
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("------------------------------------------------------------------------");
try
{
var path = NLogHelper.GetApplicationDataPath();
if (string.IsNullOrEmpty(path))
{
path = NLogHelper.GetCurrentApplicationFolder();
}
var a = XElement.Load(path);
var baseLoggingPath = a.Elements().First(s => s.Name.LocalName == "variable").Attributes().First(s => s.Value == "BasePath").NextAttribute.Value;
var loggingPath = $"{baseLoggingPath}\\{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.StrProcessStateAttention))
compressedSb.AppendLine(srLine);
srLine = sr.ReadLine();
}
// Returns the compressed file for error report printout
File.WriteAllText(fileName, compressedSb.ToString());
if (printReport)
{
Process.Start(fileName);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
/// <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 (_strAppNameVersion.Contains(Properties.Resources.StrEolSoftwareNameVersion))
{
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");
}
}
else if (_strAppNameVersion.Contains(Properties.Resources.StrPickingSoftwareNameVersion))
{
if (Thread.CurrentThread.CurrentCulture.Name.Contains("de"))
{
filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Handbuch Cordonel Montagelinie (MTL).pdf");
}
else if (Thread.CurrentThread.CurrentCulture.Name.Contains("en"))
{
filename = Path.Combine(_assemblyExecRootPath, "Docu", @"Manual Assembly Line (ABL).pdf");
}
}
if (!string.IsNullOrEmpty(filename))
{
Process.Start(filename);
}
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
private void Window_Loaded(Object sender, RoutedEventArgs e)
{
if (!Software.IsAutenticated)
OnLogin_Click(this, null);
}
}
}