using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Linq; using Newtonsoft.Json; using Xylem.Common.Hardware.Interfaces.Ports.PortCore; using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.Properties; using Xylem.Common.Hardware.WaterMeter.WaterMeterCore; using Xylem.Common.Utils.ProcessExec; using Xylem.Common.Utils.ProcessExec.EventArguments; namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore { /// /// Port scanner: /// using the Windows Device Manager listed ports, /// tries to open the port with an exception if it cannot be accessed (very time-consuming), /// tries to use the request protocol to detect a Genesis device. /// public class MeterPortScanner : IProcessState, IDisposable { /// /// Genesis for test access /// private GenesisMeter _currentGenesis; private MeterBatch _meterBatch; private readonly String _portType; /// /// Auto-detected port name /// public String AutoDetectedPortName; /// /// Port scan result event for message dispatcher to caller /// public event EventHandler OnProcessUpdate; /// /// Stop the port scan /// public Boolean StopPortScan; /// /// Number of ports /// public Int32 NumberOfPorts; /// /// Actual Port Counter /// public Int32 ActualPortCtr; /// /// Returns the port scan state /// /// /// /// - Initial /// public String GetPortScanState() { return $@"{Resources.StrScanPort} {ActualPortCtr}/{NumberOfPorts}"; } /// /// Ctor /// /// true if one port has been validated /// /// - Initial /// /// type of communication port to meter like IrDA public MeterPortScanner(String portType) { _portType = portType; } /// /// /// - Initial. /// /// /// - Remove all meters removed as _meterBatch.Dispose will remove all meters. /// public void Dispose() { // removal from meter batch includes a disposal of meter _meterBatch?.Dispose(); } /// /// Use initially the port configuration to speed up search, /// If configuration file contains wrong port setup than scan all serial ports listed in /// the windows device manager, /// Read available ports, /// Try to open port and connect to Genesis meter. /// /// slot to search for /// configuration of port to speed up search /// true if port found /// true if one port has been validated /// /// - Initial /// /// /// - Port type from Ctor, /// - Event message changed: /// - Overall message: ctr / counts - ongoing scan, /// - Actual message: Information to log /// /// /// - Changed exit of function, /// - Added configuration file handling. /// /// /// - Remove all meters removed as _meterBatch.Dispose will remove all meters. /// public Boolean ScanAllSerialPorts(Int32 slot, String portConfigFilePathName) { var serialPort = new SerialPort(); var serialPorts = new List(); _meterBatch = new MeterBatch(); serialPorts.AddRange(SerialPort.GetPortNames()); NumberOfPorts = serialPorts.Count; ActualPortCtr = 0; // If configuration file exists, read it and check for slot setup. This will speed up the recurrent detection // process as at the end the detection process the detected slot will be saved. var slotPortConfigs = new List(); if (portConfigFilePathName != null && File.Exists(portConfigFilePathName)) { using (var tr = new StreamReader(portConfigFilePathName)) { var fileStream = tr.ReadToEnd(); var slots = JsonConvert.DeserializeObject(fileStream); if (slots != null && slots.Length > 0) slotPortConfigs.AddRange(slots); } foreach (var slotPortConfig in slotPortConfigs.Where(slotPortConfig => slotPortConfig.Slot == slot && !string.IsNullOrEmpty(slotPortConfig.Request.PortName))) { // add port on bottom of port list even if it is doubled that way NumberOfPorts++; serialPorts.Add(slotPortConfig.Request.PortName); break; } } OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs("", 0, $@"{NumberOfPorts} {Resources.StrPortListDeviceManger}")); var serialPortDetected = false; StopPortScan = false; // start with last port,this might be added at current connection of interface for (var ctr = serialPorts.Count - 1; ctr >= 0; ctr--) { if (StopPortScan) break; ActualPortCtr++; serialPort.PortName = serialPorts[ctr]; var overallProcessCtrPercent = 100.0 * ActualPortCtr / (NumberOfPorts > 0 ? NumberOfPorts : 1); OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(GetPortScanState(), overallProcessCtrPercent, $@"{serialPort.PortName}: {Resources.StrPortAccessRequest}")); // skip all ports which are already opened, this might be another customer if (serialPort.IsOpen) continue; PortConfig portConfig; try { // removal from meter batch includes a disposal of meter _meterBatch.RemoveAllMeters(); portConfig = new PortConfig { PortName = serialPort.PortName, Type = _portType }; _currentGenesis = new GenesisMeter(slot, portConfig, null); if (_currentGenesis == null) return false; // configuration has to be set BEFORE adding meter to batch to avoid e.g. auto update files // from network and therefore have a long network request timeout before the task starts _currentGenesis.Configuration.UseRegisterWatchService = false; _currentGenesis.Configuration.UseMinMaxCheck = false; _currentGenesis.Configuration.AutoUpdateFiles = false; _meterBatch.AddMeter(_currentGenesis); } catch (Exception) { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(GetPortScanState(), overallProcessCtrPercent, $@"{serialPort.PortName}: {Resources.StrPortFailedToOpen}")); continue; } // if port cannot be opened on access to the port is senseless, try the next port if (_currentGenesis != null && !_currentGenesis.RequestPort.IsOpen()) continue; OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(GetPortScanState(), overallProcessCtrPercent, $@"{serialPort.PortName}: {Resources.StrPortSuccessfullyAccessed}")); // read PCB ID from meter, this is the indicator, that a Genesis is connected to this port if (_currentGenesis == null || string.Empty == _currentGenesis.GetPcbId()) { OnProcessUpdate?.Invoke(this, new ProcessExecEventArgs(GetPortScanState(), overallProcessCtrPercent, $@"{serialPort.PortName}: {Resources.StrRequestPortDetectionFailed}")); continue; } AutoDetectedPortName = serialPort.PortName; serialPortDetected = true; // check if needed to store new configuration if slot and port name is not in file if (!slotPortConfigs.Any(c => c.Slot == slot && c.Request.PortName == AutoDetectedPortName)) { slotPortConfigs.Add(new SlotConfig() { Slot = slot, Request = portConfig }); var text = JsonConvert.SerializeObject(slotPortConfigs); if (portConfigFilePathName != null) File.WriteAllText(portConfigFilePathName, text); } // stop loop if one Genesis has been detected break; } serialPort.Dispose(); // removal from meter batch includes a disposal of meter _meterBatch?.Dispose(); return serialPortDetected; } } }