diff --git a/WasatchNET/AndorSpectrometer.cs b/WasatchNET/AndorSpectrometer.cs index 9313b31..363155a 100644 --- a/WasatchNET/AndorSpectrometer.cs +++ b/WasatchNET/AndorSpectrometer.cs @@ -18,6 +18,13 @@ namespace WasatchNET { + public struct camSpeed + { + public int channel; + public int speedIndex; + public float speed; + } + public class AndorSpectrometer : Spectrometer { #if WIN32 @@ -28,6 +35,7 @@ public class AndorSpectrometer : Spectrometer internal int specIndex; int cameraHandle = 0; int yPixels; + public List speedOptions = new List(); //see page 330 of Andor SDK documentation public const int DRV_SUCCESS = 20002; @@ -81,12 +89,14 @@ internal AndorSpectrometer(UsbRegistry usbReg, int index = 0) : base(usbReg) } // Set Horizontal Speed to max (step 13) + // Populate speed list float STemp = 0; int HSnumber = 0; int ADnumber = 0; int nAD = 0; int sIndex = 0; errorValue = andorDriver.GetNumberADChannels(ref nAD); // 13.1 + speedOptions.Clear(); if (errorValue != DRV_SUCCESS) { @@ -99,6 +109,14 @@ internal AndorSpectrometer(UsbRegistry usbReg, int index = 0) : base(usbReg) for (int iSpeed = 0; iSpeed < sIndex; iSpeed++) { andorDriver.GetHSSpeed(iAD, 0, iSpeed, ref speed); // 13.3 + speedOptions.Add( + new camSpeed + { + channel = iAD, + speedIndex = iSpeed, + speed = speed + }); + if (speed > STemp) { STemp = speed; @@ -297,6 +315,17 @@ protected override async Task getSpectrumRawAsync(bool skipTrigger = f } + public bool setCamSpeed(camSpeed option) + { + uint errorValue = andorDriver.SetADChannel(option.channel); + + if (errorValue != DRV_SUCCESS) + return false; + + errorValue = andorDriver.SetHSSpeed(0, option.speedIndex); + + return errorValue == DRV_SUCCESS; + } public override bool areaScanEnabled { @@ -389,6 +418,7 @@ public override LaserPowerResolution laserPowerResolution public override bool laserInterlockEnabled { get => false; } public override byte laserWarningDelaySec { get => 0; set { } } + public override byte laserPowerAttenuation { get => 0; set { } } public override UInt64 laserModulationPeriod { get => 100; } diff --git a/WasatchNET/BoulderSpectrometer.cs b/WasatchNET/BoulderSpectrometer.cs index b2af9b1..576f6b9 100644 --- a/WasatchNET/BoulderSpectrometer.cs +++ b/WasatchNET/BoulderSpectrometer.cs @@ -809,6 +809,7 @@ public override LaserPowerResolution laserPowerResolution public override bool laserInterlockEnabled { get => false; } public override byte laserWarningDelaySec { get => 0; set { } } + public override byte laserPowerAttenuation { get => 0; set { } } public override UInt64 laserModulationPeriod { get => 100; } diff --git a/WasatchNET/COMOCTSpectrometer.cs b/WasatchNET/COMOCTSpectrometer.cs index 549afa6..210c5c4 100644 --- a/WasatchNET/COMOCTSpectrometer.cs +++ b/WasatchNET/COMOCTSpectrometer.cs @@ -58,8 +58,6 @@ internal override async Task openAsync() if (!sendCOMCommand(Opcodes.GET_LINE_PERIOD, ref resp, null)) { - port.Close(); - port.Dispose(); port = null; return false; } @@ -343,7 +341,23 @@ internal bool sendCOMCommand(Opcodes opcode, ref string response, float[] args, } command += "\r"; - port.Write(command); + try + { + Thread t1 = new Thread(() => port.Write(command)); + t1.Start(); + if (!t1.Join(TimeSpan.FromMilliseconds(100))) + { + return false; + } + + } + catch (Exception e) + { + logger.info("{0} failed with exception {1}", portName, e.Message); + return false; + } + + Thread.Sleep(33); string resp = ""; Thread t = new Thread(() => resp = tryPort(port)); diff --git a/WasatchNET/EEPROM.cs b/WasatchNET/EEPROM.cs index b3d2e52..8086241 100644 --- a/WasatchNET/EEPROM.cs +++ b/WasatchNET/EEPROM.cs @@ -38,7 +38,7 @@ public class EEPROM : IEEPROM /// accessible from existing firmware on our ARM models. A future update /// to /FX2 firmware will make them available on all spectrometers. /// - internal const int MAX_PAGES = 8; + internal const int MAX_PAGES = 9; internal const int MAX_PAGES_REAL = 512; internal const int MAX_NAME_LEN = 16; internal const int MAX_LIB_ENTRIES = 8; @@ -54,7 +54,7 @@ public class EEPROM : IEEPROM /// - rev 14 /// - adds SiG laser TEC and Has interlock feedback to feature mask /// - protected const byte FORMAT = 17; + protected const byte FORMAT = 18; protected Spectrometer spectrometer; protected Logger logger = Logger.getInstance(); @@ -678,6 +678,20 @@ public float[] linearityCoeffs // public int laserLifetimeOperationMinutes { get; private set; } // public short laserTemperatureMax { get; private set; } // public short laserTemperatureMin { get; private set; } + + public byte maxLaserTempDegC + { + get { return _maxLaserTempDegC; } + set + { + EventHandler handler = EEPROMChanged; + _maxLaserTempDegC = value; + handler?.Invoke(this, new EventArgs()); + } + } + byte _maxLaserTempDegC; + + public float maxLaserPowerMW { get { return _maxLaserPowerMW; } @@ -800,6 +814,18 @@ public HORIZONTAL_BINNING_METHOD horizontalBinningMethod } HORIZONTAL_BINNING_METHOD _horizontalBinningMethod = HORIZONTAL_BINNING_METHOD.BIN_2X2; + public byte laserDacAttenuation + { + get { return _laserDacAttenuation; } + set + { + EventHandler handler = EEPROMChanged; + _laserDacAttenuation = value; + handler?.Invoke(this, new EventArgs()); + } + } + byte _laserDacAttenuation = 0; + ///////////////////////////////////////////////////////////////////////// // Page 4 ///////////////////////////////////////////////////////////////////////// @@ -907,6 +933,18 @@ public string productConfiguration string _productConfiguration; + public byte[] assemblyRevision + { + get { return _assemblyRevision; } + set + { + EventHandler handler = EEPROMChanged; + _assemblyRevision = value; + handler?.Invoke(this, new EventArgs()); + } + } + byte[] _assemblyRevision; + public PAGE_SUBFORMAT subformat { get { return _subformat; } @@ -1256,6 +1294,25 @@ public byte librarySpectraNum } byte _librarySpectraNum = 1; + ///////////////////////////////////////////////////////////////////////// + // Page 8 Handheld Devices + ///////////////////////////////////////////////////////////////////////// + + public string laserPassword + { + get { return _laserPassword; } + set + { + EventHandler handler = EEPROMChanged; + _laserPassword = value; + handler?.Invoke(this, new EventArgs()); + } + } + + string _laserPassword; + + public FeatureMaskXS featureMaskXS = new FeatureMaskXS(); + ///////////////////////////////////////////////////////////////////////// // Pages 10-73 (subformat UNTETHERED_DEVICE) ///////////////////////////////////////////////////////////////////////// @@ -1645,6 +1702,12 @@ public virtual async Task readAsync(bool skipRead = false) maxIntegrationTimeMS = ParseData.toUInt32(pages[3], 44); } + if (format >= 18) + { + maxLaserTempDegC = ParseData.toUInt8(pages[3], 11); + } + + userData = format < 4 ? new byte[63] : new byte[64]; Array.Copy(pages[4], userData, userData.Length); @@ -1663,6 +1726,14 @@ public virtual async Task readAsync(bool skipRead = false) else productConfiguration = ""; + + assemblyRevision = new byte[6]; + if (format >= 18) + { + Array.Copy(pages[5], 46, assemblyRevision, 0, assemblyRevision.Length); + } + + if (format >= 6 && (subformat == PAGE_SUBFORMAT.INTENSITY_CALIBRATION || subformat == PAGE_SUBFORMAT.UNTETHERED_DEVICE)) { @@ -1749,6 +1820,15 @@ public virtual async Task readAsync(bool skipRead = false) horizontalBinningMethod = HORIZONTAL_BINNING_METHOD.BIN_2X2; } + if (format >= 18) + { + laserDacAttenuation = ParseData.toUInt8(pages[3], 61); + } + else + { + //default to halfway point + laserDacAttenuation = 127; + } if (format < 12) featureMask.evenOddHardwareCorrected = false; @@ -1829,6 +1909,12 @@ public virtual async Task readAsync(bool skipRead = false) regionCount = ParseData.toUInt8(pages[7], 0); } } + + if (format >= 18 && spectrometer.isSiG && pages.Count >= 8) + { + laserPassword = ParseData.toString(pages[8], 0, 16); + featureMaskXS = new FeatureMaskXS(ParseData.toUInt32(pages[8], 16)); + } } catch (Exception ex) { @@ -1990,6 +2076,9 @@ public void setFromJSON(EEPROMJSON json) featureMask.disableBLEPower = json.DisableBLEPower; featureMask.disableLaserArmedIndication = json.DisableLaserArmedIndication; featureMask.interlockExcluded = json.InterlockExcluded; + featureMask.laserTimeoutInCounts = json.LaserTimeoutInCounts; + featureMask.isOEM = json.IsOEM; + featureMaskXS.BLEDoorSensor = json.BLEDoorSensor; wavecalCoeffs[0] = (float)json.WavecalCoeffs[0]; wavecalCoeffs[1] = (float)json.WavecalCoeffs[1]; @@ -2038,10 +2127,12 @@ public void setFromJSON(EEPROMJSON json) linearityCoeffs[3] = (float)json.LinearityCoeffs[3]; linearityCoeffs[4] = (float)json.LinearityCoeffs[4]; + maxLaserTempDegC = json.MaxLaserTempDegC; maxLaserPowerMW = (float)json.MaxLaserPowerMW; minLaserPowerMW = (float)json.MinLaserPowerMW; laserWarmupSec = json.LaserWarmupS; laserExcitationWavelengthNMFloat = (float)json.ExcitationWavelengthNM; + laserDacAttenuation = json.LaserDACAttenuation; avgResolution = (float)json.AvgResolution; if (json.LaserPowerCoeffs != null) @@ -2074,7 +2165,12 @@ public void setFromJSON(EEPROMJSON json) badPixels[12] = (Int16)json.BadPixels[12]; badPixels[13] = (Int16)json.BadPixels[13]; badPixels[14] = (Int16)json.BadPixels[14]; - + if (json.AssemblyRevision != null) + { + assemblyRevision = new byte[json.AssemblyRevision.Length]; + Array.Copy(json.AssemblyRevision, assemblyRevision, json.AssemblyRevision.Length); + } + detectorSerialNumber = json.DetectorSN; if (json.ProductConfig != null) @@ -2128,7 +2224,7 @@ public void setFromJSON(EEPROMJSON json) regionCount = json.RegionCount; } - + laserPassword = json.LaserPassword; } public EEPROMJSON toJSON() @@ -2223,9 +2319,11 @@ public EEPROMJSON toJSON() json.LaserPowerCoeffs[2] = laserPowerCoeffs[2]; json.LaserPowerCoeffs[3] = laserPowerCoeffs[3]; } + json.MaxLaserTempDegC = maxLaserTempDegC; json.MaxLaserPowerMW = maxLaserPowerMW; json.MinLaserPowerMW = minLaserPowerMW; json.ExcitationWavelengthNM = laserExcitationWavelengthNMFloat; + json.LaserDACAttenuation = laserDacAttenuation; json.AvgResolution = avgResolution; json.BadPixels = new int[15]; if (badPixels != null) @@ -2246,6 +2344,12 @@ public EEPROMJSON toJSON() json.BadPixels[13] = badPixels[13]; json.BadPixels[14] = badPixels[14]; } + if (assemblyRevision != null) + { + json.AssemblyRevision = new byte[assemblyRevision.Length]; + Array.Copy(assemblyRevision, json.AssemblyRevision, assemblyRevision.Length); + } + json.DetectorSN = detectorSerialNumber; json.UserText = userText; json.ProductConfig = productConfiguration; @@ -2278,6 +2382,12 @@ public EEPROMJSON toJSON() json.DisableBLEPower = featureMask.disableBLEPower; json.DisableLaserArmedIndication = featureMask.disableLaserArmedIndication; json.InterlockExcluded = featureMask.interlockExcluded; + json.LaserTimeoutInCounts = featureMask.laserTimeoutInCounts; + json.IsOEM = featureMask.isOEM; + } + if (featureMaskXS != null) + { + json.BLEDoorSensor = featureMaskXS.BLEDoorSensor; } json.LaserWarmupS = laserWarmupSec; @@ -2321,7 +2431,9 @@ public EEPROMJSON toJSON() json.RegionCount = regionCount; } + json.LaserPassword = laserPassword; json.FeatureMask = featureMask.ToString(); + json.FeatureMaskXS = featureMaskXS.ToString(); json.HexDump = hexdump(); return json; @@ -2541,6 +2653,18 @@ protected bool writeParse() if (!ParseData.writeUInt16(detectorTimeout, pages[3], 57)) return false; if (!ParseData.writeByte((byte)horizontalBinningMethod, pages[3], 59)) return false; } + if (format >= 18) + { + if (!ParseData.writeByte(maxLaserTempDegC, pages[3], 11)) return false; + if (!ParseData.writeByte(laserDacAttenuation, pages[3], 61)) return false; + + Array.Copy(assemblyRevision, 0, pages[5], 46, assemblyRevision.Length); + if (spectrometer.isSiG && pages.Count >= 8) + { + if (!ParseData.writeString(laserPassword, pages[8], 0, 16)) return false; + if (!ParseData.writeUInt32(featureMaskXS.toUInt32(), pages[8], 16)) return false; + } + } byte[] userDataChunk2 = new byte[64]; byte[] userDataChunk3 = new byte[64]; diff --git a/WasatchNET/EEPROMJSON.cs b/WasatchNET/EEPROMJSON.cs index ce5bfa1..c2a7265 100644 --- a/WasatchNET/EEPROMJSON.cs +++ b/WasatchNET/EEPROMJSON.cs @@ -42,18 +42,21 @@ public class EEPROMJSON public int LaserWatchdogTimer; public int PowerWatchdogTimer; public int DetectorTimeout; + public byte LaserDACAttenuation; public byte LightSourceType; public byte HorizontalBinningMethod; public int ROIHorizStart; public int ROIHorizEnd; public int[] ROIVertRegionStarts; public int[] ROIVertRegionEnds; + public byte MaxLaserTempDegC; public double[] LaserPowerCoeffs; public double MaxLaserPowerMW; public double MinLaserPowerMW; public double ExcitationWavelengthNM; public double AvgResolution; public int[] BadPixels; + public byte[] AssemblyRevision; public string UserText; public string ProductConfig; public int RelIntCorrOrder; @@ -79,13 +82,18 @@ public class EEPROMJSON public int Region3VertStart; public int Region3VertEnd; public byte RegionCount; + public string LaserPassword; public bool SigLaserTEC; public bool HasInterlockFeedback; public bool HasShutter; public bool DisableBLEPower; public bool DisableLaserArmedIndication; public bool InterlockExcluded; + public bool LaserTimeoutInCounts; + public bool IsOEM; + public bool BLEDoorSensor; public string FeatureMask; + public string FeatureMaskXS; public string DetectorSN; public override bool Equals(object obj) @@ -163,6 +171,8 @@ public override bool Equals(object obj) return false; if (item.DetectorTimeout != this.DetectorTimeout) return false; + if (item.LaserDACAttenuation != this.LaserDACAttenuation) + return false; if (item.LightSourceType != this.LightSourceType) return false; if (item.HorizontalBinningMethod != this.HorizontalBinningMethod) @@ -177,6 +187,8 @@ public override bool Equals(object obj) return false; if (!floatEq(item.LaserPowerCoeffs, this.LaserPowerCoeffs)) return false; + if (item.MaxLaserTempDegC != this.MaxLaserTempDegC) + return false; if (!floatEq(item.MaxLaserPowerMW, this.MaxLaserPowerMW)) return false; if (!floatEq(item.MinLaserPowerMW, this.MinLaserPowerMW)) @@ -187,6 +199,8 @@ public override bool Equals(object obj) return false; if (!intArrayEq(item.BadPixels, this.BadPixels)) return false; + if (!byteArrayEq(item.AssemblyRevision, this.AssemblyRevision)) + return false; if (item.UserText != this.UserText) return false; if (item.ProductConfig != this.ProductConfig) @@ -198,7 +212,8 @@ public override bool Equals(object obj) if (!floatEq(item.RelIntCorrCoeffs, this.RelIntCorrCoeffs)) return false; } - + if (item.LaserPassword != this.LaserPassword) + return false; if (item.Bin2x2 != this.Bin2x2) return false; if (item.FlipXAxis != this.FlipXAxis) @@ -223,6 +238,12 @@ public override bool Equals(object obj) return false; if (item.InterlockExcluded != this.InterlockExcluded) return false; + if (item.LaserTimeoutInCounts != this.LaserTimeoutInCounts) + return false; + if (item.IsOEM != this.IsOEM) + return false; + if (item.BLEDoorSensor != this.BLEDoorSensor) + return false; if (item.Subformat != this.Subformat) return false; @@ -262,6 +283,8 @@ public override bool Equals(object obj) return false; if (item.FeatureMask != this.FeatureMask) return false; + if (item.FeatureMaskXS != this.FeatureMaskXS) + return false; if (item.DetectorSN != this.DetectorSN) return false; @@ -300,6 +323,23 @@ bool intArrayEq(int[] a, int[] b) return true; } + bool byteArrayEq(byte[] a, byte[] b) + { + if (a == null && b == null) + return true; + + if (a == null || b == null) + return false; + + if (a.Length != b.Length) + return false; + + for (int i = 0; i < a.Length; ++i) + if (a[i] != b[i]) + return false; + + return true; + } public override int GetHashCode() { @@ -335,6 +375,8 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + ActivePixelsVert.GetHashCode(); hashCode = hashCode * -1521134295 + MinIntegrationTimeMS.GetHashCode(); hashCode = hashCode * -1521134295 + MaxIntegrationTimeMS.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxLaserTempDegC.GetHashCode(); + hashCode = hashCode * -1521134295 + LaserDACAttenuation.GetHashCode(); hashCode = hashCode * -1521134295 + LaserWatchdogTimer.GetHashCode(); hashCode = hashCode * -1521134295 + PowerWatchdogTimer.GetHashCode(); hashCode = hashCode * -1521134295 + DetectorTimeout.GetHashCode(); @@ -352,6 +394,9 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(BadPixels); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(UserText); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ProductConfig); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(LaserPassword); + if (AssemblyRevision != null) + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AssemblyRevision); hashCode = hashCode * -1521134295 + RelIntCorrOrder.GetHashCode(); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(RelIntCorrCoeffs); hashCode = hashCode * -1521134295 + Bin2x2.GetHashCode(); @@ -367,7 +412,11 @@ public override int GetHashCode() hashCode = hashCode * -1521134295 + DisableBLEPower.GetHashCode(); hashCode = hashCode * -1521134295 + DisableLaserArmedIndication.GetHashCode(); hashCode = hashCode * -1521134295 + InterlockExcluded.GetHashCode(); + hashCode = hashCode * -1521134295 + LaserTimeoutInCounts.GetHashCode(); + hashCode = hashCode * -1521134295 + IsOEM.GetHashCode(); + hashCode = hashCode * -1521134295 + BLEDoorSensor.GetHashCode(); hashCode = hashCode * -1521134295 + FeatureMask.GetHashCode(); + hashCode = hashCode * -1521134295 + FeatureMaskXS.GetHashCode(); hashCode = hashCode * -1521134295 + DetectorSN.GetHashCode(); return hashCode; @@ -435,6 +484,7 @@ public string ToString(int level, int indentSize = 2) addField(sb, indent, "LaserWatchdogTimer", LaserWatchdogTimer); addField(sb, indent, "PowerWatchdogTimer", PowerWatchdogTimer); addField(sb, indent, "DetectorTimeout", DetectorTimeout); + addField(sb, indent, "LaserDACAttenuation", LaserDACAttenuation); addField(sb, indent, "LightSourceType", LightSourceType); addField(sb, indent, "HorizontalBinningMethod", HorizontalBinningMethod); addField(sb, indent, "ROIHorizStart", ROIHorizStart); @@ -442,6 +492,7 @@ public string ToString(int level, int indentSize = 2) addField(sb, indent, "ROIVertRegionStarts", ROIVertRegionStarts); addField(sb, indent, "ROIVertRegionEnds", ROIVertRegionEnds); + addField(sb, indent, "MaxLaserTempDegC", MaxLaserTempDegC); addField(sb, indent, "LaserPowerCoeffs", LaserPowerCoeffs); @@ -451,11 +502,14 @@ public string ToString(int level, int indentSize = 2) addField(sb, indent, "AvgResolution", AvgResolution); addField(sb, indent, "BadPixels", BadPixels); + if (AssemblyRevision != null) + addField(sb, indent, "AssemblyRevision", AssemblyRevision); addField(sb, indent, "UserText", UserText); addField(sb, indent, "ProductConfig", ProductConfig); addField(sb, indent, "DetectorSN", DetectorSN); addField(sb, indent, "Subformat", Subformat); + addField(sb, indent, "LaserPassword", LaserPassword); if (subformat == EEPROM.PAGE_SUBFORMAT.INTENSITY_CALIBRATION || subformat == EEPROM.PAGE_SUBFORMAT.UNTETHERED_DEVICE) addField(sb, indent, "RelIntCorrOrder", RelIntCorrOrder); @@ -473,7 +527,11 @@ public string ToString(int level, int indentSize = 2) addField(sb, indent, "DisableBLEPower", DisableBLEPower); addField(sb, indent, "DisableLaserArmedIndication", DisableLaserArmedIndication); addField(sb, indent, "InterlockExcluded", InterlockExcluded); + addField(sb, indent, "LaserTimeoutInCounts", LaserTimeoutInCounts); + addField(sb, indent, "IsOEM", IsOEM); + addField(sb, indent, "BLEDoorSensor", BLEDoorSensor); addField(sb, indent, "FeatureMask", FeatureMask); + addField(sb, indent, "FeatureMaskXS", FeatureMaskXS); addField(sb, indent, "HexDump", HexDump); sb.AppendFormat("{0}\"{1}\": {2}", indent, "LaserWarmupS", LaserWarmupS); @@ -524,6 +582,13 @@ void addField(StringBuilder sb, string indent, string name, double[] value) sb.Append(" " + string.Join(", ", value)); sb.Append(" ],\n"); } + void addField(StringBuilder sb, string indent, string name, byte[] value) + { + sb.AppendFormat("{0}\"{1}\": ", indent, name); + sb.Append("["); + sb.Append(" " + string.Join(", ", value)); + sb.Append(" ],\n"); + } void addField(StringBuilder sb, string indent, string name, int[] value) { diff --git a/WasatchNET/FeatureMask.cs b/WasatchNET/FeatureMask.cs index aeab783..31ff811 100644 --- a/WasatchNET/FeatureMask.cs +++ b/WasatchNET/FeatureMask.cs @@ -25,6 +25,8 @@ enum Flags DISABLE_BLE_POWER = 0x0100, // 2^8 DISABLE_LASER_ARMED_INDIC = 0x0200, // 2^9 INTERLOCK_EXCLUDED = 0x0400, // 2^10 + LASER_TIMEOUT_IN_COUNTS = 0x0800, // 2^10 + IS_OEM = 0x1000 // 2^10 } public FeatureMask(ushort value = 0) @@ -40,6 +42,8 @@ public FeatureMask(ushort value = 0) disableBLEPower = 0 != (value & (ushort)Flags.DISABLE_BLE_POWER); disableLaserArmedIndication = 0 != (value & (ushort)Flags.DISABLE_LASER_ARMED_INDIC); interlockExcluded = 0 != (value & (ushort)Flags.INTERLOCK_EXCLUDED); + laserTimeoutInCounts = 0 != (value & (ushort)Flags.LASER_TIMEOUT_IN_COUNTS); + isOEM = 0 != (value & (ushort)Flags.IS_OEM); } public override string ToString() @@ -61,6 +65,8 @@ public ushort toUInt16() if (disableBLEPower) value |= (ushort)Flags.DISABLE_BLE_POWER; if (disableLaserArmedIndication) value |= (ushort)Flags.DISABLE_LASER_ARMED_INDIC; if (interlockExcluded) value |= (ushort)Flags.INTERLOCK_EXCLUDED; + if (laserTimeoutInCounts) value |= (ushort)Flags.INTERLOCK_EXCLUDED; + if (isOEM) value |= (ushort)Flags.IS_OEM; return value; } @@ -160,5 +166,37 @@ public bool invertXAxis public bool disableBLEPower { get; set; } public bool disableLaserArmedIndication { get; set; } public bool interlockExcluded { get; set; } + public bool laserTimeoutInCounts { get; set; } + public bool isOEM { get; set; } + } + public class FeatureMaskXS : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + enum Flags + { + BLE_DOOR_SENSOR = 0x00000001, // 2^0 + } + + public FeatureMaskXS(uint value = 0) + { + BLEDoorSensor = 0 != (value & (ushort)Flags.BLE_DOOR_SENSOR); + } + + public override string ToString() + { + return $"0x{toUInt32():X8}"; + } + + public uint toUInt32() + { + uint value = 0; + if (BLEDoorSensor) value |= (ushort)Flags.BLE_DOOR_SENSOR; + + return value; + } + + + public bool BLEDoorSensor { get; set; } } } diff --git a/WasatchNET/IOpcodes.cs b/WasatchNET/IOpcodes.cs index 0f6fc98..86b5adb 100644 --- a/WasatchNET/IOpcodes.cs +++ b/WasatchNET/IOpcodes.cs @@ -56,6 +56,7 @@ public enum Opcodes GET_LASER_MOD_PERIOD, GET_LASER_MOD_PULSE_DELAY, GET_LASER_MOD_PULSE_WIDTH, + GET_LASER_POWER_ATTENUATOR, GET_LASER_RAMPING_MODE, // not implemented GET_LASER_TEC_MODE, GET_LASER_TEC_SETPOINT, @@ -109,6 +110,7 @@ public enum Opcodes SET_LASER_MOD_PERIOD, SET_LASER_MOD_PULSE_DELAY, SET_LASER_MOD_PULSE_WIDTH, + SET_LASER_POWER_ATTENUATOR, SET_LASER_RAMPING_MODE, // not implemented SET_LASER_TEC_MODE, SET_LASER_TEC_SETPOINT, diff --git a/WasatchNET/ISpectrometer.cs b/WasatchNET/ISpectrometer.cs index e450d89..13d74d7 100644 --- a/WasatchNET/ISpectrometer.cs +++ b/WasatchNET/ISpectrometer.cs @@ -184,6 +184,7 @@ public interface ISpectrometer /// bool laserInterlockEnabled { get; } byte laserWarningDelaySec { get; set; } + byte laserPowerAttenuation { get; set; } bool laserModulationEnabled { get; set; } bool laserModulationLinkedToIntegrationTime { get; set; } diff --git a/WasatchNET/Logger.cs b/WasatchNET/Logger.cs index 282be08..1b2ccc2 100644 --- a/WasatchNET/Logger.cs +++ b/WasatchNET/Logger.cs @@ -214,21 +214,26 @@ public void save(string pathname) public void hexdump(byte[] buf, string prefix = "") { string line = ""; - for (int i = 0; i < buf.Length; i++) + if (buf != null) { - if (i % 16 == 0) + for (int i = 0; i < buf.Length; i++) { - if (i > 0) + if (i % 16 == 0) { - debug("{0}{1}", prefix, line); - line = ""; + if (i > 0) + { + debug("{0}{1}", prefix, line); + line = ""; + } + line += String.Format("{0:x4}:", i); } - line += String.Format("{0:x4}:", i); + line += String.Format(" {0:x2}", buf[i]); } - line += String.Format(" {0:x2}", buf[i]); + if (line.Length > 0) + debug("{0}{1}", prefix, line); } - if (line.Length > 0) - debug("{0}{1}", prefix, line); + else + debug("{0}{1}", prefix, "[ ]"); } //////////////////////////////////////////////////////////////////////// diff --git a/WasatchNET/MockSpectrometer.cs b/WasatchNET/MockSpectrometer.cs index e3adaa5..bfd569e 100644 --- a/WasatchNET/MockSpectrometer.cs +++ b/WasatchNET/MockSpectrometer.cs @@ -280,6 +280,7 @@ public override bool laserInterlockEnabled } } public override byte laserWarningDelaySec { get => 0; set { } } + public override byte laserPowerAttenuation { get => 0; set { } } public override UInt64 laserModulationPeriod { diff --git a/WasatchNET/Opcodes.cs b/WasatchNET/Opcodes.cs index 85874cc..fe80cd9 100644 --- a/WasatchNET/Opcodes.cs +++ b/WasatchNET/Opcodes.cs @@ -71,6 +71,7 @@ public HashSet getArmInvertedRetvals() cmd[Opcodes.GET_LASER_MOD_PERIOD ] = 0xcb; cmd[Opcodes.GET_LASER_MOD_PULSE_DELAY ] = 0xca; cmd[Opcodes.GET_LASER_MOD_PULSE_WIDTH ] = 0xdc; + cmd[Opcodes.GET_LASER_POWER_ATTENUATOR ] = 0x83; cmd[Opcodes.GET_LASER_TEC_SETPOINT ] = 0xe8; cmd[Opcodes.GET_LASER_TEC_MODE ] = 0x85; cmd[Opcodes.GET_LINK_LASER_MOD_TO_INTEGRATION_TIME ] = 0xde; @@ -105,6 +106,7 @@ public HashSet getArmInvertedRetvals() cmd[Opcodes.SET_LASER_MOD_PERIOD ] = 0xc7; cmd[Opcodes.SET_LASER_MOD_PULSE_DELAY ] = 0xc6; cmd[Opcodes.SET_LASER_MOD_PULSE_WIDTH ] = 0xdb; + cmd[Opcodes.SET_LASER_POWER_ATTENUATOR ] = 0x82; cmd[Opcodes.SET_LASER_TEC_SETPOINT ] = 0xe7; cmd[Opcodes.SET_LASER_TEC_MODE ] = 0x84; cmd[Opcodes.SET_LASER_WARNING_DELAY ] = 0x8a; @@ -176,6 +178,7 @@ public HashSet getArmInvertedRetvals() armInvertedRetvals.Add(Opcodes.SET_LASER_MOD_PULSE_WIDTH); armInvertedRetvals.Add(Opcodes.SET_LASER_TEC_SETPOINT); armInvertedRetvals.Add(Opcodes.SET_LASER_MOD_PERIOD); + //armInvertedRetvals.Add(Opcodes.SET_LASER_POWER_ATTENUATOR); armInvertedRetvals.Add(Opcodes.SET_LASER_MOD_PULSE_DELAY); armInvertedRetvals.Add(Opcodes.SET_CONTINUOUS_ACQUISITION); armInvertedRetvals.Add(Opcodes.SET_CONTINUOUS_FRAMES); diff --git a/WasatchNET/Properties/AssemblyInfo.cs b/WasatchNET/Properties/AssemblyInfo.cs index 96c8f12..9c2946d 100644 --- a/WasatchNET/Properties/AssemblyInfo.cs +++ b/WasatchNET/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Wasatch Photonics")] [assembly: AssemblyProduct("Wasatch.NET")] -[assembly: AssemblyCopyright("Copyright © 2025, Wasatch Photonics")] +[assembly: AssemblyCopyright("Copyright © 2026, Wasatch Photonics")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.5.18.0")] -[assembly: AssemblyFileVersion("2.5.18.0")] +[assembly: AssemblyVersion("2.5.19.0")] +[assembly: AssemblyFileVersion("2.5.19.0")] diff --git a/WasatchNET/SPISpectrometer.cs b/WasatchNET/SPISpectrometer.cs index a745d16..70ee706 100644 --- a/WasatchNET/SPISpectrometer.cs +++ b/WasatchNET/SPISpectrometer.cs @@ -875,6 +875,7 @@ public override LaserPowerResolution laserPowerResolution public override bool laserInterlockEnabled { get => false; } public override byte laserWarningDelaySec { get => 0; set { } } + public override byte laserPowerAttenuation { get => 0; set { } } public override UInt64 laserModulationPeriod { get => 100; } public override UInt64 laserModulationPulseWidth { get => 0; } diff --git a/WasatchNET/Spectrometer.cs b/WasatchNET/Spectrometer.cs index 84e2f64..bc8351f 100644 --- a/WasatchNET/Spectrometer.cs +++ b/WasatchNET/Spectrometer.cs @@ -91,6 +91,11 @@ public enum IMAGE_SENSOR_STATUS // consistent with Wasatch.PY UsbEndpointReader spectralReader82; UsbEndpointReader spectralReader86; + + + // endpoint 0 reader to flush when needed + UsbEndpointReader spectralReader0; + bool usingDualEndpoints; internal Dictionary cmd = OpcodeHelper.getInstance().getDict(); @@ -1387,6 +1392,18 @@ public virtual byte laserWarningDelaySec } byte _laserWarningDelaySec; + public virtual byte laserPowerAttenuation + { + get => _laserPowerAttenuation; + set + { + byte[] temp = new byte[1]; + sendCmdReturn(Opcodes.SET_LASER_POWER_ATTENUATOR, (byte)value, buf: temp); + _laserPowerAttenuation = value; + } + } + byte _laserPowerAttenuation; + public bool laserModulationLinkedToIntegrationTime { get @@ -2618,11 +2635,13 @@ public virtual bool reconnect() logger.debug("Spectrometer.reconnect: creating readers"); spectralReader82 = usbDevice.OpenEndpointReader(ReadEndpointID.Ep02); spectralReader86 = usbDevice.OpenEndpointReader(ReadEndpointID.Ep06); + //spectralReader0 = usbDevice.OpenEndpointReader(ReadEndpointID.) logger.debug("Spectrometer.reconnect: done"); return true; } + // TODO: refactor this into Bus, UsbBus etc /// @@ -2669,6 +2688,7 @@ internal byte[] getCmd(Opcodes opcode, int len, ushort wIndex = 0, int fullLen = { logger.error("getCmd: failed to get {0} (0x{1:x4}) via DEVICE_TO_HOST ({2} of {3} bytes read, expected {4} got {5})", opcode.ToString(), cmd[opcode], bytesRead, len, expectedSuccessResult, result); + logger.hexdump(buf, String.Format("failed getCmd: {0} (0x{1:x2}) index 0x{2:x4} returned -> ", opcode.ToString(), cmd[opcode], wIndex)); return null; } } @@ -2765,13 +2785,13 @@ internal byte[] getCmd2(Opcodes opcode, int len, ushort wIndex = 0, int fakeBuff { logger.error("getCmd2: failed to get SECOND_TIER_COMMAND {0} (0x{1:x4}) via DEVICE_TO_HOST ({2} of {3} bytes read, expected {4} got {5})", opcode.ToString(), cmd[opcode], bytesRead, len, expectedSuccessResult, result); - logger.hexdump(buf, $"{opcode} result"); + logger.hexdump(buf, $"{opcode} failed read result -> "); return null; } } if (logger.debugEnabled()) - logger.hexdump(buf, String.Format("getCmd2: {0} (0x{1:x2}) index 0x{2:x4} (result {3}, expected {4}) ->", + logger.hexdump(buf, String.Format("getCmd2: {0} (0x{1:x2}) index 0x{2:x4} (result {3}, expected {4}) -> ", opcode.ToString(), cmd[opcode], wIndex, result, expectedSuccessResult)); // extract just the bytes we really needed @@ -2813,19 +2833,72 @@ internal async Task getCmd2Async(Opcodes opcode, int len, ushort wIndex { logger.error("getCmd2: failed to get SECOND_TIER_COMMAND {0} (0x{1:x4}) via DEVICE_TO_HOST ({2} of {3} bytes read, expected {4} got {5})", opcode.ToString(), cmd[opcode], bytesRead, len, expectedSuccessResult, result); - logger.hexdump(buf, $"{opcode} result"); + logger.hexdump(buf, $"{opcode} result "); return null; } if (logger.debugEnabled()) - logger.hexdump(buf, String.Format("getCmd2: {0} (0x{1:x2}) index 0x{2:x4} (result {3}, expected {4}) ->", + logger.hexdump(buf, String.Format("getCmd2: {0} (0x{1:x2}) index 0x{2:x4} (result {3}, expected {4}) -> ", opcode.ToString(), cmd[opcode], wIndex, result, expectedSuccessResult)); // extract just the bytes we really needed return Util.truncateArray(buf, len); } + internal bool sendCmdReturn(Opcodes opcode, ushort wValue = 0, ushort wIndex = 0, byte[] buf = null) + { + if (shuttingDown) + return false; + + if ((isARM || isStroker) && (buf is null)) + buf = new byte[8]; + + ushort wLength = (ushort)((buf is null) ? 0 : buf.Length); + + UsbSetupPacket packet = new UsbSetupPacket( + DEVICE_TO_HOST, // bRequestType + cmd[opcode], // bRequest + wValue, // wValue + wIndex, // wIndex + wLength); // wLength + + bool? expectedSuccessResult = true; + if (isARM) + { + if (opcode != Opcodes.SECOND_TIER_COMMAND) + expectedSuccessResult = armInvertedRetvals.Contains(opcode); + else + expectedSuccessResult = null; // no easy way to know, as we don't pass wValue as enum (MZ: whut?) + } + + lock (commsLock) + { + // don't enforce USB delay on laser commands...that could be dangerous + // or on acquire commands, which would disrupt integration throwaways + // and soft synchronization + if (opcode != Opcodes.SET_LASER_ENABLE && opcode != Opcodes.ACQUIRE_SPECTRUM) + waitForUsbAvailable(); + + logger.debug("sendCmd: about to send {0} ({1}) ({2})", opcode, stringifyPacket(packet), id); + + bool result = usbDevice.ControlTransfer(ref packet, buf, wLength, out int bytesWritten); + + if (expectedSuccessResult != null && expectedSuccessResult.Value != result) + { + logger.error("sendCmd: failed to send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) (received {5}, expected {6})", + opcode.ToString(), cmd[opcode], wValue, wIndex, wLength, result, expectedSuccessResult); + logger.hexdump(buf, "sendCmd return bytes: "); + return false; + } + else + { + logger.hexdump(buf, String.Format("Send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) return bytes: ", opcode.ToString(), cmd[opcode], wValue, wIndex, wLength)); + } + } + return true; + } + /// /// send a single control transfer command (response not checked) /// @@ -2878,8 +2951,13 @@ internal bool sendCmd(Opcodes opcode, ushort wValue = 0, ushort wIndex = 0, byte { logger.error("sendCmd: failed to send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) (received {5}, expected {6})", opcode.ToString(), cmd[opcode], wValue, wIndex, wLength, result, expectedSuccessResult); + logger.hexdump(buf, "sendCmd return bytes: "); return false; } + else + { + logger.hexdump(buf, String.Format("Send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) return bytes: ", opcode.ToString(), cmd[opcode], wValue, wIndex, wLength)); + } } return true; } @@ -2923,9 +3001,14 @@ internal async Task sendCmdAsync(Opcodes opcode, ushort wValue = 0, ushort { logger.error("sendCmd: failed to send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) (received {5}, expected {6})", opcode.ToString(), cmd[opcode], wValue, wIndex, wLength, result, expectedSuccessResult); + logger.hexdump(buf, "sendCmd return bytes: "); return false; } - + else + { + logger.hexdump(buf, String.Format("Send {0} (0x{1:x2}) (wValue 0x{2:x4}, wIndex 0x{3:x4}, wLength 0x{4:x4}) return bytes: ", opcode.ToString(), cmd[opcode], wValue, wIndex, wLength)); + } + return true; } @@ -2958,7 +3041,12 @@ internal bool sendCmd2(Opcodes opcode, ushort wIndex = 0, byte[] buf = null) { waitForUsbAvailable(); logger.debug("sendCmd2: about to send {0} ({1}) ({2})", opcode, stringifyPacket(packet), id); - return usbDevice.ControlTransfer(ref packet, buf, wLength, out int bytesWritten); + + bool ok = usbDevice.ControlTransfer(ref packet, buf, wLength, out int bytesWritten); + + logger.hexdump(buf, String.Format("sendCmd2 {0} ({1}) return bytes: ", opcode, id)); + + return ok; } } internal async Task sendCmd2Async(Opcodes opcode, ushort wIndex = 0, byte[] buf = null) @@ -2983,8 +3071,11 @@ internal async Task sendCmd2Async(Opcodes opcode, ushort wIndex = 0, byte[ await Task.Run(() =>logger.debug("sendCmd2: about to send {0} ({1}) ({2})", opcode, stringifyPacket(packet), id)); int bytesWritten; - return await Task.Run(() => usbDevice.ControlTransfer(ref packet, buf, wLength, out bytesWritten)); - + + bool ok = await Task.Run(() => usbDevice.ControlTransfer(ref packet, buf, wLength, out bytesWritten)); + logger.hexdump(buf, String.Format("sendCmd2 {0} ({1}) return bytes: ", opcode, id)); + + return ok; } diff --git a/WasatchNET/TCPSpectrometer.cs b/WasatchNET/TCPSpectrometer.cs index 571275f..9f88a0f 100644 --- a/WasatchNET/TCPSpectrometer.cs +++ b/WasatchNET/TCPSpectrometer.cs @@ -503,6 +503,7 @@ public override LaserPowerResolution laserPowerResolution public override bool laserInterlockEnabled { get => false; } public override byte laserWarningDelaySec { get => 0; set { } } + public override byte laserPowerAttenuation { get => 0; set { } } public override UInt64 laserModulationPeriod { get => 100; } public override UInt64 laserModulationPulseWidth { get => 100; } diff --git a/lib/x64/WasatchNET.dll b/lib/x64/WasatchNET.dll index 8bc59b2..174a216 100644 Binary files a/lib/x64/WasatchNET.dll and b/lib/x64/WasatchNET.dll differ diff --git a/lib/x86/WasatchNET.dll b/lib/x86/WasatchNET.dll index fd60dc7..30c6810 100644 Binary files a/lib/x86/WasatchNET.dll and b/lib/x86/WasatchNET.dll differ