/// /// 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; } } }