Merge branch 'develop/UnionTown' into develop/SLM-PT50

This commit is contained in:
Marek Frniak 2026-04-27 16:42:47 +02:00
commit 38fa86d243
34 changed files with 4159 additions and 1568 deletions

View File

@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static GenesisCordonelInterface.API.InterfaceOutsideToGCI;
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
namespace GenesisCordonelInterface.API
{
/// <summary>
/// Public data contract layer for the Genesis Cordonel Interface (GCI).
///
/// This class defines all Data Transfer Objects (DTOs) that are exposed
/// to external consumers (e.g. TBF, UI, or other integration layers).
///
/// Responsibilities:
/// - Provide stable, dependency-free models for external usage
/// - Decouple internal GCI implementation (GenesisMeter, Xylem libraries)
/// from external systems
/// - Define request/response contracts for all supported operations
/// - Contain mapping methods between public DTOs and internal domain models
///
/// Architecture:
/// External world (TBF / UI)
/// ↓
/// GciPublicModels (this layer)
/// ↓
/// Internal GCI API (InterfaceGCIToLaatzen, GenesisMeter, etc.)
///
/// Notes:
/// - Public models must NOT expose internal types (e.g. GenesisMeter, IPort, etc.)
/// - All mapping between internal and external representations must be done here
/// - DTOs are designed to be simple, serializable, and stable over time
/// - Any change in internal implementation should not affect these models
///
/// Pattern:
/// Each operation follows a consistent structure:
/// Request → Operation → Result
///
/// Example:
/// GciInitSlotRequest → InitSlot → GciInitSlotResult
/// GetSlot → GciSlotInfo
/// GetPcbId → GciGetPcbIdResult
///
/// This layer acts as a boundary between domain logic and integration logic.
/// </summary>
public class GciPublicModels
{
/// <summary>
/// Public DTOs exposed to external systems.
/// These models represent the contract of the GCI API.
/// They must remain stable and independent of internal implementation.
/// </summary>
#region ================================== PUBLIC MODELS ===========================================
public class GciSlotInfo
{
public int SlotId { get; set; }
public bool Exists { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public string PcbId { get; set; }
public GciConfigSource ConfigSource { get; set; }
public GciPasswordSource PasswordSource { get; set; }
public GciPortConfig RequestPort { get; set; }
public GciPortConfig StreamingPort { get; set; }
}
public class Result
{
public int SlotId { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public override string ToString()
{
return string.Format(
"SlotId={0}, Success={1}, Message={2}",
SlotId,
Success,
Message);
}
}
public enum GciPasswordSource
{
RestApi = 0,
OfflineFile = 1,
InterfaceInputPassword = 2,
}
public enum GciConfigSource
{
FileConfig = 0,
InterfaceInputConfig = 1,
}
/// <summary>
/// Collection of port settings
/// </summary>
public class GciPortConfig
{
public string PortName { get; set; }
public string Type { get; set; }
public override string ToString()
{
return string.Format("PortName={0}, Type={1}", PortName, Type);
}
}
public class GciInitSlotRequest
{
public int SlotId { get; set; }
public GciConfigSource ConfigSource { get; set; }
public GciPasswordSource PasswordSource { get; set; }
public GciPortConfig RequestPort { get; set; }
public GciPortConfig StreamingPort { get; set; }
public override string ToString()
{
return string.Format(
"SlotId={0}, GciConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}",
SlotId,
ConfigSource,
PasswordSource,
RequestPort,
StreamingPort);
}
}
public class GciInitSlotResult
{
public int SlotId { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public string PcbId { get; set; }
public override string ToString()
{
return string.Format(
"SlotId={0}, Success={1}, Message={2}, PcbId={3}",
SlotId,
Success,
Message,
PcbId);
}
}
public class GciCleanSlotsResult
{
public bool Success { get; set; }
public string Message { get; set; }
public override string ToString()
{
return string.Format(
"Success={0}, Message={1}",
Success,
Message);
}
}
public class GciGetPcbIdResult
{
public int SlotId { get; set; }
public bool Success { get; set; }
public string PcbId { get; set; }
public string Message { get; set; }
public override string ToString()
{
return string.Format(
"SlotId={0}, Success={1}, PcbId={2}, Message={3}",
SlotId,
Success,
PcbId,
Message);
}
}
public class GciConnectResult
{
public int SlotId { get; set; }
public bool Success { get; set; }
public string PcbId { get; set; }
public bool IsLoggedOn { get; set; }
public string FwVersion { get; set; }
public string InterfaceVersion { get; set; }
public bool InterfaceSupportsFwVersion { get; set; }
public List<GciRegisterSnapshot> Registers { get; set; } = new List<GciRegisterSnapshot>();
public string Message { get; set; }
}
public class GciRegisterSnapshot
{
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 class GciDisconnectResult
{
public int SlotId { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
public override string ToString()
{
return string.Format(
"SlotId={0}, Success={1}, Message={2}",
SlotId,
Success,
Message);
}
}
#endregion
/// <summary>
/// Mapping methods between public DTOs and internal GCI models.
/// Ensures separation between external contracts and internal domain objects.
/// </summary>
#region ================================== OUTERN/INTERN and back models mapping ==================================
public static PasswordSource MapPasswordSource(GciPasswordSource src)
{
return (PasswordSource)src;
}
public static ConfigSource MapConfigSource(GciConfigSource src)
{
return (ConfigSource)src;
}
public static Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig? MapPort(GciPortConfig port)
{
if (port == null)
return null;
Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig result = new Xylem.Common.Hardware.Interfaces.Ports.PortCore.PortConfig();
result.PortName = port.PortName;
result.Type = port.Type;
return result;
}
public static GciInitSlotResult MapInitResult(GciInitSlotResult result)
{
if (result == null)
return null;
return new GciInitSlotResult
{
SlotId = result.SlotId,
Success = result.Success,
Message = result.Message
};
}
public static GciPasswordSource MapPasswordSourceBack(PasswordSource src)
{
return (GciPasswordSource)src;
}
public static GciConfigSource MapConfigSourceBack(ConfigSource src)
{
return (GciConfigSource)src;
}
public static GciPortConfig MapPortBack(PortConfig? port)
{
if (!port.HasValue)
return null;
PortConfig value = port.Value;
return new GciPortConfig
{
PortName = value.PortName,
Type = value.Type
};
}
public static GciPortConfig MapPortBack(IPort port)
{
if (port == null)
return null;
return new GciPortConfig
{
PortName = port.GetPortName(),
Type = port.GetType().Name
};
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using static Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore.GenesisMeter;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.API
{
@ -14,111 +14,346 @@ namespace GenesisCordonelInterface.API
{
private readonly InterfaceGCIToLaatzen _innerMeterAPI;
/// <summary>
/// Initializes a new instance of the <see cref="InterfaceOutsideToGCI"/> class.
/// </summary>
public event Action<List<InterfaceGCIToLaatzen.MeterBatchDebugStatus>> MeterBatchStatusChanged;
public InterfaceOutsideToGCI()
{
_innerMeterAPI = new InterfaceGCIToLaatzen();
}
/// <summary>
/// Gets a value indicating whether the meter is currently connected and logged on.
/// </summary>
public bool IsConnected
#region ================================== PORT DETECTION ==================================
public InterfaceGCIToLaatzen.PortDetectionResult DetectStreamingPort(int slot)
{
get
{
return _innerMeterAPI.IsConnected;
}
var result = _innerMeterAPI.DetectStreamingPort(slot);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Connects to the meter on the specified slot.
/// </summary>
/// <param name="slotNo">Slot number.</param>
/// <param name="usePasswordsSource">Specifies whether offline passwords should be used.</param>
/// <returns>Connect operation result.</returns>
public InterfaceGCIToLaatzen.InitResult InitOneMeterFromExtern(Int32 slotNo, ConfigSource useConfigSource, PasswordSource usePasswordSource, PortConfig? requestPort, PortConfig? streamingPort)
public async Task<InterfaceGCIToLaatzen.PortDetectionResult> DetectStreamingPortAsync(
int slot,
CancellationToken token = default(CancellationToken))
{
return _innerMeterAPI.InitOneMeterFromExtern(slotNo, useConfigSource, usePasswordSource, requestPort, streamingPort);
var result = await _innerMeterAPI.DetectStreamingPortAsync(slot, token);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Connects to the meter on the specified slot.
/// </summary>
/// <param name="slotNo">Slot number.</param>
/// <param name="usePasswordsSource">Specifies whether offline passwords should be used.</param>
/// <returns>Connect operation result.</returns>
public InterfaceGCIToLaatzen.ConnectResult ConnectOneMeter(int slotNo)
public InterfaceGCIToLaatzen.PortDetectionResult DetectRequestPort(int slot)
{
return _innerMeterAPI.ConnectOneMeter(slotNo);
var result = _innerMeterAPI.DetectRequestPort(slot);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Connects to the meter on the specified slot.
/// </summary>
/// <param name="slotNo">Slot number.</param>
/// <param name="usePasswordsSource">Specifies whether offline passwords should be used.</param>
/// <returns>Connect operation result.</returns>
public InterfaceGCIToLaatzen.ConnectResult ConnectAllMeters(int slotNo)
public async Task<InterfaceGCIToLaatzen.PortDetectionResult> DetectRequestPortAsync(
int slot,
CancellationToken token = default(CancellationToken))
{
return _innerMeterAPI.ConnectAllMeters(slotNo);
var result = await _innerMeterAPI.DetectRequestPortAsync(slot, token);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Disconnects from the currently connected meter.
/// </summary>
public void Disconnect()
#endregion
#region ================================== INIT ==================================
public async Task<GciPublicModels.GciInitSlotResult> InitSlotAsync(GciPublicModels.GciInitSlotRequest request, CancellationToken token = default)
{
_innerMeterAPI.Disconnect();
if (request == null)
throw new ArgumentNullException(nameof(request));
var result = await _innerMeterAPI.InitOneMeterFromExternAsync(
request.SlotId,
GciPublicModels.MapConfigSource(request.ConfigSource),
GciPublicModels.MapPasswordSource(request.PasswordSource),
GciPublicModels.MapPort(request.RequestPort),
GciPublicModels.MapPort(request.StreamingPort),
token);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Reads PCB ID from the specified slot.
/// </summary>
/// <param name="slot">Slot number.</param>
/// <returns>PCB ID string.</returns>
public string GetPcbId(int slot)
public async Task<GciPublicModels.GciSlotInfo> GetSlotAsync(
int slotId,
CancellationToken token = default)
{
return _innerMeterAPI.GetPcbId(slot);
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await _innerMeterAPI.GetOneMeterInfo(slotId, token);
return result;
}
/// <summary>
/// Reads a register by name.
/// </summary>
/// <param name="registerName">Register name.</param>
/// <returns>Register read result.</returns>
public InterfaceGCIToLaatzen.RegisterReadResult ReadRegister(string registerName)
public async Task<GciPublicModels.GciCleanSlotsResult> CleanSlotsAsync(
CancellationToken token = default)
{
return _innerMeterAPI.ReadRegister(registerName);
var result = await _innerMeterAPI.CleanSlotsAsync(token);
RaiseMeterBatchStatusChanged();
return result;
}
#endregion
#region ================================== CONNECTION ==================================
public async Task<GciPublicModels.GciConnectResult> ConnectOneSlotAsync(
int slot,
CancellationToken token = default)
{
GciPublicModels.GciConnectResult result = await _innerMeterAPI.ConnectOneSlotAsync(slot, token);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Writes a value to a register.
/// </summary>
/// <param name="registerName">Register name.</param>
/// <param name="value">Value to write.</param>
/// <param name="storeToDevice">Specifies whether configuration should be stored after write.</param>
/// <param name="refreshSystemState">Specifies whether system state refresh should be triggered after write.</param>
/// <returns>Register write result.</returns>
public InterfaceGCIToLaatzen.RegisterWriteResult WriteRegister(
public async Task<GciPublicModels.GciDisconnectResult> DisconnectAsync(
int slot,
CancellationToken token = default)
{
if (slot <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await _innerMeterAPI.DisconnectAsync(slot, token);
RaiseMeterBatchStatusChanged();
return result;
}
#endregion
#region ================================== PCB ==================================
public async Task<GciPublicModels.GciGetPcbIdResult> GetPcbIdAsync(
int slot,
CancellationToken token = default)
{
if (slot <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await _innerMeterAPI.GetPcbIdAsync(slot, token);
return result;
}
#endregion
#region ================================== READ ==================================
// ----------------------------------------------------
public InterfaceGCIToLaatzen.RegisterReadResult ReadRegister(
int slot,
string registerName)
{
return _innerMeterAPI.ReadRegister(slot, registerName);
}
public Task<InterfaceGCIToLaatzen.RegisterReadResult> ReadRegisterAsync(
int slot,
string registerName,
CancellationToken token = default(CancellationToken))
{
return _innerMeterAPI.ReadRegisterAsync(slot, registerName, token);
}
// ----------------------------------------------------
#endregion
#region ================================== WRITE ==================================
// ----------------------------------------------------
public async Task<InterfaceGCIToLaatzen.RegisterWriteResult> WriteRegisterAsync(
int slot,
string registerName,
object value,
bool storeToDevice = false,
bool refreshSystemState = false)
{
return _innerMeterAPI.WriteRegister(registerName, value, storeToDevice, refreshSystemState);
var result = await _innerMeterAPI.WriteRegisterAsync(
slot,
registerName,
value,
storeToDevice,
refreshSystemState);
RaiseMeterBatchStatusChanged();
return result;
}
/// <summary>
/// Sets meter password.
/// </summary>
/// <param name="password">Password value.</param>
/// <returns>True if operation succeeded; otherwise false.</returns>
public bool SetMeterPassword(string password)
public InterfaceGCIToLaatzen.RegisterWriteResult WriteRegister(
int slot,
string registerName,
object value,
bool storeToDevice = false,
bool refreshSystemState = false)
{
return _innerMeterAPI.SetMeterPassword(password);
var result = _innerMeterAPI.WriteRegister(
slot,
registerName,
value,
storeToDevice,
refreshSystemState);
RaiseMeterBatchStatusChanged();
return result;
}
public async Task<bool> SetMeterPasswordAsync(int slot, string password)
{
var result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password);
RaiseMeterBatchStatusChanged();
return result;
}
public bool SetMeterPassword(int slot, string password)
{
var result = _innerMeterAPI.SetMeterPassword(slot, password);
RaiseMeterBatchStatusChanged();
return result;
}
// ----------------------------------------------------
#endregion
#region ================================== DEBUG STATUS ==================================
// ----------------------------------------------------
public List<InterfaceGCIToLaatzen.WorkerDebugStatus> GetWorkerDebugStatuses()
{
return _innerMeterAPI.GetWorkerDebugStatuses();
}
public List<InterfaceGCIToLaatzen.MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
{
return _innerMeterAPI.GetMeterBatchDebugStatuses();
}
public void RaiseMeterBatchStatusChanged()
{
var statuses = GetMeterBatchDebugStatuses();
var handler = MeterBatchStatusChanged;
if (handler != null)
handler(statuses);
}
// ----------------------------------------------------
#endregion
#region ================================== SLOT SELECTION ==================================
// ----------------------------------------------------
public void SetSlotSelected(int slot, bool selected)
{
_innerMeterAPI.SetSlotSelected(slot, selected);
RaiseMeterBatchStatusChanged();
}
public bool IsSlotSelected(int slot)
{
return _innerMeterAPI.IsSlotSelected(slot);
}
public List<int> GetSelectedSlots()
{
return _innerMeterAPI.GetSelectedSlots();
}
// ----------------------------------------------------
#endregion
#region ================================== SLOT PORT CONFIG ==================================
// ----------------------------------------------------
/*public void SetSlotRequestPort(int slot, string portName)
{
lock (_portLock)
{
if (string.IsNullOrWhiteSpace(portName))
{
_requestPorts.Remove(slot);
}
else
{
_requestPorts[slot] = new GciPortConfig
{
PortName = portName,
Type = "Serial"
};
}
}
RaiseMeterBatchStatusChanged();
}
public void SetSlotStreamingPort(int slot, string portName)
{
lock (_portLock)
{
if (string.IsNullOrWhiteSpace(portName))
{
_streamingPorts.Remove(slot);
}
else
{
_streamingPorts[slot] = new GciPortConfig
{
PortName = portName,
Type = "Serial"
};
}
}
RaiseMeterBatchStatusChanged();
}
public GciPortConfig? GetSlotRequestPort(int slot)
{
lock (_portLock)
{
GciPortConfig port;
if (_requestPorts.TryGetValue(slot, out port))
return port;
return null;
}
}
public GciPortConfig? GetSlotStreamingPort(int slot)
{
lock (_portLock)
{
GciPortConfig port;
if (_streamingPorts.TryGetValue(slot, out port))
return port;
return null;
}
}*/
// ----------------------------------------------------
#endregion
#region ================================== METER BATCH SETUP ==================================
// ----------------------------------------------------
public void ReloadSlotSetup()
{
_innerMeterAPI.ReloadSlotSetup();
RaiseMeterBatchStatusChanged();
}
public void SaveSlotSetup(List<InterfaceGCIToLaatzen.MeterBatchDebugStatus> data)
{
_innerMeterAPI.SaveSlotSetup(data);
RaiseMeterBatchStatusChanged();
}
// ----------------------------------------------------
#endregion
}
}

View File

@ -0,0 +1,314 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace GenesisCordonelInterface.Core.Threading
{
/*
ApiWorker per-slot sequential execution worker
This class provides a lightweight background worker that executes actions
sequentially on a dedicated thread.
PRIMARY PURPOSE
---------------
ApiWorker is designed to safely execute hardware-related operations
(e.g. meter communication) without blocking the UI thread and without
allowing concurrent access to the same device.
Each ApiWorker instance typically represents:
1 worker = 1 slot = 1 meter = 1 communication channel
KEY PROPERTIES
--------------
- Single dedicated background thread
- FIFO queue (first-in, first-out)
- Sequential execution (NO parallelism inside one worker)
- Thread-safe enqueueing
- Task-based async interface for callers
WHY THIS IS IMPORTANT
--------------------
Hardware communication (serial ports, meters, etc.) is usually NOT thread-safe.
If multiple commands are executed in parallel, communication may break or corrupt data.
ApiWorker guarantees:
- operations are executed one-by-one
- order is preserved
- no race conditions on the device
HIGH-LEVEL FLOW
---------------
Caller (UI/API)
|
v
RunAsync(...)
|
v
TaskCompletionSource created
|
v
Action wrapped into queue item
|
v
Added to BlockingCollection queue
|
v
Worker thread consumes queue
|
v
Action executed (blocking HW call)
|
v
Result propagated via TaskCompletionSource
|
v
Caller receives result via await
GRAPH
-----
Caller thread (UI)
|
v
RunAsync()
|
v
Queue (BlockingCollection)
|
v
-----------------------------
| Worker Thread (background)|
| while(queue) |
| Execute Action |
-----------------------------
|
v
Task result (await)
THREADING MODEL
---------------
- Producer/Consumer pattern
- Producer: any thread calling RunAsync
- Consumer: single worker thread
- Synchronization handled by BlockingCollection
MAIN COMPONENTS
---------------
1. BlockingCollection<Action> queue
- thread-safe queue
- stores work items
- supports blocking consumption
2. Dedicated Thread
- runs WorkerLoop()
- continuously processes queue
3. TaskCompletionSource<T>
- bridges sync execution async API
- allows caller to await result
METHODS
-------
RunAsync<T>(Func<T>)
--------------------
- Enqueues a function returning a value
- Wraps it into Action
- Executes on worker thread
- Returns Task<T> to caller
RunAsync(Action)
----------------
- Convenience overload for void methods
- Internally wraps into Func<object>
WorkerLoop()
------------
- Infinite loop consuming queue
- Executes actions one-by-one
- Stops when queue is completed
Dispose()
---------
- Stops accepting new items
- Cleans up queue
- Does NOT forcibly stop running task
CANCELLATION
------------
- CancellationToken is checked BEFORE execution
- If cancelled Task is cancelled
- Does NOT interrupt running operation
IMPORTANT LIMITATIONS
--------------------
- No parallel execution inside one worker (by design)
- Long-running action blocks worker thread
- No built-in timeout handling
- Dispose does not abort running work
WHEN TO USE
-----------
Per-device communication (serial, TCP, HW)
Ordered execution required
UI must stay responsive
WHEN NOT TO USE
---------------
CPU parallel processing (use Task.Run / Parallel)
High-throughput parallel workloads
Fire-and-forget background tasks
SUMMARY
-------
ApiWorker is a simple, robust solution for:
"Execute commands sequentially per resource, asynchronously from UI"
It is a perfect fit for:
- hardware interfaces
- device drivers
- IO-bound serialized workflows
*/
public sealed class ApiWorker : IDisposable
{
/// <summary>
/// Thread-safe FIFO queue holding work items.
/// </summary>
private readonly BlockingCollection<Action> queue = new BlockingCollection<Action>();
/// <summary>
/// Dedicated worker thread processing the queue.
/// </summary>
private readonly Thread thread;
/// <summary>
/// Indicates whether this worker has been disposed.
/// </summary>
private bool disposed;
public string Name { get; private set; }
public int QueueLength { get { return queue.Count; } }
public bool IsBusy { get; private set; }
public string CurrentOperation { get; private set; }
public string LastError { get; private set; }
public DateTime LastActivity { get; private set; }
/// <summary>
/// Creates a new ApiWorker with its own background thread.
/// </summary>
public ApiWorker(string name)
{
Name = name;
LastActivity = DateTime.Now;
thread = new Thread(WorkerLoop)
{
IsBackground = true,
Name = name
};
thread.Start();
}
/// <summary>
/// Enqueues a function returning a value for sequential execution.
/// </summary>
public Task<T> RunAsync<T>(
Func<T> action,
CancellationToken token = default(CancellationToken),
string operationName = null)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (disposed)
throw new ObjectDisposedException(nameof(ApiWorker));
var tcs = new TaskCompletionSource<T>();
queue.Add(() =>
{
if (token.IsCancellationRequested)
{
tcs.TrySetCanceled();
return;
}
try
{
IsBusy = true;
CurrentOperation = operationName ?? action.Method.Name;
LastActivity = DateTime.Now;
LastError = null;
var result = action();
tcs.TrySetResult(result);
}
catch (Exception ex)
{
LastError = ex.Message;
tcs.TrySetException(ex);
}
finally
{
IsBusy = false;
CurrentOperation = null;
LastActivity = DateTime.Now;
}
}, token);
return tcs.Task;
}
public Task RunAsync(
Action action,
CancellationToken token = default(CancellationToken),
string operationName = null)
{
return RunAsync<object>(() =>
{
action();
return null;
}, token, operationName);
}
/// <summary>
/// Enqueues a void action for sequential execution.
/// </summary>
public Task RunAsync(Action action, CancellationToken token = default)
{
return RunAsync<object>(() =>
{
action();
return null;
}, token);
}
/// <summary>
/// Main worker loop processing queued actions.
/// </summary>
private void WorkerLoop()
{
foreach (var item in queue.GetConsumingEnumerable())
{
item();
}
}
/// <summary>
/// Stops the worker and releases resources.
/// </summary>
public void Dispose()
{
if (disposed)
return;
disposed = true;
queue.CompleteAdding();
queue.Dispose();
}
}
}

View File

@ -58,8 +58,26 @@
<ItemGroup>
<Compile Include="API\InterfaceOutsideToGCI.cs" />
<Compile Include="API\InterfaceGCIToLaatzen.cs" />
<Compile Include="API\GciPublicModels.cs" />
<Compile Include="Core\Logging\UiLogBus.cs" />
<Compile Include="Core\Logging\UiTarget.cs" />
<Compile Include="Core\Threading\ApiWorker\ApiWorker.cs" />
<Compile Include="UI\Debug\MeterBatchConfigPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\Debug\MeterBatchConfigPanel.Designer.cs">
<DependentUpon>MeterBatchConfigPanel.cs</DependentUpon>
</Compile>
<Compile Include="UI\Debug\WorkerDebugPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\Debug\WorkerDebugPanel.Designer.cs">
<DependentUpon>WorkerDebugPanel.cs</DependentUpon>
</Compile>
<Compile Include="UI\Grid\MeterGridColumnConfig.cs" />
<Compile Include="UI\Grid\MeterGridConfigProvider.cs" />
<Compile Include="UI\Grid\MeterGridManager.cs" />
<Compile Include="UI\Grid\MeterRowDto.cs" />
<Compile Include="UI\LaatzenAPI_GenesisToolBox\FrmConfigurations.cs">
<SubType>Form</SubType>
</Compile>
@ -98,12 +116,31 @@
<Compile Include="UI\LaatzenAPI_CordonelPreadjustmentUI\PreAdjustmentControl.Designer.cs">
<DependentUpon>PreAdjustmentControl.cs</DependentUpon>
</Compile>
<Compile Include="UI\MainView.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\MainView.Designer.cs">
<DependentUpon>MainView.cs</DependentUpon>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\FrmGCIAPI.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\FrmGCIAPI.Designer.cs">
<DependentUpon>FrmGCIAPI.cs</DependentUpon>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\MeterInitView.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\MeterInitView.Designer.cs">
<DependentUpon>MeterInitView.cs</DependentUpon>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\MetersActionView.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="UI\StaraTuraAPI_GenesisCordonelInterface\MetersActionView.Designer.cs">
<DependentUpon>MetersActionView.cs</DependentUpon>
</Compile>
<Content Include="RuntimePackage\Build\Copy.targets.xml" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
@ -145,8 +182,7 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Core\AsTbfComponent\" />
<Folder Include="UI\AsTbfComponent\" />
<Folder Include="RuntimePackage\Package\" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services\" />
@ -221,5 +257,7 @@
<Name>Logging</Name>
</ProjectReference>
</ItemGroup>
<!-- Import custom GCI runtime packaging logic -->
<Import Project="RuntimePackage\Build\Copy.targets.xml" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,37 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Creates a GCI runtime package after each successful build.
The package contains the standalone executable and all runtime dependencies.
-->
<Target Name="CopyGciRuntimeToRuntimePackage" AfterTargets="Build">
<!-- Destination directory for the GCI runtime package -->
<PropertyGroup>
<GciRuntimePackageDir>$(ProjectDir)RuntimePackage\Package\</GciRuntimePackageDir>
</PropertyGroup>
<!-- Ensure destination directory exists -->
<MakeDir Directories="$(GciRuntimePackageDir)" />
<!-- Collect runtime files from the project output directory -->
<ItemGroup>
<GciRuntimeFiles Include="$(TargetDir)*.dll" />
<GciRuntimeFiles Include="$(TargetDir)*.exe" />
<GciRuntimeFiles Include="$(TargetDir)*.config" />
<GciRuntimeFiles Include="$(TargetDir)*.json" />
</ItemGroup>
<!-- Build output messages -->
<Message Text="Copying GCI runtime package to $(GciRuntimePackageDir)" Importance="High" />
<Message Text="Files: @(GciRuntimeFiles)" Importance="Normal" />
<!-- Copy runtime files into the package directory -->
<Copy
SourceFiles="@(GciRuntimeFiles)"
DestinationFolder="$(GciRuntimePackageDir)"
SkipUnchangedFiles="false" />
</Target>
</Project>

View File

@ -0,0 +1,57 @@
namespace GenesisCordonelInterface.UI.Debug
{
partial class MeterBatchConfigPanel
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.DataGridView grid;
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
if (api != null)
api.MeterBatchStatusChanged -= Api_MeterBatchStatusChanged;
}
base.Dispose(disposing);
}
#region Component Designer generated code
private void InitializeComponent()
{
this.grid = new System.Windows.Forms.DataGridView();
((System.ComponentModel.ISupportInitialize)(this.grid)).BeginInit();
this.SuspendLayout();
// grid
this.grid.AllowUserToAddRows = false;
this.grid.AllowUserToDeleteRows = false;
this.grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.grid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.grid.Dock = System.Windows.Forms.DockStyle.Fill;
this.grid.Name = "grid";
this.grid.RowHeadersWidth = 30;
this.grid.TabIndex = 0;
// events
this.grid.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.grid_CellValueChanged);
this.grid.CurrentCellDirtyStateChanged += new System.EventHandler(this.grid_CurrentCellDirtyStateChanged);
this.grid.DataError += new System.Windows.Forms.DataGridViewDataErrorEventHandler(this.grid_DataError);
// MeterBatchConfigPanel
this.Controls.Add(this.grid);
this.Name = "MeterBatchConfigPanel";
this.Size = new System.Drawing.Size(600, 200);
((System.ComponentModel.ISupportInitialize)(this.grid)).EndInit();
this.ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,401 @@
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using GenesisCordonelInterface.API;
namespace GenesisCordonelInterface.UI.Debug
{
public partial class MeterBatchConfigPanel : UserControl
{
private readonly InterfaceOutsideToGCI api;
private bool isRefreshing;
private List<string> comPorts = new List<string>();
public MeterBatchConfigPanel(InterfaceOutsideToGCI api)
{
this.api = api;
InitializeComponent();
RefreshComPorts();
InitializeGridColumns();
api.MeterBatchStatusChanged += Api_MeterBatchStatusChanged;
UpdateGrid(api.GetMeterBatchDebugStatuses());
}
#region INIT
// ----------------------------------------------------
private void InitializeGridColumns()
{
grid.Columns.Clear();
grid.Columns.Add("Slot", "Slot");
grid.Columns.Add(new DataGridViewCheckBoxColumn { Name = "Selected", HeaderText = "Selected" });
grid.Columns.Add("PcbId", "PcbId");
grid.Columns.Add(new DataGridViewCheckBoxColumn { Name = "IsLoggedOn", HeaderText = "IsLoggedOn" });
grid.Columns.Add(CreateComPortColumn("RequestPort", "RequestPort"));
grid.Columns.Add(CreateComPortColumn("StreamingPort", "StreamingPort"));
grid.Columns.Add(CreateButtonColumn("DetectRequest", "DetectRequest", "..."));
grid.Columns.Add(CreateButtonColumn("DetectStreaming", "DetectStreaming", "..."));
grid.Columns.Add("FwVersion", "FwVersion");
grid.Columns.Add("InterfaceVersion", "InterfaceVersion");
foreach (DataGridViewColumn col in grid.Columns)
{
col.ReadOnly =
col.Name != "Selected" &&
col.Name != "RequestPort" &&
col.Name != "StreamingPort" &&
col.Name != "DetectRequest" &&
col.Name != "DetectStreaming";
}
EnableDoubleBuffering(grid);
}
private DataGridViewComboBoxColumn CreateComPortColumn(string name, string headerText)
{
return new DataGridViewComboBoxColumn
{
Name = name,
HeaderText = headerText,
DataSource = new List<string>(comPorts),
FlatStyle = FlatStyle.Flat,
DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton
};
}
private DataGridViewButtonColumn CreateButtonColumn(string name, string headerText, string text)
{
return new DataGridViewButtonColumn
{
Name = name,
HeaderText = headerText,
Text = text,
UseColumnTextForButtonValue = true
};
}
private void RefreshComPorts()
{
comPorts = SerialPort.GetPortNames()
.OrderBy(NaturalComPortOrder)
.ToList();
if (!comPorts.Contains(""))
comPorts.Insert(0, "");
}
// ----------------------------------------------------
#endregion
#region REFRESH EVENT DRIVEN
// ----------------------------------------------------
private void Api_MeterBatchStatusChanged(
List<InterfaceGCIToLaatzen.MeterBatchDebugStatus> data)
{
if (IsDisposed)
return;
if (InvokeRequired)
{
BeginInvoke(new Action(() => UpdateGrid(data)));
return;
}
UpdateGrid(data);
}
private void UpdateGrid(List<InterfaceGCIToLaatzen.MeterBatchDebugStatus> data)
{
isRefreshing = true;
foreach (var meter in data)
{
EnsurePortValueExists(meter.RequestPort);
EnsurePortValueExists(meter.StreamingPort);
var row = FindOrCreateRow(meter.Slot);
Set(row, "Slot", meter.Slot);
Set(row, "Selected", meter.Selected);
Set(row, "PcbId", meter.PcbId);
Set(row, "IsLoggedOn", meter.IsLoggedOn);
Set(row, "RequestPort", meter.RequestPort);
Set(row, "StreamingPort", meter.StreamingPort);
Set(row, "FwVersion", meter.FwVersion);
Set(row, "InterfaceVersion", meter.InterfaceVersion);
}
isRefreshing = false;
}
// ----------------------------------------------------
#endregion
#region COM PORT HELPERS
// ----------------------------------------------------
private void UpdateComPortColumnItems(string columnName)
{
var col = grid.Columns[columnName] as DataGridViewComboBoxColumn;
if (col == null)
return;
col.DataSource = null;
col.DataSource = new List<string>(comPorts);
}
private void EnsurePortValueExists(string port)
{
if (string.IsNullOrWhiteSpace(port))
return;
if (comPorts.Contains(port))
return;
comPorts.Add(port);
comPorts = comPorts.OrderBy(NaturalComPortOrder).ToList();
if (!comPorts.Contains(""))
comPorts.Insert(0, "");
UpdateComPortColumnItems("RequestPort");
UpdateComPortColumnItems("StreamingPort");
}
private static int NaturalComPortOrder(string port)
{
if (string.IsNullOrWhiteSpace(port))
return 0;
string number = new string(port.Where(char.IsDigit).ToArray());
int parsed;
if (int.TryParse(number, out parsed))
return parsed;
return int.MaxValue;
}
// ----------------------------------------------------
#endregion
#region GRID HELPERS
// ----------------------------------------------------
private DataGridViewRow FindOrCreateRow(int slot)
{
foreach (DataGridViewRow row in grid.Rows)
{
if (row.Cells["Slot"].Value != null &&
Convert.ToInt32(row.Cells["Slot"].Value) == slot)
{
return row;
}
}
int idx = grid.Rows.Add();
var newRow = grid.Rows[idx];
newRow.Cells["Slot"].Value = slot;
return newRow;
}
private void Set(DataGridViewRow row, string col, object value)
{
if (!grid.Columns.Contains(col))
return;
if (value == null)
value = "";
var cell = row.Cells[col];
if (!Equals(cell.Value, value))
cell.Value = value;
}
// ----------------------------------------------------
#endregion
#region USER EDIT
// ----------------------------------------------------
private void grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (grid.IsCurrentCellDirty)
grid.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void grid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (isRefreshing)
return;
if (e.RowIndex < 0)
return;
string columnName = grid.Columns[e.ColumnIndex].Name;
if (columnName != "Selected" &&
columnName != "RequestPort" &&
columnName != "StreamingPort")
return;
int slot = Convert.ToInt32(grid.Rows[e.RowIndex].Cells["Slot"].Value);
if (columnName == "Selected")
{
bool selected = Convert.ToBoolean(grid.Rows[e.RowIndex].Cells["Selected"].Value);
api.SetSlotSelected(slot, selected);
return;
}
string port = Convert.ToString(grid.Rows[e.RowIndex].Cells[columnName].Value);
if (columnName == "RequestPort")
{
//api.SetSlotRequestPort(slot, port);
return;
}
if (columnName == "StreamingPort")
{
//api.SetSlotStreamingPort(slot, port);
return;
}
}
private async void grid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0)
return;
string columnName = grid.Columns[e.ColumnIndex].Name;
if (columnName != "DetectRequest" &&
columnName != "DetectStreaming")
return;
int slot = Convert.ToInt32(grid.Rows[e.RowIndex].Cells["Slot"].Value);
if (columnName == "DetectRequest")
{
await DetectRequestPortAsync(slot);
return;
}
if (columnName == "DetectStreaming")
{
await DetectStreamingPortAsync(slot);
return;
}
}
private async Task DetectRequestPortAsync(int slot)
{
try
{
grid.Enabled = false;
await api.DetectRequestPortAsync(slot);
api.RaiseMeterBatchStatusChanged();
}
finally
{
grid.Enabled = true;
}
}
private async Task DetectStreamingPortAsync(int slot)
{
try
{
grid.Enabled = false;
await api.DetectStreamingPortAsync(slot);
api.RaiseMeterBatchStatusChanged();
}
finally
{
grid.Enabled = true;
}
}
private void grid_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
e.ThrowException = false;
}
// ----------------------------------------------------
#endregion
#region PERFORMANCE
// ----------------------------------------------------
private void EnableDoubleBuffering(DataGridView dgv)
{
typeof(DataGridView)
.GetProperty(
"DoubleBuffered",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic)
?.SetValue(dgv, true, null);
}
// ----------------------------------------------------
#endregion
public void AddEmptySlotRow()
{
int nextSlot = 1;
var existing = grid.Rows
.Cast<DataGridViewRow>()
.Where(r => r.Cells["Slot"].Value != null)
.Select(r => Convert.ToInt32(r.Cells["Slot"].Value))
.ToList();
if (existing.Count > 0)
nextSlot = existing.Max() + 1;
int idx = grid.Rows.Add();
var row = grid.Rows[idx];
row.Cells["Slot"].Value = nextSlot;
row.Cells["Selected"].Value = true;
row.Cells["RequestPort"].Value = "";
row.Cells["StreamingPort"].Value = "";
}
public List<InterfaceGCIToLaatzen.MeterBatchDebugStatus> GetGridData()
{
var list = new List<InterfaceGCIToLaatzen.MeterBatchDebugStatus>();
foreach (DataGridViewRow row in grid.Rows)
{
if (row.Cells["Slot"].Value == null)
continue;
list.Add(new InterfaceGCIToLaatzen.MeterBatchDebugStatus
{
Slot = Convert.ToInt32(row.Cells["Slot"].Value),
Selected = Convert.ToBoolean(row.Cells["Selected"].Value),
RequestPort = Convert.ToString(row.Cells["RequestPort"].Value),
StreamingPort = Convert.ToString(row.Cells["StreamingPort"].Value)
});
}
return list;
}
}
}

View File

@ -0,0 +1,37 @@
namespace GenesisCordonelInterface.UI.Debug
{
partial class WorkerDebugPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}

View File

@ -0,0 +1,51 @@
using System;
using System.Windows.Forms;
using GenesisCordonelInterface.API;
namespace GenesisCordonelInterface.UI.Debug
{
public partial class WorkerDebugPanel : UserControl
{
private readonly InterfaceOutsideToGCI api;
private readonly Timer timer = new Timer();
private readonly DataGridView grid = new DataGridView();
public WorkerDebugPanel(InterfaceOutsideToGCI api)
{
this.api = api;
grid.Dock = DockStyle.Fill;
grid.ReadOnly = true;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
Controls.Add(grid);
timer.Interval = 300;
timer.Tick += (s, e) =>
{
grid.DataSource = null;
grid.DataSource = api.GetWorkerDebugStatuses();
};
timer.Start();
}
private void InitializeLayout()
{
grid.Dock = DockStyle.Fill;
grid.ReadOnly = true;
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
Controls.Add(grid);
Dock = DockStyle.Fill;
}
private void Timer_Tick(object sender, EventArgs e)
{
grid.DataSource = null;
grid.DataSource = api.GetWorkerDebugStatuses();
}
}
}

View File

@ -0,0 +1,12 @@
namespace GenesisCordonelInterface.UI.Grid
{
public class MeterGridColumnConfig
{
public string Name { get; set; }
public string HeaderText { get; set; }
public bool Visible { get; set; } = true;
public int DisplayIndex { get; set; }
public int Width { get; set; } = 80;
public bool ReadOnly { get; set; } = false;
}
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace GenesisCordonelInterface.UI.Grid
{
public static class MeterGridConfigProvider
{
public static List<MeterGridColumnConfig> GetDefault()
{
return new List<MeterGridColumnConfig>
{
new MeterGridColumnConfig { Name = "Slot", HeaderText = "Slot", DisplayIndex = 0, Width = 50, ReadOnly = true },
new MeterGridColumnConfig { Name = "Selected", HeaderText = "Selected", DisplayIndex = 1, Width = 60 },
new MeterGridColumnConfig { Name = "PcbId", HeaderText = "PcbId", DisplayIndex = 2, Width = 80 },
new MeterGridColumnConfig { Name = "IsLoggedOn", HeaderText = "IsLoggedOn", DisplayIndex = 3, Width = 80 },
new MeterGridColumnConfig { Name = "RequestPort", HeaderText = "RequestPort", DisplayIndex = 4, Width = 90 },
new MeterGridColumnConfig { Name = "StreamingPort", HeaderText = "StreamingPort", DisplayIndex = 5, Width = 100 },
new MeterGridColumnConfig { Name = "FwVersion", HeaderText = "FwVersion", DisplayIndex = 6, Width = 80 },
new MeterGridColumnConfig { Name = "InterfaceVersion", HeaderText = "InterfaceVersion", DisplayIndex = 7, Width = 110 },
};
}
}
}

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
namespace GenesisCordonelInterface.UI.Grid
{
public class MeterGridManager
{
private readonly DataGridView grid;
public MeterGridManager(DataGridView grid)
{
this.grid = grid;
EnableDoubleBuffering(grid);
}
public void Init(List<MeterGridColumnConfig> columns)
{
grid.SuspendLayout();
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.RowHeadersVisible = true;
foreach (var cfg in columns.OrderBy(c => c.DisplayIndex))
{
DataGridViewColumn col;
if (cfg.Name == "Selected" || cfg.Name == "IsLoggedOn")
col = new DataGridViewCheckBoxColumn();
else
col = new DataGridViewTextBoxColumn();
col.Name = cfg.Name;
col.HeaderText = cfg.HeaderText;
col.Visible = cfg.Visible;
col.Width = cfg.Width;
col.ReadOnly = cfg.ReadOnly;
grid.Columns.Add(col);
}
grid.ResumeLayout();
}
public void Update(List<MeterRowDto> meters)
{
grid.SuspendLayout();
foreach (var meter in meters)
{
var row = FindOrCreateRow(meter.Slot);
SetCell(row, "Slot", meter.Slot);
SetCell(row, "Selected", meter.Selected);
SetCell(row, "PcbId", meter.PcbId);
SetCell(row, "IsLoggedOn", meter.IsLoggedOn);
SetCell(row, "RequestPort", meter.RequestPort);
SetCell(row, "StreamingPort", meter.StreamingPort);
SetCell(row, "FwVersion", meter.FwVersion);
SetCell(row, "InterfaceVersion", meter.InterfaceVersion);
}
grid.ResumeLayout();
}
private DataGridViewRow FindOrCreateRow(int slot)
{
foreach (DataGridViewRow row in grid.Rows)
{
if (row.Cells["Slot"].Value != null &&
Convert.ToInt32(row.Cells["Slot"].Value) == slot)
{
return row;
}
}
int idx = grid.Rows.Add();
var newRow = grid.Rows[idx];
newRow.Cells["Slot"].Value = slot;
return newRow;
}
private void SetCell(DataGridViewRow row, string colName, object value)
{
if (!grid.Columns.Contains(colName))
return;
var cell = row.Cells[colName];
if (!Equals(cell.Value, value))
cell.Value = value;
}
private void EnableDoubleBuffering(DataGridView dgv)
{
typeof(DataGridView)
.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)
?.SetValue(dgv, true, null);
}
}
}

View File

@ -0,0 +1,14 @@
namespace GenesisCordonelInterface.UI.Grid
{
public class MeterRowDto
{
public int Slot { get; set; }
public bool Selected { get; set; }
public string PcbId { get; set; }
public bool IsLoggedOn { get; set; }
public string RequestPort { get; set; }
public string StreamingPort { get; set; }
public string FwVersion { get; set; }
public string InterfaceVersion { get; set; }
}
}

View File

@ -492,7 +492,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
DisableAllButtons();
_dataTable.Rows.Clear();
var result = await Task.Run(() => interfaceToLaatzen.ConnectOneMeter(slotNo));
var result = await interfaceToLaatzen.ConnectOneSlotAsync(slotNo);
if (result.Success)
{
@ -571,7 +571,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
lblState.Text = $@"Not Connected to PcbId:{result.PcbId}";
registerGridView.Visible = false;
MessageBox.Show(result.ErrorMessage ?? "Connect failed.", @"FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show(result.Message ?? "Connect failed.", @"FAILED", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
@ -857,7 +857,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
ForeColor = Color.Black
};
SetProgress($"Read File {filename}");
SetProgress($"Read FileConfig {filename}");
var text = File.ReadAllText(filename);
var loadedRegStore = JsonConvert.DeserializeObject<regStore>(text);
@ -941,7 +941,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
}
else
{
((DataRow)rowItem)["RawValueFile"] = "Not in File";
((DataRow)rowItem)["RawValueFile"] = "Not in FileConfig";
}
}
}
@ -949,7 +949,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
private void ShowFile(String filename)
{
SetProgress($"Read File {filename}");
SetProgress($"Read FileConfig {filename}");
var text = File.ReadAllText(filename);
var loadedRegStore = JsonConvert.DeserializeObject<regStore>(text);
@ -971,7 +971,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
}
else
{
((DataRow)rowItem)["RawValueFile"] = "Not in File";
((DataRow)rowItem)["RawValueFile"] = "Not in FileConfig";
}
done += 1;
@ -981,8 +981,8 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
private void LoadFile(String filename)
{
//SetProgreess($"Read File {filename}");
//var text = File.ReadAllText(filename);
//SetProgreess($"Read FileConfig {filename}");
//var text = FileConfig.ReadAllText(filename);
//var loadedRegStore = Newtonsoft.Json.JsonConvert.DeserializeObject<regStore>(text);
//if (loadedRegStore.PcbId != _currentPcbId)
//{
@ -1015,7 +1015,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
//{
// if (saveFileDialog.ShowDialog() == DialogResult.OK)
// File.WriteAllText(saveFileDialog.FileName, text);
// FileConfig.WriteAllText(saveFileDialog.FileName, text);
//}));
@ -1172,11 +1172,16 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
SetBusy(true, "GetPcbId");
// as new meter object will be generated the data grid shows outdated data
_dataTable.Rows.Clear();
DisableAllButtons();
_currentPcbId = await Task.Run(() => interfaceToLaatzen.GetPcbId(slotNr));
GciPublicModels.GciGetPcbIdResult result =
await interfaceToLaatzen.GetPcbIdAsync(slotNr);
if (!result.Success)
throw new Exception(result.Message);
_currentPcbId = result.PcbId;
btnConnect.Enabled = true;
@ -2026,7 +2031,7 @@ namespace GenesisCordonelInterface.UI.LaatzenAPI_GenesisToolBox
//_currentGenesis.WriteRegister("SENSUSRADIO_WakeupInterval", 0);
//_currentGenesis.Logout();
//File.AppendAllLines("RadioActivation.log", new[] { $"{freq};{_currentGenesis.PcbId};{RadioAdress};{DateTime.Now}" });
//FileConfig.AppendAllLines("RadioActivation.log", new[] { $"{freq};{_currentGenesis.PcbId};{RadioAdress};{DateTime.Now}" });
if (0x02 == RegisterConverter.ByteArrayToValue<Byte>(
_currentGenesis.ReadRegister("SENSUSRADIO_SystemState")))
{

View File

@ -11,20 +11,14 @@
private System.Windows.Forms.ToolStripMenuItem miClearLog;
private System.Windows.Forms.ToolStripMenuItem miHelp;
private System.Windows.Forms.ToolStripMenuItem miHelpAbout;
private System.Windows.Forms.Panel pnlLeftMenu;
private System.Windows.Forms.Panel pnlMain;
private System.Windows.Forms.RichTextBox rtbMainLog;
private System.Windows.Forms.Panel mainHostPanel;
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel tslStatus;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
@ -38,54 +32,39 @@
this.miClearLog = new System.Windows.Forms.ToolStripMenuItem();
this.miHelp = new System.Windows.Forms.ToolStripMenuItem();
this.miHelpAbout = new System.Windows.Forms.ToolStripMenuItem();
this.pnlLeftMenu = new System.Windows.Forms.Panel();
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.preadjustmentButton = new System.Windows.Forms.Button();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.btnPulseSetup = new System.Windows.Forms.Button();
this.btnSetup = new System.Windows.Forms.Button();
this.btnRegisterStore = new System.Windows.Forms.Button();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.pnlMain = new System.Windows.Forms.Panel();
this.rtbMainLog = new System.Windows.Forms.RichTextBox();
this.mainHostPanel = new System.Windows.Forms.Panel();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.tslStatus = new System.Windows.Forms.ToolStripStatusLabel();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.button1 = new System.Windows.Forms.Button();
this.menuStrip1.SuspendLayout();
this.pnlLeftMenu.SuspendLayout();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.tabPage2.SuspendLayout();
this.pnlMain.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.groupBox3.SuspendLayout();
this.SuspendLayout();
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.miFile,
this.miView,
this.miHelp});
this.miFile,
this.miView,
this.miHelp
});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Padding = new System.Windows.Forms.Padding(5, 2, 0, 2);
this.menuStrip1.Size = new System.Drawing.Size(1309, 24);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
//
// miFile
//
this.miFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.miExit});
this.miExit
});
this.miFile.Name = "miFile";
this.miFile.Size = new System.Drawing.Size(37, 20);
this.miFile.Text = "File";
this.miFile.Text = "FileConfig";
//
// miExit
//
@ -93,14 +72,17 @@
this.miExit.Size = new System.Drawing.Size(92, 22);
this.miExit.Text = "Exit";
this.miExit.Click += new System.EventHandler(this.miExit_Click);
//
// miView
//
this.miView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.miClearLog});
this.miClearLog
});
this.miView.Name = "miView";
this.miView.Size = new System.Drawing.Size(44, 20);
this.miView.Text = "View";
//
// miClearLog
//
@ -108,14 +90,17 @@
this.miClearLog.Size = new System.Drawing.Size(124, 22);
this.miClearLog.Text = "Clear Log";
this.miClearLog.Click += new System.EventHandler(this.miClearLog_Click);
//
// miHelp
//
this.miHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.miHelpAbout});
this.miHelpAbout
});
this.miHelp.Name = "miHelp";
this.miHelp.Size = new System.Drawing.Size(44, 20);
this.miHelp.Text = "Help";
//
// miHelpAbout
//
@ -123,177 +108,44 @@
this.miHelpAbout.Size = new System.Drawing.Size(107, 22);
this.miHelpAbout.Text = "About";
this.miHelpAbout.Click += new System.EventHandler(this.miHelpAbout_Click);
//
// pnlLeftMenu
// mainHostPanel
//
this.pnlLeftMenu.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlLeftMenu.Controls.Add(this.tabControl1);
this.pnlLeftMenu.Dock = System.Windows.Forms.DockStyle.Left;
this.pnlLeftMenu.Location = new System.Drawing.Point(0, 24);
this.pnlLeftMenu.Name = "pnlLeftMenu";
this.pnlLeftMenu.Size = new System.Drawing.Size(190, 474);
this.pnlLeftMenu.TabIndex = 1;
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Location = new System.Drawing.Point(3, 5);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(186, 464);
this.tabControl1.TabIndex = 4;
//
// tabPage1
//
this.tabPage1.Controls.Add(this.groupBox2);
this.tabPage1.Controls.Add(this.groupBox1);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(178, 438);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Laatzen API";
this.tabPage1.UseVisualStyleBackColor = true;
//
// groupBox2
//
this.groupBox2.Controls.Add(this.preadjustmentButton);
this.groupBox2.Location = new System.Drawing.Point(6, 190);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(166, 66);
this.groupBox2.TabIndex = 5;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "CordonelPreadjustmentUI";
//
// preadjustmentButton
//
this.preadjustmentButton.Location = new System.Drawing.Point(6, 19);
this.preadjustmentButton.Name = "preadjustmentButton";
this.preadjustmentButton.Size = new System.Drawing.Size(153, 35);
this.preadjustmentButton.TabIndex = 3;
this.preadjustmentButton.Text = "Preadjustment";
this.preadjustmentButton.UseVisualStyleBackColor = true;
this.preadjustmentButton.Click += new System.EventHandler(this.preadjustmentButton_Click);
//
// groupBox1
//
this.groupBox1.Controls.Add(this.btnPulseSetup);
this.groupBox1.Controls.Add(this.btnSetup);
this.groupBox1.Controls.Add(this.btnRegisterStore);
this.groupBox1.Location = new System.Drawing.Point(6, 17);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(166, 153);
this.groupBox1.TabIndex = 4;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "GenesisToolBox";
//
// btnPulseSetup
//
this.btnPulseSetup.Location = new System.Drawing.Point(7, 101);
this.btnPulseSetup.Name = "btnPulseSetup";
this.btnPulseSetup.Size = new System.Drawing.Size(153, 35);
this.btnPulseSetup.TabIndex = 2;
this.btnPulseSetup.Text = "Pulse Setup";
this.btnPulseSetup.UseVisualStyleBackColor = true;
this.btnPulseSetup.Click += new System.EventHandler(this.btnPulseSetup_Click);
//
// btnSetup
//
this.btnSetup.Location = new System.Drawing.Point(7, 19);
this.btnSetup.Name = "btnSetup";
this.btnSetup.Size = new System.Drawing.Size(153, 35);
this.btnSetup.TabIndex = 0;
this.btnSetup.Text = "Setup";
this.btnSetup.UseVisualStyleBackColor = true;
this.btnSetup.Click += new System.EventHandler(this.btnSetup_Click);
//
// btnRegisterStore
//
this.btnRegisterStore.Location = new System.Drawing.Point(7, 60);
this.btnRegisterStore.Name = "btnRegisterStore";
this.btnRegisterStore.Size = new System.Drawing.Size(153, 35);
this.btnRegisterStore.TabIndex = 1;
this.btnRegisterStore.Text = "Register Store";
this.btnRegisterStore.UseVisualStyleBackColor = true;
this.btnRegisterStore.Click += new System.EventHandler(this.btnRegisterStore_Click);
//
// tabPage2
//
this.tabPage2.Controls.Add(this.groupBox3);
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(178, 438);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "GCI API";
this.tabPage2.UseVisualStyleBackColor = true;
//
// pnlMain
//
this.pnlMain.Controls.Add(this.rtbMainLog);
this.pnlMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlMain.Location = new System.Drawing.Point(190, 24);
this.pnlMain.Name = "pnlMain";
this.pnlMain.Padding = new System.Windows.Forms.Padding(9);
this.pnlMain.Size = new System.Drawing.Size(1119, 474);
this.pnlMain.TabIndex = 2;
//
// rtbMainLog
//
this.rtbMainLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbMainLog.Font = new System.Drawing.Font("Consolas", 10F);
this.rtbMainLog.Location = new System.Drawing.Point(9, 9);
this.rtbMainLog.Name = "rtbMainLog";
this.rtbMainLog.ReadOnly = true;
this.rtbMainLog.Size = new System.Drawing.Size(1101, 456);
this.rtbMainLog.TabIndex = 0;
this.rtbMainLog.Text = "";
this.mainHostPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.mainHostPanel.Location = new System.Drawing.Point(0, 24);
this.mainHostPanel.Name = "mainHostPanel";
this.mainHostPanel.Padding = new System.Windows.Forms.Padding(0);
this.mainHostPanel.Size = new System.Drawing.Size(1309, 474);
this.mainHostPanel.TabIndex = 1;
//
// statusStrip1
//
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tslStatus});
this.tslStatus
});
this.statusStrip1.Location = new System.Drawing.Point(0, 498);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 12, 0);
this.statusStrip1.Size = new System.Drawing.Size(1309, 22);
this.statusStrip1.TabIndex = 3;
this.statusStrip1.TabIndex = 2;
this.statusStrip1.Text = "statusStrip1";
//
// tslStatus
//
this.tslStatus.Name = "tslStatus";
this.tslStatus.Size = new System.Drawing.Size(39, 17);
this.tslStatus.Text = "Ready";
//
// groupBox3
//
this.groupBox3.Controls.Add(this.button1);
this.groupBox3.Location = new System.Drawing.Point(6, 15);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(166, 66);
this.groupBox3.TabIndex = 6;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "GenesisCordonelInterface";
//
// button1
//
this.button1.Location = new System.Drawing.Point(6, 19);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(153, 35);
this.button1.TabIndex = 3;
this.button1.Text = "API";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1309, 520);
this.Controls.Add(this.pnlMain);
this.Controls.Add(this.pnlLeftMenu);
this.Controls.Add(this.mainHostPanel);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
@ -301,33 +153,14 @@
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Genesis Cordonel Interface";
this.Load += new System.EventHandler(this.MainForm_Load);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.pnlLeftMenu.ResumeLayout(false);
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false);
this.groupBox1.ResumeLayout(false);
this.tabPage2.ResumeLayout(false);
this.pnlMain.ResumeLayout(false);
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.groupBox3.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.Button btnSetup;
private System.Windows.Forms.Button preadjustmentButton;
private System.Windows.Forms.Button btnRegisterStore;
private System.Windows.Forms.Button btnPulseSetup;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.Button button1;
}
}

View File

@ -1,173 +1,42 @@
using GenesisCordonelInterface.UI.LaatzenAPI_CordonelPreadjustmentUI;
using GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface;
using System;
using System;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Xylem.Common.Ui.GenesisToolBox;
using Xylem.Common.Utils.Logging;
namespace GenesisCordonelInterface.UI
{
public partial class MainForm : Form
{
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface");
private static readonly Regex LogLevelRegex = new Regex(@"\|\s*(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s*\|", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private MainView mainView;
public MainForm()
{
InitializeComponent();
/*_logger = logger;
_logger.MessagePublished += OnLogMessagePublished;*/
UiLogBus.MessageReceived += UiLogBus_MessageReceived;
StartPosition = FormStartPosition.Manual;
Location = new Point(Screen.PrimaryScreen.WorkingArea.Width - Width, 0);
//TopRight position on screen
this.StartPosition = FormStartPosition.Manual;
this.Location = new Point(Screen.PrimaryScreen.WorkingArea.Width - this.Width, 0);
//black background
rtbMainLog.BackColor = Color.Black;
rtbMainLog.ForeColor = Color.Gainsboro;
rtbMainLog.Font = new Font("Consolas", 9f);
rtbMainLog.ReadOnly = true;
rtbMainLog.HideSelection = false;
StartPosition = FormStartPosition.CenterScreen;
WindowState = FormWindowState.Maximized;
}
private void UiLogBus_MessageReceived(string msg)
private void MainForm_Load(object sender, EventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(UiLogBus_MessageReceived), msg);
return;
}
string[] lines = msg.Replace("\r\n", "\n").Split('\n');
foreach (string originalLine in lines)
{
if (string.IsNullOrWhiteSpace(originalLine))
continue;
string line = originalLine;
// Find header (timestamp|LEVEL|)
Match m = LogLevelRegex.Match(line);
string indent = "";
if (m.Success)
{
int indentLength = m.Index + m.Length;
indent = new string(' ', indentLength);
}
// Split long message manually if needed (optional)
string[] subLines = line.Split(new[] { " - " }, 2, StringSplitOptions.None);
string firstLine = line;
string rest = null;
if (subLines.Length == 2 && subLines[1].Length > 120) // heuristic
{
firstLine = subLines[0] + " - " + subLines[1].Substring(0, 120);
rest = subLines[1].Substring(120);
}
AppendStyledLine(firstLine);
if (!string.IsNullOrEmpty(rest))
{
AppendStyledLine(indent + rest);
}
}
}
private void AppendStyledLine(string line)
{
int start = rtbMainLog.TextLength;
rtbMainLog.SelectionStart = start;
rtbMainLog.SelectionLength = 0;
rtbMainLog.SelectionColor = Color.Gainsboro;
rtbMainLog.AppendText(line + Environment.NewLine);
Match m = LogLevelRegex.Match(line);
if (m.Success)
{
rtbMainLog.SelectionStart = start + m.Index;
rtbMainLog.SelectionLength = m.Length;
rtbMainLog.SelectionColor = GetLogLevelColor(m.Groups[1].Value);
}
HighlightKeywordsInLine(line, start);
}
private int MeasureTextWidthPx(string text)
{
if (string.IsNullOrEmpty(text))
return 0;
return TextRenderer.MeasureText(text, rtbMainLog.Font).Width;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
//_logger.MessagePublished -= OnLogMessagePublished;
UiLogBus.MessageReceived -= UiLogBus_MessageReceived;
base.OnFormClosed(e);
}
private void btnSetup_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Setup open");
using (FrmSetup frm = new FrmSetup())
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: Setup closed.");
}
private void btnRegisterStore_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Register Store open.");
using (FrmRegisterStore frm = new FrmRegisterStore())
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: Register Store closed.");
}
private void btnPulseSetup_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Pulse Setup open.");
using (FrmConfigurations frm = new FrmConfigurations())
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: Pulse Setup closed.");
mainView = new MainView();
mainView.Dock = DockStyle.Fill;
Controls.Add(mainView);
mainView.BringToFront();
this.WindowState = FormWindowState.Maximized;
}
private void miExit_Click(object sender, EventArgs e)
{
this.Close();
Close();
}
private void miClearLog_Click(object sender, EventArgs e)
{
rtbMainLog.Clear();
Logger.Trace("Log cleared.");
if (mainView != null)
mainView.ClearLog();
}
private void miHelpAbout_Click(object sender, EventArgs e)
@ -178,113 +47,5 @@ namespace GenesisCordonelInterface.UI
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
private Color GetLogLevelColor(string level)
{
switch (level.Trim().ToUpperInvariant())
{
case "TRACE": return Color.Gray;
case "DEBUG": return Color.DeepSkyBlue;
case "INFO": return Color.LimeGreen;
case "WARN": return Color.Orange;
case "ERROR": return Color.Red;
case "FATAL": return Color.Magenta;
default: return Color.Gainsboro;
}
}
private bool IsSeparatorLine(string text)
{
if (string.IsNullOrWhiteSpace(text))
return false;
string trimmed = text.Trim();
// Remove spaces and tab-like spacing
string compact = new string(trimmed.Where(c => !char.IsWhiteSpace(c)).ToArray());
if (compact.Length < 4)
return false;
// Count non-letter/non-digit characters
int nonAlnumCount = compact.Count(c => !char.IsLetterOrDigit(c));
// Consider it a separator if most characters are non-alphanumeric
// Examples:
// -----CommandToMeter()-----
// ==========================
// /////////
double ratio = (double)nonAlnumCount / compact.Length;
return ratio >= 0.6;
}
/// <summary>
/// special string highlighting
/// "TX FINAL" have to go before "TX"
/// else "TX" will highlighted in the middle of "TX FINAL"
/// </summary>
private static readonly (Color color, string[] keywords)[] KeywordGroups =
{
(Color.DeepSkyBlue, new[] { "REQUEST" }),
(Color.Lime, new[] { "RESPONSE" }),
//(Color.LightGreen, new[] { "START", "END" }),
//(Color.Cyan, new[] { "TX", "TX FINAL" }),
//(Color.DeepSkyBlue,new[] { "RX", "RX FINAL", "RX CHUNK" }),
(Color.Gold, new[] { "READ-REGISTER-SESSION", "UI-CLICK"}),
//(Color.Violet, new[] { "REGADDR" }),
//(Color.Khaki, new[] { "DEFAULT", "ALIGNED", "STRING" })
};
/// <summary>
/// special string highlighting
/// </summary>
/// <param name="line"></param>
/// <param name="lineStartIndex"></param>
private void HighlightKeywordsInLine(string line, int lineStartIndex)
{
foreach (var group in KeywordGroups)
{
foreach (var keyword in group.keywords)
{
int index = 0;
while ((index = line.IndexOf(keyword, index, StringComparison.Ordinal)) >= 0)
{
rtbMainLog.SelectionStart = lineStartIndex + index;
rtbMainLog.SelectionLength = keyword.Length;
rtbMainLog.SelectionColor = group.color;
index += keyword.Length;
}
}
}
}
private void preadjustmentButton_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Preadjustment open.");
using (FrmCordonelPreadjustmentUI frm = new FrmCordonelPreadjustmentUI())
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: Preadjustment closed.");
}
private void button1_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: GCI GUI interface open.");
using (FrmGCIAPI frm = new FrmGCIAPI())
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: GCI GUI interface closed.");
}
}
}

View File

@ -0,0 +1,311 @@
namespace GenesisCordonelInterface.UI
{
partial class MainView
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Panel pnlLeftMenu;
private System.Windows.Forms.Panel pnlMain;
private System.Windows.Forms.SplitContainer splitMain;
private System.Windows.Forms.SplitContainer splitBottom;
private System.Windows.Forms.RichTextBox rtbMainLog;
private System.Windows.Forms.Panel pnlSlotConfig;
private System.Windows.Forms.Panel pnlWorkerDebug;
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.Button btnSetup;
private System.Windows.Forms.Button btnRegisterStore;
private System.Windows.Forms.Button btnPulseSetup;
private System.Windows.Forms.Button preadjustmentButton;
private System.Windows.Forms.Button btnMeterInit;
private System.Windows.Forms.Button btnMetersAction;
private System.Windows.Forms.SplitContainer splitWorkArea;
private System.Windows.Forms.Panel pnlGciViewHost;
private void InitializeComponent()
{
this.pnlLeftMenu = new System.Windows.Forms.Panel();
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.preadjustmentButton = new System.Windows.Forms.Button();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.btnPulseSetup = new System.Windows.Forms.Button();
this.btnSetup = new System.Windows.Forms.Button();
this.btnRegisterStore = new System.Windows.Forms.Button();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.btnMeterInit = new System.Windows.Forms.Button();
this.btnMetersAction = new System.Windows.Forms.Button();
this.pnlMain = new System.Windows.Forms.Panel();
this.splitMain = new System.Windows.Forms.SplitContainer();
this.splitBottom = new System.Windows.Forms.SplitContainer();
this.rtbMainLog = new System.Windows.Forms.RichTextBox();
this.pnlSlotConfig = new System.Windows.Forms.Panel();
this.pnlWorkerDebug = new System.Windows.Forms.Panel();
this.splitWorkArea = new System.Windows.Forms.SplitContainer();
this.pnlGciViewHost = new System.Windows.Forms.Panel();
this.pnlLeftMenu.SuspendLayout();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.tabPage2.SuspendLayout();
this.groupBox3.SuspendLayout();
this.pnlMain.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitMain)).BeginInit();
this.splitMain.Panel1.SuspendLayout();
this.splitMain.Panel2.SuspendLayout();
this.splitMain.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitBottom)).BeginInit();
this.splitBottom.Panel1.SuspendLayout();
this.splitBottom.Panel2.SuspendLayout();
this.splitBottom.SuspendLayout();
this.SuspendLayout();
// pnlLeftMenu
this.pnlLeftMenu.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlLeftMenu.Controls.Add(this.tabControl1);
this.pnlLeftMenu.Dock = System.Windows.Forms.DockStyle.Left;
this.pnlLeftMenu.Location = new System.Drawing.Point(0, 0);
this.pnlLeftMenu.Name = "pnlLeftMenu";
this.pnlLeftMenu.Size = new System.Drawing.Size(190, 500);
this.pnlLeftMenu.TabIndex = 0;
// tabControl1
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(188, 498);
this.tabControl1.TabIndex = 0;
// tabPage1
this.tabPage1.Controls.Add(this.groupBox2);
this.tabPage1.Controls.Add(this.groupBox1);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(180, 472);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Laatzen API";
this.tabPage1.UseVisualStyleBackColor = true;
// groupBox1
this.groupBox1.Controls.Add(this.btnPulseSetup);
this.groupBox1.Controls.Add(this.btnSetup);
this.groupBox1.Controls.Add(this.btnRegisterStore);
this.groupBox1.Location = new System.Drawing.Point(6, 10);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(166, 150);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "GenesisToolBox";
// btnSetup
this.btnSetup.Location = new System.Drawing.Point(7, 20);
this.btnSetup.Name = "btnSetup";
this.btnSetup.Size = new System.Drawing.Size(150, 30);
this.btnSetup.TabIndex = 0;
this.btnSetup.Text = "Setup";
this.btnSetup.UseVisualStyleBackColor = true;
this.btnSetup.Click += new System.EventHandler(this.btnSetup_Click);
// btnRegisterStore
this.btnRegisterStore.Location = new System.Drawing.Point(7, 55);
this.btnRegisterStore.Name = "btnRegisterStore";
this.btnRegisterStore.Size = new System.Drawing.Size(150, 30);
this.btnRegisterStore.TabIndex = 1;
this.btnRegisterStore.Text = "Register Store";
this.btnRegisterStore.UseVisualStyleBackColor = true;
this.btnRegisterStore.Click += new System.EventHandler(this.btnRegisterStore_Click);
// btnPulseSetup
this.btnPulseSetup.Location = new System.Drawing.Point(7, 90);
this.btnPulseSetup.Name = "btnPulseSetup";
this.btnPulseSetup.Size = new System.Drawing.Size(150, 30);
this.btnPulseSetup.TabIndex = 2;
this.btnPulseSetup.Text = "Pulse Setup";
this.btnPulseSetup.UseVisualStyleBackColor = true;
this.btnPulseSetup.Click += new System.EventHandler(this.btnPulseSetup_Click);
// groupBox2
this.groupBox2.Controls.Add(this.preadjustmentButton);
this.groupBox2.Location = new System.Drawing.Point(6, 170);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(166, 70);
this.groupBox2.TabIndex = 1;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "CordonelPreadjustmentUI";
// preadjustmentButton
this.preadjustmentButton.Location = new System.Drawing.Point(6, 20);
this.preadjustmentButton.Name = "preadjustmentButton";
this.preadjustmentButton.Size = new System.Drawing.Size(150, 30);
this.preadjustmentButton.TabIndex = 0;
this.preadjustmentButton.Text = "Preadjustment";
this.preadjustmentButton.UseVisualStyleBackColor = true;
this.preadjustmentButton.Click += new System.EventHandler(this.preadjustmentButton_Click);
// tabPage2
this.tabPage2.Controls.Add(this.groupBox3);
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(180, 472);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "GCI API";
this.tabPage2.UseVisualStyleBackColor = true;
// groupBox3
this.groupBox3.Controls.Add(this.btnMeterInit);
this.groupBox3.Controls.Add(this.btnMetersAction);
this.groupBox3.Size = new System.Drawing.Size(166, 105);
this.groupBox3.Location = new System.Drawing.Point(6, 10);
this.groupBox3.Name = "groupBox3";
this.groupBox3.TabIndex = 0;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "GenesisCordonelInterface";
// btnMeterInit
this.btnMeterInit.Location = new System.Drawing.Point(6, 20);
this.btnMeterInit.Name = "btnMeterInit";
this.btnMeterInit.Size = new System.Drawing.Size(150, 30);
this.btnMeterInit.Text = "Meter Init";
this.btnMeterInit.UseVisualStyleBackColor = true;
this.btnMeterInit.Click += new System.EventHandler(this.btnMeterInit_Click);
// btnMetersAction
this.btnMetersAction.Location = new System.Drawing.Point(6, 58);
this.btnMetersAction.Name = "btnMetersAction";
this.btnMetersAction.Size = new System.Drawing.Size(150, 30);
this.btnMetersAction.Text = "Meters Action";
this.btnMetersAction.UseVisualStyleBackColor = true;
this.btnMetersAction.Click += new System.EventHandler(this.btnMetersAction_Click);
// pnlMain
this.pnlMain.Controls.Add(this.splitMain);
this.pnlMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlMain.Location = new System.Drawing.Point(190, 0);
this.pnlMain.Name = "pnlMain";
this.pnlMain.Padding = new System.Windows.Forms.Padding(5);
this.pnlMain.Size = new System.Drawing.Size(710, 500);
this.pnlMain.TabIndex = 1;
// splitMain
this.splitMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitMain.Location = new System.Drawing.Point(5, 5);
this.splitMain.Name = "splitMain";
this.splitMain.Orientation = System.Windows.Forms.Orientation.Horizontal;
this.splitMain.Panel1.Controls.Add(this.splitWorkArea);
this.splitMain.Panel1MinSize = 100;
this.splitMain.Panel2.Controls.Add(this.splitBottom);
this.splitMain.Panel2MinSize = 100;
this.splitMain.Size = new System.Drawing.Size(700, 490);
this.splitMain.SplitterDistance = 320;
this.splitMain.SplitterWidth = 6;
this.splitMain.TabIndex = 0;
// rtbMainLog
this.rtbMainLog.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtbMainLog.Font = new System.Drawing.Font("Consolas", 10F);
this.rtbMainLog.Name = "rtbMainLog";
this.rtbMainLog.ReadOnly = true;
this.rtbMainLog.Size = new System.Drawing.Size(700, 320);
this.rtbMainLog.TabIndex = 0;
this.rtbMainLog.Text = "";
// splitBottom
this.splitBottom.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitBottom.Location = new System.Drawing.Point(0, 0);
this.splitBottom.Name = "splitBottom";
this.splitBottom.Orientation = System.Windows.Forms.Orientation.Vertical;
this.splitBottom.Panel1.Controls.Add(this.pnlSlotConfig);
this.splitBottom.Panel1MinSize = 250;
this.splitBottom.Panel2.Controls.Add(this.pnlWorkerDebug);
this.splitBottom.Panel2MinSize = 250;
this.splitBottom.Size = new System.Drawing.Size(700, 164);
this.splitBottom.SplitterDistance = 320;
this.splitBottom.SplitterWidth = 6;
this.splitBottom.TabIndex = 0;
// pnlSlotConfig
this.pnlSlotConfig.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlSlotConfig.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlSlotConfig.Location = new System.Drawing.Point(0, 0);
this.pnlSlotConfig.Name = "pnlSlotConfig";
this.pnlSlotConfig.Size = new System.Drawing.Size(320, 164);
this.pnlSlotConfig.TabIndex = 0;
// pnlWorkerDebug
this.pnlWorkerDebug.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlWorkerDebug.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlWorkerDebug.Location = new System.Drawing.Point(0, 0);
this.pnlWorkerDebug.Name = "pnlWorkerDebug";
this.pnlWorkerDebug.Size = new System.Drawing.Size(374, 164);
this.pnlWorkerDebug.TabIndex = 0;
// splitWorkArea
this.splitWorkArea.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitWorkArea.Orientation = System.Windows.Forms.Orientation.Vertical;
this.splitWorkArea.Panel1MinSize = 25;
this.splitWorkArea.Panel2MinSize = 25;
this.splitWorkArea.SplitterWidth = 6;
// left = active GCI view
this.splitWorkArea.Panel1.Controls.Add(this.pnlGciViewHost);
// right = black memo/log
this.splitWorkArea.Panel2.Controls.Add(this.rtbMainLog);
// pnlGciViewHost
this.pnlGciViewHost.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlGciViewHost.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
// rtbMainLog
this.rtbMainLog.Dock = System.Windows.Forms.DockStyle.Fill;
// MainView
this.Controls.Add(this.pnlMain);
this.Controls.Add(this.pnlLeftMenu);
this.Name = "MainView";
this.Size = new System.Drawing.Size(900, 500);
this.pnlLeftMenu.ResumeLayout(false);
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false);
this.groupBox1.ResumeLayout(false);
this.tabPage2.ResumeLayout(false);
this.groupBox3.ResumeLayout(false);
this.pnlMain.ResumeLayout(false);
this.splitMain.Panel1.ResumeLayout(false);
this.splitMain.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitMain)).EndInit();
this.splitMain.ResumeLayout(false);
this.splitBottom.Panel1.ResumeLayout(false);
this.splitBottom.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitBottom)).EndInit();
this.splitBottom.ResumeLayout(false);
this.ResumeLayout(false);
}
}
}

View File

@ -0,0 +1,333 @@
using GenesisCordonelInterface.UI.LaatzenAPI_CordonelPreadjustmentUI;
using GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface;
using System;
using System.Drawing;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Xylem.Common.Ui.GenesisToolBox;
using Xylem.Common.Utils.Logging;
using GenesisCordonelInterface.API;
using GenesisCordonelInterface.UI.Debug;
namespace GenesisCordonelInterface.UI
{
public partial class MainView : UserControl
{
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface");
private static readonly Regex LogLevelRegex = new Regex(@"\|\s*(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\s*\|", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly InterfaceOutsideToGCI _api = new InterfaceOutsideToGCI();
private MeterBatchConfigPanel _batchPanel;
public MainView()
{
InitializeComponent();
InitializeDebugPanels();
//InitializeWorkerDebugPanel();
UiLogBus.MessageReceived += UiLogBus_MessageReceived;
rtbMainLog.BackColor = Color.Black;
rtbMainLog.ForeColor = Color.Gainsboro;
rtbMainLog.Font = new Font("Consolas", 9f);
rtbMainLog.ReadOnly = true;
rtbMainLog.HideSelection = false;
}
private void InitializeWorkerDebugPanel()
{
pnlWorkerDebug.Controls.Clear();
var debugPanel = new WorkerDebugPanel(_api)
{
Dock = DockStyle.Fill
};
pnlWorkerDebug.Controls.Add(debugPanel);
}
private void InitializeDebugPanels()
{
_batchPanel = new MeterBatchConfigPanel(_api)
{
Dock = DockStyle.Fill
};
pnlSlotConfig.Controls.Add(_batchPanel);
pnlWorkerDebug.Controls.Add(new WorkerDebugPanel(_api)
{
Dock = DockStyle.Fill
});
}
public void AddSlotRow()
{
_batchPanel?.AddEmptySlotRow();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
BeginInvoke(new Action(() =>
{
SetSafeSplitterDistance(splitWorkArea, 420);
}));
}
private void SetSafeSplitterDistance(SplitContainer split, int desired)
{
int width = split.ClientSize.Width;
int min1 = split.Panel1MinSize;
int min2 = split.Panel2MinSize;
int splitter = split.SplitterWidth;
int max = width - min2 - splitter;
if (width <= min1 + min2 + splitter)
return;
if (desired < min1)
desired = min1;
if (desired > max)
desired = max;
split.SplitterDistance = desired;
}
protected override void Dispose(bool disposing)
{
UiLogBus.MessageReceived -= UiLogBus_MessageReceived;
base.Dispose(disposing);
}
private IWin32Window DialogOwner
{
get
{
Form owner = FindForm();
return owner ?? (IWin32Window)this;
}
}
private void btnSetup_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Setup open");
using (FrmSetup frm = new FrmSetup())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Setup closed.");
}
private void btnRegisterStore_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Register Store open.");
using (FrmRegisterStore frm = new FrmRegisterStore())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Register Store closed.");
}
private void btnPulseSetup_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Pulse Setup open.");
using (FrmConfigurations frm = new FrmConfigurations())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Pulse Setup closed.");
}
private void preadjustmentButton_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: Preadjustment open.");
using (FrmCordonelPreadjustmentUI frm = new FrmCordonelPreadjustmentUI())
{
frm.ShowDialog(DialogOwner);
}
Logger.Trace("FORM: Preadjustment closed.");
}
private void button1_Click(object sender, EventArgs e)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace("FORM: GCI GUI interface open.");
using (var frm = new FrmGCIAPI(_api))
{
frm.ShowDialog(this);
}
Logger.Trace("FORM: GCI GUI interface closed.");
}
private void SwitchGciView(string name, Control view)
{
Logger.Trace("FORM: ---------------------------------");
Logger.Trace($"FORM: GCI VIEW -> {name} OPEN");
pnlGciViewHost.Controls.Clear();
view.Dock = DockStyle.Fill;
pnlGciViewHost.Controls.Add(view);
view.BringToFront();
Logger.Trace($"FORM: GCI VIEW -> {name} LOADED");
}
private void btnMeterInit_Click(object sender, EventArgs e)
{
SwitchGciView(
"MeterInit",
new MeterInitView(_api, AddSlotRow, SaveSlots));
}
private void btnMetersAction_Click(object sender, EventArgs e)
{
SwitchGciView(
"MetersAction",
new MetersActionView(_api));
}
public void ClearLog()
{
rtbMainLog.Clear();
Logger.Trace("Log cleared.");
}
private void UiLogBus_MessageReceived(string msg)
{
if (InvokeRequired)
{
BeginInvoke(new Action<string>(UiLogBus_MessageReceived), msg);
return;
}
string[] lines = msg.Replace("\r\n", "\n").Split('\n');
foreach (string originalLine in lines)
{
if (string.IsNullOrWhiteSpace(originalLine))
continue;
string line = originalLine;
Match m = LogLevelRegex.Match(line);
string indent = "";
if (m.Success)
{
int indentLength = m.Index + m.Length;
indent = new string(' ', indentLength);
}
string[] subLines = line.Split(new[] { " - " }, 2, StringSplitOptions.None);
string firstLine = line;
string rest = null;
if (subLines.Length == 2 && subLines[1].Length > 120)
{
firstLine = subLines[0] + " - " + subLines[1].Substring(0, 120);
rest = subLines[1].Substring(120);
}
AppendStyledLine(firstLine);
if (!string.IsNullOrEmpty(rest))
AppendStyledLine(indent + rest);
}
}
private void AppendStyledLine(string line)
{
int start = rtbMainLog.TextLength;
rtbMainLog.SelectionStart = start;
rtbMainLog.SelectionLength = 0;
rtbMainLog.SelectionColor = Color.Gainsboro;
rtbMainLog.AppendText(line + Environment.NewLine);
Match m = LogLevelRegex.Match(line);
if (m.Success)
{
rtbMainLog.SelectionStart = start + m.Index;
rtbMainLog.SelectionLength = m.Length;
rtbMainLog.SelectionColor = GetLogLevelColor(m.Groups[1].Value);
}
HighlightKeywordsInLine(line, start);
}
private Color GetLogLevelColor(string level)
{
switch (level.Trim().ToUpperInvariant())
{
case "TRACE": return Color.Gray;
case "DEBUG": return Color.DeepSkyBlue;
case "INFO": return Color.LimeGreen;
case "WARN": return Color.Orange;
case "ERROR": return Color.Red;
case "FATAL": return Color.Magenta;
default: return Color.Gainsboro;
}
}
private static readonly Tuple<Color, string[]>[] KeywordGroups =
{
Tuple.Create(Color.DeepSkyBlue, new[] { "REQUEST" }),
Tuple.Create(Color.Lime, new[] { "RESPONSE" }),
Tuple.Create(Color.Gold, new[] { "READ-REGISTER-SESSION", "UI-CLICK" })
};
private void HighlightKeywordsInLine(string line, int lineStartIndex)
{
foreach (var group in KeywordGroups)
{
foreach (var keyword in group.Item2)
{
int index = 0;
while ((index = line.IndexOf(keyword, index, StringComparison.Ordinal)) >= 0)
{
rtbMainLog.SelectionStart = lineStartIndex + index;
rtbMainLog.SelectionLength = keyword.Length;
rtbMainLog.SelectionColor = group.Item1;
index += keyword.Length;
}
}
}
}
private void ShowGciView(Control view)
{
pnlGciViewHost.Controls.Clear();
view.Dock = DockStyle.Fill;
pnlGciViewHost.Controls.Add(view);
}
public void SaveSlots()
{
var data = _batchPanel.GetGridData();
_api.SaveSlotSetup(data);
}
}
}

View File

@ -82,7 +82,7 @@
this.lblConfigSource.Name = "lblConfigSource";
this.lblConfigSource.Size = new System.Drawing.Size(75, 13);
this.lblConfigSource.TabIndex = 2;
this.lblConfigSource.Text = "ConfigSource:";
this.lblConfigSource.Text = "GciConfigSource:";
//
// cmbConfigSource
//
@ -314,7 +314,7 @@
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(624, 455);
this.ClientSize = new System.Drawing.Size(624, 650);
this.Controls.Add(this.btnSetPassword);
this.Controls.Add(this.btnWriteRegister);
this.Controls.Add(this.btnReadRegister);

View File

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using GenesisCordonelInterface.API;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
@ -8,36 +9,137 @@ namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
{
public partial class FrmGCIAPI : Form
{
private readonly InterfaceOutsideToGCI _api = new InterfaceOutsideToGCI();
/*
GCI UI async execution model
public FrmGCIAPI()
The UI thread must never execute long-running meter communication directly.
When the user clicks a button, the UI only reads input values from controls
(for example slot number) and then delegates the real work to the GCI API.
The API call is executed by a per-slot worker queue:
- one slot / one meter / one communication port has its own worker
- commands for the same meter are executed sequentially
- commands for different meters can run in parallel
- after await completes, execution returns back to the UI thread safely
This prevents the WinForms UI from freezing while keeping meter communication
safe and ordered.
workflow:
User clicks button
|
v
WinForms UI thread
(read textbox values only)
|
v
await _api.GetPcbIdAsync(slot)
|
v
InterfaceOutsideToGCI
(clean public facade)
|
v
InterfaceGCIToLaatzen
(API/business logic)
|
v
Per-slot ApiWorker queue
(slot 1 / slot 2 / slot 3 ...)
|
v
GenesisMeter communication
(blocking HW operation)
|
v
Result returned
|
v
UI thread continues after await
(update labels / show MessageBox)
The UI does not freeze because hardware communication is not executed directly inside the event handler.
The event handler only calls an async method and waits using await. While waiting, the UI thread remains free.
The actual work is executed on a worker thread assigned to a specific slot/meter.
For a single meter, requests are queued, so they are executed sequentially and do not run at the same time,
which prevents communication conflicts.
*/
private readonly InterfaceOutsideToGCI _api;
public FrmGCIAPI() : this(new InterfaceOutsideToGCI())
{
}
/// <summary>
/// Initializes the form and default UI values.
/// </summary>
public FrmGCIAPI(InterfaceOutsideToGCI api)
{
_api = api ?? throw new ArgumentNullException(nameof(api));
InitializeComponent();
InitializeDefaults();
}
/// <summary>
/// Sets default values for combo boxes.
/// </summary>
private void InitializeDefaults()
{
cmbConfigSource.DataSource = Enum.GetValues(typeof(ConfigSource));
cmbPasswordSource.DataSource = Enum.GetValues(typeof(PasswordSource));
cmbConfigSource.SelectedItem = ConfigSource.ExternStorage;
cmbConfigSource.SelectedItem = ConfigSource.InterfaceInputConfig;
cmbPasswordSource.SelectedItem = PasswordSource.OfflineFile;
}
private void ExecuteApiAction(Action action)
#region UI Helpers
/// <summary>
/// Executes an async API action with UI busy state and centralized error handling.
/// </summary>
private async Task ExecuteApiActionAsync(Func<Task> action)
{
try
{
action();
SetBusy(true);
await action();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "API call failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
SetBusy(false);
}
}
private int GetSlot()
/// <summary>
/// Enables/disables UI controls and updates cursor to indicate busy state.
/// </summary>
private void SetBusy(bool busy)
{
Cursor = busy ? Cursors.WaitCursor : Cursors.Default;
btnInit.Enabled = !busy;
btnConnectOne.Enabled = !busy;
btnConnectAll.Enabled = !busy;
btnDisconnect.Enabled = !busy;
btnGetPcbId.Enabled = !busy;
btnReadRegister.Enabled = !busy;
btnWriteRegister.Enabled = !busy;
btnSetPassword.Enabled = !busy;
}
/// <summary>
/// Safely parses slot number from UI textbox.
/// </summary>
private int GetSlotSafe()
{
if (!int.TryParse(txtSlot.Text, out int slot))
throw new Exception("Invalid slot number.");
@ -45,21 +147,28 @@ namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
return slot;
}
/// <summary>
/// Converts string input to best matching primitive type.
/// </summary>
private object ParseValue(string input)
{
if (int.TryParse(input, out int intValue))
return intValue;
if (uint.TryParse(input, out uint uintValue))
return uintValue;
if (bool.TryParse(input, out bool boolValue))
return boolValue;
if (int.TryParse(input, out int i)) return i;
if (uint.TryParse(input, out uint ui)) return ui;
if (bool.TryParse(input, out bool b)) return b;
return input;
}
private PortConfig? CreatePortConfig(string portName, string baudRateText)
/// <summary>
/// Creates a public GCI port configuration object from UI input values.
/// </summary>
/// <param name="portName">Name of the port (e.g. COM3).</param>
/// <param name="baudRateText">Baud rate entered in the UI.</param>
/// <returns>
/// Instance of <see cref="GciPublicModels.GciPortConfig"/> or null if port name is empty.
/// </returns>
/// <exception cref="Exception">Thrown when baud rate is invalid.</exception>
private GciPublicModels.GciPortConfig CreateGciPortConfig(string portName, string baudRateText)
{
if (string.IsNullOrWhiteSpace(portName))
return null;
@ -67,127 +176,267 @@ namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
if (!int.TryParse(baudRateText, out int baudRate))
throw new Exception($"Invalid baud rate for port {portName}.");
var cfg = new PortConfig
return new GciPublicModels.GciPortConfig
{
PortName = portName,
Type = "Serial"
Type = "Serial",
};
var sp = cfg.GetSerialPort();
if (sp == null)
throw new Exception($"Serial port object was not created for {portName}.");
sp.BaudRate = baudRate;
return cfg;
}
private ConfigSource GetConfigSource()
/// <summary>
/// Gets selected configuration source from UI and maps it to public GCI model.
/// </summary>
/// <returns>Selected <see cref="GciPublicModels.GciConfigSource"/>.</returns>
/// <exception cref="Exception">Thrown when no value is selected.</exception>
private GciPublicModels.GciConfigSource GetConfigSource()
{
if (cmbConfigSource.SelectedItem == null)
throw new Exception("ConfigSource is not selected.");
throw new Exception("Config source is not selected.");
return (ConfigSource)cmbConfigSource.SelectedItem;
return (GciPublicModels.GciConfigSource)cmbConfigSource.SelectedItem;
}
private PasswordSource GetPasswordSource()
/// <summary>
/// Gets selected password source from UI and maps it to public GCI model.
/// </summary>
/// <returns>Selected <see cref="GciPublicModels.GciPasswordSource"/>.</returns>
/// <exception cref="Exception">Thrown when no value is selected.</exception>
private GciPublicModels.GciPasswordSource GetPasswordSource()
{
if (cmbPasswordSource.SelectedItem == null)
throw new Exception("PasswordSource is not selected.");
throw new Exception("Password source is not selected.");
return (PasswordSource)cmbPasswordSource.SelectedItem;
return (GciPublicModels.GciPasswordSource)cmbPasswordSource.SelectedItem;
}
private void btnInit_Click(object sender, EventArgs e)
#endregion
#region Buttons
/// <summary>
/// Initializes meter using external configuration.
/// </summary>
private async void btnInit_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
int slot;
try { slot = GetSlotSafe(); }
catch (Exception ex)
{
int slot = GetSlot();
MessageBox.Show(ex.Message, "Invalid input");
return;
}
var requestPort = CreatePortConfig(txtRequestPort.Text, txtRequestBaudRate.Text);
var streamingPort = CreatePortConfig(txtStreamingPort.Text, txtStreamingBaudRate.Text);
await ExecuteApiActionAsync(async () =>
{
var request = new GciPublicModels.GciInitSlotRequest
{
SlotId = slot,
ConfigSource = GetConfigSource(),
PasswordSource = GetPasswordSource(),
RequestPort = CreateGciPortConfig(txtRequestPort.Text, txtRequestBaudRate.Text),
StreamingPort = CreateGciPortConfig(txtStreamingPort.Text, txtStreamingBaudRate.Text)
};
_api.InitOneMeterFromExtern(
slot,
GetConfigSource(),
GetPasswordSource(),
requestPort,
streamingPort);
var result = await _api.InitSlotAsync(request);
if (!result.Success)
throw new Exception(result.Message);
MessageBox.Show("Meter initialized.");
});
}
private void btnConnectOne_Click(object sender, EventArgs e)
/// <summary>
/// Connects to one meter.
/// </summary>
private async void btnConnectOne_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
int slot;
try { slot = GetSlotSafe(); }
catch (Exception ex)
{
_api.ConnectOneMeter(GetSlot());
MessageBox.Show(ex.Message, "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
var result = await _api.ConnectOneSlotAsync(slot);
if (!result.Success)
throw new Exception(result.Message);
MessageBox.Show($"Connected.\r\nPCB ID: {result.PcbId}");
});
}
private void btnConnectAll_Click(object sender, EventArgs e)
/// <summary>
/// Disconnects from current meter.
/// </summary>
private async void btnDisconnect_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
int slot;
try { slot = GetSlotSafe(); }
catch (Exception ex)
{
_api.ConnectAllMeters(GetSlot());
MessageBox.Show(ex.Message, "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
await _api.DisconnectAsync(slot);
MessageBox.Show("Disconnected.");
});
}
/// <summary>
/// Reads PCB ID asynchronously.
/// </summary>
private async void btnGetPcbId_Click(object sender, EventArgs e)
{
int slot;
try { slot = GetSlotSafe(); }
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
GciPublicModels.GciGetPcbIdResult result =
await _api.GetPcbIdAsync(slot);
if (!result.Success)
throw new Exception(result.Message);
MessageBox.Show($"PCB ID: {result.PcbId}");
});
}
private void btnDisconnect_Click(object sender, EventArgs e)
/// <summary>
/// Reads register value.
/// </summary>
private async void btnReadRegister_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
{
_api.Disconnect();
});
}
int slot;
string registerName = txtRegister.Text;
private void btnGetPcbId_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
try
{
_api.GetPcbId(GetSlot());
});
}
private void btnReadRegister_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
{
string registerName = txtRegister.Text;
slot = GetSlotSafe();
if (string.IsNullOrWhiteSpace(registerName))
throw new Exception("Register name is empty.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Invalid input");
return;
}
_api.ReadRegister(registerName);
await ExecuteApiActionAsync(async () =>
{
var result = await _api.ReadRegisterAsync(slot, registerName);
if (!result.Success)
throw new Exception(result.ErrorMessage);
MessageBox.Show($"Register: {result.RegisterName}\r\nRaw: {result.RawHex}");
});
}
private void btnWriteRegister_Click(object sender, EventArgs e)
private async void btnConnectAll_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
await ExecuteApiActionAsync(async () =>
{
string registerName = txtRegister.Text;
string valueText = txtValue.Text;
var slots = _api.GetSelectedSlots();
if (slots.Count == 0)
throw new Exception("No slots selected.");
foreach (int slot in slots)
{
var result = await _api.ConnectOneSlotAsync(slot);
if (!result.Success)
throw new Exception($"Slot {slot}: {result.Message}");
}
MessageBox.Show("Selected meters connected.");
});
}
private async void btnWriteRegister_Click(object sender, EventArgs e)
{
int slot;
string registerName = txtRegister.Text;
string valueText = txtValue.Text;
try
{
slot = GetSlotSafe();
if (string.IsNullOrWhiteSpace(registerName))
throw new Exception("Register name is empty.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
object value = ParseValue(valueText);
_api.WriteRegister(registerName, value, false, false);
var result = await _api.WriteRegisterAsync(
slot,
registerName,
value,
false,
false);
if (!result.Success)
throw new Exception(result.ErrorMessage);
MessageBox.Show($"Write OK: {registerName} = {value}");
});
}
private void btnSetPassword_Click(object sender, EventArgs e)
private async void btnSetPassword_Click(object sender, EventArgs e)
{
ExecuteApiAction(() =>
int slot;
string password = txtPassword.Text;
try
{
string password = txtPassword.Text;
slot = GetSlotSafe();
if (string.IsNullOrWhiteSpace(password))
throw new Exception("Password is empty.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Invalid input");
return;
}
_api.SetMeterPassword(password);
await ExecuteApiActionAsync(async () =>
{
bool ok = await _api.SetMeterPasswordAsync(slot, password);
if (!ok)
throw new Exception("Set password failed.");
MessageBox.Show("Password set successfully.");
});
}
#endregion
}
}

View File

@ -0,0 +1,49 @@
namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
{
partial class MeterInitView
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Button btnReloadSetup;
private System.Windows.Forms.Button btnSaveSetup;
private System.Windows.Forms.Button btnAddSlot;
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.btnReloadSetup = new System.Windows.Forms.Button();
this.btnSaveSetup = new System.Windows.Forms.Button();
this.btnAddSlot = new System.Windows.Forms.Button();
this.SuspendLayout();
this.btnReloadSetup.SetBounds(20, 20, 140, 32);
this.btnReloadSetup.Text = "Reload Setup";
this.btnReloadSetup.Click += new System.EventHandler(this.btnReloadSetup_Click);
this.btnSaveSetup.SetBounds(170, 20, 140, 32);
this.btnSaveSetup.Text = "Save Setup";
this.btnSaveSetup.Click += new System.EventHandler(this.btnSaveSetup_Click);
this.btnAddSlot.SetBounds(20, 65, 290, 32);
this.btnAddSlot.Text = "Add Slot";
this.btnAddSlot.Click += new System.EventHandler(this.btnAddSlot_Click);
this.Controls.Add(this.btnReloadSetup);
this.Controls.Add(this.btnSaveSetup);
this.Controls.Add(this.btnAddSlot);
this.Name = "MeterInitView";
this.Size = new System.Drawing.Size(340, 130);
this.ResumeLayout(false);
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Windows.Forms;
using GenesisCordonelInterface.API;
namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
{
public partial class MeterInitView : UserControl
{
private readonly InterfaceOutsideToGCI _api;
private readonly Action _addSlotAction;
private readonly Action _saveAction;
public MeterInitView(InterfaceOutsideToGCI api, Action addSlotAction, Action saveAction)
{
_api = api;
_addSlotAction = addSlotAction;
_saveAction = saveAction;
InitializeComponent();
}
#region BUTTONS
// ----------------------------------------------------
private void btnReloadSetup_Click(object sender, EventArgs e)
{
ExecuteApiAction(() => _api.ReloadSlotSetup());
}
private void btnSaveSetup_Click(object sender, EventArgs e)
{
_saveAction?.Invoke();
}
private void btnAddSlot_Click(object sender, EventArgs e)
{
_addSlotAction?.Invoke();
}
// ----------------------------------------------------
#endregion
private void ExecuteApiAction(Action action)
{
try
{
SetBusy(true);
action();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "API call failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
SetBusy(false);
}
}
private void SetBusy(bool busy)
{
Cursor = busy ? Cursors.WaitCursor : Cursors.Default;
btnReloadSetup.Enabled = !busy;
btnSaveSetup.Enabled = !busy;
btnAddSlot.Enabled = !busy;
}
}
}

View File

@ -0,0 +1,99 @@
namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
{
partial class MetersActionView
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label lblRegister;
private System.Windows.Forms.TextBox txtRegister;
private System.Windows.Forms.Label lblValue;
private System.Windows.Forms.TextBox txtValue;
private System.Windows.Forms.Label lblPassword;
private System.Windows.Forms.TextBox txtPassword;
private System.Windows.Forms.Button btnConnectSelected;
private System.Windows.Forms.Button btnReadRegister;
private System.Windows.Forms.Button btnWriteRegister;
private System.Windows.Forms.Button btnSetPassword;
private System.Windows.Forms.Button btnDisconnectSelected;
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.lblRegister = new System.Windows.Forms.Label();
this.txtRegister = new System.Windows.Forms.TextBox();
this.lblValue = new System.Windows.Forms.Label();
this.txtValue = new System.Windows.Forms.TextBox();
this.lblPassword = new System.Windows.Forms.Label();
this.txtPassword = new System.Windows.Forms.TextBox();
this.btnConnectSelected = new System.Windows.Forms.Button();
this.btnReadRegister = new System.Windows.Forms.Button();
this.btnWriteRegister = new System.Windows.Forms.Button();
this.btnSetPassword = new System.Windows.Forms.Button();
this.btnDisconnectSelected = new System.Windows.Forms.Button();
this.SuspendLayout();
this.lblRegister.SetBounds(20, 20, 100, 20);
this.lblRegister.Text = "Register:";
this.txtRegister.SetBounds(130, 17, 250, 20);
this.txtRegister.Text = "GENESISFLOW_TriggerTest";
this.lblValue.SetBounds(20, 55, 100, 20);
this.lblValue.Text = "Value:";
this.txtValue.SetBounds(130, 52, 250, 20);
this.txtValue.Text = "1";
this.lblPassword.SetBounds(20, 90, 100, 20);
this.lblPassword.Text = "Password:";
this.txtPassword.SetBounds(130, 87, 250, 20);
this.txtPassword.Text = "1234";
this.btnConnectSelected.SetBounds(20, 140, 170, 32);
this.btnConnectSelected.Text = "Connect Selected";
this.btnConnectSelected.Click += new System.EventHandler(this.btnConnectSelected_Click);
this.btnDisconnectSelected.SetBounds(210, 140, 170, 32);
this.btnDisconnectSelected.Text = "Disconnect Selected";
this.btnDisconnectSelected.Click += new System.EventHandler(this.btnDisconnectSelected_Click);
this.btnReadRegister.SetBounds(20, 185, 170, 32);
this.btnReadRegister.Text = "Read Register";
this.btnReadRegister.Click += new System.EventHandler(this.btnReadRegister_Click);
this.btnWriteRegister.SetBounds(210, 185, 170, 32);
this.btnWriteRegister.Text = "Write Register";
this.btnWriteRegister.Click += new System.EventHandler(this.btnWriteRegister_Click);
this.btnSetPassword.SetBounds(20, 230, 170, 32);
this.btnSetPassword.Text = "Set Password";
this.btnSetPassword.Click += new System.EventHandler(this.btnSetPassword_Click);
this.Controls.Add(this.lblRegister);
this.Controls.Add(this.txtRegister);
this.Controls.Add(this.lblValue);
this.Controls.Add(this.txtValue);
this.Controls.Add(this.lblPassword);
this.Controls.Add(this.txtPassword);
this.Controls.Add(this.btnConnectSelected);
this.Controls.Add(this.btnDisconnectSelected);
this.Controls.Add(this.btnReadRegister);
this.Controls.Add(this.btnWriteRegister);
this.Controls.Add(this.btnSetPassword);
this.Name = "MetersActionView";
this.Size = new System.Drawing.Size(420, 320);
this.ResumeLayout(false);
this.PerformLayout();
}
}
}

View File

@ -0,0 +1,180 @@
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using GenesisCordonelInterface.API;
namespace GenesisCordonelInterface.UI.StaraTuraAPI_GenesisCordonelInterface
{
public partial class MetersActionView : UserControl
{
private readonly InterfaceOutsideToGCI _api;
public MetersActionView(InterfaceOutsideToGCI api)
{
_api = api ?? throw new ArgumentNullException(nameof(api));
InitializeComponent();
}
private async Task ExecuteApiActionAsync(Func<Task> action)
{
try
{
SetBusy(true);
await action();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "API call failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
SetBusy(false);
}
}
private void SetBusy(bool busy)
{
Cursor = busy ? Cursors.WaitCursor : Cursors.Default;
btnConnectSelected.Enabled = !busy;
btnReadRegister.Enabled = !busy;
btnWriteRegister.Enabled = !busy;
btnSetPassword.Enabled = !busy;
btnDisconnectSelected.Enabled = !busy;
}
private object ParseValue(string input)
{
if (int.TryParse(input, out int i)) return i;
if (uint.TryParse(input, out uint ui)) return ui;
if (bool.TryParse(input, out bool b)) return b;
return input;
}
private void ValidateSelectedSlots()
{
if (_api.GetSelectedSlots().Count == 0)
throw new Exception("No slots selected.");
}
private async void btnConnectSelected_Click(object sender, EventArgs e)
{
await ExecuteApiActionAsync(async () =>
{
ValidateSelectedSlots();
foreach (int slot in _api.GetSelectedSlots())
{
var result = await _api.ConnectOneSlotAsync(slot);
if (!result.Success)
throw new Exception($"Slot {slot}: {result.Message}");
}
MessageBox.Show("Selected meters connected.");
});
}
private async void btnReadRegister_Click(object sender, EventArgs e)
{
string registerName = txtRegister.Text;
if (string.IsNullOrWhiteSpace(registerName))
{
MessageBox.Show("Register name is empty.", "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
ValidateSelectedSlots();
foreach (int slot in _api.GetSelectedSlots())
{
var result = await _api.ReadRegisterAsync(slot, registerName);
if (!result.Success)
throw new Exception($"Slot {slot}: {result.ErrorMessage}");
}
MessageBox.Show("Read register finished.");
});
}
private async void btnWriteRegister_Click(object sender, EventArgs e)
{
string registerName = txtRegister.Text;
string valueText = txtValue.Text;
if (string.IsNullOrWhiteSpace(registerName))
{
MessageBox.Show("Register name is empty.", "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
ValidateSelectedSlots();
object value = ParseValue(valueText);
foreach (int slot in _api.GetSelectedSlots())
{
var result = await _api.WriteRegisterAsync(
slot,
registerName,
value,
false,
false);
if (!result.Success)
throw new Exception($"Slot {slot}: {result.ErrorMessage}");
}
MessageBox.Show("Write register finished.");
});
}
private async void btnSetPassword_Click(object sender, EventArgs e)
{
string password = txtPassword.Text;
if (string.IsNullOrWhiteSpace(password))
{
MessageBox.Show("Password is empty.", "Invalid input");
return;
}
await ExecuteApiActionAsync(async () =>
{
ValidateSelectedSlots();
foreach (int slot in _api.GetSelectedSlots())
{
bool ok = await _api.SetMeterPasswordAsync(slot, password);
if (!ok)
throw new Exception($"Slot {slot}: Set password failed.");
}
MessageBox.Show("Password set for selected meters.");
});
}
private async void btnDisconnectSelected_Click(object sender, EventArgs e)
{
await ExecuteApiActionAsync(async () =>
{
ValidateSelectedSlots();
foreach (int slot in _api.GetSelectedSlots())
{
await _api.DisconnectAsync(slot);
}
MessageBox.Show("Selected meters disconnected.");
});
}
}
}

View File

@ -0,0 +1,35 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Copies the prepared GCI runtime package into the TBF output directory.
This ensures all GCI dependencies are available next to TBF.exe.
-->
<Target Name="CopyGciRuntimePackageToTbfOutput" AfterTargets="Build">
<!-- Source directory containing the prepared GCI runtime package -->
<PropertyGroup>
<GciRuntimePackageDir>$(ProjectDir)..\GenesisCordonelInterface\RuntimePackage\Package\</GciRuntimePackageDir>
</PropertyGroup>
<!-- Collect runtime package files -->
<ItemGroup>
<GciRuntimeFiles Include="$(GciRuntimePackageDir)*.dll" />
<GciRuntimeFiles Include="$(GciRuntimePackageDir)*.exe" />
<GciRuntimeFiles Include="$(GciRuntimePackageDir)*.config" />
<GciRuntimeFiles Include="$(GciRuntimePackageDir)*.json" />
</ItemGroup>
<!-- Build output messages -->
<Message Text="Copying GCI runtime package to TBF output: $(TargetDir)" Importance="High" />
<Message Text="Source: $(GciRuntimePackageDir)" Importance="High" />
<Message Text="Files: @(GciRuntimeFiles)" Importance="Normal" />
<!-- Copy package files into TBF output directory -->
<Copy
SourceFiles="@(GciRuntimeFiles)"
DestinationFolder="$(TargetDir)"
SkipUnchangedFiles="false" />
</Target>
</Project>

View File

@ -12,7 +12,7 @@ namespace TBF.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@ -1,14 +1,18 @@
///
using GenesisCordonelInterface.API;
using log4net;
///
/// Copyright (c) 2015-2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using TBF.Rig.Generic;
using GciGUIType = GenesisCordonelInterface.UI.MainView;
using GciType = GenesisCordonelInterface.API.InterfaceOutsideToGCI;
using UdsReaderType = TBF.Rig.Input.DataStorage.UniDataStorageReader.Reader;
using UdsWriterType = TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
using ExternalInterfaceType = GenesisCordonelInterface.API.InterfaceOutsideToGCI;
using ExternalInterfaceGUIType = GenesisCordonelInterface.UI.MainForm;
namespace TBF.Rig.BridgeComponents.GciBridge
{
@ -19,37 +23,37 @@ namespace TBF.Rig.BridgeComponents.GciBridge
public class GciBridge : ComponentBase
{
private static readonly ILog log = LogManager.GetLogger(typeof(GciBridge));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
public override string ToString()
{
return string.Format("{0}({1})", ClassName, Cfg.ToString(-1));
}
readonly GciBridgeCfg gciBridgeCfg;
/// <summary>
/// Linked UniDataStorage reader component.
/// </summary>
readonly UdsReaderType reader;
/// <summary>
/// Linked UniDataStorage writer component.
/// </summary>
readonly UdsWriterType writer;
/// <summary>
/// Placeholder for GCI GUI entry point.
/// Replace object with real GCI MainForm type later.
/// </summary>
ExternalInterfaceGUIType gciGUI;
GciGUIType gciGUI;
Form gciGuiHostForm;
/// <summary>
/// Placeholder for GCI external/public interface.
/// Replace object with real GCI interface type later.
/// </summary>
ExternalInterfaceType gciExternalInterface;
GciType gciExternalInterface;
public bool HasReader { get { return reader != null; } }
public bool HasWriter { get { return writer != null; } }
public bool IsGuiInitialized { get { return gciGUI != null; } }
public bool IsExternalInitialized { get { return gciExternalInterface != null; } }
public UdsReaderType GetReader()
{
return reader;
}
public UdsWriterType GetWriter()
{
return writer;
}
public GciBridge() { }
public GciBridge(IComponentCfg cfg, IList<IComponent> components)
@ -91,36 +95,40 @@ namespace TBF.Rig.BridgeComponents.GciBridge
log.FatalFormat("{0} initialized: {1}", Name, this);
}
/// <summary>
/// Initializes access to GCI GUI.
/// Replace placeholder implementation with real MainForm creation.
/// </summary>
void TryInitializeGui()
{
try
{
using (gciGUI = new ExternalInterfaceGUIType())
{
gciGUI.ShowDialog(/*this*/);
}
if (gciGuiHostForm != null && !gciGuiHostForm.IsDisposed)
return;
log.InfoFormat("{0}: GCI GUI initialized.", Name);
gciGUI = new GciGUIType();
gciGUI.Dock = DockStyle.Fill;
gciGuiHostForm = new Form();
gciGuiHostForm.Text = "Genesis Cordonel Interface";
gciGuiHostForm.Width = 1300;
gciGuiHostForm.Height = 600;
gciGuiHostForm.StartPosition = FormStartPosition.CenterScreen;
gciGuiHostForm.Controls.Add(gciGUI);
log.InfoFormat("{0}: GCI GUI view initialized.", Name);
}
catch (Exception ex)
{
log.Error("Failed to initialize GCI GUI.", ex);
log.Error("Failed to initialize GCI GUI view.", ex);
}
}
/// <summary>
/// Initializes access to GCI external/public interface.
/// Replace placeholder implementation with real interface creation.
/// </summary>
void TryInitializeExternalInterface()
{
try
{
gciExternalInterface = new ExternalInterfaceType();
if (gciExternalInterface != null)
return;
gciExternalInterface = new GciType();
log.InfoFormat("{0}: GCI external interface initialized.", Name);
}
@ -130,80 +138,147 @@ namespace TBF.Rig.BridgeComponents.GciBridge
}
}
/// <summary>
/// Shows GCI GUI if GUI access is enabled.
/// </summary>
public void ShowGui()
{
if (!gciBridgeCfg.EnableGuiAccess) return;
if (!IsGuiInitialized)
if (gciGuiHostForm == null || gciGuiHostForm.IsDisposed)
{
TryInitializeGui();
}
if (gciGuiHostForm == null) return;
gciGuiHostForm.Show();
gciGuiHostForm.BringToFront();
log.InfoFormat("{0}: ShowGui invoked.", Name);
}
/// <summary>
/// Hides GCI GUI if GUI access is enabled.
/// </summary>
public void HideGui()
{
if (!gciBridgeCfg.EnableGuiAccess) return;
if (!IsGuiInitialized) return;
if (gciGuiHostForm == null || gciGuiHostForm.IsDisposed) return;
gciGuiHostForm.Hide();
using (gciGUI)
{
gciGUI.Hide(/*this*/);
}
log.InfoFormat("{0}: HideGui invoked.", Name);
}
/// <summary>
/// Connects the external GCI interface.
/// </summary>
public void ConnectExternal()
void EnsureExternalInterface()
{
if (!gciBridgeCfg.EnableExternalAccess) return;
if (!gciBridgeCfg.EnableExternalAccess)
throw new Exception("GCI external access is disabled.");
if (!IsExternalInitialized)
{
TryInitializeExternalInterface();
}
/// TODO:
/// Replace with real external interface connect call.
log.InfoFormat("{0}: ConnectExternal invoked.", Name);
if (gciExternalInterface == null)
throw new Exception("GCI external interface is not initialized.");
}
/// <summary>
/// Disconnects the external GCI interface.
/// </summary>
public void DisconnectExternal()
void EnsureReader()
{
if (!gciBridgeCfg.EnableExternalAccess) return;
if (!IsExternalInitialized) return;
/// TODO:
/// Replace with real external interface disconnect call.
log.InfoFormat("{0}: DisconnectExternal invoked.", Name);
if (reader == null)
throw new Exception("UniDataStorageReader is not linked to GciBridge.");
}
/// <summary>
/// Returns linked reader component.
/// </summary>
public UdsReaderType GetReader()
// API:
#region ======================================= GCI Public Interface =======================================
public async Task<GciPublicModels.GciInitSlotResult> InitSlotAsync(GciPublicModels.GciInitSlotRequest request)
{
return reader;
EnsureExternalInterface();
if (request == null)
throw new ArgumentNullException("request");
if (request.SlotId <= 0)
throw new ArgumentException("Invalid slot id.");
GciPublicModels.GciInitSlotResult result =
await gciExternalInterface.InitSlotAsync(request);
log.InfoFormat("{0}: InitSlotAsync invoked. {1}, Result={2}", Name, request, result);
return result;
}
/// <summary>
/// Returns linked writer component.
/// </summary>
public UdsWriterType GetWriter()
public async Task<GciPublicModels.GciSlotInfo> GetSlotAsync(int slotId)
{
return writer;
EnsureExternalInterface();
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
GciPublicModels.GciSlotInfo result =
await gciExternalInterface.GetSlotAsync(slotId);
log.InfoFormat("{0}: GetSlotAsync({1}) invoked. Result={2}", Name, slotId, result);
return result;
}
public async Task<GciPublicModels.GciCleanSlotsResult> CleanSlotsAsync()
{
EnsureExternalInterface();
GciPublicModels.GciCleanSlotsResult result =
await gciExternalInterface.CleanSlotsAsync();
log.InfoFormat("{0}: CleanSlotsAsync invoked.", Name);
return result;
}
public async Task<GciPublicModels.GciGetPcbIdResult> GetPcbIdAsync(
int slotId,
CancellationToken token = default)
{
EnsureExternalInterface();
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await gciExternalInterface.GetPcbIdAsync(slotId, token);
log.InfoFormat("{0}: GetPcbIdAsync({1}) invoked. Result={2}", Name, slotId, result);
return result;
}
public async Task<GciPublicModels.GciConnectResult> ConnectAsync(
int slotId,
CancellationToken token = default)
{
EnsureExternalInterface();
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await gciExternalInterface.ConnectOneSlotAsync(slotId, token);
log.InfoFormat("{0}: ConnectAsync({1}) invoked. Result={2}", Name, slotId, result);
return result;
}
public async Task<GciPublicModels.GciDisconnectResult> DisconnectAsync(
int slotId,
CancellationToken token = default)
{
EnsureExternalInterface();
if (slotId <= 0)
throw new ArgumentException("Invalid slot id.");
var result = await gciExternalInterface.DisconnectAsync(slotId, token);
log.InfoFormat("{0}: DisconnectAsync({1}) invoked. Result={2}", Name, slotId, result);
return result;
}
#endregion
}
}

View File

@ -172,12 +172,42 @@ namespace TBF.Rig.BridgeComponents.GciBridge
private void connectExternalButton_Click(object sender, EventArgs e)
{
/// TODO
try
{
GciBridge bridge = TbfComponents.FindComponent(config.Name) as GciBridge;
if (bridge == null)
{
MessageBox.Show("GciBridge component was not found.", "GCI Bridge");
return;
}
//bridge.ConnectExternal();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "GCI Bridge error");
}
}
private void showGuiButton_Click(object sender, EventArgs e)
{
/// TODO
try
{
GciBridge bridge = TbfComponents.FindComponent(config.Name) as GciBridge;
if (bridge == null)
{
MessageBox.Show("GciBridge component was not found.", "GCI Bridge");
return;
}
bridge.ShowGui();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "GCI Bridge error");
}
}
}
}

View File

@ -0,0 +1,14 @@
using log4net;
///
/// Copyright (c) 2015-2021 Sensus Slovensko a.s.
///
using System;
using TBF.Rig.Sequences;
namespace TBF.Rig.BridgeComponents.GciBridge
{
public class GciBridgeOp
{
}
}

View File

@ -13,6 +13,7 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
using Xylem.Common.Metrology.Measurements;
using Xylem.Common.Metrology.Measurements.Consts;
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
using Common;
using System.IO.Ports;
@ -261,12 +262,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication.GenesisHead
//myGenesis.SetLogger(); - private, but called into basic constructor!
myGenesis.SetupGenesisMeter(
SlotNr,
new Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig.PortConfig()
new /*Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig.*/PortConfig()
{
Type = "Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort",
PortName =$"COM{HeadComPortNr}"
},
new Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig.PortConfig()
new /*Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig.*/PortConfig()
{
Type = "",
PortName = $"COM{OptoComPortNr}"
@ -536,7 +537,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication.GenesisHead
{
myGenesis.BuildAndCheckCalibFactorsAllChannels(refVol.Value / 1000, testTimeS, Q2ErrWOCorrection);
myGenesis.BuildAndCheckCalibFactorsAllChannels(refVol.Value / 1000, testTimeS, Q2ErrWOCorrection, 0.0, (int?)null);
myGenesis.SetCalibFactorsAllChannels(false);
}

View File

@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TBF</RootNamespace>
<AssemblyName>TBF</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>2.0</OldToolsVersion>
@ -173,6 +173,10 @@
<Reference Include="Xylem.Common.CommonCore">
<HintPath>..\packages\Common\Xylem.Common.CommonCore.dll</HintPath>
</Reference>
<Reference Include="Xylem.Common.Hardware.Interfaces.Ports.PortCore, Version=2.8.18.15967, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Common\Xylem.Common.Hardware.Interfaces.Ports.PortCore.dll</HintPath>
</Reference>
<Reference Include="Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig">
<HintPath>..\packages\Common\Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig.dll</HintPath>
</Reference>
@ -211,6 +215,7 @@
<Compile Include="Rig\BridgeComponents\GciBridge\GciBridgeCfgCtrl.Designer.cs">
<DependentUpon>GciBridgeCfgCtrl.cs</DependentUpon>
</Compile>
<Compile Include="Rig\BridgeComponents\GciBridge\GciBridgeOp.cs" />
<Compile Include="Rig\BuiltIn\PumpTandem\Pump.cs" />
<Compile Include="Rig\BuiltIn\PumpTandem\PumpCfg.cs" />
<Compile Include="Rig\BuiltIn\PumpTandem\PumpCfgCtrl.cs">
@ -4185,6 +4190,7 @@
<DependentUpon>TestProgressCtrl.cs</DependentUpon>
</EmbeddedResource>
<None Include="app.config" />
<Content Include="Build\CopyGci.targets.xml" />
<Content Include="CameraBinaryFiles\GrabImage">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@ -4402,6 +4408,7 @@
<Name>Results</Name>
</ProjectReference>
</ItemGroup>
<Import Project="Build\CopyGci.targets.xml" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,46 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.122.19.1, Culture=neutral, PublicKeyToken=89b483f429c47342" />
<section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.122.19.1, Culture=neutral, PublicKeyToken=89b483f429c47342"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<system.data>
<DbProviderFactories>
<remove invariant="Oracle.ManagedDataAccess.Client" />
<add name="ODP.NET, Managed Driver" invariant="Oracle.ManagedDataAccess.Client" description="Oracle Data Provider for .NET, Managed Driver" type="Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess, Version=4.122.19.1, Culture=neutral, PublicKeyToken=89b483f429c47342" />
<remove invariant="Oracle.ManagedDataAccess.Client"/>
<add name="ODP.NET, Managed Driver" invariant="Oracle.ManagedDataAccess.Client" description="Oracle Data Provider for .NET, Managed Driver" type="Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess, Version=4.122.19.1, Culture=neutral, PublicKeyToken=89b483f429c47342"/>
</DbProviderFactories>
</system.data>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<publisherPolicy apply="no" />
<assemblyIdentity name="Oracle.ManagedDataAccess" publicKeyToken="89b483f429c47342" culture="neutral" />
<bindingRedirect oldVersion="4.121.0.0 - 4.65535.65535.65535" newVersion="4.122.19.1" />
<publisherPolicy apply="no"/>
<assemblyIdentity name="Oracle.ManagedDataAccess" publicKeyToken="89b483f429c47342" culture="neutral"/>
<bindingRedirect oldVersion="4.121.0.0 - 4.65535.65535.65535" newVersion="4.122.19.1"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Iesi.Collections" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Iesi.Collections" publicKeyToken="aa95f207798dfdb4" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="mscorlib" publicKeyToken="b77a5c561934e089" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.4000" newVersion="4.0.0.4000" />
<assemblyIdentity name="NHibernate" publicKeyToken="aa95f207798dfdb4" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.4000" newVersion="4.0.0.4000"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NLog" publicKeyToken="5120e14c03d0593c" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
<assemblyIdentity name="NLog" publicKeyToken="5120e14c03d0593c" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<oracle.manageddataaccess.client>
<version number="*">
<dataSources>
<dataSource alias="SampleDataSource" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) " />
<dataSource alias="SampleDataSource" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) "/>
</dataSources>
</version>
</oracle.manageddataaccess.client>

View File

@ -3,11 +3,11 @@
<package id="Castle.Core" version="5.1.1" targetFramework="net472" />
<package id="FluentNHibernate" version="2.0.3.0" targetFramework="net40" />
<package id="Iesi.Collections" version="4.0.0.4000" targetFramework="net40" />
<package id="MySql.Data" version="6.6.5" targetFramework="net20" />
<package id="MySql.Data" version="6.6.5" targetFramework="net20" requireReinstallation="true" />
<package id="NHibernate" version="4.0.4.4000" targetFramework="net40" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
<package id="Oracle.ManagedDataAccess" version="19.11.0" targetFramework="net472" />
<package id="System.Data.SQLite" version="1.0.90.0" targetFramework="net40" />
<package id="System.Data.SQLite" version="1.0.90.0" targetFramework="net40" requireReinstallation="true" />
<package id="System.Drawing.Common" version="9.0.5" targetFramework="net472" />
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />