///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using log4net;
using Common;
using System.Text;
namespace TBF.Rig.Output.FlexFlow
{
public class FlexFlow : ComponentBase, IOperation, GenericDevices.IResultsWriter, GenericDevices.IStartInfoReader, Generic.IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(FlexFlow));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
FlexFlowCfg flexFlowCfg;
public string StationName
{
get
{
var benchInfo = TBF.Rig.Sequences.ProcessData.BenchInfo;
return (benchInfo != null) ? benchInfo.TestBenchName : "TestBench";
}
}
public string UserId { get { return Users.CurrentUser.UserName(); } }
enum Retv
{
OK,
Error,
}
enum OpState
{
None,
GetUnitInfosScheduled,
GetUnitInfosRunning,
SaveResultsScheduled,
SaveResultsRunning,
}
///
OpState currentOpState;
bool opCompleted;
bool anyError;
///
/// Watermeters to check at the beginning of the cycle
///
IList waterMeters;
///
/// Data (tracing records) to write at the end of cycle
///
Results.Entities.Batch batch;
DateTime resultsTimeStamp;
string resultsTimeStampStr;
public FlexFlow() {}
public FlexFlow(Generic.IComponentCfg cfg)
: base(cfg)
{
flexFlowCfg = cfg as FlexFlowCfg;
}
public override void Initialize()
{
currentOpState = OpState.None;
resultsTimeStampStr = string.Empty;
if (flexFlowCfg.DebugLevel == DebugMode.Normal)
{
/// TODO: Initialize FlexFlow
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 (flexFlowCfg.DebugLevel != DebugMode.Normal) return;
/// TODO: Close FlexFlow
}
public void StopDevice2() {}
///
/// Reads information on start of a cycle, Events: Event.InfoRead
///
/// Results of water meters
/// Reference to the operation
public IOperation ReadStartInfoOp(IList waterMeters)
{
if (!flexFlowCfg.CheckPreviousRecords)
{
return null;
}
else if ((currentOpState == OpState.GetUnitInfosRunning) || (currentOpState == OpState.SaveResultsRunning))
{
throw new Exception("Sequence error");
}
else
{
this.waterMeters = waterMeters;
currentOpState = OpState.GetUnitInfosScheduled;
return this;
}
}
///
/// Writes the test cycle results into a file, Events: Event.ResultsWritten
///
/// Procedure to print the results of
/// Results to write into the file
/// Reference to the operation
public IOperation ProcessResultsOp(Results.Entities.Batch batch)
{
if (!flexFlowCfg.SaveResults)
{
log.WarnFormat("{0} saving results disabled", Name);
return null;
}
else if ((currentOpState == OpState.GetUnitInfosRunning) || (currentOpState == OpState.SaveResultsRunning))
{
log.WarnFormat("{0} sequence error", Name);
throw new Exception("Sequence error");
}
else
{
this.batch = batch;
this.resultsTimeStamp = batch.EndTime;
this.resultsTimeStampStr = batch.EndTime.ToString("yyyy-MM-dd HH:mm:ss");
currentOpState = OpState.SaveResultsScheduled;
return this;
}
}
/// Start this operation
public void Start()
{
var oriOpState = currentOpState;
if (currentOpState == OpState.GetUnitInfosScheduled)
{
currentOpState = OpState.GetUnitInfosRunning;
}
else if (currentOpState == OpState.SaveResultsScheduled)
{
currentOpState = OpState.SaveResultsRunning;
}
opCompleted = false;
anyError = false;
log.WarnFormat("{0} Start() completed, op.state {1} --> {2}", Name, oriOpState, currentOpState);
}
/// Run this operation
/// Event.ResultsWritten or Event.Error
public Event Run()
{
var oriOpState = currentOpState;
if (currentOpState == OpState.GetUnitInfosRunning)
{
if (flexFlowCfg.DebugLevel == DebugMode.Simulate)
{
return Event.InfoRead;
}
else if (!opCompleted)
{
log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState);
opCompleted = true;
if (GetUnitInfos(waterMeters) != Retv.OK)
{
anyError = true;
}
var retval = anyError ? Event.InfoNotRead : Event.InfoRead;
log.WarnFormat("{0} Run() returned {1}, op.state {2} --> {3}", Name, retval, oriOpState, currentOpState);
return retval;
}
else
{
var retval = anyError ? Event.InfoNotRead : Event.InfoRead;
log.WarnFormat("{0} Run() returned {1}, op.state {2} --> {3}", Name, retval, oriOpState, currentOpState);
return retval;
}
}
else if (currentOpState == OpState.SaveResultsRunning)
{
if (flexFlowCfg.DebugLevel == DebugMode.Simulate || batch.WaterMeters.Count == 0)
{
return Event.ResultsWritten;
}
else if (!opCompleted)
{
opCompleted = true;
if (SaveResults(batch) != Retv.OK)
{
anyError = true;
}
var retval = anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
log.WarnFormat("{0} Run() returned {1}, op.state {2} --> {3}", Name, retval, oriOpState, currentOpState);
return retval;
}
else
{
var retval = anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
log.WarnFormat("{0} Run() returned {1}, op.state {2} --> {3}", Name, retval, oriOpState, currentOpState);
return retval;
}
}
else
{
log.WarnFormat("{0} Run() returned {1}, op.state {2} --> {3}", Name, Event.None, oriOpState, currentOpState);
return Event.None;
}
}
/// Stop this operation
public void Stop()
{
var oriOpState = currentOpState;
currentOpState = OpState.None;
log.WarnFormat("{0} Stop() completed, op.state {1} --> {2}", Name, oriOpState, currentOpState);
}
Retv GetUnitInfos(IList waterMeters)
{
bool allOK = true;
foreach (var wm in waterMeters)
{
if (wm != null && !wm.Disabled)
{
GetOneUnitInfo(wm, (flexFlowCfg.GetUnitInfoApi == WebApi.REST));
if (wm.LastRecordIsNok)
{
allOK = false;
}
}
}
return allOK ? Retv.OK : Retv.Error;
}
Retv GetOneUnitInfo(Results.Entities.WaterMeter wm, bool isRest)
{
string serviceName = isRest ? "GetUnitInfo REST" : "GetUnitInfo SOAP"; /// For logging only
string url = flexFlowCfg.BaseUrl + flexFlowCfg.GetUnitInfoUrl; /// Complete URL from base URL and relative URL
string localUserId = (!flexFlowCfg.SendUserId || string.IsNullOrEmpty(UserId)) ? "admin" : UserId;
log.DebugFormat("{0} URL is {1}", serviceName, url);
if (string.IsNullOrEmpty(wm.SerialNr))
{
/// S/N is missing
wm.LastRecordIsNok = false; /// OK, this is an RFID comm. error, not a production tracing error
log.DebugFormat("{0} : pcbNr is missing", serviceName);
return Retv.OK;
}
string request;
if (isRest)
{
/// JSON request formatting
request = string.Format("{{{0}\"StationName\": \"{1}\",{0}\"UserID\": \"{2}\",{0}\"SerialNumber\": \"{3}\",{0}}}",
string.Empty, //Environment.NewLine,
StationName,
localUserId,
wm.SerialNr);
}
else
{
/// TODO: Implement XML request formatting
request = string.Format("{{{0}\"StationName\": \"{1}\",{0}\"UserID\": \"{2}\",{0}\"SerialNumber\": \"{3}\",{0}}}",
string.Empty, //Environment.NewLine,
StationName,
localUserId,
wm.SerialNr);
}
log.DebugFormat("{0} request (pcbNr = {1}):\r\n{2}", serviceName, wm.SerialNr, request);
/// Object to be updated when parsing the response
UnitInfoData data = new UnitInfoData(wm.SerialNr, StationName, localUserId);
try
{
var webRequest = isRest ? CreateRestHttpWebRequest(url) : CreateSoapHttpWebRequest(url);
using (var streamWriter = new StreamWriter(webRequest.GetRequestStream()))
{
streamWriter.Write(request);
}
using (var streamReader = new StreamReader(webRequest.GetResponse().GetResponseStream()))
{
var response = streamReader.ReadToEnd();
log.DebugFormat("{0} response (pcbNr={1}):\r\n{2}", serviceName, wm.SerialNr, response);
if (isRest)
{
/// JSON response parsing
string pattern = "\"id\":";
int position = response.IndexOf(pattern) + pattern.Length;
int length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
int idummy;
if (int.TryParse(response.Substring(position, length).Trim(), out idummy))
{
data.Id = idummy;
}
pattern = "\"value\":";
position = response.IndexOf(pattern) + pattern.Length;
length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
data.Value = response.Substring(position, length);
pattern = "\"ffUnitId\":";
position = response.IndexOf(pattern) + pattern.Length;
length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
long ldummy;
if (long.TryParse(response.Substring(position, length).Trim(), out ldummy))
{
data.FFUnitId = ldummy;
}
pattern = "\"ffTestId\":";
position = response.IndexOf(pattern) + pattern.Length;
length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
if (long.TryParse(response.Substring(position, length).Trim(), out ldummy))
{
data.FFTestId = ldummy;
}
pattern = "\"ffPartId\":";
position = response.IndexOf(pattern) + pattern.Length;
length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
if (long.TryParse(response.Substring(position, length).Trim(), out ldummy))
{
data.FFPartId = ldummy;
}
}
else
{
///
}
}
}
catch (Exception exc)
{
log.ErrorFormat("{0} failed: {1}", serviceName, exc.Message);
}
return (data != null && data.Id == 0) ? Retv.OK : Retv.Error;
}
///
/// Write results of a batch of water meters to the DB
///
/// Batch entity
Retv SaveResults(Results.Entities.Batch batch)
{
try
{
int writtenRecordsCount = 0;
foreach (var wMtr in batch.WaterMeters)
{
writtenRecordsCount += SaveOneUnitResult(wMtr, (flexFlowCfg.SaveResultsApi == WebApi.REST));
}
log.ErrorFormat("Batch {0}: {1} of {2} watermeters written to the regular production tracing DB",
batch.BatchNr, writtenRecordsCount, batch.WaterMeters.Count);
}
catch (Exception exc)
{
log.WarnFormat("Failed to write results of batch {0} to production tracing DB: {1}",
batch.BatchNr, exc.Message);
return Retv.Error;
}
return Retv.OK;
}
///
/// Write one meter results to the DB
///
/// Watermeter entity
/// Number of written water meters (0 or 1)
int SaveOneUnitResult(Results.Entities.WaterMeter wm, bool isRest)
{
string serviceName = isRest ? "SaveTestResult REST" : "SaveTestResult SOAP"; /// For logging only
string url = flexFlowCfg.BaseUrl + flexFlowCfg.SaveResultsUrl; /// Complete URL from base URL and relative URL
string localUserId = (!flexFlowCfg.SendUserId || string.IsNullOrEmpty(UserId)) ? "admin" : UserId;
log.DebugFormat("{0} URL is {1}", serviceName, url);
if (string.IsNullOrEmpty(wm.SerialNr))
{
/// S/N is missing
log.DebugFormat("{0} : pcbNr is missing", serviceName);
return 0;
}
///
/// Request formatting
///
string request;
if (isRest)
{
/// JSON request formatting
string status = wm.Passed ? "Passed" : "Failed";
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("{{"));
sb.AppendLine(string.Format(" \"timestamp\": \"{0:yyyy-MM-dd HH:mm:ss}\",", DateTime.Now));
sb.AppendLine(string.Format(" \"syntaxRev\": \"1.1\","));
sb.AppendLine(string.Format(" \"compatibleRev\": \"1.1\","));
sb.AppendLine(string.Format(" \"factory\": {{"));
sb.AppendLine(string.Format(" \"name\": \"{0}\",", "Flex Chennai"));
sb.AppendLine(string.Format(" \"line\": \"\","));
sb.AppendLine(string.Format(" \"tester\": \"{0}\",", StationName));
sb.AppendLine(string.Format(" \"user\": \"{0}\"", localUserId));
sb.AppendLine(string.Format(" }},"));
sb.AppendLine(string.Format(" \"product\": {{"));
sb.AppendLine(string.Format(" \"name\": \"{0}\",", "iPERL"));
sb.AppendLine(string.Format(" \"revision\": \"{0}\"", "S4"));
sb.AppendLine(string.Format(" }},"));
sb.AppendLine(string.Format(" \"panel\": {{"));
sb.AppendLine(string.Format(" \"id\": \"{0}\",", wm.SerialNr));
sb.AppendLine(string.Format(" \"comment\": \"\","));
sb.AppendLine(string.Format(" \"runmode\": \"{0}\",", "Production"));
sb.AppendLine(string.Format(" \"timestamp\": \"{0:yyyy-MM-dd HH:mm:ss}\",", DateTime.Now));
sb.AppendLine(string.Format(" \"testTime\": \"{0}\",", 0));
sb.AppendLine(string.Format(" \"status\": \"{0}\",", status));
sb.AppendLine(string.Format(" \"dut\": [{{"));
sb.AppendLine(string.Format(" \"id\": \"{0}\",", wm.SerialNr));
sb.AppendLine(string.Format(" \"comment\": \"\","));
sb.AppendLine(string.Format(" \"panel\": \"{0}\",", 1));
sb.AppendLine(string.Format(" \"socket\": \"{0}\",", 0));
sb.AppendLine(string.Format(" \"timestamp\": \"{0:yyyy-MM-dd HH:mm:ss}\",", resultsTimeStamp));
sb.AppendLine(string.Format(" \"runmode\": \"{0}\",", "Production"));
sb.AppendLine(string.Format(" \"testTime\": \"{0}\",", (wm.Batch.EndTime - wm.Batch.StartTime).TotalSeconds.ToString("F0")));
sb.AppendLine(string.Format(" \"status\": \"{0}\",", status));
sb.AppendLine(string.Format(" \"group\": {{"));
sb.AppendLine(string.Format(" \"name\": \"{0}\",", "iPERL"));
sb.AppendLine(string.Format(" \"groupIndex\": \"{0}\",", 0));
sb.AppendLine(string.Format(" \"loopIndex\": \"{0}\",", 0));
sb.AppendLine(string.Format(" \"type\": \"{0}\",", "SequenceCall"));
sb.AppendLine(string.Format(" \"moduleTime\": \"{0}\",", 0));
sb.AppendLine(string.Format(" \"totalTime\": \"{0}\",", (wm.Batch.EndTime - wm.Batch.StartTime).TotalSeconds.ToString("F0")));
sb.AppendLine(string.Format(" \"timestamp\": \"{0:yyyy-MM-dd HH:mm:ss}\",", resultsTimeStamp));
sb.AppendLine(string.Format(" \"status\": \"{0}\"", status));
sb.AppendLine(string.Format(" }}"));
sb.AppendLine(string.Format(" }}]"));
sb.AppendLine(string.Format(" }}"));
sb.AppendLine(string.Format("}}"));
request = sb.ToString();
}
else
{
/// XML request formatting
string status = wm.Passed ? "PASS" : "FAIL";
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("", resultsTimeStampStr));
sb.AppendLine(string.Format(" {0}", resultsTimeStampStr));
sb.AppendLine(string.Format(" "));
sb.AppendLine(string.Format(" "));
sb.AppendLine(string.Format(" ", wm.SerialNr, resultsTimeStampStr, status));
sb.AppendLine(string.Format(" ", wm.SerialNr, resultsTimeStampStr, status));
sb.AppendLine(string.Format(" ", wm.SerialNr, resultsTimeStampStr, status));
sb.AppendLine(string.Format(" ", status));
sb.AppendLine(string.Format(" "));
sb.AppendLine(string.Format(" "));
sb.AppendLine(string.Format(" "));
sb.AppendLine(string.Format(""));
request = sb.ToString();
}
log.DebugFormat("{0} request (pcbNr = {1}):\r\n{2}", serviceName, wm.SerialNr, request);
/// Object to be updated when parsing the response
ResultsData data = new ResultsData(wm.SerialNr, StationName, localUserId);
try
{
var webRequest = isRest ? CreateRestHttpWebRequest(url) : CreateSoapHttpWebRequest(url);
using (var streamWriter = new StreamWriter(webRequest.GetRequestStream()))
{
streamWriter.Write(request);
}
using (var streamReader = new StreamReader(webRequest.GetResponse().GetResponseStream()))
{
var response = streamReader.ReadToEnd();
log.DebugFormat("{0} response (pcbNr={1}):\r\n{2}", serviceName, wm.SerialNr, response);
if (isRest)
{
/// JSON response parsing
string pattern = "\"id\":";
int position = response.IndexOf(pattern) + pattern.Length;
int length = response.Substring(position).IndexOfAny(new char[] { ',', '}', '\r', '\n' });
int idummy;
if (int.TryParse(response.Substring(position, length).Trim(), out idummy))
{
data.Id = idummy;
}
}
else
{
/// TODO: XML resonse parsing
}
}
}
catch (Exception exc)
{
log.ErrorFormat("{0} failed: {1}", serviceName, exc.Message);
}
return (data != null && data.Id == 0) ? 1 : 0;
}
public HttpWebRequest CreateRestHttpWebRequest(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = "application/json";
request.Method = "POST";
return request;
}
public HttpWebRequest CreateSoapHttpWebRequest(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.Headers.Add(@"SOAPAction:http://tempuri.org/Addition");
request.ContentType = "application/xml";
request.Accept = "text/xml";
request.Method = "POST";
return request;
}
}
}