tbf/TBF/Rig/Output/DB/ProductionTracing/Tracing.cs

683 lines
26 KiB
C#
Raw Normal View History

///
/// Copyright (c) 2018-2019 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using log4net;
using NHibernate;
using NHibernate.Criterion;
using Common;
using Config.Entities;
using TracingDB.Entities;
using Events.Entities;
2021-10-26 09:23:57 +00:00
namespace TBF.Rig.Output.DB.ProductionTracing
{
public enum Retv
{
OK,
Error,
}
class DateTimeComparer : IComparer<DateTime>
{
public int Compare(DateTime x, DateTime y)
{
return DateTime.Compare(x, y);
}
}
public class Tracing : ComponentBase, IOperation, GenericDevices.IResultsWriter, GenericDevices.IStartInfoReader, Generic.IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(Tracing));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
public const string WorkstepName = "Test_bench";
MonitoringCfg tracingCfg;
string workplace
{
get
{
2021-10-26 09:23:57 +00:00
TBF.Rig.GenericDevices.IBenchInfo benchInfo = TBF.Rig.Sequences.ProcessData.BenchInfo;
return (benchInfo != null) ? benchInfo.TestBenchName : "TestBench";
}
}
IPAddress ipAddress;
IPAddress netMask;
public IPAddress IPAddress { get { return ipAddress; } }
public IPAddress NetMask { get { return netMask; } }
enum OpState
{
None,
CheckPreviousRecordsScheduled,
CheckPreviousRecordsRunning,
SaveTracingRecordsScheduled,
SaveTracingRecordsRunning,
}
///
OpState currentOpState;
bool opCompleted;
bool anyError;
/// <summary>
/// Watermeters to check at the beginning of the cycle
/// </summary>
IList<Results.Entities.WaterMeter> waterMeters;
/// <summary>
/// Data (tracing records) to write at the end of cycle
/// </summary>
Results.Entities.Batch batch;
ISessionFactory sessionFactory; /// Factory to create database sessions that is initialized in the constructor
IList<Process> processes;
Dictionary<int, Process> processDictionary;
Dictionary<int, IList<Workstep>> workstepsDictionary;
ISessionFactory sessionFactory2; /// Factory to create database sessions that is initialized in the constructor
IList<Process> processes2;
Dictionary<int, Process> processDictionary2;
Dictionary<int, IList<Workstep>> workstepsDictionary2;
///
/// Previous workstep verification info
///
Process currentProcess; /// Currently used process
Workstep currentWorkstep; /// Currently used workstep of this test bench
Workstep verifiedWorkstep;
Part verifiedPart; /// Applicable when verifyReferencePart == false
bool verifyReferencePart;
public Tracing() {}
public Tracing(Generic.IComponentCfg cfg)
: base(cfg)
{
tracingCfg = cfg as MonitoringCfg;
}
public override void Initialize()
{
Network.AdapterInfo.RefreshNetAdaptersInfo();
Network.AdapterInfo netadapter = Network.AdapterInfo.GetNetAdapter(tracingCfg.NetAdapter);
ipAddress = netadapter.IPAddress;
netMask = netadapter.NetMask;
currentProcess = null;
currentOpState = OpState.None;
if (tracingCfg.DebugLevel == DebugMode.Normal)
{
/// Session factory is used to create database sessions
sessionFactory = FluentNHibernate.Cfg.Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MySQLConfiguration.Standard.ConnectionString(tracingCfg.ConnStr))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TracingDB.Entities.Process>())
.ExposeConfiguration(TracingDB.DB.BuildSchema)
.BuildSessionFactory();
/// Session factory 2 is used to create the 2nd database sessions
if (!string.IsNullOrEmpty(tracingCfg.ConnStr2))
{
sessionFactory2 = FluentNHibernate.Cfg.Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MySQLConfiguration.Standard.ConnectionString(tracingCfg.ConnStr2))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TracingDB.Entities.Process>())
.ExposeConfiguration(TracingDB.DB.BuildSchema)
.BuildSessionFactory();
}
/// Register this test bench in the tracing DB for approx. 2 weeks
ISession session = sessionFactory.OpenSession();
#if !DEBUG
/// Only a release version registers a workplace
TracingDB.DB.RegisterWorkplace(session,
workplace,
Users.CurrentUser.UserName(),
(ipAddress != null) ? ipAddress.ToString() : "1.2.3.4",
"<multiple>",
WorkstepName,
DateTime.Now + new TimeSpan(15, 0, 0, 0));
#endif
session.Flush();
session.Close();
TracingDB.DB.SessionFactory = sessionFactory;
if (session != null) session.Dispose();
log.FatalFormat("{0} initialized: {1}", Name, this);
}
else
{
log.FatalFormat("{0} simulated: {1}", Name, this);
}
}
///
/// IDevice interface implementation
///
public void RunDeviceBefore() { }
public void RunDeviceAfter() { }
///
public void StopDevice()
{
if (tracingCfg.DebugLevel != DebugMode.Normal) return;
using (ISession session = sessionFactory.OpenSession())
{
TracingDB.DB.UnregisterWorkplaceObsolete(session, workplace);
session.Flush();
}
}
public void StopDevice2() {}
/// <summary>
/// Reads information on start of a cycle, Events: Event.InfoRead
/// </summary>
/// <param name="waterMeters">Results of water meters</param>
/// <returns>Reference to the operation</returns>
public IOperation ReadStartInfoOp(IList<Results.Entities.WaterMeter> waterMeters)
{
if (!tracingCfg.CheckPreviousRecords)
{
return null;
}
else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning))
{
throw new Exception("Sequence error");
}
else
{
this.waterMeters = waterMeters;
currentOpState = OpState.CheckPreviousRecordsScheduled;
return this;
}
}
/// <summary>
/// Writes the test cycle results into a file, Events: Event.ResultsWritten
/// </summary>
/// <param name="procedure">Procedure to print the results of</param>
/// <param name="unsortedResults">Results to write into the file</param>
/// <returns>Reference to the operation</returns>
public IOperation ProcessResultsOp(Results.Entities.Batch batch)
{
if (!tracingCfg.SaveTracingRecords)
{
return null;
}
else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning))
{
throw new Exception("Sequence error");
}
else
{
this.batch = batch;
currentOpState = OpState.SaveTracingRecordsScheduled;
return this;
}
}
/// <summary>Start this operation</summary>
public void Start()
{
if (currentOpState == OpState.CheckPreviousRecordsScheduled)
{
currentOpState = OpState.CheckPreviousRecordsRunning;
}
else if (currentOpState == OpState.SaveTracingRecordsScheduled)
{
currentOpState = OpState.SaveTracingRecordsRunning;
}
opCompleted = false;
anyError = false;
}
/// <summary>Run this operation</summary>
/// <returns>Event.ResultsWritten or Event.Error</returns>
public Event Run()
{
if (currentOpState == OpState.CheckPreviousRecordsRunning)
{
if (tracingCfg.DebugLevel == DebugMode.Simulate)
{
return Event.InfoRead;
}
else if (!opCompleted)
{
log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState);
opCompleted = true;
if (CheckPreviousRecords(waterMeters) != Retv.OK) anyError = true;
return anyError ? Event.InfoNotRead : Event.InfoRead;
}
else
{
return anyError ? Event.InfoNotRead : Event.InfoRead;
}
}
else if (currentOpState == OpState.SaveTracingRecordsRunning)
{
if (tracingCfg.DebugLevel == DebugMode.Simulate || batch.WaterMeters.Count == 0)
{
return Event.ResultsWritten;
}
else if (!opCompleted)
{
log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState);
opCompleted = true;
if (SaveTracingRecords(batch) != Retv.OK) anyError = true;
if (anyError && !string.IsNullOrEmpty(Events.DB.ConnectionString))
{
try
{
NHibernate.ISession session = Events.DB.CreateSession();
Events.DB.LoadSubscribers(session);
TBF.UiBridge.Bridge.TriggerEvent(session, Name, EventClass.ComputerResources, Severity.Error,
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB: Číslo dávky={0}", batch.BatchNr),
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB\r\nČíslo dávky = {0}", batch.BatchNr),
SubscriberGroup.Metrology | SubscriberGroup.Maintenance | SubscriberGroup.Production);
}
catch (Exception exc)
{
log.ErrorFormat("Triggering event(s) failed: {0}", exc.Message);
}
}
return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
}
else
{
if (anyError && !string.IsNullOrEmpty(Events.DB.ConnectionString))
{
try
{
NHibernate.ISession session = Events.DB.CreateSession();
Events.DB.LoadSubscribers(session);
TBF.UiBridge.Bridge.TriggerEvent(session, Name, EventClass.ComputerResources, Severity.Error,
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB: Číslo dávky={0}", batch.BatchNr),
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB\r\nČíslo dávky = {0}", batch.BatchNr),
SubscriberGroup.Metrology | SubscriberGroup.Maintenance | SubscriberGroup.Production);
}
catch (Exception exc)
{
log.ErrorFormat("Triggering event(s) failed: {0}", exc.Message);
}
}
return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
}
}
else
{
return Event.None;
}
}
/// <summary>Stop this operation</summary>
public void Stop()
{
currentOpState = OpState.None;
}
Retv CheckPreviousRecords(IList<Results.Entities.WaterMeter> waterMeters)
{
Retv retVal = Retv.Error;
bool isAtLeastOneNOK = false;
using (ISession session = sessionFactory.OpenSession())
{
for (int i = 0; i < waterMeters.Count; i++)
{
Results.Entities.WaterMeter wm = waterMeters[i];
if ((wm != null) && !wm.Disabled)
{
CheckPreviousRecordOfSingleWM(session, wm, 1);
if (wm.LastRecordIsNok)
{
isAtLeastOneNOK = true;
}
}
}
retVal = Retv.OK;
}
if (isAtLeastOneNOK && (sessionFactory2 != null))
{
using (ISession session2 = sessionFactory2.OpenSession())
{
for (int i = 0; i < waterMeters.Count; i++)
{
Results.Entities.WaterMeter wm = waterMeters[i];
if ((wm != null) && !wm.Disabled && wm.LastRecordIsNok)
{
CheckPreviousRecordOfSingleWM(session2, wm, 2);
}
}
retVal = Retv.OK;
}
}
return retVal;
}
Retv CheckPreviousRecordOfSingleWM(ISession session, Results.Entities.WaterMeter wm, int sessionId)
{
if (string.IsNullOrEmpty(wm.SerialNr))
{
/// S/N is missing
wm.ProcessId = 0;
wm.SessionId = 0;
wm.LastRecordIsNok = false; /// OK, this is an RFID comm. error, not a production tracing error
return Retv.OK;
}
///
/// Get all already existing reference records with Code == wm.SerialNr, refRecords[0] will be the most recent one
///
ReferenceRecord refRecord = null;
Workstep workstep = null;
ProjectionList projections = Projections.ProjectionList();
projections.Add(Projections.Property(() => refRecord.Id)); /// rslt[0] = reference record ID
projections.Add(Projections.Property(() => workstep.Id)); /// rslt[1] = workstep ID
projections.Add(Projections.Property(() => refRecord.Process.Id)); /// rslt[2] = process ID
projections.Add(Projections.Property(() => refRecord.TimeStamp)); /// rslt[3] = reference record time stamp
///
/// Get all reference records with Code == SerialNr from all worksteps different from 'Test_bench'
///
IList<object[]> results = session.QueryOver<ReferenceRecord>(() => refRecord)
.Where(rr => (rr.Code == wm.SerialNr))
.JoinQueryOver<Workstep>(rr => rr.Workstep, () => workstep)
.And(ws => (ws.Name != WorkstepName))
.Select(projections)
.List<object[]>();
if (results.Count == 0)
{
/// No records found
wm.ProcessId = 0;
wm.SessionId = sessionId;
wm.LastRecordIsNok = true; /// NOK
///
return Retv.OK;
}
///
/// Get processId of the last record
///
int processId = 0;
DateTime lastTimeStamp = DateTime.MinValue;
foreach (var rslt in results)
{
if (DateTime.Compare((DateTime)rslt[3], lastTimeStamp) > 0)
{
lastTimeStamp = (DateTime)rslt[3];
processId = (int)rslt[2];
}
}
if (processId == 0) return Retv.Error; /// This should never happen
if ((currentProcess == null) || (currentProcess.Id != processId))
{
///
/// Process changed ==> update currentProcess / currentWorkstep / verifiedWorkstep / verifiedPart / verifyReferencePart
///
var processes = session.QueryOver<Process>()
.Where(p => (p.Id == processId))
.List();
if (processes.Count != 1) return Retv.Error;
IList<Workstep> worksteps = session.QueryOver<Workstep>()
.Where(ws => (ws.Process == processes[0]))
.And( ws => (ws.Name == WorkstepName))
.List();
if (worksteps.Count != 1) return Retv.Error;
TracingDB.ScanVerificationInfo info;
///
if (null == (info = TracingDB.DB.AnalyzeProcess(session, processes[0], worksteps[0])))
{
return Retv.Error; /// Unable to
}
///
currentProcess = processes[0];
currentWorkstep = worksteps[0];
verifiedWorkstep = info.Workstep;
verifiedPart = info.Part;
verifyReferencePart = info.VerifyReferencePart;
}
wm.ProcessId = currentProcess.Id;
wm.SessionId = sessionId;
wm.LastRecordIsNok = true; /// NOK (=default if no matching record found)
foreach (var rslt in results)
{
if ((verifiedWorkstep.Id == (int)rslt[1]) && (processId == (int)rslt[2]))
{
/// This record fits verification requirements
wm.LastRecordIsNok = false; /// OK
break;
}
}
return Retv.OK;
}
/// <summary>
/// Write results of a batch of water meters to the DB
/// </summary>
/// <param name="session">DB session</param>
/// <param name="batch">Batch entity</param>
Retv SaveTracingRecords(Results.Entities.Batch batch)
{
ITransaction transaction = null;
ISession session = null;
///
/// Save to regular tracing DB
///
try
{
session = sessionFactory.OpenSession();
transaction = session.BeginTransaction();
int writtenRecordsCount = 0;
foreach (var wMtr in batch.WaterMeters)
{
if (wMtr.SessionId == 1)
{
writtenRecordsCount += SaveSingleWM2DB(session, wMtr);
}
}
transaction.Commit();
session.Flush();
log.ErrorFormat("Batch {0}: {1} of {2} watermeters written to the regular production tracing DB",
batch.BatchNr, writtenRecordsCount, batch.WaterMeters.Count);
}
catch (Exception exc)
{
if (transaction != null && !transaction.WasCommitted) transaction.Rollback();
log.WarnFormat("Failed to write results of batch {0} to production tracing DB: {1}",
batch.BatchNr, exc.Message);
return Retv.Error;
}
finally
{
if (session != null)
{
session.Close();
session.Dispose();
}
}
if (sessionFactory2 != null)
{
///
/// Save to alternative tracing DB
///
transaction = null;
session = null;
try
{
session = sessionFactory2.OpenSession();
transaction = session.BeginTransaction();
int writtenRecordsCount = 0;
foreach (var wMtr in batch.WaterMeters)
{
if (wMtr.SessionId == 2)
{
writtenRecordsCount += SaveSingleWM2DB(session, wMtr);
}
}
transaction.Commit();
session.Flush();
log.ErrorFormat("Batch {0}: {1} of {2} watermeters written to the alternative production tracing DB",
batch.BatchNr, writtenRecordsCount, batch.WaterMeters.Count);
}
catch (Exception exc)
{
if (transaction != null && !transaction.WasCommitted) transaction.Rollback();
log.WarnFormat("Failed to write results of batch {0} to production tracing DB: {1}",
batch.BatchNr, exc.Message);
return Retv.Error;
}
finally
{
if (session != null)
{
session.Close();
session.Dispose();
}
}
}
return Retv.OK;
}
/// <summary>
/// Write one meter results to the DB
/// </summary>
/// <param name="session">DB session</param>
/// <param name="wm">Watermeter entity</param>
/// <returns>Number of written reference record</returns>
int SaveSingleWM2DB(ISession session, Results.Entities.WaterMeter wm)
{
if (string.IsNullOrEmpty(wm.SerialNr) || (wm.ProcessId == 0))
{
/// Without PCB number there is no DB activity
return 0;
}
else if ((currentProcess != null) && (wm.ProcessId == currentProcess.Id))
{
/// Process is already loaded => save a reference record for this WM
session.SaveOrUpdate(new ReferenceRecord(currentProcess, currentWorkstep, wm.SerialNr, Users.CurrentUser.UserName(), workplace, wm.Passed ? 0 : 1));
return 1;
}
else if (wm.ProcessId != 0)
{
/// Process ID is already known, however process and workstep have to be loaded
IList<Process> processes = session.QueryOver<Process>()
.Where(x => (x.Id == wm.ProcessId))
.List();
if (processes.Count != 1) return 0;
foreach (var ws in processes[0].Worksteps)
{
if (ws.Name == WorkstepName)
{
session.SaveOrUpdate(new ReferenceRecord(processes[0], ws, wm.SerialNr, Users.CurrentUser.UserName(), workplace, wm.Passed ? 0 : 1));
return 1;
}
}
return 0; /// No workste with 'WorkstepName' found
}
else
{
///
/// Process ID is unknown => do everything from scratch
///
/// Get all already existing reference records with Code == wm.SerialNr, refRecords[0] will be the most recent one
IList<ReferenceRecord> refRecords;
int trialsCount = 0;
do
{
if (trialsCount > 0) ReadProcessesFromDB(session);
refRecords = session.QueryOver<ReferenceRecord>()
.Where(rr => (rr.Code == wm.SerialNr))
.OrderBy(rr => rr.TimeStamp).Desc
.List();
trialsCount++;
}
while (refRecords.Count == 0 && trialsCount <= 2);
/// Save a new reference record from this test bench if workstep "Test_bench" is defined in the obtained WM process
IList<Workstep> worksteps;
if ((refRecords.Count > 0) && workstepsDictionary.TryGetValue(refRecords[0].Process.Id, out worksteps) && (worksteps.Count == 1))
{
session.SaveOrUpdate(new ReferenceRecord(refRecords[0].Process, worksteps[0], wm.SerialNr, Users.CurrentUser.UserName(), workplace, wm.Passed ? 0 : 1));
return 1;
}
return 0;
}
}
/// <summary>
/// Updates processes, processDictionary and workstepsDictionary
/// </summary>
/// <param name="session">DB session</param>
void ReadProcessesFromDB(ISession session)
{
processes = session.QueryOver<Process>()
.List();
processDictionary = new Dictionary<int, Process>();
workstepsDictionary = new Dictionary<int, IList<Workstep>>();
///
foreach (var pr in processes)
{
IList<Workstep> worksteps = session.QueryOver<Workstep>()
.Where(ws => ((ws.Process == pr) && (ws.Name == WorkstepName)))
.List();
processDictionary.Add(pr.Id, pr);
workstepsDictionary.Add(pr.Id, worksteps);
}
}
}
}