Upgrade (develop) - GCI - GciBridge - full parallel threading slots actions
This commit is contained in:
parent
63f8dd7467
commit
5e637ae02e
@ -1,5 +1,6 @@
|
||||
using GenesisCordonelInterface.Core.Threading;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -9,6 +10,7 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Linq;
|
||||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore;
|
||||
using Xylem.Common.Hardware.Interfaces.Ports.PortCore.EventArguments;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
|
||||
@ -30,6 +32,10 @@ namespace GenesisCordonelInterface.API
|
||||
|
||||
private readonly MeterBatch _meterBatch = new MeterBatch();
|
||||
|
||||
// Protects all access to _meterBatch.ListOfMeters
|
||||
private readonly object _meterBatchLock = new object();
|
||||
private static readonly object _setupGenesisMeterLock = new object();
|
||||
|
||||
private readonly ConcurrentDictionary<int, ApiWorker> _workers = new ConcurrentDictionary<int, ApiWorker>();
|
||||
|
||||
private readonly ConcurrentDictionary<int, bool> _selectedSlots = new ConcurrentDictionary<int, bool>();
|
||||
@ -58,7 +64,7 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
Slot = x.Key,
|
||||
Name = x.Value.Name,
|
||||
QueueLength = x.Value.IsDisposed ? 0 : x.Value.QueueLength,
|
||||
QueueLength = /*x.Value.IsDisposed ? 0 : */x.Value.QueueLength,
|
||||
IsBusy = !x.Value.IsDisposed && x.Value.IsBusy,
|
||||
CurrentOperation = x.Value.IsDisposed ? null : x.Value.CurrentOperation,
|
||||
LastError = x.Value.LastError,
|
||||
@ -72,8 +78,17 @@ namespace GenesisCordonelInterface.API
|
||||
|
||||
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
|
||||
{
|
||||
return _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
List<GenesisMeter> meters;
|
||||
|
||||
//just snapshot of list under lock
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
meters = _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return meters
|
||||
.Select(m => new MeterBatchDebugStatus
|
||||
{
|
||||
Slot = m.Slot,
|
||||
@ -81,13 +96,19 @@ namespace GenesisCordonelInterface.API
|
||||
PcbId = m.PcbId,
|
||||
IsConnected = m.IsConnected,
|
||||
IsLoggedOn = m.IsLoggedOn,
|
||||
RequestPort = m.RequestPort?.GetPortName(),
|
||||
StreamingPort = m.StreamingPort?.GetPortName(),
|
||||
RequestPortType =
|
||||
m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.RfidSerialPort ? "RFID" :
|
||||
m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.UartSerialPort ? "UART" :
|
||||
m.RequestPort is Xylem.Common.Hardware.Interfaces.Ports.SerialPorts.IrdaSerialPort ? "IRDA" :
|
||||
null,
|
||||
|
||||
RequestPort = m.requestPortConfig.HasValue
|
||||
? m.requestPortConfig.Value.PortName
|
||||
: "",
|
||||
|
||||
StreamingPort = m.streamingPortConfig.HasValue
|
||||
? m.streamingPortConfig.Value.PortName
|
||||
: "",
|
||||
|
||||
RequestPortType = m.requestPortConfig.HasValue
|
||||
? m.requestPortConfig.Value.Type
|
||||
: "",
|
||||
|
||||
FwVersion = m.FwVersion,
|
||||
InterfaceVersion = m.InterfaceInfo?.InterfaceVersion
|
||||
})
|
||||
@ -126,16 +147,57 @@ namespace GenesisCordonelInterface.API
|
||||
|
||||
#region ================================== Helpers ==================================
|
||||
|
||||
private GenesisMeter GetMeter(int slot)
|
||||
private GenesisMeter GetMeterThreadSafe(int slot)
|
||||
{
|
||||
var meter = _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
.FirstOrDefault(m => m.Slot == slot);
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
return _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
.FirstOrDefault(m => m.Slot == slot);
|
||||
}
|
||||
}
|
||||
|
||||
//if (meter == null)
|
||||
// throw new InvalidOperationException($"Meter for slot {slot} not initialized.");
|
||||
private bool RemoveMeterThreadSafe(GenesisMeter meter)
|
||||
{
|
||||
if (meter == null)
|
||||
return false;
|
||||
|
||||
return meter;
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
return _meterBatch.ListOfMeters.Remove(meter);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMetersThreadSafe()
|
||||
{
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
_meterBatch.ListOfMeters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveWorkersThreadSafe()
|
||||
{
|
||||
foreach (var pair in _workers.ToList())
|
||||
{
|
||||
ApiWorker worker;
|
||||
|
||||
if (_workers.TryRemove(pair.Key, out worker))
|
||||
{
|
||||
worker.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeMetersThreadSafe()
|
||||
{
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
foreach (var meter in _meterBatch.ListOfMeters.OfType<GenesisMeter>())
|
||||
{
|
||||
meter.DisposeMeter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureConnected(GenesisMeter meter)
|
||||
@ -201,7 +263,7 @@ namespace GenesisCordonelInterface.API
|
||||
req.HasValue ? req.Value.PortName : "NA",
|
||||
str.HasValue ? str.Value.PortName : "NA"));
|
||||
|
||||
var existingMeter = GetMeter(slot);
|
||||
var existingMeter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (existingMeter != null)
|
||||
{
|
||||
@ -253,7 +315,7 @@ namespace GenesisCordonelInterface.API
|
||||
req.HasValue ? req.Value.PortName : "NA",
|
||||
str.HasValue ? str.Value.PortName : "NA"));
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (meter == null)
|
||||
{
|
||||
@ -293,7 +355,10 @@ namespace GenesisCordonelInterface.API
|
||||
meter.streamingPortConfig = str;
|
||||
|
||||
//meter.SetupGenesisMeter(slot, req, str, true);
|
||||
_meterBatch.AddMeter2(meter);
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
_meterBatch.AddMeter2(meter);
|
||||
}
|
||||
|
||||
return meter;
|
||||
}
|
||||
@ -394,7 +459,7 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (meter == null)
|
||||
{
|
||||
@ -424,8 +489,8 @@ namespace GenesisCordonelInterface.API
|
||||
ConfigSource = ModelsMapping.MapConfigSourceBack(meter.useConfigSource),
|
||||
PasswordSource = ModelsMapping.MapPasswordSourceBack(meter.usePasswordSource),
|
||||
|
||||
RequestPort = ModelsMapping.MapPortBack(meter.RequestPort),
|
||||
StreamingPort = ModelsMapping.MapPortBack(meter.StreamingPort)
|
||||
RequestPort = ModelsMapping.MapPortBack(meter.requestPortConfig),
|
||||
StreamingPort = ModelsMapping.MapPortBack(meter.streamingPortConfig)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -458,28 +523,31 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, "Start.");
|
||||
|
||||
var result = new PublicModels.GciAllSlotsInfo
|
||||
lock (_meterBatchLock)
|
||||
{
|
||||
Success = true,
|
||||
Message = "Slots read.",
|
||||
Slots = _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
.Select(meter => new PublicModels.GciSlotInfo
|
||||
{
|
||||
SlotId = meter.Slot,
|
||||
Success = true,
|
||||
Exists = true,
|
||||
Message = "Slot found.",
|
||||
PcbId = meter.PcbId,
|
||||
ConfigSource = ModelsMapping.MapConfigSourceBack(meter.useConfigSource),
|
||||
PasswordSource = ModelsMapping.MapPasswordSourceBack(meter.usePasswordSource),
|
||||
RequestPort = ModelsMapping.MapPortBack(meter.RequestPort),
|
||||
StreamingPort = ModelsMapping.MapPortBack(meter.StreamingPort)
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
var result = new PublicModels.GciAllSlotsInfo
|
||||
{
|
||||
Success = true,
|
||||
Message = "Slots read.",
|
||||
Slots = _meterBatch.ListOfMeters
|
||||
.OfType<GenesisMeter>()
|
||||
.Select(meter => new PublicModels.GciSlotInfo
|
||||
{
|
||||
SlotId = meter.Slot,
|
||||
Success = true,
|
||||
Exists = true,
|
||||
Message = "Slot found.",
|
||||
PcbId = meter.PcbId,
|
||||
ConfigSource = ModelsMapping.MapConfigSourceBack(meter.useConfigSource),
|
||||
PasswordSource = ModelsMapping.MapPasswordSourceBack(meter.usePasswordSource),
|
||||
RequestPort = ModelsMapping.MapPortBack(meter.requestPortConfig),
|
||||
StreamingPort = ModelsMapping.MapPortBack(meter.streamingPortConfig)
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -512,12 +580,12 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, string.Format("Start. Slot={0}", slot));
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (meter != null)
|
||||
{
|
||||
meter.DisposeMeter();
|
||||
_meterBatch.ListOfMeters.Remove(meter);
|
||||
RemoveMeterThreadSafe(meter);
|
||||
}
|
||||
|
||||
ApiWorker worker;
|
||||
@ -561,22 +629,9 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, "Start.");
|
||||
|
||||
foreach (var meter in _meterBatch.ListOfMeters)
|
||||
{
|
||||
meter.DisposeMeter();
|
||||
}
|
||||
|
||||
_meterBatch.ListOfMeters.Clear();
|
||||
|
||||
foreach (var pair in _workers.ToList())
|
||||
{
|
||||
ApiWorker worker;
|
||||
|
||||
if (_workers.TryRemove(pair.Key, out worker))
|
||||
{
|
||||
worker.Dispose();
|
||||
}
|
||||
}
|
||||
DisposeMetersThreadSafe();
|
||||
RemoveMetersThreadSafe();
|
||||
RemoveWorkersThreadSafe();
|
||||
|
||||
return new PublicModels.GciCleanAllSlotsResult
|
||||
{
|
||||
@ -720,7 +775,7 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (!meter.IsLoggedOn)
|
||||
{
|
||||
@ -794,7 +849,7 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
if (!meter.IsConnected)
|
||||
{
|
||||
@ -885,27 +940,42 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
meter.Logout();
|
||||
meter.Disconnect();
|
||||
//meter.Dispose();
|
||||
//meter.SoftDispose();
|
||||
|
||||
LogInfo(operation, $"Success. Slot={slot}");
|
||||
bool success = !meter.IsConnected && !meter.IsLoggedOn;
|
||||
|
||||
if (success)
|
||||
{
|
||||
LogInfo(operation, $"Success. Slot={slot}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogInfo(
|
||||
operation,
|
||||
$"Disconnect incomplete. Slot={slot}, IsConnected={meter.IsConnected}, IsLoggedOn={meter.IsLoggedOn}");
|
||||
}
|
||||
|
||||
//if (!meter.IsConnected && meter.IsLoggedOn)
|
||||
// meter.ConnectMeter(); // repair of this situation
|
||||
|
||||
return new PublicModels.GciDisconnectResult
|
||||
{
|
||||
SlotId = slot,
|
||||
Success = true,
|
||||
Success = success,
|
||||
Message = string.Join(
|
||||
" | ",
|
||||
new[]
|
||||
{
|
||||
meter.LastLogoutStatus,
|
||||
meter.LastDisposeStatus
|
||||
}
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||
" | ",
|
||||
new[]
|
||||
{
|
||||
meter.LastLogoutStatus,
|
||||
meter.LastDisposeStatus,
|
||||
success
|
||||
? null
|
||||
: $"Disconnect state invalid. IsConnected={meter.IsConnected}, IsLoggedOn={meter.IsLoggedOn}"
|
||||
}
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -924,6 +994,7 @@ namespace GenesisCordonelInterface.API
|
||||
if (_workers.TryRemove(slot, out var worker))
|
||||
{
|
||||
worker.Dispose();
|
||||
|
||||
LogInfo(operation, $"Worker disposed. Slot={slot}");
|
||||
}
|
||||
}
|
||||
@ -948,7 +1019,7 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
|
||||
//meter.Logout();
|
||||
|
||||
@ -1001,14 +1072,16 @@ namespace GenesisCordonelInterface.API
|
||||
{
|
||||
LogInfo(operation, $"Start. Slot={slot}, Register={name}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
EnsureConnected(meter);
|
||||
|
||||
var raw = meter.ReadRegister(name);
|
||||
|
||||
bool success = raw != null && raw.Length > 0;
|
||||
|
||||
var result = new RegisterReadResult
|
||||
{
|
||||
Success = true,
|
||||
Success = success,
|
||||
RegisterName = name,
|
||||
RawHex = ToHex(raw)
|
||||
};
|
||||
@ -1040,12 +1113,9 @@ namespace GenesisCordonelInterface.API
|
||||
object value,
|
||||
bool storeToDevice = false,
|
||||
bool refreshSystemState = false,
|
||||
CancellationToken token = default(CancellationToken))
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return GetWorker(slot).RunAsync(
|
||||
() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState),
|
||||
token,
|
||||
"WriteRegister");
|
||||
return GetWorker(slot).RunAsync(() => WriteRegister(slot, registerName, value, storeToDevice, refreshSystemState), token);
|
||||
}
|
||||
|
||||
public RegisterWriteResult WriteRegister(
|
||||
@ -1063,7 +1133,7 @@ namespace GenesisCordonelInterface.API
|
||||
$"Start. Slot={slot}, Register={registerName}, Value={value}, " +
|
||||
$"StoreToDevice={storeToDevice}, RefreshSystemState={refreshSystemState}");
|
||||
|
||||
var meter = GetMeter(slot);
|
||||
var meter = GetMeterThreadSafe(slot);
|
||||
EnsureConnected(meter);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(registerName))
|
||||
@ -1174,7 +1244,7 @@ namespace GenesisCordonelInterface.API
|
||||
|
||||
LogInfo(operation, $"Start. Slot={slot}");
|
||||
|
||||
GenesisMeter meter = GetMeter(slot);
|
||||
GenesisMeter meter = GetMeterThreadSafe(slot);
|
||||
|
||||
meter?.SetPassword(password);//SetupFromExternConfig();
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ namespace GenesisCordonelInterface.API
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================================== INIT ==================================
|
||||
#region ================================== INIT/UPDATE/GET slot ==================================
|
||||
|
||||
public async Task<GciInitSlotResult> InitSlotAsync(
|
||||
GciInitSlotRequest request,
|
||||
@ -73,7 +73,7 @@ namespace GenesisCordonelInterface.API
|
||||
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
||||
ModelsMapping.MapPort(request.RequestPort),
|
||||
ModelsMapping.MapPort(request.StreamingPort),
|
||||
token);
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -93,7 +93,7 @@ namespace GenesisCordonelInterface.API
|
||||
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
||||
ModelsMapping.MapPort(request.RequestPort),
|
||||
ModelsMapping.MapPort(request.StreamingPort),
|
||||
token);
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -107,7 +107,7 @@ namespace GenesisCordonelInterface.API
|
||||
if (slotId <= 0)
|
||||
throw new ArgumentException("Invalid slot id.");
|
||||
|
||||
var result = await _innerMeterAPI.GetOneMeterInfo(slotId, token);
|
||||
var result = await _innerMeterAPI.GetOneMeterInfo(slotId, token).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -115,7 +115,7 @@ namespace GenesisCordonelInterface.API
|
||||
public async Task<GciAllSlotsInfo> GetAllSlotsAsync(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var result = await _innerMeterAPI.GetAllMetersInfo(token);
|
||||
var result = await _innerMeterAPI.GetAllMetersInfo(token).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -127,7 +127,7 @@ namespace GenesisCordonelInterface.API
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||
|
||||
var result = await _innerMeterAPI.CleanSlotAsync(slot, token);
|
||||
var result = await _innerMeterAPI.CleanSlotAsync(slot, token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -137,7 +137,7 @@ namespace GenesisCordonelInterface.API
|
||||
public async Task<GciCleanAllSlotsResult> CleanAllSlotsAsync(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var result = await _innerMeterAPI.CleanAllSlotsAsync(token);
|
||||
var result = await _innerMeterAPI.CleanAllSlotsAsync(token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -157,9 +157,9 @@ namespace GenesisCordonelInterface.API
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new ArgumentException("Password is empty.");
|
||||
|
||||
GciSetPasswordResult result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password, token);
|
||||
var result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password, token).ConfigureAwait(false);
|
||||
|
||||
RaiseMeterBatchStatusChanged();
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -167,11 +167,17 @@ namespace GenesisCordonelInterface.API
|
||||
#endregion
|
||||
|
||||
#region ================================== LOGIN ==================================
|
||||
public Task<PublicModels.GciLoginResult> LoginOneSlotAsync(
|
||||
public async Task<PublicModels.GciLoginResult> LoginOneSlotAsync(
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return _innerMeterAPI.LoginOneSlotAsync(slot, token);
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.");
|
||||
|
||||
var result = await _innerMeterAPI.LoginOneSlotAsync(slot, token).ConfigureAwait(false);
|
||||
return result;
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -181,7 +187,10 @@ namespace GenesisCordonelInterface.API
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
GciConnectResult result = await _innerMeterAPI.ConnectOneSlotAsync(slot, token);
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.");
|
||||
|
||||
var result = await _innerMeterAPI.ConnectOneSlotAsync(slot, token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -195,7 +204,7 @@ namespace GenesisCordonelInterface.API
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.");
|
||||
|
||||
var result = await _innerMeterAPI.DisconnectAsync(slot, token);
|
||||
var result = await _innerMeterAPI.DisconnectAsync(slot, token).ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
@ -211,93 +220,92 @@ namespace GenesisCordonelInterface.API
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.");
|
||||
|
||||
var result = await _innerMeterAPI.GetPcbIdAsync(slot, token);
|
||||
var result = await _innerMeterAPI.GetPcbIdAsync(slot, token).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ================================== READ ==================================
|
||||
// ----------------------------------------------------
|
||||
|
||||
public RegisterReadResult ReadRegister(
|
||||
int slot,
|
||||
string registerName)
|
||||
{
|
||||
return _innerMeterAPI.ReadRegister(slot, registerName);
|
||||
}
|
||||
|
||||
public Task<RegisterReadResult> ReadRegisterAsync(
|
||||
public async Task<RegisterReadResult> ReadRegisterAsync(
|
||||
int slot,
|
||||
string registerName,
|
||||
CancellationToken token = default(CancellationToken))
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return _innerMeterAPI.ReadRegisterAsync(slot, registerName, token);
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(registerName))
|
||||
throw new ArgumentException("Register name is empty.", nameof(registerName));
|
||||
|
||||
var result = await _innerMeterAPI
|
||||
.ReadRegisterAsync(slot, registerName, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
#endregion
|
||||
|
||||
#region ================================== WRITE ==================================
|
||||
// ----------------------------------------------------
|
||||
|
||||
public async Task<RegisterWriteResult> WriteRegisterAsync(
|
||||
int slot,
|
||||
string registerName,
|
||||
object value,
|
||||
bool storeToDevice = false,
|
||||
bool refreshSystemState = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(registerName))
|
||||
throw new ArgumentException("Register name is empty.", nameof(registerName));
|
||||
|
||||
var result = await _innerMeterAPI
|
||||
.WriteRegisterAsync(
|
||||
slot,
|
||||
registerName,
|
||||
value,
|
||||
storeToDevice,
|
||||
refreshSystemState,
|
||||
token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ================================== Password ==================================
|
||||
|
||||
public async Task<GciSetPasswordResult> SetMeterPasswordAsync(
|
||||
int slot,
|
||||
string registerName,
|
||||
object value,
|
||||
bool storeToDevice = false,
|
||||
bool refreshSystemState = false,
|
||||
string password,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var result = await _innerMeterAPI.WriteRegisterAsync(
|
||||
slot,
|
||||
registerName,
|
||||
value,
|
||||
storeToDevice,
|
||||
refreshSystemState,
|
||||
token);
|
||||
if (slot <= 0)
|
||||
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
throw new ArgumentException("Password is empty.", nameof(password));
|
||||
|
||||
var result = await _innerMeterAPI
|
||||
.SetMeterPasswordAsync(slot, password, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RegisterWriteResult WriteRegister(
|
||||
int slot,
|
||||
string registerName,
|
||||
object value,
|
||||
bool storeToDevice = false,
|
||||
bool refreshSystemState = false)
|
||||
{
|
||||
var result = _innerMeterAPI.WriteRegister(
|
||||
slot,
|
||||
registerName,
|
||||
value,
|
||||
storeToDevice,
|
||||
refreshSystemState);
|
||||
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<GciSetPasswordResult> SetMeterPasswordAsync(int slot, string password)
|
||||
{
|
||||
var result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password);
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
public GciSetPasswordResult SetMeterPassword(int slot, string password)
|
||||
{
|
||||
var result = _innerMeterAPI.SetMeterPassword(slot, password);
|
||||
//RaiseMeterBatchStatusChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
#endregion
|
||||
|
||||
#region ================================== DEBUG STATUS ==================================
|
||||
// ----------------------------------------------------
|
||||
|
||||
public List<WorkerDebugStatus> GetWorkerDebugStatuses()
|
||||
{
|
||||
|
||||
@ -126,6 +126,15 @@ namespace GenesisCordonelInterface.API
|
||||
};
|
||||
}
|
||||
|
||||
public static PublicModels.GciPortConfig MapPortBack(string portName, string portTyoe)
|
||||
{
|
||||
return new PublicModels.GciPortConfig
|
||||
{
|
||||
PortName = portName,
|
||||
Type = portTyoe
|
||||
};
|
||||
}
|
||||
|
||||
public static MeterBatchDebugStatus ToMeterBatchDebugStatus(GciSlotInfo slot)
|
||||
{
|
||||
if (slot == null)
|
||||
|
||||
@ -49,6 +49,18 @@ namespace GenesisCordonelInterface.API
|
||||
/// </summary>
|
||||
public class PublicModels
|
||||
{
|
||||
public static bool HideSensitiveValues { get; set; } = true;
|
||||
|
||||
private static string FormatPassword(string password)
|
||||
{
|
||||
if (!HideSensitiveValues)
|
||||
return password ?? "<null>";
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
return "<empty>";
|
||||
|
||||
return "********";
|
||||
}
|
||||
/// <summary>
|
||||
/// Public DTOs exposed to external systems.
|
||||
/// These models represent the contract of the GCI API.
|
||||
@ -165,7 +177,7 @@ namespace GenesisCordonelInterface.API
|
||||
IsLoggedOn,
|
||||
Message,
|
||||
PcbId,
|
||||
Password,
|
||||
FormatPassword(Password),
|
||||
ConfigSource,
|
||||
PasswordSource,
|
||||
RequestPort,
|
||||
@ -270,12 +282,13 @@ namespace GenesisCordonelInterface.API
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"SlotId={0}, GciConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}, Password=hidden",
|
||||
"SlotId={0}, GciConfigSource={1}, PasswordSource={2}, RequestPort={3}, StreamingPort={4}, Password={5}",
|
||||
SlotId,
|
||||
ConfigSource,
|
||||
PasswordSource,
|
||||
RequestPort,
|
||||
StreamingPort);
|
||||
StreamingPort,
|
||||
FormatPassword(Password));
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,7 +443,7 @@ namespace GenesisCordonelInterface.API
|
||||
"SlotId={0}, Success={1}, Password={2}, Message={3}",
|
||||
SlotId,
|
||||
Success,
|
||||
Password,
|
||||
FormatPassword(Password),
|
||||
Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -173,6 +174,7 @@ namespace GenesisCordonelInterface.Core.Threading
|
||||
|
||||
public sealed class ApiWorker : IDisposable
|
||||
{
|
||||
private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("GenesisCordonelInterface-threading");
|
||||
/// <summary>
|
||||
/// Thread-safe FIFO queue holding work items.
|
||||
/// </summary>
|
||||
@ -236,9 +238,9 @@ namespace GenesisCordonelInterface.Core.Threading
|
||||
/// 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)
|
||||
Func<T> action,
|
||||
CancellationToken token = default,
|
||||
string operationName = null)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
@ -246,58 +248,52 @@ namespace GenesisCordonelInterface.Core.Threading
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(nameof(ApiWorker));
|
||||
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
var tcs = new TaskCompletionSource<T>(
|
||||
TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
queue.Add(() =>
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
Debug.WriteLine($"ENQUEUE {operationName} slot worker={Name} time={DateTime.Now:HH:mm:ss.fff}");
|
||||
queue.Add(() =>
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
queue.Add(() =>
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
return;
|
||||
}
|
||||
tcs.TrySetCanceled();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
CurrentOperation = operationName ?? action.Method.Name;
|
||||
LastActivity = DateTime.Now;
|
||||
LastError = null;
|
||||
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);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
tcs.TrySetException(new ObjectDisposedException(nameof(ApiWorker), ex));
|
||||
}
|
||||
}, token);
|
||||
var result = action();
|
||||
|
||||
tcs.TrySetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LastError = ex.Message;
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
CurrentOperation = null;
|
||||
LastActivity = DateTime.Now;
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
tcs.TrySetException(new ObjectDisposedException(nameof(ApiWorker), ex));
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GenesisCordonelInterface.Core.Threading
|
||||
{
|
||||
public sealed class RetryResult<T>
|
||||
{
|
||||
public T Result { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public bool TimedOut { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"Success={0}, Attempts={1}, Duration={2} ms, TimedOut={3}, Result={4}",
|
||||
Success,
|
||||
Attempts,
|
||||
(int)Duration.TotalMilliseconds,
|
||||
TimedOut,
|
||||
Result == null ? "<null>" : Result.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class RetryWorker
|
||||
{
|
||||
public static async Task<RetryResult<T>> RunWithRetryAsync<T>(
|
||||
Func<Task<T>> action,
|
||||
Func<T, bool> isSuccess,
|
||||
Action<string> log,
|
||||
Action<string, T> logResult,
|
||||
string operationName,
|
||||
int maxAttempts = 3,
|
||||
int delayMs = 500,
|
||||
int timeoutMs = 30000)
|
||||
{
|
||||
var started = DateTime.Now;
|
||||
T lastResult = default(T);
|
||||
bool timedOut = false;
|
||||
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
var actionTask = action();
|
||||
var timeoutTask = Task.Delay(timeoutMs);
|
||||
|
||||
var completedTask = await Task.WhenAny(actionTask, timeoutTask);
|
||||
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
timedOut = true;
|
||||
log?.Invoke($"{operationName} timeout attempt {attempt}/{maxAttempts}");
|
||||
}
|
||||
else
|
||||
{
|
||||
lastResult = await actionTask;
|
||||
|
||||
if (isSuccess(lastResult))
|
||||
{
|
||||
return new RetryResult<T>
|
||||
{
|
||||
Result = lastResult,
|
||||
Success = true,
|
||||
Attempts = attempt,
|
||||
Duration = DateTime.Now - started,
|
||||
TimedOut = false
|
||||
};
|
||||
}
|
||||
|
||||
logResult?.Invoke($"{operationName} failed attempt {attempt}/{maxAttempts}", lastResult);
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts)
|
||||
await Task.Delay(delayMs);
|
||||
}
|
||||
|
||||
return new RetryResult<T>
|
||||
{
|
||||
Result = lastResult,
|
||||
Success = false,
|
||||
Attempts = maxAttempts,
|
||||
Duration = DateTime.Now - started,
|
||||
TimedOut = timedOut
|
||||
};
|
||||
}
|
||||
|
||||
public static void EnsureSuccess<T>(
|
||||
RetryResult<T> retryResult,
|
||||
string operationName)
|
||||
{
|
||||
if (!retryResult.Success)
|
||||
throw new Exception($"{operationName} failed after {retryResult.Attempts} attempts.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,6 +63,7 @@
|
||||
<Compile Include="Core\Logging\UiLogBus.cs" />
|
||||
<Compile Include="Core\Logging\UiTarget.cs" />
|
||||
<Compile Include="Core\Threading\ApiWorker\ApiWorker.cs" />
|
||||
<Compile Include="Core\Threading\RetryWorker\RetryWorker.cs" />
|
||||
<Compile Include="UI\Debug\MeterBatchConfigPanel.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using GenesisCordonelInterface.Core.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.Interfaces
|
||||
{
|
||||
@ -42,6 +44,19 @@ namespace TBF.Rig.BridgeComponents.GciBridge.Interfaces
|
||||
/// </summary>
|
||||
public class PublicModels
|
||||
{
|
||||
public static bool HideSensitiveValues { get; set; } = true;
|
||||
|
||||
private static string FormatPassword(string password)
|
||||
{
|
||||
if (!HideSensitiveValues)
|
||||
return password ?? "<null>";
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
return "<empty>";
|
||||
|
||||
return "********";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public DTOs exposed to external systems.
|
||||
/// These models represent the contract of the GCI API.
|
||||
@ -75,10 +90,44 @@ namespace TBF.Rig.BridgeComponents.GciBridge.Interfaces
|
||||
"Success={0}, PcbId={1}, Password={2}, Message={3}",
|
||||
Success,
|
||||
PcbId,
|
||||
string.IsNullOrEmpty(Password) ? "<empty>" : Password,
|
||||
FormatPassword(Password),
|
||||
Message);
|
||||
}
|
||||
}
|
||||
|
||||
public class GciFullLoginResult
|
||||
{
|
||||
public int SlotId { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public RetryResult<GciPublicModels.GciConnectResult> ConnectResult { get; set; }
|
||||
public RetryResult<GciPublicModels.GciGetPcbIdResult> PcbResult { get; set; }
|
||||
public RetryResult<UdsPasswordResult> PasswordResult { get; set; }
|
||||
public RetryResult<GciPublicModels.GciSetPasswordResult> SetPasswordResult { get; set; }
|
||||
public RetryResult<GciPublicModels.GciLoginResult> LoginResult { get; set; }
|
||||
|
||||
public GciFullLoginResult Fail(string message)
|
||||
{
|
||||
Success = false;
|
||||
Message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"SlotId={0}, Success={1}, Message={2}, Connect={3}, Pcb={4}, Password={5}, SetPassword={6}, Login={7}",
|
||||
SlotId,
|
||||
Success,
|
||||
Message,
|
||||
ConnectResult,
|
||||
PcbResult,
|
||||
PasswordResult,
|
||||
SetPasswordResult,
|
||||
LoginResult);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,6 +385,27 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Debug
|
||||
return _gridManager.GetSelectedGridData();
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
grid.EndEdit();
|
||||
|
||||
foreach (DataGridViewRow row in grid.Rows)
|
||||
{
|
||||
if (row.IsNewRow)
|
||||
continue;
|
||||
|
||||
if (grid.Columns.Contains("Selected"))
|
||||
{
|
||||
row.Cells["Selected"].Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
grid.ClearSelection();
|
||||
|
||||
if (grid.CurrentCell != null)
|
||||
grid.CurrentCell = null;
|
||||
}
|
||||
|
||||
private void grid_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Control && e.KeyCode == Keys.V)
|
||||
|
||||
@ -31,8 +31,15 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Debug
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// WorkerDebugPanel
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Name = "WorkerDebugPanel";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -26,7 +26,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Debug
|
||||
|
||||
Controls.Add(grid);
|
||||
|
||||
timer.Interval = 300;
|
||||
timer.Interval = 150;
|
||||
timer.Tick += (s, e) =>
|
||||
{
|
||||
grid.DataSource = null;
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@ -1,9 +1,12 @@
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
using static TBF.Rig.BridgeComponents.GciBridge.UI.Grid.MeterGridConfigProvider;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
{
|
||||
public class MeterGridColumnConfig
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string HeaderText { get; set; }
|
||||
public MeterGridColumnType ColumnType { get; set; }
|
||||
public bool Visible { get; set; } = true;
|
||||
public int DisplayIndex { get; set; }
|
||||
public int Width { get; set; } = 80;
|
||||
|
||||
@ -4,19 +4,70 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
{
|
||||
public static class MeterGridConfigProvider
|
||||
{
|
||||
public enum MeterGridColumnType
|
||||
{
|
||||
Text,
|
||||
CheckBox
|
||||
}
|
||||
|
||||
public static List<MeterGridColumnConfig> GetDefault()
|
||||
{
|
||||
return new List<MeterGridColumnConfig>
|
||||
{
|
||||
new MeterGridColumnConfig { Name = "Slot", HeaderText = "Slot", DisplayIndex = 0, Width = 30, ReadOnly = true },
|
||||
new MeterGridColumnConfig { Name = "Selected", HeaderText = "Selected", DisplayIndex = 1, Width = 30 },
|
||||
new MeterGridColumnConfig { Name = "PcbId", HeaderText = "PcbId", DisplayIndex = 2, Width = 80 },
|
||||
new MeterGridColumnConfig { Name = "IsLoggedOn", HeaderText = "IsLoggedOn", DisplayIndex = 3, Width = 30 },
|
||||
new MeterGridColumnConfig { Name = "IsLoggedOn", HeaderText = "IsLoggedOn", DisplayIndex = 3, Width = 30 },
|
||||
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 },
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "Slot",
|
||||
HeaderText = "Slot",
|
||||
ColumnType = MeterGridColumnType.Text,
|
||||
DisplayIndex = 0,
|
||||
Width = 30,
|
||||
ReadOnly = true
|
||||
},
|
||||
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "Selected",
|
||||
HeaderText = "Selected",
|
||||
ColumnType = MeterGridColumnType.CheckBox,
|
||||
DisplayIndex = 1,
|
||||
Width = 30
|
||||
},
|
||||
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "PcbId",
|
||||
HeaderText = "PcbId",
|
||||
ColumnType = MeterGridColumnType.Text,
|
||||
DisplayIndex = 2,
|
||||
Width = 80
|
||||
},
|
||||
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "IsConnected",
|
||||
HeaderText = "IsConnected",
|
||||
ColumnType = MeterGridColumnType.CheckBox,
|
||||
DisplayIndex = 3,
|
||||
Width = 30
|
||||
},
|
||||
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "IsLoggedOn",
|
||||
HeaderText = "IsLoggedOn",
|
||||
ColumnType = MeterGridColumnType.CheckBox,
|
||||
DisplayIndex = 4,
|
||||
Width = 30
|
||||
},
|
||||
|
||||
new MeterGridColumnConfig
|
||||
{
|
||||
Name = "FwVersion",
|
||||
HeaderText = "FwVersion",
|
||||
ColumnType = MeterGridColumnType.Text,
|
||||
DisplayIndex = 5,
|
||||
Width = 80
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,32 +30,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
grid.AllowUserToDeleteRows = false;
|
||||
grid.RowHeadersVisible = true;
|
||||
|
||||
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 = "IsConnected", HeaderText = "IsConnected" });
|
||||
grid.Columns.Add(new DataGridViewCheckBoxColumn { Name = "IsLoggedOn", HeaderText = "IsLoggedOn" });
|
||||
|
||||
grid.Columns.Add(CreateComPortColumn("RequestPort", "RequestPort", comPorts));
|
||||
grid.Columns.Add(CreateRequestPortTypeColumn());
|
||||
grid.Columns.Add(CreateComPortColumn("StreamingPort", "StreamingPort", comPorts));
|
||||
|
||||
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 != "RequestPortType" &&
|
||||
col.Name != "StreamingPort" &&
|
||||
col.Name != "DetectRequest" &&
|
||||
col.Name != "DetectStreaming";
|
||||
}
|
||||
CreateColumns(comPorts);
|
||||
ConfigureReadOnlyColumns();
|
||||
ConfigureSelection();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -66,6 +43,70 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
grid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
|
||||
}
|
||||
|
||||
private void CreateColumns(List<string> comPorts)
|
||||
{
|
||||
var configs = MeterGridConfigProvider
|
||||
.GetDefault()
|
||||
.OrderBy(x => x.DisplayIndex);
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
DataGridViewColumn column;
|
||||
|
||||
switch (config.ColumnType)
|
||||
{
|
||||
case MeterGridConfigProvider.MeterGridColumnType.CheckBox:
|
||||
column = new DataGridViewCheckBoxColumn();
|
||||
break;
|
||||
|
||||
default:
|
||||
column = new DataGridViewTextBoxColumn();
|
||||
break;
|
||||
}
|
||||
|
||||
column.Name = config.Name;
|
||||
column.HeaderText = config.HeaderText;
|
||||
column.Width = config.Width;
|
||||
column.ReadOnly = config.ReadOnly;
|
||||
column.DisplayIndex = config.DisplayIndex;
|
||||
|
||||
grid.Columns.Add(column);
|
||||
}
|
||||
|
||||
grid.Columns.Add(CreateComPortColumn("RequestPort", "RequestPort", comPorts));
|
||||
grid.Columns.Add(CreateRequestPortTypeColumn());
|
||||
grid.Columns.Add(CreateComPortColumn("StreamingPort", "StreamingPort", comPorts));
|
||||
|
||||
grid.Columns.Add(CreateButtonColumn("DetectRequest", "DetectRequest", "..."));
|
||||
grid.Columns.Add(CreateButtonColumn("DetectStreaming", "DetectStreaming", "..."));
|
||||
}
|
||||
|
||||
private void ConfigureReadOnlyColumns()
|
||||
{
|
||||
foreach (DataGridViewColumn col in grid.Columns)
|
||||
{
|
||||
col.ReadOnly =
|
||||
col.Name != "Selected" &&
|
||||
col.Name != "RequestPort" &&
|
||||
col.Name != "RequestPortType" &&
|
||||
col.Name != "StreamingPort" &&
|
||||
col.Name != "DetectRequest" &&
|
||||
col.Name != "DetectStreaming";
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureSelection()
|
||||
{
|
||||
grid.MultiSelect = true;
|
||||
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
|
||||
grid.CellContentClick -= Grid_CellContentClick;
|
||||
grid.CellContentClick += Grid_CellContentClick;
|
||||
|
||||
grid.CurrentCellDirtyStateChanged -= Grid_CurrentCellDirtyStateChanged;
|
||||
grid.CurrentCellDirtyStateChanged += Grid_CurrentCellDirtyStateChanged;
|
||||
}
|
||||
|
||||
public void UpdateComPortItems(List<string> comPorts)
|
||||
{
|
||||
UpdateComPortColumnItems("RequestPort", comPorts);
|
||||
@ -407,5 +448,33 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.Grid
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.SetValue(dgv, true, null);
|
||||
}
|
||||
|
||||
private void Grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (grid.IsCurrentCellDirty)
|
||||
{
|
||||
grid.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||||
}
|
||||
}
|
||||
|
||||
private void Grid_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
if (e.RowIndex < 0)
|
||||
return;
|
||||
|
||||
if (grid.Columns[e.ColumnIndex].Name != "Selected")
|
||||
return;
|
||||
|
||||
bool clickedValue = Convert.ToBoolean(
|
||||
grid.Rows[e.RowIndex].Cells["Selected"].Value);
|
||||
|
||||
foreach (DataGridViewRow row in grid.SelectedRows)
|
||||
{
|
||||
if (row.Index == e.RowIndex)
|
||||
continue;
|
||||
|
||||
row.Cells["Selected"].Value = clickedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,9 +7,20 @@ using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using Xylem.Common.Utils.Logging;
|
||||
using TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Main WinForms UserControl for the GCI Bridge application.
|
||||
///
|
||||
/// Responsibilities:
|
||||
/// - Hosts and switches child UI views
|
||||
/// - Manages slot configuration panels
|
||||
/// - Displays global application logs
|
||||
/// - Buffers log messages to keep UI responsive
|
||||
/// - Connects UI with GCI bridge APIs
|
||||
/// </summary>
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
#region DECLARATION
|
||||
@ -20,7 +31,23 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
public MainForm _mainform;
|
||||
public GciBridge _bridge;
|
||||
public Debug.MeterBatchConfigPanel _batchPanel;
|
||||
public event Action<List<GenesisCordonelInterface.API.PublicModels.MeterBatchDebugStatus>> MeterBatchStatusChanged;// object status from place of his location
|
||||
public event Action<List<PublicModels.MeterBatchDebugStatus>> MeterBatchStatusChanged;// object status from place of his location
|
||||
|
||||
private const int WM_SETREDRAW = 0x000B;
|
||||
|
||||
[DllImport("user32.dll")] //faster ritchbox redrawing
|
||||
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe UI log buffering infrastructure.
|
||||
///
|
||||
/// Incoming log messages may arrive from multiple worker threads.
|
||||
/// Messages are queued and periodically flushed to RichTextBox
|
||||
/// by a UI timer to prevent UI freezes during parallel operations.
|
||||
/// </summary>
|
||||
private readonly object _uiLogLock = new object();
|
||||
private readonly Queue<string> _pendingUiLogs = new Queue<string>();
|
||||
private readonly Timer _uiLogFlushTimer = new Timer();
|
||||
#endregion
|
||||
|
||||
public MainView(GciBridge bridge, MainForm mainform)
|
||||
@ -40,8 +67,146 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
rtbMainLog.Font = new Font("Consolas", 9f);
|
||||
rtbMainLog.ReadOnly = true;
|
||||
rtbMainLog.HideSelection = false;
|
||||
|
||||
//UI htread safe
|
||||
_uiLogFlushTimer.Interval = 250;
|
||||
_uiLogFlushTimer.Tick += UiLogFlushTimer_Tick;
|
||||
_uiLogFlushTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Periodically flushes buffered log messages into RichTextBox.
|
||||
///
|
||||
/// Runs on the UI thread because WinForms Timer executes on UI thread.
|
||||
/// Processes messages in batches to reduce UI overhead.
|
||||
/// </summary>
|
||||
/// Plynule pridavanie do mema
|
||||
/*private void UiLogFlushTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (IsDisposed || !IsHandleCreated)
|
||||
return;
|
||||
|
||||
List<string> messages = new List<string>();
|
||||
|
||||
lock (_uiLogLock)
|
||||
{
|
||||
while (_pendingUiLogs.Count > 0 && messages.Count < 500)
|
||||
{
|
||||
messages.Add(_pendingUiLogs.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.Count == 0)
|
||||
return;
|
||||
|
||||
rtbMainLog.SuspendLayout();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string msg in messages)
|
||||
{
|
||||
AppendLogMessage(msg);
|
||||
}
|
||||
|
||||
const int maxTextLength = 200000;
|
||||
|
||||
if (rtbMainLog.TextLength > maxTextLength)
|
||||
{
|
||||
rtbMainLog.Select(0, rtbMainLog.TextLength - maxTextLength);
|
||||
rtbMainLog.SelectedText = "";
|
||||
}
|
||||
|
||||
rtbMainLog.SelectionStart = rtbMainLog.TextLength;
|
||||
rtbMainLog.ScrollToCaret();
|
||||
}
|
||||
finally
|
||||
{
|
||||
rtbMainLog.ResumeLayout();
|
||||
}
|
||||
}*/
|
||||
|
||||
private void UiLogFlushTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (IsDisposed || !IsHandleCreated || rtbMainLog == null || rtbMainLog.IsDisposed)
|
||||
return;
|
||||
|
||||
List<string> messages = new List<string>();
|
||||
|
||||
lock (_uiLogLock)
|
||||
{
|
||||
while (_pendingUiLogs.Count > 0 && messages.Count < 500)
|
||||
{
|
||||
messages.Add(_pendingUiLogs.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.Count == 0)
|
||||
return;
|
||||
|
||||
string batchText = string.Join(Environment.NewLine, messages) + Environment.NewLine;
|
||||
|
||||
SendMessage(rtbMainLog.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
try
|
||||
{
|
||||
int batchStart = rtbMainLog.TextLength;
|
||||
|
||||
rtbMainLog.SelectionStart = batchStart;
|
||||
rtbMainLog.SelectionLength = 0;
|
||||
rtbMainLog.SelectionColor = Color.Gainsboro;
|
||||
|
||||
rtbMainLog.AppendText(batchText);
|
||||
|
||||
// Optional: re-apply line highlighting after bulk insertion
|
||||
//ApplyHighlightingToBatch(batchText, batchStart);
|
||||
|
||||
const int maxTextLength = 200000;
|
||||
|
||||
if (rtbMainLog.TextLength > maxTextLength)
|
||||
{
|
||||
rtbMainLog.Select(0, rtbMainLog.TextLength - maxTextLength);
|
||||
rtbMainLog.SelectedText = "";
|
||||
}
|
||||
|
||||
rtbMainLog.SelectionStart = rtbMainLog.TextLength;
|
||||
rtbMainLog.ScrollToCaret();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SendMessage(rtbMainLog.Handle, WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
|
||||
rtbMainLog.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyHighlightingToBatch(string batchText, int batchStart)
|
||||
{
|
||||
string[] lines = batchText.Replace("\r\n", "\n").Split('\n');
|
||||
|
||||
int offset = 0;
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
Match m = LogLevelRegex.Match(line);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
rtbMainLog.SelectionStart = batchStart + offset + m.Index;
|
||||
rtbMainLog.SelectionLength = m.Length;
|
||||
rtbMainLog.SelectionColor = GetLogLevelColor(m.Groups[1].Value);
|
||||
}
|
||||
|
||||
HighlightKeywordsInLine(line, batchStart + offset);
|
||||
}
|
||||
|
||||
offset += line.Length + Environment.NewLine.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes debug/configuration panels hosted inside MainView.
|
||||
/// </summary>
|
||||
private void InitializeDebugPanels()
|
||||
{
|
||||
_batchPanel = new Debug.MeterBatchConfigPanel(_mainform, this)
|
||||
@ -72,6 +237,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely adjusts SplitContainer distance while respecting
|
||||
/// minimum panel sizes and current control dimensions.
|
||||
/// </summary>
|
||||
private void SetSafeSplitterDistance(SplitContainer split, int desired)
|
||||
{
|
||||
int width = split.ClientSize.Width;
|
||||
@ -99,6 +268,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
if (disposing)
|
||||
{
|
||||
UiLogBus.MessageReceived -= UiLogBus_MessageReceived;
|
||||
|
||||
_uiLogFlushTimer.Stop();
|
||||
_uiLogFlushTimer.Tick -= UiLogFlushTimer_Tick;
|
||||
_uiLogFlushTimer.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
@ -121,12 +294,18 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
pnlGciViewHost.Controls.Add(view);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves current slot configuration from the grid into backend storage.
|
||||
/// </summary>
|
||||
public void SaveSlots()
|
||||
{
|
||||
var data = _batchPanel.GetGridData();
|
||||
_laatzenApi.SaveSlotSetup(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switches currently displayed GCI view inside the host panel.
|
||||
/// </summary>
|
||||
private void button2_Click(object sender, EventArgs e)
|
||||
{
|
||||
SwitchGciView(
|
||||
@ -236,6 +415,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
new MeterInitView(_gciApi, AddSlotRow, SaveSlots));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the main UI log window.
|
||||
/// </summary>
|
||||
public void ClearLog()
|
||||
{
|
||||
rtbMainLog.Clear();
|
||||
@ -244,6 +426,13 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
#endregion
|
||||
|
||||
#region GLOBAL LOGGING to memo in this view
|
||||
|
||||
/// <summary>
|
||||
/// Receives global log messages from UiLogBus.
|
||||
///
|
||||
/// This method may be called from background threads.
|
||||
/// Messages are only queued here and later processed by UI timer.
|
||||
/// </summary>
|
||||
void UiLogBus_MessageReceived(string loggerName, string msg)
|
||||
{
|
||||
if (loggerName != "GciBridge" && loggerName != "GenesisCordonelInterface")
|
||||
@ -252,15 +441,18 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
if (IsDisposed || !IsHandleCreated)
|
||||
return;
|
||||
|
||||
if (InvokeRequired)
|
||||
lock (_uiLogLock)
|
||||
{
|
||||
BeginInvoke(new Action<string, string>(UiLogBus_MessageReceived), loggerName, msg);
|
||||
return;
|
||||
}
|
||||
_pendingUiLogs.Enqueue(msg);
|
||||
|
||||
AppendLogMessage(msg);
|
||||
while (_pendingUiLogs.Count > 1000)
|
||||
_pendingUiLogs.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits multiline log messages and appends each line separately.
|
||||
/// </summary>
|
||||
private void AppendLogMessage(string msg)
|
||||
{
|
||||
string[] lines = msg.Replace("\r\n", "\n").Split('\n');
|
||||
@ -274,6 +466,13 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a single styled log line into RichTextBox.
|
||||
///
|
||||
/// Performs syntax highlighting for:
|
||||
/// - log levels
|
||||
/// - important keywords
|
||||
/// </summary>
|
||||
private void AppendStyledLine(string line)
|
||||
{
|
||||
if (rtbMainLog == null || rtbMainLog.IsDisposed)
|
||||
@ -297,6 +496,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
HighlightKeywordsInLine(line, start);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns color associated with a log level.
|
||||
/// </summary>
|
||||
private Color GetLogLevelColor(string level)
|
||||
{
|
||||
switch (level.Trim().ToUpperInvariant())
|
||||
@ -318,6 +520,9 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI
|
||||
Tuple.Create(Color.Gold, new[] { "READ-REGISTER-SESSION", "UI-CLICK" })
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Highlights important keywords inside a log line.
|
||||
/// </summary>
|
||||
private void HighlightKeywordsInLine(string line, int lineStartIndex)
|
||||
{
|
||||
foreach (var group in KeywordGroups)
|
||||
|
||||
@ -28,7 +28,12 @@
|
||||
this.btnGetPcb = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.txtLog = new System.Windows.Forms.TextBox();
|
||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
this.button1 = new System.Windows.Forms.Button();
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.button3 = new System.Windows.Forms.Button();
|
||||
this.grpStorage.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// grpStorage
|
||||
@ -86,6 +91,9 @@
|
||||
//
|
||||
// txtLog
|
||||
//
|
||||
this.txtLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.txtLog.Location = new System.Drawing.Point(220, 10);
|
||||
this.txtLog.Multiline = true;
|
||||
this.txtLog.Name = "txtLog";
|
||||
@ -95,17 +103,65 @@
|
||||
this.txtLog.TabIndex = 4;
|
||||
this.txtLog.WordWrap = false;
|
||||
//
|
||||
// groupBox1
|
||||
//
|
||||
this.groupBox1.Controls.Add(this.button1);
|
||||
this.groupBox1.Controls.Add(this.button2);
|
||||
this.groupBox1.Controls.Add(this.button3);
|
||||
this.groupBox1.Location = new System.Drawing.Point(10, 166);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Size = new System.Drawing.Size(200, 139);
|
||||
this.groupBox1.TabIndex = 3;
|
||||
this.groupBox1.TabStop = false;
|
||||
this.groupBox1.Text = "Combined actions";
|
||||
//
|
||||
// button1
|
||||
//
|
||||
this.button1.Location = new System.Drawing.Point(12, 96);
|
||||
this.button1.Name = "button1";
|
||||
this.button1.Size = new System.Drawing.Size(175, 28);
|
||||
this.button1.TabIndex = 2;
|
||||
this.button1.Text = "...";
|
||||
this.button1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// button2
|
||||
//
|
||||
this.button2.Location = new System.Drawing.Point(12, 62);
|
||||
this.button2.Name = "button2";
|
||||
this.button2.Size = new System.Drawing.Size(175, 28);
|
||||
this.button2.TabIndex = 1;
|
||||
this.button2.Text = "...";
|
||||
this.button2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// button3
|
||||
//
|
||||
this.button3.Location = new System.Drawing.Point(12, 28);
|
||||
this.button3.Name = "button3";
|
||||
this.button3.Size = new System.Drawing.Size(175, 28);
|
||||
this.button3.TabIndex = 0;
|
||||
this.button3.Text = "Connect/FullPass/Login";
|
||||
this.button3.UseVisualStyleBackColor = true;
|
||||
this.button3.Click += new System.EventHandler(this.button3_Click);
|
||||
//
|
||||
// CombinedInterfaceView
|
||||
//
|
||||
this.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.Controls.Add(this.groupBox1);
|
||||
this.Controls.Add(this.grpStorage);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.Controls.Add(this.txtLog);
|
||||
this.Name = "CombinedInterfaceView";
|
||||
this.Size = new System.Drawing.Size(740, 370);
|
||||
this.grpStorage.ResumeLayout(false);
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
private System.Windows.Forms.Button button1;
|
||||
private System.Windows.Forms.Button button2;
|
||||
private System.Windows.Forms.Button button3;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,9 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using PublicModels = GenesisCordonelInterface.API.PublicModels;
|
||||
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
|
||||
using GenesisCordonelInterface.Core.Threading;
|
||||
using TBF.Rig.BridgeComponents.GciBridge.Interfaces;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
@ -25,7 +27,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private List<PublicModels.MeterBatchDebugStatus> GetSelectedSlots()
|
||||
private List<GciPublicModels.MeterBatchDebugStatus> GetSelectedSlots()
|
||||
{
|
||||
var slots = _mainView._batchPanel.GetSelectedGridData();
|
||||
|
||||
@ -41,44 +43,38 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
_pcbBySlot.Clear();
|
||||
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
var slots = GetSelectedSlots().ToList();
|
||||
|
||||
var tasks = slots.Select(async slot =>
|
||||
{
|
||||
var result = await _bridge.GetPcbIdAsync(slot.Slot, token);
|
||||
//var result = await _bridge.GetPcbIdAsync(slot.Slot, token);
|
||||
var result = await _bridge.GetPcbIdWithRetryAsync(slot.Slot, token);
|
||||
|
||||
LogResult($"GCI/GetPCB slot {slot.Slot}", result);
|
||||
|
||||
if (result != null &&
|
||||
result.Success &&
|
||||
!string.IsNullOrWhiteSpace(result.PcbId))
|
||||
return new
|
||||
{
|
||||
_pcbBySlot[slot.Slot] = result.PcbId;
|
||||
Log($"Stored PCB: Slot={slot.Slot}, PCB={result.PcbId}");
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"GCI/GetPCB slot {item.Slot}", item.Result);
|
||||
|
||||
if (item.Result != null &&
|
||||
item.Result.Success &&
|
||||
!string.IsNullOrWhiteSpace(item.Result.Result.PcbId))
|
||||
{
|
||||
_pcbBySlot[item.Slot] = item.Result.Result.PcbId;
|
||||
Log($"Stored PCB: Slot={item.Slot}, PCB={item.Result.Result.PcbId}");
|
||||
}
|
||||
}
|
||||
|
||||
Log($"PCB stored count: {_pcbBySlot.Count}");
|
||||
});
|
||||
}
|
||||
/*private void btnGetPcb_Click(object sender, EventArgs e)
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
_pcbBySlot.Clear();
|
||||
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
string fakePcbId = "231630243"; // docasne platna PCB
|
||||
|
||||
_pcbBySlot[slot.Slot] = fakePcbId;
|
||||
|
||||
Log($"TEMP Stored PCB: Slot={slot.Slot}, PCB={fakePcbId}");
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
Log($"PCB stored count: {_pcbBySlot.Count}");
|
||||
});
|
||||
}*/
|
||||
|
||||
private void btnGetPasswordByPcb_Click(object sender, EventArgs e)
|
||||
{
|
||||
@ -94,15 +90,16 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
int slot = item.Key;
|
||||
string pcbId = item.Value;
|
||||
|
||||
var result = await _bridge.GetPasswordAsync(pcbId, token);
|
||||
//var result = await _bridge.GetPasswordAsync(pcbId, token);
|
||||
var result = await _bridge.GetPasswordWithRetryAsync(pcbId, token);
|
||||
|
||||
LogResult($"UDSR/GetPassword slot {slot}, PCB={pcbId}", result);
|
||||
|
||||
if (result != null &&
|
||||
result.Success &&
|
||||
!string.IsNullOrWhiteSpace(result.Password))
|
||||
!string.IsNullOrWhiteSpace(result.Result.Password))
|
||||
{
|
||||
_passwordBySlot[slot] = result.Password;
|
||||
_passwordBySlot[slot] = result.Result.Password;
|
||||
Log($"Stored password: Slot={slot}, PCB={pcbId}, Password=hidden");
|
||||
}
|
||||
}
|
||||
@ -123,7 +120,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
int slot = item.Key;
|
||||
string password = item.Value;
|
||||
|
||||
var result = await _bridge.SetPasswordAsync(slot, password, token);
|
||||
//var result = await _bridge.SetPasswordAsync(slot, password, token);
|
||||
var result = await _bridge.SetPasswordWithRetryAsync(slot, password, token);
|
||||
|
||||
LogResult($"GCI/SetPassword slot {slot}", result);
|
||||
}
|
||||
@ -206,5 +204,55 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
message +
|
||||
Environment.NewLine);
|
||||
}
|
||||
|
||||
private void button3_Click(object sender, EventArgs e)
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
var selectedSlots = GetSelectedSlots().ToList();
|
||||
|
||||
var tasks = selectedSlots.Select(async slot =>
|
||||
{
|
||||
var result = await _bridge.ConnectFullPassLoginWithRetryAsync(slot.Slot, token);
|
||||
|
||||
LogResult($"ConnectFullPassLoginAsync slot {slot.Slot}", result);
|
||||
|
||||
LogFullLoginResult(result);
|
||||
|
||||
return result;
|
||||
}).ToList();
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
RefreshGrid();
|
||||
});
|
||||
}
|
||||
|
||||
private void LogFullLoginResult(PublicModels.GciFullLoginResult result)
|
||||
{
|
||||
Log($"====================== SLOT {result.SlotId} ======================");
|
||||
Log($"FullLogin Success={result.Success}, Message={result.Message}");
|
||||
|
||||
LogRetryResult("Connect", result.ConnectResult);
|
||||
LogRetryResult("GetPcbId", result.PcbResult);
|
||||
LogRetryResult("GetPassword", result.PasswordResult);
|
||||
LogRetryResult("SetPassword", result.SetPasswordResult);
|
||||
LogRetryResult("Login", result.LoginResult);
|
||||
}
|
||||
|
||||
private void LogRetryResult<T>(
|
||||
string operationName,
|
||||
RetryResult<T> retryResult)
|
||||
{
|
||||
if (retryResult == null)
|
||||
{
|
||||
Log($"{operationName}: <not executed>");
|
||||
return;
|
||||
}
|
||||
|
||||
LogResult(
|
||||
$"{operationName} | success={retryResult.Success} | attempts={retryResult.Attempts} | duration={retryResult.Duration.TotalSeconds:F1}s | timeout={retryResult.TimedOut}",
|
||||
retryResult.Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,6 +49,7 @@
|
||||
//
|
||||
// ConfigurationView
|
||||
//
|
||||
this.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.Controls.Add(this.readSlotsButton);
|
||||
this.Controls.Add(this.clearSlotsButton);
|
||||
this.Controls.Add(this.btnAddSlot);
|
||||
|
||||
@ -235,6 +235,9 @@
|
||||
//
|
||||
// txtLog
|
||||
//
|
||||
this.txtLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.txtLog.Location = new System.Drawing.Point(10, 356);
|
||||
this.txtLog.Multiline = true;
|
||||
this.txtLog.Name = "txtLog";
|
||||
@ -246,6 +249,7 @@
|
||||
//
|
||||
// GenesisCordonelInterfaceView
|
||||
//
|
||||
this.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.Controls.Add(this.grpSlots);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.Controls.Add(this.txtLog);
|
||||
|
||||
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Linq;
|
||||
using GciPublicModels = GenesisCordonelInterface.API.PublicModels;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
@ -105,7 +106,7 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
StreamingPort = null
|
||||
};
|
||||
|
||||
var result = await _bridge.InitSlotAsync(request);
|
||||
var result = await _bridge.InitSlotAsync(request, token);
|
||||
|
||||
LogResult($"InitSlotAsync new slot {slotId}", result);
|
||||
|
||||
@ -113,13 +114,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var slot in selectedSlots)
|
||||
var tasks = selectedSlots.Select(async slot =>
|
||||
{
|
||||
var request = CreateSlotRequest(slot);
|
||||
|
||||
var result = await _bridge.InitSlotAsync(request);
|
||||
//var result = await _bridge.InitSlotAsync(request, token);
|
||||
var result = await _bridge.InitSlotWithRetryAsync(request, token);
|
||||
|
||||
LogResult($"InitSlotAsync slot {slot.Slot}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"InitSlotAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -130,13 +143,33 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
var selectedSlots = _mainView._batchPanel.GetSelectedGridData();
|
||||
|
||||
if (selectedSlots.Count == 0)
|
||||
{
|
||||
LogResult("UpdateSlotAsync", "No slot selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
var tasks = selectedSlots.Select(async slot =>
|
||||
{
|
||||
var request = CreateSlotRequest(slot);
|
||||
|
||||
var result = await _bridge.UpdateSlotAsync(request);
|
||||
//var result = await _bridge.UpdateSlotAsync(request, token);
|
||||
var result = await _bridge.UpdateSlotWithRetryAsync(request, token);
|
||||
|
||||
LogResult($"UpdateSlotAsync slot {slot.Slot}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"UpdateSlotAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -147,11 +180,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.GetSlotAsync(slot.Slot);
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.GetSlotAsync(slot.Slot);
|
||||
var result = await _bridge.GetSlotWithRetryAsync(slot.Slot);
|
||||
|
||||
LogResult($"GetSlotAsync slot {slot.Slot}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"GetSlotAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -162,14 +209,30 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.CleanSlotAsync(slot.Slot);
|
||||
var result = await _bridge.CleanSlotWithRetryAsync(slot.Slot);
|
||||
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
var result = await _bridge.CleanSlotAsync(slot.Slot);
|
||||
LogResult($"CleanSlotAsync slot {item.Slot}", item.Result);
|
||||
|
||||
LogResult($"CleanSlotAsync slot {slot.Slot}", result);
|
||||
|
||||
RefreshGrid(slot.Slot);
|
||||
RefreshGrid(item.Slot);
|
||||
}
|
||||
|
||||
_mainView._batchPanel.ClearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
@ -177,7 +240,10 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
var result = await _bridge.CleanAllSlotsAsync();
|
||||
//var result = await _bridge.CleanAllSlotsAsync();
|
||||
var result = await _bridge.CleanAllSlotsWithRetryAsync();
|
||||
|
||||
_mainView._batchPanel.ClearSelection();
|
||||
|
||||
LogResult("CleanAllSlotsAsync", result);
|
||||
|
||||
@ -189,11 +255,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.GetPcbIdAsync(slot.Slot, token);
|
||||
var slots = GetSelectedSlots().ToList();
|
||||
|
||||
LogResult($"GetPcbIdAsync slot {slot.Slot}", result);
|
||||
var tasks = slots.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.GetPcbIdAsync(slot.Slot, token);
|
||||
var result = await _bridge.GetPcbIdWithRetryAsync(slot.Slot, token);
|
||||
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"GetPcbIdAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -204,11 +284,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.ConnectAsync(slot.Slot, token);
|
||||
var slots = GetSelectedSlots().ToList();
|
||||
|
||||
LogResult($"ConnectAsync slot {slot.Slot}", result);
|
||||
var tasks = slots.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.ConnectAsyncc(slot.Slot, token);
|
||||
var result = await _bridge.ConnectWithRetryAsync(slot.Slot, token);
|
||||
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"ConnectAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -219,11 +313,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.LoginAsync(slot.Slot, token);
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.LoginAsync(slot.Slot, token);
|
||||
var result = await _bridge.LoginWithRetryAsync(slot.Slot, token);
|
||||
|
||||
LogResult($"LoginAsync slot {slot.Slot}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"LoginAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -234,11 +342,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
{
|
||||
ExecuteAsync(async token =>
|
||||
{
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.DisconnectAsync(slot.Slot, token);
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.DisconnectAsync(slot.Slot, token);
|
||||
var result = await _bridge.DisconnectWithRetryAsync(slot.Slot, token);
|
||||
|
||||
LogResult($"DisconnectAsync slot {slot.Slot}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"DisconnectAsync slot {item.Slot}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -254,14 +376,25 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
if (string.IsNullOrWhiteSpace(registerName))
|
||||
throw new Exception("Read register name is empty.");
|
||||
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
{
|
||||
var result = await _bridge.ReadRegisterAsync(
|
||||
slot.Slot,
|
||||
registerName,
|
||||
token);
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
//var result = await _bridge.ReadRegisterAsync(slot.Slot, registerName, token);
|
||||
var result = await _bridge.ReadRegisterWithRetryAsync(slot.Slot, registerName, token);
|
||||
|
||||
LogResult($"ReadRegisterAsync slot {slot.Slot}, register {registerName}", result);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult($"ReadRegisterAsync slot {item.Slot}, register {registerName}", item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
@ -281,36 +414,44 @@ namespace TBF.Rig.BridgeComponents.GciBridge.UI.StaraTuraAPI_GciBridge
|
||||
if (string.IsNullOrWhiteSpace(valueText))
|
||||
throw new Exception("Write register value is empty.");
|
||||
|
||||
foreach (var slot in GetSelectedSlots())
|
||||
object value;
|
||||
|
||||
if (registerName == "GENESISFLOW_LedMode")
|
||||
{
|
||||
object value;
|
||||
value = byte.Parse(valueText);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = valueText;
|
||||
}
|
||||
|
||||
if (registerName == "GENESISFLOW_LedMode")
|
||||
var tasks = GetSelectedSlots()
|
||||
.Select(async slot =>
|
||||
{
|
||||
value = byte.Parse(valueText);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = valueText;
|
||||
}
|
||||
//var result = await _bridge.WriteRegisterAsync(slot.Slot, registerName, value, false, false, token);
|
||||
var result = await _bridge.WriteRegisterWithRetryAsync(slot.Slot, registerName, value, false, false, token);
|
||||
|
||||
var result = await _bridge.WriteRegisterAsync(
|
||||
slot.Slot,
|
||||
registerName,
|
||||
value,
|
||||
false,
|
||||
false,
|
||||
token);
|
||||
return new
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Result = result
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
|
||||
LogResult($"WriteRegisterAsync slot {slot.Slot}, register {registerName}, value {value}", result);
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var item in results.OrderBy(x => x.Slot))
|
||||
{
|
||||
LogResult(
|
||||
$"WriteRegisterAsync slot {item.Slot}, register {registerName}, value {value}",
|
||||
item.Result);
|
||||
}
|
||||
|
||||
RefreshGrid();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
|
||||
@ -3301,6 +3301,9 @@
|
||||
<EmbeddedResource Include="Rig\BridgeComponents\GciBridge\GciBridgeCfgCtrl.resx">
|
||||
<DependentUpon>GciBridgeCfgCtrl.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Rig\BridgeComponents\GciBridge\UI\Debug\WorkerDebugPanel.resx">
|
||||
<DependentUpon>WorkerDebugPanel.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Rig\BridgeComponents\GciBridge\UI\MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user