239 lines
9.9 KiB
C#
239 lines
9.9 KiB
C#
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
|
|
{
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class MeterPortScanner : IProcessState, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Genesis for test access
|
|
/// </summary>
|
|
private GenesisMeter _currentGenesis;
|
|
private MeterBatch _meterBatch;
|
|
private readonly String _portType;
|
|
|
|
/// <summary>
|
|
/// Auto-detected port name
|
|
/// </summary>
|
|
public String AutoDetectedPortName;
|
|
|
|
/// <summary>
|
|
/// Port scan result event for message dispatcher to caller
|
|
/// </summary>
|
|
public event EventHandler<ProcessExecEventArgs> OnProcessUpdate;
|
|
|
|
/// <summary>
|
|
/// Stop the port scan
|
|
/// </summary>
|
|
public Boolean StopPortScan;
|
|
|
|
/// <summary>
|
|
/// Number of ports
|
|
/// </summary>
|
|
public Int32 NumberOfPorts;
|
|
|
|
/// <summary>
|
|
/// Actual Port Counter
|
|
/// </summary>
|
|
public Int32 ActualPortCtr;
|
|
|
|
/// <summary>
|
|
/// Returns the port scan state
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <remarks date="2020-Oct-21" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
public String GetPortScanState()
|
|
{
|
|
return $@"{Resources.StrScanPort} {ActualPortCtr}/{NumberOfPorts}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ctor
|
|
/// </summary>
|
|
/// <returns>true if one port has been validated</returns>
|
|
/// <remarks date="2020-Dec-11" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <param name="portType">type of communication port to meter like IrDA</param>
|
|
public MeterPortScanner(String portType)
|
|
{
|
|
_portType = portType;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
|
|
/// - Initial.
|
|
/// </remarks>
|
|
/// <remarks date="2021-Mai-11" author="Thomas Wiedebusch">
|
|
/// - Remove all meters removed as _meterBatch.Dispose will remove all meters.
|
|
/// </remarks>
|
|
public void Dispose()
|
|
{
|
|
// removal from meter batch includes a disposal of meter
|
|
_meterBatch?.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="slot">slot to search for</param>
|
|
/// <param name="portConfigFilePathName">configuration of port to speed up search</param>
|
|
/// <returns>true if port found</returns>
|
|
/// <returns>true if one port has been validated</returns>
|
|
/// <remarks date="2020-Oct-14" author="Thomas Wiedebusch">
|
|
/// - Initial
|
|
/// </remarks>
|
|
/// <remarks date="2020-Dec-11" author="Thomas Wiedebusch">
|
|
/// - Port type from Ctor,
|
|
/// - Event message changed:
|
|
/// - Overall message: ctr / counts - ongoing scan,
|
|
/// - Actual message: Information to log
|
|
/// </remarks>
|
|
/// <remarks date="2021-Jan-05" author="Thomas Wiedebusch">
|
|
/// - Changed exit of function,
|
|
/// - Added configuration file handling.
|
|
/// </remarks>
|
|
/// <remarks date="2021-Mai-11" author="Thomas Wiedebusch">
|
|
/// - Remove all meters removed as _meterBatch.Dispose will remove all meters.
|
|
/// </remarks>
|
|
public Boolean ScanAllSerialPorts(Int32 slot, String portConfigFilePathName)
|
|
{
|
|
var serialPort = new SerialPort();
|
|
var serialPorts = new List<String>();
|
|
_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<SlotConfig>();
|
|
if (portConfigFilePathName != null && File.Exists(portConfigFilePathName))
|
|
{
|
|
using (var tr = new StreamReader(portConfigFilePathName))
|
|
{
|
|
var fileStream = tr.ReadToEnd();
|
|
var slots = JsonConvert.DeserializeObject<SlotConfig[]>(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;
|
|
}
|
|
}
|
|
}
|