639 lines
24 KiB
C#
639 lines
24 KiB
C#
using CordonelPreadjustmentUi;
|
|
using CordonelPreadjustmentUi.Processes.Itinerary;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO.Ports;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Xylem.Common.Hardware.WaterMeter.WaterMeterCore;
|
|
using Xylem.Common.Ui.CordonelPreadjustmentUi;
|
|
using static GenesisCordonelInterface.API.PublicModels;
|
|
|
|
namespace GenesisCordonelInterface.API
|
|
{
|
|
/// <summary>
|
|
/// Public-facing facade for external applications integrating with
|
|
/// Genesis Cordonel Interface.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class exposes a simplified and controlled API for external callers.
|
|
/// It validates public input, maps public request models to internal models,
|
|
/// forwards operations to the internal GCI implementation and exposes status
|
|
/// notifications for meter batch changes.
|
|
///
|
|
/// This layer should stay thin. Business logic and meter communication are
|
|
/// handled by <see cref="InterfaceGCIToLaatzen"/>.
|
|
/// </remarks>
|
|
public class InterfaceOutsideToGCI
|
|
{
|
|
/// <summary>
|
|
/// Internal GCI implementation used by this public facade.
|
|
/// </summary>
|
|
public readonly InterfaceGCIToLaatzen _innerMeterAPI;
|
|
|
|
/// <summary>
|
|
/// Occurs when meter batch status information changes.
|
|
/// </summary>
|
|
public event Action<List<MeterBatchDebugStatus>> MeterBatchStatusChanged;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the public GCI facade.
|
|
/// </summary>
|
|
public InterfaceOutsideToGCI()
|
|
{
|
|
_innerMeterAPI = new InterfaceGCIToLaatzen();
|
|
}
|
|
|
|
// Laatzen ToolBox actions
|
|
|
|
#region ================================== PORT DETECTION ==================================
|
|
|
|
public PortDetectionResult DetectStreamingPort(int slot)
|
|
{
|
|
var result = _innerMeterAPI.DetectStreamingPort(slot);
|
|
//RaiseMeterBatchStatusChanged();
|
|
return result;
|
|
}
|
|
|
|
public async Task<PortDetectionResult> DetectStreamingPortAsync(
|
|
int slot,
|
|
CancellationToken token = default(CancellationToken))
|
|
{
|
|
var result = await _innerMeterAPI.DetectStreamingPortAsync(slot, token);
|
|
//RaiseMeterBatchStatusChanged();
|
|
return result;
|
|
}
|
|
|
|
public PortDetectionResult DetectRequestPort(int slot)
|
|
{
|
|
var result = _innerMeterAPI.DetectRequestPort(slot);
|
|
//RaiseMeterBatchStatusChanged();
|
|
return result;
|
|
}
|
|
|
|
public async Task<PortDetectionResult> DetectRequestPortAsync(
|
|
int slot,
|
|
CancellationToken token = default(CancellationToken))
|
|
{
|
|
var result = await _innerMeterAPI.DetectRequestPortAsync(slot, token);
|
|
//RaiseMeterBatchStatusChanged();
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== INIT/UPDATE/GET slot ==================================
|
|
|
|
/// <summary>
|
|
/// Initializes a meter slot using the provided slot configuration.
|
|
/// </summary>
|
|
/// <param name="request">
|
|
/// Slot initialization request containing slot id, configuration source,
|
|
/// password source, request port and streaming port.
|
|
/// </param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Result describing whether the slot was created, updated, already existed or failed.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// Thrown when <paramref name="request"/> is null.
|
|
/// </exception>
|
|
public async Task<GciInitSlotResult> InitSlotAsync(
|
|
GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
var result = await _innerMeterAPI.InitSlotAsync(
|
|
request.SlotId,
|
|
ModelsMapping.MapConfigSource(request.ConfigSource),
|
|
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
|
ModelsMapping.MapPort(request.RequestPort),
|
|
ModelsMapping.MapPort(request.StreamingPort),
|
|
token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates configuration of an existing meter slot.
|
|
/// </summary>
|
|
/// <param name="request">
|
|
/// Slot configuration request containing updated configuration source,
|
|
/// password source and port settings.
|
|
/// </param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Result describing whether the slot update succeeded or failed.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// Thrown when <paramref name="request"/> is null.
|
|
/// </exception>
|
|
public async Task<GciInitSlotResult> UpdateSlotAsync(
|
|
GciInitSlotRequest request,
|
|
CancellationToken token = default)
|
|
{
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
var result = await _innerMeterAPI.UpdateSlotAsync(
|
|
request.SlotId,
|
|
ModelsMapping.MapConfigSource(request.ConfigSource),
|
|
ModelsMapping.MapPasswordSource(request.PasswordSource),
|
|
ModelsMapping.MapPort(request.RequestPort),
|
|
ModelsMapping.MapPort(request.StreamingPort),
|
|
token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets information about one meter slot.
|
|
/// </summary>
|
|
/// <param name="slotId">Slot id to query. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Slot information including existence, connection state, login state,
|
|
/// PCB id and configured communication ports.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slotId"/> is invalid.
|
|
/// </exception>
|
|
public async Task<GciSlotInfo> GetSlotAsync(
|
|
int slotId,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slotId <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await _innerMeterAPI.GetOneMeterInfo(slotId, token).ConfigureAwait(false);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets information about all currently initialized meter slots.
|
|
/// </summary>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Collection of slot information records for all known meters.
|
|
/// </returns>
|
|
public async Task<GciAllSlotsInfo> GetAllSlotsAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
var result = await _innerMeterAPI.GetAllMetersInfo(token).ConfigureAwait(false);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans one meter slot and releases its runtime resources.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id to clean. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Result describing whether the slot cleanup succeeded or failed.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slot"/> is invalid.
|
|
/// </exception>
|
|
public async Task<GciCleanSlotResult> CleanSlotAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.", nameof(slot));
|
|
|
|
var result = await _innerMeterAPI.CleanSlotAsync(slot, token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans all initialized meter slots and releases related runtime resources.
|
|
/// </summary>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Result describing whether cleanup of all slots succeeded or failed.
|
|
/// </returns>
|
|
public async Task<GciCleanAllSlotsResult> CleanAllSlotsAsync(
|
|
CancellationToken token = default)
|
|
{
|
|
var result = await _innerMeterAPI.CleanAllSlotsAsync(token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
#region ================================== PASSWORD ==================================
|
|
|
|
/// <summary>
|
|
/// Sets runtime password for the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="password">Password to assign to the meter.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>Result containing password update status.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when slot id is invalid or password is empty.
|
|
/// </exception>
|
|
public async Task<GciSetPasswordResult> SetPasswordAsync(
|
|
int slot,
|
|
string password,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
if (string.IsNullOrWhiteSpace(password))
|
|
throw new ArgumentException("Password is empty.");
|
|
|
|
var result = await _innerMeterAPI.SetMeterPasswordAsync(slot, password, token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== LOGIN ==================================
|
|
|
|
/// <summary>
|
|
/// Logs in to the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>Login result containing login state and status message.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slot"/> is invalid.
|
|
/// </exception>
|
|
public async Task<PublicModels.GciLoginResult> LoginOneSlotAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await _innerMeterAPI.LoginOneSlotAsync(slot, token).ConfigureAwait(false);
|
|
return result;
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
}
|
|
#endregion
|
|
|
|
#region ================================== CONNECTION ==================================
|
|
|
|
/// <summary>
|
|
/// Connects the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>Connection result containing connection state and status message.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slot"/> is invalid.
|
|
/// </exception>
|
|
public async Task<GciConnectResult> ConnectOneSlotAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await _innerMeterAPI.ConnectOneSlotAsync(slot, token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnects the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>Disconnect result containing final connection state and status message.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slot"/> is invalid.
|
|
/// </exception>
|
|
public async Task<GciDisconnectResult> DisconnectAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await _innerMeterAPI.DisconnectAsync(slot, token).ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
#region ================================== PCB ==================================
|
|
|
|
/// <summary>
|
|
/// Reads PCB identifier from the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>Result containing PCB id and read status.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when <paramref name="slot"/> is invalid.
|
|
/// </exception>
|
|
public async Task<GciGetPcbIdResult> GetPcbIdAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
if (slot <= 0)
|
|
throw new ArgumentException("Invalid slot id.");
|
|
|
|
var result = await _innerMeterAPI.GetPcbIdAsync(slot, token).ConfigureAwait(false);
|
|
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
#region ================================== READ ==================================
|
|
|
|
/// <summary>
|
|
/// Reads a firmware register value from the meter assigned to the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="registerName">Register identifier to read.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Register read result containing raw register value and operation status.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when slot id or register name is invalid.
|
|
/// </exception>
|
|
public async Task<RegisterReadResult> ReadRegisterAsync(
|
|
int slot,
|
|
string registerName,
|
|
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
|
|
.ReadRegisterAsync(slot, registerName, token)
|
|
.ConfigureAwait(false);
|
|
|
|
//RaiseMeterBatchStatusChanged();
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== WRITE ==================================
|
|
|
|
/// <summary>
|
|
/// Writes a value to a firmware register.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="registerName">Register identifier to write.</param>
|
|
/// <param name="value">Value to write.</param>
|
|
/// <param name="storeToDevice">
|
|
/// Indicates whether configuration should be permanently stored.
|
|
/// </param>
|
|
/// <param name="refreshSystemState">
|
|
/// Indicates whether firmware system state should be refreshed after write.
|
|
/// </param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Register write result describing write status.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when slot id or register name is invalid.
|
|
/// </exception>
|
|
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 ==================================
|
|
|
|
/// <summary>
|
|
/// Updates meter password for the specified slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id. Must be greater than zero.</param>
|
|
/// <param name="password">New password.</param>
|
|
/// <param name="token">Cancellation token used to cancel the asynchronous operation.</param>
|
|
/// <returns>
|
|
/// Password update result.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// Thrown when slot id or password is invalid.
|
|
/// </exception>
|
|
public async Task<GciSetPasswordResult> SetMeterPasswordAsync(
|
|
int slot,
|
|
string password,
|
|
CancellationToken token = default)
|
|
{
|
|
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;
|
|
}
|
|
#endregion
|
|
|
|
#region ================================== DEBUG STATUS ==================================
|
|
|
|
/// <summary>
|
|
/// Gets runtime diagnostic information for all active workers.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Collection containing worker state, queue information,
|
|
/// current operation and activity timestamps.
|
|
/// </returns>
|
|
public List<WorkerDebugStatus> GetWorkerDebugStatuses()
|
|
{
|
|
return _innerMeterAPI.GetWorkerDebugStatuses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets runtime diagnostic information for all meter slots.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Collection containing slot state, connection state,
|
|
/// selected state and communication configuration.
|
|
/// </returns>
|
|
public List<MeterBatchDebugStatus> GetMeterBatchDebugStatuses()
|
|
{
|
|
return _innerMeterAPI.GetMeterBatchDebugStatuses();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises meter batch status change notification.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Intended to notify external consumers after changes in meter state.
|
|
/// </remarks>
|
|
public void RaiseMeterBatchStatusChanged()
|
|
{
|
|
var statuses = GetMeterBatchDebugStatuses();
|
|
|
|
var handler = MeterBatchStatusChanged;
|
|
if (handler != null)
|
|
handler(statuses);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== SLOT SELECTION ==================================
|
|
|
|
/// <summary>
|
|
/// Sets selection state for a slot.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id.</param>
|
|
/// <param name="selected">Selection state.</param>
|
|
public void SetSlotSelected(int slot, bool selected)
|
|
{
|
|
_innerMeterAPI.SetSlotSelected(slot, selected);
|
|
RaiseMeterBatchStatusChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified slot is selected.
|
|
/// </summary>
|
|
/// <param name="slot">Slot id.</param>
|
|
/// <returns>
|
|
/// True if slot is selected; otherwise false.
|
|
/// </returns>
|
|
public bool IsSlotSelected(int slot)
|
|
{
|
|
return _innerMeterAPI.IsSlotSelected(slot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all selected slot identifiers.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Ordered collection of selected slot ids.
|
|
/// </returns>
|
|
public List<int> GetSelectedSlots()
|
|
{
|
|
return _innerMeterAPI.GetSelectedSlots();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================================== Register names ==================================
|
|
|
|
/// <summary>
|
|
/// Gets all available firmware register identifiers.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Ordered collection of register names.
|
|
/// </returns>
|
|
public List<string> GetAllRegisterNames()
|
|
{
|
|
return _innerMeterAPI.GetAllRegisterNames();
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Laatzen Preadjustment processes
|
|
|
|
#region ================================== PreAdjustment ==================================
|
|
public PreAdjustmentInitializationResult Preadjustment_Initialization(
|
|
ProcessProgress pp,
|
|
List<MeterStateControl> mc)
|
|
{
|
|
return _innerMeterAPI.Preadjustment_Initialization(pp, mc);
|
|
}
|
|
|
|
public Task<PreadjustmentDetectResult> PreAdjustment_DetectAsync(
|
|
IEnumerable<PublicModels.MeterBatchDebugStatus> selectedSlots,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_DetectAsync(selectedSlots, token);
|
|
}
|
|
|
|
public Task<PreAdjustmentProcessResult> PreAdjustment_PreparationAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_PreparationAsync(slot, token);
|
|
}
|
|
|
|
public Task<PreAdjustmentProcessResult> PreAdjustment_AmplitudeTestAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_AmplitudeTestAsync(slot, token);
|
|
}
|
|
|
|
public Task<PreAdjustmentProcessResult> PreAdjustment_TemperatureCalibrationAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_TemperatureCalibrationAsync(slot, token);
|
|
}
|
|
|
|
public Task<PreAdjustmentProcessResult> PreAdjustment_OffsetTestAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_OffsetTestAsync(slot, token);
|
|
}
|
|
|
|
public Task<PreAdjustmentProcessResult> PreAdjustment_CompletionAsync(
|
|
int slot,
|
|
CancellationToken token = default)
|
|
{
|
|
return _innerMeterAPI.PreAdjustment_CompletionAsync(slot, token);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |