Files

1136 lines
55 KiB
C#
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
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<T> : SLICE6_Base<T> where T : IConnection, new()
{
}
public class SLICE6_Base<T> : SLICE2_Base<T>, 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;
}
/// <summary>
/// returns whether the device supports start completion inversion or not
/// <inheritdoc cref="IDASCommunication"/>
/// </summary>
/// <returns>true if the device supports start inversion, false otherwise</returns>
public override bool SupportsStartInversion() => HardwareConstants.SupportsStartInversion(GetHardwareType(), ProtocolVersion);
/// <summary>
/// returns whether the device supports trigger completion inversion or not
/// <inheritdoc cref="IDASCommunication"/>
/// </summary>
/// <returns>true if the device supports trigger inversion, false otherwise</returns>
public override bool SupportsTriggerInversion() => HardwareConstants.SupportsTriggerInversion(GetHardwareType(), ProtocolVersion);
public double[] TiltSensorCals { get; protected set; }
/// <summary>
/// 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)
/// </summary>
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<ushort>();
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);
}
}
/// <summary>
/// 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...
/// </summary>
/// <param name="eventNum"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="moduleIndex"></param>
/// <param name="channelOnModule"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// Convert gain code to value based on Slice 1 conversion table
/// </summary>
/// <param name="gainCode"></param>
/// <returns></returns>
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<DFConstantsAndEnums.ProtocolLimitedCommands, byte> SLICE6_MinimumProtocols = new Dictionary<DFConstantsAndEnums.ProtocolLimitedCommands, byte>();
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);
}
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// SLICE6 config attributes, mostly inherits from SLICE.ConfigAttributes with some functionality removed
/// </summary>
protected class SLICE6ConfigAttributes : SLICE2ConfigAttributes
{
public override void ConfigureCoupling(bool[] IsACCoupledArray)
{
}
/// <summary>
/// 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
/// </summary>
/// <param name="das"></param>
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)
{
}
/// <summary>
/// QueryEventData also is customized for SLICE6, it needs to perform SLICE6 specific data marshalling
/// </summary>
/// <returns></returns>
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<IDASCommunication, ushort>();
slice6AirTmtFile.WriteTmtFile(this, info.DataChannelIds, info.TimeChannelIds, uartLookup, DASIndex, this);
}
catch (ArgumentOutOfRangeException ex)
{
var eventAggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
var msg =
$"Failed to write TMT file for: {SerialNumber}\r\nReduce channel name lengths or TMT template size";
eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[]
{
msg
}, null));
throw new ArgumentOutOfRangeException(msg, ex);
}
catch (Exception ex)
{
var eventAggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
var msg =
$"Failed to write TMT file for: {SerialNumber}\r\n{ex.Message}";
eventAggregator.GetEvent<PageErrorEvent>().Publish(new PageErrorArg(new[]
{
msg
}, null));
throw new Exception(ex.Message, ex);
}
SetTMATSInterval(info.TMATSIntervalMs);
}
}
/// <summary>
/// the order of this DAS among multiple das
/// </summary>
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.");
}
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="configAsyncInfo"></param>
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<byte>();
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);
}
}
/// <summary>
/// hardcoded constants right now ... maybe these belong in attributes in the firmware!
/// </summary>
protected override uint MaxAAFilterRateHz => SLICE6.MaxAAFilterRateHz;
protected override uint MaxSampleRateHz => 400000;
/// <summary>
/// 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)
/// </summary>
/// <returns></returns>
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
});
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="tmatsIntervalLookup">lookup </param>
protected void SetTMATSInterval(IReadOnlyDictionary<IDASCommunication, ushort> 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
/// <summary>
/// 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 ...]
/// </summary>
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<EthernetConnection> slice6_Ethernet)
{
return ((WhatToDownloadSlice2)slice6_Ethernet.WhatToDownload).RequestedStartSport;
}
else if (recorder is SLICE6AIR<EthernetConnection> slice6air_Ethernet)
{
return ((WhatToDownloadSlice2)slice6air_Ethernet.WhatToDownload).RequestedStartSport;
}
else if (recorder is SLICE6AIRBR<EthernetConnection> 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<EthernetConnection> slice6_Ethernet)
{
slice6_Ethernet.PushLeftOverData(daters);
}
else if (recorder is SLICE6AIR<EthernetConnection> slice6air_Ethernet)
{
slice6air_Ethernet.PushLeftOverData(daters);
}
else if (recorder is SLICE6AIRBR<EthernetConnection> 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<EthernetConnection> slice6_Ethernet)
{
return slice6_Ethernet.PopLeftOverData();
}
else if (recorder is SLICE6AIR<EthernetConnection> slice6air_Ethernet)
{
return slice6air_Ethernet.PopLeftOverData();
}
else if (recorder is SLICE6AIRBR<EthernetConnection> 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
}