ProductionUiCordonel: - manually merged some modules from main EOL_2.6.20
This commit is contained in:
@@ -155,7 +155,7 @@
|
|||||||
</site>
|
</site>
|
||||||
<site name="MeterProcessState" id="2">
|
<site name="MeterProcessState" id="2">
|
||||||
<application path="/" applicationPool="Clr4IntegratedAppPool">
|
<application path="/" applicationPool="Clr4IntegratedAppPool">
|
||||||
<virtualDirectory path="/" physicalPath="C:\Users\SZLATEV\source\repos\laa_production\Common\Service\MeterProcessState" />
|
<virtualDirectory path="/" physicalPath="D:\Projekte\SENSUS_GitLab\LAA_PRODUCTION\Common\Service\MeterProcessState" />
|
||||||
</application>
|
</application>
|
||||||
<bindings>
|
<bindings>
|
||||||
<binding protocol="http" bindingInformation="*:56011:localhost" />
|
<binding protocol="http" bindingInformation="*:56011:localhost" />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The port is not assigned.
|
/// The port is not assigned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const String PortNotAssigned = "NA";
|
public const String PortNotAssigned = "NA";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
|
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
|
||||||
@@ -193,6 +193,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose serial port
|
/// Dispose serial port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
||||||
|
/// - Dispose procedure changed.
|
||||||
|
/// </remarks>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -206,6 +209,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
|
|
||||||
//Run thread again to notice CancellationToken has changed
|
//Run thread again to notice CancellationToken has changed
|
||||||
_onSyncReceiveThread.Set();
|
_onSyncReceiveThread.Set();
|
||||||
|
|
||||||
|
// dispose directly called from here to overcome glitches caused by USB to serial interface
|
||||||
|
_serialPort.Dispose();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -419,6 +425,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
/// The IrDA sniffer showed these on the communication line, but they are incompletely received.
|
/// The IrDA sniffer showed these on the communication line, but they are incompletely received.
|
||||||
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
|
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
||||||
|
/// - Dispose procedure changed.
|
||||||
|
/// </remarks>
|
||||||
private void ReadingThreadLoop()
|
private void ReadingThreadLoop()
|
||||||
{
|
{
|
||||||
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
|
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
|
||||||
@@ -448,8 +457,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun)
|
if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun)
|
||||||
{
|
{
|
||||||
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
|
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
|
||||||
_receiveTimeStampPc, dataSyncMarkThisRun));
|
_receiveTimeStampPc, dataSyncMarkThisRun));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RecordStreamingRawData)
|
if (RecordStreamingRawData)
|
||||||
{
|
{
|
||||||
AsciiDataLogger.Trace($"{rxStringRecord}");
|
AsciiDataLogger.Trace($"{rxStringRecord}");
|
||||||
@@ -503,12 +513,14 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
||||||
{
|
{
|
||||||
if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) &&
|
if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) &&
|
||||||
rxByteRecord[rxByteRecord.Count - 1].Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
rxByteRecord[rxByteRecord.Count - 1]
|
||||||
|
.Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
||||||
{
|
{
|
||||||
//skips this byte and read the next one
|
//skips this byte and read the next one
|
||||||
rxByte = (Byte)_serialPort.ReadByte();
|
rxByte = (Byte)_serialPort.ReadByte();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//add byte to receive result buffer
|
//add byte to receive result buffer
|
||||||
rxByteRecord.Add(rxByte);
|
rxByteRecord.Add(rxByte);
|
||||||
|
|
||||||
@@ -552,7 +564,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ThreadAbortException)
|
catch (ThreadAbortException)
|
||||||
{ throw; }
|
{
|
||||||
|
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
||||||
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException)
|
||||||
{
|
{
|
||||||
//ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming
|
//ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming
|
||||||
@@ -575,12 +589,12 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
if (_receiveThread.ThreadState == ThreadState.Aborted
|
if (_receiveThread.ThreadState == ThreadState.Aborted
|
||||||
|| _receiveThread.ThreadState == ThreadState.AbortRequested)
|
|| _receiveThread.ThreadState == ThreadState.AbortRequested)
|
||||||
{
|
{
|
||||||
_byteDataLogger.Warn(ex, $"{ Ident} ThreadState is Aborted or AbortRequested but an " +
|
_byteDataLogger.Warn(ex, $"{Ident} ThreadState is Aborted or AbortRequested but an " +
|
||||||
"error occurred while reading records from serial port");
|
"error occurred while reading records from serial port");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_byteDataLogger.Error(ex, $"{ Ident} Error while reading records from serial port");
|
_byteDataLogger.Error(ex, $"{Ident} Error while reading records from serial port");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -598,31 +612,33 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//while (!_tokenReadData.IsCancellationRequested)
|
} //while (!_tokenReadData.IsCancellationRequested)
|
||||||
}// over hole reading thread loop
|
} // over hole reading thread loop
|
||||||
catch (ThreadAbortException)
|
catch (ThreadAbortException)
|
||||||
{ }
|
{
|
||||||
|
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
||||||
}
|
}
|
||||||
finally
|
//finally
|
||||||
{
|
//{
|
||||||
if (_serialPort.IsOpen)
|
// //var retries = 5;
|
||||||
{
|
// //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0)
|
||||||
_serialPort.Close();
|
// //{
|
||||||
if (_serialPort.IsOpen)
|
// // _serialPort.Close();
|
||||||
{
|
// // if (_serialPort.IsOpen)
|
||||||
_byteDataLogger.Info($"{Ident} Port close delay!");
|
// // {
|
||||||
Thread.Sleep(50);
|
// // _byteDataLogger.Info($"{Ident} Port closing delay!");
|
||||||
_serialPort.Close();
|
// // Thread.Sleep(500);
|
||||||
}
|
// // }
|
||||||
_serialPort.Dispose();
|
// //}
|
||||||
_byteDataLogger.Info($"{Ident} Port is closed");
|
// //if (_serialPort != null && _serialPort.IsOpen)
|
||||||
|
// // _byteDataLogger.Info($"{Ident} Port unable to close");
|
||||||
|
// //else
|
||||||
}
|
// // _byteDataLogger.Info($"{Ident} Port is closed");
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -0,0 +1,693 @@
|
|||||||
|
using NLog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Threading;
|
||||||
|
using Xylem.Common.CommonCore.Consts;
|
||||||
|
using Xylem.Common.CommonCore.ThreadWatcher;
|
||||||
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||||||
|
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
|
||||||
|
using Xylem.Common.Utils.Logging;
|
||||||
|
|
||||||
|
namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All Ports must use BasePort as an base class
|
||||||
|
/// its support some wrapping event/function handling
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseSerialPort : IPort, IDisposable
|
||||||
|
{
|
||||||
|
|
||||||
|
private ILogger _byteDataLogger;
|
||||||
|
internal ILogger AsciiDataLogger;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The port is not assigned.
|
||||||
|
/// </summary>
|
||||||
|
public const String PortNotAssigned = "NA";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
|
||||||
|
|
||||||
|
//initially do not signal event
|
||||||
|
private readonly AutoResetEvent _onSyncReceiveThread = new AutoResetEvent(false);
|
||||||
|
private readonly Thread _receiveThread;
|
||||||
|
private readonly CancellationTokenSource _receiveToken = new CancellationTokenSource();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay between bytes if receiving has started in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
private const Int32 InterByteReadDelayMs = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// internal SerialPort class
|
||||||
|
/// </summary>
|
||||||
|
private readonly SerialPort _serialPort;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activate raw record recording
|
||||||
|
/// </summary>
|
||||||
|
public Boolean RecordStreamingRawData = false;
|
||||||
|
//public Boolean RecordStreamingRawData
|
||||||
|
//{
|
||||||
|
// get => _recordStreamingRawData;
|
||||||
|
// set => _recordStreamingRawData = value;
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// date and time of incoming first date interrupt on serial IO to set the
|
||||||
|
/// PC time-stamp to the record
|
||||||
|
/// </summary>
|
||||||
|
private DateTimeOffset _receiveTimeStampPc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Individual port settings depending on transmit protocol
|
||||||
|
/// </summary>
|
||||||
|
protected TransmitPortSettings PortSettingsForTransmitProtocol;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public String GetPortName()
|
||||||
|
{
|
||||||
|
return _serialPort.PortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// store a identification of a port, this contains the Slot, Port, Protocol and Type
|
||||||
|
/// e.g. "Slot:1, Port:COM4, Protocol:Request, Type:IrDA -"
|
||||||
|
/// </summary>
|
||||||
|
public String Ident;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor for SerialComPort with and full constructed <see cref="T:System.IO.Ports.SerialPort" /> properties
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ident">set port as unique</param>
|
||||||
|
/// <param name="serialPort">Class for communication, all parameters for serial communication needs to be set</param>
|
||||||
|
/// <param name="setting">Class for store all parameters for serial communication needs to be set came from transmit protocol</param>
|
||||||
|
protected BaseSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
|
||||||
|
{
|
||||||
|
Ident = ident;
|
||||||
|
|
||||||
|
//communication port settings
|
||||||
|
_serialPort = serialPort;
|
||||||
|
_serialPort.BaudRate = (Int32)setting.BaudRate;
|
||||||
|
|
||||||
|
PortSettingsForTransmitProtocol = setting;
|
||||||
|
|
||||||
|
//assign thread to loop
|
||||||
|
_receiveThread = new Thread(ReadingThreadLoop) { Name = $"{Ident} Reading thread" };
|
||||||
|
|
||||||
|
AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
|
||||||
|
_byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void RefreshLogger()
|
||||||
|
{
|
||||||
|
|
||||||
|
AsciiDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "LedRawData", "TargetLedRawDataBase", "LedRawDataBase");
|
||||||
|
_byteDataLogger = NLogHelper.CreateOrGetMultiLogger(Ident, "", "UartSerialPort", "COMBase", "BaseSerialPort");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// received bytes in buffer of SerialComPort, calls base function
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>number of bytes in Rx buffer</returns>
|
||||||
|
protected Int32 BytesToRead()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _serialPort.BytesToRead;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error(ex);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// read single byte from Rx buffer of SerialComPort, calls base function
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected Int32 ReadByte()
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _serialPort.ReadByte();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error(ex);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void PortWrite(Byte[] record)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SpecificPortWrite(record);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new SystemException($"{Ident} Communication error while sending data to port", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// flush Rx and Tx buffer of SerialComPort, calls base functions
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_serialPort.DiscardInBuffer();
|
||||||
|
_serialPort.DiscardOutBuffer();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error(ex);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose serial port
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
//remove registration for receive delegate
|
||||||
|
_serialPort.DataReceived -= PhysicalDataReceived;
|
||||||
|
|
||||||
|
//Cancel send and receive tokens
|
||||||
|
_receiveToken.Cancel();
|
||||||
|
|
||||||
|
//Run thread again to notice CancellationToken has changed
|
||||||
|
_onSyncReceiveThread.Set();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error(ex);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
//set receive interrupt threshold for immediate execution
|
||||||
|
_serialPort.ReceivedBytesThreshold = 1;
|
||||||
|
_serialPort.DataReceived += PhysicalDataReceived;
|
||||||
|
|
||||||
|
//start reading thread, will be immediately put to waitSleepJoin in ReadingThreadLoop
|
||||||
|
//to avoid side effects with RFID communication
|
||||||
|
|
||||||
|
ThreadWatcher.Instance.Start(_receiveThread);
|
||||||
|
|
||||||
|
if (!_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
_serialPort.Open();
|
||||||
|
if (!_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error($"{Ident} Port cannot be opened");
|
||||||
|
throw new ApplicationException($"{Ident} Port cannot be opened");
|
||||||
|
}
|
||||||
|
_byteDataLogger.Info($"{Ident} Port is opened");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_byteDataLogger.Warn($"{Ident} Port was already opened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
||||||
|
throw new ApplicationException($"{Ident} {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Getting the base port
|
||||||
|
/// </summary>
|
||||||
|
protected SerialPort GetBaseComport()
|
||||||
|
{
|
||||||
|
return _serialPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill FIFO with received data
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
|
||||||
|
/// - Initial
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Feb-21" author="T.Wiedebusch">
|
||||||
|
/// - Time stamp added
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
|
||||||
|
/// - Timeout moved from <see cref="ReadingThreadLoop"/> to PhysicalDataReceived to avoid
|
||||||
|
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
|
||||||
|
/// data.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
|
||||||
|
/// - Inter byte read delay used instead of response delay!!!
|
||||||
|
/// </remarks>
|
||||||
|
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
public virtual void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_receiveToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
//remove event delegate to avoid repeated execution during one record
|
||||||
|
_serialPort.DataReceived -= PhysicalDataReceived;
|
||||||
|
|
||||||
|
//put the actual time stamp to this record being able to assign it correctly
|
||||||
|
//even if the decoding is delayed. This time stamp will be used to do the first
|
||||||
|
//synchronization at start and stop of the measurement. Therefore the serial buffer
|
||||||
|
//has to be flushed in advance to avoid wrong time stamp to "old" records
|
||||||
|
_receiveTimeStampPc = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
//start timeout for hanging read communication called inter byte delay
|
||||||
|
_serialPort.ReadTimeout = InterByteReadDelayMs;
|
||||||
|
// _serialPort.ReadTimeout = PortSettingsForTransmitProtocol.ResponseTimeoutMs;
|
||||||
|
|
||||||
|
//wake up the reading threat from JoinWaitSleep
|
||||||
|
_onSyncReceiveThread.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="SyncMarkRecord" />
|
||||||
|
/// </summary>
|
||||||
|
private SyncMarkRecord _syncMarkRecord;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
//not in test bench situation only for temp logging
|
||||||
|
if (syncMarkRecord == SyncMarkRecord.FlushBuffer)
|
||||||
|
{
|
||||||
|
FlushBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//DecodeEveryPackage is ongoing and SkipDecoding is requested = do nothing
|
||||||
|
if (_syncMarkRecord == SyncMarkRecord.DecodeEveryPackage &&
|
||||||
|
syncMarkRecord == SyncMarkRecord.SkipDecoding)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//deny intermediate record when a start or end record is requested
|
||||||
|
if (syncMarkRecord == SyncMarkRecord.DecodeIntermediate &&
|
||||||
|
(_syncMarkRecord == SyncMarkRecord.SyncEnd ||
|
||||||
|
_syncMarkRecord == SyncMarkRecord.SyncStart))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//being able to detect the first incoming record after a sync is requested, the buffer is going to be flushed
|
||||||
|
//if the measurement will be started or stopped
|
||||||
|
if (syncMarkRecord == SyncMarkRecord.SyncEnd ||
|
||||||
|
syncMarkRecord == SyncMarkRecord.SyncStart ||
|
||||||
|
syncMarkRecord == SyncMarkRecord.DecodeIntermediate)
|
||||||
|
{
|
||||||
|
FlushBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
_byteDataLogger.Info($"{Ident} Mark next incoming record as {syncMarkRecord} (was {_syncMarkRecord})");
|
||||||
|
_syncMarkRecord = syncMarkRecord;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushBuffer()
|
||||||
|
{
|
||||||
|
//check if flushing is allowed
|
||||||
|
if (null != PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
|
||||||
|
{
|
||||||
|
//flush buffer above threshold to get an accurate actual record with next record coming in
|
||||||
|
if (_serialPort.IsOpen && _serialPort.BytesToRead > PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Warn($"{Ident} Flushed receive buffer({_serialPort.BytesToRead}Byte)");
|
||||||
|
_serialPort.DiscardInBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reading thread loop getting data from serial port
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
|
||||||
|
/// - Initial
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Mar-08" author="T.Wiedebusch">
|
||||||
|
/// - Reading will be executed and repeated until buffer is empty or timeout
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Mar-09" author="T.Wiedebusch">
|
||||||
|
/// - Receive byte protocol more dynamically on position of length information in record
|
||||||
|
/// and additional length,
|
||||||
|
/// - decoding of data controlled by dataSyncMarker!= SyncMarkRecord.SkipDecoding to speed up recording
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
|
||||||
|
/// - Directly invoked onRawRecord Received event,
|
||||||
|
/// - Doubling of sync byte implemented
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
|
||||||
|
/// - Timeout moved from ReadingThreadLoop to <see cref="PhysicalDataReceived"/> to avoid
|
||||||
|
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
|
||||||
|
/// data and flush receive buffer at timeout to force task to enter JoinWaitSleep state.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
|
||||||
|
/// - Hide data in logging for e.g. passwords
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-03" author="T.Wiedebusch">
|
||||||
|
/// - Raw record recording for missing sync-byte issues.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-04" author="T.Wiedebusch">
|
||||||
|
/// - Try to recover raw record on missing SYNC byte by adding the SYNC upfront and sending
|
||||||
|
/// the record to the decoding thread which will detect if just the SYNC byte had been
|
||||||
|
/// missed, then the CRC will match and the record can be decoded.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-05" author="T.Wiedebusch">
|
||||||
|
/// - Improved recovering of data sets at byte records with preceding SYNC byte:
|
||||||
|
/// - used payload length == 0 to skip decoding, it makes no sense to decode something where
|
||||||
|
/// the payload is empty,
|
||||||
|
/// - wait a certain time to complete incoming data.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
|
||||||
|
/// - Inter byte read delay used instead of response delay.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2019-Dec-14" author="T.Wiedebusch">
|
||||||
|
/// - Flush buffer if received length is 0.
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2022-Jul-15" author="T.Wiedebusch">
|
||||||
|
/// - Flush buffer (DiscardInBuffer) removed as some bytes are missing from time to time.
|
||||||
|
/// The IrDA sniffer showed these on the communication line, but they are incompletely received.
|
||||||
|
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
|
||||||
|
/// </remarks>
|
||||||
|
private void ReadingThreadLoop()
|
||||||
|
{
|
||||||
|
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
|
||||||
|
//RFID communication, because RFID will handle the physical receive by itself
|
||||||
|
//here the assignment of the _synReadingThreadEvent to the reading thread is being done
|
||||||
|
_onSyncReceiveThread.WaitOne();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!_receiveToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//remind data marker for this Thread execution time slice
|
||||||
|
var dataSyncMarkThisRun = _syncMarkRecord;
|
||||||
|
|
||||||
|
//read line of ASCII indicated by syncByte == null
|
||||||
|
if (null == PortSettingsForTransmitProtocol.ProtSyncByte)
|
||||||
|
{
|
||||||
|
//exit is timeout from serialPort or received line
|
||||||
|
var rxStringRecord = _serialPort.ReadLine();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(rxStringRecord))
|
||||||
|
{
|
||||||
|
//decode optional at synchronized data SyncStart, SyncEnd or DecodeIntermediate
|
||||||
|
//todo remove after air problem
|
||||||
|
if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun)
|
||||||
|
{
|
||||||
|
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
|
||||||
|
_receiveTimeStampPc, dataSyncMarkThisRun));
|
||||||
|
}
|
||||||
|
if (RecordStreamingRawData)
|
||||||
|
{
|
||||||
|
AsciiDataLogger.Trace($"{rxStringRecord}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//byte record with preceding SYNC byte
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//normally decoding is required
|
||||||
|
dataSyncMarkThisRun = SyncMarkRecord.DecodeEveryPackage;
|
||||||
|
|
||||||
|
//this is the record for decoding
|
||||||
|
var rxByteRecord = new List<Byte>();
|
||||||
|
|
||||||
|
//this is the informational record for logging on undetected SYNC byte
|
||||||
|
var rxRawRecord = new List<Byte>();
|
||||||
|
|
||||||
|
//each byte protocol has to start with a syncByte, this has to be detected first
|
||||||
|
Byte rxByte;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
rxByte = (Byte)_serialPort.ReadByte();
|
||||||
|
//record raw data stream for output on missing sync-byte to investigate this issue
|
||||||
|
rxRawRecord.Add(rxByte);
|
||||||
|
} while (_serialPort.BytesToRead > 0 && PortSettingsForTransmitProtocol.ProtSyncByte != rxByte);
|
||||||
|
|
||||||
|
// if syncByte has not been detected skip read loop and wait for next incoming record
|
||||||
|
if (PortSettingsForTransmitProtocol.ProtSyncByte == rxByte)
|
||||||
|
{
|
||||||
|
//save received SYNC byte
|
||||||
|
rxByteRecord.Add(rxByte);
|
||||||
|
|
||||||
|
//to assemble the length it has to be extracted first from the record at given index,
|
||||||
|
//the payload length index cannot be 0 because at this position is always the SYNC byte,
|
||||||
|
//the overall length includes the SYNC byte!
|
||||||
|
var length = PortSettingsForTransmitProtocol.ProtLengthIndex + 1;
|
||||||
|
|
||||||
|
//if the protLengthIndex is null the protAddLength equals the entire record length
|
||||||
|
if (null == PortSettingsForTransmitProtocol.ProtLengthIndex)
|
||||||
|
{
|
||||||
|
length = PortSettingsForTransmitProtocol.ProtAddLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
//read until length index to extract the length from the record
|
||||||
|
//the length index cannot be at position 0, because this is always the syncByte
|
||||||
|
do
|
||||||
|
{
|
||||||
|
rxByte = (Byte)_serialPort.ReadByte();
|
||||||
|
//read the next byte if doubled sync byte detected and required by transmit protocol settings
|
||||||
|
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
||||||
|
{
|
||||||
|
if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) &&
|
||||||
|
rxByteRecord[rxByteRecord.Count - 1].Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
||||||
|
{
|
||||||
|
//skips this byte and read the next one
|
||||||
|
rxByte = (Byte)_serialPort.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//add byte to receive result buffer
|
||||||
|
rxByteRecord.Add(rxByte);
|
||||||
|
|
||||||
|
//capture the length at given index and readjust the record length
|
||||||
|
if (null != PortSettingsForTransmitProtocol.ProtLengthIndex
|
||||||
|
&& rxByteRecord.Count - 1 == PortSettingsForTransmitProtocol.ProtLengthIndex)
|
||||||
|
{
|
||||||
|
//if the received length indicates empty payload, then nothing is to decode
|
||||||
|
if (rxByte == 0)
|
||||||
|
{
|
||||||
|
//exit this loop
|
||||||
|
dataSyncMarkThisRun = SyncMarkRecord.SkipDecoding;
|
||||||
|
FlushBuffer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//build the new length to continue this receive loop
|
||||||
|
length = (UInt16)(PortSettingsForTransmitProtocol.ProtAddLength + rxByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//wait a certain time to let the data stream coming in
|
||||||
|
if (length - rxByteRecord.Count - 1 > _serialPort.BytesToRead)
|
||||||
|
{
|
||||||
|
Thread.Sleep(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (rxByteRecord.Count < length && dataSyncMarkThisRun != SyncMarkRecord.SkipDecoding);
|
||||||
|
|
||||||
|
//all data received or skip decoding marked, skip decoding will clear the response timeout
|
||||||
|
//these protocols have always to be decoded because the base is a request protocol
|
||||||
|
OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(rxByteRecord,
|
||||||
|
_receiveTimeStampPc, dataSyncMarkThisRun));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//output actual byte and recorded raw data stream to investigate missed sync-byte
|
||||||
|
_byteDataLogger.Warn($"{Ident} Response SYNC Byte missing. Raw Byte Received: " +
|
||||||
|
$"{BitConverter.ToString(rxRawRecord.ToArray())}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{ throw; }
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
//ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming
|
||||||
|
//bytes and therefore destroys the already started data stream. Here it had been left in to indicate
|
||||||
|
//this critical issue!
|
||||||
|
/*-------------------------------DO NOT ACTIVATE-----------------------------------------------------*/
|
||||||
|
//flush receive buffer at timeout to force task to enter JoinWaitSleep state in finally
|
||||||
|
//if (_serialPort.IsOpen)
|
||||||
|
//{
|
||||||
|
// _serialPort.DiscardInBuffer();
|
||||||
|
//}
|
||||||
|
/*---------------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
_byteDataLogger.Trace($"{Ident} Read timeout({InterByteReadDelayMs}ms)");
|
||||||
|
//_byteDataLogger.Trace($"{Ident} Read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (_receiveThread.ThreadState == ThreadState.Aborted
|
||||||
|
|| _receiveThread.ThreadState == ThreadState.AbortRequested)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Warn(ex, $"{ Ident} ThreadState is Aborted or AbortRequested but an " +
|
||||||
|
"error occurred while reading records from serial port");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error(ex, $"{ Ident} Error while reading records from serial port");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_serialPort.IsOpen && !_receiveToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
//it makes no sense to put the thread to sleep if there is something to read
|
||||||
|
if (0 == _serialPort.BytesToRead)
|
||||||
|
{
|
||||||
|
//restore event delegate to activate handle for incoming records
|
||||||
|
_serialPort.DataReceived += PhysicalDataReceived;
|
||||||
|
|
||||||
|
//put this thread (receive thread) to JainWaitSleep
|
||||||
|
_onSyncReceiveThread.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//while (!_tokenReadData.IsCancellationRequested)
|
||||||
|
}// over hole reading thread loop
|
||||||
|
catch (ThreadAbortException)
|
||||||
|
{ }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
_serialPort.Close();
|
||||||
|
if (_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Info($"{Ident} Port close delay!");
|
||||||
|
Thread.Sleep(50);
|
||||||
|
_serialPort.Close();
|
||||||
|
}
|
||||||
|
_serialPort.Dispose();
|
||||||
|
_byteDataLogger.Info($"{Ident} Port is closed");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Boolean IsOpen()
|
||||||
|
{
|
||||||
|
return _serialPort != null && _serialPort.IsOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serial port specific write routine
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="txBuffer"></param>
|
||||||
|
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
|
||||||
|
/// - Hide data in logging for e.g. passwords
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual void SpecificPortWrite(Byte[] txBuffer)
|
||||||
|
{
|
||||||
|
PhysicalWrite(txBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Physically sending to the serial port
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks date="2017" author="R.Drabesch">
|
||||||
|
/// - Initial
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
|
||||||
|
/// - Doubling of sync byte in protocol behind sync byte itself implemented
|
||||||
|
/// </remarks>
|
||||||
|
/// <remarks date="2022-Feb-25" author="T.Wiedebusch, R.Drabesch">
|
||||||
|
/// - Flush buffer as MOXA sometimes takes two messages and combines these to one!
|
||||||
|
/// As the Genesis needs a separated wake-up message with a following delay before the
|
||||||
|
/// real payload message, this causes a lot of trouble.
|
||||||
|
/// </remarks>
|
||||||
|
protected void PhysicalWrite(Byte[] txBuffer)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//don't double the sync byte itself
|
||||||
|
var txByteList = new List<Byte> { txBuffer[0] };
|
||||||
|
for (var byteCtr = 1; byteCtr < txBuffer.Length; byteCtr++)
|
||||||
|
{
|
||||||
|
txByteList.Add(txBuffer[byteCtr]);
|
||||||
|
|
||||||
|
//write the doubled sync byte again if required by transmit protocol settings
|
||||||
|
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
||||||
|
{
|
||||||
|
if (txBuffer[byteCtr].Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
|
||||||
|
{
|
||||||
|
txByteList.Add(txBuffer[byteCtr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_serialPort.Write(txByteList.ToArray(), 0, txByteList.Count);
|
||||||
|
|
||||||
|
// flush the buffer for MOXA, to avoid two subsequent communications assembled to one communication!
|
||||||
|
_serialPort.BaseStream.Flush();
|
||||||
|
|
||||||
|
OnRawRecordSendOut?.Invoke(this, new ListBytePortDataEventArgs(txByteList, DateTimeOffset.UtcNow));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_byteDataLogger.Error($"{Ident} {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -857,11 +857,20 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
/// - CancellationToken.
|
/// - CancellationToken.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <remarks date="2025-Mai-07" author="Thomas Wiedebusch">
|
||||||
|
/// - Temporary excluded un-comparable registers from write as this make no sense to write them blind being
|
||||||
|
/// unable to read the correct value back.
|
||||||
|
/// </remarks>
|
||||||
private StatusReturn WriteRegisterParameterSet(List<RecoveryRegisterItem> recoveryRegistersDb, Int32 recRegistersCount,
|
private StatusReturn WriteRegisterParameterSet(List<RecoveryRegisterItem> recoveryRegistersDb, Int32 recRegistersCount,
|
||||||
MeterRegisters meterRegisters, CancellationToken cancellationToken)
|
MeterRegisters meterRegisters, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var recReg in recoveryRegistersDb)
|
foreach (var recReg in recoveryRegistersDb)
|
||||||
{
|
{
|
||||||
|
//TODO THW Check
|
||||||
|
// Temporary excluded un-comparable registers from write as this make no sense to write them blind being
|
||||||
|
// unable to read the correct value back.
|
||||||
|
if (_excludedFromCompareParameters.Any( regName => recReg.RegisterIdent.Equals(regName)))
|
||||||
|
continue;
|
||||||
_actualRegisterCtr++;
|
_actualRegisterCtr++;
|
||||||
var overallProcessCtrPercent = 100.0 * _actualRegisterCtr / recRegistersCount;
|
var overallProcessCtrPercent = 100.0 * _actualRegisterCtr / recRegistersCount;
|
||||||
|
|
||||||
|
|||||||
@@ -146,10 +146,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingAll = new List<RecoveryRegisterItem>
|
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingAll = new List<RecoveryRegisterItem>
|
||||||
{
|
{
|
||||||
|
// Writing 1 will force a log flush
|
||||||
new RecoveryRegisterItem("LOGGER_TriggerLogFlush", new Byte[] { 0x01 }),
|
new RecoveryRegisterItem("LOGGER_TriggerLogFlush", new Byte[] { 0x01 }),
|
||||||
// set counter to 0 as this is a value how often the radio detected a reset
|
// Trigger idle 0 releases the display to normal operation
|
||||||
new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }),
|
|
||||||
// trigger idle 0 releases the display to normal operation
|
|
||||||
new RecoveryRegisterItem("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 })
|
new RecoveryRegisterItem("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 })
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,12 +158,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingEmea = new List<RecoveryRegisterItem>
|
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingEmea = new List<RecoveryRegisterItem>
|
||||||
{
|
{
|
||||||
|
// Set counter to 0 as this is a value how often the radio detected a reset
|
||||||
|
new RecoveryRegisterItem("SENSUSRADIO_ResetCounter", new Byte[] { 0x00 }),
|
||||||
// System state 0xFF will force a storage of all parameters and reset, followed by system state 0x02
|
// System state 0xFF will force a storage of all parameters and reset, followed by system state 0x02
|
||||||
// for customer mode, the reset needs only a few milliseconds
|
// for customer mode, the reset needs only a few milliseconds
|
||||||
// Attention logout-delay-login required after this step!
|
// Attention logout-delay-login required after this step!
|
||||||
new RecoveryRegisterItem("SENSUSRADIO_SystemState", new Byte[] { 0xFF }),
|
new RecoveryRegisterItem("SENSUSRADIO_SystemState", new Byte[] { 0xFF }),
|
||||||
// seal the display to lock against changes
|
// Seal the display to lock against changes
|
||||||
new RecoveryRegisterItem("GENESISFLOW_SealDisplay", new Byte[] { 0x01 }),
|
new RecoveryRegisterItem("GENESISFLOW_SealDisplay", new Byte[] { 0x01 }),
|
||||||
|
// Set counter to 0 as this is a value how often the radio detected a reset
|
||||||
|
new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -172,8 +175,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingEol = new List<RecoveryRegisterItem>
|
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingEol = new List<RecoveryRegisterItem>
|
||||||
{
|
{
|
||||||
|
// Writing 1 to this register clears the volume accumulator
|
||||||
new RecoveryRegisterItem("GENESISFLOW_ResetAccumulators", new Byte[] { 0x01 }),
|
new RecoveryRegisterItem("GENESISFLOW_ResetAccumulators", new Byte[] { 0x01 }),
|
||||||
|
// Writing 1 to this register erases the RMA area
|
||||||
new RecoveryRegisterItem("NFC_EraseRMA", new Byte[] { 0x01 }),
|
new RecoveryRegisterItem("NFC_EraseRMA", new Byte[] { 0x01 }),
|
||||||
|
// Set counter to 0 as this is a value how often the customer app detected a reset
|
||||||
new RecoveryRegisterItem("CUSTOMER_RebootCount", new Byte[] { 0x00 })
|
new RecoveryRegisterItem("CUSTOMER_RebootCount", new Byte[] { 0x00 })
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -182,11 +188,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Int32 _actualRegisterCtr;
|
private Int32 _actualRegisterCtr;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop register access
|
|
||||||
/// </summary>
|
|
||||||
public Boolean StopRegisterAccess;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Genesis meter object
|
/// Genesis meter object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -573,6 +574,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
List<RecoveryRegisterItem> recoveryRegistersDb, out String errorMsg)
|
List<RecoveryRegisterItem> recoveryRegistersDb, out String errorMsg)
|
||||||
{
|
{
|
||||||
errorMsg = "";
|
errorMsg = "";
|
||||||
|
if (string.IsNullOrEmpty(pcbId))
|
||||||
|
return StatusReturn.Failed;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetProgrammingParameters?PcbID={pcbId}";
|
var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetProgrammingParameters?PcbID={pcbId}";
|
||||||
@@ -620,14 +623,18 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if setup succeeded</returns>
|
/// <returns>true if setup succeeded</returns>
|
||||||
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public StatusReturn EolProgrammingRegisters(List<RecoveryRegisterItem> recoveryRegistersDb)
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public StatusReturn EolProgrammingRegisters(List<RecoveryRegisterItem> recoveryRegistersDb, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// mark EOL as true to force accu reset
|
// mark EOL as true to force accu reset
|
||||||
return WriteRegisters(recoveryRegistersDb, true);
|
return WriteRegisters(recoveryRegistersDb, cancellationToken, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -668,6 +675,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <param name="eolProgramming">EOL (end of line) programming with accu reset</param>
|
/// <param name="eolProgramming">EOL (end of line) programming with accu reset</param>
|
||||||
/// <returns>true if restoring succeeded</returns>
|
/// <returns>true if restoring succeeded</returns>
|
||||||
/// <remarks date="2023-Oct-09" author="Thomas Wiedebusch">
|
/// <remarks date="2023-Oct-09" author="Thomas Wiedebusch">
|
||||||
@@ -722,7 +730,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2025-Feb-14" author="Thomas Wiedebusch">
|
/// <remarks date="2025-Feb-14" author="Thomas Wiedebusch">
|
||||||
/// - Exported write routines to <see cref="WriteRegisterParameterSet"/>.
|
/// - Exported write routines to <see cref="WriteRegisterParameterSet"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public StatusReturn WriteRegisters(List<RecoveryRegisterItem> recoveryRegistersDb, Boolean eolProgramming = false)
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public StatusReturn WriteRegisters(List<RecoveryRegisterItem> recoveryRegistersDb, CancellationToken cancellationToken,
|
||||||
|
Boolean eolProgramming = false)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null ||
|
if (_currentGenesis == null ||
|
||||||
string.IsNullOrEmpty(_currentGenesis.PcbId) ||
|
string.IsNullOrEmpty(_currentGenesis.PcbId) ||
|
||||||
@@ -740,7 +752,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
RecoveryRegisters = recoveryRegistersDb;
|
RecoveryRegisters = recoveryRegistersDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
StopRegisterAccess = false;
|
|
||||||
_actualRegisterCtr = 0;
|
_actualRegisterCtr = 0;
|
||||||
|
|
||||||
// Build string list for debug
|
// Build string list for debug
|
||||||
@@ -787,7 +798,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryPreparation,
|
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryPreparation,
|
||||||
actualProcessMessage: Resources.StrRegisterRecoveryPreparation));
|
actualProcessMessage: Resources.StrRegisterRecoveryPreparation));
|
||||||
|
|
||||||
if (StatusReturn.Failed == WriteRegisterParameterSet(_prepareProgramming, recRegistersCount, meterRegisters))
|
if (StatusReturn.Failed == WriteRegisterParameterSet(_prepareProgramming, recRegistersCount, meterRegisters,
|
||||||
|
cancellationToken))
|
||||||
{
|
{
|
||||||
// the logout will force the radio app to activate the settings
|
// the logout will force the radio app to activate the settings
|
||||||
_currentGenesis?.Logout();
|
_currentGenesis?.Logout();
|
||||||
@@ -798,7 +810,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryExec,
|
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryExec,
|
||||||
actualProcessMessage: Resources.StrRegisterRecoveryExec));
|
actualProcessMessage: Resources.StrRegisterRecoveryExec));
|
||||||
|
|
||||||
if (StatusReturn.Failed == WriteRegisterParameterSet(recoveryRegistersDb, recRegistersCount, meterRegisters))
|
if (StatusReturn.Failed == WriteRegisterParameterSet(recoveryRegistersDb, recRegistersCount, meterRegisters,
|
||||||
|
cancellationToken))
|
||||||
{
|
{
|
||||||
// the logout will force the radio app to activate the settings
|
// the logout will force the radio app to activate the settings
|
||||||
_currentGenesis?.Logout();
|
_currentGenesis?.Logout();
|
||||||
@@ -809,7 +822,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryFinalization,
|
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(Resources.StrRegisterRecoveryFinalization,
|
||||||
actualProcessMessage: Resources.StrRegisterRecoveryFinalization));
|
actualProcessMessage: Resources.StrRegisterRecoveryFinalization));
|
||||||
|
|
||||||
if (StatusReturn.Failed == WriteRegisterParameterSet(finalizeProgrammingAll, recRegistersCount, meterRegisters))
|
if (StatusReturn.Failed == WriteRegisterParameterSet(finalizeProgrammingAll, recRegistersCount, meterRegisters,
|
||||||
|
cancellationToken))
|
||||||
{
|
{
|
||||||
// the logout will force the radio app to activate the settings
|
// the logout will force the radio app to activate the settings
|
||||||
_currentGenesis?.Logout();
|
_currentGenesis?.Logout();
|
||||||
@@ -835,12 +849,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <param name="recoveryRegistersDb"></param>
|
/// <param name="recoveryRegistersDb"></param>
|
||||||
/// <param name="recRegistersCount"></param>
|
/// <param name="recRegistersCount"></param>
|
||||||
/// <param name="meterRegisters"></param>
|
/// <param name="meterRegisters"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <remarks date="2025-Feb-14" author="Thomas Wiedebusch">
|
/// <remarks date="2025-Feb-14" author="Thomas Wiedebusch">
|
||||||
/// - Initial imported from <see cref="WriteRegisters"/>.
|
/// - Initial imported from <see cref="WriteRegisters"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
private StatusReturn WriteRegisterParameterSet(List<RecoveryRegisterItem> recoveryRegistersDb, Int32 recRegistersCount,
|
private StatusReturn WriteRegisterParameterSet(List<RecoveryRegisterItem> recoveryRegistersDb, Int32 recRegistersCount,
|
||||||
MeterRegisters meterRegisters)
|
MeterRegisters meterRegisters, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var recReg in recoveryRegistersDb)
|
foreach (var recReg in recoveryRegistersDb)
|
||||||
{
|
{
|
||||||
@@ -913,7 +931,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
overallProcessCtrPercent, actualProcessMsg));
|
overallProcessCtrPercent, actualProcessMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StopRegisterAccess)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,6 +944,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// - The _restoreRegisters has to be filled with all registers being able to restore (RW),
|
/// - The _restoreRegisters has to be filled with all registers being able to restore (RW),
|
||||||
/// - The _finalReadRegisters has to be filled.
|
/// - The _finalReadRegisters has to be filled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if equal</returns>
|
/// <returns>true if equal</returns>
|
||||||
/// <remarks date="2023-Nov-20" author="Thomas Wiedebusch">
|
/// <remarks date="2023-Nov-20" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
@@ -963,13 +982,15 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2024-Mar-20" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Mar-20" author="Thomas Wiedebusch">
|
||||||
/// - Hash password and encryption key with SHA256.
|
/// - Hash password and encryption key with SHA256.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean CompareRegisters()
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean CompareRegisters(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_pcbIdFinalRegisterRead != _pcbIdWriteRegisters ||
|
if (_pcbIdFinalRegisterRead != _pcbIdWriteRegisters ||
|
||||||
RecoveryRegisters == null)
|
RecoveryRegisters == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
StopRegisterAccess = false;
|
|
||||||
_actualRegisterCtr = 0;
|
_actualRegisterCtr = 0;
|
||||||
var retVal = true;
|
var retVal = true;
|
||||||
|
|
||||||
@@ -1130,7 +1151,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StopRegisterAccess)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1145,6 +1166,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// Read the registers after the update for comparision. Build the post update registers here,
|
/// Read the registers after the update for comparision. Build the post update registers here,
|
||||||
/// because an update may have changed the registers (new or removed registers).
|
/// because an update may have changed the registers (new or removed registers).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if registers could be read</returns>
|
/// <returns>true if registers could be read</returns>
|
||||||
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
@@ -1174,7 +1196,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2024-Dec-12" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Dec-12" author="Thomas Wiedebusch">
|
||||||
/// - Check readBack value for null.
|
/// - Check readBack value for null.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean FinalReadRegisters()
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean FinalReadRegisters(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null)
|
if (_currentGenesis == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -1197,7 +1222,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
_finalRegisterList.Sort();
|
_finalRegisterList.Sort();
|
||||||
|
|
||||||
// executes login read logout
|
// executes login read logout
|
||||||
retVal = ReadRegistersSet(ref _finalRegisters);
|
retVal = ReadRegistersSet(ref _finalRegisters, cancellationToken);
|
||||||
|
|
||||||
// backup read data to RecoveryRegisters
|
// backup read data to RecoveryRegisters
|
||||||
if (RecoveryRegisters != null && _finalRegisters != null)
|
if (RecoveryRegisters != null && _finalRegisters != null)
|
||||||
@@ -1239,6 +1264,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// The defined registers are based on the "configuration.json" and the installed application with
|
/// The defined registers are based on the "configuration.json" and the installed application with
|
||||||
/// a specific version.
|
/// a specific version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if registers could be read</returns>
|
/// <returns>true if registers could be read</returns>
|
||||||
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
@@ -1263,7 +1289,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean InitialReadRegisters()
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean InitialReadRegisters(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null)
|
if (_currentGenesis == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -1289,7 +1318,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
_initialRegisterList = _initialRegisters.Select(register => register.RegisterIdent).ToList();
|
_initialRegisterList = _initialRegisters.Select(register => register.RegisterIdent).ToList();
|
||||||
_initialRegisterList.Sort();
|
_initialRegisterList.Sort();
|
||||||
|
|
||||||
return ReadRegistersSet(ref _initialRegisters);
|
return ReadRegistersSet(ref _initialRegisters, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1344,6 +1373,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// The defined registers are based on the "configuration.json" and the installed application with a specific
|
/// The defined registers are based on the "configuration.json" and the installed application with a specific
|
||||||
/// version.
|
/// version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if registers could be read</returns>
|
/// <returns>true if registers could be read</returns>
|
||||||
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
|
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
@@ -1351,7 +1381,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean ReadRegisters()
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean ReadRegisters(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null)
|
if (_currentGenesis == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -1366,17 +1399,21 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
|
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
|
||||||
Resources.StrReadRegistersBeforeUpdate));
|
Resources.StrReadRegistersBeforeUpdate));
|
||||||
|
|
||||||
return ReadRegistersSet(ref registers);
|
return ReadRegistersSet(ref registers, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read predefined registers from RecoveryRegisters.
|
/// Read predefined registers from RecoveryRegisters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if registers could be read</returns>
|
/// <returns>true if registers could be read</returns>
|
||||||
/// <remarks date="2024-Sep-25" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Sep-25" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public Boolean ReadPresetRegisters()
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
public Boolean ReadPresetRegisters(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null || RecoveryRegisters == null || RecoveryRegisters.Count == 0)
|
if (_currentGenesis == null || RecoveryRegisters == null || RecoveryRegisters.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -1387,7 +1424,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
_pcbIdWriteRegisters = _currentGenesis.PcbId;
|
_pcbIdWriteRegisters = _currentGenesis.PcbId;
|
||||||
_pcbIdFinalRegisterRead = _currentGenesis.PcbId;
|
_pcbIdFinalRegisterRead = _currentGenesis.PcbId;
|
||||||
|
|
||||||
return ReadRegistersSet(ref RecoveryRegisters);
|
return ReadRegistersSet(ref RecoveryRegisters, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1399,6 +1436,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// Executes login, read cycle and logout.
|
/// Executes login, read cycle and logout.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="registers">registers to read</param>
|
/// <param name="registers">registers to read</param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns>true if registers could be read</returns>
|
/// <returns>true if registers could be read</returns>
|
||||||
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
|
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
|
||||||
/// - Initial.
|
/// - Initial.
|
||||||
@@ -1428,11 +1466,13 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private Boolean ReadRegistersSet(ref List<RecoveryRegisterItem> registers)
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
|
/// - CancellationToken.
|
||||||
|
/// </remarks>
|
||||||
|
private Boolean ReadRegistersSet(ref List<RecoveryRegisterItem> registers, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_currentGenesis == null)
|
if (_currentGenesis == null)
|
||||||
return false;
|
return false;
|
||||||
StopRegisterAccess = false;
|
|
||||||
_actualRegisterCtr = 0;
|
_actualRegisterCtr = 0;
|
||||||
|
|
||||||
// read values from Genesis
|
// read values from Genesis
|
||||||
@@ -1501,7 +1541,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
|||||||
overallProcessCtrPercent, actualProcessMsg));
|
overallProcessCtrPercent, actualProcessMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StopRegisterAccess)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
8cb55c312de586b683d72cf58c13e2801d10e14b767777d98338d3e1473a1213
|
7df2ad6be70922b931820861acc675522243ee78
|
||||||
|
|||||||
@@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="Mode" value="2" /> <!--1 = EOL ; 2 = Picking-->
|
<add key="Mode" value="1" /> <!--1 = EOL ; 2 = Picking-->
|
||||||
<add key="SirtComport433" value="14" /> <!--Individual for Thomas-->
|
<add key="SirtComport433" value="14" /> <!--Individual for Thomas-->
|
||||||
<add key="SirtComport868" value="15" /> <!--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="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="StationID" value="1" /> <!--Roland and Thomas, shipping station is 1-->
|
||||||
|
|
||||||
</appSettings>
|
</appSettings>
|
||||||
|
|
||||||
|
|||||||
+5
-13
@@ -176,6 +176,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr
|
|||||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||||
/// - CancellationToken.
|
/// - CancellationToken.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <remarks date="2025-Mai-07" author="Thomas Wiedebusch">
|
||||||
|
/// - Removed change and read back of MainAlarmMask and ExtendedAlarmMask.
|
||||||
|
/// </remarks>
|
||||||
public override StatusReturn ExecuteProcess()
|
public override StatusReturn ExecuteProcess()
|
||||||
{
|
{
|
||||||
// This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress
|
// 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();
|
DisableAllButtons();
|
||||||
}// !skipRadioCheck
|
}// !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
|
// set system state back to shipping mode
|
||||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
|
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
|
||||||
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
|
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
|
||||||
ActualProcessProgress++;
|
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"));
|
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_SystemState"));
|
||||||
ActualProcessProgress++;
|
ActualProcessProgress++;
|
||||||
|
|
||||||
if (// Radio mode and alarm masks NOT okay forces always an error
|
if (// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
||||||
// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
_finalRadioSystemState != 0x02
|
||||||
_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
|
// If radio check is enabled the signal level has to be in range and the test has to be finished
|
||||||
|| (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished)))
|
|| (!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">
|
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||||
/// - Handle genesis meter is null as new meter.
|
/// - Handle genesis meter is null as new meter.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
|
||||||
|
/// - Check _genesisMeter not null before equality with old pcbid.
|
||||||
|
/// </remarks>
|
||||||
private Boolean CheckForNewMeter()
|
private Boolean CheckForNewMeter()
|
||||||
{
|
{
|
||||||
if (_genesisMeter == null ||
|
if (_genesisMeter == null ||
|
||||||
(!string.IsNullOrEmpty(_lastPcbId) &&
|
(!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
|
// This loop will be entered after the meter has changed, but not on the initial meter
|
||||||
// after program start.
|
// 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
|
// remove last userControl
|
||||||
dpSubProcess.Children.Clear();
|
dpSubProcess.Children.Clear();
|
||||||
rtbLog.Document.Blocks.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);
|
_processCtrl?.SetProcessState(ProcessState.DetectCordonel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +895,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel
|
|||||||
// Returns the compressed file for error report printout
|
// Returns the compressed file for error report printout
|
||||||
File.WriteAllText(fileName, compressedSb.ToString());
|
File.WriteAllText(fileName, compressedSb.ToString());
|
||||||
if (printReport)
|
if (printReport)
|
||||||
|
{
|
||||||
Process.Start(fileName);
|
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