diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index 37fc9cb74..5e3cce197 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("3.9.3145.0")] -[assembly: AssemblyFileVersion("3.9.3145.0")] +[assembly: AssemblyVersion("3.9.3147.0")] +[assembly: AssemblyFileVersion("3.9.314.0")] diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs index d7566ca3f..637ce8157 100644 --- a/TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyMeterReader.cs @@ -38,6 +38,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader private readonly List opticalSamples = new List(); private readonly Queue pendingOpticalLines = new Queue(); + private readonly AllyOpticalDiagnosticWriter opticalDiagnosticWriter = new AllyOpticalDiagnosticWriter(); private readonly AllyReaderCfg allyCfg; private IAllyTransport commandTransport; private AllyCommandService commandService; @@ -794,6 +795,29 @@ namespace TBF.Rig.RegisterReaders.AllyReader }); } + private void CaptureOpticalDiagnostic(string line, DateTime receivedUtc, string status, + AllyOpticalSample sample, AllyOpticalTelegramRaw telegram) + { + if (allyCfg == null || !allyCfg.EnableDetailedLogPerWM) return; + opticalDiagnosticWriter.Write(true, allyCfg.OpticalDiagnosticFormat, + allyCfg.RawOpticalDataFormat, allyCfg.IncludeParsedValues, + Sequences.ProcessData.BatchNr, Position > 0 ? Position.ToString() : Name, + Name, SerialNr, receivedUtc, "OpticalText/0x" + ((byte)activeOpticalOutputMode).ToString("X2"), + status, Encoding.ASCII.GetBytes(line), line, + telegram == null ? (object)null : telegram.Counter, + sample == null ? (object)null : sample.Sequence, + sample == null ? (object)null : sample.RawFlow, + sample == null ? (object)null : sample.RawVolume, + telegram == null ? (object)null : telegram.VolumeRawExtended, + telegram == null ? (object)null : telegram.VolumeDeltaRaw, + telegram == null ? (object)null : sample.ExtendedVolumeLiters, + null, // Meter time is not supplied by the ALLY C6 payload. + telegram == null ? (object)null : telegram.ElapsedSeconds, + telegram == null ? status : telegram.Flags.ToString(), + sample == null ? (object)null : sample.IsEmptyPipe, + sample == null ? (object)null : sample.IsFastHptc); + } + private void ProcessOpticalText(string text) { ProcessOpticalText(text, DateTime.UtcNow); @@ -831,6 +855,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader // either format as the known C2 layout. if (activeOpticalOutputMode != AllyOpticalOutputMode.LegacyC2) { + CaptureOpticalDiagnostic(line, receivedAtUtc, "UnparsedMode", null, null); allyLog.InfoFormat( "ALLY_OPTO RAW CANDIDATE; mode=0x{0:X2}; port=COM{1}; bytes={2}; ascii='{3}'; hex={4}", (byte)activeOpticalOutputMode, @@ -844,6 +869,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader AllyOpticalSample sample; if (!AllyOpticalSample.TryParse(line, receivedAtUtc, out sample)) { + CaptureOpticalDiagnostic(line, receivedAtUtc, "ParseRejected", null, null); if (AllyOpticalSample.IsMetrologyPacket(line)) { rejectedOpticalTelegramCount++; @@ -875,6 +901,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader uint previousRawVolumeBefore = previousRawVolume; if (!TryExtendRawVolume(sample.RawVolume, sample.RawFlow, out volumeDeltaRaw, out volumeRollover)) { + CaptureOpticalDiagnostic(line, receivedAtUtc, "BackwardAccumulatorRejected", sample, null); rejectedOpticalTelegramCount++; allyLog.WarnFormat( "ALLY_OPTO VOLUME REJECTED; telegram=#{0}; port=COM{1}; sequence=0x{2:X2}; rawVolume=0x{3:X8}; rawFlow={4}; previousRawVolume=0x{5:X8}; reason=backward-accumulator", @@ -937,7 +964,9 @@ namespace TBF.Rig.RegisterReaders.AllyReader allyLog.InfoFormat("ALLY_OPTO TEST START; telegram=#{0}; volumeL={1:F6}; elapsedHostS={2:F3}", rawTelegram.Counter, rawTelegram.VolumeLiters, rawTelegram.ElapsedSeconds); } - allyLog.Info(rawTelegram.ToLogRecord()); + CaptureOpticalDiagnostic(line, receivedAtUtc, "Accepted", sample, rawTelegram); + if (allyCfg == null || !allyCfg.EnableDetailedLogPerWM) + allyLog.Info(rawTelegram.ToLogRecord()); log.DebugFormat( "ALLY_OPTO parsed COM{0}: sequence=0x{1:X2}, rawVolume=0x{2:X8}, flow={3}, volume={4:F6} l, elapsed={5:F3} s, emptyPipe={6}, fastHptc={7}", diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs index b8a6c78bc..3f9849a07 100644 --- a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfg.cs @@ -17,6 +17,10 @@ namespace TBF.Rig.RegisterReaders.AllyReader public int OptoBaudRate; public AllyMeterSize ConfiguredMeterSize; public double MeterPulsesPerLiter; + public bool EnableDetailedLogPerWM; + public string OpticalDiagnosticFormat; + public string RawOpticalDataFormat; + public bool IncludeParsedValues; private AllyReaderCfg() { @@ -50,6 +54,10 @@ namespace TBF.Rig.RegisterReaders.AllyReader OptoBaudRate = 9600; ConfiguredMeterSize = AllyMeterSize.AutoDetect; MeterPulsesPerLiter = 1000D; + EnableDetailedLogPerWM = false; + OpticalDiagnosticFormat = "Csv"; + RawOpticalDataFormat = "Hex"; + IncludeParsedValues = true; } public int ParamsCount() @@ -185,6 +193,10 @@ namespace TBF.Rig.RegisterReaders.AllyReader OptoBaudRate = OptoBaudRate, ConfiguredMeterSize = ConfiguredMeterSize, MeterPulsesPerLiter = MeterPulsesPerLiter + ,EnableDetailedLogPerWM = EnableDetailedLogPerWM + ,OpticalDiagnosticFormat = OpticalDiagnosticFormat + ,RawOpticalDataFormat = RawOpticalDataFormat + ,IncludeParsedValues = IncludeParsedValues }; } } diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.Designer.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.Designer.cs index 635400b0e..4cc7441c6 100644 --- a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.Designer.cs +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.Designer.cs @@ -6,7 +6,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } private void InitializeComponent() { - tabControl1 = new System.Windows.Forms.TabControl(); tabPage1 = new System.Windows.Forms.TabPage(); tabPage2 = new System.Windows.Forms.TabPage(); + tabControl1 = new System.Windows.Forms.TabControl(); tabPage1 = new System.Windows.Forms.TabPage(); tabPage2 = new System.Windows.Forms.TabPage(); tabPage3 = new System.Windows.Forms.TabPage(); logsCfgCtrl = new AllyReaderLogsCfgCtrl(); classNameLabel = new System.Windows.Forms.Label(); nameLabel = new System.Windows.Forms.Label(); nameTextBox = new System.Windows.Forms.TextBox(); muxBoardNrLabel = new System.Windows.Forms.Label(); muxBoardNrTextBox = new System.Windows.Forms.TextBox(); label3 = new System.Windows.Forms.Label(); groupLabel = new System.Windows.Forms.Label(); groupTextBox = new System.Windows.Forms.TextBox(); label4 = new System.Windows.Forms.Label(); @@ -15,7 +15,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader groupBox1 = new System.Windows.Forms.GroupBox(); label1 = new System.Windows.Forms.Label(); comboBoxCommunicationInterface = new System.Windows.Forms.ComboBox(); rfidSerialPortNrLabel = new System.Windows.Forms.Label(); rfidPortNrTextBox = new System.Windows.Forms.TextBox(); groupBox2 = new System.Windows.Forms.GroupBox(); label2 = new System.Windows.Forms.Label(); headPortNrTextBox = new System.Windows.Forms.TextBox(); tabControl1.SuspendLayout(); tabPage1.SuspendLayout(); optoDataGroupBox.SuspendLayout(); groupBox1.SuspendLayout(); groupBox2.SuspendLayout(); SuspendLayout(); - tabControl1.Controls.Add(tabPage1); tabControl1.Controls.Add(tabPage2); tabControl1.Location = new System.Drawing.Point(3,3); tabControl1.Name="tabControl1"; tabControl1.SelectedIndex=0; tabControl1.Size=new System.Drawing.Size(611,432); + tabControl1.Controls.Add(tabPage1); tabControl1.Controls.Add(tabPage2); tabControl1.Controls.Add(tabPage3); tabControl1.Location = new System.Drawing.Point(3,3); tabControl1.Name="tabControl1"; tabControl1.SelectedIndex=0; tabControl1.Size=new System.Drawing.Size(611,432); tabPage1.Controls.Add(groupBox2); tabPage1.Controls.Add(label4); tabPage1.Controls.Add(label3); tabPage1.Controls.Add(groupBox1); tabPage1.Controls.Add(optoDataGroupBox); tabPage1.Controls.Add(groupTextBox); tabPage1.Controls.Add(groupLabel); tabPage1.Controls.Add(muxBoardNrTextBox); tabPage1.Controls.Add(muxBoardNrLabel); tabPage1.Controls.Add(nameTextBox); tabPage1.Controls.Add(nameLabel); tabPage1.Controls.Add(classNameLabel); tabPage1.Location=new System.Drawing.Point(4,25); tabPage1.Name="tabPage1"; tabPage1.Padding=new System.Windows.Forms.Padding(3); tabPage1.Size=new System.Drawing.Size(603,403); tabPage1.Text="Config"; tabPage1.UseVisualStyleBackColor=true; classNameLabel.AutoSize=true; classNameLabel.Location=new System.Drawing.Point(149,11); classNameLabel.Name="classNameLabel"; classNameLabel.Text="ClassName"; nameLabel.AutoSize=true; nameLabel.Location=new System.Drawing.Point(6,44); nameLabel.Name="nameLabel"; nameLabel.Text="Name"; @@ -44,8 +44,10 @@ namespace TBF.Rig.RegisterReaders.AllyReader label2.AutoSize=true; label2.Location=new System.Drawing.Point(321,18); label2.Name="label2"; label2.Text="Serial port nr.:"; headPortNrTextBox.Enabled=false; headPortNrTextBox.Location=new System.Drawing.Point(439,15); headPortNrTextBox.Name="headPortNrTextBox"; headPortNrTextBox.Size=new System.Drawing.Size(44,22); tabPage2.Location=new System.Drawing.Point(4,25); tabPage2.Name="tabPage2"; tabPage2.Padding=new System.Windows.Forms.Padding(3); tabPage2.Size=new System.Drawing.Size(603,403); tabPage2.Text="Test"; tabPage2.UseVisualStyleBackColor=true; + tabPage3.Controls.Add(logsCfgCtrl); tabPage3.Location=new System.Drawing.Point(4,25); tabPage3.Name="tabPage3"; tabPage3.Padding=new System.Windows.Forms.Padding(3); tabPage3.Size=new System.Drawing.Size(603,403); tabPage3.Text="Logs"; tabPage3.UseVisualStyleBackColor=true; + logsCfgCtrl.Location=new System.Drawing.Point(-4,-4); logsCfgCtrl.Name="logsCfgCtrl"; logsCfgCtrl.Size=new System.Drawing.Size(611,432); AutoScaleDimensions=new System.Drawing.SizeF(8F,16F); AutoScaleMode=System.Windows.Forms.AutoScaleMode.Font; Controls.Add(tabControl1); Margin=new System.Windows.Forms.Padding(4); Name="AllyReaderCfgCtrl"; Size=new System.Drawing.Size(617,438); tabControl1.ResumeLayout(false); tabPage1.ResumeLayout(false); tabPage1.PerformLayout(); optoDataGroupBox.ResumeLayout(false); optoDataGroupBox.PerformLayout(); groupBox1.ResumeLayout(false); groupBox1.PerformLayout(); groupBox2.ResumeLayout(false); groupBox2.PerformLayout(); ResumeLayout(false); } - private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage tabPage1; private System.Windows.Forms.TabPage tabPage2; private System.Windows.Forms.Label classNameLabel,nameLabel,muxBoardNrLabel,groupLabel,label1,label2,label3,label4,ipAddressLabel,tcpipPortLabel,optoSerialPortLabel,rfidSerialPortNrLabel; private System.Windows.Forms.TextBox nameTextBox,muxBoardNrTextBox,groupTextBox,ipAddressTextBox,tcpipPortTextBox,optoSerialPortTextBox,rfidPortNrTextBox,headPortNrTextBox; private System.Windows.Forms.GroupBox optoDataGroupBox,groupBox1,groupBox2; private System.Windows.Forms.RadioButton radioButton1,radioButton2; private System.Windows.Forms.ComboBox comboBoxCommunicationInterface; + private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage tabPage1; private System.Windows.Forms.TabPage tabPage2; private System.Windows.Forms.TabPage tabPage3; private AllyReaderLogsCfgCtrl logsCfgCtrl; private System.Windows.Forms.Label classNameLabel,nameLabel,muxBoardNrLabel,groupLabel,label1,label2,label3,label4,ipAddressLabel,tcpipPortLabel,optoSerialPortLabel,rfidSerialPortNrLabel; private System.Windows.Forms.TextBox nameTextBox,muxBoardNrTextBox,groupTextBox,ipAddressTextBox,tcpipPortTextBox,optoSerialPortTextBox,rfidPortNrTextBox,headPortNrTextBox; private System.Windows.Forms.GroupBox optoDataGroupBox,groupBox1,groupBox2; private System.Windows.Forms.RadioButton radioButton1,radioButton2; private System.Windows.Forms.ComboBox comboBoxCommunicationInterface; } } diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.cs index 0c586b416..e2439829a 100644 --- a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.cs +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderCfgCtrl.cs @@ -18,6 +18,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader tabPage2.Controls.Add(manualTestControl); tabPage1.Text = "Settings"; tabPage2.Text = "Manual test"; + tabPage3.Text = "Logs"; groupBox1.Text = "Touch-Read communication"; groupBox2.Text = "Touch-Read settings"; muxBoardNrLabel.Text = "Configured meter size"; @@ -34,9 +35,13 @@ namespace TBF.Rig.RegisterReaders.AllyReader radioButton2.Checked = true; } - public IComponentCfg Config { get { return config; } set { config = value as AllyReaderCfg; Redraw(); manualTestControl.Config = config; } } + public IComponentCfg Config { get { return config; } set { config = value as AllyReaderCfg; Redraw(); manualTestControl.Config = config; logsCfgCtrl.Config = config; } } public bool ShowMore { get { return false; } } - public void Unlock() { nameTextBox.Enabled = muxBoardNrTextBox.Enabled = groupTextBox.Enabled = optoSerialPortTextBox.Enabled = tcpipPortTextBox.Enabled = rfidPortNrTextBox.Enabled = headPortNrTextBox.Enabled = true; } + public void Unlock() + { + nameTextBox.Enabled = muxBoardNrTextBox.Enabled = groupTextBox.Enabled = optoSerialPortTextBox.Enabled = tcpipPortTextBox.Enabled = rfidPortNrTextBox.Enabled = headPortNrTextBox.Enabled = true; + logsCfgCtrl.Unlock(); + } public CfgUpdateFlags VerifyCfg(ref string message) { @@ -48,7 +53,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader if (!int.TryParse(tcpipPortTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Optical baud rate"); if (!int.TryParse(rfidPortNrTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Touch-Read serial port nr."); if (!int.TryParse(headPortNrTextBox.Text, out n) || n <= 0) return Invalid(ref message, "Touch-Read baud rate"); - return CfgUpdateFlags.None; + return logsCfgCtrl.VerifyCfg(ref message); } public CfgUpdateFlags UpdateCfg() { @@ -58,7 +63,7 @@ namespace TBF.Rig.RegisterReaders.AllyReader config.MeterPulsesPerLiter = double.Parse(groupTextBox.Text, CultureInfo.InvariantCulture); config.OptoComPortNr = int.Parse(optoSerialPortTextBox.Text); config.OptoBaudRate = int.Parse(tcpipPortTextBox.Text); config.CommandComPortNr = int.Parse(rfidPortNrTextBox.Text); config.CommandBaudRate = int.Parse(headPortNrTextBox.Text); - return CfgUpdateFlags.RestartRqrd; + return CfgUpdateFlags.RestartRqrd | logsCfgCtrl.UpdateCfg(); } public void Closing() { manualTestControl.StopOpticalStream(); } private void Redraw() diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.Designer.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.Designer.cs new file mode 100644 index 000000000..6eb739278 --- /dev/null +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.Designer.cs @@ -0,0 +1,36 @@ +namespace TBF.Rig.RegisterReaders.AllyReader +{ + partial class AllyReaderLogsCfgCtrl + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } + private void InitializeComponent() + { + groupBoxOpticalDiagnostics = new System.Windows.Forms.GroupBox(); + checkBoxEnableDetailedLogPerWM = new System.Windows.Forms.CheckBox(); + labelOpticalDiagnosticFormat = new System.Windows.Forms.Label(); + comboBoxOpticalDiagnosticFormat = new System.Windows.Forms.ComboBox(); + labelRawOpticalDataFormat = new System.Windows.Forms.Label(); + comboBoxRawOpticalDataFormat = new System.Windows.Forms.ComboBox(); + checkBoxIncludeParsedValues = new System.Windows.Forms.CheckBox(); + groupBoxOpticalDiagnostics.SuspendLayout(); SuspendLayout(); + groupBoxOpticalDiagnostics.Controls.Add(checkBoxEnableDetailedLogPerWM); groupBoxOpticalDiagnostics.Controls.Add(labelOpticalDiagnosticFormat); groupBoxOpticalDiagnostics.Controls.Add(comboBoxOpticalDiagnosticFormat); groupBoxOpticalDiagnostics.Controls.Add(labelRawOpticalDataFormat); groupBoxOpticalDiagnostics.Controls.Add(comboBoxRawOpticalDataFormat); groupBoxOpticalDiagnostics.Controls.Add(checkBoxIncludeParsedValues); + groupBoxOpticalDiagnostics.Location = new System.Drawing.Point(5, 4); groupBoxOpticalDiagnostics.Name = "groupBoxOpticalDiagnostics"; groupBoxOpticalDiagnostics.Size = new System.Drawing.Size(591, 161); groupBoxOpticalDiagnostics.TabStop = false; groupBoxOpticalDiagnostics.Text = "Optical diagnostic logging"; + checkBoxEnableDetailedLogPerWM.AutoSize = true; checkBoxEnableDetailedLogPerWM.Enabled = false; checkBoxEnableDetailedLogPerWM.Location = new System.Drawing.Point(18, 30); checkBoxEnableDetailedLogPerWM.Name = "checkBoxEnableDetailedLogPerWM"; checkBoxEnableDetailedLogPerWM.Size = new System.Drawing.Size(189, 20); checkBoxEnableDetailedLogPerWM.Text = "Enable detailed log per WM"; checkBoxEnableDetailedLogPerWM.UseVisualStyleBackColor = true; + labelOpticalDiagnosticFormat.AutoSize = true; labelOpticalDiagnosticFormat.Location = new System.Drawing.Point(18, 68); labelOpticalDiagnosticFormat.Name = "labelOpticalDiagnosticFormat"; labelOpticalDiagnosticFormat.Text = "Output format:"; + comboBoxOpticalDiagnosticFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; comboBoxOpticalDiagnosticFormat.Enabled = false; comboBoxOpticalDiagnosticFormat.Location = new System.Drawing.Point(212, 64); comboBoxOpticalDiagnosticFormat.Name = "comboBoxOpticalDiagnosticFormat"; comboBoxOpticalDiagnosticFormat.Size = new System.Drawing.Size(160, 24); + labelRawOpticalDataFormat.AutoSize = true; labelRawOpticalDataFormat.Location = new System.Drawing.Point(18, 100); labelRawOpticalDataFormat.Name = "labelRawOpticalDataFormat"; labelRawOpticalDataFormat.Text = "RAW data encoding:"; + comboBoxRawOpticalDataFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; comboBoxRawOpticalDataFormat.Enabled = false; comboBoxRawOpticalDataFormat.Location = new System.Drawing.Point(212, 96); comboBoxRawOpticalDataFormat.Name = "comboBoxRawOpticalDataFormat"; comboBoxRawOpticalDataFormat.Size = new System.Drawing.Size(160, 24); + checkBoxIncludeParsedValues.AutoSize = true; checkBoxIncludeParsedValues.Enabled = false; checkBoxIncludeParsedValues.Location = new System.Drawing.Point(393, 99); checkBoxIncludeParsedValues.Name = "checkBoxIncludeParsedValues"; checkBoxIncludeParsedValues.Size = new System.Drawing.Size(153, 20); checkBoxIncludeParsedValues.Text = "Include parsed values"; checkBoxIncludeParsedValues.UseVisualStyleBackColor = true; + AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Controls.Add(groupBoxOpticalDiagnostics); Name = "AllyReaderLogsCfgCtrl"; Size = new System.Drawing.Size(611, 432); + groupBoxOpticalDiagnostics.ResumeLayout(false); groupBoxOpticalDiagnostics.PerformLayout(); ResumeLayout(false); + } + private System.Windows.Forms.GroupBox groupBoxOpticalDiagnostics; + private System.Windows.Forms.CheckBox checkBoxEnableDetailedLogPerWM; + private System.Windows.Forms.Label labelOpticalDiagnosticFormat; + private System.Windows.Forms.ComboBox comboBoxOpticalDiagnosticFormat; + private System.Windows.Forms.Label labelRawOpticalDataFormat; + private System.Windows.Forms.ComboBox comboBoxRawOpticalDataFormat; + private System.Windows.Forms.CheckBox checkBoxIncludeParsedValues; + } +} diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.cs b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.cs new file mode 100644 index 000000000..4b8cbc0f4 --- /dev/null +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows.Forms; +using Common; + +namespace TBF.Rig.RegisterReaders.AllyReader +{ + public partial class AllyReaderLogsCfgCtrl : UserControl + { + private AllyReaderCfg config; + private bool unlocked; + + public AllyReaderLogsCfgCtrl() + { + InitializeComponent(); + comboBoxOpticalDiagnosticFormat.Items.AddRange(new object[] { "Csv", "Log" }); + comboBoxRawOpticalDataFormat.Items.AddRange(new object[] { "Disabled", "Hex", "Ascii", "Text", "Base64" }); + checkBoxEnableDetailedLogPerWM.CheckedChanged += CheckBoxEnableDetailedLogPerWM_CheckedChanged; + } + + public AllyReaderCfg Config { get { return config; } set { config = value; Redraw(); } } + + public void Unlock() + { + unlocked = true; + checkBoxEnableDetailedLogPerWM.Enabled = true; + UpdateEnabledState(); + } + + public CfgUpdateFlags VerifyCfg(ref string message) + { + if (config == null || comboBoxOpticalDiagnosticFormat.SelectedItem == null || comboBoxRawOpticalDataFormat.SelectedItem == null) + { + message += Environment.NewLine + "Optical diagnostic logging configuration is not valid."; + return CfgUpdateFlags.Error; + } + return CfgUpdateFlags.None; + } + + public CfgUpdateFlags UpdateCfg() + { + if (config == null) return CfgUpdateFlags.Error; + config.EnableDetailedLogPerWM = checkBoxEnableDetailedLogPerWM.Checked; + config.OpticalDiagnosticFormat = comboBoxOpticalDiagnosticFormat.Text; + config.RawOpticalDataFormat = comboBoxRawOpticalDataFormat.Text; + config.IncludeParsedValues = checkBoxIncludeParsedValues.Checked; + return CfgUpdateFlags.RestartRqrd; + } + + private void Redraw() + { + if (config == null) return; + checkBoxEnableDetailedLogPerWM.Checked = config.EnableDetailedLogPerWM; + comboBoxOpticalDiagnosticFormat.SelectedItem = string.IsNullOrEmpty(config.OpticalDiagnosticFormat) ? "Csv" : config.OpticalDiagnosticFormat; + comboBoxRawOpticalDataFormat.SelectedItem = string.IsNullOrEmpty(config.RawOpticalDataFormat) ? "Hex" : config.RawOpticalDataFormat; + checkBoxIncludeParsedValues.Checked = config.IncludeParsedValues; + UpdateEnabledState(); + } + + private void CheckBoxEnableDetailedLogPerWM_CheckedChanged(object sender, EventArgs e) { UpdateEnabledState(); } + private void UpdateEnabledState() + { + bool enabled = unlocked && checkBoxEnableDetailedLogPerWM.Checked; + comboBoxOpticalDiagnosticFormat.Enabled = enabled; + comboBoxRawOpticalDataFormat.Enabled = enabled; + checkBoxIncludeParsedValues.Enabled = enabled; + } + } +} diff --git a/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.resx b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.resx new file mode 100644 index 000000000..13913bdda --- /dev/null +++ b/TBF/Rig/RegisterReaders/AllyReader/AllyReaderLogsCfgCtrl.resx @@ -0,0 +1,7 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicOpticalDiagnosticWriter.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicOpticalDiagnosticWriter.cs new file mode 100644 index 000000000..5eb4e0fd7 --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicOpticalDiagnosticWriter.cs @@ -0,0 +1,209 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using log4net; +using TBF.Rig.Diagnostics; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer; + +namespace TBF.Rig.RegisterReaders.AsicReader +{ + /// + /// Optional per-watermeter optical diagnostics for ASIC readers. + /// + internal sealed class AsicOpticalDiagnosticWriter + { + private readonly string diagnosticRoot; + internal AsicOpticalDiagnosticWriter(string root = @"C:\TBF\Logs\ASIC") + { + diagnosticRoot = root; + } + private static readonly ILog LegacyLog = LogManager.GetLogger("AsicOptical"); + private readonly object sync = new object(); + private StreamWriter writer; + + public void Write( + AsicReaderCfg cfg, + int batchNr, + int wmPosition, + string serialNumber, + int telegramIndex, + bool isMeasurementData, + string rawLine, + byte[] rawBytes, + DiagnosticLedState7Data parsedData, + double extendedVolume, + double extendedTimestamp) + { + try + { + if (!cfg.EnableDetailedLogPerWM) + { + LegacyLog.Info(CreateLegacyMessage( + batchNr, wmPosition, serialNumber, telegramIndex, + isMeasurementData, rawLine, rawBytes, parsedData, + extendedVolume, extendedTimestamp, cfg)); + return; + } + + lock (sync) + { + string extension = IsLogFormat(cfg.OpticalDiagnosticFormat) ? ".log" : ".csv"; + OpticalLogArchive.Append(diagnosticRoot, batchNr, directory => + { + string path = Path.Combine(directory, "ASIC_WM_" + Math.Max(0, wmPosition) + extension); + try + { + EnsureWriter(path, extension == ".csv"); + if (extension == ".csv") + WriteCsvRow(cfg, batchNr, wmPosition, serialNumber, telegramIndex, + isMeasurementData, rawLine, rawBytes, parsedData, + extendedVolume, extendedTimestamp); + else + WriteTextEntry(cfg, batchNr, wmPosition, serialNumber, telegramIndex, + isMeasurementData, rawLine, rawBytes, parsedData, + extendedVolume, extendedTimestamp); + } + finally + { + if (writer != null) + { + writer.Dispose(); + writer = null; + } + } + }, ex => LegacyLog.Error("ASIC optical archive failed; source data retained.", ex)); + } + } + catch (Exception ex) + { + // Diagnostics must never stop the measurement data stream. + LegacyLog.Error("Unable to write detailed ASIC optical diagnostic.", ex); + } + } + + private void EnsureWriter(string path, bool writeCsvHeader) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + bool newFile = !File.Exists(path) || new FileInfo(path).Length == 0; + writer = new StreamWriter( + new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read), + new UTF8Encoding(false)); + writer.AutoFlush = true; + + if (writeCsvHeader && newFile) + { + writer.WriteLine("PcTimestamp;BatchNr;WmPosition;SerialNumber;TelegramIndex;Capture;ParseStatus;RawFormat;Raw;ExtendedTimeS;ExtendedVolume;ParsedValues"); + } + } + + private void WriteCsvRow( + AsicReaderCfg cfg, int batchNr, int wmPosition, string serialNumber, + int telegramIndex, bool isMeasurementData, string rawLine, byte[] rawBytes, + DiagnosticLedState7Data parsedData, double extendedVolume, double extendedTimestamp) + { + string rawFormat = NormalizedRawFormat(cfg.RawOpticalDataFormat); + string parsedValues = cfg.IncludeParsedValues && parsedData != null ? parsedData.ToString() : String.Empty; + writer.WriteLine(String.Join(";", new[] + { + Csv(DateTime.Now.ToString("O", CultureInfo.InvariantCulture)), + Csv(batchNr.ToString(CultureInfo.InvariantCulture)), + Csv(wmPosition.ToString(CultureInfo.InvariantCulture)), + Csv(serialNumber), + Csv(telegramIndex.ToString(CultureInfo.InvariantCulture)), + Csv(isMeasurementData ? "Measurement" : "Flush"), + Csv(parsedData == null ? "Invalid" : "Parsed"), + Csv(rawFormat), + Csv(FormatRaw(rawFormat, rawLine, rawBytes)), + Csv(cfg.IncludeParsedValues ? Number(extendedTimestamp) : String.Empty), + Csv(cfg.IncludeParsedValues ? Number(extendedVolume) : String.Empty), + Csv(parsedValues) + })); + } + + private void WriteTextEntry( + AsicReaderCfg cfg, int batchNr, int wmPosition, string serialNumber, + int telegramIndex, bool isMeasurementData, string rawLine, byte[] rawBytes, + DiagnosticLedState7Data parsedData, double extendedVolume, double extendedTimestamp) + { + string rawFormat = NormalizedRawFormat(cfg.RawOpticalDataFormat); + writer.WriteLine("{0:O} | batch={1} | wm={2} | serial={3} | telegram={4} | capture={5} | parse={6}", + DateTime.Now, batchNr, wmPosition, serialNumber ?? String.Empty, telegramIndex, + isMeasurementData ? "Measurement" : "Flush", parsedData == null ? "Invalid" : "Parsed"); + writer.WriteLine("RAW [{0}] {1}", rawFormat, FormatRaw(rawFormat, rawLine, rawBytes)); + if (cfg.IncludeParsedValues) + { + writer.WriteLine("EXTENDED time_s={0}; volume={1}", Number(extendedTimestamp), Number(extendedVolume)); + writer.WriteLine("PARSED {0}", parsedData == null ? String.Empty : parsedData.ToString()); + } + } + + private static string CreateLegacyMessage( + int batchNr, int wmPosition, string serialNumber, int telegramIndex, + bool isMeasurementData, string rawLine, byte[] rawBytes, + DiagnosticLedState7Data parsedData, double extendedVolume, + double extendedTimestamp, AsicReaderCfg cfg) + { + string rawFormat = NormalizedRawFormat(cfg.RawOpticalDataFormat); + return String.Format(CultureInfo.InvariantCulture, + "batch={0}; wm={1}; serial={2}; telegram={3}; capture={4}; parse={5}; raw[{6}]={7}; extendedTimeS={8}; extendedVolume={9}; parsed={10}", + batchNr, wmPosition, serialNumber ?? String.Empty, telegramIndex, + isMeasurementData ? "Measurement" : "Flush", parsedData == null ? "Invalid" : "Parsed", + rawFormat, FormatRaw(rawFormat, rawLine, rawBytes), + cfg.IncludeParsedValues ? Number(extendedTimestamp) : String.Empty, + cfg.IncludeParsedValues ? Number(extendedVolume) : String.Empty, + cfg.IncludeParsedValues && parsedData != null ? parsedData.ToString() : String.Empty); + } + + private static bool IsLogFormat(string format) + { + return String.Equals(format, "Log", StringComparison.OrdinalIgnoreCase); + } + + private static string NormalizedRawFormat(string format) + { + if (String.Equals(format, "Ascii", StringComparison.OrdinalIgnoreCase)) return "Ascii"; + if (String.Equals(format, "Text", StringComparison.OrdinalIgnoreCase)) return "Text"; + if (String.Equals(format, "Base64", StringComparison.OrdinalIgnoreCase)) return "Base64"; + if (String.Equals(format, "Hex", StringComparison.OrdinalIgnoreCase)) return "Hex"; + return "Disabled"; + } + + private static string FormatRaw(string format, string rawLine, byte[] rawBytes) + { + rawBytes = rawBytes ?? new byte[0]; + if (format == "Disabled") return String.Empty; + if (format == "Text") return rawLine ?? String.Empty; + if (format == "Base64") return Convert.ToBase64String(rawBytes); + if (format == "Ascii") return ToEscapedAscii(rawBytes); + return BitConverter.ToString(rawBytes).Replace("-", " "); + } + + private static string ToEscapedAscii(byte[] bytes) + { + StringBuilder result = new StringBuilder(); + foreach (byte value in bytes) + { + if (value == 13) result.Append("\\r"); + else if (value == 10) result.Append("\\n"); + else if (value == 9) result.Append("\\t"); + else if (value >= 32 && value <= 126) result.Append((char)value); + else result.Append("\\x").Append(value.ToString("X2", CultureInfo.InvariantCulture)); + } + return result.ToString(); + } + + private static string Number(double value) + { + return Double.IsNaN(value) || Double.IsInfinity(value) + ? String.Empty + : value.ToString("R", CultureInfo.InvariantCulture); + } + + private static string Csv(string value) + { + value = value ?? String.Empty; + return "\"" + value.Replace("\"", "\"\"") + "\""; + } + } +} diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReader.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReader.cs index 1e715cc91..159ad5052 100644 --- a/TBF/Rig/RegisterReaders/AsicReader/AsicReader.cs +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReader.cs @@ -2,6 +2,8 @@ using System; using TBF.Rig.Generic; using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer; +using TBF.Rig.Sequences; namespace TBF.Rig.RegisterReaders.AsicReader { @@ -15,6 +17,10 @@ namespace TBF.Rig.RegisterReaders.AsicReader /// public class AsicReader : IperlHead, ISmartReader { + private readonly AsicReaderCfg asicCfg; + private readonly AsicOpticalDiagnosticWriter opticalDiagnosticWriter = + new AsicOpticalDiagnosticWriter(); + public AsicReader() { } @@ -22,6 +28,7 @@ namespace TBF.Rig.RegisterReaders.AsicReader public AsicReader(IComponentCfg cfg) : base(cfg) { + asicCfg = cfg as AsicReaderCfg; } // The original IperlHead API keeps the communication interface strongly @@ -51,5 +58,35 @@ namespace TBF.Rig.RegisterReaders.AsicReader SetCommunicationInterface(parsedInterface); } + + /// + /// Captures ASIC diagnostic data after the parser has updated the + /// rollover-corrected time and volume counters. + /// + protected override void NotifyOpticalTelegramCaptured( + string rawLine, + byte[] rawBytes, + DiagnosticLedState7Data parsedData, + int telegramIndex, + bool isMeasurementData, + double extendedVolume, + double extendedTimestamp) + { + if (asicCfg == null) + return; + + opticalDiagnosticWriter.Write( + asicCfg, + ProcessData.BatchNr, + Position, + SerialNr, + telegramIndex, + isMeasurementData, + rawLine, + rawBytes, + parsedData, + extendedVolume, + extendedTimestamp); + } } } diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfg.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfg.cs index 6a2226d40..69009453b 100644 --- a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfg.cs +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfg.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Xml.Serialization; using TBF.Rig.Generic; using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; @@ -15,6 +16,16 @@ namespace TBF.Rig.RegisterReaders.AsicReader [XmlRoot("AsicReaderCfg")] public class AsicReaderCfg : IperlHeadCfg { + public bool EnableDetailedLogPerWM; + public string OpticalDiagnosticFormat = "Csv"; + public string RawOpticalDataFormat = "Hex"; + public bool IncludeParsedValues = true; + + public override IComponentCfgCtrl GetControl(IList cmpntEntities) + { + return new AsicReaderCfgCtrl(); + } + public static readonly XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(AsicReaderCfg) })[0]; diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.Designer.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.Designer.cs new file mode 100644 index 000000000..bfa5ac3d0 --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.Designer.cs @@ -0,0 +1,35 @@ +namespace TBF.Rig.RegisterReaders.AsicReader +{ + partial class AsicReaderCfgCtrl + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } + private void InitializeComponent() + { + tabControl = new System.Windows.Forms.TabControl(); + settingsTabPage = new System.Windows.Forms.TabPage(); + manualTestTabPage = new System.Windows.Forms.TabPage(); + logsTabPage = new System.Windows.Forms.TabPage(); + settingsCfgCtrl = new TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl(); + manualTestCfgCtrl = new TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl(); + logsCfgCtrl = new AsicReaderLogsCfgCtrl(); + tabControl.SuspendLayout(); settingsTabPage.SuspendLayout(); manualTestTabPage.SuspendLayout(); logsTabPage.SuspendLayout(); SuspendLayout(); + tabControl.Controls.Add(settingsTabPage); tabControl.Controls.Add(manualTestTabPage); tabControl.Controls.Add(logsTabPage); tabControl.Location = new System.Drawing.Point(3,3); tabControl.Name = "tabControl"; tabControl.SelectedIndex = 0; tabControl.Size = new System.Drawing.Size(611,432); + settingsTabPage.Controls.Add(settingsCfgCtrl); settingsTabPage.Location = new System.Drawing.Point(4,25); settingsTabPage.Name = "settingsTabPage"; settingsTabPage.Size = new System.Drawing.Size(603,403); settingsTabPage.Text = "Settings"; settingsTabPage.UseVisualStyleBackColor = true; + settingsCfgCtrl.Location = new System.Drawing.Point(-4,-4); settingsCfgCtrl.Name = "settingsCfgCtrl"; settingsCfgCtrl.Size = new System.Drawing.Size(617,438); + manualTestTabPage.Controls.Add(manualTestCfgCtrl); manualTestTabPage.Location = new System.Drawing.Point(4,25); manualTestTabPage.Name = "manualTestTabPage"; manualTestTabPage.Size = new System.Drawing.Size(603,403); manualTestTabPage.Text = "Manual test"; manualTestTabPage.UseVisualStyleBackColor = true; + manualTestCfgCtrl.Location = new System.Drawing.Point(-4,-4); manualTestCfgCtrl.Name = "manualTestCfgCtrl"; manualTestCfgCtrl.Size = new System.Drawing.Size(617,438); + logsTabPage.Controls.Add(logsCfgCtrl); logsTabPage.Location = new System.Drawing.Point(4,25); logsTabPage.Name = "logsTabPage"; logsTabPage.Size = new System.Drawing.Size(603,403); logsTabPage.Text = "Logs"; logsTabPage.UseVisualStyleBackColor = true; + logsCfgCtrl.Location = new System.Drawing.Point(-4,-4); logsCfgCtrl.Name = "logsCfgCtrl"; logsCfgCtrl.Size = new System.Drawing.Size(611,432); + AutoScaleDimensions = new System.Drawing.SizeF(8F,16F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Controls.Add(tabControl); Name = "AsicReaderCfgCtrl"; Size = new System.Drawing.Size(617,438); + tabControl.ResumeLayout(false); settingsTabPage.ResumeLayout(false); manualTestTabPage.ResumeLayout(false); logsTabPage.ResumeLayout(false); ResumeLayout(false); + } + private System.Windows.Forms.TabControl tabControl; + private System.Windows.Forms.TabPage settingsTabPage; + private System.Windows.Forms.TabPage manualTestTabPage; + private System.Windows.Forms.TabPage logsTabPage; + private TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl settingsCfgCtrl; + private TBF.Rig.TestMethods.iPerlCommunication.iPerlHead.IperlHeadCfgCtrl manualTestCfgCtrl; + private AsicReaderLogsCfgCtrl logsCfgCtrl; + } +} diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.cs new file mode 100644 index 000000000..9a78a501a --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderCfgCtrl.cs @@ -0,0 +1,41 @@ +using System; +using System.Windows.Forms; +using Common; +using TBF.Rig.Generic; +using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; + +namespace TBF.Rig.RegisterReaders.AsicReader +{ + public partial class AsicReaderCfgCtrl : UserControl, IComponentCfgCtrl + { + private AsicReaderCfg config; + public AsicReaderCfgCtrl() { InitializeComponent(); } + public bool ShowMore { get { return false; } } + public IComponentCfg Config + { + get { return config; } + set + { + config = value as AsicReaderCfg; + settingsCfgCtrl.Config = config; + settingsCfgCtrl.ShowOnlyTab(false); + manualTestCfgCtrl.Config = config; + manualTestCfgCtrl.ShowOnlyTab(true); + logsCfgCtrl.Config = config; + } + } + public void Unlock() { settingsCfgCtrl.Unlock(); logsCfgCtrl.Unlock(); } + public void Closing() { settingsCfgCtrl.Closing(); manualTestCfgCtrl.Closing(); } + public CfgUpdateFlags VerifyCfg(ref string message) + { + CfgUpdateFlags flags = settingsCfgCtrl.VerifyCfg(ref message); + return flags == CfgUpdateFlags.None ? logsCfgCtrl.VerifyCfg(ref message) : flags; + } + public CfgUpdateFlags UpdateCfg() + { + CfgUpdateFlags flags = settingsCfgCtrl.UpdateCfg(); + if ((flags & CfgUpdateFlags.Error) != 0) return flags; + return flags | logsCfgCtrl.UpdateCfg(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.Designer.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.Designer.cs new file mode 100644 index 000000000..63f2556b9 --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.Designer.cs @@ -0,0 +1,30 @@ +namespace TBF.Rig.RegisterReaders.AsicReader +{ + partial class AsicReaderLogsCfgCtrl + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } + private void InitializeComponent() + { + groupBox = new System.Windows.Forms.GroupBox(); enableDetailedLogCheckBox = new System.Windows.Forms.CheckBox(); diagnosticFormatLabel = new System.Windows.Forms.Label(); diagnosticFormatComboBox = new System.Windows.Forms.ComboBox(); rawFormatLabel = new System.Windows.Forms.Label(); rawFormatComboBox = new System.Windows.Forms.ComboBox(); includeParsedValuesCheckBox = new System.Windows.Forms.CheckBox(); + groupBox.SuspendLayout(); SuspendLayout(); + groupBox.Controls.Add(enableDetailedLogCheckBox); groupBox.Controls.Add(diagnosticFormatLabel); groupBox.Controls.Add(diagnosticFormatComboBox); groupBox.Controls.Add(rawFormatLabel); groupBox.Controls.Add(rawFormatComboBox); groupBox.Controls.Add(includeParsedValuesCheckBox); + groupBox.Location = new System.Drawing.Point(5,4); groupBox.Name = "groupBox"; groupBox.Size = new System.Drawing.Size(591,161); groupBox.TabStop = false; groupBox.Text = "Optical diagnostic logging"; + enableDetailedLogCheckBox.AutoSize = true; enableDetailedLogCheckBox.Enabled = false; enableDetailedLogCheckBox.Location = new System.Drawing.Point(18,30); enableDetailedLogCheckBox.Name = "enableDetailedLogCheckBox"; enableDetailedLogCheckBox.Text = "Enable detailed log per WM"; + diagnosticFormatLabel.AutoSize = true; diagnosticFormatLabel.Location = new System.Drawing.Point(18,68); diagnosticFormatLabel.Text = "Output format:"; + diagnosticFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; diagnosticFormatComboBox.Enabled = false; diagnosticFormatComboBox.Location = new System.Drawing.Point(212,64); diagnosticFormatComboBox.Size = new System.Drawing.Size(160,24); + rawFormatLabel.AutoSize = true; rawFormatLabel.Location = new System.Drawing.Point(18,100); rawFormatLabel.Text = "RAW data encoding:"; + rawFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; rawFormatComboBox.Enabled = false; rawFormatComboBox.Location = new System.Drawing.Point(212,96); rawFormatComboBox.Size = new System.Drawing.Size(160,24); + includeParsedValuesCheckBox.AutoSize = true; includeParsedValuesCheckBox.Enabled = false; includeParsedValuesCheckBox.Location = new System.Drawing.Point(393,99); includeParsedValuesCheckBox.Text = "Include parsed values"; + AutoScaleDimensions = new System.Drawing.SizeF(8F,16F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Controls.Add(groupBox); Name = "AsicReaderLogsCfgCtrl"; Size = new System.Drawing.Size(611,432); + groupBox.ResumeLayout(false); groupBox.PerformLayout(); ResumeLayout(false); + } + private System.Windows.Forms.GroupBox groupBox; + private System.Windows.Forms.CheckBox enableDetailedLogCheckBox; + private System.Windows.Forms.Label diagnosticFormatLabel; + private System.Windows.Forms.ComboBox diagnosticFormatComboBox; + private System.Windows.Forms.Label rawFormatLabel; + private System.Windows.Forms.ComboBox rawFormatComboBox; + private System.Windows.Forms.CheckBox includeParsedValuesCheckBox; + } +} diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.cs b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.cs new file mode 100644 index 000000000..045331d8d --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.cs @@ -0,0 +1,55 @@ +using System; +using System.Windows.Forms; +using Common; + +namespace TBF.Rig.RegisterReaders.AsicReader +{ + public partial class AsicReaderLogsCfgCtrl : UserControl + { + private AsicReaderCfg config; + private bool unlocked; + public AsicReaderLogsCfgCtrl() + { + InitializeComponent(); + diagnosticFormatComboBox.Items.AddRange(new object[] { "Csv", "Log" }); + rawFormatComboBox.Items.AddRange(new object[] { "Disabled", "Hex", "Ascii", "Text", "Base64" }); + enableDetailedLogCheckBox.CheckedChanged += (s, e) => UpdateEnabledState(); + } + public AsicReaderCfg Config { get { return config; } set { config = value; Redraw(); } } + public void Unlock() { unlocked = true; enableDetailedLogCheckBox.Enabled = true; UpdateEnabledState(); } + public CfgUpdateFlags VerifyCfg(ref string message) + { + if (config == null || diagnosticFormatComboBox.SelectedItem == null || rawFormatComboBox.SelectedItem == null) + { + message += Environment.NewLine + "Optical diagnostic logging configuration is not valid."; + return CfgUpdateFlags.Error; + } + return CfgUpdateFlags.None; + } + public CfgUpdateFlags UpdateCfg() + { + if (config == null) return CfgUpdateFlags.Error; + config.EnableDetailedLogPerWM = enableDetailedLogCheckBox.Checked; + config.OpticalDiagnosticFormat = diagnosticFormatComboBox.Text; + config.RawOpticalDataFormat = rawFormatComboBox.Text; + config.IncludeParsedValues = includeParsedValuesCheckBox.Checked; + return CfgUpdateFlags.RestartRqrd; + } + private void Redraw() + { + if (config == null) return; + enableDetailedLogCheckBox.Checked = config.EnableDetailedLogPerWM; + diagnosticFormatComboBox.SelectedItem = string.IsNullOrEmpty(config.OpticalDiagnosticFormat) ? "Csv" : config.OpticalDiagnosticFormat; + rawFormatComboBox.SelectedItem = string.IsNullOrEmpty(config.RawOpticalDataFormat) ? "Hex" : config.RawOpticalDataFormat; + includeParsedValuesCheckBox.Checked = config.IncludeParsedValues; + UpdateEnabledState(); + } + private void UpdateEnabledState() + { + bool enabled = unlocked && enableDetailedLogCheckBox.Checked; + diagnosticFormatComboBox.Enabled = enabled; + rawFormatComboBox.Enabled = enabled; + includeParsedValuesCheckBox.Enabled = enabled; + } + } +} diff --git a/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.resx b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.resx new file mode 100644 index 000000000..13913bdda --- /dev/null +++ b/TBF/Rig/RegisterReaders/AsicReader/AsicReaderLogsCfgCtrl.resx @@ -0,0 +1,7 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs index 03037f0ab..312bf400b 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/CliRunner.cs @@ -412,12 +412,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop try { value = JsonConvert.DeserializeObject(json); + CaptureDiagnosticJson(value, json); return value != null; } catch (Exception ex) { log?.Debug(ex.Message); - return TryConvert(json, out value); + bool converted = TryConvert(json, out value); + if (converted) CaptureDiagnosticJson(value, json); + return converted; } } @@ -425,6 +428,14 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop return false; } + private static void CaptureDiagnosticJson(T value, string json) + { + var data = (object)value as JsonDataFromPoseidon; + if (data == null) return; + data.DiagnosticRawJson = json; + data.DiagnosticReceivedUtc = DateTime.UtcNow; + } + private static bool TryConvert(string json, out T value) where T : new() { T obj = new T(); diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/JsonDataFromPoseidon.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/JsonDataFromPoseidon.cs index 71da1c0c1..047b95af4 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/JsonDataFromPoseidon.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/JsonDataFromPoseidon.cs @@ -4,6 +4,11 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop { public class JsonDataFromPoseidon { + // Original extracted CLI JSON; not optical wire bytes or reserialized data. + [Newtonsoft.Json.JsonIgnore] + public string DiagnosticRawJson { get; set; } + [Newtonsoft.Json.JsonIgnore] + public System.DateTime DiagnosticReceivedUtc { get; set; } public bool? NfcTagDetected { get; set; } public int? ProductType { get; set; } public string ProductTypeVersion { get; set; } diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCfg.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCfg.cs index bbd66db12..1858048cb 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCfg.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonCfg.cs @@ -23,6 +23,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop public int ComPortNr; public string CliFileName; public int MeterType; + public bool EnableDetailedLogPerWM; + public string OpticalDiagnosticFormat; + public string RawOpticalDataFormat; + public bool IncludeParsedValues; /// Procedure parameters @@ -38,6 +42,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop "Serial port number", /// 0 "CLI program name", /// 1 "Meter Type", /// 2 + "Enable detailed log per WM", /// 3 + "Optical diagnostic format", /// 4 + "RAW optical data format", /// 5 + "Include parsed values", /// 6 }; public string ParamName(int i) { return paramNames[i]; } public int ParamsCount() { return paramNames.Length; } @@ -53,6 +61,14 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop case 2: return new string[] { "74" }; + case 3: + case 6: + return new string[] { bool.FalseString, bool.TrueString }; + case 4: + return new string[] { "Csv", "Log" }; + case 5: + return new string[] { "Disabled", "Hex", "Ascii", "Text", "Base64" }; + default: return null; } @@ -66,6 +82,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop ComPortNr = 3; CliFileName = "HatCLI.exe"; MeterType = 74; + EnableDetailedLogPerWM = false; + OpticalDiagnosticFormat = "Csv"; + RawOpticalDataFormat = "Hex"; + IncludeParsedValues = true; } public bool ValidateParam(int i, string strValue, out string message) @@ -85,6 +105,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop case 2: if (int.TryParse(strValue, out idummy) && idummy > 0) return true; break; + case 3: + case 6: + bool boolValue; + if (bool.TryParse(strValue, out boolValue)) return true; + break; + case 4: + case 5: + if (ParamValues(i).Contains(strValue)) return true; + break; default: message = "Invalid index"; return false; @@ -101,6 +130,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop case 0: ComPortNr = int.Parse(str); return CfgUpdateFlags.RestartRqrd; case 1: CliFileName = str; return CfgUpdateFlags.RestartRqrd; case 2: MeterType = int.Parse(str); return CfgUpdateFlags.RestartRqrd; + case 3: EnableDetailedLogPerWM = bool.Parse(str); return CfgUpdateFlags.RestartRqrd; + case 4: OpticalDiagnosticFormat = str; return CfgUpdateFlags.RestartRqrd; + case 5: RawOpticalDataFormat = str; return CfgUpdateFlags.RestartRqrd; + case 6: IncludeParsedValues = bool.Parse(str); return CfgUpdateFlags.RestartRqrd; default: return CfgUpdateFlags.None; } } @@ -139,6 +172,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop case 0: return ComPortNr.ToString(); case 1: return CliFileName; case 2: return MeterType.ToString(); + case 3: return EnableDetailedLogPerWM.ToString(); + case 4: return OpticalDiagnosticFormat; + case 5: return RawOpticalDataFormat; + case 6: return IncludeParsedValues.ToString(); default: return string.Format("{0}: Com{1}, CliName =`{2}`, MeterType = {3}", Name, ComPortNr, CliFileName, MeterType); @@ -150,6 +187,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop prms.ComPortNr = this.ComPortNr; prms.CliFileName = this.CliFileName; prms.MeterType = this.MeterType; + prms.EnableDetailedLogPerWM = this.EnableDetailedLogPerWM; + prms.OpticalDiagnosticFormat = this.OpticalDiagnosticFormat; + prms.RawOpticalDataFormat = this.RawOpticalDataFormat; + prms.IncludeParsedValues = this.IncludeParsedValues; prms.ProcParams = this.ProcParams; } } diff --git a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs index d889ee351..d80b5448b 100644 --- a/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs +++ b/TBF/Rig/RegisterReaders/PoseidonCmdStartStop/PoseidonReader.cs @@ -29,6 +29,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop internal const string SimulatedCliFileName = "cmdSleepTest.exe"; public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + private readonly TBF.Rig.RegisterReaders.PoseidonReader.PoseidonOpticalDiagnosticWriter opticalDiagnosticWriter = + new TBF.Rig.RegisterReaders.PoseidonReader.PoseidonOpticalDiagnosticWriter(); readonly PoseidonCfg registerReaderCfg; readonly ControlBoard.IControlBoard controlBoard; @@ -560,6 +562,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop } } + CaptureCliDiagnostic(data); CliRunner.Clear(); _currentOp = _lastOpTimedOut ? CurrentPoseidonOp.Error : CurrentPoseidonOp.Done; return Event.Done; @@ -569,6 +572,25 @@ namespace TBF.Rig.RegisterReaders.PoseidonCmdStartStop } + private void CaptureCliDiagnostic(JsonDataFromPoseidon data) + { + if (registerReaderCfg == null) return; + string raw = data == null ? null : data.DiagnosticRawJson; + DateTime received = data == null || data.DiagnosticReceivedUtc == default(DateTime) + ? DateTime.UtcNow : data.DiagnosticReceivedUtc; + opticalDiagnosticWriter.Write(registerReaderCfg.EnableDetailedLogPerWM, + registerReaderCfg.OpticalDiagnosticFormat, registerReaderCfg.RawOpticalDataFormat, + registerReaderCfg.IncludeParsedValues, Sequences.ProcessData.BatchNr, + Position > 0 ? Position.ToString() : Name, Name, wmSerialNr, received, + "CLI/ExtractedJson", lastCliReadSucceeded ? "Accepted" : "Rejected", + System.Text.Encoding.UTF8.GetBytes(raw ?? ""), raw, + null, null, null, null, + lastCliReadSucceeded ? (object)(_isReadingStart ? beginWMState : endWMState) : null, + fullTimeInMilis / 1000D, data == null ? null : data.Reading, + data == null ? null : data.Reading_Units, _isReadingStart ? "START" : "END", + lastCliReadFailureReason); + } + public static bool TryParseCliReading(string reading, out double value) { value = 0; diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfg.cs b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfg.cs index cc7850c9a..9d8fd93ea 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfg.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfg.cs @@ -31,6 +31,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader public ECommunicationInterface CommunicationInterface; /// Communication Interface: NFC, touch capl public string CliProgramName; public string CliMeterType; + public bool EnableDetailedLogPerWM; + public string OpticalDiagnosticFormat = "Csv"; + public string RawOpticalDataFormat = "Hex"; + public bool IncludeParsedValues = true; /// Procedure parameters diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.cs index ec8bd22fc..80f89a18b 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.cs @@ -31,12 +31,16 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader { config = value as PoseidonCfg; Redraw(); + logsCfgCtrl.Config = config; } } public PoseidonCfgCtrl() { InitializeComponent(); + tabPage1.Text = "Settings"; + tabPage2.Text = "Manual test"; + tabPage3.Text = "Logs"; InitComPortsComboByList(listOfComPoertsComboBox, GetAvailableComPorts()); InitComboByList(cliProgramComboBox, GetAvailableCliPrograms()); @@ -113,11 +117,14 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader meterTypeComboBox.Enabled = true; listOfComPoertsComboBox.Enabled = true; button_refreshComPorts.Enabled = true; + logsCfgCtrl.Unlock(); } public CfgUpdateFlags VerifyCfg(ref string message) { CfgUpdateFlags flags = CfgUpdateFlags.None; + + flags |= logsCfgCtrl.VerifyCfg(ref message); int dummy; @@ -214,6 +221,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text); config.CliProgramName = cliProgramComboBox.Text; config.CliMeterType = meterTypeComboBox.Text; + flags |= logsCfgCtrl.UpdateCfg(); return flags; } diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.designer.cs b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.designer.cs index 95af2ec6e..1e57e2388 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.designer.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonCfgCtrl.designer.cs @@ -57,6 +57,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader this.nameLabel = new System.Windows.Forms.Label(); this.classNameLabel = new System.Windows.Forms.Label(); this.tabPage2 = new System.Windows.Forms.TabPage(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.logsCfgCtrl = new PoseidonLogsCfgCtrl(); this.tabControl1.SuspendLayout(); this.tabPage1.SuspendLayout(); this.groupBox3.SuspendLayout(); @@ -69,6 +71,7 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader // this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); this.tabControl1.Location = new System.Drawing.Point(3, 4); this.tabControl1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabControl1.Name = "tabControl1"; @@ -341,6 +344,15 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader this.tabPage2.Text = "Test"; this.tabPage2.UseVisualStyleBackColor = true; // + // tabPage3 + // + this.tabPage3.Controls.Add(this.logsCfgCtrl); + this.tabPage3.Location = new System.Drawing.Point(4, 29); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Size = new System.Drawing.Size(679, 507); + this.tabPage3.Text = "Logs"; + this.logsCfgCtrl.Location = new System.Drawing.Point(0, 0); + this.logsCfgCtrl.Size = new System.Drawing.Size(679, 507); // PoseidonCfgCtrl // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); @@ -394,6 +406,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader private System.Windows.Forms.Label nameLabel; private System.Windows.Forms.Label classNameLabel; private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.TabPage tabPage3; + private PoseidonLogsCfgCtrl logsCfgCtrl; private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.TextBox headPortNrTextBox; private System.Windows.Forms.Label label2; diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.Designer.cs b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.Designer.cs new file mode 100644 index 000000000..e4dda27f7 --- /dev/null +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.Designer.cs @@ -0,0 +1,30 @@ +namespace TBF.Rig.RegisterReaders.PoseidonReader +{ + partial class PoseidonLogsCfgCtrl + { + private System.ComponentModel.IContainer components = null; + protected override void Dispose(bool disposing) { if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } + private void InitializeComponent() + { + groupBox = new System.Windows.Forms.GroupBox(); enableDetailedLogCheckBox = new System.Windows.Forms.CheckBox(); diagnosticFormatLabel = new System.Windows.Forms.Label(); diagnosticFormatComboBox = new System.Windows.Forms.ComboBox(); rawFormatLabel = new System.Windows.Forms.Label(); rawFormatComboBox = new System.Windows.Forms.ComboBox(); includeParsedValuesCheckBox = new System.Windows.Forms.CheckBox(); + groupBox.SuspendLayout(); SuspendLayout(); + groupBox.Controls.Add(enableDetailedLogCheckBox); groupBox.Controls.Add(diagnosticFormatLabel); groupBox.Controls.Add(diagnosticFormatComboBox); groupBox.Controls.Add(rawFormatLabel); groupBox.Controls.Add(rawFormatComboBox); groupBox.Controls.Add(includeParsedValuesCheckBox); + groupBox.Location = new System.Drawing.Point(5,4); groupBox.Name = "groupBox"; groupBox.Size = new System.Drawing.Size(650,161); groupBox.TabStop = false; groupBox.Text = "Optical diagnostic logging"; + enableDetailedLogCheckBox.AutoSize = true; enableDetailedLogCheckBox.Enabled = false; enableDetailedLogCheckBox.Location = new System.Drawing.Point(18,30); enableDetailedLogCheckBox.Text = "Enable detailed log per WM"; + diagnosticFormatLabel.AutoSize = true; diagnosticFormatLabel.Location = new System.Drawing.Point(18,68); diagnosticFormatLabel.Text = "Output format:"; + diagnosticFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; diagnosticFormatComboBox.Enabled = false; diagnosticFormatComboBox.Location = new System.Drawing.Point(212,64); diagnosticFormatComboBox.Size = new System.Drawing.Size(160,28); + rawFormatLabel.AutoSize = true; rawFormatLabel.Location = new System.Drawing.Point(18,106); rawFormatLabel.Text = "RAW data encoding:"; + rawFormatComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; rawFormatComboBox.Enabled = false; rawFormatComboBox.Location = new System.Drawing.Point(212,102); rawFormatComboBox.Size = new System.Drawing.Size(160,28); + includeParsedValuesCheckBox.AutoSize = true; includeParsedValuesCheckBox.Enabled = false; includeParsedValuesCheckBox.Location = new System.Drawing.Point(393,105); includeParsedValuesCheckBox.Text = "Include parsed values"; + AutoScaleDimensions = new System.Drawing.SizeF(9F,20F); AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; Controls.Add(groupBox); Name = "PoseidonLogsCfgCtrl"; Size = new System.Drawing.Size(679,507); + groupBox.ResumeLayout(false); groupBox.PerformLayout(); ResumeLayout(false); + } + private System.Windows.Forms.GroupBox groupBox; + private System.Windows.Forms.CheckBox enableDetailedLogCheckBox; + private System.Windows.Forms.Label diagnosticFormatLabel; + private System.Windows.Forms.ComboBox diagnosticFormatComboBox; + private System.Windows.Forms.Label rawFormatLabel; + private System.Windows.Forms.ComboBox rawFormatComboBox; + private System.Windows.Forms.CheckBox includeParsedValuesCheckBox; + } +} diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.cs new file mode 100644 index 000000000..ac715a0c9 --- /dev/null +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.cs @@ -0,0 +1,55 @@ +using System; +using System.Windows.Forms; +using Common; + +namespace TBF.Rig.RegisterReaders.PoseidonReader +{ + public partial class PoseidonLogsCfgCtrl : UserControl + { + private PoseidonCfg config; + private bool unlocked; + public PoseidonLogsCfgCtrl() + { + InitializeComponent(); + diagnosticFormatComboBox.Items.AddRange(new object[] { "Csv", "Log" }); + rawFormatComboBox.Items.AddRange(new object[] { "Disabled", "Hex", "Ascii", "Text", "Base64" }); + enableDetailedLogCheckBox.CheckedChanged += (s, e) => UpdateEnabledState(); + } + public PoseidonCfg Config { get { return config; } set { config = value; Redraw(); } } + public void Unlock() { unlocked = true; enableDetailedLogCheckBox.Enabled = true; UpdateEnabledState(); } + public CfgUpdateFlags VerifyCfg(ref string message) + { + if (config == null || diagnosticFormatComboBox.SelectedItem == null || rawFormatComboBox.SelectedItem == null) + { + message += Environment.NewLine + "Optical diagnostic logging configuration is not valid."; + return CfgUpdateFlags.Error; + } + return CfgUpdateFlags.None; + } + public CfgUpdateFlags UpdateCfg() + { + if (config == null) return CfgUpdateFlags.Error; + config.EnableDetailedLogPerWM = enableDetailedLogCheckBox.Checked; + config.OpticalDiagnosticFormat = diagnosticFormatComboBox.Text; + config.RawOpticalDataFormat = rawFormatComboBox.Text; + config.IncludeParsedValues = includeParsedValuesCheckBox.Checked; + return CfgUpdateFlags.RestartRqrd; + } + private void Redraw() + { + if (config == null) return; + enableDetailedLogCheckBox.Checked = config.EnableDetailedLogPerWM; + diagnosticFormatComboBox.SelectedItem = string.IsNullOrEmpty(config.OpticalDiagnosticFormat) ? "Csv" : config.OpticalDiagnosticFormat; + rawFormatComboBox.SelectedItem = string.IsNullOrEmpty(config.RawOpticalDataFormat) ? "Hex" : config.RawOpticalDataFormat; + includeParsedValuesCheckBox.Checked = config.IncludeParsedValues; + UpdateEnabledState(); + } + private void UpdateEnabledState() + { + bool enabled = unlocked && enableDetailedLogCheckBox.Checked; + diagnosticFormatComboBox.Enabled = enabled; + rawFormatComboBox.Enabled = enabled; + includeParsedValuesCheckBox.Enabled = enabled; + } + } +} diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.resx b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.resx new file mode 100644 index 000000000..13913bdda --- /dev/null +++ b/TBF/Rig/RegisterReaders/PoseidonReader/PoseidonLogsCfgCtrl.resx @@ -0,0 +1,7 @@ + + + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/SmartReader.cs b/TBF/Rig/RegisterReaders/PoseidonReader/SmartReader.cs index d296ee0d5..ad31a7279 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/SmartReader.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/SmartReader.cs @@ -45,6 +45,11 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader public const int StartEndFilterSamplesCount2 = 20; /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1 public const int FeatureVectorSize = 9; + private readonly PoseidonOpticalDiagnosticWriter opticalDiagnosticWriter = new PoseidonOpticalDiagnosticWriter(); + private DateTime? diagnosticFirstUtc; + private uint? diagnosticLastAccumulator; + private long diagnosticExtendedAccumulator; + readonly PoseidonCfg _poseidonCfg; @@ -541,6 +546,10 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader OptoHeadService.Con connection = new OptoHeadService.Con($"COM{iHeadCfg.OptoComPortNr}", iHeadCfg.DebugLevel); _optoHeadService = new OptoHeadService(connection); + diagnosticFirstUtc = null; + diagnosticLastAccumulator = null; + diagnosticExtendedAccumulator = 0; + _optoHeadService.DiagnosticReceived = CaptureOpticalDiagnostic; return _optoHeadService.CreateSerialConnection(); } } @@ -572,6 +581,36 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader return false; } + private void CaptureOpticalDiagnostic(byte[] raw, DateTime receivedUtc, + WaterMetrologyData data, bool simulatedPayload) + { + if (_poseidonCfg == null) return; + WaterMetrologyDataC2 parsed = data == null ? null : (data.C7Data ?? data.C2Data); + bool wrapped = false; + long? extended = null; + if (parsed != null && !simulatedPayload) + { + diagnosticExtendedAccumulator = PoseidonOpticalDiagnosticWriter.ExtendAccumulator( + parsed.Accumulator, diagnosticLastAccumulator, diagnosticExtendedAccumulator, out wrapped); + diagnosticLastAccumulator = parsed.Accumulator; + extended = diagnosticExtendedAccumulator; + } + if (!diagnosticFirstUtc.HasValue) diagnosticFirstUtc = receivedUtc; + opticalDiagnosticWriter.Write(_poseidonCfg.EnableDetailedLogPerWM, + _poseidonCfg.OpticalDiagnosticFormat, _poseidonCfg.RawOpticalDataFormat, + _poseidonCfg.IncludeParsedValues, Sequences.ProcessData.BatchNr, + Position > 0 ? Position.ToString() : Name, Name, SerialNr, receivedUtc, + simulatedPayload ? "OpticalService/SimulatedPayload" : "OpticalService/DriverBytes", + parsed == null ? "ParseRejected" : "Parsed", + raw, null, + parsed == null ? (object)null : parsed.Accumulator, + extended, wrapped, + parsed == null ? (object)null : parsed.FlowRateLPerS, + parsed == null ? (object)null : wmVolume, + (receivedUtc - diagnosticFirstUtc.Value).TotalSeconds, + null, null, "Stream", data == null ? null : data.ToString()); + } + private void OnOptoHandler(object sender, CommonRR.IPerl.communication.OptoReceivedEventArgs e) { //received data from optohead diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/communication/C7/OptoHeadService.cs b/TBF/Rig/RegisterReaders/PoseidonReader/communication/C7/OptoHeadService.cs index c3d56d35d..ce7ff2ddd 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/communication/C7/OptoHeadService.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/communication/C7/OptoHeadService.cs @@ -64,6 +64,9 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.communication.C7 /// public WaterMetrologyData WaterMetrologyData { get; private set; } + // Driver bytes and parse outcome, including rejected packets. + public Action DiagnosticReceived { get; set; } + public OptoHeadService(Con con) { Connection = con; @@ -151,12 +154,25 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader.communication.C7 while (!dissableRunLoop) { _bRunStarted = true; - WaterMetrologyData = ParseData(RunReading()); + byte[] rawDiagnosticData = RunReading(); + DateTime receivedUtc = DateTime.UtcNow; + WaterMetrologyData = ParseData(rawDiagnosticData); if (OnOptoReceivedHandler != null && WaterMetrologyData != null) { log.Debug($"Received OptoData: {WaterMetrologyData}"); OnOptoReceivedHandler?.Invoke(this, new CommonRR.IPerl.communication.OptoReceivedEventArgs(WaterMetrologyData?.ToString(), WaterMetrologyData)); } + try + { + // Reflect the CURRENT ParseData branch, including its legacy + // simulation condition. Do not label simulated values as wire data. + DiagnosticReceived?.Invoke(rawDiagnosticData, receivedUtc, WaterMetrologyData, + Connection?.DebugModeSetting != DebugMode.Simulate); + } + catch (Exception ex) + { + log.Warn("Poseidon optical diagnostic callback failed.", ex); + } } } diff --git a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs index 5471e2582..18ae3a2d0 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHead.cs @@ -1107,6 +1107,15 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead timestampExtLast); } + NotifyOpticalTelegramCaptured( + line, + bytes, + data, + optoDataCount, + true, + data == null ? Double.NaN : volumeRawExtLast, + data == null ? Double.NaN : timestampExtLast); + optoDataCount++; } else @@ -1115,6 +1124,15 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead DiagnosticLedState7Data data = (DiagnosticLedState7Data)parser.ParseLine(line, false); + NotifyOpticalTelegramCaptured( + line, + bytes, + data, + optoDataCount, + false, + data == null ? Double.NaN : volumeRawExtLast, + data == null ? Double.NaN : timestampExtLast); + flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast); } } @@ -1133,6 +1151,22 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead } } + /// + /// Gives a derived register-reader family access to the exact optical + /// line received from the serial port. The default implementation keeps + /// legacy iPerl behavior unchanged. + /// + protected virtual void NotifyOpticalTelegramCaptured( + string rawLine, + byte[] rawBytes, + DiagnosticLedState7Data parsedData, + int telegramIndex, + bool isMeasurementData, + double extendedVolume, + double extendedTimestamp) + { + } + public string ReadOptoData() { if (optoSerialPort is null) return ""; diff --git a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfg.cs b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfg.cs index 1cea9fee0..5a75b462f 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfg.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfg.cs @@ -13,7 +13,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(IperlHeadCfg) })[0]; public override XmlSerializer GetSerializer() { return Serializer; } - public IComponentCfgCtrl GetControl(IList cmpntEntities) { return new IperlHeadCfgCtrl(); } + public virtual IComponentCfgCtrl GetControl(IList cmpntEntities) { return new IperlHeadCfgCtrl(); } /// /// Serialized parameters diff --git a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfgCtrl.cs b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfgCtrl.cs index 20992b2a6..3a13cd997 100644 --- a/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfgCtrl.cs +++ b/TBF/Rig/TestMethods/iPerlCommunication/iPerlHead/IperlHeadCfgCtrl.cs @@ -30,6 +30,14 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead InitializeComponent(); } + public void ShowOnlyTab(bool manualTest) + { + tabControl1.Appearance = TabAppearance.FlatButtons; + tabControl1.ItemSize = new System.Drawing.Size(0, 1); + tabControl1.SizeMode = TabSizeMode.Fixed; + tabControl1.SelectedTab = manualTest ? tabPage2 : tabPage1; + } + private void WaterMeterCfgCtrl_Load(object sender, EventArgs e) { nameLabel.Text = Strings.Name; diff --git a/TBF/SampleConfig/log4netConfig.xml b/TBF/SampleConfig/log4netConfig.xml index 5df2f7d5f..3a8a349ce 100644 --- a/TBF/SampleConfig/log4netConfig.xml +++ b/TBF/SampleConfig/log4netConfig.xml @@ -53,6 +53,21 @@ + + + + + + + + + + + + + + + @@ -74,6 +89,11 @@ + + + + + @@ -89,4 +109,21 @@ + + + + + + + + + + + + + + + + + diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index b4ec4d2bf..433196edb 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -1630,6 +1630,8 @@ UserControl + + @@ -1640,6 +1642,12 @@ AllyReaderCfgCtrl.cs + + UserControl + + + AllyReaderLogsCfgCtrl.cs + UserControl @@ -1659,6 +1667,20 @@ + + + + UserControl + + + AsicReaderCfgCtrl.cs + + + UserControl + + + AsicReaderLogsCfgCtrl.cs + GenesisCfgCtrl.cs @@ -1802,6 +1824,12 @@ PoseidonCfgCtrl.cs + + UserControl + + + PoseidonLogsCfgCtrl.cs + UserControl @@ -3886,9 +3914,15 @@ AllyReaderCfgCtrl.cs + + AllyReaderLogsCfgCtrl.cs + AllyReaderManualTestCtrl.cs + + AsicReaderLogsCfgCtrl.cs + IPerlUniCfgCtrl.cs @@ -3919,6 +3953,9 @@ PoseidonCfgCtrl.cs + + PoseidonLogsCfgCtrl.cs + PoseidonHeadTestCtrl.cs