/// <summary> /// This generates a Response of a given Type with a custom message and Error Code /// </summary> /// <typeparam name="T">The Type of Response to create and fill</typeparam> /// <param name="code">The Alpaca Error Code</param> /// <param name="message">The message to use</param> /// <param name="clientTransactionID">The Client Transaction ID</param> /// <param name="serverTransactionID">The Server Transaction ID</param> /// <returns></returns> public static T ExceptionResponseBuilder <T>(AlpacaErrors code, string message, uint clientTransactionID = 0, uint serverTransactionID = 0) where T : Response, new() { return(new T() { ClientTransactionID = clientTransactionID, ServerTransactionID = serverTransactionID, ErrorNumber = code, ErrorMessage = message }); }
/// <summary> /// Initialise the ArrayMetadataV1 structure /// </summary> /// <param name="errorNumber">Transaction error number.</param> /// <param name="clientTransactionID">Client's transaction ID</param> /// <param name="serverTransactionID">Device's transaction ID</param> /// <param name="imageElementType">Intended element type of the resultant array.</param> /// <param name="transmissionElementType">Element type actually sent.</param> /// <param name="arrayRank">Rank of the arrayresultant array.</param> /// <param name="arrayDimension1">Size of the first dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> /// <param name="arrayDimension1">Size of the second dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> /// <param name="arrayDimension2">Size of the third dimension of the resultant array (array[Dimension1, Dimension2, Dimension3]).</param> public ArrayMetadataV1(AlpacaErrors errorNumber, uint clientTransactionID, uint serverTransactionID, ImageArrayElementTypes imageElementType, ImageArrayElementTypes transmissionElementType, int arrayRank, int arrayDimension1, int arrayDimension2, int arrayDimension3) { MetadataVersion = 1; this.ErrorNumber = errorNumber; this.ClientTransactionID = clientTransactionID; this.ServerTransactionID = serverTransactionID; DataStart = AlpacaTools.ARRAY_METADATAV1_LENGTH; this.ImageElementType = imageElementType; this.TransmissionElementType = transmissionElementType; this.Rank = arrayRank; this.Dimension1 = arrayDimension1; this.Dimension2 = arrayDimension2; this.Dimension3 = arrayDimension3; }
/// <summary> /// Gets a driver exception from an error code and adds the message if available /// </summary> /// <param name="code">The Alpaca ErrorCode for the Exception</param> /// <param name="message">The optional message of the exception</param> /// <returns>Null if there is no exception, otherwise the ASCOM Exception for the error code</returns> public static DriverException ExceptionFromErrorCode(AlpacaErrors code, string message = "") { switch (code) { case AlpacaErrors.InvalidValue: return(new InvalidValueException(message)); case AlpacaErrors.ValueNotSet: return(new ValueNotSetException(message)); case AlpacaErrors.NotConnected: return(new NotConnectedException(message)); case AlpacaErrors.InvalidWhileParked: return(new ParkedException(message)); case AlpacaErrors.InvalidWhileSlaved: return(new SlavedException(message)); case AlpacaErrors.InvalidOperationException: return(new InvalidOperationException(message)); case AlpacaErrors.UnspecifiedError: return(new DriverException(message)); case AlpacaErrors.NotImplemented: return(new NotImplementedException(message)); case AlpacaErrors.ActionNotImplementedException: return(new ActionNotImplementedException(message)); case AlpacaErrors.AlpacaNoError: //No Error default: return(null); } }
/// <summary> /// Create a new ShutterStateResponse with the supplied parameter values /// </summary> /// <param name="clientTransactionID">Client transaction ID</param> /// <param name="serverTransactionID">Server transaction ID</param> /// <param name="errorMessage">Value to return</param> /// <param name="errorCode">Server transaction ID</param> public ShutterStateResponse(uint clientTransactionID, uint serverTransactionID, string errorMessage, AlpacaErrors errorCode) { base.ServerTransactionID = serverTransactionID; base.ClientTransactionID = clientTransactionID; base.ErrorMessage = errorMessage; base.ErrorNumber = errorCode; }
/// <summary> /// Create a new EquatorialCoordinateTypeResponse with the supplied parameter values /// </summary> /// <param name="clientTransactionID">Client transaction ID</param> /// <param name="serverTransactionID">Server transaction ID</param> /// <param name="errorMessage">Value to return</param> /// <param name="errorCode">Server transaction ID</param> public EquatorialCoordinateTypeResponse(uint clientTransactionID, uint serverTransactionID, string errorMessage, AlpacaErrors errorCode) { base.ServerTransactionID = serverTransactionID; base.ClientTransactionID = clientTransactionID; base.ErrorMessage = errorMessage; base.ErrorNumber = errorCode; }
/// <summary> /// Convert an Alpaca error number and message to a byte array for transfer to a client. /// </summary> /// <param name="metadataVersion">Required metadata version - Currently 1</param> /// <param name="alpacaErrorNumber">Alpaca error number</param> /// <param name="errorMessage">Error message to encode.</param> /// <returns></returns> /// <exception cref="InvalidValueException"></exception> public static byte[] ErrorMessageToByteArray(int metadataVersion, uint clientTransactionID, uint serverTransactionID, AlpacaErrors alpacaErrorNumber, string errorMessage) { // Validate supplied parameters if (metadataVersion != 1) { throw new InvalidValueException($"ErrorMessageToByteArray - Unsupported metadata version: {metadataVersion}."); } if (alpacaErrorNumber == AlpacaErrors.AlpacaNoError) { throw new InvalidValueException($"ErrorMessageToByteArray - Supplied error number is {alpacaErrorNumber}, this indicates 'Success' rather than an 'Error'."); } if ((alpacaErrorNumber < AlpacaErrors.AlpacaNoError) | (alpacaErrorNumber > AlpacaErrors.DriverMax)) { throw new InvalidValueException($"ErrorMessageToByteArray - Invalid Alpaca error number: {alpacaErrorNumber}."); } if (string.IsNullOrEmpty(errorMessage)) { throw new InvalidValueException($"ErrorMessageToByteArray - Error message is either null or an empty string."); } switch (metadataVersion) { case 1: // Create a metadata structure containing the supplied error number ArrayMetadataV1 arrayMetadataV1 = new ArrayMetadataV1(alpacaErrorNumber, clientTransactionID, serverTransactionID, ImageArrayElementTypes.Unknown, ImageArrayElementTypes.Unknown, 0, 0, 0, 0); // Create a byte array from the metadata structure byte[] arrayMetadataV1Bytes = arrayMetadataV1.ToByteArray <ArrayMetadataV1>(); // Create a byte array containing the UTF8 encoded string byte[] errorMessagebytes = Encoding.UTF8.GetBytes(errorMessage); // Create a return byte array that is large enough to contain both the metadata bytes and the UTF8 encoded message bytes byte[] returnByteArray = new byte[ARRAY_METADATAV1_LENGTH + errorMessagebytes.Length]; // Copy the metadata bytes to the start of the return array Array.Copy(arrayMetadataV1Bytes, returnByteArray, arrayMetadataV1Bytes.Length); // Copy the error message bytes after the metadata bytes Array.Copy(errorMessagebytes, 0, returnByteArray, ARRAY_METADATAV1_LENGTH, errorMessagebytes.Length); // Return the composite byte array return(returnByteArray); default: throw new InvalidValueException($"ErrorMessageToByteArray - Unsupported metadata version: {metadataVersion}"); } }
/// <summary> /// Alpaca Extension - Convert the array or error message to a byte array for transmission to a client /// </summary> /// <param name="imageArray">The 2D or 3D source image array. (Ignored when returning an error.)</param> /// <param name="metadataVersion">Metadata version to use (Currently 1).</param> /// <param name="clientTransactionID">Client's transaction ID.</param> /// <param name="serverTransactionID">Device's transaction ID.</param> /// <param name="errorNumber">Error number. 0 for success, non-zero for an error.</param> /// <param name="errorMessage">Error message. Empty string for success, error message for an error.</param> /// <returns>Byte array prefixed with array metadata.</returns> /// <exception cref="InvalidValueException">If only one of the error number and error message indicates an error.</exception> /// <exception cref="InvalidValueException">Image array is null for a successful transaction or the array rank is <2 or >3 or the array is of type object</exception> /// <exception cref="InvalidValueException">The array element type is not supported.</exception> public static byte[] ToByteArray(this Array imageArray, int metadataVersion, uint clientTransactionID, uint serverTransactionID, AlpacaErrors errorNumber, string errorMessage) { int transmissionElementSize; // Managed size of transmitted elements // Handle error conditions if ((errorNumber != 0) | (!string.IsNullOrWhiteSpace(errorMessage))) { // Validate error parameters if ((errorNumber == 0) & (!string.IsNullOrWhiteSpace(errorMessage))) { throw new InvalidValueException($"ToByteArray - Error number is {errorNumber} but an error message has been supplied: '{errorMessage}'"); } if ((errorNumber != 0) & (string.IsNullOrWhiteSpace(errorMessage))) { throw new InvalidValueException($"ToByteArray - Error number is {errorNumber} but no error message has been supplied: '{errorMessage}'"); } // Handle metadata versions switch (metadataVersion) { case 1: byte[] errorMessageBytes = Encoding.UTF8.GetBytes(errorMessage); ArrayMetadataV1 metadataVersion1 = new ArrayMetadataV1(); metadataVersion1.ErrorNumber = errorNumber; byte[] metadataBytes = metadataVersion1.ToByteArray(); byte[] returnBytes = new byte[metadataBytes.Length + errorMessageBytes.Length]; Array.Copy(metadataBytes, returnBytes, metadataBytes.Length); Array.Copy(errorMessageBytes, 0, returnBytes, metadataBytes.Length, errorMessageBytes.Length); return(returnBytes); default: throw new InvalidValueException($"Unsupported metadata version: {metadataVersion}"); } } // At this point we have a successful transaction so validate the incoming array if (imageArray is null) { throw new InvalidValueException("ToByteArray - Supplied array is null."); } if ((imageArray.Rank < 2) | (imageArray.Rank > 3)) { throw new InvalidValueException($"ToByteArray - Only arrays of rank 2 and 3 are supported. The supplied array has a rank of {imageArray.Rank}."); } // We can't handle object arrays so test for this string arrayTypeName = imageArray.GetType().Name; if (arrayTypeName.ToLowerInvariant().Contains("object")) { throw new InvalidValueException($"ToByteArray - Object arrays of type {arrayTypeName} are not supported."); } // Set the array type ImageArrayElementTypes intendedElementType = ImageArrayElementTypes.Unknown; ImageArrayElementTypes transmissionElementType = ImageArrayElementTypes.Unknown; // Get the type code of the array elements TypeCode arrayElementTypeCode = Type.GetTypeCode(imageArray.GetType().GetElementType()); // Set the element type of the intended array and default the transmission element type to be the same as the intended type switch (arrayElementTypeCode) { case TypeCode.Byte: intendedElementType = ImageArrayElementTypes.Byte; transmissionElementType = ImageArrayElementTypes.Byte; transmissionElementSize = 1; break; case TypeCode.Int16: intendedElementType = ImageArrayElementTypes.Int16; transmissionElementType = ImageArrayElementTypes.Int16; transmissionElementSize = 2; break; case TypeCode.Int32: intendedElementType = ImageArrayElementTypes.Int32; transmissionElementType = ImageArrayElementTypes.Int32; transmissionElementSize = 4; break; case TypeCode.Int64: intendedElementType = ImageArrayElementTypes.Int64; transmissionElementType = ImageArrayElementTypes.Int64; transmissionElementSize = 8; break; case TypeCode.Single: intendedElementType = ImageArrayElementTypes.Single; transmissionElementType = ImageArrayElementTypes.Single; transmissionElementSize = 4; break; case TypeCode.Double: intendedElementType = ImageArrayElementTypes.Double; transmissionElementType = ImageArrayElementTypes.Double; transmissionElementSize = 8; break; case TypeCode.Decimal: intendedElementType = ImageArrayElementTypes.Decimal; transmissionElementType = ImageArrayElementTypes.Decimal; transmissionElementSize = 16; break; default: throw new InvalidValueException($"ToByteArray - Received an unsupported return array type: {imageArray.GetType().Name}, with elements of type: {imageArray.GetType().GetElementType().Name}"); } // Special handling for Int32 arrays - see if we can convert them to Int16 or UInt16 arrays if (arrayElementTypeCode == TypeCode.Int32) { // NOTE // NOTE - This algorithm uses a UInt16 array to transmit an array with Int16 values because we are only interested in the byte values for this purpose, // NOTE - not the arithmetic interpretation of those bytes. // NOTE bool arrayIsInt16 = true; // Flag indicating whether the supplied array conforms to the Int16 value range -32768 to +32767. Start by assuming success bool arrayIsUint16 = true; // Flag indicating whether the supplied array conforms to the UInt16 value range 0 to +65535. Start by assuming success // Handle 2D and 3D arrays switch (imageArray.Rank) { case 2: UInt16[,] uInt16Array = new UInt16[imageArray.GetLength(0), imageArray.GetLength(1)]; // Array to hold the 16bit transmission array (either Int16 or UInt16 values) // Get the device's Int32 image array int[,] int2dArray = (int[, ])imageArray; // Parellelise the array copy to improve performance Parallel.For(0, imageArray.GetLength(0), (i) => // Iterate over the slowest changing dimension { Int32 int32ElementValue; // Local variable to hold the Int32 element being tested (saves calculating an array offset later in the process) Int16 int16ElementValue; // Local variable to hold the Int16 element value (saves calculating an array offset later in the process) UInt16 uInt16ElementValue; // Local variable to hold the Unt16 element value (saves calculating an array offset later in the process) bool arrayIsInt16Internal = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable. bool arrayIsUint16Internal = true; // Local variable to hold the UInt16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable. // Iterate over the fastest changing dimension for (int j = 0; j < imageArray.GetLength(1); j++) { // Get the current array element value int32ElementValue = int2dArray[i, j]; // Truncate the supplied 4-byte Int32 value to create a 2-byte UInt16 value uInt16ElementValue = (UInt16)int32ElementValue; // Truncate the supplied 4-byte Int32 value to create a 2-byte Int16 value int16ElementValue = (Int16)int32ElementValue; // Store the UInt16 value in the array. uInt16Array[i, j] = uInt16ElementValue; // Compare the Int16 and Int32 values. if (int16ElementValue != int32ElementValue) { arrayIsInt16Internal = false; } // Compare the UInt16 and Int32 values. if (uInt16ElementValue != int32ElementValue) { arrayIsUint16Internal = false; } } // Update the master arrayIsInt16 and arrayIsUint16 variables as the logical AND of the mater and update values. arrayIsInt16 &= arrayIsInt16Internal; arrayIsUint16 &= arrayIsUint16Internal; }); // Return the appropriate array if either a UInt16 or Int16 array was provided by the device if (arrayIsUint16) // Supplied array has UInt16 values so return the shorter array in place of the supplied Int32 array { imageArray = uInt16Array; // Assign the Int16 array to the imageArray variable in place of the Int32 array transmissionElementType = ImageArrayElementTypes.UInt16; // Flag that the array elements are UInt16 transmissionElementSize = sizeof(UInt16); // Indicate that the transmitted elements are of UInt16 size rather than Int32 size } else if (arrayIsInt16) // Supplied array has Int16 values so return the shorter array in place of the supplied Int32 array { imageArray = uInt16Array; // Assign the UInt16 array to the imageArray variable in place of the Int32 array (at the byte level Int32 and UInt32 are equivalent - both consist of two bytes) transmissionElementType = ImageArrayElementTypes.Int16; // Flag that the array elements are Int16 transmissionElementSize = sizeof(Int16); // Indicate that the transmitted elements are of UInt16 size rather than Int32 size } else { // No action, continue to use the supplied Int32 array because its values fall outside the Uint16 and Int16 number ranges } break; case 3: UInt16[,,] uInt163dArray = new UInt16[imageArray.GetLength(0), imageArray.GetLength(1), imageArray.GetLength(2)]; // Array to hold the 16bit transmission array (either Int16 or UInt16 values) // Get the device's Int32 image array Int32[,,] int3dArray = (Int32[, , ])imageArray; // Parellelise the array copy to improve performance Parallel.For(0, imageArray.GetLength(0), (i) => // Iterate over the slowest changing dimension { bool arrayIsInt16Internal1 = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayisInt16 variable. bool arrayIsUint16Internal1 = true; // Local variable to hold the UInt16 status within this particular thread. Used to reduce thread conflict when updating the arrayisInt16 variable. // Iterate over the mid changing dimension for (int j = 0; j < imageArray.GetLength(1); j++) { Int32 int32ElementValue; // Local variable to hold the Int32 element being tested (saves calculating an array offset later in the process) Int16 int16ElementValue; // Local variable to hold the Int16 element value (saves calculating an array offset later in the process) UInt16 uInt16ElementValue; // Local variable to hold the UInt16 element value (saves calculating an array offset later in the process) bool arrayIsInt16Internal2 = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsInt16 variable. bool arrayIsUInt16Internal2 = true; // Local variable to hold the Int16 status within this particular thread. Used to reduce thread conflict when updating the arrayIsUInt16 variable. // Iterate over the fastest changing dimension for (int k = 0; k < imageArray.GetLength(2); k++) { // Get the current array element value int32ElementValue = int3dArray[i, j, k]; // Truncate the supplied 4-byte Int32 value to create a 2-byte UInt16 value uInt16ElementValue = (UInt16)int32ElementValue; // Truncate the supplied 4-byte Int32 value to create a 2-byte Int16 value int16ElementValue = (Int16)int32ElementValue; // Copy the Int32 value to the corresponding Int16 array element. uInt163dArray[i, j, k] = uInt16ElementValue; // Compare the Int16 and Int32 values. if (int16ElementValue != int32ElementValue) { arrayIsInt16Internal2 = false; // If they are not the same the Int32 value was outside the range of Int16 and arrayIsInt16Internal will be set false } // Compare the UInt16 and Int32 values. if (uInt16ElementValue != int32ElementValue) { arrayIsUInt16Internal2 = false; } } // Update the arrayIsInt16Internal1 and arrayIsUint16Internal1 variables as the logical AND of the mater and update values. arrayIsInt16Internal1 &= arrayIsInt16Internal2; arrayIsUint16Internal1 &= arrayIsUInt16Internal2; } // Update the master arrayIsInt16 and arrayIsUint16 variables as the logical AND of the mater and update values. arrayIsInt16 &= arrayIsInt16Internal1; arrayIsUint16 &= arrayIsUint16Internal1; }); // Return the appropriate array if either a UInt16 or Int16 array was provided by the device if (arrayIsUint16) // Supplied array has UInt16 values so return the shorter array in place of the supplied Int32 array { imageArray = uInt163dArray; // Assign the Int16 array to the imageArray variable in place of the Int32 array transmissionElementType = ImageArrayElementTypes.UInt16; // Flag that the array elements are UInt16 transmissionElementSize = sizeof(UInt16); // Indicate that the transmitted elements are of UInt16 size rather than Int32 size } else if (arrayIsInt16) // Supplied array has Int16 values so return the shorter array in place of the supplied Int32 array { imageArray = uInt163dArray; // Assign the UInt16 array to the imageArray variable in place of the Int32 array (at the byte level Int32 and UInt32 are equivalent - both consist of two bytes) transmissionElementType = ImageArrayElementTypes.Int16; // Flag that the array elements are Int16 transmissionElementSize = sizeof(Int16); // Indicate that the transmitted elements are of UInt16 size rather than Int32 size } else { // No action, continue to use the supplied Int32 array because its values fall outside the Uint16 and Int16 number ranges } break; default: throw new InvalidValueException($"ToByteArray - The camera returned an array of rank: {imageArray.Rank}, which is not supported."); } } switch (metadataVersion) { case 1: // Create a version 1 metadata structure ArrayMetadataV1 metadataVersion1; if (imageArray.Rank == 2) { metadataVersion1 = new ArrayMetadataV1(AlpacaErrors.AlpacaNoError, clientTransactionID, serverTransactionID, intendedElementType, transmissionElementType, 2, imageArray.GetLength(0), imageArray.GetLength(1), 0); } else { metadataVersion1 = new ArrayMetadataV1(AlpacaErrors.AlpacaNoError, clientTransactionID, serverTransactionID, intendedElementType, transmissionElementType, 3, imageArray.GetLength(0), imageArray.GetLength(1), imageArray.GetLength(2)); } // Turn the metadata structure into a byte array byte[] metadataVersion2Bytes = metadataVersion1.ToByteArray <ArrayMetadataV1>(); // Create a return array of size equal to the sum of the metadata and image array lengths byte[] imageArrayBytesV2 = new byte[imageArray.Length * transmissionElementSize + metadataVersion2Bytes.Length]; // Size the image array bytes as the product of the transmission element size and the number of elements // Copy the metadata bytes to the start of the return byte array Array.Copy(metadataVersion2Bytes, imageArrayBytesV2, metadataVersion2Bytes.Length); // Copy the image array bytes after the metadata Buffer.BlockCopy(imageArray, 0, imageArrayBytesV2, metadataVersion2Bytes.Length, imageArray.Length * transmissionElementSize); // Return the byte array return(imageArrayBytesV2); default: throw new InvalidValueException($"ToByteArray - Unsupported metadata version: {metadataVersion}"); } }
/// <summary> /// Create a new AlpacaConfiguredDevicesResponse with the supplied parameter values /// </summary> /// <param name="clientTransactionID">Client transaction ID</param> /// <param name="serverTransactionID">Server transaction ID</param> /// <param name="errorMessage">Value to return</param> /// <param name="errorCode">Server transaction ID</param> public AlpacaConfiguredDevicesResponse(uint clientTransactionID, uint serverTransactionID, string errorMessage, AlpacaErrors errorCode) { base.ServerTransactionID = serverTransactionID; base.ClientTransactionID = clientTransactionID; base.ErrorMessage = errorMessage; base.ErrorNumber = errorCode; }