Adding QHY SDK Support to NINA

After doing some touch-ups to NINA’s integration with ZWO cameras to get familiar with coding in C# and how camera support in NINA is done, I felt ready to tackle implementing support for an entirely new camera. For this, I chose to implement native support for QHY cameras and started work on this over the Christmas holiday. This journey has turned out to be challenging in several ways. Major functionality such as making exposures, setting binning, maintaining cooling and such are present. However there are some issues which block the completion of this work and thus prevents including it in a general release of NINA.

First, the only QHY camera I possess is a PoleMaster. The PoleMaster takes the MT9M034 sensor of the QHY5L-II and mates it with a focusable CCTV lens inside a custom housing that you secure to your mount’s RA axis. It has a very specific purpose of assisting in the accurate polar alignment of a mount and nothing beyond that; therefore the firmware it is loaded with is very role-specific in order to work with the PoleMaster software. All this means is that support for this camera in QHY’s SDK seems quite minimal. So it’s not a very good example camera to use to implement and test support for QHY cameras, but it is suitable for the most basic, functional verification of working code. For testing things such as color cameras and cooler control and perhaps other advanced features, I need to find volunteers who have such cameras to contact the Dev team on the NINA Discord. If you wish, you will be given credit for your assistance.

Second, there are issues with SDK itself. Some of the issues are general bugs that have been identified and need to be fixed. There is also a large problem with using the 32bit version of the SDK DLL with C# programs, such as NINA. It is a combination of these issues which currently blocks the completion of this integration work, and I will detail each issue below.

Issue 1: 32bit DLL exports wrong symbols

We deliver each release of NINA in both 32bit (“x86”) and 64bit (“x64”) forms. Therefore with every SDK we integrate with, we need both 32bit and 64bit flavors of their libraries. With the QHY SDK, the 32bit DLL exports all of its public symbols with an underscore prepended to the function names. For example, ScanQHYCCD() is exported as _ScanQHYCCD(). This is not an issue with C++ programs because QHY also supplies an exports translation file (qhyccd.exp). The C++ linker uses this file to translate the normal symbol names to the underscored symbols. But with C# programs, there is no such facility. In C#, a DLL is used in an “unmanaged” mode where the DLL is loaded and the symbol table is directly accessed by the C# runtime. There is no opportunity for symbol translation here. This means if you look for ScanQHYCCD(), it’s not available, as it has a different name of _ScanQHYCCD(). The 64bit DLL does not exhibit this problem. Other camera SDKs that NINA uses also do not exhibit this issue. Here is a screenshot of Dependency Walker which shows the 32bit DLL’s symbol table:

There are ways around this in the C# code by using C#’s imitation of C Pre-Processor style #if/#define or by using wrapper functions. But these make the code unnecessarily cluttered and awkward. The real solution is to build the 32bit DLL with a normal exported symbol table, as is already done with the 64bit version.

Issue 2: 32bit DLL is still linked against pssdk.dll

I am using the latest release posted to the QHY website, version 20180502_0. The release notes specify one change in this release:

2. Removed dependence of pssdk.dll,but it still contain tbb.dll and ftd2xx.dll

However the 32bit DLL appears to still have the want to link against pssdk.dll:

The 64bit DLL appears to be correct and does not link against pssdk.dll.

Issue 3: Cooled CMOS cooler ambiguity

To manage cooling, we allow the user to set a specific temperature to cool the sensor to. We also allow the user to turn the cooler off. To do this with QHY cameras, the documentation says we must set the CONTROL_MANULPWM control to 0. When users tested this with QHY183C and QHY163M cameras, the cooling does appear to turn off because the sensor temperature increases, however the SDK reports that CONTROL_CURPWM is 255, which is supposed to mean that the cooler is at full power. Obviously that is not the case and it confuses the both the cooling management logic and the user. Any subsequent attempts to set a target temperature seems to sometimes work and sometimes it does not (with the PWM stuck on reporting 255).

The following issues are not blockers as I am able to work around them. However they should eventually be addressed and fixed in the SDK or at least be documented behaviors.

Issue 4: QHY163M (and maybe others) return unexpected values for gain.

A user testing with a QHY163M reported strange behavior with the Gain slider in NINA. When setting camera Gain, NINA issues a command to the camera SDK to set the gain to the desired value, and then reads the Gain setting back from the camera to verify. With the QHY163M, the user would set the gain to a valid number in the range of 0 to 580, however the camera would report back that value, but divided by 10. A requested Gain setting of 123 would mean the SDK reports the gain to be 12.3. I have implemented code which detects this and works around it so that the proper values may be displayed in the UI.

Issue 5: QHY163M (and maybe others) do not use CONTROL_USBTRAFFIC

The QHY163M will claim that CONTROL_USBTRAFFIC is a valid control, however it will silently ignore any changes made to it. Detection of this so that we can disable the UI control for USB Traffic is here.

Issue 6: PoleMaster CONTROL_OFFSET is +50

The PoleMaster will always report its Offset value to always be 50 points higher than what was requested. Setting an Offset of 1 will result in the Offset being reported as 51 and so-on. I detect and work around that here.