diff --git a/DataStreamMeter/MeterSimulation.cs b/DataStreamMeter/MeterSimulation.cs index 823fe5602..47ac01eb8 100644 --- a/DataStreamMeter/MeterSimulation.cs +++ b/DataStreamMeter/MeterSimulation.cs @@ -86,7 +86,7 @@ namespace DataStreamMeter public int GetMetersCount() { - return 1; + return 20; } public bool OpenConnection(int meterIx, string connectionParameters, out string meterID) diff --git a/TBF/BenchControl/RegisterReaders/DataStream/MefImport/Factory.cs b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/Factory.cs new file mode 100644 index 000000000..8f0707c32 --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/Factory.cs @@ -0,0 +1,24 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System.Collections.Generic; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.RegisterReaders.DataStream.MefImport +{ + public class Factory : IComponentFactory + { + public string ClassName { get { return GetType().Namespace.Substring(17); } } + + public IComponent DummyComponent() { return new MefImport(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new MefImport(cfg); } + + public IComponentCfg DefaultConfig() { return new MefImportCfg(this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(MefImportCfg.Serializer, component, this); + } + } +} diff --git a/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImport.cs b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImport.cs new file mode 100644 index 000000000..94de854d2 --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImport.cs @@ -0,0 +1,67 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using log4net; +using Config.Entities; +using DataStreamInterface; + +namespace TBF.BenchControl.RegisterReaders.DataStream.MefImport +{ + /// + /// MEF interface to an external data stream reader component. + /// + public class MefImport : ComponentBase + { + private static readonly ILog log = LogManager.GetLogger(typeof(MefImport)); + public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + + const Int64 MIN_SAMPLES_COUNT = 20; + public Int64 MinSamplesCount { get { return MIN_SAMPLES_COUNT; } } + + + [Import(typeof(IDataStreamMeter))] + public IDataStreamMeter DataStreamIFace; + + + readonly MefImportCfg myCfg; + public string CatalogDir { get { return myCfg.CatalogDir; } } + + + public MefImport() { } + + public MefImport(Generic.IComponentCfg cfg) + : base(cfg) + { + myCfg = cfg as MefImportCfg; + } + + public override void Initialize() + { + if (DebugLevel == DebugMode.Normal) + { + var catalog = new AggregateCatalog(); + catalog.Catalogs.Add(new AssemblyCatalog(typeof(DataStreamInterface.IDataStreamMeter).Assembly)); + catalog.Catalogs.Add(new DirectoryCatalog(CatalogDir)); + (new CompositionContainer(catalog)).ComposeParts(this); + + log.FatalFormat("{0} initialized: {1}", Name, this); + log.FatalFormat(" Meters count = {0}", DataStreamIFace.GetMetersCount()); + log.FatalFormat(" Time unit = {0}", DataStreamIFace.GetTimeUnits().ToString()); + log.FatalFormat(" Volume unit = {0}", DataStreamIFace.GetVolumeUnits().ToString()); + for (int i = 0; i < DataStreamIFace.GetQuantitiesCount(); i++) + { + log.FatalFormat(" Quantity_{0} = {1}, unit = {2}", i + 1, + DataStreamIFace.GetQuantityCaption(i), + DataStreamIFace.GetQuantityUnits(i).ToString()); + } + } + else + { + log.FatalFormat("{0} simulated: {1}", Name, this); + } + } + } +} diff --git a/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImportCfg.cs b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImportCfg.cs new file mode 100644 index 000000000..8dfb5bbb1 --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/MefImport/MefImportCfg.cs @@ -0,0 +1,122 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using Config.Entities; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.RegisterReaders.DataStream.MefImport +{ + /// + /// Holds information identifying the test bench - serializable configuration. + /// + public class MefImportCfg : ComponentCfgBase, Generic.IComponentCfg, Config.Entities.IParamsProvider + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(MefImportCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public IComponentCfgCtrl GetControl() { return new Configs.ParamsProvider.ComponentCfgCtrl(this, null); } + + /// + /// Serialized parameters + /// + public string CatalogDir; + + + /// Private parameterless constructor invoked by all other (public) constructors + MefImportCfg() {} + + public MefImportCfg(IComponentFactory factory) + : this() + { + Factory = factory; + Name = "MefImport"; + ParentName = string.Empty; + InitializeAll(); + } + + public string ComponentName { get { return Name; } } + + public void InitializeAll() + { + CatalogDir = "..\\..\\..\\DataStreamMeter\\bin"; + } + + string[] paramNames = new string[] + { + "Catalog directory", + }; + public string ParamName(int i) { return paramNames[i]; } + public int ParamsCount() { return paramNames.Length; } + + public IList ParamValues(int i) + { + switch (i) + { + case 0: + return null; + default: + return null; + } + } + + public string ToString(int i) + { + switch (i) + { + case 0: + return CatalogDir; + default: + return string.Format("{0}: CatalogDir = {1}", Name, CatalogDir); + } + } + + public CfgUpdateFlags UpdateParam(int i, string strValue) + { + switch (i) + { + case 0: + CatalogDir = strValue; + return CfgUpdateFlags.RestartRqrd; + default: + return CfgUpdateFlags.None; + } + } + + public bool ValidateParam(int i, string strValue, out string message) + { + message = string.Empty; + + switch (i) + { + case 0: + return true; + default: + message = "Invalid index"; + return false; + } + + message = ParamName(i) + " is invalid"; + return false; + } + + void CopyContentTo(MefImportCfg prms) + { + prms.CatalogDir = this.CatalogDir; + } + + public Config.Entities.IParamsProvider Clone() + { + MefImportCfg pars = new MefImportCfg(); + CopyContentTo(pars); + return pars; + } + + public bool UpdateEmbeddedDbEntity() + { + return true; /// =OK, do nothing + } + } +} diff --git a/TBF/BenchControl/RegisterReaders/DataStream/Reader/Factory.cs b/TBF/BenchControl/RegisterReaders/DataStream/Reader/Factory.cs new file mode 100644 index 000000000..97cb4da1b --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/Reader/Factory.cs @@ -0,0 +1,24 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System.Collections.Generic; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.RegisterReaders.DataStream.Reader +{ + public class Factory : IComponentFactory + { + public string ClassName { get { return GetType().Namespace.Substring(17); } } + + public IComponent DummyComponent() { return new Reader(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new Reader(cfg, components); } + + public IComponentCfg DefaultConfig() { return new ReaderCfg(this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(ReaderCfg.Serializer, component, this); + } + } +} diff --git a/TBF/BenchControl/RegisterReaders/DataStream/Reader/Reader.cs b/TBF/BenchControl/RegisterReaders/DataStream/Reader/Reader.cs new file mode 100644 index 000000000..44322ddc7 --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/Reader/Reader.cs @@ -0,0 +1,209 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.Xml.Serialization; +using log4net; +using Config.Entities; +using DataStreamInterface; +using TBF.BenchControl; +using TBF.BenchControl.Generic; +using TBF.BenchControl.RegisterReaders.DataStream; + +namespace TBF.BenchControl.RegisterReaders.DataStream.Reader +{ + /// + /// Holds information identifying the test bench. + /// + public class Reader : ComponentBase, IOperation, GenericDevices.IRegReaderDatastream + { + private static readonly ILog log = LogManager.GetLogger(typeof(Reader)); + public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + + const Int64 MinSamplesCount = 20; + + MefImport.MefImport mefImport; + Int64 minSamplesCount { get { return mefImport.MinSamplesCount; } } + + /// + /// IRegReader interface part 1 + /// + readonly ReaderCfg myCfg; + public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.DataStream; } } + public int Position { get { return (myCfg != null) ? myCfg.Position : 1; } } + public double PulsesPerLtr { get { return 1.0; } } + public double LtrsPerPulse { get { return 1.0; } } + + string waterMeterID; + Int64 storedFramesCount; + Int64 startIx; + Int64 endIx; + Dictionary samplesDict; + DataStreamInterface.Unit timeUnit; + DataStreamInterface.Unit volumeUnit; + + + public Reader() { } + + public Reader(Generic.IComponentCfg cfg, IList components) + : base(cfg) + { + myCfg = cfg as ReaderCfg; + + mefImport = TbfComponents.FindComponent(cfg.ParentName, components) as MefImport.MefImport; + if (mefImport == null) throw new Exception("Cannot find " + Name + " parent"); + } + + public override void Initialize() + { + if (DebugLevel == DebugMode.Normal) + { + if (Position < 1 || Position > Math.Min(Config.Data.WMsCount, mefImport.DataStreamIFace.GetMetersCount())) + { + throw new ArgumentException("Position is out of range"); + } + + samplesDict = new Dictionary(); + + log.FatalFormat("{0} initialized: {1}", Name, this); + } + else + { + log.FatalFormat("{0} simulated: {1}", Name, this); + } + } + + + /// + /// IRegReader interface part 2 + /// + int wmPulses; + int wmRefPulses; + double wmVolume; + double beginWMState; + double endWMState; + /// + public int WMPulses { get { return wmPulses; } } /// Counted pulses of the water meter + public int WMRefPulses { get { return wmRefPulses; } } /// 'Gated' reference pulses of the water meters + public double WMVolume { get { return wmVolume; } } /// Counted volume of the water meter + public double BeginWMState { get { return beginWMState; } } /// Test begin state of the water meter + public double EndWMState { get { return endWMState; } } /// Test end state of the water meter + + + /// To be used at the beginning of a test + public void Clear() + { + storedFramesCount = 0; + startIx = -1; + endIx = -1; + beginWMState = 0; + endWMState = 0; + wmVolume = 0; + wmPulses = 0; + wmRefPulses = 0; + samplesDict.Clear(); + timeUnit = DataStreamInterface.Unit.s; + volumeUnit = DataStreamInterface.Unit.l; + } + + /// To be used when the test is completed ... + public void TestCompleted() + { + mefImport.DataStreamIFace.CloseConnection(Position - 1); + } + + public IOperation ReadRegisterOp() + { + return this; + } + + /// + /// IOperation interface + /// + public void Start() + { + Clear(); + mefImport.DataStreamIFace.OpenConnection(Position - 1, "", out waterMeterID); + timeUnit = mefImport.DataStreamIFace.GetTimeUnits(); + volumeUnit = mefImport.DataStreamIFace.GetVolumeUnits(); + mefImport.DataStreamIFace.StartMeasurement(Position - 1); + } + + public Event Run() + { + return Event.None; + } + + public void Stop() + { + mefImport.DataStreamIFace.StopMeasurement(Position - 1, out storedFramesCount); + + if (storedFramesCount >= minSamplesCount) + { + startIx = storedFramesCount / minSamplesCount; + endIx = (storedFramesCount * (minSamplesCount - 1) / minSamplesCount) - 1; + + beginWMState = VolumeLtrStart; + endWMState = VolumeLtrEnd; + wmVolume = Math.Abs(VolumeLtrEnd - VolumeLtrStart); + wmPulses = (int)Math.Round(wmVolume * PulsesPerLtr); + wmRefPulses = StateMachine.ControlBoard.EtPulses(0); + } + } + + /// ... for more accurate WMPulses, WMRefPulses calculation + + + /// + /// IRegReaderDatastream interface + /// + public bool NoSamples { get { return storedFramesCount < minSamplesCount || startIx < 0 || endIx < 0; } } + public double VolumeLtrStart { get { return (startIx >= 0) ? GetVolumeFromSamples(startIx) : 0; } } + public double VolumeLtrEnd { get { return (endIx >= 0) ? GetVolumeFromSamples(endIx) : 0; } } + public double TimestampSecStart { get { return (startIx >= 0) ? GetTimeFromSamples(startIx) : 0; } } + public double TimestampSecEnd { get { return (endIx >= 0) ? GetTimeFromSamples(endIx) : 0; } } + + double GetTimeFromSamples(Int64 ix) + { + DataFrame oneFrame; + DataFrame[] frames; + + if (samplesDict.TryGetValue(ix, out oneFrame)) + { + return DataStreamInterface.Units.ConvertFrom(timeUnit, oneFrame.Time); + } + else if ((frames = mefImport.DataStreamIFace.GetFrames(Position - 1, ix, 1)).Length == 1) + { + samplesDict.Add(ix, frames[0]); + return DataStreamInterface.Units.ConvertFrom(timeUnit, frames[0].Time); + } + else + { + return 0; + } + } + + double GetVolumeFromSamples(Int64 ix) + { + DataFrame oneFrame; + DataFrame[] frames; + + if (samplesDict.TryGetValue(ix, out oneFrame)) + { + return DataStreamInterface.Units.ConvertFrom(volumeUnit, oneFrame.Volume); + } + else if ((frames = mefImport.DataStreamIFace.GetFrames(Position - 1, ix, 1)).Length == 1) + { + samplesDict.Add(ix, frames[0]); + return DataStreamInterface.Units.ConvertFrom(volumeUnit, frames[0].Volume); + } + else + { + return 0; + } + } + } +} diff --git a/TBF/BenchControl/RegisterReaders/DataStream/Reader/ReaderCfg.cs b/TBF/BenchControl/RegisterReaders/DataStream/Reader/ReaderCfg.cs new file mode 100644 index 000000000..748250b32 --- /dev/null +++ b/TBF/BenchControl/RegisterReaders/DataStream/Reader/ReaderCfg.cs @@ -0,0 +1,132 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using Config.Entities; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.RegisterReaders.DataStream.Reader +{ + /// + /// Holds information identifying the test bench - serializable configuration. + /// + public class ReaderCfg : ComponentCfgBase, Generic.IChildComponentCfg, Config.Entities.IParamsProvider + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(ReaderCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public IComponentCfgCtrl GetControl() + { + IList parents = new List(); + parents.Add("MefImport"); + + return new Configs.ParamsProvider.ComponentCfgCtrl(this, parents); + } + + /// + /// Serialized parameters + /// + public int Position; + + + /// Private parameterless constructor invoked by all other (public) constructors + ReaderCfg() {} + + public ReaderCfg(IComponentFactory factory) + : this() + { + Factory = factory; + Name = "Reader"; + ParentName = "MefImport"; + InitializeAll(); + } + + public string ComponentName { get { return Name; } } + + public void InitializeAll() + { + Position = 1; + } + + string[] paramNames = new string[] + { + "Position", + }; + public string ParamName(int i) { return paramNames[i]; } + public int ParamsCount() { return paramNames.Length; } + + public IList ParamValues(int i) + { + switch (i) + { + case 0: + IList retv = new List(); + for (int j = 1; j <= Config.Data.WMsCount; j++) retv.Add(j.ToString()); + return retv; + default: + return null; + } + } + + public string ToString(int i) + { + switch (i) + { + case 0: + return Position.ToString(); + default: + return string.Format("{0}: Position = {1}", Name, Position); + } + } + + public CfgUpdateFlags UpdateParam(int i, string strValue) + { + switch (i) + { + case 0: + Position = int.Parse(strValue); + return CfgUpdateFlags.RestartRqrd; + default: + return CfgUpdateFlags.None; + } + } + + public bool ValidateParam(int i, string strValue, out string message) + { + message = string.Empty; + int idummy; + + switch (i) + { + case 0: + if (int.TryParse(strValue, out idummy) && idummy > 0 && idummy <= Config.Data.WMsCount) return true; + break; + default: + message = "Invalid index"; + return false; + } + + message = ParamName(i) + " is invalid"; + return false; + } + + void CopyContentTo(ReaderCfg prms) + { + prms.Position = this.Position; + } + + public Config.Entities.IParamsProvider Clone() + { + ReaderCfg pars = new ReaderCfg(); + CopyContentTo(pars); + return pars; + } + + public bool UpdateEmbeddedDbEntity() + { + return true; /// =OK, do nothing + } + } +} diff --git a/TBF/BenchControl/TbfComponents.cs b/TBF/BenchControl/TbfComponents.cs index 3bb429694..18453d11c 100644 --- a/TBF/BenchControl/TbfComponents.cs +++ b/TBF/BenchControl/TbfComponents.cs @@ -176,6 +176,8 @@ namespace TBF.BenchControl Factories.Add(new RegisterReaders.PulsesFromEldeCB.Factory()); /// 'RegisterReader' Factories.Add(new Elde.FixedStartRegisterReader.RegisterReaderFactory()); /// 'RegisterReader for standing start/stop' Factories.Add(new TestMethods.iPerlCommunication.iPerlHead.Factory()); /// 'RegisterReader for iPerl' + Factories.Add(new RegisterReaders.DataStream.MefImport.Factory()); /// 'Interface for data stream stream via MEF' + Factories.Add(new RegisterReaders.DataStream.Reader.Factory()); /// 'RegisterReader for data stream stream via MEF' Factories.Add(new RegisterReaders.SerialStream.Factory()); /// 'RegisterReader for generic serial stream' Factories.Add(new RegisterReaders.KPackE.RegisterReader.Factory()); /// KPackE register reader Factories.Add(new RegisterReaders.KPackE.Radio.Factory()); /// Radio for KPackE register readers diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index 85d367aa8..e72086064 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("2.26.1682.0")] -[assembly: AssemblyFileVersion("2.26.1682.0")] +[assembly: AssemblyVersion("2.26.1683.0")] +[assembly: AssemblyFileVersion("2.26.1683.0")] diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 8f660583e..b1024cb5c 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -134,6 +134,7 @@ ..\packages\Renci.SshNet\Renci.SshNet.dll + @@ -1172,6 +1173,12 @@ WriterCfgCtrl.cs + + + + + + Form @@ -3712,6 +3719,10 @@ {743DF7DB-C7B6-42EB-986D-0F485E5588E4} Config + + {7ebeea14-91c4-48d7-af0a-7a4bc3ff9a28} + DataStreamInterface + {439D0878-C76E-452B-B17D-209A89E91D36} Dirichlet.Numerics