ProductionUiCordonel: - manually merged some modules from main EOL_2.6.20
This commit is contained in:
@@ -155,7 +155,7 @@
|
||||
</site>
|
||||
<site name="MeterProcessState" id="2">
|
||||
<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>
|
||||
<bindings>
|
||||
<binding protocol="http" bindingInformation="*:56011:localhost" />
|
||||
|
||||
@@ -193,6 +193,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
/// <summary>
|
||||
/// Dispose serial port
|
||||
/// </summary>
|
||||
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
||||
/// - Dispose procedure changed.
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
@@ -207,6 +210,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
//Run thread again to notice CancellationToken has changed
|
||||
_onSyncReceiveThread.Set();
|
||||
|
||||
// dispose directly called from here to overcome glitches caused by USB to serial interface
|
||||
_serialPort.Dispose();
|
||||
|
||||
}
|
||||
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.
|
||||
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
|
||||
/// - Dispose procedure changed.
|
||||
/// </remarks>
|
||||
private void ReadingThreadLoop()
|
||||
{
|
||||
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
|
||||
@@ -450,6 +459,7 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
|
||||
_receiveTimeStampPc, dataSyncMarkThisRun));
|
||||
}
|
||||
|
||||
if (RecordStreamingRawData)
|
||||
{
|
||||
AsciiDataLogger.Trace($"{rxStringRecord}");
|
||||
@@ -503,12 +513,14 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
|
||||
{
|
||||
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
|
||||
rxByte = (Byte)_serialPort.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
//add byte to receive result buffer
|
||||
rxByteRecord.Add(rxByte);
|
||||
|
||||
@@ -552,7 +564,9 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{ throw; }
|
||||
{
|
||||
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
//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
|
||||
|| _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");
|
||||
}
|
||||
else
|
||||
{
|
||||
_byteDataLogger.Error(ex, $"{ Ident} Error while reading records from serial port");
|
||||
_byteDataLogger.Error(ex, $"{Ident} Error while reading records from serial port");
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -598,31 +612,33 @@ namespace Xylem.Common.Hardware.Interfaces.Ports.SerialPorts
|
||||
}
|
||||
}
|
||||
}
|
||||
}//while (!_tokenReadData.IsCancellationRequested)
|
||||
}// over hole reading thread loop
|
||||
} //while (!_tokenReadData.IsCancellationRequested)
|
||||
} // over hole reading thread loop
|
||||
catch (ThreadAbortException)
|
||||
{ }
|
||||
{
|
||||
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
|
||||
}
|
||||
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");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
//finally
|
||||
//{
|
||||
// //var retries = 5;
|
||||
// //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0)
|
||||
// //{
|
||||
// // _serialPort.Close();
|
||||
// // if (_serialPort.IsOpen)
|
||||
// // {
|
||||
// // _byteDataLogger.Info($"{Ident} Port closing delay!");
|
||||
// // Thread.Sleep(500);
|
||||
// // }
|
||||
// //}
|
||||
// //if (_serialPort != null && _serialPort.IsOpen)
|
||||
// // _byteDataLogger.Info($"{Ident} Port unable to close");
|
||||
// //else
|
||||
// // _byteDataLogger.Info($"{Ident} Port is closed");
|
||||
//}
|
||||
}
|
||||
|
||||
/// <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">
|
||||
/// - CancellationToken.
|
||||
/// </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,
|
||||
MeterRegisters meterRegisters, CancellationToken cancellationToken)
|
||||
{
|
||||
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++;
|
||||
var overallProcessCtrPercent = 100.0 * _actualRegisterCtr / recRegistersCount;
|
||||
|
||||
|
||||
@@ -146,10 +146,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
private static readonly List<RecoveryRegisterItem> _finalizeProgrammingAll = new List<RecoveryRegisterItem>
|
||||
{
|
||||
// Writing 1 will force a log flush
|
||||
new RecoveryRegisterItem("LOGGER_TriggerLogFlush", 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 }),
|
||||
// trigger idle 0 releases the display to normal operation
|
||||
// Trigger idle 0 releases the display to normal operation
|
||||
new RecoveryRegisterItem("GENESISFLOW_TriggerIdle", new Byte[] { 0x00 })
|
||||
};
|
||||
|
||||
@@ -159,12 +158,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
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
|
||||
// for customer mode, the reset needs only a few milliseconds
|
||||
// Attention logout-delay-login required after this step!
|
||||
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 }),
|
||||
// Set counter to 0 as this is a value how often the radio detected a reset
|
||||
new RecoveryRegisterItem("PERIODICLOG_ResetCounter", new Byte[] { 0x00 }),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -172,8 +175,11 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
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 }),
|
||||
// Writing 1 to this register erases the RMA area
|
||||
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 })
|
||||
};
|
||||
|
||||
@@ -182,11 +188,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
private Int32 _actualRegisterCtr;
|
||||
|
||||
/// <summary>
|
||||
/// Stop register access
|
||||
/// </summary>
|
||||
public Boolean StopRegisterAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Genesis meter object
|
||||
/// </summary>
|
||||
@@ -573,6 +574,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
List<RecoveryRegisterItem> recoveryRegistersDb, out String errorMsg)
|
||||
{
|
||||
errorMsg = "";
|
||||
if (string.IsNullOrEmpty(pcbId))
|
||||
return StatusReturn.Failed;
|
||||
try
|
||||
{
|
||||
var url = $"{ServiceUrls.GenesisFinalCheckServiceUrl()}GetProgrammingParameters?PcbID={pcbId}";
|
||||
@@ -620,14 +623,18 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
||||
/// </param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if setup succeeded</returns>
|
||||
/// <remarks date="2024-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </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
|
||||
return WriteRegisters(recoveryRegistersDb, true);
|
||||
return WriteRegisters(recoveryRegistersDb, cancellationToken, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -668,6 +675,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// </summary>
|
||||
/// <param name="recoveryRegistersDb">list of registers to update from database without special sequence
|
||||
/// </param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <param name="eolProgramming">EOL (end of line) programming with accu reset</param>
|
||||
/// <returns>true if restoring succeeded</returns>
|
||||
/// <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">
|
||||
/// - Exported write routines to <see cref="WriteRegisterParameterSet"/>.
|
||||
/// </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 ||
|
||||
string.IsNullOrEmpty(_currentGenesis.PcbId) ||
|
||||
@@ -740,7 +752,6 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
RecoveryRegisters = recoveryRegistersDb;
|
||||
}
|
||||
|
||||
StopRegisterAccess = false;
|
||||
_actualRegisterCtr = 0;
|
||||
|
||||
// Build string list for debug
|
||||
@@ -787,7 +798,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(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
|
||||
_currentGenesis?.Logout();
|
||||
@@ -798,7 +810,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(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
|
||||
_currentGenesis?.Logout();
|
||||
@@ -809,7 +822,8 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(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
|
||||
_currentGenesis?.Logout();
|
||||
@@ -835,12 +849,16 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <param name="recoveryRegistersDb"></param>
|
||||
/// <param name="recRegistersCount"></param>
|
||||
/// <param name="meterRegisters"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2025-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial imported from <see cref="WriteRegisters"/>.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
private StatusReturn WriteRegisterParameterSet(List<RecoveryRegisterItem> recoveryRegistersDb, Int32 recRegistersCount,
|
||||
MeterRegisters meterRegisters)
|
||||
MeterRegisters meterRegisters, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var recReg in recoveryRegistersDb)
|
||||
{
|
||||
@@ -913,7 +931,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
overallProcessCtrPercent, actualProcessMsg));
|
||||
}
|
||||
|
||||
if (StopRegisterAccess)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
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 _finalReadRegisters has to be filled.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if equal</returns>
|
||||
/// <remarks date="2023-Nov-20" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
@@ -963,13 +982,15 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <remarks date="2024-Mar-20" author="Thomas Wiedebusch">
|
||||
/// - Hash password and encryption key with SHA256.
|
||||
/// </remarks>
|
||||
public Boolean CompareRegisters()
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
public Boolean CompareRegisters(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_pcbIdFinalRegisterRead != _pcbIdWriteRegisters ||
|
||||
RecoveryRegisters == null)
|
||||
return false;
|
||||
|
||||
StopRegisterAccess = false;
|
||||
_actualRegisterCtr = 0;
|
||||
var retVal = true;
|
||||
|
||||
@@ -1130,7 +1151,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
}
|
||||
}
|
||||
|
||||
if (StopRegisterAccess)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
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,
|
||||
/// because an update may have changed the registers (new or removed registers).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if registers could be read</returns>
|
||||
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
@@ -1174,7 +1196,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <remarks date="2024-Dec-12" author="Thomas Wiedebusch">
|
||||
/// - Check readBack value for null.
|
||||
/// </remarks>
|
||||
public Boolean FinalReadRegisters()
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
public Boolean FinalReadRegisters(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_currentGenesis == null)
|
||||
return false;
|
||||
@@ -1197,7 +1222,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
_finalRegisterList.Sort();
|
||||
|
||||
// executes login read logout
|
||||
retVal = ReadRegistersSet(ref _finalRegisters);
|
||||
retVal = ReadRegistersSet(ref _finalRegisters, cancellationToken);
|
||||
|
||||
// backup read data to RecoveryRegisters
|
||||
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
|
||||
/// a specific version.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if registers could be read</returns>
|
||||
/// <remarks date="2020-Dec-08" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
@@ -1263,7 +1289,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||
/// </remarks>
|
||||
public Boolean InitialReadRegisters()
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
public Boolean InitialReadRegisters(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_currentGenesis == null)
|
||||
return false;
|
||||
@@ -1289,7 +1318,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
_initialRegisterList = _initialRegisters.Select(register => register.RegisterIdent).ToList();
|
||||
_initialRegisterList.Sort();
|
||||
|
||||
return ReadRegistersSet(ref _initialRegisters);
|
||||
return ReadRegistersSet(ref _initialRegisters, cancellationToken);
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// version.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if registers could be read</returns>
|
||||
/// <remarks date="2023-Oct-16" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
@@ -1351,7 +1381,10 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||
/// </remarks>
|
||||
public Boolean ReadRegisters()
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
public Boolean ReadRegisters(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_currentGenesis == null)
|
||||
return false;
|
||||
@@ -1366,17 +1399,21 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs($@"{Resources.StrReadRegister}", 0,
|
||||
Resources.StrReadRegistersBeforeUpdate));
|
||||
|
||||
return ReadRegistersSet(ref registers);
|
||||
return ReadRegistersSet(ref registers, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read predefined registers from RecoveryRegisters.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if registers could be read</returns>
|
||||
/// <remarks date="2024-Sep-25" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </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)
|
||||
return false;
|
||||
@@ -1387,7 +1424,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
_pcbIdWriteRegisters = _currentGenesis.PcbId;
|
||||
_pcbIdFinalRegisterRead = _currentGenesis.PcbId;
|
||||
|
||||
return ReadRegistersSet(ref RecoveryRegisters);
|
||||
return ReadRegistersSet(ref RecoveryRegisters, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1399,6 +1436,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// Executes login, read cycle and logout.
|
||||
/// </summary>
|
||||
/// <param name="registers">registers to read</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>true if registers could be read</returns>
|
||||
/// <remarks date="2020-Dec-17" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
@@ -1428,11 +1466,13 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
/// <remarks date="2024-Feb-19" author="Thomas Wiedebusch">
|
||||
/// - Register list changed from RegisterDefinition to RecoveryRegisterItem list.
|
||||
/// </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)
|
||||
return false;
|
||||
StopRegisterAccess = false;
|
||||
_actualRegisterCtr = 0;
|
||||
|
||||
// read values from Genesis
|
||||
@@ -1501,7 +1541,7 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
|
||||
overallProcessCtrPercent, actualProcessMsg));
|
||||
}
|
||||
|
||||
if (StopRegisterAccess)
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
8cb55c312de586b683d72cf58c13e2801d10e14b767777d98338d3e1473a1213
|
||||
7df2ad6be70922b931820861acc675522243ee78
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
|
||||
|
||||
<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="SirtComport868" value="15" /> <!--Individual for Thomas-->
|
||||
<add key="SirtBoxNr" value="2" /> <!--Roland and Thomas, box for development, box at shipping station is 1-->
|
||||
<add key="StationID" value="2" /> <!--Roland and Thomas, shipping station is 1-->
|
||||
<add key="StationID" value="1" /> <!--Roland and Thomas, shipping station is 1-->
|
||||
|
||||
</appSettings>
|
||||
|
||||
|
||||
+5
-13
@@ -176,6 +176,9 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mai-07" author="Thomas Wiedebusch">
|
||||
/// - Removed change and read back of MainAlarmMask and ExtendedAlarmMask.
|
||||
/// </remarks>
|
||||
public override StatusReturn ExecuteProcess()
|
||||
{
|
||||
// This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress
|
||||
@@ -304,27 +307,16 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolPr
|
||||
DisableAllButtons();
|
||||
}// !skipRadioCheck
|
||||
|
||||
// reset all radio alarms
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_MainAlarmMask",
|
||||
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_ExtendedAlarmMask",
|
||||
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
||||
// set system state back to shipping mode
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
|
||||
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
|
||||
ActualProcessProgress++;
|
||||
|
||||
// check radio is in customer mode 0x02 and alarm masks are 0
|
||||
var mainAlarmMask =
|
||||
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask"));
|
||||
var extendedAlarmMask =
|
||||
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask"));
|
||||
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_SystemState"));
|
||||
ActualProcessProgress++;
|
||||
|
||||
if (// Radio mode and alarm masks NOT okay forces always an error
|
||||
// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
||||
_finalRadioSystemState != 0x02 || mainAlarmMask != 0 || extendedAlarmMask != 0
|
||||
if (// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
||||
_finalRadioSystemState != 0x02
|
||||
// If radio check is enabled the signal level has to be in range and the test has to be finished
|
||||
|| (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished)))
|
||||
{
|
||||
|
||||
+829
@@ -0,0 +1,829 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Windows.Threading;
|
||||
using LaaPackages.Features.Cordonel.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SIRTCOM;
|
||||
using Xylem.Common.CommonCore.Configuration;
|
||||
using Xylem.Common.CommonCore.Consts;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
||||
using Xylem.Common.Logic.ProductionOrderCore;
|
||||
using Xylem.Common.Logic.SoftwareAccessHelper;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.Properties;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.UserControls;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
|
||||
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EolProcesses
|
||||
{
|
||||
public class CheckRadio : BaseProductionProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum of expected processing steps for this process to feed the GUI progress bar.
|
||||
/// Use the sleep 250 to create x seconds timeout;
|
||||
/// </summary>
|
||||
private const Int32 TimeoutLoop_ms = 250;
|
||||
private const Int32 Timeout_s = 10;
|
||||
private const Int32 MaxActualProgressSteps = 1000 / TimeoutLoop_ms * Timeout_s;
|
||||
|
||||
/// <summary>
|
||||
/// Remind the ID of the radio result as feedback from the DB.
|
||||
/// </summary>
|
||||
private Int64 _radioResultCheckId;
|
||||
|
||||
/// <summary>
|
||||
/// Final state of radio system.
|
||||
/// </summary>
|
||||
private Int32 _finalRadioSystemState;
|
||||
/// <summary>
|
||||
/// Reminder for specific user control
|
||||
/// </summary>
|
||||
private UcRadioCheck _checkRadioUserControl;
|
||||
|
||||
/// <summary>
|
||||
/// Signal level successfully tested
|
||||
/// </summary>
|
||||
private Boolean _successSignalLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Radio encryption key converted to string
|
||||
/// </summary>
|
||||
private String _radioEncryptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// Test finished can be good or abort by done button to exit this production process
|
||||
/// </summary>
|
||||
private Boolean _testFinished;
|
||||
|
||||
/// <summary>
|
||||
/// SIRT communication okay
|
||||
/// </summary>
|
||||
private Boolean _sirtComOkay;
|
||||
|
||||
private DataContainer _dataContainer;
|
||||
private Statemashine _stateMachine;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum PAM signal level of SIRT got from DB
|
||||
/// </summary>
|
||||
private Byte? _rssiPamSirtDbMin;
|
||||
/// <summary>
|
||||
/// Maximum PAM signal level of SIRT got from DB
|
||||
/// </summary>
|
||||
private Byte? _rssiPamSirtDbMax;
|
||||
/// <summary>
|
||||
/// Minimum SEMI signal level of Cordonel response got from DB
|
||||
/// </summary>
|
||||
private Byte? _rssiSemiCordonelDbMin;
|
||||
/// <summary>
|
||||
/// Maximum SEMI signal level of Cordonel response got from DB
|
||||
/// </summary>
|
||||
private Byte? _rssiSemiCordonelDbMax;
|
||||
|
||||
/// <summary>
|
||||
/// Measured from SIRT the signal strength of the Cordonel SEMI answer
|
||||
/// </summary>
|
||||
private Byte? _rssiSemiCordonelMeasuredBySirt;
|
||||
|
||||
/// <summary>
|
||||
/// Measured from Cordonel the signal strength of the SIRT PAM received
|
||||
/// </summary>
|
||||
private Byte? _rssiPamSirtMeasuredByCordonel;
|
||||
|
||||
/// <summary>
|
||||
/// Number of the SIRT box as these are evaluated for their signal strength
|
||||
/// </summary>
|
||||
private Byte _sirtBoxNr;
|
||||
|
||||
/// <summary>
|
||||
/// Comport number of SIRT
|
||||
/// </summary>
|
||||
private Byte _sirtComport;
|
||||
|
||||
/// <summary>
|
||||
/// Radio address Cordonel
|
||||
/// </summary>
|
||||
private UInt32 _radioAddressCordonel;
|
||||
|
||||
/// <summary>
|
||||
/// Ctor
|
||||
/// </summary>
|
||||
public CheckRadio(String name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void InitUserControl()
|
||||
{
|
||||
UserControl = new UcRadioCheck();
|
||||
_checkRadioUserControl = (UcRadioCheck)UserControl;
|
||||
_checkRadioUserControl.OnResetRequested += delegate
|
||||
{
|
||||
Retry();
|
||||
};
|
||||
//_checkRadioUserControl.OnDoneRequested += delegate
|
||||
//{
|
||||
// SaveRssiLevelsToDb();
|
||||
//};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check of radio signal strength, skips process on special permission or if NA region
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2023-Jun-22" author="Thomas Wiedebusch/Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-27" author="Thomas Wiedebusch">
|
||||
/// - Skip process on special permission or if NA region,
|
||||
/// - Remove userControl from main screen if skipped.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
||||
/// - Fire SingleProcessState event with argument.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-07" author="Thomas Wiedebusch">
|
||||
/// - Removed signals for <see cref="SingleProcessState.Error"/> and <see cref="SingleProcessState.Succeeded"/> as
|
||||
/// these will be automatically set based on return value in
|
||||
/// <see cref="BaseProductionProcess.FinalizeProcess"/>.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
|
||||
/// - Assignment of stateMachine and dataContainer.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-20..22" author="Thomas Wiedebusch">
|
||||
/// - Get encryption key moved to process <see cref="ExecFinalParametrization"/> to speed up
|
||||
/// this process as this process needs to be run exclusively.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-05" author="Thomas Wiedebusch">
|
||||
/// - EOL progress signaled.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Logout at finalization,
|
||||
/// - Log all write accesses.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Jan-21" author="Thomas Wiedebusch">
|
||||
/// - SkipRadioCheck not nullable.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Feb-11" author="Thomas Wiedebusch">
|
||||
/// - SetTestInit as bool.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Feb-20" author="Thomas Wiedebusch">
|
||||
/// - Separate TestInit and SirtInit (only if test shouldn't be skipped).
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - CancellationToken.
|
||||
/// </remarks>
|
||||
public override StatusReturn ExecuteProcess()
|
||||
{
|
||||
// This will force to reset all values, Sentinel and Prepare shipping will fail based on EOL Progress
|
||||
if (Meter == null || ProductionRequirements == null ||
|
||||
(!Meter.Region.Equals("NA") && RadioEncryptionKey?.Count == 0))
|
||||
{
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
// Setup all parameters being able to execute initially or repeat the test
|
||||
TestInit();
|
||||
|
||||
// Check first if radio is installed,
|
||||
// North America Region(NA) doesn't have a Sensus radio application installed
|
||||
if (Meter.Region.Equals("NA"))
|
||||
{
|
||||
EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA;
|
||||
LogNewStatusMsg(Resources.StrSkippedMsgRadioCheckRegion);
|
||||
PrepareExit();
|
||||
return StatusReturn.Skipped;
|
||||
}
|
||||
|
||||
// Starting point of time-consuming operations
|
||||
SignalRunningState(MaxActualProgressSteps);
|
||||
|
||||
// If requirement forces skipping but radio is installed, it should at least be set to customer mode and checked for
|
||||
// it.
|
||||
// Reminder for skip radio check forced by requirements
|
||||
var skipRadioCheck = ProductionRequirements.SkipRadioCheck;
|
||||
|
||||
try
|
||||
{
|
||||
if (!Meter.IsLoggedOn)
|
||||
{
|
||||
Meter.Login();
|
||||
if (!Meter.IsLoggedOn)
|
||||
{
|
||||
return SetTestFailed(Resources.StrErrorMsgCordonelLogin);
|
||||
}
|
||||
}
|
||||
|
||||
// Standard run for radio signal test (RSSI)
|
||||
if (!skipRadioCheck)
|
||||
{
|
||||
if (UserControl == null)
|
||||
{
|
||||
return SetTestFailed(Resources.StrErrorMsgRadioCheck);
|
||||
}
|
||||
|
||||
// Initialize the SIRT
|
||||
if (!SirtInit())
|
||||
{
|
||||
return SetTestFailed(Resources.StrErrorMsgSirt);
|
||||
}
|
||||
|
||||
//FrequencyIndicator
|
||||
var freq = RegisterConverter.ByteArrayToValue<UInt32>(
|
||||
Meter.ReadRegister("SENSUSRADIO_FrequencyIndicator"));
|
||||
_radioAddressCordonel =
|
||||
RegisterConverter.ByteArrayToValue<UInt32>(Meter.ReadRegister("SENSUSRADIO_RadioAddress"));
|
||||
ActualProcessProgress++;
|
||||
|
||||
var meterRawEncryptionKey = Meter.ReadRegister("SENSUSRADIO_EncryptionKey", 16);
|
||||
var meterEncryptionKey = "";
|
||||
|
||||
// convert byte array to string
|
||||
if (meterRawEncryptionKey != null)
|
||||
{
|
||||
meterEncryptionKey = BitConverter.ToString(meterRawEncryptionKey.ToArray()).Replace("-", "");
|
||||
}
|
||||
|
||||
if (RadioEncryptionKey != null)
|
||||
{
|
||||
_radioEncryptionKey = RadioEncryptionKey[0].ToUpper();
|
||||
}
|
||||
|
||||
// Compare the meterEncryptionKey read out from meter with the database RadioEncryptionKey
|
||||
// got with the MeterProcessState/api/FinalCheck/GetEncryptionKey
|
||||
if (meterRawEncryptionKey == null || RadioEncryptionKey == null ||
|
||||
string.IsNullOrEmpty(meterEncryptionKey) || string.IsNullOrEmpty(_radioEncryptionKey) ||
|
||||
!_radioEncryptionKey.Equals(meterEncryptionKey))
|
||||
{
|
||||
return SetTestFailed(Resources.StrErrorMsgRadioKey);
|
||||
}
|
||||
|
||||
SuccessMsgDispatcher(Resources.StrSuccessMsgDbToMeterEncryptionKey);
|
||||
ActualProcessProgress++;
|
||||
|
||||
_sirtComport = Convert.ToByte(ConfigurationManager.AppSettings[$"SirtComport{freq}"]);
|
||||
_sirtBoxNr = Convert.ToByte(ConfigurationManager.AppSettings["SirtBoxNr"]);
|
||||
var ucRadio = (UcRadioCheck)UserControl;
|
||||
ucRadio.SetHeadline($"{Resources.StrStateRadioCheck} BOX: {_sirtBoxNr}");
|
||||
LogNewStatusMsg($"{Resources.StrStateRadioCheck} Port: COM{_sirtComport}, BOX: {_sirtBoxNr}, " +
|
||||
$"f: {freq} MHz");
|
||||
|
||||
// Tell the radio that something is in the slot which will impact the radio transmission
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_IrDAModulePresent",
|
||||
new Byte[] { 1 }, ProgrammingSource.ProgramInternal));
|
||||
|
||||
ActualProcessProgress++;
|
||||
|
||||
// Clear the measurement as on a retry started from main control the old values will be displayed
|
||||
ClearRssiMeasurement();
|
||||
// kick off RSSI test
|
||||
StartRssiMeasurement();
|
||||
|
||||
// Exit if meanwhile cancellation is required
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
CancellationProcedure();
|
||||
|
||||
var exitTime = DateTime.Now + TimeSpan.FromSeconds(Timeout_s);
|
||||
var actualTime = DateTime.Now;
|
||||
// wait on result or timeout or stop request from main window
|
||||
while (!_testFinished && exitTime > actualTime && SingleProcessState != SingleProcessState.Idle)
|
||||
{
|
||||
// Exit if meanwhile cancellation is required
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
CancellationProcedure();
|
||||
|
||||
Thread.Sleep(TimeoutLoop_ms);
|
||||
actualTime = DateTime.Now;
|
||||
ActualProcessProgress++;
|
||||
}
|
||||
|
||||
// lock all buttons as process is finished
|
||||
DisableAllButtons();
|
||||
}// !skipRadioCheck
|
||||
|
||||
// reset all radio alarms
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_MainAlarmMask",
|
||||
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_ExtendedAlarmMask",
|
||||
new Byte[] { 0 }, ProgrammingSource.ProgramInternal));
|
||||
// set system state back to shipping mode
|
||||
WriteRegisterLogAndProcessCtr(new ProgrammingParameters("SENSUSRADIO_SystemState",
|
||||
new Byte[] { 0xFF }, ProgrammingSource.ProgramInternal));
|
||||
ActualProcessProgress++;
|
||||
|
||||
// check radio is in customer mode 0x02 and alarm masks are 0
|
||||
var mainAlarmMask =
|
||||
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_MainAlarmMask"));
|
||||
var extendedAlarmMask =
|
||||
RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_ExtendedAlarmMask"));
|
||||
_finalRadioSystemState = RegisterConverter.ByteArrayToValue<Byte>(Meter.ReadRegister("SENSUSRADIO_SystemState"));
|
||||
ActualProcessProgress++;
|
||||
|
||||
if (// Radio mode and alarm masks NOT okay forces always an error
|
||||
// radio system state 0x02 shipping mode, 0x01 production mode, 0x0C transmission and data logger active
|
||||
_finalRadioSystemState != 0x02 || mainAlarmMask != 0 || extendedAlarmMask != 0
|
||||
// If radio check is enabled the signal level has to be in range and the test has to be finished
|
||||
|| (!skipRadioCheck && (!_successSignalLevel || !_sirtComOkay || !_testFinished)))
|
||||
{
|
||||
return SetTestFailed(!_sirtComOkay ? Resources.StrErrorMsgSirt : Resources.StrErrorMsgRadioCheck);
|
||||
}
|
||||
|
||||
SuccessMsgDispatcher(Resources.StrSuccessMsgRadioInCustomerMode);
|
||||
SuccessMsgDispatcher(Resources.StrSuccessMsgRadioAlarmsReset);
|
||||
|
||||
// Skip radio check by requirement
|
||||
if (skipRadioCheck)
|
||||
{
|
||||
EolProgress.FinalRadioSystemStateCheck = EOLStatus.NA;
|
||||
WarningMsgDispatcher(Resources.StrSkippedMsgRadioCheckSpecialPermission);
|
||||
PrepareExit();
|
||||
return StatusReturn.Skipped;
|
||||
}
|
||||
return SetTestSucceeded();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Meter?.WriteLog(ex.Message);
|
||||
return SetTestFailed(Resources.StrErrorMsgRadioCheck);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common for test Init
|
||||
/// </summary>
|
||||
private void TestInit()
|
||||
{
|
||||
// Uncheck executed process to leave the result open needed for retries
|
||||
EolProgress.RadioCheckId = null;
|
||||
EolProgress.FinalRadioSystemState = null;
|
||||
EolProgress.FinalRadioSystemStateCheck = null;
|
||||
// Value not saved as test hasn't been executed
|
||||
_radioResultCheckId = 0;
|
||||
_finalRadioSystemState = 0;
|
||||
|
||||
_sirtComOkay = false;
|
||||
_testFinished = false;
|
||||
_successSignalLevel = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the SIRT
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Boolean SirtInit()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Reset needed for repeated run
|
||||
_stateMachine?.Stop();
|
||||
_stateMachine?.ClearBUPRadioAdresses();
|
||||
_stateMachine?.ClearPam();
|
||||
_stateMachine = null;
|
||||
_dataContainer = null;
|
||||
_stateMachine = new Statemashine();
|
||||
_dataContainer = new DataContainer();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Meter?.WriteLog(ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Common to prepare exit for all status returns
|
||||
/// </summary>
|
||||
private void PrepareExit()
|
||||
{
|
||||
EolProgress.RadioCheckId = _radioResultCheckId;
|
||||
EolProgress.FinalRadioSystemState = _finalRadioSystemState;
|
||||
|
||||
// Essential to logout from meter at any exit
|
||||
Meter?.Logout();
|
||||
|
||||
_stateMachine?.Stop();
|
||||
_stateMachine = null;
|
||||
_dataContainer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common for test failed
|
||||
/// </summary>
|
||||
/// <param name="errorMsg">error message to dispatch and log</param>
|
||||
/// <remarks><see cref="StatusReturn.Failed"/></remarks>
|
||||
private StatusReturn SetTestFailed(String errorMsg)
|
||||
{
|
||||
EolProgress.FinalRadioSystemStateCheck = EOLStatus.FAIL;
|
||||
ErrorMsgDispatcher(errorMsg);
|
||||
PrepareExit();
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common for test succeeded
|
||||
/// </summary>
|
||||
/// <remarks><see cref="StatusReturn.Okay"/></remarks>
|
||||
private StatusReturn SetTestSucceeded()
|
||||
{
|
||||
EolProgress.FinalRadioSystemStateCheck = EOLStatus.OK;
|
||||
PrepareExit();
|
||||
return StatusReturn.Okay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable all buttons of user control.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void DisableAllButtons()
|
||||
{
|
||||
//EnableBtn(_checkRadioUserControl.btnSkip, false);
|
||||
EnableBtn(_checkRadioUserControl.btnRetry, false);
|
||||
}
|
||||
|
||||
private void EnableBtn(Button btn, Boolean enable)
|
||||
{
|
||||
btn.Dispatcher.Invoke(DispatcherPriority.Send,
|
||||
new Action(() =>
|
||||
{
|
||||
btn.IsEnabled = enable;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private void UpdateUc()
|
||||
{
|
||||
_checkRadioUserControl.SetUi(new UcRadioCheck.UiContainer(_rssiPamSirtDbMin, _rssiSemiCordonelDbMin,
|
||||
_rssiPamSirtMeasuredByCordonel, _rssiSemiCordonelMeasuredBySirt, _rssiPamSirtDbMax, _rssiSemiCordonelDbMax));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check of radio signal strength.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void StartRssiMeasurement()
|
||||
{
|
||||
// disable all buttons during ongoing measurement process
|
||||
DisableAllButtons();
|
||||
UpdateUc();
|
||||
|
||||
try
|
||||
{
|
||||
ActualProcessProgress++;
|
||||
GetRssiMinMaxValuesFromDb(_sirtBoxNr, Meter.OrderNumber);
|
||||
SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey);
|
||||
UpdateUc();
|
||||
ActualProcessProgress++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_testFinished = true;
|
||||
Meter?.WriteLog(ex.Message);
|
||||
LogNewStatusMsg(Resources.StrErrorMsgRadioCheck);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear RSSI values for display of unknown values but leave limits active as
|
||||
/// those are already loaded from DB and will not change during retry. Therefore,
|
||||
/// the process can be optimized to avoid recurrent DB access!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void ClearRssiMeasurement()
|
||||
{
|
||||
_sirtComOkay = false;
|
||||
_rssiPamSirtMeasuredByCordonel = null;
|
||||
_rssiSemiCordonelMeasuredBySirt = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retry collection of RSSI values (entire process with sending PAM and receiving SEMI).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void Retry()
|
||||
{
|
||||
// reset process counter to start from scratch
|
||||
ActualProcessProgress = 0;
|
||||
if (_stateMachine != null)
|
||||
{
|
||||
_stateMachine.ClearPam();
|
||||
_stateMachine.Stop();
|
||||
}
|
||||
ClearRssiMeasurement();
|
||||
UpdateUc();
|
||||
SendPamListenSemi((Int32)_radioAddressCordonel, _sirtComport, _radioEncryptionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ATTENTION:
|
||||
/// As those definitions are directly used as DB definitions for pushing the result:
|
||||
///
|
||||
/// LEAVE THOSE NAMING AS DEFINED HERE!
|
||||
///
|
||||
/// </summary>
|
||||
private class DataContainer
|
||||
{
|
||||
public Int32 RadioAdress
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Byte RSSI_PAM
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Byte RSSI_SEMI
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Int32 LimitId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public Byte Result
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save RSSI values to DB.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void SaveRssiLevelsToDb()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_dataContainer != null)
|
||||
{
|
||||
_dataContainer.RadioAdress = (Int32)_radioAddressCordonel; // 0-4294967295
|
||||
var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}SaveRSSI";
|
||||
|
||||
var response = LocalWebRequest.PostRequestAsync(url, 30000, out var httpStatus, _dataContainer);
|
||||
if (!string.IsNullOrEmpty(response) && httpStatus == HttpStatusCode.OK)
|
||||
{
|
||||
var jsonObj = JObject.Parse(response);
|
||||
_radioResultCheckId = Convert.ToInt32(jsonObj.SelectToken("radiocheckid"));
|
||||
}
|
||||
|
||||
SuccessMsgDispatcher(Resources.StrSuccessMsgSetRadioRssiLimitsToDb);
|
||||
ActualProcessProgress++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Meter?.WriteLog(ex.Message);
|
||||
ErrorMsgDispatcher(Resources.StrErrorMsgSetRadioRssiLimitsToDb);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_testFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get RSSI values from DB.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void GetRssiMinMaxValuesFromDb(Byte BoxNr, Int32 productionOrderNo)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_dataContainer != null)
|
||||
{
|
||||
var url = $"{ServiceUrls.GenesisRadioCheckServiceUrl()}GetLimits/{BoxNr}/{productionOrderNo}";
|
||||
var body = LocalWebRequest.GetRequest(url, 300000, out var httpStatus);
|
||||
if (httpStatus != HttpStatusCode.OK || string.IsNullOrEmpty(body))
|
||||
{
|
||||
ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb);
|
||||
return;
|
||||
}
|
||||
var o = JObject.Parse(body);
|
||||
|
||||
_dataContainer.LimitId = Convert.ToInt32(o.SelectToken("LimitId")?.ToString());
|
||||
_rssiPamSirtDbMin = Convert.ToByte(o.SelectToken("RSSI_PAM_Min")?.ToString());
|
||||
_rssiPamSirtDbMax = Convert.ToByte(o.SelectToken("RSSI_PAM_Max")?.ToString());
|
||||
_rssiSemiCordonelDbMin = Convert.ToByte(o.SelectToken("RSSI_SEMI_Min")?.ToString());
|
||||
_rssiSemiCordonelDbMax = Convert.ToByte(o.SelectToken("RSSI_SEMI_Max")?.ToString());
|
||||
SuccessMsgDispatcher(Resources.StrSuccessMsgGetRadioRssiLimitsFromDb +
|
||||
$": ID: ({_dataContainer.LimitId}), " +
|
||||
$"SIRT Min: ({_rssiPamSirtDbMin}), " +
|
||||
$"SIRT Max: ({_rssiPamSirtDbMax}), " +
|
||||
$"Cordonel Min: ({_rssiSemiCordonelDbMin}), " +
|
||||
$"Cordonel Max: ({_rssiSemiCordonelDbMax})");
|
||||
ActualProcessProgress++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Meter?.WriteLog(ex.Message);
|
||||
ErrorMsgDispatcher(Resources.StrErrorMsgGetRadioRssiLimitsFromDb);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send PAM to force a BUP and a SEMI and start the RSSI observer to measure the signal strength.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-07-11" author="Thomas Wiedebusch">
|
||||
/// - Optimized based on new documentations.
|
||||
/// </remarks>
|
||||
private void SendPamListenSemi(Int32 radioAddress, Int32 byteComPort, String strKey)
|
||||
{
|
||||
if (_dataContainer == null || _stateMachine == null)
|
||||
return;
|
||||
|
||||
_rssiPamSirtMeasuredByCordonel = null;
|
||||
_rssiSemiCordonelMeasuredBySirt = null;
|
||||
|
||||
Byte P81_1 = 0;
|
||||
|
||||
_dataContainer.RadioAdress = radioAddress;
|
||||
|
||||
_stateMachine.initialise((Byte)byteComPort);
|
||||
var date = DateTime.Now.Date;
|
||||
var loggingPath = $"C:\\GenesisLog\\{date:yyyy-MM-dd}\\ProductionUiCordonel\\{Meter.PcbId}_RadioCheck.log";
|
||||
_stateMachine.StartLoggin(loggingPath);
|
||||
|
||||
LogNewStatusMsg($"{Resources.StrRadioMsgSendPam}: {radioAddress.ToString()}");
|
||||
|
||||
// Initialize the state machine of the SIRT dll
|
||||
_stateMachine.Stop();
|
||||
_stateMachine.ClearPam();
|
||||
_stateMachine.Timeout_s = 30;
|
||||
|
||||
// Dummy PAM 00 with radio encryption key, SIRT dll takes the key for the response and the
|
||||
// address for the request and response, CommandHex 00 is data size of 0, so it makes no sense
|
||||
// to encrypt the 00 PAM, but the key will be used in the SEMI response
|
||||
_stateMachine.SendPAM(radioAddress.ToString(), "00", "", P81_1, 1, 0, strKey, "0", true);
|
||||
ActualProcessProgress++;
|
||||
|
||||
// SystemState 0x55 will force meter immediately to send BUP, internal the system state remains
|
||||
// on 0x02. This has to be sent after PAM!
|
||||
Meter.WriteRegister("SENSUSRADIO_SystemState", 0x55);
|
||||
|
||||
// Start polling
|
||||
_stateMachine.OnStateChanged += StateMachineOnStateChange_Handler;
|
||||
_stateMachine.Start();
|
||||
|
||||
ActualProcessProgress++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get RSSI (Received Signal Strength Indicator) values from SIRT (Sensus Interface Radio Tool).
|
||||
/// Set the <see cref="_successSignalLevel"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-09-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void GotRssiCallback(Byte rssiSemiCordonel, Byte rssiPamSirt)
|
||||
{
|
||||
if (_dataContainer == null)
|
||||
return;
|
||||
|
||||
// RSSI PAM sent by SIRT and measured by Cordonel
|
||||
LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiSirt}: ({rssiPamSirt})");
|
||||
// RSSI SEMI sent by Cordonel and measured by SIRT
|
||||
LogNewStatusMsg($"{Resources.StrMeasuredMsgRadioRssiCordonel}: ({rssiSemiCordonel})");
|
||||
|
||||
_rssiPamSirtMeasuredByCordonel = rssiPamSirt;
|
||||
_rssiSemiCordonelMeasuredBySirt = rssiSemiCordonel;
|
||||
_dataContainer.RSSI_PAM = rssiPamSirt;
|
||||
_dataContainer.RSSI_SEMI = rssiSemiCordonel;
|
||||
|
||||
UpdateUc();
|
||||
|
||||
// threshold check for failed test
|
||||
if (_rssiPamSirtMeasuredByCordonel < _rssiPamSirtDbMin ||
|
||||
_rssiPamSirtMeasuredByCordonel > _rssiPamSirtDbMax ||
|
||||
_rssiSemiCordonelMeasuredBySirt < _rssiSemiCordonelDbMin ||
|
||||
_rssiSemiCordonelMeasuredBySirt > _rssiSemiCordonelDbMax)
|
||||
{
|
||||
_dataContainer.Result = 0;
|
||||
// done can finish the test which is equal to abort on error
|
||||
//EnableBtn(_checkRadioUserControl.btnSkip, true);
|
||||
// reset can repeat the test as often as wanted
|
||||
EnableBtn(_checkRadioUserControl.btnRetry, true);
|
||||
_successSignalLevel = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// on success there is no choice to made, it is finished
|
||||
_dataContainer.Result = 1;
|
||||
_successSignalLevel = true;
|
||||
|
||||
// safe those results to DB for logging
|
||||
SaveRssiLevelsToDb();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State machine state change handler.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void StateMachineOnStateChange_Handler(Object sender, MyEventArgs e)
|
||||
{
|
||||
if (_stateMachine == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
// Reading back sent PAM from SIRT to Cordonel
|
||||
var pam = _stateMachine.GetPAM(1);
|
||||
|
||||
if (pam != null)
|
||||
{
|
||||
if (pam.State == PAM.PAMStates.Timeout)
|
||||
{
|
||||
LogNewStatusMsg($"Timeout {pam.GetTime()} s");
|
||||
}
|
||||
|
||||
if (pam.CountSemi > 0)
|
||||
{
|
||||
// SEMI received from Cordonel
|
||||
_stateMachine.Stop();
|
||||
_stateMachine.ClearPam();
|
||||
|
||||
var hexBytesSemi = pam.Semi.HexData.Replace(" ", "");
|
||||
var semiBytes = new Byte[hexBytesSemi.Length / 2];
|
||||
|
||||
for (var i = 0; i < hexBytesSemi.Length; i += 2)
|
||||
semiBytes[i / 2] = Convert.ToByte(hexBytesSemi.Substring(i, 2), 16);
|
||||
|
||||
// SEMI Status okay if 0, if SIRT dll returns unequal to 0 the encryption key is wrong
|
||||
// If 0 the encryption key is correct
|
||||
if (semiBytes[19] == 0)
|
||||
{
|
||||
// RSSI SEMI sent by Cordonel and measured by SIRT (signal level of Cordonel in byte 2)
|
||||
var rssiSemiMeasuredBySirt = semiBytes[2];
|
||||
|
||||
// RSSI PAM sent by SIRT and measured by Cordonel (signal level of SIRT packet to SEMI in byte 20)
|
||||
var rssiPamMeasuredByCordonel = semiBytes[20];
|
||||
|
||||
GotRssiCallback(rssiSemiMeasuredBySirt, rssiPamMeasuredByCordonel);
|
||||
_sirtComOkay = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// SEMI Status not 0 is internal error which should be logged
|
||||
LogNewStatusMsg(pam.Semi.GetPamErrorMessage());
|
||||
_sirtComOkay = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogNewStatusMsg($"BUPS: ({pam.CountBup})");
|
||||
_sirtComOkay = false;
|
||||
}
|
||||
}//pam not null
|
||||
else
|
||||
{
|
||||
_stateMachine.Stop();
|
||||
_stateMachine.ClearPam();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Meter?.WriteLog(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,11 +453,14 @@ namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Handle genesis meter is null as new meter.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mai-12" author="Thomas Wiedebusch">
|
||||
/// - Check _genesisMeter not null before equality with old pcbid.
|
||||
/// </remarks>
|
||||
private Boolean CheckForNewMeter()
|
||||
{
|
||||
if (_genesisMeter == null ||
|
||||
(!string.IsNullOrEmpty(_lastPcbId) &&
|
||||
!_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
_genesisMeter?.PcbId != null && !_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
{
|
||||
// This loop will be entered after the meter has changed, but not on the initial meter
|
||||
// after program start.
|
||||
|
||||
@@ -0,0 +1,857 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using LaaPackages.Features.Cordonel.Models;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus;
|
||||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.Enum;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.EventArgs;
|
||||
using Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses.GenericProcesses;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Xylem.Common.Production.ProductionUiCordonel.ProductionProcesses
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation for all methods needed to drive the state machine of the process controller
|
||||
/// </summary>
|
||||
public class ProcessController
|
||||
{
|
||||
#region ------------------------------------------ Variables --------------------------------------------------
|
||||
// actual genesis meter
|
||||
private IGenesisMeter _genesisMeter;
|
||||
// Cordonel requirements for production
|
||||
private CordonelRequirements _cordonelRequirements;
|
||||
// EOL progress states for production
|
||||
private EOLProgressModel _cordonelEolProgress;
|
||||
// last pcbId for check of changed genesisMeter
|
||||
private String _lastPcbId;
|
||||
// batch just used for setup, as only one meter will be assigned
|
||||
private MeterBatch _meterBatch;
|
||||
// state of the state machine
|
||||
private ProcessState _stateMachineState;
|
||||
// locker to avoid repeated state execution
|
||||
private ProcessState _lockedProcessState;
|
||||
// sender of state change event to evaluate in e.g. error process
|
||||
private IProductionProcess _stateChangeRequestProcess;
|
||||
// List of production processes
|
||||
public List<IProductionProcess> Processes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
// Container for production process states
|
||||
private IProcessStateDef _processStateDef;
|
||||
|
||||
// Container for production status information, a list is used to keep the content over all classes of production
|
||||
// processes as a reference seems not to return their value (always reset to the initial value)
|
||||
private GenesisStatus _productionStatus;
|
||||
|
||||
// register restorer
|
||||
private RegisterRestorer _registerRestorer;
|
||||
|
||||
// sensus radio encryption key
|
||||
private List<String> _radioEncryptionKey;
|
||||
|
||||
// Password container with all information, a list is used to keep the content over all classes of production
|
||||
// processes as a reference seems not to return their value (always reset to the initial value)
|
||||
private MeterPwdDb _pwdContainer;
|
||||
|
||||
// reminder for slot
|
||||
private readonly Int32 _slot;
|
||||
|
||||
/// <summary>
|
||||
/// Change the overview table of the GUI to the state of the individual process
|
||||
/// </summary>
|
||||
public event EventHandler<SingleProcessStateArgs> OnSingleProcessStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The overall process chain changed needed to signal to the GUI
|
||||
/// - Can be forced by the state machine to invoke,
|
||||
/// - Will be used via the OverallProcessStateChanged_Handler by the individual processes to return the next
|
||||
/// required sate after break, error or completion to change the state of the state machine.
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessStateArgs> OnOverallProcessStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Request for logging to GUI output and log-file
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessLogArgs> OnProcessLogRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Keep the progress bars of the GUI up to date
|
||||
/// </summary>
|
||||
public event EventHandler<ProcessProgressArgs> OnProcessProgressChanged;
|
||||
|
||||
// cancellation token
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
// cyclic timer for state machine call
|
||||
private Timer _processCtrlTimer;
|
||||
private const Int32 ProcessTimerCycleMs = 250;
|
||||
|
||||
/// <summary>
|
||||
/// Avoid waiting for a single process completion.
|
||||
/// </summary>
|
||||
public const IProductionProcess DoNotWaitForSingleProcessCompletion = null;
|
||||
|
||||
/// <summary>
|
||||
/// Kick off a parallel execution of the state machine.
|
||||
/// </summary>
|
||||
public const Boolean ExecuteInParallel = true;
|
||||
|
||||
#endregion --------------------------------------- Variables --------------------------------------------------
|
||||
#region ------------------------------------------ Class ------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Ctor:
|
||||
/// - Setup process timer for cyclic call of state machine,
|
||||
/// - Create and setup Cordonel.
|
||||
/// </summary>
|
||||
/// <param name="slot">physical slot for this meter</param>
|
||||
/// <param name="processSateDef"></param>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-04" author="Thomas Wiedebusch">
|
||||
/// - Always set to production mode to load the registers for this FW and not the latest,
|
||||
/// - Always RegisterWatchService on,
|
||||
/// - Always MinMaxCHeck on.
|
||||
/// </remarks>
|
||||
public ProcessController(Int32 slot, IProcessStateDef processSateDef)
|
||||
{
|
||||
_processCtrlTimer = new Timer(TmrProcessCtrlCycle_Elapsed, null, ProcessTimerCycleMs, ProcessTimerCycleMs);
|
||||
_slot = slot;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
_processStateDef = processSateDef;
|
||||
InitNewMeter();
|
||||
InitProcesses();
|
||||
// program start with idle process
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization of new meter
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Initialize processes to build a list of all production processes.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes
|
||||
/// </remarks>
|
||||
private void InitNewMeter()
|
||||
{
|
||||
_genesisMeter = new GenesisMeter();
|
||||
_genesisMeter.SetupFromConfigFile(_slot);
|
||||
// Important setup to overwrite setting from "Setup" in GTB
|
||||
_genesisMeter.Configuration.ProductionMode = true;
|
||||
_genesisMeter.Configuration.UseRegisterWatchService = true;
|
||||
_genesisMeter.Configuration.UseMinMaxCheck = true;
|
||||
// Meter batch even if only one meter in use as the meter batch routines can be used
|
||||
_meterBatch = new MeterBatch();
|
||||
_meterBatch.AddMeter(_genesisMeter);
|
||||
// Set lock process to unreachable state delimiter to force initial entry of state machine
|
||||
_lockedProcessState = ProcessState.StateListDelimiter;
|
||||
// Create a register restorer
|
||||
_registerRestorer = new RegisterRestorer(_genesisMeter);
|
||||
_radioEncryptionKey = new List<String>();
|
||||
// Containers for production status and standard or special requirement for production
|
||||
_productionStatus = new GenesisStatus();
|
||||
_cordonelRequirements = new CordonelRequirements();
|
||||
// Password container as reminder for repeated test on identical device
|
||||
_pwdContainer = new MeterPwdDb();
|
||||
// EOL progress to feed the sentinel
|
||||
_cordonelEolProgress = new EOLProgressModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removal of old meter
|
||||
/// </summary>
|
||||
private void RemoveMeter()
|
||||
{
|
||||
_genesisMeter?.ClearPassword();
|
||||
// meter batch dispose disposes all IMeters
|
||||
_meterBatch?.Dispose();
|
||||
_meterBatch = null;
|
||||
_genesisMeter = null;
|
||||
_registerRestorer = null;
|
||||
_radioEncryptionKey = null;
|
||||
_productionStatus = null;
|
||||
_cordonelRequirements = null;
|
||||
_pwdContainer = null;
|
||||
_cordonelEolProgress = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
_processCtrlTimer?.Dispose();
|
||||
_processCtrlTimer = null;
|
||||
_processStateDef = null;
|
||||
KillProcesses();
|
||||
RemoveMeter();
|
||||
}
|
||||
|
||||
#endregion --------------------------------------- Class ------------------------------------------------------
|
||||
#region ------------------------------------------ Events -----------------------------------------------------
|
||||
/// <summary>
|
||||
/// Single process state or process list changed event,
|
||||
/// Use event to force special procedure like uninstalling events
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="2023-Feb-14" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-29" author="Thomas Wiedebusch">
|
||||
/// - Remove process events if process is finished.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - Remove process events if process is finished or skipped.
|
||||
/// </remarks>
|
||||
private void SingleProcessStateChanged_Handler(Object sender, SingleProcessStateArgs e)
|
||||
{
|
||||
IProductionProcess pp = null;
|
||||
if (sender is IProductionProcess process)
|
||||
{
|
||||
pp = process;
|
||||
}
|
||||
// uninstall events
|
||||
switch (e.SingleProcessState)
|
||||
{
|
||||
// after execution independent of the result, the events coming from the process
|
||||
// are needed to be removed
|
||||
case SingleProcessState.Succeeded:
|
||||
case SingleProcessState.Skipped:
|
||||
case SingleProcessState.Abort:
|
||||
case SingleProcessState.Error:
|
||||
RemoveProcessEvents(pp);
|
||||
break;
|
||||
|
||||
case SingleProcessState.Retry:
|
||||
case SingleProcessState.Waiting:
|
||||
case SingleProcessState.Running:
|
||||
break;
|
||||
}
|
||||
// signal state change to GUI
|
||||
OnSingleProcessStateChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ProcessProgress_Handler(Object sender, ProcessProgressArgs e)
|
||||
{
|
||||
OnProcessProgressChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ProcessLog_Handler(Object sender, ProcessLogArgs e)
|
||||
{
|
||||
OnProcessLogRequest?.Invoke(sender, e);
|
||||
}
|
||||
/// <summary>
|
||||
/// Handler to set a new state for the state machine regarding the entire final-process
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks date="2023-Mar-03" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void OverallProcessStateChanged_Handler(Object sender, ProcessStateArgs e)
|
||||
{
|
||||
// remind caller of the state change request to act on e.g. error processing
|
||||
_stateChangeRequestProcess = (IProductionProcess)sender;
|
||||
|
||||
// received new status from external
|
||||
if (e.State != null)
|
||||
{
|
||||
_stateMachineState = (ProcessState)e.State;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process timer
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void TmrProcessCtrlCycle_Elapsed(Object state)
|
||||
{
|
||||
StateMachine();
|
||||
}
|
||||
|
||||
#endregion --------------------------------------- Events -----------------------------------------------------
|
||||
#region ------------------------------------------ State Machine ----------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// State-machine of ProcessController which serves all production processes.
|
||||
/// This-state machine executes all states defined in the "GenericStateMachineDef" and the individual
|
||||
/// production-process specific "XxxStateMachineDef".
|
||||
///
|
||||
/// A few states with special functions will be caught in advance in this StateMachine
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
||||
/// - Generic implementation
|
||||
/// </remarks>
|
||||
private void StateMachine()
|
||||
{// avoid repeated execution of the state machine on unchanged state
|
||||
if (_stateMachineState == _lockedProcessState)
|
||||
{
|
||||
Thread.Sleep(ProcessTimerCycleMs >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// remind backup state to avoid repeated execution and side effects
|
||||
_lockedProcessState = _stateMachineState;
|
||||
|
||||
// signal overall process progress to GUI based on ready processes
|
||||
CalcAndPushOverAllProgress();
|
||||
|
||||
// signal state change to logger
|
||||
OnOverallProcessStateChanged?.Invoke(this, new ProcessStateArgs(_stateMachineState));
|
||||
|
||||
// get the processStateStruct of the actual set stateMachineSTate
|
||||
var processStateStruct = _processStateDef.GetProcessStateStructOfState(_stateMachineState);
|
||||
|
||||
switch (_stateMachineState)
|
||||
{
|
||||
case ProcessState.Idle:
|
||||
// This state needs an external state change like user input to go ahead
|
||||
break;
|
||||
|
||||
case ProcessState.DetectCordonel:
|
||||
// Wait for cancellation
|
||||
if (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
// Reset the cancellation request
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
// Set up the counter for maximum overall process progress bar in GUI
|
||||
CalcAndPushMaxOverAllProcessSteps();
|
||||
|
||||
// Detection of Cordonel has to be completed before assigning next state, so leave to automatic
|
||||
// to change the state after successfully execution. The detection is the base for all others.
|
||||
StartProcess(processStateStruct);
|
||||
break;
|
||||
|
||||
case ProcessState.CheckNewMeter:
|
||||
// Detection of Cordonel has to be completed before assigning next state. It has to be checked if
|
||||
// the meter changed to clear all collected contents of previous run. To UPDATE ALL INFORMATION after
|
||||
// new meter assignment the "DetectCordonel" has to be repeated to return here "Old Meter" and then
|
||||
// enter the "ConnectCordonel"!
|
||||
_stateMachineState = CheckForNewMeter() ? ProcessState.DetectCordonel : ProcessState.ConnectCordonel;
|
||||
break;
|
||||
|
||||
|
||||
case ProcessState.RepeatFailedTests:
|
||||
_stateMachineState = RepeatFailedTests();
|
||||
break;
|
||||
|
||||
case ProcessState.AbortTest:
|
||||
AbortProcesses();
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
case ProcessState.Stop:
|
||||
KillProcesses();
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
case ProcessState.Error:
|
||||
// Common error handling routine
|
||||
ProcessErrorHandler(_stateChangeRequestProcess);
|
||||
// Start the error handler with its user feedback request. It has to be started
|
||||
// always even if the abortion of all processes is required! The error handler
|
||||
// will change the exit state based on the user input.
|
||||
StartProcess(processStateStruct);
|
||||
break;
|
||||
|
||||
case ProcessState.StateListDelimiter:
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is the call to the processes for a specific test
|
||||
StartProcess(processStateStruct);
|
||||
if (processStateStruct.KickOffParallelState != null)
|
||||
{
|
||||
_stateMachineState = (ProcessState)processStateStruct.KickOffParallelState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException ex)
|
||||
{
|
||||
_genesisMeter?.WriteLog(ex.Message);
|
||||
_stateMachineState = ProcessState.Idle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_genesisMeter?.WriteLog(ex.Message);
|
||||
_stateMachineState = ProcessState.Error;
|
||||
}
|
||||
}
|
||||
}// state locked against repeated execution
|
||||
|
||||
|
||||
#endregion --------------------------------------- State Machine ----------------------------------------------
|
||||
#region ------------------------------------------ Tools ------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Returns the currentGenesis information for display
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public CordonelRequirements GetProductionRequirements()
|
||||
{
|
||||
return _cordonelRequirements;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the currentGenesis information for display
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="2023-Feb-27" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public IGenesisMeter GetGenesisMeter()
|
||||
{
|
||||
return _genesisMeter;
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears all collected data on new genesisMeter and reminds the detected for next
|
||||
/// detection and run of this comparison.
|
||||
/// </summary>
|
||||
/// <returns>true for new meter</returns>
|
||||
/// <remarks date="2023-Jun-28" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - The StartProcessSequence has already removed all processes from list to have a clean start,
|
||||
/// avoid it here again, because the detect is now in the list!
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Handle genesis meter is null as new meter.
|
||||
/// </remarks>
|
||||
private Boolean CheckForNewMeter()
|
||||
{
|
||||
if (_genesisMeter == null ||
|
||||
(!string.IsNullOrEmpty(_lastPcbId) &&
|
||||
!_genesisMeter.PcbId.Equals(_lastPcbId)))
|
||||
{
|
||||
// This loop will be entered after the meter has changed, but not on the initial meter
|
||||
// after program start.
|
||||
KillProcesses();
|
||||
RemoveMeter();
|
||||
InitNewMeter();
|
||||
InitProcesses();
|
||||
// Remind actual genesis
|
||||
_lastPcbId = _genesisMeter?.PcbId;
|
||||
|
||||
// New meter detected
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remind actual genesis
|
||||
_lastPcbId = _genesisMeter?.PcbId;
|
||||
|
||||
// Old meter
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart final test on first failed or aborted test.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jul-18" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes and search in Processes
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Oct-21" author="Thomas Wiedebusch">
|
||||
/// - Leave skipped processes as is.
|
||||
/// </remarks>
|
||||
private ProcessState RepeatFailedTests()
|
||||
{
|
||||
var state = ProcessState.Idle;
|
||||
|
||||
// Search for first test in processes list which failed or aborted due to final test in
|
||||
// parallel processing forced abort of active processes
|
||||
foreach (var p in Processes)
|
||||
{
|
||||
if (p.SingleProcessState == SingleProcessState.Error ||
|
||||
p.SingleProcessState == SingleProcessState.Abort ||
|
||||
p.SingleProcessState == SingleProcessState.Retry)
|
||||
{
|
||||
// set the state to the first entry to kick off the state machine here
|
||||
state = _processStateDef.GetStateOfProcess(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Put all processes which are not successfully completed to init (Waiting for execution)
|
||||
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
|
||||
pp.SingleProcessState != SingleProcessState.Skipped))
|
||||
{
|
||||
pp.IdleProcess();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
/// <summary>
|
||||
/// Error handler
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Feb-15" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Feb-07" author="Thomas Wiedebusch">
|
||||
/// - Activated check for sequences in production process for ProductionProcessCheckFinalParametrization
|
||||
/// to re-start with ProductionProcessExecFinalParametrization.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-09" author="Thomas Wiedebusch">
|
||||
/// - Removed list of failed production processes
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Jan-28" author="Thomas Wiedebusch">
|
||||
/// - Kick off exec parametrization if check after reboot failed.
|
||||
/// </remarks>
|
||||
private void ProcessErrorHandler(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
// Special action if parametrization check failed to repeat it
|
||||
if (ProcessState.CheckFinalParametrization == _processStateDef.GetStateOfProcess(process))
|
||||
{
|
||||
// Signal state change to retry sequence exec config, store config, reboot, check config
|
||||
var pp = _processStateDef.GetProcessOfState(ProcessState.ExecFinalParametrization);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.ExecStoreConfiguration);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.RebootCordonel);
|
||||
pp.RetryProcess();
|
||||
pp = _processStateDef.GetProcessOfState(ProcessState.CheckFinalParametrization);
|
||||
pp.RetryProcess();
|
||||
}
|
||||
|
||||
// Error signal needed to abort to all processes
|
||||
AbortProcesses();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the maximum of all processes expected to execute and signal to GUI.
|
||||
/// </summary>
|
||||
private void CalcAndPushMaxOverAllProcessSteps()
|
||||
{
|
||||
if (_processStateDef == null)
|
||||
return;
|
||||
|
||||
var _maxOverallProcessesSteps = _processStateDef.GetNumberOfInitializedProcesses();
|
||||
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_maxOverallProcessesSteps,
|
||||
ProcessProgressArgs.ProcessProgressType.MaxOverallProcessSteps));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculate all processes which are completed or skipped and signal to GUI.
|
||||
/// </summary>
|
||||
private void CalcAndPushOverAllProgress()
|
||||
{
|
||||
if (Processes == null)
|
||||
return;
|
||||
|
||||
// check the readiness of all processes in the loop
|
||||
var _overallReadyProcesses = 0;
|
||||
foreach (var p in Processes)
|
||||
{
|
||||
if (p.SingleProcessState == SingleProcessState.Succeeded ||
|
||||
p.SingleProcessState == SingleProcessState.Skipped)
|
||||
_overallReadyProcesses++;
|
||||
}
|
||||
|
||||
OnProcessProgressChanged?.Invoke(this, new ProcessProgressArgs(_overallReadyProcesses,
|
||||
ProcessProgressArgs.ProcessProgressType.OverallProcessesSteps));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all processes which are connected to a production process.
|
||||
/// </summary>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void InitProcesses()
|
||||
{
|
||||
if (Processes == null)
|
||||
Processes = new List<IProductionProcess>();
|
||||
|
||||
// get all processes relevant for EOL from table which has to be sorted
|
||||
var processes = _processStateDef.GetAllEolRelevantProcesses();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
// process is standby for execution after idle, this overrides the eventually abort state
|
||||
process.IdleProcess();
|
||||
|
||||
// add process to list to feed the process window in main screen
|
||||
if (Processes.All(pp => pp.ProcessName != process.ProcessName))
|
||||
{
|
||||
Processes.Add(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production process kick off:
|
||||
/// - Sets single process state to waiting within the process init,
|
||||
/// - Adds new process to process list,
|
||||
/// - Optional waits for single process to complete before process is going to be started,
|
||||
/// - Optional waits for all previously assigned and started processes to complete before process is going to
|
||||
/// be started,
|
||||
/// - Assigns slot, productionStatus, programmingParameters and genesisMeter to process,
|
||||
/// - Adds event handlers for process information,
|
||||
/// - Starts process.
|
||||
/// </summary>
|
||||
/// <param name="processStateStruct"></param>
|
||||
/// <returns>actual state</returns>
|
||||
/// <remarks date="2023-Jan-31" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
||||
/// - Skipped process due to one of the previous processes failed
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-17" author="Thomas Wiedebusch">
|
||||
/// - Restructured to avoid process start if processes to wait for are not completed
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Dec-15" author="Thomas Wiedebusch">
|
||||
/// - Changed from programming parameters to register restorer
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Jul-15" author="Thomas Wiedebusch">
|
||||
/// - Changed setup process to start process being able to create a process list in advance.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-01" author="Thomas Wiedebusch">
|
||||
/// - Changed setup process to start process being able to create a process list in advance.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-15" author="Thomas Wiedebusch">
|
||||
/// - Feed with processStateStruct.
|
||||
/// </remarks>
|
||||
protected void StartProcess(ProcessStateStruct processStateStruct)
|
||||
{
|
||||
// deny access if uninitialized
|
||||
if (Processes == null || processStateStruct.ProductionProcess == null)
|
||||
return;
|
||||
|
||||
// do not start a new process if any error or abort request is signaled
|
||||
if (AnyProcessRequiresAbortion() && !processStateStruct.StartAlways )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// process is waiting for execution after init, this overrides the eventually abort state
|
||||
processStateStruct.ProductionProcess.InitProcess();
|
||||
|
||||
// wait for a single process to complete before start of new depending on process
|
||||
if (processStateStruct.WaitForSingleProcess != null)
|
||||
{
|
||||
// each process will have its own timer until a timeout will change the state
|
||||
while (Processes.Any(pp => !AnyProcessRequiresAbortion()
|
||||
&& _processStateDef.GetStateOfProcess(pp) == processStateStruct.WaitForSingleProcess
|
||||
&& WaitForProcess(pp)))
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all initialized processes to complete before start the new process
|
||||
if (processStateStruct.WaitForAllProcesses)
|
||||
{
|
||||
// each process will have its own timer until a timeout will change the state,
|
||||
// avoid checking the new process, which is not started due to this waiting loop
|
||||
while (Processes.Any(pp => pp.ProcessName != processStateStruct.ProductionProcess.ProcessName
|
||||
&& WaitForProcess(pp)
|
||||
&& !AnyProcessRequiresAbortion()))
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
// if any process exits meanwhile with an error or abort request avoid a start of this process
|
||||
if (( !processStateStruct.StartAlways && AnyProcessRequiresAbortion())
|
||||
|| processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Idle
|
||||
|| processStateStruct.ProductionProcess.SingleProcessState == SingleProcessState.Abort)
|
||||
{
|
||||
processStateStruct.ProductionProcess.AbortProcess();
|
||||
return;
|
||||
}
|
||||
|
||||
// References to commonly used production process objects
|
||||
processStateStruct.ProductionProcess.Meter = _genesisMeter;
|
||||
processStateStruct.ProductionProcess.RegisterRestorer = _registerRestorer;
|
||||
processStateStruct.ProductionProcess.RadioEncryptionKey = _radioEncryptionKey;
|
||||
processStateStruct.ProductionProcess.PwdContainer = _pwdContainer;
|
||||
processStateStruct.ProductionProcess.ProductionStatus = _productionStatus;
|
||||
processStateStruct.ProductionProcess.ProductionRequirements = _cordonelRequirements;
|
||||
processStateStruct.ProductionProcess.EolProgress = _cordonelEolProgress;
|
||||
processStateStruct.ProductionProcess.CancellationToken = _cancellationToken;
|
||||
|
||||
// kick off process execution, the breakExitState will not be used
|
||||
processStateStruct.ProductionProcess.StartProcess(processStateStruct.NextStateOnSuccess,
|
||||
processStateStruct.ErrorExitState, processStateStruct.BreakExitState);
|
||||
// connect process events to actual process
|
||||
AddProcessEvents(processStateStruct.ProductionProcess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all processes for error or abort request
|
||||
/// </summary>
|
||||
/// <returns>true if abort required</returns>
|
||||
/// <remarks date="2025-Apr-10" author="Thomas Wiedebusch">
|
||||
/// - Cancellation token.
|
||||
/// </remarks>
|
||||
private Boolean AnyProcessRequiresAbortion()
|
||||
{
|
||||
return Processes.Any(pp => pp.SingleProcessState == SingleProcessState.Error ||
|
||||
pp.SingleProcessState == SingleProcessState.Abort ||
|
||||
_cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
/// <summary>
|
||||
/// Check process if in status waiting or running (incomplete)
|
||||
/// </summary>
|
||||
/// <param name="pp">production process</param>
|
||||
/// <returns>true if process to wait for execution or exit running state</returns>
|
||||
private Boolean WaitForProcess(IProductionProcess pp)
|
||||
{
|
||||
return pp.SingleProcessState == SingleProcessState.Waiting ||
|
||||
pp.SingleProcessState == SingleProcessState.Running;
|
||||
}
|
||||
/// <summary>
|
||||
/// Install process events
|
||||
/// </summary>
|
||||
/// <param name="process"></param>
|
||||
private void AddProcessEvents(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
process.OnProcessProgressChanged += ProcessProgress_Handler;
|
||||
process.OnProcessLogRequest += ProcessLog_Handler;
|
||||
process.OnSingleProcessStateChanged += SingleProcessStateChanged_Handler;
|
||||
process.OnOverallProcessStateChanged += OverallProcessStateChanged_Handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove process events
|
||||
/// </summary>
|
||||
/// <param name="process"></param>
|
||||
private void RemoveProcessEvents(IProductionProcess process)
|
||||
{
|
||||
if (process == null)
|
||||
return;
|
||||
|
||||
process.OnProcessProgressChanged -= ProcessProgress_Handler;
|
||||
process.OnProcessLogRequest -= ProcessLog_Handler;
|
||||
process.OnSingleProcessStateChanged -= SingleProcessStateChanged_Handler;
|
||||
process.OnOverallProcessStateChanged -= OverallProcessStateChanged_Handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the actual process state of the state machine
|
||||
/// </summary>
|
||||
/// <returns>actual process state</returns>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private ProcessState GetProcessState()
|
||||
{
|
||||
return _stateMachineState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the new process state of the state machine
|
||||
/// </summary>
|
||||
/// <returns>true, if process state is set</returns>
|
||||
/// <remarks date="2023-Jan-17" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
public Boolean SetProcessState(ProcessState newProcessState)
|
||||
{
|
||||
if (newProcessState != _stateMachineState)
|
||||
{
|
||||
_stateMachineState = newProcessState;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all processes from the <see cref="Processes"/> list:
|
||||
/// - Signal change to GUI,
|
||||
/// - Remove process events all,
|
||||
/// - Set state to abort.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jul-11" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
|
||||
/// - Abort register access.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Apr-07" author="Thomas Wiedebusch">
|
||||
/// - Cancellation token source.
|
||||
/// </remarks>
|
||||
private void KillProcesses()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
Thread.Sleep(1000);
|
||||
|
||||
if (Processes == null || Processes.Count == 0)
|
||||
return;
|
||||
|
||||
// put all processes to idle and remove events of these processes
|
||||
foreach (var pp in Processes)
|
||||
{
|
||||
pp.IdleProcess();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abort all processes if one previous process failed, but leave the <see cref="Processes"/> list intact.
|
||||
/// Leave all events for those which are already running intact to force a clean stop.
|
||||
/// </summary>
|
||||
/// <remarks date="2023-Jun-21" author="Thomas Wiedebusch">
|
||||
/// - Initial
|
||||
/// </remarks>
|
||||
private void AbortProcesses()
|
||||
{
|
||||
if (Processes == null || Processes.Count == 0)
|
||||
return;
|
||||
|
||||
// Skip all processes which are not succeeded, erroneous or skipped to leave the status intact
|
||||
// and request abort for all others
|
||||
foreach (var pp in Processes.Where(pp => pp.SingleProcessState != SingleProcessState.Succeeded &&
|
||||
pp.SingleProcessState != SingleProcessState.Error &&
|
||||
pp.SingleProcessState != SingleProcessState.Skipped &&
|
||||
pp.SingleProcessState != SingleProcessState.Retry))
|
||||
{
|
||||
pp.AbortProcess();
|
||||
}
|
||||
}
|
||||
#endregion --------------------------------------- Tools ------------------------------------------------------
|
||||
}
|
||||
}
|
||||
@@ -803,6 +803,18 @@ namespace Xylem.Common.Production.ProductionUiCordonel
|
||||
// remove last userControl
|
||||
dpSubProcess.Children.Clear();
|
||||
rtbLog.Document.Blocks.Clear();
|
||||
|
||||
// clear all visual fields as it may be a new pcbId
|
||||
UpdateContentControl(lblOrderNumberValue, "?");
|
||||
UpdateContentControl(lblRadioAddressValue, "");
|
||||
UpdateContentControl(lblRadioAddressText, "");
|
||||
UpdateContentControl(lblFwValue, "?");
|
||||
UpdateContentControl(lblMeterSizeValue, "?");
|
||||
UpdateContentControl(lblLutValue, "?");
|
||||
UpdateContentControl(lblSerialNumberValue, "?");
|
||||
UpdateContentControl(lblRegionValue, "?");
|
||||
UpdateUi(lblPcbIdValue, "?");
|
||||
|
||||
_processCtrl?.SetProcessState(ProcessState.DetectCordonel);
|
||||
}
|
||||
|
||||
@@ -883,9 +895,11 @@ namespace Xylem.Common.Production.ProductionUiCordonel
|
||||
// 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
|
||||
|
||||
@@ -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