using System; using System.Collections.Generic; using System.Linq; using DTS.DASLib.Command.SLICE; using DTS.Common.DAS.Concepts; using DTS.DASLib.Command; using System.Threading; using DTS.Common; using DTS.Common.ICommunication; using DTS.Common.Utilities.Logging; using DTS.DASLib.Command.SLICE.DownloadCommands; using System.IO; using DTS.DASLib.Command.SLICE.RealtimeCommands; using DTS.Common.DAS.Concepts.DAS.Channel; using DTS.Common.Enums.Sensors; using DTS.Common.Interface.Connection; using DTS.Common.Interface.DASFactory.Diagnostics; using DTS.Common.Enums.DASFactory; using DTS.Common.Interface.DASFactory; using DTS.DASLib.Service.Interfaces; using DTS.Common.Constant.DASSpecific; using DTS.Common.Enums.Hardware; using DTS.DASLib.Service.Classes; using Prism.Ioc; using DTS.Common.Events; using Prism.Events; namespace DTS.DASLib.Service { public class SLICE6 : SLICE6_Base where T : IConnection, new() { } public class SLICE6_Base : SLICE2_Base, IClockSyncActions, ITiltSensorCalAware, ITMATSStreamingDevice where T : IConnection, new() { internal class SetUARTSettingsAsyncInfo : SliceServiceAsyncInfo { public uint BaudRate { get; set; } public uint DataBits { get; set; } public uint StopBits { get; set; } public uint Parity { get; set; } public uint FlowControl { get; set; } public SetUARTSettingsAsyncInfo(ServiceCallback callback, object userData, uint baudRate, uint dataBits, uint stopBits, uint parity, uint flowControl) : base(callback, userData) { BaudRate = baudRate; DataBits = dataBits; StopBits = stopBits; Parity = parity; FlowControl = flowControl; } } public virtual int GetMaxFileLengthTMATS() { return InformationCommands.MAX_FILE_LENGTH_ID100; } protected override void RestoreOriginalStartRecordDelay() { //DO NOT do this for S6 and beyond, the attribute is used differently, see //http://manuscript.dts.local/f/cases/43048/ } protected override void StoreOriginalStartRecordDelayAndClear() { //DO NOT do this for S6 and beyond, the attribute is used differently, see //http://manuscript.dts.local/f/cases/43048/ } protected override bool AdjustInputRange(AnalogInputDASChannel analog) { return false; } /// /// returns whether the device supports start completion inversion or not /// /// /// true if the device supports start inversion, false otherwise public override bool SupportsStartInversion() => HardwareConstants.SupportsStartInversion(GetHardwareType(), ProtocolVersion); /// /// returns whether the device supports trigger completion inversion or not /// /// /// true if the device supports trigger inversion, false otherwise public override bool SupportsTriggerInversion() => HardwareConstants.SupportsTriggerInversion(GetHardwareType(), ProtocolVersion); public double[] TiltSensorCals { get; protected set; } /// /// indicates whether the DAS supports streaming /// 10572 implement SW side for single command streaming real time /// (probably needs to check protocol of devices ... but I don't have info on protocol currently) /// public override bool SupportsUDPRealtimeStreaming { get { return ProtocolVersion >= GetMinProto(DFConstantsAndEnums.ProtocolLimitedCommands.UDPRealtimeStream); } } protected override void ResetEventListPriorToArm() { //12638 DAS does not record data in recorder mode during calibration ~ 40% of time. //reseteventlist only gets called before configuring with SLICE6 } protected void ResetEventListPriorToConfigure() { var resetEvents = new ResetEventList(this); resetEvents.SyncExecute(); } public override void Download(ServiceCallback callback, object userData) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.DownloadStreaming)) { DownloadStream(callback, userData); } else { base.Download(callback, userData); } } private void DownloadStream(ServiceCallback callback, object userData) { var state = new SliceDownloadState(callback, userData, WhatToDownload); try { var start = new StartDownloadStreamData(this) { FirstSample = WhatToDownload.StartSample, EventNumber = WhatToDownload.EventNumber, LastSample = WhatToDownload.EndSample }; APILogger.Log("Starting stream ", WhatToDownload.StartSample, WhatToDownload.EndSample); start.SyncExecute(); int samplesToExtract; ulong totalDownloaded = 0; var getDownloadData = new GetNextDownloadStreamDataSamples(this); var needed = WhatToDownload.EndSample - WhatToDownload.StartSample; needed -= 1248; var queue = new Queue(); while (totalDownloaded < needed) { getDownloadData.SyncExecute(); getDownloadData.ProcessData(); if (getDownloadData.DlData?.DlData == null) { Thread.Sleep(10); continue; } foreach (var sample in getDownloadData.DlData.DlData) { queue.Enqueue(sample); } samplesToExtract = Convert.ToInt32(Math.Floor(queue.Count / 6D)); if (samplesToExtract > 0) { var newData = new short[6][]; for (var i = 0; i < 6; i++) { newData[i] = new short[samplesToExtract]; } for (var sampleIndex = 0; sampleIndex < samplesToExtract; sampleIndex++) { for (var channelIndex = 0; channelIndex < 6; channelIndex++) { var unsignedValue = queue.Dequeue(); var adc = (short)((((unsignedValue & 0x00FF) << 8) | ((unsignedValue >> 8) & 0x00FF)) + 0x8000); newData[channelIndex][sampleIndex] = adc; } } state.NewData(newData, 0, ulong.MinValue, ulong.MinValue); } var ratio = 100D * totalDownloaded / needed; if (ratio < 0) { ratio = 0; } else if (ratio > 100) { ratio = 100D; } state.Progress(Convert.ToInt32(ratio)); totalDownloaded += (ulong)getDownloadData.DlData.DlData.Length; } samplesToExtract = Convert.ToInt32(Math.Floor(queue.Count / 6D)); if (samplesToExtract > 0) { var newData = new short[6][]; for (var i = 0; i < 6; i++) { newData[i] = new short[samplesToExtract]; } for (var sampleIndex = 0; sampleIndex < samplesToExtract; sampleIndex++) { for (var channelIndex = 0; channelIndex < 6; channelIndex++) { var unsignedValue = queue.Dequeue(); var adc = (short)((((unsignedValue & 0x00FF) << 8) | ((unsignedValue >> 8) & 0x00FF)) + 0x8000); newData[channelIndex][sampleIndex] = adc; } } state.NewData(newData, 0, ulong.MinValue, ulong.MinValue); } state.Success(); } catch (Exception ex) { APILogger.Log(ex); state.Error(ex.Message); } } /// /// 10826 Parent Case for missing Data in SLICE 6 Downloads /// a SLICE6 will ALWAYS have 6 channels, so warn in the logs we have a different value, but /// stick with the 6... /// /// /// protected override uint GetEventTotalChannels(int eventNum) { try { var eventTC = new QueryEventAttribute(this); eventTC.EventNumber = (ushort)eventNum; eventTC.Key = AttributeTypes.ArmAndEventAttributes.TotalChannels; eventTC.SyncExecute(); var value = Convert.ToUInt32(eventTC.Value); if (value != 6) { APILogger.Log($"Invalid number of channels ({value}) for unit {SerialNumber} - ignoring"); } } catch (Exception ex) { APILogger.Log("failed to get event total channels: ", ex); } return 0x06; } /// /// gets the expected excitation for a channel /// note in SLICE6 theres only one REAL module, although there is 2 in DataPRO /// this is for consistency of the API with SLICE1 /// also note we have different attributes for SLICE6 and only support 5V excitation /// returns 0 if excitation could not be retrieved, otherwise excitation in mV /// /// /// /// protected override double GetExpectedExcitationMV(int moduleIndex, int channelOnModule) { //convert from channel ABC, ABC to ABCDEF [only 1x6 arrangement on base 2x3 in datapro] if (1 == moduleIndex) { channelOnModule += 3; } if (ConfigData?.Modules == null || ConfigData.Modules.Length <= moduleIndex) { APILogger.Log("unabled to get expected excitation, no config data to base excitation on"); return 0D; } if (!(ConfigData.Modules[moduleIndex].Channels[channelOnModule] is AnalogInputDASChannel aic)) { //we don't expect to ever get here [SLICE6 should only have analog channels] but if we do, don't crash APILogger.Log( "unable to get expected excitation, channel has no excitation information (is not analog)"); return 0D; } var excitation = Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(aic.Excitation) * 1000D; try { var qsa = new QuerySystemAttribute_BridgeSlice6(this); switch (channelOnModule) { case 0: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChA_5V; break; case 1: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChB_5V; break; case 2: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChC_5V; break; case 3: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChD_5V; break; case 4: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChE_5V; break; default: qsa.Key = AttributeTypes.SystemAttributes_BridgeSlice6.FactoryCal_ChF_5V; break; } qsa.DeviceID = 0x01; //per Loc we can always use 1 with slice6 qsa.SyncExecute(); var bridgeExcitation = Convert.ToDouble(qsa.Value);//these appear to already by in mV, no conversion needed... var delta = Math.Abs(excitation - bridgeExcitation); //if this one isn't with 500mV of target, it's likely invalid too ... don't use it if (delta < 500) { excitation = bridgeExcitation; } } catch (Exception ex) { APILogger.Log("failed to get excitation: ", ex); } return excitation; } public override bool CheckAAF(float rate) { return true; } public override bool SupportsTimeSynchronization => true; public override double[] GetNominalRanges(SensorConstants.BridgeType bridgeType) { //FB15462 separate S6 gain limits from S6A switch (bridgeType) { case SensorConstants.BridgeType.IEPE: return WinUSBSlice.StaticDASS6EIEPEInfo.NominalRanges; default: return WinUSBSlice.StaticDASBridgeInfo.NominalRanges; } } /// /// Convert gain code to value based on Slice 1 conversion table /// /// /// protected override double GainCodeToGainValue(ushort gainCode) { //Run the same code as Slice 1.0, not base:GainCodeToGainValue which is SLICE 2 var gainValueString = ((GainCodes)gainCode).ToString(); if (!double.TryParse(gainValueString.ToString().TrimStart('G'), out double gainValue)) { gainValue = 1.0D; } return gainValue; } private readonly Dictionary SLICE6_MinimumProtocols = new Dictionary(); protected override bool SupportsIEPECalSignal => IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.IEPE); protected virtual int MIN_PROTOCOL_TMATS_INTERVAL => int.MaxValue; public override void InitMinProto() { // SLICE 6.0 Protocol Limitations SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.MultipleAndHybridEvents] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.MultipleEvents] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.AutoArm] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.AutoArmRepeatEnable] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.SetDefaultMIF] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.FileData] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.StackSensors] = SLICE6.STACK_SENSORS; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.BaseSystemTime] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.TestCommunication] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.StackLowPowerMode] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.SetRealtimeSampleRate] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.SLICE2_OneWireID] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.HardwareRevision] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.HardwareConfiguration] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.EventFaultFlags] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.EventArmAttempts] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.QueryActualSampleRateImmediate] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.InitHardwareInputLines] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.VoltageSysAttributes] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.LevelTrigger] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.AttributeStoreBlocks] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.QueryArmAndTriggerStatus_VoltageReadings] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.MaxEvents] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.AutoArmDiagnosticDelay] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.StackChannelAutoArmDiagLevel] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.FlashClear] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.MultipleSamplesRealtime] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.BaseCalibrationDate] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.IgnoreShortedStartEvent] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.ResetAttributeStore] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.DiangosShuntDAC] = SLICE6.DIAGNOS_SHUNT_DAC; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.VoltageInsertion] = SLICE6.DIAGNOS_SHUNT_DAC; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.PTPTimestamp] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.StartRecDelayInSecond] = SLICE6.START_REC_DELAY_IN_SECONDS; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.QueryTiltSensorData] = SLICE6.START_REC_DELAY_IN_SECONDS; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.InSliceTiltSensorADCPre] = SLICE6.IN_SLICE_TILT_SENSOR_ADC_PRE; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.StartRealtimeStream] = SLICE6.START_REALTIME_STREAM; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.UDPRealtimeStream] = SLICE6.UDP_REALTIME_STREAM; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.GenerateEvent] = 18; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.PTPSyncStatus] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.SetClockSyncConfig] = SLICE6.MIN_PROTOCOL_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.PTPDomainID] = SLICE6.PTP_DOMAIN_ID_VER; SLICE6_MinimumProtocols[DFConstantsAndEnums.ProtocolLimitedCommands.ActiveRAM] = SLICE6.MIN_PROTOCOL_VER; MinimumProtocols = SLICE6_MinimumProtocols; } protected override void AsyncBeginFlashErase(object asyncInfo) { //14042 Flash Clear turns of excitation for s6 //we have to cache the sample average for now, but in the future we can not do this check try { var measureOffset = new RetrieveSampleAverage(this); measureOffset.DeviceID = 0; // send to base var avgTimeSeconds = 1.0 / 60.0 * 2.0; // 2 * 60Hz cycles measureOffset.Samples = (ushort)(10000 * avgTimeSeconds);//should use sample rate here? if (measureOffset.Samples < 1) { measureOffset.Samples = 1; } measureOffset.SyncExecute(); foreach (var m in ConfigData.Modules) { foreach (var ch in m.Channels) { if (ch is AnalogInputDASChannel) { if (!(ch is ILevelTriggerable triggerable)) { continue; } triggerable.SampleAverageADC = null; var aCh = ch as AnalogInputDASChannel; if (aCh.LevelTriggerType == LevelTriggerTypes.NONE || aCh.ConfigurationMode != DFConstantsAndEnums.ConfigMode.Normal) continue; try { var ADC = measureOffset.GetChannelData(aCh.Number); triggerable.SampleAverageADC = ADC; } catch (Exception ex) { APILogger.Log(ex); } } } } } catch (Exception ex) { APILogger.Log(ex); } base.AsyncBeginFlashErase(asyncInfo); } /// /// complete any work we need to do before starting trigger check. /// 18736 in S6, clear the LT cache too so we don't use the old values when checking /// protected override void AsyncPreStartTriggerCheck(object asyncInfo) { base.AsyncPreStartTriggerCheck(asyncInfo); TriggerCheckService.ClearLevelTriggerCache(ConfigData); } protected override ConfigAttributes GetConfigAttributes(DTS.Common.Interface.DASFactory.ICommunication com) { return new SLICE6ConfigAttributes(com); } /// /// SLICE6 config attributes, mostly inherits from SLICE.ConfigAttributes with some functionality removed /// protected class SLICE6ConfigAttributes : SLICE2ConfigAttributes { public override void ConfigureCoupling(bool[] IsACCoupledArray) { } /// /// I'm not aware of a purgeStaleData function in SLICE6 yet /// this may be unnecessary if the firmware is intelligent enough, and maybe this is legacy /// for now I just hollow it out /// /// public override void PurgeStaleData(IDASCommunication das) { } public SLICE6ConfigAttributes(DTS.Common.Interface.DASFactory.ICommunication _com) : base(_com) { } } public override int GetDASDisplayOrder() { return -1; } public override void SetDASDisplayOrder(int order) { } public override int[] GetChannelDisplayOrder() { return new[] { -1 }; } public override void SetChannelDisplayOrder(int[] order) { } /// /// QueryEventData also is customized for SLICE6, it needs to perform SLICE6 specific data marshalling /// /// protected override QueryEventDataBase GetQueryEventData() { return new QueryEventData_SLICE6(this, QueryEventData_SLICE6.Default_IO_Timeout); } protected virtual void ConfigureTMATS(SliceConfigServiceAsyncInfo info, float[] scaleFactors, float[] ranges, float[] measuredOffset) { if (ShouldWriteStreamInfo() && null != ChannelDiagnosticsResults && ChannelDiagnosticsResults.Any()) { try { var outMod = Array.Find(ConfigData.Modules, m => m.ModuleType() == DFConstantsAndEnums.ModuleType.StreamOut); //FB 30035 Refactored to use strategy patten based on profile type ITmtFile slice6AirTmtFile = null != outMod ? TmtFile.GetS6ATMATSFileTypeForProfile(outMod.StreamProfile, SerialNumber, DASInfo, ConfigData, ChannelDiagnosticsResults) : new Slice6AirAnalogTmtFile(SerialNumber, DASInfo, ConfigData, ChannelDiagnosticsResults); //FB 25526 var uartLookup = new Dictionary(); slice6AirTmtFile.WriteTmtFile(this, info.DataChannelIds, info.TimeChannelIds, uartLookup, DASIndex, this); } catch (ArgumentOutOfRangeException ex) { var eventAggregator = ContainerLocator.Container.Resolve(); var msg = $"Failed to write TMT file for: {SerialNumber}\r\nReduce channel name lengths or TMT template size"; eventAggregator.GetEvent().Publish(new PageErrorArg(new[] { msg }, null)); throw new ArgumentOutOfRangeException(msg, ex); } catch (Exception ex) { var eventAggregator = ContainerLocator.Container.Resolve(); var msg = $"Failed to write TMT file for: {SerialNumber}\r\n{ex.Message}"; eventAggregator.GetEvent().Publish(new PageErrorArg(new[] { msg }, null)); throw new Exception(ex.Message, ex); } SetTMATSInterval(info.TMATSIntervalMs); } } /// /// the order of this DAS among multiple das /// public int DASIndex { get; set; } = -1; protected override void ConfigureStreaming(SliceConfigServiceAsyncInfo info, float[] scaleFactors, float[] ranges, float[] measuredOffset) { //14531 Implement TMATS support for S6A stream on boot ConfigureTMATS(info, scaleFactors, ranges, measuredOffset); if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.SetDSPFilterSettings)) { // Implement DSP IIR/FIR Profile API http://manuscript.dts.local/f/cases/17913/Implement-DSP-IIR-FIR-Profile-API try { uint[] filterConfig = new uint[6]; uint dspFilterType = (uint)(info.DSPFilterType?.EnumValue ?? 0); // ALL of these values are fixed. They are intended for FW tuning only. uint overSampleRate = 80000; uint oversampleSCFClock = 8000; uint recordWhileStreamConfig = 0; // Reserved as of 1/22/2021 uint filterConfigStage2 = 0; uint overSampleRateStage2 = 20000; filterConfig[0] = dspFilterType; filterConfig[1] = overSampleRate; filterConfig[2] = oversampleSCFClock; filterConfig[3] = recordWhileStreamConfig; filterConfig[4] = filterConfigStage2; filterConfig[5] = overSampleRateStage2; var filterSet = new SetSystemAttributeSLICE6AIR(this); filterSet.SetValue(AttributeTypes.SystemAttributesSLICE6AIR.DspFilterAndStreamWhileRecordConfig, filterConfig, true); filterSet.SyncExecute(); var s6ADSPFilterFilename = string.Empty; if (!string.IsNullOrEmpty(s6ADSPFilterFilename) && File.Exists(s6ADSPFilterFilename)) { byte[] ByteArrayData = System.IO.File.ReadAllBytes(s6ADSPFilterFilename); // new char[maxLen]; var maxLen = ByteArrayData.Length; SetFileData sfd = new SetFileData(this, 600000) { StartByteCount = 0, FileID = Constants.FILE_STORE_DSP_FILTER_ID, Data = Constants.XML_STORE_MAGIC_BYTES }; sfd.SyncExecute(); //Store Header - data length sfd.StartByteCount = (Constants.XML_HEADER_LENGTH / 2); sfd.FileID = Constants.FILE_STORE_DSP_FILTER_ID; sfd.Data = BitConverter.GetBytes((uint)maxLen); sfd.SyncExecute(); //Store Data for (uint i = 0; i < maxLen; i += (uint)sfd.MaximumFileStreamBytes) { long array_size = sfd.MaximumFileStreamBytes; if ((i + sfd.MaximumFileStreamBytes) > maxLen) { array_size = maxLen - i; } byte[] dataToSend = new byte[array_size]; Array.Copy(ByteArrayData, i, dataToSend, 0, array_size); sfd.Data = dataToSend; sfd.FileID = Constants.FILE_STORE_DSP_FILTER_ID; sfd.StartByteCount = i + Constants.XML_HEADER_LENGTH; sfd.SyncExecute(); } } } catch { // doing nothing. exit. APILogger.Log("Realtime stream DSP Filtering feature is not supported in this device."); } } } /// /// we can probably simplify and take common items (slice6+slice1) out of this function, but for now /// it's mostly a copy of SLICE1.AsyncConfigure /// /// protected override void AsyncConfigure(object configAsyncInfo) { var info = configAsyncInfo as SliceConfigServiceAsyncInfo; IncrementNumberOfTimesWritten(); //12638 DAS does not record data in recorder mode during calibration ~ 40% of time. //for SLICE6 we call reseteventlist here, prior to configuring and NOT before arming ResetEventListPriorToConfigure(); int progressValue = 0; bool bReleased = true; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.ProgramStackChannels)) { ReconfigureAccordingToConfig(); } PresetSampleRate(); SetVoltageRequirements(); SetPolarity(); SetArmDisableShortCheck(); try { Lock(); bReleased = false; // loop thru the modules (slices) and configure the channels var numChannels = DASInfo.Modules.Sum(mod => mod.NumberOfChannels); var rangeArray = new float[numChannels]; //var IsHalfBridgeArray = new bool[numChannels]; var bridgeModeArray = new byte[numChannels]; var BridgeResistanceArray = new ushort[numChannels]; var IsACCoupledArray = new bool[numChannels]; // level trigger values var enableLowerLevelTriggerThreshold = new bool[numChannels]; var enableUpperLevelTriggerThreshold = new bool[numChannels]; var lowerLevelTriggerThreshold = new float[numChannels]; var upperLevelTriggerThreshold = new float[numChannels]; var qualificationSamples = new int[numChannels]; var bridgeACCouplingArray = new bool[numChannels]; var diagnosticChannels = new List(); var bModified = false; CommonConfigureWork(diagnosticChannels, qualificationSamples, ref bReleased, info, bridgeModeArray, IsACCoupledArray, BridgeResistanceArray, ref bModified, rangeArray, enableUpperLevelTriggerThreshold, upperLevelTriggerThreshold, enableLowerLevelTriggerThreshold, lowerLevelTriggerThreshold, bridgeACCouplingArray); if (bReleased) { return; } // report progress progressValue = 5; info.Progress(progressValue); StoreConfigAttributes(info, rangeArray, ref bReleased, ref progressValue, bridgeModeArray, IsACCoupledArray, BridgeResistanceArray, enableLowerLevelTriggerThreshold, lowerLevelTriggerThreshold, enableUpperLevelTriggerThreshold, upperLevelTriggerThreshold, qualificationSamples, numChannels, out var config, bridgeACCouplingArray, 0, 0); if (bReleased) { return; } RemainingConfigWork(ref progressValue, info, diagnosticChannels, config, ref bReleased, null, null, null); } catch (CanceledException) { if (!bReleased) { bReleased = true; Release(); } info.Cancel(); } catch (Exception ex) { if (!bReleased) { bReleased = true; Release(); } info.Error(ex.Message, ex); } finally { if (!bReleased) { bReleased = true; Release(); } } info.Progress(100); info.Success(); } // SLICE6 doesn't have a battery, don't make the measurement ... protected override double MeasureBackupMilliVolts() { return double.NaN; } protected override bool SupportsDiagnosticsMode => false; protected override void PerformVoltageInsertionCheck(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.VoltageInsertion)) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => ShouldPerformVoltageInsertionCheck(a)); if (NumToMeasure == 0) return; var queryChannelInsertResults = new QueryVoltageInsertaionResult_SLICE2(this); queryChannelInsertResults.DeviceID = 0; // send to base var insertionChannelList = new byte[NumToMeasure]; var channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { var actions = ChannelActions[idx]; if (ShouldPerformVoltageInsertionCheck(actions)) { insertionChannelList[channelCounter] = (byte)actions.DASChannelNumber; channelCounter++; } } queryChannelInsertResults.StackChannelList = insertionChannelList; try { queryChannelInsertResults.SyncExecute(); channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { var actions = ChannelActions[idx]; if (ShouldPerformVoltageInsertionCheck(actions)) { results[idx].MeasuredGain = queryChannelInsertResults.ActualGain[channelCounter]; results[idx].TargetGain = queryChannelInsertResults.ExpectedGain[channelCounter]; channelCounter++; } } } catch (Exception ex) { APILogger.Log("Failed to perform voltageinsertion check", ex); // If the functionality doesn't exist, just make sure the results are set to null // and the app will do the right thing. // ClearVoltageInsertionResults(ChannelActions, results); } } else { ClearVoltageInsertionResults(ChannelActions, results); } } /// /// hardcoded constants right now ... maybe these belong in attributes in the firmware! /// protected override uint MaxAAFilterRateHz => SLICE6.MaxAAFilterRateHz; protected override uint MaxSampleRateHz => 400000; /// /// calculates the max sample rate /// these are not exact max sample rates, but convenient close enough limits /// drop 100k every module after 3 (starting at 500k) /// /// public override uint MaxSampleRate(int numberOfConfiguredChannels) { return MaxSampleRateHz; } public override uint MaxAAFilterRate() { return MaxAAFilterRateHz; } protected override DASModule MakeConfigModuleFromInfoModule(InfoResult.Module infoModule) { var configModule = new DASModule(infoModule.ModuleArrayIndex, this); configModule.Channels = new AnalogInputDASChannel[infoModule.NumberOfChannels]; for (var i = 0; i < infoModule.NumberOfChannels; i++) { var channel = new AnalogInputDASChannel(configModule, i); if (infoModule.TypeOfModule == DFConstantsAndEnums.ModuleType.SLICEIEPE) { channel.IEPEChannel = true; channel.SupportedBridges = new SensorConstants.BridgeType[] { SensorConstants.BridgeType.IEPE }; } else { channel.IEPEChannel = false; channel.SupportedBridges = new SensorConstants.BridgeType[] {SensorConstants.BridgeType.FullBridge, SensorConstants.BridgeType.HalfBridge}; } if (infoModule.IsProgrammable) { channel.SupportedBridges = new SensorConstants.BridgeType[] { SensorConstants.BridgeType.FullBridge, SensorConstants.BridgeType.HalfBridge, SensorConstants.BridgeType.IEPE}; } configModule.Channels[i] = channel; } return configModule; } protected override void AsyncArmNow(object asyncInfo) { // We dont want to do this here. We want to do this on connect. TriggerCheckService.ClearLevelTriggerCache(ConfigData); base.AsyncArmNow(asyncInfo); } #region IClockSyncActions void IClockSyncActions.SetClockSyncConfig(ServiceCallback callback, object userData, ClockSyncProfile profile) { var info = new SliceServiceAsyncInfo(callback, userData) { functionData = profile }; var packet = new SetClockSyncConfigPacket(info); LaunchAsyncWorker("Slice.SetClockSyncConfig", AsyncSetClockSyncConfig, packet); } void IClockSyncActions.GetClockSyncStatus(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); var packet = new GetClockSyncStatusPacket(info); LaunchAsyncWorker("Slice.GetClockSyncStatus", AsyncGetClockSyncStatus, packet); } void IClockSyncActions.GetPTPDomainID(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); var packet = new GetPTPDomainIDPacket(info); LaunchAsyncWorker("Slice.GetPTPDomainID", AsyncGetPTPDomainID, packet); } void IClockSyncActions.SetPTPDomainID(ServiceCallback callback, object userData, byte domainID) { var info = new SliceServiceAsyncInfo(callback, userData) { functionData = domainID }; var packet = new SetPTPDomainIDPacket(info); LaunchAsyncWorker("Slice.SetPTPDomainID", AsyncSetPTPDomainID, packet); } #endregion protected virtual double[] GetTiltCalFactors() { try { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryTiltSensorData)) { var tiltSensorCals = new double[18]; for (int tiltCalKeyOffset = 0; tiltCalKeyOffset < 18; tiltCalKeyOffset++) { var qSA_BS6 = new QuerySystemAttribute_BridgeSlice6(this); qSA_BS6.DeviceID = 1; qSA_BS6.Key = (AttributeTypes.SystemAttributes_BridgeSlice6)((int)AttributeTypes.SystemAttributes_BridgeSlice6.TILTSENSOR_CAL_1 + tiltCalKeyOffset); qSA_BS6.SyncExecute(); tiltSensorCals[tiltCalKeyOffset] = (float)qSA_BS6.Value; } return tiltSensorCals; } } catch (Exception ex) { APILogger.Log(ex); } return new double[0]; } protected override void GetTiltResults(ArmCheckResults dasResults) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryTiltSensorData)) { if (!ConfigData.Modules.Any()) { return; } var tiltSensorCals = GetTiltCalFactors(); TiltSensorCals = tiltSensorCals; var qtsd = new QueryTiltSensorData(this); qtsd.DeviceID = 1; qtsd.SyncExecute(); dasResults.TiltSensorDataPre = new short[] {qtsd.Channel1ValueAdc, qtsd.Channel2ValueAdc, qtsd.Channel3ValueAdc}; dasResults.TiltDegrees = DTS.Common.DAS.Concepts.Test.Module.GetTiltDegreesEU( dasResults.TiltSensorDataPre, tiltSensorCals, ConfigData.Modules[0].TiltAxes, ConfigData.Modules[0].AxisIgnored, new float[] { (float)ConfigData.Modules[0].MountOffsetAxisOne, (float)ConfigData.Modules[0].MountOffsetAxisTwo }); } } /// /// sets Attribute 145 (TMATS interval) if information is present, logs otherwise /// http://manuscript.dts.local/f/cases/29987/Add-CG-DP-TMATS-interval-UI-support /// /// lookup protected void SetTMATSInterval(IReadOnlyDictionary tmatsIntervalLookup) { try { if (null == tmatsIntervalLookup || !tmatsIntervalLookup.ContainsKey(this)) { APILogger.Log($"TMATS interval information not present for {SerialNumber} attribute will not be set"); return; } if (ProtocolVersion < MIN_PROTOCOL_TMATS_INTERVAL) { APILogger.Log($"TMATS interval not supported by {SerialNumber} attribute will not be set"); return; } var interval = tmatsIntervalLookup[this]; var setSystemAttribute = new SetSystemAttributeSLICE6AIR(this); setSystemAttribute.SetValue(AttributeTypes.SystemAttributesSLICE6AIR.S6A_IrigCGDPSendIntervalMsec, interval, true); setSystemAttribute.SyncExecute(); } catch (Exception ex) { APILogger.Log("SetTMATSInterval failed", ex); } } } #region QueryEventData_SLICE6 /// /// this meaty class handles skipping parts of the download not needed (start of page to desired start sample) /// [also note we'll need to do the same thing with the end sample too if we want to use the ECC properly, /// but ECC isn't even implemented yet ...] /// public class QueryEventData_SLICE6 : QueryEventDataBase { public override UInt64 FirstSample { get => base.FirstSample; set => base.FirstSample = value; } public override UInt64 LastSample { get => base.LastSample; set => base.LastSample = value; } public QueryEventData_SLICE6(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { LogCommands = false; } public QueryEventData_SLICE6(DTS.Common.Interface.DASFactory.ICommunication sock, int TimeoutMillisec) : base(sock, TimeoutMillisec) { LogCommands = false; } private ulong GetRequestedStartSpot() { if (recorder is SLICE6 slice6_Ethernet) { return ((WhatToDownloadSlice2)slice6_Ethernet.WhatToDownload).RequestedStartSport; } else if (recorder is SLICE6AIR slice6air_Ethernet) { return ((WhatToDownloadSlice2)slice6air_Ethernet.WhatToDownload).RequestedStartSport; } else if (recorder is SLICE6AIRBR slice6air_br_Ethernet) { return ((WhatToDownloadSlice2)slice6air_br_Ethernet.WhatToDownload).RequestedStartSport; } else { throw new NotSupportedException("GetRequestedStartSport not supported for " + recorder.ConnectString); } } private void PushLeftOverData(ushort[] daters) { if (recorder is SLICE6 slice6_Ethernet) { slice6_Ethernet.PushLeftOverData(daters); } else if (recorder is SLICE6AIR slice6air_Ethernet) { slice6air_Ethernet.PushLeftOverData(daters); } else if (recorder is SLICE6AIRBR slice6air_br_Ethernet) { slice6air_br_Ethernet.PushLeftOverData(daters); } } protected override CommandReceiveAction WholePackagePost() { // now send the data to the user var stat = CommandStatus.Success; if (response.Status != DFConstantsAndEnums.CommandStatus.StatusNoError) { var s = (int)response.Status; APILogger.LogString("QueryEventData.WholePackagePost: reporting failure, status==" + CommandPacket.StatusLabels[s] + " (0x" + s.ToString("X") + ")"); stat = CommandStatus.Failure; } var cbReport = new QueryEventDataReport(stat, UserCallbackData); cbReport.Data = new short[_channelsDownloaded][]; for (var i = 0; i < _channelsDownloaded; i++) GetChannelData(i, out cbReport.Data[i]); //we have processed some data, but there may be some left over (since data isn't channel sample aligned ...) //figure out what we used and what's left over //now we have two situations, one, we have already skimmed beyond all the data we need //or two, we are somewhere in between, we need to skip a few samples var requestedStartSpot = GetRequestedStartSpot(); if ((FirstSample + (ulong)_data.Length) < requestedStartSpot) { //push no data, we don't want it! } else if (FirstSample > requestedStartSpot) {//we want everything in here ... var samplesProcessed = Convert.ToInt32(Math.Truncate((double)_data.Length / ChannelsDownloaded)); var offset = samplesProcessed * ChannelsDownloaded; var leftover = new ushort[_data.Length - offset]; for (var i = 0; i < leftover.Length; i++) { leftover[i] = _data[i + offset]; } PushLeftOverData(leftover); } else { //we need to calculate samples only from the start of the data we are interested in var offset = Convert.ToInt32(requestedStartSpot - FirstSample); int samplesProcessed = Convert.ToInt32(Math.Truncate(((double)_data.Length - offset) / ChannelsDownloaded)); var product = samplesProcessed * ChannelsDownloaded; var leftover = new ushort[_data.Length - offset - product]; offset += product; for (var i = 0; i < leftover.Length; i++) { leftover[i] = _data[i + offset]; } PushLeftOverData(leftover); } return UserCallback(cbReport); } private ushort[] PopLeftOverData() { if (recorder is SLICE6 slice6_Ethernet) { return slice6_Ethernet.PopLeftOverData(); } else if (recorder is SLICE6AIR slice6air_Ethernet) { return slice6air_Ethernet.PopLeftOverData(); } else if (recorder is SLICE6AIRBR slice6air_br_Ethernet) { return slice6air_br_Ethernet.PopLeftOverData(); } else { throw new NotImplementedException(); } } protected override CommandReceiveAction WholePackage() { if (response.Status != DFConstantsAndEnums.CommandStatus.StatusNoError) { return CommandReceiveAction.StopReceiving; } //we are going to process the data shortly, but before we do we'll need to //pre-pend any left over data we have to the new incoming data //since we already count the samples downloaded for samples in the left over stuff //we don't need to recount it, just the new incoming samples _samplesDownloaded = (ulong)(response.Parameter.Length) / 2; var leftover = PopLeftOverData(); _data = new ushort[_samplesDownloaded + (ulong)leftover.Length]; leftover.CopyTo(_data, 0); for (var i = 0; (ulong)i < _samplesDownloaded; i++) { response.GetParameter(2 * i, out _data[i + leftover.Length]); } return CommandReceiveAction.StopReceiving; } public override void GetChannelData(int channel, out short[] signedADC) { if (channel < 0 || channel > _channelsDownloaded) { throw new ApplicationException("QueryEventData.GetChannelData: Data requested on a channel that wasn't downloaded."); } //first short circuit if we know we are still completely skipping data if (((ulong)_data.Length + FirstSample) < GetRequestedStartSpot())//(slice2.WhatToDownload as WhatToDownloadSlice2).RequestedStartSport) { signedADC = new short[0];//nothing to see here (we are completely before the start of our requested data) return; } //now we have two situations, one, we have already skimmed beyond all the data we need //or two, we are somewhere in between, we need to skip a few samples var offset = 0; if (GetRequestedStartSpot() > FirstSample) { offset = Convert.ToInt32(GetRequestedStartSpot() - FirstSample); } // Data order for a 9 channel stack // 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 etc. ushort val; var completeSamples = Convert.ToInt32(Math.Truncate((double)(_data.Length - offset) / ChannelsDownloaded)); //performance improvements, use simpler structures and do less calculations over the iterations var rv = new short[completeSamples]; offset += channel; for (var i = 0; i < completeSamples; i++) { val = _data[i * ChannelsDownloaded + offset]; rv[i] = (short)((((val & 0x00FF) << 8) | ((val >> 8) & 0x00FF)) + 0x8000); } signedADC = rv.ToArray(); } // this function isn't used by SLICEWare, but it is used by the FirmwareTestUtility // SW uses the GetChannelData above public override void GetRawIndexedData(int index, out ushort[] data) { data = new ushort[_samplesDownloaded]; for (var i = 0; i < data.Length; i++) { data[i] = _data[i + index]; } } } #endregion }