123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799 |
- using AutoMapper;
- using Edge.Core.Database;
- using Edge.Core.Processor;
- using Edge.Core.IndustryStandardInterface.Pump;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Logging.Abstractions;
- using Wayne_VaporRecoveryDataCollectorBoard.MessageEntity;
- using Wayne_VaporRecoveryDataCollectorBoard.MessageEntity.Incoming;
- using Wayne_VaporRecoveryDataCollectorBoard.MessageEntity.Outgoing;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Xml;
- using System.Xml.Serialization;
- using Timer = System.Timers.Timer;
- using Edge.Core.Processor.Dispatcher.Attributes;
- using Edge.Core.Processor.Communicator;
- using Edge.Core.UniversalApi;
- using System.Threading;
- namespace Wayne_VaporRecoveryDataCollectorBoard
- {
- [MetaPartsRequired(typeof(HalfDuplexActivePollingDeviceProcessor<,>))]
- [MetaPartsRequired(typeof(ComPortCommunicator<>))]
- [MetaPartsRequired(typeof(TcpClientCommunicator<>))]
- //[UniversalApi(Name = GenericAlarm.UniversalApiEventName, EventDataType = typeof(GenericAlarm[]), Description = "Fire GenericAlarms to AlarmBar for attracting users.")]
- [MetaPartsDescriptor(
- "lang-zh-cn:稳牌气液比数据收集板lang-zh-tw:穩牌氣液比數據收集板lang-en-us:稳牌气液比数据收集板",
- "lang-zh-cn:用于驱动 稳牌气液比数据收集板,日志名称 DynamicPrivate_VaporRecoveryDataCollectorBoard" +
- "lang-zh-tw:用於驅動 穩牌氣液比數據收集板" +
- "lang-en-us:Used for driven Wayne Vapor and Liquid collector board, log file name: DynamicPrivate_VaporRecoveryDataCollectorBoard",
- new[] { "lang-zh-cn:在线监测lang-zh-tw:在線監測lang-en-us:OnlineWatch" })]
- /// <summary>
- /// one comm channel may have multiple devices connected.
- /// </summary>
- public class GroupHandler : TestableActivePollingDeviceHandler<byte[], DataCollectorMessageBase>, IEnumerable<Board>, IDisposable
- {
- //private DateTime lastDeviceMessageReceivedTime;
- // by seconds, change this value need change the correlated deviceOfflineCountdownTimer's interval as well
- //public const int maxSecondsDeviceMuteWillTreatAsOffline = 30;
- //private Timer deviceOfflineCountdownTimer;
- private IServiceProvider services;
- public event EventHandler<BoardStateChangeEventArgs> OnBoardStateChange;
- /// <summary>
- /// notify only the nozzle fuelling state change.
- /// </summary>
- public event EventHandler<BoardNozzleStateChangeEventArgs> OnBoardNozzleStateChange;
- /// <summary>
- /// either a trx done flow data, or a serials of real time flow data.
- /// </summary>
- public event EventHandler<DataRecievedEventArgs> OnDataRecieved;
- //public string HardwareIdentity { get; set; }
- public IContext<byte[], DataCollectorMessageBase> Context { get { return this.context; } }
- static ILogger logger = NullLogger.Instance;
- private int singleBoardOfflineTimeThresholdByMs = 15000;
- private Timer singleBoardOfflineCheckTimer;
- public List<Board> Boards { get; private set; } = new List<Board>();
- /// <summary>
- /// the device has an internal time, if that time lagging too much from Fc, then FC will send a Write Time.
- /// this value controls as the threshold.
- /// </summary>
- private const int maxTimeGapFromFcAndDeviceBySeconds = 60 * 5;
- //private XmlConfiguration configuration;
- private IContext<byte[], DataCollectorMessageBase> context;
- //public byte DeviceAddress { get; set; }
- public DeviceConfigV1 DeviceConfig { get; private set; }
- public DataCollectorBoardTypeEnum? CurrentDataCollectorType { get; set; }
- //public int DeviceId { get; }
- public class DeviceConfigV1
- {
- public IEnumerable<BoardConfigV1> BoardConfigs { get; set; }
- }
- public class BoardConfigV1
- {
- /// <summary>
- /// each board has a serial number of global unique, like the mac address.
- /// </summary>
- public int SerialNumber { get; set; }
- /// <summary>
- /// fcc will based on serial number to set a physical address to board dynamically.
- /// </summary>
- public byte SetPhysicalAddress { get; set; }
- public IEnumerable<BoardNozzleConfigV1> NozzleConfigs { get; set; }
- }
- public class BoardInitParameterConfigV1
- {
- public byte 最大未变化次数 { get; set; }
- public int 开始加油阀值 { get; set; }
- public int 停止加油阀值 { get; set; }
- public int 最小加油量 { get; set; }
- public int 加油脉冲当量 { get; set; }
- public int 油气脉冲当量 { get; set; }
- /// <summary>
- /// 控制板将依据此值在加油过程中调节气泵回气量,以求尽可能靠近此比值
- /// </summary>
- public double 气液比值 { get; set; }
- public string ToLogString()
- {
- return "最大未变化次数: " + this.最大未变化次数
- + ", 开始加油阀值: " + this.开始加油阀值
- + ", 停止加油阀值: " + this.停止加油阀值
- + ", 最小加油量: " + this.最小加油量
- + ", 加油脉冲当量: " + this.加油脉冲当量
- + ", 油气脉冲当量: " + this.油气脉冲当量
- + ", 气液比值: " + this.气液比值;
- }
- }
- public class BoardNozzleConfigV1
- {
- public int OnBoardNozzleNumber { get; set; }
- /// <summary>
- /// each vr watched nozzle is a physical pump nozzle, and each nozzle must have a site level nozzle id(in China),
- /// here is the binding info, this is the key info for draw UI for user.
- /// </summary>
- public int SiteLevelNozzleId { get; set; }
- /// <summary>
- /// specified by config in device handler, has the priority compare to external input values.
- /// </summary>
- public BoardInitParameterConfigV1 ManualInitParameter { get; set; }
- /// <summary>
- /// specified by config from external(like an app), has the low priority compare to internal manual values.
- /// </summary>
- public BoardInitParameterConfigV1 ExternalInitParameter { get; set; }
- }
- protected void FireOnBoardStateChangeEvent(Board board)
- {
- this.OnBoardStateChange?.Invoke(this, new BoardStateChangeEventArgs(board));
- }
- protected void FireOnDataReceivedEvent(DataCollectorMessageBase data)
- {
- this.OnDataRecieved?.Invoke(this, new DataRecievedEventArgs(data));
- }
- protected void FireOnBoardNozzleStateChangeEvent(BoardNozzle nozzle)
- {
- this.OnBoardNozzleStateChange?.Invoke(this, new BoardNozzleStateChangeEventArgs(nozzle));
- }
- [ParamsJsonSchemas("DeviceGroupHandlerCtorParamsJsonSchemas")]
- public GroupHandler(DeviceConfigV1 deviceConfig, IServiceProvider services)
- {
- if (deviceConfig.BoardConfigs.Select(bc => bc.SetPhysicalAddress).GroupBy(g => g).Any(g => g.Count() >= 2))
- throw new ArgumentException("2 or more board configs have the same PhysicalAddress defined, while it should be unique per one comm channel");
- if (deviceConfig.BoardConfigs.Select(bc => bc.SerialNumber).GroupBy(g => g).Any(g => g.Count() >= 2))
- throw new ArgumentException("2 or more board configs have the same SerialNumber defined, while it should be unique per one comm channel");
- this.services = services;
- this.DeviceConfig = deviceConfig;
- var loggerFactory = services.GetRequiredService<ILoggerFactory>();
- logger = loggerFactory.CreateLogger("DynamicPrivate_VaporRecoveryDataCollectorBoard");
- deviceConfig.BoardConfigs.ToList().ForEach(bc =>
- {
- var board = new Board(bc.SerialNumber, bc.SetPhysicalAddress, BoardStateEnum.UnInit,
- bc.NozzleConfigs.Select(nc => new BoardNozzle()
- {
- NozzleNumberOnBoard = (byte)nc.OnBoardNozzleNumber,
- SiteLevelNozzleId = nc.SiteLevelNozzleId,
- NozzleState = NozzleStateEnum.Unknown
- }).ToList());
- board.LastIncomingMessageReceivedTime = DateTime.Now;
- this.Boards.Add(board);
- });
- //DeviceId = deviceId;
- //HardwareIdentity = config.SerialNumber;
- // sample
- // <XmlConfiguration>
- // <Nozzle dataCollectorNozzleNumber = "1" siteLevelNozzleId="8" siteLevelDispenserId="#1号机">
- // <最大未变化次数>5</最大未变化次数>
- // <开始加油阀值>16777255</开始加油阀值>
- // <停止加油阀值>268435495</停止加油阀值>
- // <最小加油量>268435495</最小加油量>
- // <加油脉冲当量>268435456</加油脉冲当量>
- // <油气脉冲当量>1677721600</油气脉冲当量>
- // <气液比值>2013265924</气液比值>
- // </Nozzle>
- // <Nozzle dataCollectorNozzleNumber = "2" siteLevelNozzleId="9" siteLevelDispenserId="#1号机">
- // <最大未变化次数>5</最大未变化次数>
- // <开始加油阀值>16777255</开始加油阀值>
- // <停止加油阀值>268435495</停止加油阀值>
- // <最小加油量>268435495</最小加油量>
- // <加油脉冲当量>268435456</加油脉冲当量>
- // <油气脉冲当量>1677721600</油气脉冲当量>
- // <气液比值>2013265924</气液比值>
- // </Nozzle>
- // </XmlConfiguration>
- //this.DeviceAddress = (byte)bindingDataCollectorAddress;
- //var xmlSerializer = new XmlSerializer(typeof(XmlConfiguration));
- //this.configuration =
- // xmlSerializer.Deserialize(new StringReader(xmlConfig)) as XmlConfiguration;
- }
- public async override void Init(IContext<byte[], DataCollectorMessageBase> context)
- {
- base.Init(context);
- this.context = context;
- // may have many boards in underlying communicator(share one channel), so here dynamic set offline time but with a safe value for avoid too sensitive.
- this.singleBoardOfflineTimeThresholdByMs = 2200 * this.Boards.Count >= 6000 ? 2200 * this.Boards.Count : 6000;
- //accuracy is 1000ms
- this.singleBoardOfflineCheckTimer = new Timer(1000);
- this.singleBoardOfflineCheckTimer.Elapsed += async (a, b) =>
- {
- var offlineBoards = this.Boards.Where(b => DateTime.Now.Subtract(b.LastIncomingMessageReceivedTime).TotalMilliseconds >= this.singleBoardOfflineTimeThresholdByMs);
- foreach (var offBoard in offlineBoards)
- {
- if (offBoard.State == BoardStateEnum.UnInit || offBoard.State == BoardStateEnum.Initializing) continue;
- logger.LogInformation($"Board with serialNumber: {offBoard.SerialNumber}, PhyAddr: {offBoard.BoardPhysicalAddress} is offline({this.singleBoardOfflineTimeThresholdByMs}ms no see data), turn state to UnInit...");
- offBoard.State = BoardStateEnum.UnInit;
- this.FireOnBoardStateChangeEvent(offBoard);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarmIfNotExists(this.context.Processor,
- new GenericAlarm()
- {
- Title = $"lang-en-us:V/R board disconnectedlang-zh-cn:气液比采集板通讯断开lang-zh-tw:氣液比數據采集板通訊斷開",
- Category = $"lang-zh-cn:气液比采集板lang-zh-tw:氣液比采集板",
- Detail = $"lang-zh-cn:断开采集板的SN是:{offBoard.SerialNumber},上面配置的全站枪号是: " +
- $"{string.Join(", ", offBoard.Nozzles.Select(n => n.SiteLevelNozzleId.ToString()))}" +
- $"lang-zh-tw:斷開采集板的SN是:{offBoard.SerialNumber},上面配置的全站槍號是: " +
- $"{string.Join(", ", offBoard.Nozzles.Select(n => n.SiteLevelNozzleId.ToString()))}",
- Severity = GenericAlarmSeverity.Warning
- },
- ga => ga.Detail,
- ga => ga.Detail);
- }
- };
- this.singleBoardOfflineCheckTimer.Start();
- this.context.Incoming.OnLongTimeNoSeeMessage += async (_, __) =>
- {
- logger.LogInformation($"Boards group(serialNumbers are: {string.Join(" ,", this.Boards.Select(b => b.SerialNumber.ToString()))}, PhyAddr are: {string.Join(" ,", this.Boards.Select(b => b.BoardPhysicalAddress.ToString()))}) has lone time no see incoming message from underlying comm, switching all boards' state to UnInit.");
- this.Boards.ForEach(s =>
- {
- if (s.State != BoardStateEnum.UnInit)
- {
- s.State = BoardStateEnum.UnInit;
- this.FireOnBoardStateChangeEvent(s);
- }
- });
- var disconnectedBoardPhyAddressesStr = this.DeviceConfig.BoardConfigs?.Select(b => b.SetPhysicalAddress.ToString()).Aggregate("", (acc, n) => acc + ", " + n);
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.FirePersistGenericAlarmIfNotExists(context.Processor,
- new GenericAlarm()
- {
- Title = $"lang-en-us:Multiple V/R board disconnectedlang-zh-cn:多块气液比数据采集板通讯断开lang-zh-tw:多塊氣液比數據采集板通訊斷開",
- Category = $"lang-zh-cn:气液比采集板lang-zh-tw:氣液比采集板",
- Detail = $"lang-zh-cn:断开采集板的物理地址分别是:{disconnectedBoardPhyAddressesStr}" +
- $"lang-zh-tw:斷開采集板的物理地址分別是:{disconnectedBoardPhyAddressesStr}",
- Severity = GenericAlarmSeverity.Warning
- }, ga => ga.Detail,
- ga => ga.Detail);
- };
- this.context.Incoming.LongTimeNoSeeMessageTimeout = 18000;
- var timeWindowWithActivePollingOutgoing = this.context.Outgoing as TimeWindowWithActivePollingOutgoing<byte[], DataCollectorMessageBase>;
- int prePolledBoardIndex = 0;
- timeWindowWithActivePollingOutgoing.PollingMsgProducer = () =>
- {
- if (prePolledBoardIndex == this.Boards.Count) prePolledBoardIndex = 0;
- var targetBoard = this.Boards[prePolledBoardIndex];
- prePolledBoardIndex++;
- switch (targetBoard.State)
- {
- case BoardStateEnum.Initialized:
- {
- return new READ_WORKING_TYPE_Request()
- {
- Address = targetBoard.BoardPhysicalAddress
- };
- }
- case BoardStateEnum.UnInit:
- {
- //logger.LogInformation($"Board with SerialNumber: {targetBoard.SerialNumber }, PhysicalAddress: {targetBoard.BoardPhysicalAddress} is turning state from UnInit to Initializing...");
- targetBoard.State = BoardStateEnum.Initializing;
- this.FireOnBoardStateChangeEvent(targetBoard);
- Task.Run(() =>
- {
- try
- {
- this.InitializeDevice(targetBoard.SerialNumber, targetBoard.BoardPhysicalAddress).ContinueWith(async (pt) =>
- {
- if (pt.Result)
- {
- targetBoard.State = BoardStateEnum.Initialized;
- var universalApiHub = this.services.GetRequiredService<UniversalApiHub>();
- await universalApiHub.ClosePersistGenericAlarms(this.context.Processor, $"断开", "conn recovered");
- }
- else
- {
- targetBoard.State = BoardStateEnum.UnInit;
- }
- this.FireOnBoardStateChangeEvent(targetBoard);
- });
- }
- catch (Exception exxx)
- {
- logger.LogError($"InitializeDevice Board with SerialNumber: {targetBoard.SerialNumber} will set to PhyAddr: {targetBoard.BoardPhysicalAddress} exceptioned with: {exxx}");
- }
- });
- return null;
- }
- case BoardStateEnum.Initializing: { return null; }
- };
- return null;
- };
- context.Communicator.OnConnected += async (_, __) =>
- {
- //await Task.Delay(5000).ContinueWith((t) => this.InitializeDevice());
- };
- }
- /// <summary>
- /// send params and re-sync time from device.
- /// </summary>,
- /// <returns></returns>
- private async Task<bool> InitializeDevice(int boardSerialNumber, byte setBoardPhysicalAddress)
- {
- logger.LogInformation($"InitializeDevice ==========> Board with SerialNumber: {boardSerialNumber} will set to PhyAddr: {setBoardPhysicalAddress}...");
- var setAddressResponse = await context.Outgoing.WriteAsync(
- new SET_ADDRESS_Request(boardSerialNumber, setBoardPhysicalAddress),
- (_, testResponse) => testResponse is SET_ADDRESS_Response r && r.Address == 0
- && r.DeviceSerialNumber == boardSerialNumber && r.NewDeviceAddress == setBoardPhysicalAddress, 1000) as SET_ADDRESS_Response;
- if (setAddressResponse == null)
- {
- logger.LogError($" Board with SerialNumber: { boardSerialNumber} timedout to set to PhyAddr: { setBoardPhysicalAddress}");
- return false;
- }
- var b = this.Boards?.FirstOrDefault(b => b.BoardPhysicalAddress == setAddressResponse.NewDeviceAddress);
- if (b == null)
- {
- logger.LogInformation($" B PhyAddr: {setAddressResponse.NewDeviceAddress} does not defined via local configs");
- return false;
- }
- //logger.LogInformation($" Board with SerialNumber: {boardSerialNumber}, " +
- // $"Will read board side Params for its total {this.DeviceConfig.BoardConfigs.FirstOrDefault(bc => bc.SerialNumber == boardSerialNumber)?.NozzleConfigs?.Count() ?? 0} nozzles");
- foreach (var nozzleNo in
- this.DeviceConfig.BoardConfigs.FirstOrDefault(bc => bc.SerialNumber == boardSerialNumber)?.NozzleConfigs?.Select(nc => (byte)nc.OnBoardNozzleNumber))
- {
- //var readParaResponse = await context.Outgoing.WriteAsync(
- // new READ_PARA_Request(nozzleNo) { Address = setBoardPhysicalAddress },
- // (request, response) => response is READ_PARA_Response r && r.Address == setBoardPhysicalAddress, 5000) as READ_PARA_Response;
- //if (readParaResponse == null)
- //{
- // logger.LogInformation(" B PhyAddr: " + setBoardPhysicalAddress + ", board side Parameter read for nozzle " +
- // nozzleNo +
- // " timedout, will skip this nozzle and continue...");
- // continue;
- //}
- //logger.LogInformation(" B PhyAddr: " + setBoardPhysicalAddress + ", board side Parameter was read for nozzle " + readParaResponse.NozzleNumber
- // + ", detail: " + readParaResponse.ToLogString());
- var nozzleInitParameter = this.DeviceConfig.BoardConfigs.FirstOrDefault(bc => bc.SerialNumber == boardSerialNumber)
- ?.NozzleConfigs?.FirstOrDefault(nc => nc.OnBoardNozzleNumber == nozzleNo)?.ManualInitParameter;
- if (nozzleInitParameter == null)
- {
- logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, Will try use ExternalInitParameter to Write Para to board for nozzle: {nozzleNo}");
- nozzleInitParameter = this.DeviceConfig.BoardConfigs.FirstOrDefault(bc => bc.SerialNumber == boardSerialNumber)
- ?.NozzleConfigs?.FirstOrDefault(nc => nc.OnBoardNozzleNumber == nozzleNo)?.ExternalInitParameter;
- }
- else
- logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, Will use ManualInitParameter to Write para to board for nozzle: {nozzleNo}");
- if (nozzleInitParameter != null)
- {
- //logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, Will set new PARA to board for nozzle: {nozzleNo}");
- var writeParamsResponse = await context.Outgoing.WriteAsync(
- new WRITE_PARA_Request(nozzleNo)
- {
- Address = setBoardPhysicalAddress,
- 最大未变化次数 = nozzleInitParameter.最大未变化次数,
- 开始加油阀值 = nozzleInitParameter.开始加油阀值,
- 停止加油阀值 = nozzleInitParameter.停止加油阀值,
- 最小加油量 = nozzleInitParameter.最小加油量,
- 加油脉冲当量 = nozzleInitParameter.加油脉冲当量,
- 油气脉冲当量 = nozzleInitParameter.油气脉冲当量,
- 气液比值 = nozzleInitParameter.气液比值
- },
- (_, test) => test is WRITE_PARA_Response r && r.Address == setBoardPhysicalAddress, 2000);
- if (writeParamsResponse != null)
- logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, new Parameter was set successfully to nozzle {nozzleNo}");
- else
- logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, new Parameter was failed set to nozzle {nozzleNo}");
- }
- else
- logger.LogInformation($" B PhyAddr: {setBoardPhysicalAddress }, Skip set new PARA to board for nozzle: {nozzleNo} as Manual and External InitParmater are both null");
- }
- b.LastIncomingMessageReceivedTime = DateTime.Now;
- logger.LogInformation($" Board with SerialNumber: { boardSerialNumber} has successfully set to PhyAddr: { setBoardPhysicalAddress}");
- var syncTimeResult = await this.ReadAndWriteTimeIfMaxTimeGapReached(setBoardPhysicalAddress);
- if (syncTimeResult)
- {
- logger.LogInformation("B PhyAddr: " + setBoardPhysicalAddress + ", Sync time succeed, and init succeed!");
- return true;
- }
- else
- {
- logger.LogInformation("B PhyAddr: " + setBoardPhysicalAddress + ", Sync time failed, init failed");
- return false;
- }
- }
- [UniversalApi(Description = "获取VR board油枪的初始化参数值")]
- public async virtual Task<READ_PARA_Response> ReadBoardNozzleInitParameters(byte boardPhysicalAddress, byte onBoardNozzleNumber)
- {
- logger.LogInformation($"B PhyAddr: {boardPhysicalAddress }, Will ReadBoardNozzleInitParameters for nozzle { onBoardNozzleNumber}");
- var targetBoard = this.Boards.FirstOrDefault(b => b.BoardPhysicalAddress == boardPhysicalAddress && b.Nozzles.Any(n => n.NozzleNumberOnBoard == onBoardNozzleNumber));
- if (targetBoard == null)
- {
- logger.LogInformation($" B PhyAddr: {boardPhysicalAddress }, parameters were read failed for nozzle {onBoardNozzleNumber} due to target board or nozzel does not exists");
- return null;
- }
- var response = await this.context.Outgoing.WriteAsync(
- new READ_PARA_Request(onBoardNozzleNumber) { Address = boardPhysicalAddress },
- (_, test) => test is READ_PARA_Response r && r.Address == boardPhysicalAddress, 2000);
- if (response is READ_PARA_Response readParameterResponse && readParameterResponse != null)
- {
- logger.LogDebug($" B PhyAddr: {boardPhysicalAddress }, parameters were read successfully for nozzle {onBoardNozzleNumber}: {readParameterResponse.ToLogString()}");
- return readParameterResponse;
- }
- else
- {
- logger.LogInformation($" B PhyAddr: {boardPhysicalAddress }, parameters were read failed for nozzle {onBoardNozzleNumber}");
- return null;
- }
- }
- [UniversalApi(Description = "写入VR board油枪的初始化参数值")]
- public async virtual Task<bool> WriteBoardNozzleInitParameters(byte boardPhysicalAddress, byte onBoardNozzleNumber, BoardInitParameterConfigV1 parametersConfig)
- {
- logger.LogInformation($"B PhyAddr: {boardPhysicalAddress }, Will WriteBoardNozzleInitParameters for nozzle { onBoardNozzleNumber}: {parametersConfig.ToLogString()}");
- var targetBoard = this.Boards.FirstOrDefault(b => b.BoardPhysicalAddress == boardPhysicalAddress && b.Nozzles.Any(n => n.NozzleNumberOnBoard == onBoardNozzleNumber));
- if (targetBoard == null)
- {
- logger.LogInformation($" B PhyAddr: {boardPhysicalAddress }, write Parameters failed for nozzle {onBoardNozzleNumber} due to target board or nozzel does not exists");
- return false;
- }
- var response = await context.Outgoing.WriteAsync(
- new WRITE_PARA_Request(onBoardNozzleNumber)
- {
- Address = boardPhysicalAddress,
- 最大未变化次数 = parametersConfig.最大未变化次数,
- 开始加油阀值 = parametersConfig.开始加油阀值,
- 停止加油阀值 = parametersConfig.停止加油阀值,
- 最小加油量 = parametersConfig.最小加油量,
- 加油脉冲当量 = parametersConfig.加油脉冲当量,
- 油气脉冲当量 = parametersConfig.油气脉冲当量,
- 气液比值 = parametersConfig.气液比值
- },
- (_, test) => test is WRITE_PARA_Response r && r.Address == boardPhysicalAddress, 2000);
- if (response is WRITE_PARA_Response writeParameterResponse && writeParameterResponse != null)
- {
- logger.LogInformation($" B PhyAddr: {boardPhysicalAddress }, write Parameters successfully for nozzle {onBoardNozzleNumber}");
- return true;
- }
- else
- {
- logger.LogInformation($" B PhyAddr: {boardPhysicalAddress }, write Parameters failed for nozzle {onBoardNozzleNumber}");
- return false;
- }
- }
- private async Task<bool> ReadAndWriteTimeIfMaxTimeGapReached(byte boardPhysicalAddress)
- {
- logger.LogInformation(">>>>>B PhyAddr: " + boardPhysicalAddress + ", trying sync time with FCC==========");
- var readTimeResponse = await context.Outgoing.WriteAsync(
- new READ_TIME_Request() { Address = boardPhysicalAddress },
- (request, response) => response is READ_TIME_Response r && r.Address == boardPhysicalAddress, 5000) as READ_TIME_Response;
- if (readTimeResponse == null)
- {
- logger.LogInformation("B PhyAddr: " + boardPhysicalAddress + ", Time was read failed from DataCollector, init failed.");
- return false;
- }
- //this.lastDeviceMessageReceivedTime = DateTime.Now;
- logger.LogDebug("B PhyAddr: " + boardPhysicalAddress + ", Time was read from DataCollector board which is: " + readTimeResponse.CurrentTime.ToString("yyyy-MM-dd HH:mm:ss"));
- if (Math.Abs(readTimeResponse.CurrentTime.Subtract(DateTime.Now).TotalSeconds) >= maxTimeGapFromFcAndDeviceBySeconds)
- {
- logger.LogDebug("B PhyAddr: " + boardPhysicalAddress + ", there's " + maxTimeGapFromFcAndDeviceBySeconds +
- " seconds time gap detected between DataCollector and local FC, will start time sync...");
- var writeTimeResponse = await context.Outgoing.WriteAsync(
- new WRITE_TIME_Request(DateTime.Now) { Address = boardPhysicalAddress },
- (request0, response0) => response0 is WRITE_TIME_Response r && r.Address == boardPhysicalAddress,
- 5000);
- if (writeTimeResponse != null)
- {
- this.Boards.First(b => b.BoardPhysicalAddress == writeTimeResponse.Address).LastIncomingMessageReceivedTime = DateTime.Now;
- //this.lastDeviceMessageReceivedTime = DateTime.Now;
- logger.LogDebug("B PhyAddr: " + boardPhysicalAddress + ", Sync time succeed!");
- return true;
- }
- else
- {
- logger.LogInformation("B PhyAddr: " + boardPhysicalAddress + ", Sync time failed!");
- return false;
- }
- }
- else
- {
- logger.LogDebug("B PhyAddr: " + boardPhysicalAddress + ", No need sync time.");
- }
- return true;
- }
- private int onReadAndClearHistoryDataFromBoardGuard = 0;
- public async override Task Process(IContext<byte[], DataCollectorMessageBase> context)
- {
- if (!this.DeviceConfig.BoardConfigs.Any(bc => bc.SetPhysicalAddress == context.Incoming.Message.Address))
- throw new ArgumentException($"Incoming message from board with PhyAddr: {context.Incoming.Message.Address} does not has definition in this device handler config, may the FC side config wrong here?");
- this.Boards.First(b => b.BoardPhysicalAddress == context.Incoming.Message.Address).LastIncomingMessageReceivedTime = DateTime.Now;
- switch (context.Incoming.Message)
- {
- case READ_WORKING_AND_TYPE_Response realTimeStateAndDataResponse:
- if (realTimeStateAndDataResponse.NozzleStates?.Any() ?? false)
- {
- foreach (var newNozzleState in realTimeStateAndDataResponse.NozzleStates)
- {
- var nozzle =
- this.Boards.FirstOrDefault(b => b.BoardPhysicalAddress == realTimeStateAndDataResponse.Address)
- ?.Nozzles.FirstOrDefault(n => n.NozzleNumberOnBoard == newNozzleState.Key);
- if (nozzle == null)
- {
- logger.LogError($"Incoming a device msg from Board(on comm: {context.Communicator.Identity ?? ""}, with PhysicalAddress: {realTimeStateAndDataResponse.Address}) for board nozzle(with index:{newNozzleState.Key}) say its state changed to: {newNozzleState.Value}, but no correlated nozzle in FCC, may the config missing in FCC??");
- }
- else
- {
- if (nozzle.NozzleState != newNozzleState.Value)
- {
- logger.LogDebug($"B PhyAddr: { context.Incoming.Message.Address}, Nozzle: {nozzle.NozzleNumberOnBoard} is swtching state from: {nozzle.NozzleState} to: {newNozzleState.Value}");
- nozzle.NozzleState = newNozzleState.Value;
- this.FireOnBoardNozzleStateChangeEvent(nozzle);
- }
- }
- }
- this.FireOnDataReceivedEvent(realTimeStateAndDataResponse);
- }
- this.CurrentDataCollectorType = realTimeStateAndDataResponse.DataCollectorType;
- var withHistoryDataNozzles = realTimeStateAndDataResponse.NozzleHistoryDataStates.Where(h => h.Value == NozzleHistoryDataStateEnum.HasData);
- foreach (var n in withHistoryDataNozzles)
- {
- //context.Outgoing.Write(new READ_HISTORY_NUM_Request(n.Key) { Address = realTimeStateAndDataResponse.Address });
- if (0 == Interlocked.CompareExchange(ref this.onReadAndClearHistoryDataFromBoardGuard, 1, 0))
- {
- var nozzleNumber = n.Key;
- var targetBoard = this.Boards.FirstOrDefault(b => b.BoardPhysicalAddress == context.Incoming.Message.Address);
- try
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: { targetBoard.BoardPhysicalAddress}, Nozzle: {nozzleNumber} has data, will try ReadAndClear...");
- var result = await ReadAndClearHistoryDataFromBoard(context, targetBoard, nozzleNumber);
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: { targetBoard.BoardPhysicalAddress}, Nozzle: {nozzleNumber} ReadAndClearHistoryData overall result is: {result}");
- }
- finally
- {
- logger.LogDebug($"B PhyAddr: { targetBoard?.BoardPhysicalAddress ?? -1}, Nozzle: {nozzleNumber} released guard");
- this.onReadAndClearHistoryDataFromBoardGuard = 0;
- }
- }
- else
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: { context.Incoming.Message.Address}, Nozzle: {n.Key} skip this round of ReadAndClear as prev still running, will try later...");
- break;
- }
- }
- //this.OnDataRecieved?.Invoke(this, new DataRecievedEventArgs(realTimeStateAndDataResponse));
- break;
- //case READ_HISTORY_NUM_Response historyNumResponse:
- // context.Outgoing.Write(
- // new READ_ONE_HISTORY_DATA_Request(historyNumResponse.NozzleHistoryUnreadRecordCount.Key) { Address = historyNumResponse.Address });
- // break;
- //case READ_ONE_HISTORY_DATA_Response historyDataResponse:
- // logger.LogDebug("B PhyAddr: " + historyDataResponse.Address + ", Read a history data from nozzle " + historyDataResponse.NozzleNumber
- // + ", LiquidVolume " + historyDataResponse.LiquidVolume
- // + ", AirVolume " + historyDataResponse.AirVolume
- // + " which trx started from " + historyDataResponse.FuellingStartTime.ToString("yyyy-MM-dd HH:mm:ss")
- // + " to " + historyDataResponse.FuellingEndTime.ToString("yyyy-MM-dd HH:mm:ss"));
- // var clearResult = await ClearHistory(context, historyDataResponse);
- // if (clearResult) this.FireOnDataReceivedEvent(historyDataResponse);
- // break;
- }
- }
- private async Task<bool> ReadAndClearHistoryDataFromBoard(IContext<byte[], DataCollectorMessageBase> context, Board board, byte onBoardNozzleNumber)
- {
- var readHistoryNumResponse = await context.Outgoing.WriteAsync(
- new READ_HISTORY_NUM_Request(onBoardNozzleNumber) { Address = board.BoardPhysicalAddress },
- (r, testResponse) => testResponse is READ_HISTORY_NUM_Response rp && rp.Address == board.BoardPhysicalAddress && rp.NozzleHistoryUnreadRecordCount.Key == onBoardNozzleNumber,
- 1500) as READ_HISTORY_NUM_Response;
- if (readHistoryNumResponse == null)
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: {board.BoardPhysicalAddress}, Nozzle: {onBoardNozzleNumber} timedout for READ_HISTORY_NUM_Request");
- return false;
- }
- if (readHistoryNumResponse.NozzleHistoryUnreadRecordCount.Value < 1)
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: {board.BoardPhysicalAddress}, Nozzle: {onBoardNozzleNumber} indicates no un-read data?!");
- return false;
- }
- int retryingTimes = 0;
- retryReadData:
- var operationResponse = await context.Outgoing.WriteAsync(
- new READ_ONE_HISTORY_DATA_Request(onBoardNozzleNumber) { Address = board.BoardPhysicalAddress },
- (r, testResponse) => (testResponse is READ_ONE_HISTORY_DATA_Response rp && rp.Address == board.BoardPhysicalAddress && rp.NozzleNumber == onBoardNozzleNumber)
- || (testResponse is COLLECTOR_BUSY_Response bRp && bRp.Address == board.BoardPhysicalAddress),
- 1500);
- if (operationResponse == null)
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: {board.BoardPhysicalAddress}, Nozzle: {onBoardNozzleNumber} timedout for READ_ONE_HISTORY_DATA_Request");
- return false;
- }
- else if (operationResponse is COLLECTOR_BUSY_Response)
- {
- //0 indicates disabled for retry.
- int maxRetryTimes = 0;
- retryingTimes++;
- if (retryingTimes > maxRetryTimes)
- {
- logger.LogInformation($"B PhyAddr: {board.BoardPhysicalAddress}, Nozzle: {onBoardNozzleNumber} report busy and deny for READ_ONE_HISTORY_DATA_Request");
- return false;
- }
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: {board.BoardPhysicalAddress}, Nozzle: {onBoardNozzleNumber} Retrying for READ_ONE_HISTORY_DATA_Request...");
- goto retryReadData;
- }
- var historyDataResponse = operationResponse as READ_ONE_HISTORY_DATA_Response;
- logger.LogDebug($"B PhyAddr: { historyDataResponse.Address }, Nozzle {historyDataResponse.NozzleNumber} Read a history data"
- + $", LiquidVolume: {historyDataResponse.LiquidVolume}, AirVolume: { historyDataResponse.AirVolume}"
- + $", trx started from { historyDataResponse.FuellingStartTime.ToString("yyyy-MM-dd HH:mm:ss")}"
- + $" to { historyDataResponse.FuellingEndTime.ToString("yyyy-MM-dd HH:mm:ss")}, will clear...");
- var clearHistoryResponse = await context.Outgoing.WriteAsync(
- new CLEAR_ONE_HISTORY_Request(historyDataResponse.NozzleNumber)
- { Address = historyDataResponse.Address },
- (request, response) => response is CLEAR_ONE_HISTORY_Response r && r.Address == historyDataResponse.Address, 1500) as CLEAR_ONE_HISTORY_Response;
- if (clearHistoryResponse != null)
- {
- if (logger.IsEnabled(LogLevel.Debug))
- logger.LogDebug($"B PhyAddr: {historyDataResponse.Address}, Nozzle: {historyDataResponse.NozzleNumber} One history has been cleared");
- this.FireOnDataReceivedEvent(historyDataResponse);
- return true;
- }
- else
- {
- logger.LogInformation($"B PhyAddr: { historyDataResponse.Address }, Nozzle: {historyDataResponse.NozzleNumber} Previous clear One history request failed");
- return false;
- }
- }
- [UniversalApi(Description = "Get state for all boards under this comm channel")]
- public Task<List<Board>> GetBoardStates()
- {
- return Task.FromResult(this.Boards);
- }
- [UniversalApi(Description = "Send a query right now to all boards under this comm channel, and return the datas, if read timedout for certain board, the specific data will not be returned.")]
- public async Task<IEnumerable<READ_WORKING_AND_TYPE_Response>> ReadRealTimeDatas()
- {
- List<READ_WORKING_AND_TYPE_Response> results = new List<READ_WORKING_AND_TYPE_Response>();
- var addresses = this.DeviceConfig.BoardConfigs.Select(bc => bc.SetPhysicalAddress).ToList();
- foreach (var adrs in addresses)
- {
- var rsp = await this.context.Outgoing.WriteAsync(
- new READ_WORKING_TYPE_Request()
- {
- Address = adrs
- },
- (request, testingResponse) => testingResponse is READ_WORKING_AND_TYPE_Response p && p.Address == adrs,
- 4000) as READ_WORKING_AND_TYPE_Response;
- if (rsp != null)
- results.Add(rsp);
- }
- return results;
- }
- public IEnumerator<Board> GetEnumerator()
- {
- return this.Boards.GetEnumerator();
- }
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return this.Boards.GetEnumerator();
- }
- public virtual void Dispose()
- {
- this.singleBoardOfflineCheckTimer?.Dispose();
- foreach (var d in this.OnBoardStateChange?.GetInvocationList() ?? new Delegate[] { })
- {
- this.OnBoardStateChange -= d as EventHandler<BoardStateChangeEventArgs>;
- }
- foreach (var d in this.OnBoardNozzleStateChange?.GetInvocationList() ?? new Delegate[] { })
- {
- this.OnBoardNozzleStateChange -= d as EventHandler<BoardNozzleStateChangeEventArgs>;
- }
- foreach (var d in this.OnDataRecieved?.GetInvocationList() ?? new Delegate[] { })
- {
- this.OnDataRecieved -= d as EventHandler<DataRecievedEventArgs>;
- }
- }
- }
- public enum BoardStateEnum
- {
- /// <summary>
- /// first time eastalized the connection with FC, or disconnected from a established connection.
- /// </summary>
- UnInit,
- /// <summary>
- /// processing and not ready for other command.
- /// </summary>
- Initializing,
- Initialized,
- }
- public class BoardNozzle
- {
- public byte NozzleNumberOnBoard { get; set; }
- /// <summary>
- /// each vr watching nozzle is a physical pump nozzle, here is the bind info, will be used to draw UI.
- /// </summary>
- public int SiteLevelNozzleId { get; set; }
- public NozzleStateEnum NozzleState { get; set; }
- }
- public class Board
- {
- public int SerialNumber { get; }
- public byte BoardPhysicalAddress { get; }
- public IEnumerable<BoardNozzle> Nozzles { get; }
- public BoardStateEnum State { get; set; }
- /// <summary>
- /// used for track the last received message which for caculate the offline state of this device.
- /// </summary>
- public DateTime LastIncomingMessageReceivedTime { get; set; }
- internal Board(int serialNumber, byte boardPhysicalAddress, BoardStateEnum state, IEnumerable<BoardNozzle> nozzles)
- {
- this.SerialNumber = serialNumber;
- this.BoardPhysicalAddress = boardPhysicalAddress;
- this.State = state;
- this.Nozzles = nozzles;
- }
- }
- }
|