123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- using Edge.Core.Processor;
- using Edge.Core.IndustryStandardInterface.Pump;
- using Edge.Core.IndustryStandardInterface.ATG;
- using Edge.Core.UniversalApi;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using VeederRoot_ATG_Console.MessageEntity;
- using VeederRoot_ATG_Console.MessageEntity.DispenserInterface.Outgoing;
- using VeederRoot_ATG_Console.MessageEntity.Incoming;
- using VeederRoot_ATG_Console.MessageEntity.Outgoing;
- using Wayne.FDCPOSLibrary;
- using static VeederRoot_ATG_Console.MessageEntity.Incoming.QueryInTankInventoryReportResponse.InventoryData;
- using static VeederRoot_ATG_Console.MessageEntity.Incoming.QueryInTankStatusReportResponse;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using Edge.Core.Processor.Communicator;
- namespace VeederRoot_ATG_Console
- {
- [UniversalApi(Name = "OnStateChange", EventDataType = typeof(AtgStateChangeEventArg))]
- [UniversalApi(Name = GenericAlarm.UniversalApiEventName, EventDataType = typeof(GenericAlarm[]), Description = "Fire GenericAlarms to AlarmBar for attracting users.")]
- [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
- [MetaPartsRequired(typeof(ComPortCommunicator<>))]
- [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
- [MetaPartsRequired(typeof(TcpServerCommunicator<>))]
- [MetaPartsDescriptor(
- "lang-zh-cn:维德路特液位仪lang-en-us:VeederRoot ATG",
- "lang-zh-cn:用于驱动维德路特协议(或兼容)的液位仪控制台lang-en-us:Used for driven ATG console that use VeederRoot ATG protocol(or compatible with)",
- new[] { "lang-zh-cn:液位仪lang-en-us:ATG" })]
- public class Handler : TestableActivePollingDeviceHandler<byte[], MessageBaseGeneric>, IAutoTankGaugeController
- {
- private ILogger logger = NullLogger.Instance;
- /// <summary>
- /// Range from 0 to 9.
- /// Event Message Identifier.
- /// Start Events and Stop Events contain event IDs to help the dispenser
- /// interface module identify transmissions that are repeated as a result of
- /// communication errors.
- /// Once an event report (start or end) is successfully transmitted,
- /// the Event Message ID must change so the next event report (start or end)
- /// will get a new ID in the range 0 - 9.
- /// An event report must keep the same ID until it is successfully transmitted.
- /// The status report does not require an ID.
- /// </summary>
- public static byte nextRotateEventId;
- private DateTime lastLogicalDeviceStateReceivedTime = DateTime.Now;
- // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
- public const int lastLogicalDeviceStateExpiredTime = 15;
- private System.Timers.Timer deviceOfflineCountdownTimer;
- private IContext<byte[], MessageBaseGeneric> context;
- private IEnumerable<Tank> tanks;
- /// <summary>
- /// </summary>
- private int loadAsync_Guard = 0;
- private IServiceProvider services;
- public string MetaConfigName => "VeederRoot_ATG_Console";
- public IEnumerable<Tank> Tanks => this.tanks;
- private int deviceId;
- public event EventHandler<AtgStateChangeEventArg> OnStateChange;
- public event EventHandler<AtgAlarmEventArg> OnAlarm;
- public int DeviceId => this.deviceId;
- public SystemUnit SystemUnit { get; private set; }
- public AtgState State { get; private set; } = AtgState.Offline;
- #region UniversalApi Service
- [UniversalApi()]
- public Task<IEnumerable<Tank>> GetTanksAsync()
- {
- return Task.FromResult(this.tanks);
- }
- #endregion
- public Handler(int deviceId, IServiceProvider services)
- {
- this.services = services;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- this.logger = loggerFactory.CreateLogger("PumpHandler");
- this.deviceId = deviceId;
- this.deviceOfflineCountdownTimer = new System.Timers.Timer(3000);
- this.deviceOfflineCountdownTimer.Elapsed += async (_, __) =>
- {
- if (DateTime.Now.Subtract(this.lastLogicalDeviceStateReceivedTime).TotalSeconds
- >= lastLogicalDeviceStateExpiredTime)
- {
- if (this.State != AtgState.Offline)
- {
- this.State = AtgState.Offline;
- logger.LogInformation("VeederRoot ATG Console with Id: " + this.deviceId + ", " + " State switched to OFFLINE due to long time no see data incoming");
- var onStateChangeEventArg = new AtgStateChangeEventArg(this.State, "VR ATG switched to Offline due to device offline timer timeout reached");
- this.OnStateChange?.Invoke(this, onStateChangeEventArg);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this.context.Processor, "OnStateChange", onStateChangeEventArg);
- }
- }
- };
- this.deviceOfflineCountdownTimer.Start();
- }
- /// <summary>
- /// Notify the Veeder Root ATG console that a fueling has started or done.
- /// </summary>
- /// <param name="startOrStopEventReportRequest">start or stop event which for notify ATG console.</param>
- /// <returns></returns>
- public async Task<MessageEntity.DispenserInterface.Incoming.GenericResponse>
- NotifyFuelTrxEventToAtgConsoleAsync(MessageEntity.DispenserInterface.Outgoing.OutgoingMessageBase startOrStopEventReportRequest)
- {
- MessageEntity.DispenserInterface.Incoming.GenericResponse response = null;
- startOrStopEventReportRequest.EventId = nextRotateEventId++;
- if (nextRotateEventId > 9)
- nextRotateEventId = 0;
- await Task.Factory.StartNew(() =>
- {
- ManualResetEvent block = new ManualResetEvent(false);
- this.context.Outgoing.WriteAsync(
- startOrStopEventReportRequest,
- (req, resp) => resp is MessageEntity.DispenserInterface.Incoming.GenericResponse,
- (req, resp) =>
- {
- if (resp != null)
- {
- var data = resp as MessageEntity.DispenserInterface.Incoming.GenericResponse;
- response = data;
- }
- else
- logger.LogError("NotifyFuelTrxEventToAtgConsoleAsync failed with timed out");
- block.Set();
- }, 2000);
- block.WaitOne(5 * 1000);
- });
- return response;
- }
- public override void Init(IContext<byte[], MessageBaseGeneric> context)
- {
- base.Init(context);
- this.context = context;
- this.context.Processor.Communicator.OnDisconnected += async (_, __) =>
- {
- if (this.State != AtgState.Offline)
- {
- this.State = AtgState.Offline;
- logger.LogInformation("VeederRoot ATG Console with Id: " + this.deviceId + ", " + " State switched to OFFLINE due to Communicator.OnDisconnected event received");
- var onStateChangeEventArg = new AtgStateChangeEventArg(this.State, "VR ATG switched to Offline due to Communicator OnDisconnected");
- this.OnStateChange?.Invoke(this, onStateChangeEventArg);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this.context.Processor, "OnStateChange", onStateChangeEventArg);
- }
- };
- var timeWindowWithActivePollingOutgoing =
- this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], MessageBaseGeneric>;
- DateTime? lastPollingRequestSendTime = null;
- VeederRoot_ATG_Console.MessageEntity.Outgoing.OutgoingMessageBase lastPollingMessage = null;
- timeWindowWithActivePollingOutgoing.PollingMsgProducer = () =>
- {
- /* for quickly send out the request in polling queue to real device,
- * the polling interval configurated in TimeWindowWithActivePollingOutgoing would not be set long, say 500ms,
- * but, this also make this PollingMsgProducer called too fast which may not proper for this VR device,
- * so below we're trying to slow it down to 2 seconds.
- */
- if (lastPollingRequestSendTime == null)
- {
- lastPollingRequestSendTime = DateTime.Now;
- var polling = new QueryTimeOfDayRequest(MessageBase.MessageFormat.Computer);
- return polling;
- }
- else if (DateTime.Now.Subtract(lastPollingRequestSendTime.Value).TotalSeconds >= 2)
- {
- /* every 2 seconds query tanks' alarms and status, the response handle will be in function: Process(..., ...) */
- lastPollingRequestSendTime = DateTime.Now;
- if (lastPollingMessage == null || lastPollingMessage is QueryInTankStatusReportRequest)
- {
- /* for query tank status, like if in delivery or not*/
- lastPollingMessage = new QueryInTankInventoryReportRequest(MessageBase.MessageFormat.Computer, 0);
- }
- else
- {
- /* for query tank alarms*/
- lastPollingMessage = new QueryInTankStatusReportRequest(MessageBase.MessageFormat.Computer, 0);
- }
- return lastPollingMessage;
- }
- return null;
- };
- }
- public override async Task Process(IContext<byte[], MessageBaseGeneric> context)
- {
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- if (this.State == AtgState.Offline)
- {
- if (1 == Interlocked.CompareExchange(ref this.loadAsync_Guard, 1, 0))
- return;
- try
- {
- logger.LogInformation($"VR ATG is switching From Offline to Idle by received a Device Msg: {(context.Incoming.Message?.ToLogString() ?? "")}, will reloading tanks...");
- this.tanks = await this.LoadAsync();
- if (this.tanks == null)
- {
- logger.LogInformation($"VR ATG switching to Idle failed as failure in Load tanks, will treat ATG still in Offline");
- return;
- }
- logger.LogInformation($"Loaded Tanks info(total: {this.tanks.Count()}): " + Environment.NewLine +
- this.tanks.Select(t =>
- $"Tank with TankNumber: { t.TankNumber} => ProductCode: { (t.Product?.ProductCode ?? "")}, " +
- $"ProductLabel: {t.Product?.ProductLabel ?? ""}, Diameter: {t.Diameter ?? -1}, " +
- $"TankState: {t.State}, ProbeLength: {t.Probe.ProbeLength}, ProbeState: {t.Probe.State ?? ""}" +
- //$"ProbeReadings: ({t.Probe.ProbeReading.ToString()})
- "").Aggregate((acc, n) => acc + Environment.NewLine + n));
- this.State = AtgState.Idle;
- }
- catch (Exception eeee)
- {
- logger.LogError($"VR ATG switching to Idle got exception: {eeee}{Environment.NewLine}Will treat ATG still in Offline");
- return;
- }
- finally
- {
- this.loadAsync_Guard = 0;
- }
- var onStateChangeEventArg = new AtgStateChangeEventArg(AtgState.TanksReloaded, "VR ATG switched from Offline to Online due to Process(...) get called");
- this.OnStateChange?.Invoke(this, onStateChangeEventArg);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this.context.Processor, "OnStateChange", onStateChangeEventArg);
- }
- if (context.Incoming.Message is QueryInTankStatusReportResponse pollingQueryTankAlarmResponse)
- {
- var alarmsForTanks = pollingQueryTankAlarmResponse?.TanksOfAlarms.Select(ta =>
- {
- var alarms = new List<Edge.Core.IndustryStandardInterface.ATG.Alarm>();
- alarms.AddRange(ta?.Alarms.Select(aType => new Edge.Core.IndustryStandardInterface.ATG.Alarm()
- {
- TankNumber = (byte)ta.TankNumber,
- Priority = AlarmPriority.Alarm,
- Type = aType,
- CreatedTimeStamp = DateTime.Now,
- }));
- return alarms;
- });
- foreach (var alarmsForTank in alarmsForTanks)
- {
- var alarmEventArg = new AtgAlarmEventArg(
- alarmsForTank.First().TankNumber,
- alarmsForTank.Select(alarm => new Alarm()
- {
- TankNumber = alarmsForTank.First().TankNumber,
- Priority = AlarmPriority.Alarm,
- Type = alarm.Type,
- CreatedTimeStamp = DateTime.Now,
- Description = alarmsForTank.First().TankNumber + "号油罐 报告其状态为:" + alarm.Type
- }));
- this.OnAlarm?.Invoke(this, alarmEventArg);
- }
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this.context.Processor, GenericAlarm.UniversalApiEventName,
- alarmsForTanks.SelectMany(als => als, (als, al) =>
- new GenericAlarm()
- {
- Category = "VeederRoot ATG Alarms from tanks",
- Title = $"Tank {al.TankNumber} is alarming",
- Detail = $"{al.Description}",
- Severity = (al.Priority == AlarmPriority.Alarm ? GenericAlarmSeverity.Error : GenericAlarmSeverity.Warning),
- }));
- }
- else if (context.Incoming.Message is QueryInTankInventoryReportResponse pollingQueryTankStateResponse
- && pollingQueryTankStateResponse.InventoryDatas != null)
- {
- foreach (var data in pollingQueryTankStateResponse.InventoryDatas)
- {
- var targetTank = this.tanks.FirstOrDefault(t => t.TankNumber == (byte)(data.TankNumber ?? -1));
- if (targetTank == null) continue;
- var deliveryingStateReportedFromDevice =
- data.States?.Exists(s => s == QueryInTankInventoryReportResponse.InventoryData.TankState.DeliveryInProgress_LSB) ?? false;
- if (deliveryingStateReportedFromDevice
- && targetTank.State != Edge.Core.IndustryStandardInterface.ATG.TankState.Delivering)
- {
- targetTank.State = Edge.Core.IndustryStandardInterface.ATG.TankState.Delivering;
- this.logger.LogInformation("VR ATG has a tank entered AtgTankState.Delivering due to TankState.DeliveryInProgress_LSB reported for tankNumber: " + (data.TankNumber ?? -1));
- }
- else if (!deliveryingStateReportedFromDevice
- && targetTank.State == Edge.Core.IndustryStandardInterface.ATG.TankState.Delivering)
- {
- targetTank.State = Edge.Core.IndustryStandardInterface.ATG.TankState.Idle;
- this.logger.LogInformation("VR ATG has a tank quit from AtgTankState.Delivering for tankNumber: " + (data.TankNumber ?? -1));
- }
- else return;
- var evtArg = new AtgStateChangeEventArg(targetTank, this.State, "the target tank changed tank state to: " + targetTank.State);
- this.OnStateChange?.Invoke(this, evtArg);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FireEvent(this.context.Processor, "OnStateChange", evtArg);
- }
- }
- }
- private async Task<IEnumerable<Tank>> LoadAsync()
- {
- this.logger.LogInformation("VR ATG is starting LoadAsync()...");
- var _ = await this.context.Outgoing.WriteAsync(
- new QuerySystemTypeAndLanguageFlagsRequest(MessageBase.MessageFormat.Computer),
- (req, testResp) => testResp is QueryOrSetSystemTypeAndLanguageFlagsResponse, 5000);
- if (_ is QueryOrSetSystemTypeAndLanguageFlagsResponse querySystemTypeAndLanguageFlagsResponse)
- {
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- this.logger.LogInformation($" VR ATG, querySystemTypeAndLanguageFlagsResponse: {querySystemTypeAndLanguageFlagsResponse.ToLogString()}");
- var timeGapBySeconds = DateTime.Now.Subtract((querySystemTypeAndLanguageFlagsResponse.CurrentDateAndTime ?? DateTime.Now)).TotalSeconds;
- if (timeGapBySeconds >= 60)
- {
- this.logger.LogInformation($" VR ATG has its CurrentDateAndTime differency from fusion time with gap of TotalSeconds: " +
- $"{timeGapBySeconds}, will Send SetTimeOfDayRequest for align 2 sides...");
- var setTimeResponse = await this.context.Outgoing.WriteAsync(
- new SetTimeOfDayRequest(MessageBase.MessageFormat.Computer, DateTime.Now),
- (req, testResp) => testResp is MessageBaseGeneric, 5000);
- if (setTimeResponse == null)
- this.logger.LogInformation($" VR ATG SetTimeOfDayRequest failed due to response timedout");
- }
- }
- else
- {
- _ = await this.context.Outgoing.WriteAsync(
- new QuerySystemTypeAndLanguageFlags_Extended(MessageBase.MessageFormat.Computer),
- (req, testResp) => testResp is QueryOrSetSystemTypeAndLanguageFlagsResponse, 3000);
- if (_ is QueryOrSetSystemTypeAndLanguageFlagsResponse querySystemTypeAndLanguageFlagsResponse_Extended)
- {
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- this.logger.LogInformation($" VR ATG QuerySystemTypeAndLanguageFlags_Extended responded: {querySystemTypeAndLanguageFlagsResponse_Extended.ToLogString()}");
- this.SystemUnit = querySystemTypeAndLanguageFlagsResponse_Extended?.SystemUnits ?? SystemUnit.Metric;
- }
- else
- this.logger.LogInformation(" Seems this ATG console does not " +
- "support 'QuerySystemTypeAndLanguageFlags' since the query returned with response: " + (_?.ToLogString() ?? "") +
- Environment.NewLine + "no way to know the ATG Console System's Unit");
- }
- var queryInTankInventoryReportResponse =
- await this.context.Outgoing.WriteAsync(
- new QueryInTankInventoryReportRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryInTankInventoryReportResponse, 4000) as QueryInTankInventoryReportResponse;
- //var queryInTankDiagnosticReportResponse = _ as QueryInTankDiagnosticReportResponse;
- if (queryInTankInventoryReportResponse == null)
- {
- this.logger.LogInformation(" QueryInTankInventoryReportResponse timed out, treat as ReloadTanks failed.");
- return null;
- //below request is used for get probe info
- var queryInTankDiagnosticReportResponse = await this.context.Outgoing.WriteAsync(
- new QueryInTankDiagnosticReportRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryInTankDiagnosticReportResponse, 4000) as QueryInTankDiagnosticReportResponse;
- }
- _ = await this.context.Outgoing.WriteAsync(
- new QueryTankDiameterRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryOrSetTankDiameterResponse, 4000);
- var queryOrSetTankDiameterResponse = _ as QueryOrSetTankDiameterResponse;
- if (queryOrSetTankDiameterResponse == null)
- {
- this.logger.LogInformation(" QueryOrSetTankDiameterResponse timed out, so diameter value will not presents.");
- }
- _ = await this.context.Outgoing.WriteAsync(
- new QueryTankProductLabelRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryOrSetTankProductLabelResponse, 5000);
- var queryOrSetTankProductLabelResponse = _ as QueryOrSetTankProductLabelResponse;
- if (queryOrSetTankProductLabelResponse == null)
- {
- this.logger.LogInformation(" QueryOrSetTankProductLabelResponse timed out, so product name value will not presents.");
- }
- //this.logger.LogInformation(queryOrSetTankProductLabelResponse.ToLogString());
- //var probeReadings = await this.GetTankProbeReadingsAsync(null);
- var innerTanks = new List<Tank>();
- foreach (var inventoryData in queryInTankInventoryReportResponse.InventoryDatas)
- {
- innerTanks.Add(
- new Tank()
- {
- TankNumber = (byte)(inventoryData?.TankNumber ?? -1),
- State = Edge.Core.IndustryStandardInterface.ATG.TankState.Idle,
- Diameter = queryOrSetTankDiameterResponse?.Diameters?.FirstOrDefault(pl => pl.Item1 == (inventoryData.TankNumber ?? -1))?.Item2 ?? -1,
- Product = new Product()
- {
- ProductCode = inventoryData.ProductCode?.ToString() ?? "",
- ProductLabel = queryOrSetTankProductLabelResponse?.ProductLabels?.FirstOrDefault(pl => pl.Item1 == (inventoryData.TankNumber ?? -1))?.Item2 ?? ""
- },
- Limit = new TankLimit()
- {
- },
- Probe = new Probe()
- {
- ProbeLength = -1,
- //ProbeReading = probeReadings.First(pl => pl.Item1 == probe.TankNumber).Item2,
- },
- });
- }
- return innerTanks;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="tankNumber">0 or null for query all tanks</param>
- /// <returns></returns>
- [UniversalApi(Description = "", InputParametersExampleJson = "[1]")]
- public async Task<TankReading> GetTankReadingAsync(int tankNumber)
- {
- //some china produced ATG may not support specific tank number query, so here have to pass in hardcoded 0 for query all tanks, and then filter out
- //the data for target tank
- var inventoryReportResponse = await this.context.Outgoing.WriteAsync(
- new QueryInTankInventoryReportRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryInTankInventoryReportResponse r, 5000) as QueryInTankInventoryReportResponse;
- if (inventoryReportResponse == null) throw new TimeoutException($"GetTankReadingAsync, QueryInTankInventoryReportRequest timedout");
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- if (tankNumber != 0)
- {
- var targetTankData = inventoryReportResponse.InventoryDatas.FirstOrDefault(id => id.TankNumber == tankNumber);
- if (targetTankData == null) throw new ArgumentException($"GetTankReadingAsync, Target tank with number: {tankNumber} does not have InventoryReport queried, you may input the not existing tank number?");
- }
- //if caller input tanknumber 0, then randomly give back a single data, otherwise filter out the data by tanknumber.
- return inventoryReportResponse.InventoryDatas.Where(id => tankNumber == 0 ? true : id.TankNumber == tankNumber)
- .Select(rd =>
- {
- #region parse each fields
- int tankNumber = rd.TankNumber ?? -1;
- double? volume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Volume)
- ? rd.TankReadingDatas[TankReadingDataName.Volume]
- : default(double?);
- double? tc_Volume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.TC_Volume)
- ? rd.TankReadingDatas[TankReadingDataName.TC_Volume]
- : default(double?);
- double? ullage
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Ullage)
- ? rd.TankReadingDatas[TankReadingDataName.Ullage]
- : default(double?);
- double? height
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Height)
- ? rd.TankReadingDatas[TankReadingDataName.Height]
- : default(double?);
- double? water
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Water)
- ? rd.TankReadingDatas[TankReadingDataName.Water]
- : default(double?);
- double? temperature
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Temperature)
- ? rd.TankReadingDatas[TankReadingDataName.Temperature]
- : default(double?);
- double? waterVolume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.WaterVolume)
- ? rd.TankReadingDatas[TankReadingDataName.WaterVolume]
- : default(double?);
- #endregion
- //this.logger.LogInformation("QueryInTankInventoryReportDatas: "
- // + "TankNumber: " + ((rd?.TankNumber.Value.ToString()) ?? "")
- // + ", ProductCode: " + ((rd?.ProductCode.Value.ToString()) ?? "")
- // + ", State: " + (rd.States.Any() ? rd.States.First().ToString() : "")
- // + ", Volume: " + (volume ?? -1)
- // + ", TcVolume: " + (tc_Volume ?? -1)
- // + ", Ullage: " + (ullage ?? -1)
- // + ", Height: " + (height ?? -1)
- // + ", Water: " + (water ?? -1)
- // + ", Temperature: " + (temperature ?? -1)
- // + ", WaterVolume: " + (waterVolume ?? -1));
- var tankReading = new TankReading()
- {
- TankNumber = tankNumber,
- Volume = volume,
- TcVolume = tc_Volume,
- Ullage = ullage,
- Height = height,
- Water = water,
- Temperature = temperature,
- WaterVolume = waterVolume
- };
- return tankReading;
- }).FirstOrDefault();
- }
- /// <summary>
- /// Get reading for all tanks.
- /// </summary>
- /// <returns>reading for all tanks</returns>
- [UniversalApi(Description = "Get all tanks' reading")]
- public async Task<IEnumerable<TankReading>> GetTanksReadingAsync()
- {
- //some china produced ATG may not support specific tank number query, so here have to pass in hardcoded 0 for query all tanks, and then filter out
- //the data for target tank
- var inventoryReportResponse = await this.context.Outgoing.WriteAsync(
- new QueryInTankInventoryReportRequest(MessageBase.MessageFormat.Computer, 0),
- (req, testResp) => testResp is QueryInTankInventoryReportResponse r, 5000) as QueryInTankInventoryReportResponse;
- if (inventoryReportResponse == null) throw new TimeoutException($"GetTanksReadingAsync timedout");
- this.lastLogicalDeviceStateReceivedTime = DateTime.Now;
- return inventoryReportResponse.InventoryDatas.Select(rd =>
- {
- #region parse each fields
- int tankNumber = rd.TankNumber ?? -1;
- double? volume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Volume)
- ? rd.TankReadingDatas[TankReadingDataName.Volume]
- : default(double?);
- double? tc_Volume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.TC_Volume)
- ? rd.TankReadingDatas[TankReadingDataName.TC_Volume]
- : default(double?);
- double? ullage
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Ullage)
- ? rd.TankReadingDatas[TankReadingDataName.Ullage]
- : default(double?);
- double? height
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Height)
- ? rd.TankReadingDatas[TankReadingDataName.Height]
- : default(double?);
- double? water
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Water)
- ? rd.TankReadingDatas[TankReadingDataName.Water]
- : default(double?);
- double? temperature
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.Temperature)
- ? rd.TankReadingDatas[TankReadingDataName.Temperature]
- : default(double?);
- double? waterVolume
- = rd.TankReadingDatas.ContainsKey(TankReadingDataName.WaterVolume)
- ? rd.TankReadingDatas[TankReadingDataName.WaterVolume]
- : default(double?);
- #endregion
- //this.logger.LogInformation("QueryInTankInventoryReportDatas: "
- // + "TankNumber: " + ((rd?.TankNumber.Value.ToString()) ?? "")
- // + ", ProductCode: " + ((rd?.ProductCode.Value.ToString()) ?? "")
- // + ", State: " + (rd.States.Any() ? rd.States.First().ToString() : "")
- // + ", Volume: " + (volume ?? -1)
- // + ", TcVolume: " + (tc_Volume ?? -1)
- // + ", Ullage: " + (ullage ?? -1)
- // + ", Height: " + (height ?? -1)
- // + ", Water: " + (water ?? -1)
- // + ", Temperature: " + (temperature ?? -1)
- // + ", WaterVolume: " + (waterVolume ?? -1));
- var tankReading = new TankReading()
- {
- TankNumber = tankNumber,
- Volume = volume,
- TcVolume = tc_Volume,
- Ullage = ullage,
- Height = height,
- Water = water,
- Temperature = temperature,
- WaterVolume = waterVolume
- };
- return tankReading;
- });
- }
- [UniversalApi(Description = "ONLY the deliveries read from ATG device side with timestamp > filterTimestamp will be returned.", InputParametersExampleJson = "[1,8,0,\"2020-04-01T18:25:43.511Z\"]")]
- public async Task<IEnumerable<Delivery>> GetTankDeliveryAsync(int tankNumber, int pageRowCount = 10, int pageIndex = 0, DateTime? filterTimestamp = null)
- {
- this.logger.LogDebug($"VR ATG is GetTankDeliveryAsync for tankNumber: {tankNumber} with filterTimestamp: {(filterTimestamp?.ToString("yyyy-MM-dd HH:mm:ss fff") ?? "")}");
- var response = await this.context.Outgoing.WriteAsync(
- new QueryInTankMostRecentDeliveryReportRequest(MessageBase.MessageFormat.Computer, tankNumber),
- (req, testResp) =>
- testResp is QueryInTankMostRecentDeliveryReportResponse r && r.TankNumberInFunctionCode == tankNumber,
- 20000);
- var mostRecentDeliveryReportResponse = response as QueryInTankMostRecentDeliveryReportResponse;
- if (response == null) throw new TimeoutException($" QueryInTankMostRecentDeliveryReportRequest(tankNumber: {tankNumber}) timedout");
- this.logger.LogDebug($" GetTankDeliveryAsync for tankNumber: {tankNumber} got mostRecentDeliveryReportResponse: {mostRecentDeliveryReportResponse.ToLogString()}");
- var results = mostRecentDeliveryReportResponse.DeliveriesForTanks.SelectMany(d => d.Deliveries)
- .Select(queried => new Delivery()
- {
- TankNumber = (byte)(queried.TankNumber ?? -1),
- StartingDateTime = queried.StartingDateTime.Value,
- StartingFuelHeight = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.StartingHeight).Value,
- StartingFuelVolume = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.StartingVolume).Value,
- StartingFuelTCVolume = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.StartingTcVolume).Value,
- StartingTemperture = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.StartingTemp).Value,
- StartingWaterHeight = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.StartingWater).Value,
- EndingDateTime = queried.EndingDateTime,
- EndingFuelHeight = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.EndingHeight).Value,
- EndingFuelVolume = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.EndingVolume).Value,
- EndingFuelTCVolume = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.EndingTcVolume).Value,
- EndingTemperture = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.EndingTemp).Value,
- EndingWaterHeight = queried.Datas.FirstOrDefault(d => d.Key == DeliveryReadingDataName.EndingWater).Value,
- })
- .Where(d => d.StartingDateTime > (filterTimestamp ?? DateTime.MinValue))
- .OrderByDescending(d => d.StartingDateTime).Skip(pageRowCount * pageIndex).Take(pageRowCount);
- this.logger.LogDebug($" TankNumber: {tankNumber} has {results.Count()} delivery records satisfy filter timestamp({(filterTimestamp?.ToString("yyyy-MM-dd HH:mm:ss fff") ?? "")}) condition.");
- return results;
- }
- //public async Task<IEnumerable<TankAlarms>> GetTankActiveOrUnackedAlarmsAsync(int tankNumber, int count)
- //{
- // var response = await this.context.Outgoing.WriteAsync(
- // new QueryInTankStatusReportRequest(MessageBase.MessageFormat.Computer, tankNumber),
- // (req, testResp) => testResp is MessageBaseGeneric, 3000);
- // if (response == null) throw new TimeoutException($"QueryInTankStatusReportRequest(tankNumber: {tankNumber}) timedout");
- // var queryInTankStatusReportResponse = response as QueryInTankStatusReportResponse;
- // return queryInTankStatusReportResponse?.TanksOfAlarms;
- //}
- [UniversalApi(Description = "", InputParametersExampleJson = "[1,8,0,\"2020-04-01T18:25:43.511Z\"]")]
- public async Task<IEnumerable<Inventory>> GetTankInventoryAsync(int tankNumber, int pageRowCount = 10, int pageIndex = 0, DateTime? filterTimestamp = null)
- {
- var reading = await this.GetTankReadingAsync(tankNumber);
- if (reading == null) return null;
- var inventory = new Inventory()
- {
- TankNumber = tankNumber,
- FuelHeight = reading.Height ?? -1,
- FuelVolume = reading.Volume ?? -1,
- FuelTCVolume = reading.TcVolume ?? -1,
- WaterHeight = reading.Water ?? -1,
- Temperture = reading.Temperature ?? int.MinValue,
- TimeStamp = DateTime.Now,
- };
- return new[] { inventory };
- }
- [UniversalApi(Description = "", InputParametersExampleJson = "[1,8,0,\"2020-04-01T18:25:43.511Z\"]")]
- public async Task<IEnumerable<Alarm>> GetTankAlarmAsync(int tankNumber, int pageRowCount = 10, int pageIndex = 0, DateTime? filterTimestamp = null)
- {
- var response = await this.context.Outgoing.WriteAsync(
- new QueryInTankStatusReportRequest(MessageBase.MessageFormat.Computer, tankNumber),
- (req, testResp) => testResp is QueryInTankStatusReportResponse r && r.TankNumberInFunctionCode == tankNumber, 5000);
- if (response == null) throw new TimeoutException($"QueryInTankStatusReportRequest(tankNumber: {tankNumber}) timedout");
- var queryInTankStatusReportResponse = response as QueryInTankStatusReportResponse;
- var alarmsForTanks = queryInTankStatusReportResponse?.TanksOfAlarms.Select(ta =>
- {
- var alarms = new List<Edge.Core.IndustryStandardInterface.ATG.Alarm>();
- alarms.AddRange(ta?.Alarms.Select(aType => new Edge.Core.IndustryStandardInterface.ATG.Alarm()
- {
- TankNumber = (byte)ta.TankNumber,
- Priority = AlarmPriority.Alarm,
- Type = aType,
- CreatedTimeStamp = DateTime.Now,
- }));
- return alarms;
- });
- return alarmsForTanks?.SelectMany(al => al)
- .Where(al => al.CreatedTimeStamp > (filterTimestamp ?? DateTime.MinValue))
- .OrderByDescending(d => d.CreatedTimeStamp).Skip(pageRowCount * pageIndex).Take(pageRowCount);
- }
- }
- }
|