1056 lines
35 KiB
C#
1056 lines
35 KiB
C#
using Logic.ProductionToProductMapper.Cordonel;
|
||
using Newtonsoft.Json;
|
||
using NLog;
|
||
using System;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Reflection;
|
||
using System.Security.Policy;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using Xylem.Common.CommonCore.Configuration;
|
||
using Xylem.Common.CommonCore.Consts;
|
||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Applications.Const;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.Consts;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
|
||
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
||
using Xylem.Common.Logic.ProductionOrderCore.TestResults;
|
||
using Xylem.Common.Logic.SoftwareAccessHelper;
|
||
using Xylem.Common.Utils.Logging;
|
||
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
|
||
using Access = Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Access;
|
||
using Register = Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Register;
|
||
|
||
namespace GenesisCordonelInterface.API
|
||
{
|
||
/// <summary>
|
||
/// Provides a public API for Genesis meter operations.
|
||
///
|
||
/// This class exposes reusable functionality extracted from the original UI code
|
||
/// so it can be used from other projects within the solution.
|
||
///
|
||
/// The API is intended to gradually consolidate meter-related operations such as:
|
||
/// - port detection
|
||
/// - PCB ID reading
|
||
/// - communication setup
|
||
/// - requests and commands
|
||
/// - additional service actions
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This class should contain business logic only and should not depend on UI elements
|
||
/// such as forms, controls, MessageBox, or DataGridView.
|
||
///
|
||
/// UI-specific code should remain outside this class and call this API instead.
|
||
/// </remarks>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new Api2();
|
||
///
|
||
/// var request = api.DetectRequestPort(3);
|
||
/// if (request.Success)
|
||
/// {
|
||
/// Console.WriteLine($"PCB ID: {request.PcbId}");
|
||
/// }
|
||
///
|
||
/// var streaming = api.DetectStreamingPort(3);
|
||
/// if (streaming.Success)
|
||
/// {
|
||
/// Console.WriteLine($"Streaming port: {streaming.PortName}");
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
public class InterfaceGCIToLaatzen
|
||
{
|
||
#region Declaration region
|
||
private static readonly Lazy<ILogger> Logger = new Lazy<ILogger>(() => NLogHelper.CreateOrGetLogger("GenesisCordonelInterface"));
|
||
|
||
public class regStore
|
||
{
|
||
public String PcbId;
|
||
public DateTimeOffset created;
|
||
public List<regDefValue> keyValues;
|
||
}
|
||
|
||
public class regDefValue
|
||
{
|
||
public RegisterDefinition def;
|
||
public String value;
|
||
}
|
||
|
||
private GenesisMeter _currentGenesis;
|
||
private MeterBatch _meterBatch = new MeterBatch();
|
||
private regStore _regsToStore;
|
||
private String _currentPcbId = "";
|
||
|
||
private Boolean IsBusy
|
||
{
|
||
get;
|
||
set;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a value indicating whether the meter is connected and logged on.
|
||
/// </summary>
|
||
public bool IsConnected
|
||
{
|
||
get
|
||
{
|
||
return _currentGenesis != null && _currentGenesis.IsLoggedOn;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region API - Port Detection region(extracted from FrmSetup:DgvConfig_CellContentClick)
|
||
|
||
public class PortDetectionResult
|
||
{
|
||
/// <summary>
|
||
/// Indicates whether the detection was successful.
|
||
/// </summary>
|
||
public bool Success { get; set; }
|
||
|
||
/// <summary>
|
||
/// Slot number used for the detection.
|
||
/// </summary>
|
||
public int Slot { get; set; }
|
||
|
||
/// <summary>
|
||
/// Name of the detected communication port.
|
||
/// </summary>
|
||
public string PortName { get; set; }
|
||
|
||
/// <summary>
|
||
/// PCB ID read from the device (available for request detection).
|
||
/// </summary>
|
||
public string PcbId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Error message describing why detection failed (if not successful).
|
||
/// </summary>
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
/// </summary>
|
||
/// <param name="slot">Slot number.</param>
|
||
/// <returns>
|
||
/// Result containing success status and detected port name.
|
||
/// </returns>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new Api2();
|
||
/// var result = api.DetectStreamingPort(3);
|
||
///
|
||
/// if (result.Success)
|
||
/// {
|
||
/// Console.WriteLine($"Port: {result.PortName}");
|
||
/// }
|
||
/// else
|
||
/// {
|
||
/// Console.WriteLine("Streaming detection failed");
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
public PortDetectionResult DetectStreamingPort(int slot)
|
||
{
|
||
if (slot <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slot));
|
||
|
||
using (var mb = new MeterBatch())
|
||
using (var meter = new GenesisMeter())
|
||
{
|
||
//meter.SetupFromConfigFile(slot, false);
|
||
mb.AddMeter(meter);
|
||
|
||
var rawData = new ConcurrentBag<string>();
|
||
|
||
meter.StreamingPort.OnRawRecordReceived += (o, rawMsg) =>
|
||
{
|
||
var data = (string)rawMsg.GetData();
|
||
rawData.Add(data);
|
||
};
|
||
|
||
Thread.Sleep(500);
|
||
|
||
var success = rawData.Any();
|
||
var portName = meter.StreamingPort.GetPortName();
|
||
|
||
return new PortDetectionResult
|
||
{
|
||
Success = success,
|
||
Slot = slot,
|
||
PortName = portName,
|
||
ErrorMessage = success ? null : "No streaming data received."
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Detects the request port by attempting to read the PCB ID.
|
||
/// </summary>
|
||
/// <param name="slot">Slot number.</param>
|
||
/// <returns>
|
||
/// Result containing success status, port name, and PCB ID if successful.
|
||
/// </returns>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new Api2();
|
||
/// var result = api.DetectRequestPort(3);
|
||
///
|
||
/// if (result.Success)
|
||
/// {
|
||
/// Console.WriteLine($"PCB ID: {result.PcbId}");
|
||
/// }
|
||
/// else
|
||
/// {
|
||
/// Console.WriteLine("Detection failed");
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
public PortDetectionResult DetectRequestPort(int slot)
|
||
{
|
||
if (slot <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slot));
|
||
|
||
using (var mb = new MeterBatch())
|
||
using (var meter = new GenesisMeter())
|
||
{
|
||
//meter.SetupFromConfigFile(slot, false);
|
||
mb.AddMeter(meter);
|
||
|
||
meter.Logout();
|
||
|
||
var pcbId = meter.GetPcbId();
|
||
var success = !string.IsNullOrEmpty(pcbId);
|
||
var portName = meter.RequestPort.GetPortName();
|
||
|
||
return new PortDetectionResult
|
||
{
|
||
Success = success,
|
||
Slot = slot,
|
||
PortName = portName,
|
||
PcbId = pcbId,
|
||
ErrorMessage = success ? null : "PCB ID was empty."
|
||
};
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region API - PCB ID region
|
||
|
||
/// <summary>
|
||
/// Reads PCB ID for the specified slot.
|
||
/// </summary>
|
||
/// <param name="slot">Slot number.</param>
|
||
/// <returns>PCB ID read from the meter.</returns>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new Api2();
|
||
/// string pcbId = api.GetPcbId(3);
|
||
/// Console.WriteLine(pcbId);
|
||
/// </code>
|
||
/// </example>
|
||
public string GetPcbId(int slot)
|
||
{
|
||
if (slot <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slot), "Slot number must be greater than zero.");
|
||
|
||
using (var mb = new MeterBatch())
|
||
using (var meter = new GenesisMeter())
|
||
{
|
||
meter.SetupFromConfigFile(slot, false);
|
||
mb.AddMeter(meter);
|
||
|
||
meter.Logout();
|
||
|
||
return meter.GetPcbId();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Password and Login
|
||
/// <summary>
|
||
/// Sets meter password.
|
||
/// </summary>
|
||
public bool SetMeterPassword(string password)
|
||
{
|
||
EnsureConnected();
|
||
|
||
if (string.IsNullOrWhiteSpace(password))
|
||
throw new ArgumentException("Password cannot be null or empty.", nameof(password));
|
||
|
||
try
|
||
{
|
||
// Replace this with the real Genesis API call if available.
|
||
// Example:
|
||
// return _currentGenesis.SetMeterPassword(password);
|
||
|
||
var result = WriteRegister("SECURITY_Password", password, true, true);
|
||
return result.Success;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Value.Error(ex, "SetMeterPassword failed.");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Performs login using provided password.
|
||
/// </summary>
|
||
public bool Login(string password)
|
||
{
|
||
if (_currentGenesis == null)
|
||
throw new InvalidOperationException("Genesis meter is not initialized. Call Connect first.");
|
||
|
||
if (string.IsNullOrWhiteSpace(password))
|
||
throw new ArgumentException("Password cannot be null or empty.", nameof(password));
|
||
|
||
try
|
||
{
|
||
// IMPORTANT:
|
||
// Replace with actual Genesis API method if available
|
||
|
||
// Variant A – direct login method (preferred)
|
||
// return _currentGenesis.Login(password);
|
||
|
||
// Variant B – if password must be set first
|
||
// _currentGenesis.Password = password;
|
||
// return _currentGenesis.Login();
|
||
|
||
// TEMP fallback (if no direct method known)
|
||
bool result = _currentGenesis.Login();
|
||
|
||
if (!result)
|
||
Logger.Value.Warn("Login failed.");
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Value.Error(ex, "Login failed.");
|
||
return false;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region API - Connect
|
||
|
||
public class InitResult
|
||
{
|
||
public bool Success { get; set; }
|
||
public int Slot { get; set; }
|
||
public string ErrorMessage { get; set; }
|
||
public string InterfaceVersion { get; set; }
|
||
public bool InterfaceSupportsFwVersion { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represents the result of a connect operation.
|
||
/// </summary>
|
||
public class ConnectResult
|
||
{
|
||
/// <summary>
|
||
/// Indicates whether the connect operation was successful.
|
||
/// </summary>
|
||
public bool Success { get; set; }
|
||
|
||
/// <summary>
|
||
/// Slot number used for connect.
|
||
/// </summary>
|
||
public int Slot { get; set; }
|
||
|
||
/// <summary>
|
||
/// Connected PCB ID.
|
||
/// </summary>
|
||
public string PcbId { get; set; }
|
||
|
||
/// <summary>
|
||
/// Indicates whether the meter is logged on.
|
||
/// </summary>
|
||
public bool IsLoggedOn { get; set; }
|
||
|
||
/// <summary>
|
||
/// Firmware version reported by the meter.
|
||
/// </summary>
|
||
public string FwVersion { get; set; }
|
||
|
||
/// <summary>
|
||
/// Interface version from configuration.
|
||
/// </summary>
|
||
public string InterfaceVersion { get; set; }
|
||
|
||
/// <summary>
|
||
/// Indicates whether the loaded configuration supports the detected firmware version.
|
||
/// </summary>
|
||
public bool InterfaceSupportsFwVersion { get; set; }
|
||
|
||
/// <summary>
|
||
/// Registers available after successful connect.
|
||
/// </summary>
|
||
public List<RegisterSnapshot> Registers { get; set; } = new List<RegisterSnapshot>();
|
||
|
||
/// <summary>
|
||
/// Error message if connect failed.
|
||
/// </summary>
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Represents one register returned after connect.
|
||
/// </summary>
|
||
public class RegisterSnapshot
|
||
{
|
||
public string Name { get; set; }
|
||
public string Type { get; set; }
|
||
public string RawValue { get; set; }
|
||
public string Min { get; set; }
|
||
public string Max { get; set; }
|
||
public string Description { get; set; }
|
||
public string Version { get; set; }
|
||
public string IsAvailable { get; set; }
|
||
public string Privilege { get; set; }
|
||
}
|
||
|
||
|
||
|
||
public InitResult InitOneMeterFromExtern(Int32 slotNo, ConfigSource useConfigSource, PasswordSource usePasswordSource, PortConfig? requestPort, PortConfig? streamingPort)
|
||
{
|
||
try
|
||
{
|
||
if (slotNo <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slotNo), "Slot number must be greater than zero.");
|
||
|
||
//_meterBatch.RemoveMeter(slotNo);
|
||
_currentGenesis?.DisposeMeter();
|
||
|
||
_currentGenesis = new GenesisMeter();
|
||
_currentGenesis.UseOfflinePasswords = usePasswordSource == PasswordSource.OfflineFile;
|
||
_currentGenesis.usePasswordSource = usePasswordSource;
|
||
_currentGenesis.useConfigSource = useConfigSource;
|
||
|
||
_currentGenesis.SetupFromExternConfig(
|
||
slotNo,
|
||
requestPort,
|
||
streamingPort,
|
||
true);
|
||
|
||
_meterBatch.AddMeter(_currentGenesis);
|
||
|
||
return new InitResult
|
||
{
|
||
Success = true,
|
||
Slot = slotNo,
|
||
InterfaceVersion = _currentGenesis.InterfaceInfo?.InterfaceVersion,
|
||
InterfaceSupportsFwVersion = _currentGenesis.InterfaceSupportsFwVersion
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//_meterBatch.RemoveAllMeters();
|
||
_currentGenesis?.DisposeMeter();
|
||
_currentGenesis = null;
|
||
|
||
return new InitResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Connects to a Genesis meter for the specified slot.
|
||
/// </summary>
|
||
/// <param name="slotNo">Slot number.</param>
|
||
/// <param name="useOfflinePasswords">Specifies whether offline passwords should be used.</param>
|
||
/// <returns>
|
||
/// Connect operation result including PCB ID, firmware/configuration info, and register snapshots.
|
||
/// </returns>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new GenesisAPI();
|
||
/// var result = api.Connect(3, true);
|
||
///
|
||
/// if (result.Success)
|
||
/// {
|
||
/// Console.WriteLine(result.PcbId);
|
||
/// Console.WriteLine(result.InterfaceVersion);
|
||
/// }
|
||
/// else
|
||
/// {
|
||
/// Console.WriteLine(result.ErrorMessage);
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
public ConnectResult ConnectOneMeter(int slotNo)
|
||
{
|
||
try
|
||
{
|
||
// Validate input
|
||
if (slotNo <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slotNo), "Slot number must be greater than zero.");
|
||
|
||
// Find meter in batch by slot
|
||
var meter = _meterBatch.ListOfMeters
|
||
.OfType<GenesisMeter>()
|
||
.FirstOrDefault(m => m.Slot == slotNo);
|
||
|
||
// Meter not initialized
|
||
if (meter == null)
|
||
{
|
||
return new ConnectResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
ErrorMessage = $"Meter for slot {slotNo} not found in batch."
|
||
};
|
||
}
|
||
|
||
// Set current working meter
|
||
_currentGenesis = meter;
|
||
|
||
// Perform login for meters in batch
|
||
_meterBatch.MetersLogin();
|
||
|
||
// Validate connection result
|
||
if (!_currentGenesis.IsLoggedOn && string.IsNullOrEmpty(_currentGenesis.PcbId))
|
||
{
|
||
return new ConnectResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
IsLoggedOn = false,
|
||
PcbId = _currentGenesis.PcbId,
|
||
ErrorMessage = "Cannot read out PcbId. Access to Cordonel denied."
|
||
};
|
||
}
|
||
|
||
// Build successful result
|
||
var result = new ConnectResult
|
||
{
|
||
Success = _currentGenesis.IsLoggedOn,
|
||
Slot = slotNo,
|
||
PcbId = _currentGenesis.PcbId,
|
||
IsLoggedOn = _currentGenesis.IsLoggedOn,
|
||
FwVersion = _currentGenesis.FwVersion,
|
||
InterfaceVersion = _currentGenesis.InterfaceInfo?.InterfaceVersion,
|
||
InterfaceSupportsFwVersion = _currentGenesis.InterfaceSupportsFwVersion
|
||
};
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// Return failure result on exception
|
||
return new ConnectResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Connects to a Genesis meter for the specified slot.
|
||
/// </summary>
|
||
/// <param name="slotNo">Slot number.</param>
|
||
/// <param name="useOfflinePasswords">Specifies whether offline passwords should be used.</param>
|
||
/// <returns>
|
||
/// Connect operation result including PCB ID, firmware/configuration info, and register snapshots.
|
||
/// </returns>
|
||
/// <example>
|
||
/// <code>
|
||
/// var api = new GenesisAPI();
|
||
/// var result = api.Connect(3, true);
|
||
///
|
||
/// if (result.Success)
|
||
/// {
|
||
/// Console.WriteLine(result.PcbId);
|
||
/// Console.WriteLine(result.InterfaceVersion);
|
||
/// }
|
||
/// else
|
||
/// {
|
||
/// Console.WriteLine(result.ErrorMessage);
|
||
/// }
|
||
/// </code>
|
||
/// </example>
|
||
public ConnectResult ConnectAllMeters(int slotNo)
|
||
{
|
||
/*try
|
||
{
|
||
if (slotNo <= 0)
|
||
throw new ArgumentOutOfRangeException(nameof(slotNo), "Slot number must be greater than zero.");
|
||
|
||
_currentGenesis?.DisposeMeter();
|
||
_meterBatch.RemoveAllMeters();
|
||
_currentGenesis = null;
|
||
|
||
_currentGenesis = new GenesisMeter();
|
||
_currentGenesis.UseOfflinePasswords = usePasswordSource == PasswordSource.OfflineFile;
|
||
_currentGenesis.usePasswordSource = usePasswordSource;
|
||
_currentGenesis.useConfigSource = useConfigSource;
|
||
_currentGenesis.SetupFromConfigFile(slotNo);//...MF
|
||
_currentGenesis.SetupFromExternConfig(slotNo);
|
||
_meterBatch.AddMeter(_currentGenesis);
|
||
|
||
_meterBatch.MetersLogin();
|
||
|
||
if (!_currentGenesis.IsLoggedOn && string.IsNullOrEmpty(_currentGenesis.PcbId))
|
||
{
|
||
return new ConnectResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
IsLoggedOn = false,
|
||
PcbId = _currentGenesis.PcbId,
|
||
ErrorMessage = "Cannot read out PcbId. Access to Cordonel denied."
|
||
};
|
||
}
|
||
|
||
var result = new ConnectResult
|
||
{
|
||
Success = _currentGenesis.IsLoggedOn,
|
||
Slot = slotNo,
|
||
PcbId = _currentGenesis.PcbId,
|
||
IsLoggedOn = _currentGenesis.IsLoggedOn,
|
||
FwVersion = _currentGenesis.FwVersion,
|
||
InterfaceVersion = _currentGenesis.InterfaceInfo?.InterfaceVersion,
|
||
InterfaceSupportsFwVersion = _currentGenesis.InterfaceSupportsFwVersion
|
||
};
|
||
|
||
foreach (var item in _currentGenesis.GetRegistersDic())
|
||
{
|
||
var from = item.Key.RegisterDetail.Version.First.HasValue
|
||
? item.Key.RegisterDetail.Version.First.Value.ToString()
|
||
: "-";
|
||
|
||
var to = item.Key.RegisterDetail.Version.Last.HasValue
|
||
? item.Key.RegisterDetail.Version.Last.Value.ToString()
|
||
: "-";
|
||
|
||
result.Registers.Add(new RegisterSnapshot
|
||
{
|
||
Name = item.Key.GetIdent(),
|
||
Type = item.Key.DataType.Name,
|
||
RawValue = BitConverter.ToString(item.Value).Replace("-", " "),
|
||
Min = item.Key.Minimum?.ToString(),
|
||
Max = item.Key.Maximum?.ToString(),
|
||
Description = item.Key.RegisterDetail.Description,
|
||
Version = $"from {from} to {to}",
|
||
IsAvailable = item.Key.IsAvailable.ToString(),
|
||
Privilege = item.Key.RegisterDetail.Privilege.Lvl8.ToString()
|
||
});
|
||
}
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_meterBatch.RemoveAllMeters();
|
||
_currentGenesis?.DisposeMeter();
|
||
|
||
return new ConnectResult
|
||
{
|
||
Success = false,
|
||
Slot = slotNo,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}*/
|
||
return null;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Meter Registers
|
||
/// <summary>
|
||
/// Result of a register read operation.
|
||
/// </summary>
|
||
public class RegisterReadResult
|
||
{
|
||
/// <summary>
|
||
/// Indicates whether the read operation was successful.
|
||
/// </summary>
|
||
public bool Success { get; set; }
|
||
|
||
/// <summary>
|
||
/// Name of the register.
|
||
/// </summary>
|
||
public string RegisterName { get; set; }
|
||
|
||
/// <summary>
|
||
/// Raw bytes returned from the device.
|
||
/// </summary>
|
||
public byte[] RawBytes { get; set; }
|
||
|
||
/// <summary>
|
||
/// Raw value formatted as hexadecimal string.
|
||
/// </summary>
|
||
public string RawHex { get; set; }
|
||
|
||
/// <summary>
|
||
/// Converted value based on register data type (if possible).
|
||
/// </summary>
|
||
public object TypedValue { get; set; }
|
||
|
||
/// <summary>
|
||
/// String representation of the converted value.
|
||
/// </summary>
|
||
public string TypedValueText { get; set; }
|
||
|
||
/// <summary>
|
||
/// Data type of the register.
|
||
/// </summary>
|
||
public string DataType { get; set; }
|
||
|
||
/// <summary>
|
||
/// Error message if operation failed.
|
||
/// </summary>
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Result of a register write operation.
|
||
/// </summary>
|
||
public class RegisterWriteResult
|
||
{
|
||
/// <summary>
|
||
/// Indicates whether the write operation was successful.
|
||
/// </summary>
|
||
public bool Success { get; set; }
|
||
|
||
/// <summary>
|
||
/// Name of the register.
|
||
/// </summary>
|
||
public string RegisterName { get; set; }
|
||
|
||
/// <summary>
|
||
/// Value that was written to the register.
|
||
/// </summary>
|
||
public object WrittenValue { get; set; }
|
||
|
||
/// <summary>
|
||
/// Indicates whether configuration was stored to the device.
|
||
/// </summary>
|
||
public bool StoreToDevice { get; set; }
|
||
|
||
/// <summary>
|
||
/// Indicates whether system state refresh was triggered.
|
||
/// </summary>
|
||
public bool RefreshSystemState { get; set; }
|
||
|
||
/// <summary>
|
||
/// Error message if operation failed.
|
||
/// </summary>
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
//Helper methods
|
||
|
||
/// <summary>
|
||
/// Ensures that the meter is connected and logged on.
|
||
/// </summary>
|
||
private void EnsureConnected()
|
||
{
|
||
if (_currentGenesis == null)
|
||
throw new InvalidOperationException("Genesis meter is not initialized. Call Connect first.");
|
||
|
||
if (!_currentGenesis.IsLoggedOn)
|
||
throw new InvalidOperationException("Genesis meter is not logged on. Call Connect first.");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Finds register definition by name.
|
||
/// </summary>
|
||
private RegisterDefinition FindRegisterDefinition(string registerName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(registerName))
|
||
throw new ArgumentException("Register name cannot be null or empty.", nameof(registerName));
|
||
|
||
var match = _currentGenesis
|
||
.GetRegistersDic()
|
||
.Keys
|
||
.FirstOrDefault(r => string.Equals(r.GetIdent(), registerName, StringComparison.OrdinalIgnoreCase));
|
||
|
||
if (match == null)
|
||
throw new KeyNotFoundException($"Register '{registerName}' was not found.");
|
||
|
||
return match;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts byte array to hex string.
|
||
/// </summary>
|
||
private string ToHex(byte[] data)
|
||
{
|
||
if (data == null || data.Length == 0)
|
||
return string.Empty;
|
||
|
||
return BitConverter.ToString(data).Replace("-", " ");
|
||
}
|
||
|
||
//Read register
|
||
/// <summary>
|
||
/// Reads register value by register name.
|
||
/// </summary>
|
||
public RegisterReadResult ReadRegister(string registerName)
|
||
{
|
||
try
|
||
{
|
||
EnsureConnected();
|
||
|
||
var register = FindRegisterDefinition(registerName);
|
||
var raw = _currentGenesis.ReadRegister(registerName);
|
||
|
||
object typedValue = null;
|
||
string typedValueText = null;
|
||
|
||
try
|
||
{
|
||
typedValue = ConvertRegisterValue(register, raw);
|
||
typedValueText = typedValue?.ToString();
|
||
}
|
||
catch
|
||
{
|
||
// Ignore conversion errors, raw value is still valid
|
||
}
|
||
|
||
return new RegisterReadResult
|
||
{
|
||
Success = true,
|
||
RegisterName = registerName,
|
||
RawBytes = raw,
|
||
RawHex = ToHex(raw),
|
||
TypedValue = typedValue,
|
||
TypedValueText = typedValueText,
|
||
DataType = register.DataType?.Name
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Value.Error(ex, $"ReadRegister failed for '{registerName}'.");
|
||
|
||
return new RegisterReadResult
|
||
{
|
||
Success = false,
|
||
RegisterName = registerName,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
}
|
||
|
||
//Typed conversion
|
||
/// <summary>
|
||
/// Converts raw register value to a typed value based on register definition.
|
||
/// </summary>
|
||
private object ConvertRegisterValue(RegisterDefinition register, byte[] raw)
|
||
{
|
||
var typeName = register.DataType?.Name;
|
||
|
||
switch (typeName)
|
||
{
|
||
case "Boolean":
|
||
return RegisterConverter.ByteArrayToValue<bool>(raw);
|
||
|
||
case "Byte":
|
||
return RegisterConverter.ByteArrayToValue<byte>(raw);
|
||
|
||
case "Int32":
|
||
return RegisterConverter.ByteArrayToValue<int>(raw);
|
||
|
||
case "UInt32":
|
||
return RegisterConverter.ByteArrayToValue<uint>(raw);
|
||
|
||
case "Double":
|
||
return RegisterConverter.ByteArrayToValue<double>(raw);
|
||
|
||
case "Single":
|
||
return RegisterConverter.ByteArrayToValue<float>(raw);
|
||
|
||
case "String":
|
||
return Encoding.ASCII.GetString(raw).TrimEnd('\0');
|
||
|
||
default:
|
||
return ToHex(raw);
|
||
}
|
||
}
|
||
|
||
//Generic login
|
||
|
||
/// <summary>
|
||
/// Reads register and converts it directly to specified type.
|
||
/// </summary>
|
||
public T ReadRegisterValue<T>(string registerName)
|
||
{
|
||
EnsureConnected();
|
||
|
||
var raw = _currentGenesis.ReadRegister(registerName);
|
||
return RegisterConverter.ByteArrayToValue<T>(raw);
|
||
}
|
||
|
||
//Write register
|
||
/// <summary>
|
||
/// Writes value to register.
|
||
/// </summary>
|
||
public RegisterWriteResult WriteRegister(
|
||
string registerName,
|
||
object value,
|
||
bool storeToDevice = false,
|
||
bool refreshSystemState = false)
|
||
{
|
||
try
|
||
{
|
||
EnsureConnected();
|
||
|
||
bool writeOk = _currentGenesis.WriteRegister(registerName, value);
|
||
|
||
if (!writeOk)
|
||
{
|
||
return new RegisterWriteResult
|
||
{
|
||
Success = false,
|
||
RegisterName = registerName,
|
||
WrittenValue = value,
|
||
ErrorMessage = "Write operation failed."
|
||
};
|
||
}
|
||
|
||
if (storeToDevice)
|
||
{
|
||
if (!_currentGenesis.StoreAllConfigurations())
|
||
{
|
||
return new RegisterWriteResult
|
||
{
|
||
Success = false,
|
||
RegisterName = registerName,
|
||
WrittenValue = value,
|
||
ErrorMessage = "StoreAllConfigurations failed."
|
||
};
|
||
}
|
||
}
|
||
|
||
if (refreshSystemState)
|
||
{
|
||
if (!_currentGenesis.WriteRegister("SENSUSRADIO_SYSTEMSTATE", 0xFF, true, false))
|
||
{
|
||
return new RegisterWriteResult
|
||
{
|
||
Success = false,
|
||
RegisterName = registerName,
|
||
WrittenValue = value,
|
||
ErrorMessage = "System state refresh failed."
|
||
};
|
||
}
|
||
}
|
||
|
||
return new RegisterWriteResult
|
||
{
|
||
Success = true,
|
||
RegisterName = registerName,
|
||
WrittenValue = value,
|
||
StoreToDevice = storeToDevice,
|
||
RefreshSystemState = refreshSystemState
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Value.Error(ex, $"WriteRegister failed for '{registerName}'.");
|
||
|
||
return new RegisterWriteResult
|
||
{
|
||
Success = false,
|
||
RegisterName = registerName,
|
||
WrittenValue = value,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
}
|
||
|
||
//Bulk operations
|
||
|
||
/// <summary>
|
||
/// Reads multiple registers.
|
||
/// </summary>
|
||
public List<RegisterReadResult> ReadRegisters(IEnumerable<string> registerNames)
|
||
{
|
||
var result = new List<RegisterReadResult>();
|
||
|
||
foreach (var name in registerNames)
|
||
{
|
||
result.Add(ReadRegister(name));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Writes multiple registers.
|
||
/// </summary>
|
||
public List<RegisterWriteResult> WriteRegisters(
|
||
Dictionary<string, object> registerValues,
|
||
bool storeToDevice = false,
|
||
bool refreshSystemState = false)
|
||
{
|
||
var results = new List<RegisterWriteResult>();
|
||
|
||
int index = 0;
|
||
int total = registerValues.Count;
|
||
|
||
foreach (var pair in registerValues)
|
||
{
|
||
bool doStore = storeToDevice && index == total - 1;
|
||
bool doRefresh = refreshSystemState && index == total - 1;
|
||
|
||
results.Add(WriteRegister(pair.Key, pair.Value, doStore, doRefresh));
|
||
index++;
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
//Disconnect
|
||
|
||
/// <summary>
|
||
/// Disconnects from the meter and releases resources.
|
||
/// </summary>
|
||
public void Disconnect()
|
||
{
|
||
try
|
||
{
|
||
if (_currentGenesis != null && _currentGenesis.IsLoggedOn)
|
||
{
|
||
_currentGenesis.Logout();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Value.Error(ex, "Disconnect failed.");
|
||
}
|
||
finally
|
||
{
|
||
_meterBatch.RemoveAllMeters();
|
||
_currentGenesis?.DisposeMeter();
|
||
_currentGenesis = null;
|
||
_currentPcbId = string.Empty;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
}
|